summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorȘtefan Talpalaru <stefantalpalaru@yahoo.com>2018-10-11 22:15:17 +0200
committerAndreas Rumpf <rumpf_a@web.de>2018-10-11 22:15:17 +0200
commit10f5f6776799af43ae34501e7aa47fb7fe52dd20 (patch)
tree57b817a1d29c5e47c886cc0db14e5e4872a90bbf
parentd48e964950d92a57c24770d40ef0f05ddfe51b7e (diff)
downloadNim-10f5f6776799af43ae34501e7aa47fb7fe52dd20.tar.gz
gogc: GCC-8.2.0 compatibility and other improvements (#9211)
- Go's write barriers are now plugged-in in all the relevant points
- "gcGo" is correctly classified by usesWriteBarrier()
- some gogc structures and functions now use golib wrappers to keep GCC
  version-specific conditions out of the compiler/stdlib code
- we no longer allow mixing the C malloc with Go's
- fix a problem with string copying
-rw-r--r--compiler/ccgexprs.nim27
-rw-r--r--compiler/cgen.nim8
-rw-r--r--compiler/options.nim6
-rw-r--r--lib/system/mmdisp.nim160
-rw-r--r--lib/system/sysstr.nim2
5 files changed, 84 insertions, 119 deletions
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 54e805a44..d73dac82f 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -169,7 +169,7 @@ proc canMove(p: BProc, n: PNode): bool =
   #  result = false
 
 proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
-  if dest.storage == OnStack or not usesWriteBarrier(p.config):
+  if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
     linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
   elif dest.storage == OnHeap:
     # location is on heap
@@ -265,7 +265,7 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
         rdLoc(dest), rdLoc(src))
   elif needToCopy notin flags or
       tfShallow in skipTypes(dest.t, abstractVarRange).flags:
-    if dest.storage == OnStack or not usesWriteBarrier(p.config):
+    if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
       linefmt(p, cpsStmts,
            "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
            addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest))
@@ -302,7 +302,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
     elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode):
       genRefAssign(p, dest, src, flags)
     else:
-      if dest.storage == OnStack or not usesWriteBarrier(p.config):
+      if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
         linefmt(p, cpsStmts, "$1 = #copyString($2);$n", dest.rdLoc, src.rdLoc)
       elif dest.storage == OnHeap:
         # we use a temporary to care for the dreaded self assignment:
@@ -1193,13 +1193,19 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) =
 
   let args = [getTypeDesc(p.module, typ), ti, sizeExpr]
   if a.storage == OnHeap and usesWriteBarrier(p.config):
-    # use newObjRC1 as an optimization
     if canFormAcycle(a.t):
       linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", a.rdLoc)
     else:
       linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", a.rdLoc)
-    b.r = ropecg(p.module, "($1) #newObjRC1($2, $3)", args)
-    linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc)
+    if p.config.selectedGC == gcGo:
+      # newObjRC1() would clash with unsureAsgnRef() - which is used by gcGo to
+      # implement the write barrier
+      b.r = ropecg(p.module, "($1) #newObj($2, $3)", args)
+      linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n", addrLoc(p.config, a), b.rdLoc)
+    else:
+      # use newObjRC1 as an optimization
+      b.r = ropecg(p.module, "($1) #newObjRC1($2, $3)", args)
+      linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc)
   else:
     b.r = ropecg(p.module, "($1) #newObj($2, $3)", args)
     genAssignment(p, a, b, {})  # set the object type:
@@ -1229,8 +1235,13 @@ proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) =
     else:
       linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", dest.rdLoc)
     if not lenIsZero:
-      call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args)
-      linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc)
+      if p.config.selectedGC == gcGo:
+        # we need the write barrier
+        call.r = ropecg(p.module, "($1) #newSeq($2, $3)", args)
+        linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n", addrLoc(p.config, dest), call.rdLoc)
+      else:
+        call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args)
+        linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc)
   else:
     if lenIsZero:
       call.r = rope"NIM_NIL"
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 37b07d38d..86aa7fb45 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -702,8 +702,12 @@ proc closureSetup(p: BProc, prc: PSym) =
   #echo "created environment: ", env.id, " for ", prc.name.s
   assignLocalVar(p, ls)
   # generate cast assignment:
-  linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n",
-          rdLoc(env.loc), getTypeDesc(p.module, env.typ))
+  if p.config.selectedGC == gcGo:
+    linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, ($2) ClE_0);$n",
+            addrLoc(p.config, env.loc), getTypeDesc(p.module, env.typ))
+  else:
+    linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n",
+            rdLoc(env.loc), getTypeDesc(p.module, env.typ))
 
 proc containsResult(n: PNode): bool =
   if n.kind == nkSym and n.sym.kind == skResult:
diff --git a/compiler/options.nim b/compiler/options.nim
index b4d2bb64e..4b7d664f4 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -104,8 +104,10 @@ type
     cmdJsonScript             # compile a .json build file
   TStringSeq* = seq[string]
   TGCMode* = enum             # the selected GC
-    gcNone, gcBoehm, gcGo, gcRegions, gcMarkAndSweep, gcDestructors,
-    gcRefc, gcV2
+    gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcDestructors,
+    gcRefc, gcV2, gcGo
+    # gcRefc and the GCs that follow it use a write barrier,
+    # as far as usesWriteBarrier() is concerned
 
   IdeCmd* = enum
     ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod,
diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim
index e7e14b948..9a7af0a28 100644
--- a/lib/system/mmdisp.nim
+++ b/lib/system/mmdisp.nim
@@ -197,15 +197,11 @@ elif defined(gogc):
   else:
     const goLib = "libgo.so"
 
-  proc roundup(x, v: int): int {.inline.} =
-    result = (x + (v-1)) and not (v-1)
-
   proc initGC() = discard
-  # runtime_setgcpercent is only available in GCC 5
   proc GC_disable() = discard
   proc GC_enable() = discard
-  proc goRuntimeGC(force: int32) {.importc: "runtime_gc", dynlib: goLib.}
-  proc GC_fullCollect() = goRuntimeGC(2)
+  proc go_gc() {.importc: "go_gc", dynlib: goLib.}
+  proc GC_fullCollect() = go_gc()
   proc GC_setStrategy(strategy: GC_Strategy) = discard
   proc GC_enableMarkAndSweep() = discard
   proc GC_disableMarkAndSweep() = discard
@@ -214,67 +210,24 @@ elif defined(gogc):
     goNumSizeClasses = 67
 
   type
-    goMStats_inner_struct = object
-      size: uint32
-      nmalloc: uint64
-      nfree: uint64
-
     goMStats = object
-      # General statistics.
-      alloc: uint64            # bytes allocated and still in use
-      total_alloc: uint64      # bytes allocated (even if freed)
-      sys: uint64              # bytes obtained from system (should be sum of xxx_sys below, no locking, approximate)
-      nlookup: uint64          # number of pointer lookups
-      nmalloc: uint64          # number of mallocs
-      nfree: uint64            # number of frees
-      # Statistics about malloc heap.
-      # protected by mheap.Lock
-      heap_alloc: uint64       # bytes allocated and still in use
-      heap_sys: uint64         # bytes obtained from system
-      heap_idle: uint64        # bytes in idle spans
-      heap_inuse: uint64       # bytes in non-idle spans
-      heap_released: uint64    # bytes released to the OS
-      heap_objects: uint64 # total number of allocated objects
-      # Statistics about allocation of low-level fixed-size structures.
-      # Protected by FixAlloc locks.
-      stacks_inuse: uint64     # bootstrap stacks
-      stacks_sys: uint64
-      mspan_inuse: uint64      # MSpan structures
-      mspan_sys: uint64
-      mcache_inuse: uint64     # MCache structures
-      mcache_sys: uint64
-      buckhash_sys: uint64     # profiling bucket hash table
-      gc_sys: uint64
-      other_sys: uint64
-      # Statistics about garbage collector.
-      # Protected by mheap or stopping the world during GC.
-      next_gc: uint64          # next GC (in heap_alloc time)
-      last_gc: uint64          # last GC (in absolute time)
-      pause_total_ns: uint64
-      pause_ns: array[256, uint64] # circular buffer of recent gc pause lengths
-      pause_end: array[256, uint64] # circular buffer of recent gc end times (nanoseconds since 1970)
-      numgc: uint32
-      numforcedgc: uint32      # number of user-forced GCs
-      gc_cpu_fraction: float64 # fraction of CPU time used by GC
-      enablegc: bool
-      debuggc: bool
-      # Statistics about allocation size classes.
-      by_size: array[goNumSizeClasses, goMStats_inner_struct]
-      # Statistics below here are not exported to MemStats directly.
-      tinyallocs: uint64       # number of tiny allocations that didn't cause actual allocation; not exported to go directly
-      gc_trigger: uint64
-      heap_live: uint64
-      heap_scan: uint64
-      heap_marked: uint64
-
-  proc goRuntime_ReadMemStats(a2: ptr goMStats) {.cdecl,
-    importc: "runtime_ReadMemStats",
-    codegenDecl: "$1 $2$3 __asm__ (\"runtime.ReadMemStats\");\n$1 $2$3",
-    dynlib: goLib.}
+      alloc: uint64          # bytes allocated and still in use
+      total_alloc: uint64    # bytes allocated (even if freed)
+      sys: uint64            # bytes obtained from system
+      nlookup: uint64        # number of pointer lookups
+      nmalloc: uint64        # number of mallocs
+      nfree: uint64          # number of frees
+      heap_objects: uint64   # total number of allocated objects
+      pause_total_ns: uint64 # cumulative nanoseconds in GC stop-the-world pauses since the program started
+      numgc: uint32          # number of completed GC cycles
+
+  proc goMemStats(): goMStats {.importc: "go_mem_stats", dynlib: goLib.}
+  proc goMalloc(size: uint): pointer {.importc: "go_malloc", dynlib: goLib.}
+  proc goSetFinalizer(obj: pointer, f: pointer) {.importc: "set_finalizer", codegenDecl:"$1 $2$3 __asm__ (\"main.Set_finalizer\");\n$1 $2$3", dynlib: goLib.}
+  proc writebarrierptr(dest: PPointer, src: pointer) {.importc: "writebarrierptr", codegenDecl:"$1 $2$3 __asm__ (\"main.Atomic_store_pointer\");\n$1 $2$3", dynlib: goLib.}
 
   proc GC_getStatistics(): string =
-    var mstats: goMStats
-    goRuntime_ReadMemStats(addr mstats)
+    var mstats = goMemStats()
     result = "[GC] total allocated memory: " & $(mstats.total_alloc) & "\n" &
              "[GC] total memory obtained from system: " & $(mstats.sys) & "\n" &
              "[GC] occupied memory: " & $(mstats.alloc) & "\n" &
@@ -282,107 +235,100 @@ elif defined(gogc):
              "[GC] number of mallocs: " & $(mstats.nmalloc) & "\n" &
              "[GC] number of frees: " & $(mstats.nfree) & "\n" &
              "[GC] heap objects: " & $(mstats.heap_objects) & "\n" &
-             "[GC] numgc: " & $(mstats.numgc) & "\n" &
-             "[GC] enablegc: " & $(mstats.enablegc) & "\n" &
-             "[GC] debuggc: " & $(mstats.debuggc) & "\n" &
-             "[GC] total pause time [ms]: " & $(mstats.pause_total_ns div 1000_000)
+             "[GC] number of completed GC cycles: " & $(mstats.numgc) & "\n" &
+             "[GC] total GC pause time [ms]: " & $(mstats.pause_total_ns div 1000_000)
 
   proc getOccupiedMem(): int =
-    var mstats: goMStats
-    goRuntime_ReadMemStats(addr mstats)
+    var mstats = goMemStats()
     result = int(mstats.alloc)
 
   proc getFreeMem(): int =
-    var mstats: goMStats
-    goRuntime_ReadMemStats(addr mstats)
+    var mstats = goMemStats()
     result = int(mstats.sys - mstats.alloc)
 
   proc getTotalMem(): int =
-    var mstats: goMStats
-    goRuntime_ReadMemStats(addr mstats)
+    var mstats = goMemStats()
     result = int(mstats.sys)
 
   proc nimGC_setStackBottom(theStackBottom: pointer) = discard
 
   proc alloc(size: Natural): pointer =
-    result = c_malloc(size)
-    if result == nil: raiseOutOfMem()
+    result = goMalloc(size.uint)
 
   proc alloc0(size: Natural): pointer =
-    result = alloc(size)
-    zeroMem(result, size)
+    result = goMalloc(size.uint)
 
   proc realloc(p: pointer, newsize: Natural): pointer =
-    result = c_realloc(p, newsize)
-    if result == nil: raiseOutOfMem()
+    raise newException(Exception, "not implemented")
 
-  proc dealloc(p: pointer) = c_free(p)
+  proc dealloc(p: pointer) =
+    discard
 
   proc allocShared(size: Natural): pointer =
-    result = c_malloc(size)
-    if result == nil: raiseOutOfMem()
+    result = alloc(size)
 
   proc allocShared0(size: Natural): pointer =
-    result = alloc(size)
-    zeroMem(result, size)
+    result = alloc0(size)
 
   proc reallocShared(p: pointer, newsize: Natural): pointer =
-    result = c_realloc(p, newsize)
-    if result == nil: raiseOutOfMem()
+    result = realloc(p, newsize)
 
-  proc deallocShared(p: pointer) = c_free(p)
+  proc deallocShared(p: pointer) = dealloc(p)
 
   when hasThreadSupport:
     proc getFreeSharedMem(): int = discard
     proc getTotalSharedMem(): int = discard
     proc getOccupiedSharedMem(): int = discard
 
-  const goFlagNoZero: uint32 = 1 shl 3
-  proc goRuntimeMallocGC(size: uint, typ: uint, flag: uint32): pointer {.
-    importc: "runtime_mallocgc", dynlib: goLib.}
-
-  proc goSetFinalizer(obj: pointer, f: pointer) {.
-    importc: "set_finalizer", codegenDecl: "$1 $2$3 __asm__ (\"main.Set_finalizer\");\n$1 $2$3",
-    dynlib: goLib.}
-
   proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} =
-    result = goRuntimeMallocGC(roundup(size, sizeof(pointer)).uint, 0.uint, 0.uint32)
+    writebarrierptr(addr(result), goMalloc(size.uint))
     if typ.finalizer != nil:
       goSetFinalizer(result, typ.finalizer)
 
+  proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
+    writebarrierptr(addr(result), newObj(typ, size))
+
   proc newObjNoInit(typ: PNimType, size: int): pointer =
-    result = goRuntimeMallocGC(roundup(size, sizeof(pointer)).uint, 0.uint, goFlagNoZero)
-    if typ.finalizer != nil:
-      goSetFinalizer(result, typ.finalizer)
+    writebarrierptr(addr(result), newObj(typ, size))
 
   proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} =
-    result = newObj(typ, len * typ.base.size + GenericSeqSize)
+    writebarrierptr(addr(result), newObj(typ, len * typ.base.size + GenericSeqSize))
     cast[PGenericSeq](result).len = len
     cast[PGenericSeq](result).reserved = len
     cast[PGenericSeq](result).elemSize = typ.base.size
 
+  proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} =
+    writebarrierptr(addr(result), newSeq(typ, len))
+
   proc nimNewSeqOfCap(typ: PNimType, cap: int): pointer {.compilerproc.} =
     result = newObj(typ, cap * typ.base.size + GenericSeqSize)
     cast[PGenericSeq](result).len = 0
     cast[PGenericSeq](result).reserved = cap
     cast[PGenericSeq](result).elemSize = typ.base.size
 
+  proc typedMemMove(dest: pointer, src: pointer, size: uint) {.importc: "typedmemmove", dynlib: goLib.}
+
   proc growObj(old: pointer, newsize: int): pointer =
     # the Go GC doesn't have a realloc
+    var metadataOld = cast[PGenericSeq](old)
+    if metadataOld.elemSize == 0:
+      metadataOld.elemSize = 1
     let oldsize = cast[PGenericSeq](old).len * cast[PGenericSeq](old).elemSize + GenericSeqSize
-    result = goRuntimeMallocGC(roundup(newsize, sizeof(pointer)).uint, 0.uint, goFlagNoZero)
-    copyMem(result, old, oldsize)
-    zeroMem(cast[pointer](cast[ByteAddress](result) +% oldsize), newsize - oldsize)
+    writebarrierptr(addr(result), goMalloc(newsize.uint))
+    typedMemMove(result, old, oldsize.uint)
 
   proc nimGCref(p: pointer) {.compilerproc, inline.} = discard
   proc nimGCunref(p: pointer) {.compilerproc, inline.} = discard
+  proc nimGCunrefNoCycle(p: pointer) {.compilerProc, inline.} = discard
+  proc nimGCunrefRC1(p: pointer) {.compilerProc, inline.} = discard
+  proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = discard
 
   proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
-    dest[] = src
+    writebarrierptr(dest, src)
   proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
-    dest[] = src
+    writebarrierptr(dest, src)
   proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline.} =
-    dest[] = src
+    writebarrierptr(dest, src)
 
   type
     MemRegion = object
diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim
index 6438a0541..33cd4415f 100644
--- a/lib/system/sysstr.nim
+++ b/lib/system/sysstr.nim
@@ -122,6 +122,8 @@ proc copyStringRC1(src: NimString): NimString {.compilerRtl.} =
       result = cast[NimString](newObjRC1(addr(strDesc), sizeof(TGenericSeq) +
                                s+1))
       result.reserved = s
+      when defined(gogc):
+        result.elemSize = 1
     else:
       result = rawNewStringNoInit(src.len)
     result.len = src.len