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
This commit is contained in:
Ștefan Talpalaru 2018-10-11 22:15:17 +02:00 • committed by Andreas Rumpf
commit 10f5f67767
5 changed files with 83 additions and 118 deletions

View file

@ -169,7 +169,7 @@ proc canMove(p: BProc, n: PNode): bool =
# result = false # result = false
proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = 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)) linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
elif dest.storage == OnHeap: elif dest.storage == OnHeap:
# location is on heap # location is on heap
@ -265,7 +265,7 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
rdLoc(dest), rdLoc(src)) rdLoc(dest), rdLoc(src))
elif needToCopy notin flags or elif needToCopy notin flags or
tfShallow in skipTypes(dest.t, abstractVarRange).flags: 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, linefmt(p, cpsStmts,
"#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest)) 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): elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode):
genRefAssign(p, dest, src, flags) genRefAssign(p, dest, src, flags)
else: 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) linefmt(p, cpsStmts, "$1 = #copyString($2);$n", dest.rdLoc, src.rdLoc)
elif dest.storage == OnHeap: elif dest.storage == OnHeap:
# we use a temporary to care for the dreaded self assignment: # 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] let args = [getTypeDesc(p.module, typ), ti, sizeExpr]
if a.storage == OnHeap and usesWriteBarrier(p.config): if a.storage == OnHeap and usesWriteBarrier(p.config):
# use newObjRC1 as an optimization
if canFormAcycle(a.t): if canFormAcycle(a.t):
linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", a.rdLoc) linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", a.rdLoc)
else: else:
linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", a.rdLoc) linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", a.rdLoc)
b.r = ropecg(p.module, "($1) #newObjRC1($2, $3)", args) if p.config.selectedGC == gcGo:
linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc) # 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: else:
b.r = ropecg(p.module, "($1) #newObj($2, $3)", args) b.r = ropecg(p.module, "($1) #newObj($2, $3)", args)
genAssignment(p, a, b, {}) # set the object type: genAssignment(p, a, b, {}) # set the object type:
@ -1229,8 +1235,13 @@ proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) =
else: else:
linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", dest.rdLoc) linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", dest.rdLoc)
if not lenIsZero: if not lenIsZero:
call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args) if p.config.selectedGC == gcGo:
linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc) # 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: else:
if lenIsZero: if lenIsZero:
call.r = rope"NIM_NIL" call.r = rope"NIM_NIL"

View file

@ -702,8 +702,12 @@ proc closureSetup(p: BProc, prc: PSym) =
#echo "created environment: ", env.id, " for ", prc.name.s #echo "created environment: ", env.id, " for ", prc.name.s
assignLocalVar(p, ls) assignLocalVar(p, ls)
# generate cast assignment: # generate cast assignment:
linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n", if p.config.selectedGC == gcGo:
rdLoc(env.loc), getTypeDesc(p.module, env.typ)) 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 = proc containsResult(n: PNode): bool =
if n.kind == nkSym and n.sym.kind == skResult: if n.kind == nkSym and n.sym.kind == skResult:

View file

@ -104,8 +104,10 @@ type
cmdJsonScript # compile a .json build file cmdJsonScript # compile a .json build file
TStringSeq* = seq[string] TStringSeq* = seq[string]
TGCMode* = enum # the selected GC TGCMode* = enum # the selected GC
gcNone, gcBoehm, gcGo, gcRegions, gcMarkAndSweep, gcDestructors, gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcDestructors,
gcRefc, gcV2 gcRefc, gcV2, gcGo
# gcRefc and the GCs that follow it use a write barrier,
# as far as usesWriteBarrier() is concerned
IdeCmd* = enum IdeCmd* = enum
ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod, ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod,

View file

@ -197,15 +197,11 @@ elif defined(gogc):
else: else:
const goLib = "libgo.so" const goLib = "libgo.so"
proc roundup(x, v: int): int {.inline.} =
result = (x + (v-1)) and not (v-1)
proc initGC() = discard proc initGC() = discard
# runtime_setgcpercent is only available in GCC 5
proc GC_disable() = discard proc GC_disable() = discard
proc GC_enable() = discard proc GC_enable() = discard
proc goRuntimeGC(force: int32) {.importc: "runtime_gc", dynlib: goLib.} proc go_gc() {.importc: "go_gc", dynlib: goLib.}
proc GC_fullCollect() = goRuntimeGC(2) proc GC_fullCollect() = go_gc()
proc GC_setStrategy(strategy: GC_Strategy) = discard proc GC_setStrategy(strategy: GC_Strategy) = discard
proc GC_enableMarkAndSweep() = discard proc GC_enableMarkAndSweep() = discard
proc GC_disableMarkAndSweep() = discard proc GC_disableMarkAndSweep() = discard
@ -214,67 +210,24 @@ elif defined(gogc):
goNumSizeClasses = 67 goNumSizeClasses = 67
type type
goMStats_inner_struct = object
size: uint32
nmalloc: uint64
nfree: uint64
goMStats = object goMStats = object
# General statistics. alloc: uint64 # bytes allocated and still in use
alloc: uint64 # bytes allocated and still in use total_alloc: uint64 # bytes allocated (even if freed)
total_alloc: uint64 # bytes allocated (even if freed) sys: uint64 # bytes obtained from system
sys: uint64 # bytes obtained from system (should be sum of xxx_sys below, no locking, approximate) nlookup: uint64 # number of pointer lookups
nlookup: uint64 # number of pointer lookups nmalloc: uint64 # number of mallocs
nmalloc: uint64 # number of mallocs nfree: uint64 # number of frees
nfree: uint64 # number of frees heap_objects: uint64 # total number of allocated objects
# Statistics about malloc heap. pause_total_ns: uint64 # cumulative nanoseconds in GC stop-the-world pauses since the program started
# protected by mheap.Lock numgc: uint32 # number of completed GC cycles
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, proc goMemStats(): goMStats {.importc: "go_mem_stats", dynlib: goLib.}
importc: "runtime_ReadMemStats", proc goMalloc(size: uint): pointer {.importc: "go_malloc", dynlib: goLib.}
codegenDecl: "$1 $2$3 __asm__ (\"runtime.ReadMemStats\");\n$1 $2$3", proc goSetFinalizer(obj: pointer, f: pointer) {.importc: "set_finalizer", codegenDecl:"$1 $2$3 __asm__ (\"main.Set_finalizer\");\n$1 $2$3", dynlib: goLib.}
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 = proc GC_getStatistics(): string =
var mstats: goMStats var mstats = goMemStats()
goRuntime_ReadMemStats(addr mstats)
result = "[GC] total allocated memory: " & $(mstats.total_alloc) & "\n" & result = "[GC] total allocated memory: " & $(mstats.total_alloc) & "\n" &
"[GC] total memory obtained from system: " & $(mstats.sys) & "\n" & "[GC] total memory obtained from system: " & $(mstats.sys) & "\n" &
"[GC] occupied memory: " & $(mstats.alloc) & "\n" & "[GC] occupied memory: " & $(mstats.alloc) & "\n" &
@ -282,107 +235,100 @@ elif defined(gogc):
"[GC] number of mallocs: " & $(mstats.nmalloc) & "\n" & "[GC] number of mallocs: " & $(mstats.nmalloc) & "\n" &
"[GC] number of frees: " & $(mstats.nfree) & "\n" & "[GC] number of frees: " & $(mstats.nfree) & "\n" &
"[GC] heap objects: " & $(mstats.heap_objects) & "\n" & "[GC] heap objects: " & $(mstats.heap_objects) & "\n" &
"[GC] numgc: " & $(mstats.numgc) & "\n" & "[GC] number of completed GC cycles: " & $(mstats.numgc) & "\n" &
"[GC] enablegc: " & $(mstats.enablegc) & "\n" & "[GC] total GC pause time [ms]: " & $(mstats.pause_total_ns div 1000_000)
"[GC] debuggc: " & $(mstats.debuggc) & "\n" &
"[GC] total pause time [ms]: " & $(mstats.pause_total_ns div 1000_000)
proc getOccupiedMem(): int = proc getOccupiedMem(): int =
var mstats: goMStats var mstats = goMemStats()
goRuntime_ReadMemStats(addr mstats)
result = int(mstats.alloc) result = int(mstats.alloc)
proc getFreeMem(): int = proc getFreeMem(): int =
var mstats: goMStats var mstats = goMemStats()
goRuntime_ReadMemStats(addr mstats)
result = int(mstats.sys - mstats.alloc) result = int(mstats.sys - mstats.alloc)
proc getTotalMem(): int = proc getTotalMem(): int =
var mstats: goMStats var mstats = goMemStats()
goRuntime_ReadMemStats(addr mstats)
result = int(mstats.sys) result = int(mstats.sys)
proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc nimGC_setStackBottom(theStackBottom: pointer) = discard
proc alloc(size: Natural): pointer = proc alloc(size: Natural): pointer =
result = c_malloc(size) result = goMalloc(size.uint)
if result == nil: raiseOutOfMem()
proc alloc0(size: Natural): pointer = proc alloc0(size: Natural): pointer =
result = alloc(size) result = goMalloc(size.uint)
zeroMem(result, size)
proc realloc(p: pointer, newsize: Natural): pointer = proc realloc(p: pointer, newsize: Natural): pointer =
result = c_realloc(p, newsize) raise newException(Exception, "not implemented")
if result == nil: raiseOutOfMem()
proc dealloc(p: pointer) = c_free(p) proc dealloc(p: pointer) =
discard
proc allocShared(size: Natural): pointer = proc allocShared(size: Natural): pointer =
result = c_malloc(size) result = alloc(size)
if result == nil: raiseOutOfMem()
proc allocShared0(size: Natural): pointer = proc allocShared0(size: Natural): pointer =
result = alloc(size) result = alloc0(size)
zeroMem(result, size)
proc reallocShared(p: pointer, newsize: Natural): pointer = proc reallocShared(p: pointer, newsize: Natural): pointer =
result = c_realloc(p, newsize) result = realloc(p, newsize)
if result == nil: raiseOutOfMem()
proc deallocShared(p: pointer) = c_free(p) proc deallocShared(p: pointer) = dealloc(p)
when hasThreadSupport: when hasThreadSupport:
proc getFreeSharedMem(): int = discard proc getFreeSharedMem(): int = discard
proc getTotalSharedMem(): int = discard proc getTotalSharedMem(): int = discard
proc getOccupiedSharedMem(): 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.} = 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: if typ.finalizer != nil:
goSetFinalizer(result, typ.finalizer) 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 = proc newObjNoInit(typ: PNimType, size: int): pointer =
result = goRuntimeMallocGC(roundup(size, sizeof(pointer)).uint, 0.uint, goFlagNoZero) writebarrierptr(addr(result), newObj(typ, size))
if typ.finalizer != nil:
goSetFinalizer(result, typ.finalizer)
proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} = 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).len = len
cast[PGenericSeq](result).reserved = len cast[PGenericSeq](result).reserved = len
cast[PGenericSeq](result).elemSize = typ.base.size 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.} = proc nimNewSeqOfCap(typ: PNimType, cap: int): pointer {.compilerproc.} =
result = newObj(typ, cap * typ.base.size + GenericSeqSize) result = newObj(typ, cap * typ.base.size + GenericSeqSize)
cast[PGenericSeq](result).len = 0 cast[PGenericSeq](result).len = 0
cast[PGenericSeq](result).reserved = cap cast[PGenericSeq](result).reserved = cap
cast[PGenericSeq](result).elemSize = typ.base.size 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 = proc growObj(old: pointer, newsize: int): pointer =
# the Go GC doesn't have a realloc # 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 let oldsize = cast[PGenericSeq](old).len * cast[PGenericSeq](old).elemSize + GenericSeqSize
result = goRuntimeMallocGC(roundup(newsize, sizeof(pointer)).uint, 0.uint, goFlagNoZero) writebarrierptr(addr(result), goMalloc(newsize.uint))
copyMem(result, old, oldsize) typedMemMove(result, old, oldsize.uint)
zeroMem(cast[pointer](cast[ByteAddress](result) +% oldsize), newsize - oldsize)
proc nimGCref(p: pointer) {.compilerproc, inline.} = discard proc nimGCref(p: pointer) {.compilerproc, inline.} = discard
proc nimGCunref(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.} = proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
dest[] = src writebarrierptr(dest, src)
proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} = proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
dest[] = src writebarrierptr(dest, src)
proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline.} = proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline.} =
dest[] = src writebarrierptr(dest, src)
type type
MemRegion = object MemRegion = object

View file

@ -122,6 +122,8 @@ proc copyStringRC1(src: NimString): NimString {.compilerRtl.} =
result = cast[NimString](newObjRC1(addr(strDesc), sizeof(TGenericSeq) + result = cast[NimString](newObjRC1(addr(strDesc), sizeof(TGenericSeq) +
s+1)) s+1))
result.reserved = s result.reserved = s
when defined(gogc):
result.elemSize = 1
else: else:
result = rawNewStringNoInit(src.len) result = rawNewStringNoInit(src.len)
result.len = src.len result.len = src.len