ARC: cycle detector (#12823)

* first implementation of the =trace and =dispose hooks for the cycle collector
* a cycle collector for ARC: progress
* manual: the .acyclic pragma is a thing once again
* gcbench: adaptations for --gc:arc
* enable valgrind tests for the strutils tests
* testament: better valgrind support
* ARC refactoring: growable jumpstacks
* ARC cycle detector: non-recursive algorithm
* moved and renamed core/ files back to system/
* refactoring: --gc:arc vs --gc:orc since 'orc' is even more experimental and we want to ship --gc:arc soonish
This commit is contained in:
Andreas Rumpf 2019-12-17 17:37:50 +01:00 • committed by GitHub
commit 83a736a34a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 939 additions and 229 deletions

View file

@ -478,7 +478,7 @@ type
nfExecuteOnReload # A top-level statement that will be executed during reloads nfExecuteOnReload # A top-level statement that will be executed during reloads
TNodeFlags* = set[TNodeFlag] TNodeFlags* = set[TNodeFlag]
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: ~38) TTypeFlag* = enum # keep below 32 for efficiency reasons (now: ~39)
tfVarargs, # procedure has C styled varargs tfVarargs, # procedure has C styled varargs
# tyArray type represeting a varargs list # tyArray type represeting a varargs list
tfNoSideEffect, # procedure type does not allow side effects tfNoSideEffect, # procedure type does not allow side effects
@ -537,6 +537,7 @@ type
tfContravariant # contravariant generic param tfContravariant # contravariant generic param
tfCheckedForDestructor # type was checked for having a destructor. tfCheckedForDestructor # type was checked for having a destructor.
# If it has one, t.destructor is not nil. # If it has one, t.destructor is not nil.
tfAcyclic # object type was annotated as .acyclic
TTypeFlags* = set[TTypeFlag] TTypeFlags* = set[TTypeFlag]
@ -635,7 +636,7 @@ type
mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast, mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast,
mNewString, mNewStringOfCap, mParseBiggestFloat, mNewString, mNewStringOfCap, mParseBiggestFloat,
mMove, mWasMoved, mDestroy, mMove, mWasMoved, mDestroy,
mDefault, mUnown, mAccessEnv, mReset, mDefault, mUnown, mAccessEnv, mAccessTypeInfo, mReset,
mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs, mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs,
mRef, mPtr, mVar, mDistinct, mVoid, mTuple, mRef, mPtr, mVar, mDistinct, mVoid, mTuple,
mOrdinal, mOrdinal,
@ -870,6 +871,8 @@ type
attachedDestructor, attachedDestructor,
attachedAsgn, attachedAsgn,
attachedSink, attachedSink,
attachedTrace,
attachedDispose,
attachedDeepCopy attachedDeepCopy
TType* {.acyclic.} = object of TIdObj # \ TType* {.acyclic.} = object of TIdObj # \
@ -1298,7 +1301,8 @@ const
UnspecifiedLockLevel* = TLockLevel(-1'i16) UnspecifiedLockLevel* = TLockLevel(-1'i16)
MaxLockLevel* = 1000'i16 MaxLockLevel* = 1000'i16
UnknownLockLevel* = TLockLevel(1001'i16) UnknownLockLevel* = TLockLevel(1001'i16)
AttachedOpToStr*: array[TTypeAttachedOp, string] = ["=destroy", "=", "=sink", "=deepcopy"] AttachedOpToStr*: array[TTypeAttachedOp, string] = [
"=destroy", "=", "=sink", "=trace", "=dispose", "=deepcopy"]
proc `$`*(x: TLockLevel): string = proc `$`*(x: TLockLevel): string =
if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>" if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>"

View file

@ -1210,7 +1210,7 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) =
genAssignment(p, a, b, {}) genAssignment(p, a, b, {})
else: else:
let ti = genTypeInfo(p.module, typ, a.lode.info) let ti = genTypeInfo(p.module, typ, a.lode.info)
if bt.destructor != nil and not trivialDestructor(bt.destructor): if bt.destructor != nil and not isTrivialProc(bt.destructor):
# the prototype of a destructor is ``=destroy(x: var T)`` and that of a # the prototype of a destructor is ``=destroy(x: var T)`` and that of a
# finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling # finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
# convention at least: # convention at least:
@ -1584,6 +1584,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
gcUsage(p.config, e) gcUsage(p.config, e)
proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) = proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) =
discard cgsym(p.module, "TNimType")
let t = e[1].typ let t = e[1].typ
putIntoDest(p, d, e, genTypeInfo(p.module, t, e.info)) putIntoDest(p, d, e, genTypeInfo(p.module, t, e.info))
@ -2077,6 +2078,21 @@ proc genEnumToStr(p: BProc, e: PNode, d: var TLoc) =
n[0] = newSymNode(toStrProc) n[0] = newSymNode(toStrProc)
expr(p, n, d) expr(p, n, d)
proc rdMType(p: BProc; a: TLoc; nilCheck: var Rope): Rope =
result = rdLoc(a)
var t = skipTypes(a.t, abstractInst)
while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
if t.kind notin {tyVar, tyLent}: nilCheck = result
if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
result = "(*$1)" % [result]
t = skipTypes(t.lastSon, abstractInst)
discard getTypeDesc(p.module, t)
if not p.module.compileToCpp:
while t.kind == tyObject and t[0] != nil:
result.add(".Sup")
t = skipTypes(t[0], skipPtrs)
result.add ".m_type"
proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
case op case op
of mOr, mAnd: genAndOr(p, e, d, op) of mOr, mAnd: genAndOr(p, e, d, op)
@ -2260,6 +2276,11 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
of mMove: genMove(p, e, d) of mMove: genMove(p, e, d)
of mDestroy: genDestroy(p, e) of mDestroy: genDestroy(p, e)
of mAccessEnv: unaryExpr(p, e, d, "$1.ClE_0") of mAccessEnv: unaryExpr(p, e, d, "$1.ClE_0")
of mAccessTypeInfo:
var a: TLoc
var dummy: Rope
initLocExpr(p, e[1], a)
putIntoDest(p, d, e, rdMType(p, a, dummy))
of mSlice: of mSlice:
localError(p.config, e.info, "invalid context for 'toOpenArray'; " & localError(p.config, e.info, "invalid context for 'toOpenArray'; " &
"'toOpenArray' is only valid within a call expression") "'toOpenArray' is only valid within a call expression")
@ -2407,28 +2428,17 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) =
initLocExpr(p, n[0], a) initLocExpr(p, n[0], a)
let dest = skipTypes(n.typ, abstractPtrs) let dest = skipTypes(n.typ, abstractPtrs)
if optObjCheck in p.options and not isObjLackingTypeField(dest): if optObjCheck in p.options and not isObjLackingTypeField(dest):
var r = rdLoc(a) var nilCheck = Rope(nil)
var nilCheck: Rope = nil let r = rdMType(p, a, nilCheck)
var t = skipTypes(a.t, abstractInst)
while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
if t.kind notin {tyVar, tyLent}: nilCheck = r
if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
r = "(*$1)" % [r]
t = skipTypes(t.lastSon, abstractInst)
discard getTypeDesc(p.module, t)
if not p.module.compileToCpp:
while t.kind == tyObject and t[0] != nil:
r.add(".Sup")
t = skipTypes(t[0], skipPtrs)
let checkFor = if optTinyRtti in p.config.globalOptions: let checkFor = if optTinyRtti in p.config.globalOptions:
genTypeInfo2Name(p.module, dest) genTypeInfo2Name(p.module, dest)
else: else:
genTypeInfo(p.module, dest, n.info) genTypeInfo(p.module, dest, n.info)
if nilCheck != nil: if nilCheck != nil:
linefmt(p, cpsStmts, "if ($1) #chckObj($2.m_type, $3);$n", linefmt(p, cpsStmts, "if ($1) #chckObj($2, $3);$n",
[nilCheck, r, checkFor]) [nilCheck, r, checkFor])
else: else:
linefmt(p, cpsStmts, "#chckObj($1.m_type, $2);$n", linefmt(p, cpsStmts, "#chckObj($1, $2);$n",
[r, checkFor]) [r, checkFor])
if n[0].typ.kind != tyObject: if n[0].typ.kind != tyObject:
putIntoDest(p, d, n, putIntoDest(p, d, n,

View file

@ -1273,30 +1273,44 @@ proc genTypeInfo2Name(m: BModule; t: PType): Rope =
it = it[0] it = it[0]
result = makeCString(res) result = makeCString(res)
proc trivialDestructor(s: PSym): bool {.inline.} = s.ast[bodyPos].len == 0 proc isTrivialProc(s: PSym): bool {.inline.} = s.ast[bodyPos].len == 0
proc genObjectInfoV2(m: BModule, t, origType: PType, name: Rope; info: TLineInfo) = proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp): Rope =
assert t.kind == tyObject let theProc = t.attachedOps[op]
if incompleteType(t): if theProc != nil and not isTrivialProc(theProc):
localError(m.config, info, "request for RTTI generation for incomplete object: " &
typeToString(t))
var d: Rope
if t.destructor != nil and not trivialDestructor(t.destructor):
# the prototype of a destructor is ``=destroy(x: var T)`` and that of a # the prototype of a destructor is ``=destroy(x: var T)`` and that of a
# finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling # finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
# convention at least: # convention at least:
if t.destructor.typ == nil or t.destructor.typ.callConv != ccDefault: if theProc.typ == nil or theProc.typ.callConv != ccDefault:
localError(m.config, info, localError(m.config, info,
"the destructor that is turned into a finalizer needs " & theProc.name.s & " needs to have the 'nimcall' calling convention")
"to have the 'nimcall' calling convention")
genProc(m, t.destructor) genProc(m, theProc)
d = t.destructor.loc.r result = theProc.loc.r
else: else:
d = rope("NIM_NIL") result = rope("NIM_NIL")
proc genTypeInfoV2(m: BModule, t, origType: PType, name: Rope; info: TLineInfo) =
var typeName: Rope
if t.kind == tyObject:
if incompleteType(t):
localError(m.config, info, "request for RTTI generation for incomplete object: " &
typeToString(t))
typeName = genTypeInfo2Name(m, t)
else:
typeName = rope("NIM_NIL")
m.s[cfsData].addf("TNimType $1;$n", [name]) m.s[cfsData].addf("TNimType $1;$n", [name])
m.s[cfsTypeInit3].addf("$1.destructor = (void*)$2; $1.size = sizeof($3); $1.name = $4;$n", [ let destroyImpl = genHook(m, t, info, attachedDestructor)
name, d, getTypeDesc(m, t), genTypeInfo2Name(m, t)]) let traceImpl = genHook(m, t, info, attachedTrace)
let disposeImpl = genHook(m, t, info, attachedDispose)
m.s[cfsTypeInit3].addf("$1.destructor = (void*)$2; $1.size = sizeof($3);$n" &
"$1.name = $4;$n" &
"$1.traceImpl = (void*)$5;$n" &
"$1.disposeImpl = (void*)$6;$n", [
name, destroyImpl, getTypeDesc(m, t), typeName,
traceImpl, disposeImpl])
proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope = proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope =
let origType = t let origType = t
@ -1333,49 +1347,51 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope =
return prefixTI.rope & result & ")".rope return prefixTI.rope & result & ")".rope
m.g.typeInfoMarker[sig] = (str: result, owner: owner) m.g.typeInfoMarker[sig] = (str: result, owner: owner)
case t.kind
of tyEmpty, tyVoid: result = rope"0" if optTinyRtti in m.config.globalOptions:
of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent: genTypeInfoV2(m, t, origType, result, info)
genTypeInfoAuxBase(m, t, t, result, rope"0", info) else:
of tyStatic: case t.kind
if t.n != nil: result = genTypeInfo(m, lastSon t, info) of tyEmpty, tyVoid: result = rope"0"
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')') of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent:
of tyUserTypeClasses:
internalAssert m.config, t.isResolvedUserTypeClass
return genTypeInfo(m, t.lastSon, info)
of tyProc:
if t.callConv != ccClosure:
genTypeInfoAuxBase(m, t, t, result, rope"0", info) genTypeInfoAuxBase(m, t, t, result, rope"0", info)
else: of tyStatic:
let x = fakeClosureType(m, t.owner) if t.n != nil: result = genTypeInfo(m, lastSon t, info)
genTupleInfo(m, x, x, result, info) else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
of tySequence: of tyUserTypeClasses:
genTypeInfoAux(m, t, t, result, info) internalAssert m.config, t.isResolvedUserTypeClass
if optSeqDestructors notin m.config.globalOptions: return genTypeInfo(m, t.lastSon, info)
of tyProc:
if t.callConv != ccClosure:
genTypeInfoAuxBase(m, t, t, result, rope"0", info)
else:
let x = fakeClosureType(m, t.owner)
genTupleInfo(m, x, x, result, info)
of tySequence:
genTypeInfoAux(m, t, t, result, info)
if optSeqDestructors notin m.config.globalOptions:
if m.config.selectedGC >= gcMarkAndSweep:
let markerProc = genTraverseProc(m, origType, sig)
m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
of tyRef:
genTypeInfoAux(m, t, t, result, info)
if m.config.selectedGC >= gcMarkAndSweep: if m.config.selectedGC >= gcMarkAndSweep:
let markerProc = genTraverseProc(m, origType, sig) let markerProc = genTraverseProc(m, origType, sig)
m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc]) m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
of tyRef: of tyPtr, tyRange, tyUncheckedArray: genTypeInfoAux(m, t, t, result, info)
genTypeInfoAux(m, t, t, result, info) of tyArray: genArrayInfo(m, t, result, info)
if m.config.selectedGC >= gcMarkAndSweep: of tySet: genSetInfo(m, t, result, info)
let markerProc = genTraverseProc(m, origType, sig) of tyEnum: genEnumInfo(m, t, result, info)
m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc]) of tyObject:
of tyPtr, tyRange, tyUncheckedArray: genTypeInfoAux(m, t, t, result, info)
of tyArray: genArrayInfo(m, t, result, info)
of tySet: genSetInfo(m, t, result, info)
of tyEnum: genEnumInfo(m, t, result, info)
of tyObject:
if optTinyRtti in m.config.globalOptions:
genObjectInfoV2(m, t, origType, result, info)
else:
genObjectInfo(m, t, origType, result, info) genObjectInfo(m, t, origType, result, info)
of tyTuple: of tyTuple:
# if t.n != nil: genObjectInfo(m, t, result) # if t.n != nil: genObjectInfo(m, t, result)
# else: # else:
# BUGFIX: use consistently RTTI without proper field names; otherwise # BUGFIX: use consistently RTTI without proper field names; otherwise
# results are not deterministic! # results are not deterministic!
genTupleInfo(m, t, origType, result, info) genTupleInfo(m, t, origType, result, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')') else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
if t.attachedOps[attachedDeepCopy] != nil: if t.attachedOps[attachedDeepCopy] != nil:
genDeepCopyProc(m, t.attachedOps[attachedDeepCopy], result) genDeepCopyProc(m, t.attachedOps[attachedDeepCopy], result)
elif origType.attachedOps[attachedDeepCopy] != nil: elif origType.attachedOps[attachedDeepCopy] != nil:

View file

@ -229,7 +229,8 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
of "v2": result = false of "v2": result = false
of "markandsweep": result = conf.selectedGC == gcMarkAndSweep of "markandsweep": result = conf.selectedGC == gcMarkAndSweep
of "generational": result = false of "generational": result = false
of "destructors", "arc": result = conf.selectedGC == gcDestructors of "destructors", "arc": result = conf.selectedGC == gcArc
of "orc": result = conf.selectedGC == gcOrc
of "hooks": result = conf.selectedGC == gcHooks of "hooks": result = conf.selectedGC == gcHooks
of "go": result = conf.selectedGC == gcGo of "go": result = conf.selectedGC == gcGo
of "none": result = conf.selectedGC == gcNone of "none": result = conf.selectedGC == gcNone
@ -455,8 +456,18 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.selectedGC = gcMarkAndSweep conf.selectedGC = gcMarkAndSweep
defineSymbol(conf.symbols, "gcmarkandsweep") defineSymbol(conf.symbols, "gcmarkandsweep")
of "destructors", "arc": of "destructors", "arc":
conf.selectedGC = gcDestructors conf.selectedGC = gcArc
defineSymbol(conf.symbols, "gcdestructors") defineSymbol(conf.symbols, "gcdestructors")
defineSymbol(conf.symbols, "gcarc")
incl conf.globalOptions, optSeqDestructors
incl conf.globalOptions, optTinyRtti
if pass in {passCmd2, passPP}:
defineSymbol(conf.symbols, "nimSeqsV2")
defineSymbol(conf.symbols, "nimV2")
of "orc":
conf.selectedGC = gcOrc
defineSymbol(conf.symbols, "gcdestructors")
defineSymbol(conf.symbols, "gcorc")
incl conf.globalOptions, optSeqDestructors incl conf.globalOptions, optSeqDestructors
incl conf.globalOptions, optTinyRtti incl conf.globalOptions, optTinyRtti
if pass in {passCmd2, passPP}: if pass in {passCmd2, passPP}:

View file

@ -218,7 +218,7 @@ proc genCopyNoCheck(c: Con; dest, ri: PNode): PNode =
proc genCopy(c: var Con; dest, ri: PNode): PNode = proc genCopy(c: var Con; dest, ri: PNode): PNode =
let t = dest.typ let t = dest.typ
if tfHasOwned in t.flags: if tfHasOwned in t.flags and ri.kind != nkNilLit:
# try to improve the error message here: # try to improve the error message here:
if c.otherRead == nil: discard isLastRead(ri, c) if c.otherRead == nil: discard isLastRead(ri, c)
checkForErrorPragma(c, t, ri, "=") checkForErrorPragma(c, t, ri, "=")
@ -409,7 +409,7 @@ proc isCursor(n: PNode): bool =
result = false result = false
proc cycleCheck(n: PNode; c: var Con) = proc cycleCheck(n: PNode; c: var Con) =
if c.graph.config.selectedGC != gcDestructors: return if c.graph.config.selectedGC != gcArc: return
var value = n[1] var value = n[1]
if value.kind == nkClosure: if value.kind == nkClosure:
value = value[1] value = value[1]
@ -512,7 +512,7 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode =
result[i] = p(n[i], c, normal) result[i] = p(n[i], c, normal)
if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}: if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
result[0] = copyTree(n[0]) result[0] = copyTree(n[0])
if c.graph.config.selectedGC in {gcHooks, gcDestructors}: if c.graph.config.selectedGC in {gcHooks, gcArc, gcOrc}:
let destroyOld = genDestroy(c, result[1]) let destroyOld = genDestroy(c, result[1])
result = newTree(nkStmtList, destroyOld, result) result = newTree(nkStmtList, destroyOld, result)
else: else:

View file

@ -353,6 +353,7 @@ proc createUpField(c: var DetectionPass; dest, dep: PSym; info: TLineInfo) =
# with cycles properly, so it's better to produce a weak ref (=ptr) here. # with cycles properly, so it's better to produce a weak ref (=ptr) here.
# This seems to be generally correct but since it's a bit risky it's disabled # This seems to be generally correct but since it's a bit risky it's disabled
# for now. # for now.
# XXX This is wrong for the 'hamming' test, so remove this logic again.
let fieldType = if isDefined(c.graph.config, "nimCycleBreaker"): let fieldType = if isDefined(c.graph.config, "nimCycleBreaker"):
c.getEnvTypeForOwnerUp(dep, info) #getHiddenParam(dep).typ c.getEnvTypeForOwnerUp(dep, info) #getHiddenParam(dep).typ
else: else:

View file

@ -41,7 +41,8 @@ proc at(a, i: PNode, elemType: PType): PNode =
proc fillBodyTup(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc fillBodyTup(c: var TLiftCtx; t: PType; body, x, y: PNode) =
for i in 0..<t.len: for i in 0..<t.len:
let lit = lowerings.newIntLit(c.g, x.info, i) let lit = lowerings.newIntLit(c.g, x.info, i)
fillBody(c, t[i], body, x.at(lit, t[i]), y.at(lit, t[i])) let b = if c.kind == attachedTrace: y else: y.at(lit, t[i])
fillBody(c, t[i], body, x.at(lit, t[i]), b)
proc dotField(x: PNode, f: PSym): PNode = proc dotField(x: PNode, f: PSym): PNode =
result = newNodeI(nkDotExpr, x.info, 2) result = newNodeI(nkDotExpr, x.info, 2)
@ -58,18 +59,19 @@ proc newAsgnStmt(le, ri: PNode): PNode =
result[1] = ri result[1] = ri
proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
if c.kind != attachedDestructor: if c.kind in {attachedAsgn, attachedDeepCopy, attachedSink}:
body.add newAsgnStmt(x, y) body.add newAsgnStmt(x, y)
proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode) = proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode) =
case n.kind case n.kind
of nkSym: of nkSym:
let f = n.sym let f = n.sym
let b = if c.kind == attachedTrace: y else: y.dotField(f)
if sfCursor in f.flags and f.typ.skipTypes(abstractInst).kind in {tyRef, tyProc} and if sfCursor in f.flags and f.typ.skipTypes(abstractInst).kind in {tyRef, tyProc} and
c.g.config.selectedGC in {gcDestructors, gcHooks}: c.g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
defaultOp(c, f.typ, body, x.dotField(f), y.dotField(f)) defaultOp(c, f.typ, body, x.dotField(f), b)
else: else:
fillBody(c, f.typ, body, x.dotField(f), y.dotField(f)) fillBody(c, f.typ, body, x.dotField(f), b)
of nkNilLit: discard of nkNilLit: discard
of nkRecCase: of nkRecCase:
if c.kind in {attachedSink, attachedAsgn, attachedDeepCopy}: if c.kind in {attachedSink, attachedAsgn, attachedDeepCopy}:
@ -117,13 +119,17 @@ proc genAddr(g: ModuleGraph; x: PNode): PNode =
result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ)) result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ))
result.add x result.add x
proc newAsgnCall(g: ModuleGraph; op: PSym; x, y: PNode): PNode = proc newHookCall(g: ModuleGraph; op: PSym; x, y: PNode): PNode =
#if sfError in op.flags: #if sfError in op.flags:
# localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s) # localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s)
result = newNodeI(nkCall, x.info) result = newNodeI(nkCall, x.info)
result.add newSymNode(op) result.add newSymNode(op)
result.add genAddr(g, x) if op.typ.sons[1].kind == tyVar:
result.add y result.add genAddr(g, x)
else:
result.add x
if y != nil:
result.add y
proc newOpCall(op: PSym; x: PNode): PNode = proc newOpCall(op: PSym; x: PNode): PNode =
result = newNodeIT(nkCall, x.info, op.typ[0]) result = newNodeIT(nkCall, x.info, op.typ[0])
@ -142,6 +148,10 @@ proc useNoGc(c: TLiftCtx; t: PType): bool {.inline.} =
result = optSeqDestructors in c.g.config.globalOptions and result = optSeqDestructors in c.g.config.globalOptions and
({tfHasGCedMem, tfHasOwned} * t.flags != {} or t.isGCedMem) ({tfHasGCedMem, tfHasOwned} * t.flags != {} or t.isGCedMem)
proc requiresDestructor(c: TLiftCtx; t: PType): bool {.inline.} =
result = optSeqDestructors in c.g.config.globalOptions and
containsGarbageCollectedRef(t)
proc instantiateGeneric(c: var TLiftCtx; op: PSym; t, typeInst: PType): PSym = proc instantiateGeneric(c: var TLiftCtx; op: PSym; t, typeInst: PType): PSym =
if c.c != nil and typeInst != nil: if c.c != nil and typeInst != nil:
result = c.c.instTypeBoundOp(c.c, op, typeInst, c.info, attachedAsgn, 1) result = c.c.instTypeBoundOp(c.c, op, typeInst, c.info, attachedAsgn, 1)
@ -160,7 +170,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
#else: #else:
# markUsed(c.g.config, c.info, op, c.g.usageSym) # markUsed(c.g.config, c.info, op, c.g.usageSym)
onUse(c.info, op) onUse(c.info, op)
body.add newAsgnCall(c.g, op, x, y) body.add newHookCall(c.g, op, x, y)
result = true result = true
elif tfHasAsgn in t.flags: elif tfHasAsgn in t.flags:
var op: PSym var op: PSym
@ -189,7 +199,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
#debug(t) #debug(t)
#return false #return false
assert op.ast[genericParamsPos].kind == nkEmpty assert op.ast[genericParamsPos].kind == nkEmpty
body.add newAsgnCall(c.g, op, x, y) body.add newHookCall(c.g, op, x, y)
result = true result = true
proc addDestructorCall(c: var TLiftCtx; orig: PType; body, x: PNode) = proc addDestructorCall(c: var TLiftCtx; orig: PType; body, x: PNode) =
@ -202,7 +212,7 @@ proc addDestructorCall(c: var TLiftCtx; orig: PType; body, x: PNode) =
op = instantiateGeneric(c, op, t, t.typeInst) op = instantiateGeneric(c, op, t, t.typeInst)
t.attachedOps[attachedDestructor] = op t.attachedOps[attachedDestructor] = op
if op == nil and useNoGc(c, t): if op == nil and (useNoGc(c, t) or requiresDestructor(c, t)):
op = produceSym(c.g, c.c, t, attachedDestructor, c.info) op = produceSym(c.g, c.c, t, attachedDestructor, c.info)
doAssert op != nil doAssert op != nil
doAssert op == t.destructor doAssert op == t.destructor
@ -231,10 +241,10 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
body.add destructorCall(c.g, op, x) body.add destructorCall(c.g, op, x)
result = true result = true
#result = addDestructorCall(c, t, body, x) #result = addDestructorCall(c, t, body, x)
of attachedAsgn: of attachedAsgn, attachedSink, attachedTrace:
result = considerAsgnOrSink(c, t, body, x, y, t.assignment) result = considerAsgnOrSink(c, t, body, x, y, t.attachedOps[c.kind])
of attachedSink: of attachedDispose:
result = considerAsgnOrSink(c, t, body, x, y, t.asink) result = considerAsgnOrSink(c, t, body, x, nil, t.attachedOps[c.kind])
of attachedDeepCopy: of attachedDeepCopy:
let op = t.attachedOps[attachedDeepCopy] let op = t.attachedOps[attachedDeepCopy]
if op != nil: if op != nil:
@ -305,8 +315,8 @@ proc forallElements(c: var TLiftCtx; t: PType; body, x, y: PNode) =
let i = declareCounter(c, body, toInt64(firstOrd(c.g.config, t))) let i = declareCounter(c, body, toInt64(firstOrd(c.g.config, t)))
let whileLoop = genWhileLoop(c, i, x) let whileLoop = genWhileLoop(c, i, x)
let elemType = t.lastSon let elemType = t.lastSon
fillBody(c, elemType, whileLoop[1], x.at(i, elemType), let b = if c.kind == attachedTrace: y else: y.at(i, elemType)
y.at(i, elemType)) fillBody(c, elemType, whileLoop[1], x.at(i, elemType), b)
addIncStmt(c, whileLoop[1], i) addIncStmt(c, whileLoop[1], i)
body.add whileLoop body.add whileLoop
@ -330,6 +340,11 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# destroy all elements: # destroy all elements:
forallElements(c, t, body, x, y) forallElements(c, t, body, x, y)
body.add genBuiltin(c.g, mDestroy, "destroy", x) body.add genBuiltin(c.g, mDestroy, "destroy", x)
of attachedTrace:
# follow all elements:
forallElements(c, t, body, x, y)
of attachedDispose:
body.add genBuiltin(c.g, mDestroy, "destroy", x)
proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
createTypeBoundOps(c.g, c.c, t, body.info) createTypeBoundOps(c.g, c.c, t, body.info)
@ -344,7 +359,7 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case c.kind case c.kind
of attachedAsgn, attachedDeepCopy: of attachedAsgn, attachedDeepCopy:
doAssert t.assignment != nil doAssert t.assignment != nil
body.add newAsgnCall(c.g, t.assignment, x, y) body.add newHookCall(c.g, t.assignment, x, y)
of attachedSink: of attachedSink:
# we always inline the move for better performance: # we always inline the move for better performance:
let moveCall = genBuiltin(c.g, mMove, "move", x) let moveCall = genBuiltin(c.g, mMove, "move", x)
@ -355,10 +370,14 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# alternatively we could do this: # alternatively we could do this:
when false: when false:
doAssert t.asink != nil doAssert t.asink != nil
body.add newAsgnCall(c.g, t.asink, x, y) body.add newHookCall(c.g, t.asink, x, y)
of attachedDestructor: of attachedDestructor:
doAssert t.destructor != nil doAssert t.destructor != nil
body.add destructorCall(c.g, t.destructor, x) body.add destructorCall(c.g, t.destructor, x)
of attachedTrace:
body.add newHookCall(c.g, t.attachedOps[c.kind], x, y)
of attachedDispose:
body.add newHookCall(c.g, t.attachedOps[c.kind], x, nil)
proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case c.kind case c.kind
@ -370,8 +389,10 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
doAssert t.destructor != nil doAssert t.destructor != nil
moveCall.add destructorCall(c.g, t.destructor, x) moveCall.add destructorCall(c.g, t.destructor, x)
body.add moveCall body.add moveCall
of attachedDestructor: of attachedDestructor, attachedDispose:
body.add genBuiltin(c.g, mDestroy, "destroy", x) body.add genBuiltin(c.g, mDestroy, "destroy", x)
of attachedTrace:
discard "strings are atomic and have no inner elements that are to trace"
proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
var actions = newNodeI(nkStmtList, c.info) var actions = newNodeI(nkStmtList, c.info)
@ -384,7 +405,18 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(x, nkDerefExpr)) addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(x, nkDerefExpr))
actions.add callCodegenProc(c.g, "nimDestroyAndDispose", c.info, x) actions.add callCodegenProc(c.g, "nimDestroyAndDispose", c.info, x)
let cond = callCodegenProc(c.g, "nimDecRefIsLast", c.info, x) let isCyclic = c.g.config.selectedGC == gcOrc and types.canFormAcycle(t)
var cond: PNode
if isCyclic:
if isFinal(elemType):
let typInfo = genBuiltin(c.g, mGetTypeInfo, "getTypeInfo", newNodeIT(nkType, x.info, elemType))
typInfo.typ = getSysType(c.g, c.info, tyPointer)
cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicStatic", c.info, x, typInfo)
else:
cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicDyn", c.info, x)
else:
cond = callCodegenProc(c.g, "nimDecRefIsLast", c.info, x)
cond.typ = getSysType(c.g, x.info, tyBool) cond.typ = getSysType(c.g, x.info, tyBool)
case c.kind case c.kind
@ -392,7 +424,8 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
body.add genIf(c, cond, actions) body.add genIf(c, cond, actions)
body.add newAsgnStmt(x, y) body.add newAsgnStmt(x, y)
of attachedAsgn: of attachedAsgn:
body.add genIf(c, y, callCodegenProc(c.g, "nimIncRef", c.info, y)) body.add genIf(c, y, callCodegenProc(c.g,
if isCyclic: "nimIncRefCyclic" else: "nimIncRef", c.info, y))
body.add genIf(c, cond, actions) body.add genIf(c, cond, actions)
body.add newAsgnStmt(x, y) body.add newAsgnStmt(x, y)
of attachedDestructor: of attachedDestructor:
@ -401,6 +434,22 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
actions.add newAsgnStmt(x, newNodeIT(nkNilLit, body.info, t)) actions.add newAsgnStmt(x, newNodeIT(nkNilLit, body.info, t))
body.add genIf(c, cond, actions) body.add genIf(c, cond, actions)
of attachedDeepCopy: assert(false, "cannot happen") of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace:
if isFinal(elemType):
let typInfo = genBuiltin(c.g, mGetTypeInfo, "getTypeInfo", newNodeIT(nkType, x.info, elemType))
typInfo.typ = getSysType(c.g, c.info, tyPointer)
body.add callCodegenProc(c.g, "nimTraceRef", c.info, x, typInfo, y)
else:
# If the ref is polymorphic we have to account for this
body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, x, y)
of attachedDispose:
# this is crucial! dispose is like =destroy but we don't follow refs
# as that is dealt within the cycle collector.
when false:
let cond = copyTree(x)
cond.typ = getSysType(c.g, x.info, tyBool)
actions.add callCodegenProc(c.g, "nimRawDispose", c.info, x)
body.add genIf(c, cond, actions)
proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
## Closures are really like refs except they always use a virtual destructor ## Closures are really like refs except they always use a virtual destructor
@ -411,7 +460,10 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
var actions = newNodeI(nkStmtList, c.info) var actions = newNodeI(nkStmtList, c.info)
actions.add callCodegenProc(c.g, "nimDestroyAndDispose", c.info, xenv) actions.add callCodegenProc(c.g, "nimDestroyAndDispose", c.info, xenv)
let cond = callCodegenProc(c.g, "nimDecRefIsLast", c.info, xenv) let decRefProc =
if c.g.config.selectedGC == gcOrc: "nimDecRefIsLastCyclicDyn"
else: "nimDecRefIsLast"
let cond = callCodegenProc(c.g, decRefProc, c.info, xenv)
cond.typ = getSysType(c.g, x.info, tyBool) cond.typ = getSysType(c.g, x.info, tyBool)
case c.kind case c.kind
@ -421,7 +473,10 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedAsgn: of attachedAsgn:
let yenv = genBuiltin(c.g, mAccessEnv, "accessEnv", y) let yenv = genBuiltin(c.g, mAccessEnv, "accessEnv", y)
yenv.typ = getSysType(c.g, c.info, tyPointer) yenv.typ = getSysType(c.g, c.info, tyPointer)
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv)) let incRefProc =
if c.g.config.selectedGC == gcOrc: "nimIncRefCyclic"
else: "nimIncRef"
body.add genIf(c, yenv, callCodegenProc(c.g, incRefProc, c.info, yenv))
body.add genIf(c, cond, actions) body.add genIf(c, cond, actions)
body.add newAsgnStmt(x, y) body.add newAsgnStmt(x, y)
of attachedDestructor: of attachedDestructor:
@ -430,6 +485,16 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
actions.add newAsgnStmt(xenv, newNodeIT(nkNilLit, body.info, xenv.typ)) actions.add newAsgnStmt(xenv, newNodeIT(nkNilLit, body.info, xenv.typ))
body.add genIf(c, cond, actions) body.add genIf(c, cond, actions)
of attachedDeepCopy: assert(false, "cannot happen") of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace:
body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, xenv, y)
of attachedDispose:
# this is crucial! dispose is like =destroy but we don't follow refs
# as that is dealt within the cycle collector.
when false:
let cond = copyTree(xenv)
cond.typ = getSysType(c.g, xenv.info, tyBool)
actions.add callCodegenProc(c.g, "nimRawDispose", c.info, xenv)
body.add genIf(c, cond, actions)
proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case c.kind case c.kind
@ -451,6 +516,7 @@ proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
else: else:
body.sons.insert(des, 0) body.sons.insert(des, 0)
of attachedDeepCopy: assert(false, "cannot happen") of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace, attachedDispose: discard
proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
var actions = newNodeI(nkStmtList, c.info) var actions = newNodeI(nkStmtList, c.info)
@ -473,6 +539,7 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedDestructor: of attachedDestructor:
body.add genIf(c, x, actions) body.add genIf(c, x, actions)
of attachedDeepCopy: assert(false, "cannot happen") of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace, attachedDispose: discard
proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
if c.kind == attachedDeepCopy: if c.kind == attachedDeepCopy:
@ -484,7 +551,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
call[1] = y call[1] = y
body.add newAsgnStmt(x, call) body.add newAsgnStmt(x, call)
elif (optOwnedRefs in c.g.config.globalOptions and elif (optOwnedRefs in c.g.config.globalOptions and
optRefCheck in c.g.config.options) or c.g.config.selectedGC == gcDestructors: optRefCheck in c.g.config.options) or c.g.config.selectedGC in {gcArc, gcOrc}:
let xx = genBuiltin(c.g, mAccessEnv, "accessEnv", x) let xx = genBuiltin(c.g, mAccessEnv, "accessEnv", x)
xx.typ = getSysType(c.g, c.info, tyPointer) xx.typ = getSysType(c.g, c.info, tyPointer)
case c.kind case c.kind
@ -506,6 +573,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
else: else:
body.sons.insert(des, 0) body.sons.insert(des, 0)
of attachedDeepCopy: assert(false, "cannot happen") of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace, attachedDispose: discard
proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
let xx = genBuiltin(c.g, mAccessEnv, "accessEnv", x) let xx = genBuiltin(c.g, mAccessEnv, "accessEnv", x)
@ -520,6 +588,7 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedDestructor: of attachedDestructor:
body.add genIf(c, xx, actions) body.add genIf(c, xx, actions)
of attachedDeepCopy: assert(false, "cannot happen") of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace, attachedDispose: discard
proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case t.kind case t.kind
@ -528,7 +597,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
tyPtr, tyOpt, tyUncheckedArray, tyVar, tyLent: tyPtr, tyOpt, tyUncheckedArray, tyVar, tyLent:
defaultOp(c, t, body, x, y) defaultOp(c, t, body, x, y)
of tyRef: of tyRef:
if c.g.config.selectedGC == gcDestructors: if c.g.config.selectedGC in {gcArc, gcOrc}:
atomicRefOp(c, t, body, x, y) atomicRefOp(c, t, body, x, y)
elif (optOwnedRefs in c.g.config.globalOptions and elif (optOwnedRefs in c.g.config.globalOptions and
optRefCheck in c.g.config.options): optRefCheck in c.g.config.options):
@ -537,7 +606,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
defaultOp(c, t, body, x, y) defaultOp(c, t, body, x, y)
of tyProc: of tyProc:
if t.callConv == ccClosure: if t.callConv == ccClosure:
if c.g.config.selectedGC == gcDestructors: if c.g.config.selectedGC in {gcArc, gcOrc}:
atomicClosureOp(c, t, body, x, y) atomicClosureOp(c, t, body, x, y)
else: else:
closureOp(c, t, body, x, y) closureOp(c, t, body, x, y)
@ -569,7 +638,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# 'selectedGC' here to determine if we have the new runtime. # 'selectedGC' here to determine if we have the new runtime.
discard considerUserDefinedOp(c, t, body, x, y) discard considerUserDefinedOp(c, t, body, x, y)
elif tfHasAsgn in t.flags: elif tfHasAsgn in t.flags:
if c.kind != attachedDestructor: if c.kind in {attachedAsgn, attachedSink, attachedDeepCopy}:
body.add newSeqCall(c.g, x, y) body.add newSeqCall(c.g, x, y)
forallElements(c, t, body, x, y) forallElements(c, t, body, x, y)
else: else:
@ -632,30 +701,40 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
a.asgnForType = typ a.asgnForType = typ
let dest = newSym(skParam, getIdent(g.cache, "dest"), result, info) let dest = newSym(skParam, getIdent(g.cache, "dest"), result, info)
let src = newSym(skParam, getIdent(g.cache, "src"), result, info) let src = newSym(skParam, getIdent(g.cache, if kind == attachedTrace: "env" else: "src"), result, info)
var d: PNode
#if kind notin {attachedTrace, attachedDispose}:
dest.typ = makeVarType(typ.owner, typ) dest.typ = makeVarType(typ.owner, typ)
src.typ = typ d = newDeref(newSymNode(dest))
#else:
# dest.typ = typ
# d = newSymNode(dest)
if kind == attachedTrace:
src.typ = getSysType(g, info, tyPointer)
else:
src.typ = typ
result.typ = newProcType(info, typ.owner) result.typ = newProcType(info, typ.owner)
result.typ.addParam dest result.typ.addParam dest
if kind != attachedDestructor: if kind notin {attachedDestructor, attachedDispose}:
result.typ.addParam src result.typ.addParam src
# register this operation already: # register this operation already:
typ.attachedOps[kind] = result typ.attachedOps[kind] = result
var tk: TTypeKind var tk: TTypeKind
if g.config.selectedGC in {gcDestructors, gcHooks}: if g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind
else: else:
tk = tyNone # no special casing for strings and seqs tk = tyNone # no special casing for strings and seqs
case tk case tk
of tySequence: of tySequence:
fillSeqOp(a, typ, body, newSymNode(dest).newDeref, newSymNode(src)) fillSeqOp(a, typ, body, d, newSymNode(src))
of tyString: of tyString:
fillStrOp(a, typ, body, newSymNode(dest).newDeref, newSymNode(src)) fillStrOp(a, typ, body, d, newSymNode(src))
else: else:
fillBody(a, typ, body, newSymNode(dest).newDeref, newSymNode(src)) fillBody(a, typ, body, d, newSymNode(src))
var n = newNodeI(nkProcDef, info, bodyPos+1) var n = newNodeI(nkProcDef, info, bodyPos+1)
for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info) for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)
@ -704,7 +783,7 @@ proc isTrival(s: PSym): bool {.inline.} =
proc isEmptyContainer(g: ModuleGraph, t: PType): bool = proc isEmptyContainer(g: ModuleGraph, t: PType): bool =
(t.kind == tyArray and lengthOrd(g.config, t[0]) == 0) or (t.kind == tyArray and lengthOrd(g.config, t[0]) == 0) or
(t.kind == tySequence and t[0].kind == tyError) (t.kind == tySequence and t[0].kind == tyError)
@ -731,8 +810,13 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
# 4. We have a custom destructor. # 4. We have a custom destructor.
# 5. We have a (custom) generic destructor. # 5. We have a (custom) generic destructor.
# we do not generate '=trace' nor '=dispose' procs if we
# have the cycle detection disabled, saves code size.
let lastAttached = if g.config.selectedGC == gcOrc: attachedDispose
else: attachedSink
# we generate the destructor first so that other operators can depend on it: # we generate the destructor first so that other operators can depend on it:
for k in attachedDestructor..attachedSink: for k in attachedDestructor..lastAttached:
if canon.attachedOps[k] == nil: if canon.attachedOps[k] == nil:
discard produceSym(g, c, canon, k, info) discard produceSym(g, c, canon, k, info)
else: else:

View file

@ -116,7 +116,7 @@ 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
gcUnselected, gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcDestructors, gcUnselected, gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcArc, gcOrc,
gcHooks, gcHooks,
gcRefc, gcV2, gcGo gcRefc, gcV2, gcGo
# gcRefc and the GCs that follow it use a write barrier, # gcRefc and the GCs that follow it use a write barrier,

View file

@ -940,7 +940,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
of wAcyclic: of wAcyclic:
noVal(c, it) noVal(c, it)
if sym.typ == nil: invalidPragma(c, it) if sym.typ == nil: invalidPragma(c, it)
# now: ignored else: incl(sym.typ.flags, tfAcyclic)
of wShallow: of wShallow:
noVal(c, it) noVal(c, it)
if sym.typ == nil: invalidPragma(c, it) if sym.typ == nil: invalidPragma(c, it)

View file

@ -17,6 +17,7 @@ import strutils, intsets, tables, ropes, db_sqlite, msgs, options,
## - Dependency computation should use *signature* hashes in order to ## - Dependency computation should use *signature* hashes in order to
## avoid recompiling dependent modules. ## avoid recompiling dependent modules.
## - Patch the rest of the compiler to do lazy loading of proc bodies. ## - Patch the rest of the compiler to do lazy loading of proc bodies.
## - serialize the AST in a smarter way (avoid storing some ASTs twice!)
template db(): DbConn = g.incr.db template db(): DbConn = g.incr.db

View file

@ -1601,7 +1601,7 @@ proc borrowCheck(c: PContext, n, le, ri: PNode) =
proc scopedLifetime(c: PContext; ri: PNode): bool {.inline.} = proc scopedLifetime(c: PContext; ri: PNode): bool {.inline.} =
let n = getRoot(ri, followDeref = false) let n = getRoot(ri, followDeref = false)
result = (ri.kind in nkCallKinds+{nkObjConstr}) or result = (ri.kind in nkCallKinds+{nkObjConstr}) or
(n.kind == nkSym and n.sym.owner == c.p.owner) (n.kind == nkSym and n.sym.owner == c.p.owner and n.sym.kind != skResult)
proc escapes(c: PContext; le: PNode): bool {.inline.} = proc escapes(c: PContext; le: PNode): bool {.inline.} =
# param[].foo[] = self definitely escapes, we don't need to # param[].foo[] = self definitely escapes, we don't need to

View file

@ -872,6 +872,9 @@ proc track(tracked: PEffects, n: PNode) =
createTypeBoundOps(tracked, x[1].typ, n.info) createTypeBoundOps(tracked, x[1].typ, n.info)
setLen(tracked.guards.s, oldFacts) setLen(tracked.guards.s, oldFacts)
if tracked.owner.kind != skMacro: if tracked.owner.kind != skMacro:
# XXX n.typ can be nil in runnableExamples, we need to do something about it.
if n.typ != nil and n.typ.skipTypes(abstractInst).kind == tyRef:
createTypeBoundOps(tracked, n.typ.lastSon, n.info)
createTypeBoundOps(tracked, n.typ, n.info) createTypeBoundOps(tracked, n.typ, n.info)
of nkTupleConstr: of nkTupleConstr:
for i in 0..<n.len: for i in 0..<n.len:

View file

@ -1594,44 +1594,52 @@ proc canonType(c: PContext, t: PType): PType =
else: else:
result = t result = t
proc semOverride(c: PContext, s: PSym, n: PNode) = proc prevDestructor(c: PContext; prevOp: PSym; obj: PType; info: TLineInfo) =
proc prevDestructor(c: PContext; prevOp: PSym; obj: PType; info: TLineInfo) = var msg = "cannot bind another '" & prevOp.name.s & "' to: " & typeToString(obj)
var msg = "cannot bind another '" & prevOp.name.s & "' to: " & typeToString(obj) if sfOverriden notin prevOp.flags:
if sfOverriden notin prevOp.flags: msg.add "; previous declaration was constructed here implicitly: " & (c.config $ prevOp.info)
msg.add "; previous declaration was constructed here implicitly: " & (c.config $ prevOp.info) else:
else: msg.add "; previous declaration was here: " & (c.config $ prevOp.info)
msg.add "; previous declaration was here: " & (c.config $ prevOp.info) localError(c.config, info, errGenerated, msg)
localError(c.config, n.info, errGenerated, msg)
proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
let t = s.typ
var noError = false
let cond = if op == attachedDestructor:
t.len == 2 and t[0] == nil and t[1].kind == tyVar
else:
t.len >= 2 and t[0] == nil
if cond:
var obj = t[1].skipTypes({tyVar})
while true:
incl(obj.flags, tfHasAsgn)
if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon
elif obj.kind == tyGenericInvocation: obj = obj[0]
else: break
if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
obj = canonType(c, obj)
if obj.attachedOps[op] == s:
discard "forward declared destructor"
elif obj.attachedOps[op].isNil and tfCheckedForDestructor notin obj.flags:
obj.attachedOps[op] = s
else:
prevDestructor(c, obj.attachedOps[op], obj, n.info)
noError = true
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
if not noError and sfSystemModule notin s.owner.flags:
localError(c.config, n.info, errGenerated,
"signature for '" & s.name.s & "' must be proc[T: object](x: var T)")
incl(s.flags, sfUsed)
incl(s.flags, sfOverriden)
proc semOverride(c: PContext, s: PSym, n: PNode) =
let name = s.name.s.normalize let name = s.name.s.normalize
case name case name
of "=destroy": of "=destroy":
let t = s.typ bindTypeHook(c, s, n, attachedDestructor)
var noError = false
if t.len == 2 and t[0] == nil and t[1].kind == tyVar:
var obj = t[1][0]
while true:
incl(obj.flags, tfHasAsgn)
if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon
elif obj.kind == tyGenericInvocation: obj = obj[0]
else: break
if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
obj = canonType(c, obj)
if obj.attachedOps[attachedDestructor] == s:
discard "forward declared destructor"
elif obj.destructor.isNil and tfCheckedForDestructor notin obj.flags:
obj.attachedOps[attachedDestructor] = s
else:
prevDestructor(c, obj.destructor, obj, n.info)
noError = true
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `=destroy` can be defined only in the same module with its type (" & obj.typeToString() & ")")
if not noError and sfSystemModule notin s.owner.flags:
localError(c.config, n.info, errGenerated,
"signature for '" & s.name.s & "' must be proc[T: object](x: var T)")
incl(s.flags, sfUsed)
incl(s.flags, sfOverriden)
of "deepcopy", "=deepcopy": of "deepcopy", "=deepcopy":
if s.typ.len == 2 and if s.typ.len == 2 and
s.typ[1].skipTypes(abstractInst).kind in {tyRef, tyPtr} and s.typ[1].skipTypes(abstractInst).kind in {tyRef, tyPtr} and
@ -1698,6 +1706,10 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
if sfSystemModule notin s.owner.flags: if sfSystemModule notin s.owner.flags:
localError(c.config, n.info, errGenerated, localError(c.config, n.info, errGenerated,
"signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)") "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)")
of "=trace":
bindTypeHook(c, s, n, attachedTrace)
of "=dispose":
bindTypeHook(c, s, n, attachedDispose)
else: else:
if sfOverriden in s.flags: if sfOverriden in s.flags:
localError(c.config, n.info, errGenerated, localError(c.config, n.info, errGenerated,

View file

@ -351,7 +351,9 @@ proc canFormAcycleNode(marker: var IntSet, n: PNode, startId: int): bool =
proc canFormAcycleAux(marker: var IntSet, typ: PType, startId: int): bool = proc canFormAcycleAux(marker: var IntSet, typ: PType, startId: int): bool =
result = false result = false
if typ == nil: return if typ == nil: return
if tfAcyclic in typ.flags: return
var t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc}) var t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc})
if tfAcyclic in t.flags: return
case t.kind case t.kind
of tyTuple, tyObject, tyRef, tySequence, tyArray, tyOpenArray, tyVarargs: of tyTuple, tyObject, tyRef, tySequence, tyArray, tyOpenArray, tyVarargs:
if not containsOrIncl(marker, t.id): if not containsOrIncl(marker, t.id):

View file

@ -6017,8 +6017,32 @@ The ``noreturn`` pragma is used to mark a proc that never returns.
acyclic pragma acyclic pragma
-------------- --------------
The ``acyclic`` pragma applies to type declarations. It is deprecated and The ``acyclic`` pragma can be used for object types to mark them as acyclic
ignored. even though they seem to be cyclic. This is an **optimization** for the garbage
collector to not consider objects of this type as part of a cycle:
.. code-block:: nim
type
Node = ref NodeObj
NodeObj {.acyclic.} = object
left, right: Node
data: string
Or if we directly use a ref object:
.. code-block:: nim
type
Node {.acyclic.} = ref object
left, right: Node
data: string
In the example a tree structure is declared with the ``Node`` type. Note that
the type definition is recursive and the GC has to assume that objects of
this type may form a cyclic graph. The ``acyclic`` pragma passes the
information that this cannot happen to the GC. If the programmer uses the
``acyclic`` pragma for data types that are in reality cyclic, the memory leaks
can be the result, but memory safety is preserved.
final pragma final pragma

View file

@ -237,8 +237,8 @@ This uses the configuration defined in ``config\nim.cfg`` for ``lvm_gcc``.
If nimcache already contains compiled code from a different compiler for the same project, If nimcache already contains compiled code from a different compiler for the same project,
add the ``-f`` flag to force all files to be recompiled. add the ``-f`` flag to force all files to be recompiled.
The default compiler is defined at the top of ``config\nim.cfg``. Changing this setting The default compiler is defined at the top of ``config\nim.cfg``.
affects the compiler used by ``koch`` to (re)build Nim. Changing this setting affects the compiler used by ``koch`` to (re)build Nim.
Cross compilation Cross compilation
@ -557,9 +557,9 @@ For example, to generate code for an `AVR`:idx: processor use this command::
For the ``standalone`` target one needs to provide For the ``standalone`` target one needs to provide
a file ``panicoverride.nim``. a file ``panicoverride.nim``.
See ``tests/manyloc/standalone/panicoverride.nim`` for an example See ``tests/manyloc/standalone/panicoverride.nim`` for an example
implementation. Additionally, users should specify the implementation. Additionally, users should specify the
amount of heap space to use with the ``-d:StandaloneHeapSize=<size>`` amount of heap space to use with the ``-d:StandaloneHeapSize=<size>``
command line switch. Note that the total heap size will be command line switch. Note that the total heap size will be
``<size> * sizeof(float64)``. ``<size> * sizeof(float64)``.

View file

@ -3112,11 +3112,13 @@ when not defined(js):
destructor: pointer destructor: pointer
size: int size: int
name: cstring name: cstring
traceImpl: pointer
disposeImpl: pointer
PNimType = ptr TNimType PNimType = ptr TNimType
when defined(nimSeqsV2) and not defined(nimscript): when defined(nimSeqsV2) and not defined(nimscript):
include "core/strs" include "system/strs_v2"
include "core/seqs" include "system/seqs_v2"
{.pop.} {.pop.}
@ -3139,7 +3141,7 @@ when not defined(JS) and not defined(nimscript):
{.pop.} {.pop.}
when defined(nimV2): when defined(nimV2):
include core/runtime_v2 include system/refs_v2
import system/assertions import system/assertions
export assertions export assertions

View file

@ -0,0 +1,232 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2019 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# Cycle collector based on Lins' Jump Stack and other ideas,
# see for example:
# https://pdfs.semanticscholar.org/f2b2/0d168acf38ff86305809a55ef2c5d6ebc787.pdf
# Further refinement in 2008 by the notion of "critical links", see
# "Cyclic reference counting" by Rafael Dueire Lins
# R.D. Lins / Information Processing Letters 109 (2008) 71–78
const
colGreen = 0b000
colYellow = 0b001
colRed = 0b010
jumpStackFlag = 0b100 # stored in jumpstack
rcShift = 3 # shift by rcShift to get the reference counter
colorMask = 0b011
type
TraceProc = proc (p, env: pointer) {.nimcall, benign.}
DisposeProc = proc (p: pointer) {.nimcall, benign.}
template color(c): untyped = c.rc and colorMask
template setColor(c, col) =
when col == colGreen:
c.rc = c.rc and not colorMask
else:
c.rc = c.rc and not colorMask or col
proc nimIncRefCyclic(p: pointer) {.compilerRtl, inl.} =
let h = head(p)
inc h.rc, rcIncrement
h.setColor colYellow # mark as potential cycle!
proc markCyclic*[T](x: ref T) {.inline.} =
## Mark the underlying object as a candidate for cycle collections.
## Experimental API. Do not use!
let h = head(cast[pointer](x))
h.setColor colYellow
type
CellTuple = (Cell, PNimType)
CellArray = ptr UncheckedArray[CellTuple]
CellSeq = object
len, cap: int
d: CellArray
GcEnv = object
traceStack: CellSeq
jumpStack: CellSeq
# ------------------- cell seq handling --------------------------------------
proc add(s: var CellSeq, c: Cell; t: PNimType) {.inline.} =
if s.len >= s.cap:
s.cap = s.cap * 3 div 2
when defined(useMalloc):
var d = cast[CellArray](c_malloc(uint(s.cap * sizeof(CellTuple))))
else:
var d = cast[CellArray](alloc(s.cap * sizeof(CellTuple)))
copyMem(d, s.d, s.len * sizeof(CellTuple))
when defined(useMalloc):
c_free(s.d)
else:
dealloc(s.d)
s.d = d
# XXX: realloc?
s.d[s.len] = (c, t)
inc(s.len)
proc init(s: var CellSeq, cap: int = 1024) =
s.len = 0
s.cap = cap
when defined(useMalloc):
s.d = cast[CellArray](c_malloc(uint(s.cap * sizeof(CellTuple))))
else:
s.d = cast[CellArray](alloc(s.cap * sizeof(CellTuple)))
proc deinit(s: var CellSeq) =
when defined(useMalloc):
c_free(s.d)
else:
dealloc(s.d)
s.d = nil
s.len = 0
s.cap = 0
proc pop(s: var CellSeq): (Cell, PNimType) =
result = s.d[s.len-1]
dec s.len
# ----------------------------------------------------------------------------
proc trace(s: Cell; desc: PNimType; j: var GcEnv) {.inline.} =
if desc.traceImpl != nil:
var p = s +! sizeof(RefHeader)
cast[TraceProc](desc.traceImpl)(p, addr(j))
proc free(s: Cell; desc: PNimType) {.inline.} =
when traceCollector:
cprintf("[From ] %p rc %ld color %ld in jumpstack %ld\n", s, s.rc shr rcShift,
s.color, s.rc and jumpStackFlag)
var p = s +! sizeof(RefHeader)
if desc.disposeImpl != nil:
cast[DisposeProc](desc.disposeImpl)(p)
nimRawDispose(p)
proc collect(s: Cell; desc: PNimType; j: var GcEnv) =
if s.color == colRed:
s.setColor colGreen
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
if t.color == colRed:
t.setColor colGreen
trace(t, desc, j)
free(t, desc)
free(s, desc)
#cprintf("[Cycle free] %p %ld\n", s, s.rc shr rcShift)
proc markRed(s: Cell; desc: PNimType; j: var GcEnv) =
if s.color != colRed:
s.setColor colRed
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
when traceCollector:
cprintf("[Cycle dec] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
dec t.rc, rcIncrement
if (t.rc and not rcMask) >= 0 and (t.rc and jumpStackFlag) == 0:
t.rc = t.rc or jumpStackFlag
when traceCollector:
cprintf("[Now in jumpstack] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
j.jumpStack.add(t, desc)
if t.color != colRed:
t.setColor colRed
trace(t, desc, j)
proc scanGreen(s: Cell; desc: PNimType; j: var GcEnv) =
s.setColor colGreen
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
if t.color != colGreen:
t.setColor colGreen
trace(t, desc, j)
inc t.rc, rcIncrement
when traceCollector:
cprintf("[Cycle inc] %p %ld color %ld\n", t, t.rc shr rcShift, t.color)
proc nimTraceRef(p: pointer; desc: PNimType; env: pointer) {.compilerRtl.} =
if p != nil:
var t = head(p)
var j = cast[ptr GcEnv](env)
j.traceStack.add(t, desc)
proc nimTraceRefDyn(p: pointer; env: pointer) {.compilerRtl.} =
if p != nil:
let desc = cast[ptr PNimType](p)[]
var t = head(p)
var j = cast[ptr GcEnv](env)
j.traceStack.add(t, desc)
proc scan(s: Cell; desc: PNimType; j: var GcEnv) =
when traceCollector:
cprintf("[doScanGreen] %p %ld\n", s, s.rc shr rcShift)
# even after trial deletion, `s` is still alive, so undo
# the decrefs by calling `scanGreen`:
if (s.rc and not rcMask) >= 0:
scanGreen(s, desc, j)
s.setColor colYellow
else:
# first we have to repair all the nodes we have seen
# that are still alive; we also need to mark what they
# refer to as alive:
while j.jumpStack.len > 0:
let (t, desc) = j.jumpStack.pop
# not in jump stack anymore!
t.rc = t.rc and not jumpStackFlag
if t.color == colRed and (t.rc and not rcMask) >= 0:
scanGreen(t, desc, j)
t.setColor colYellow
when traceCollector:
cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
# we have proven that `s` and its subgraph are dead, so we can
# collect these nodes:
collect(s, desc, j)
proc traceCycle(s: Cell; desc: PNimType) {.noinline.} =
when traceCollector:
cprintf("[traceCycle] %p %ld\n", s, s.rc shr rcShift)
var j: GcEnv
init j.jumpStack
init j.traceStack
markRed(s, desc, j)
scan(s, desc, j)
while j.jumpStack.len > 0:
let (t, desc) = j.jumpStack.pop
# not in jump stack anymore!
t.rc = t.rc and not jumpStackFlag
deinit j.jumpStack
deinit j.traceStack
proc nimDecRefIsLastCyclicDyn(p: pointer): bool {.compilerRtl, inl.} =
if p != nil:
var cell = head(p)
if (cell.rc and not rcMask) == 0:
result = true
#cprintf("[DESTROY] %p\n", p)
else:
dec cell.rc, rcIncrement
if cell.color == colYellow:
let desc = cast[ptr PNimType](p)[]
traceCycle(cell, desc)
# According to Lins it's correct to do nothing else here.
#cprintf("[DeCREF] %p\n", p)
proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimType): bool {.compilerRtl, inl.} =
if p != nil:
var cell = head(p)
if (cell.rc and not rcMask) == 0:
result = true
#cprintf("[DESTROY] %p %s\n", p, desc.name)
else:
dec cell.rc, rcIncrement
if cell.color == colYellow: traceCycle(cell, desc)
#cprintf("[DeCREF] %p %s %ld\n", p, desc.name, cell.rc)

View file

@ -499,7 +499,8 @@ else:
when not defined(gcRegions): when not defined(gcRegions):
include "system/alloc" include "system/alloc"
include "system/cellsets" when not usesDestructors:
include "system/cellsets"
when not leakDetector and not useCellIds: when not leakDetector and not useCellIds:
sysAssert(sizeof(Cell) == sizeof(FreeCell), "sizeof FreeCell") sysAssert(sizeof(Cell) == sizeof(FreeCell), "sizeof FreeCell")
when compileOption("gc", "v2"): when compileOption("gc", "v2"):

View file

@ -1,3 +1,12 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2019 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
#[ #[
In this new runtime we simplify the object layouts a bit: The runtime type In this new runtime we simplify the object layouts a bit: The runtime type
information is only accessed for the objects that have it and it's always information is only accessed for the objects that have it and it's always
@ -25,11 +34,16 @@ hash of ``package & "." & module & "." & name`` to save space.
]# ]#
const
rcIncrement = 0b1000 # so that lowest 3 bits are not touched
rcMask = 0b111
type type
RefHeader = object RefHeader = object
rc: int # the object header is now a single RC field. rc: int # the object header is now a single RC field.
# we could remove it in non-debug builds for the 'owned ref' # we could remove it in non-debug builds for the 'owned ref'
# design but this seems unwise. # design but this seems unwise.
Cell = ptr RefHeader
template `+!`(p: pointer, s: int): pointer = template `+!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) +% s) cast[pointer](cast[int](p) +% s)
@ -37,8 +51,11 @@ template `+!`(p: pointer, s: int): pointer =
template `-!`(p: pointer, s: int): pointer = template `-!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) -% s) cast[pointer](cast[int](p) -% s)
template head(p: pointer): ptr RefHeader = template head(p: pointer): Cell =
cast[ptr RefHeader](cast[int](p) -% sizeof(RefHeader)) cast[Cell](cast[int](p) -% sizeof(RefHeader))
const
traceCollector = defined(traceArc)
var allocs*: int var allocs*: int
@ -56,28 +73,22 @@ proc nimNewObj(size: int): pointer {.compilerRtl.} =
atomicInc allocs atomicInc allocs
else: else:
inc allocs inc allocs
when traceCollector:
cprintf("[Allocated] %p\n", result -! sizeof(RefHeader))
proc nimDecWeakRef(p: pointer) {.compilerRtl, inl.} = proc nimDecWeakRef(p: pointer) {.compilerRtl, inl.} =
when hasThreadSupport: dec head(p).rc, rcIncrement
atomicDec head(p).rc
else:
dec head(p).rc
proc nimIncRef(p: pointer) {.compilerRtl, inl.} = proc nimIncRef(p: pointer) {.compilerRtl, inl.} =
when hasThreadSupport: inc head(p).rc, rcIncrement
atomicInc head(p).rc #cprintf("[INCREF] %p\n", p)
else:
inc head(p).rc
#cprintf("[INCREF] %p\n", p)
proc nimRawDispose(p: pointer) {.compilerRtl.} = proc nimRawDispose(p: pointer) {.compilerRtl.} =
when not defined(nimscript): when not defined(nimscript):
when traceCollector:
cprintf("[Freed] %p\n", p -! sizeof(RefHeader))
when defined(nimOwnedEnabled): when defined(nimOwnedEnabled):
when hasThreadSupport: if head(p).rc >= rcIncrement:
let hasDanglingRefs = atomicLoadN(addr head(p).rc, ATOMIC_RELAXED) != 0
else:
let hasDanglingRefs = head(p).rc != 0
if hasDanglingRefs:
cstderr.rawWrite "[FATAL] dangling references exist\n" cstderr.rawWrite "[FATAL] dangling references exist\n"
quit 1 quit 1
when defined(useMalloc): when defined(useMalloc):
@ -108,21 +119,19 @@ proc nimDestroyAndDispose(p: pointer) {.compilerRtl.} =
cstderr.rawWrite "has destructor!\n" cstderr.rawWrite "has destructor!\n"
nimRawDispose(p) nimRawDispose(p)
when defined(gcOrc):
include cyclicrefs_v2
proc nimDecRefIsLast(p: pointer): bool {.compilerRtl, inl.} = proc nimDecRefIsLast(p: pointer): bool {.compilerRtl, inl.} =
if p != nil: if p != nil:
when hasThreadSupport: var cell = head(p)
if atomicLoadN(addr head(p).rc, ATOMIC_RELAXED) == 0: if (cell.rc and not rcMask) == 0:
result = true result = true
else: #cprintf("[DESTROY] %p\n", p)
discard atomicDec(head(p).rc)
else: else:
if head(p).rc == 0: dec cell.rc, rcIncrement
result = true # According to Lins it's correct to do nothing else here.
#cprintf("[DESTROY] %p\n", p) #cprintf("[DeCREF] %p\n", p)
else:
dec head(p).rc
# According to Lins it's correct to do nothing else here.
#cprintf("[DeCREF] %p\n", p)
proc GC_unref*[T](x: ref T) = proc GC_unref*[T](x: ref T) =
## New runtime only supports this operation for 'ref T'. ## New runtime only supports this operation for 'ref T'.

View file

@ -18,7 +18,7 @@ type
when defined(nimv2): when defined(nimv2):
import core / allocators import system / allocators
type type
WideCString* = ptr UncheckedArray[Utf16Char] WideCString* = ptr UncheckedArray[Utf16Char]

View file

@ -179,9 +179,9 @@ proc gcTests(r: var TResults, cat: Category, options: string) =
" -d:release -d:useRealtimeGC", cat) " -d:release -d:useRealtimeGC", cat)
when filename != "gctest": when filename != "gctest":
testSpec r, makeTest("tests/gc" / filename, options & testSpec r, makeTest("tests/gc" / filename, options &
" --gc:arc", cat) " --gc:orc", cat)
testSpec r, makeTest("tests/gc" / filename, options & testSpec r, makeTest("tests/gc" / filename, options &
" --gc:arc -d:release", cat) " --gc:orc -d:release", cat)
template testWithoutBoehm(filename: untyped) = template testWithoutBoehm(filename: untyped) =
testWithoutMs filename testWithoutMs filename

View file

@ -170,7 +170,8 @@ proc parseSpec*(filename: string): TSpec =
of "tcolumn": of "tcolumn":
discard parseInt(e.value, result.tcolumn) discard parseInt(e.value, result.tcolumn)
of "output": of "output":
result.outputCheck = ocEqual if result.outputCheck != ocSubstr:
result.outputCheck = ocEqual
result.output = strip(e.value) result.output = strip(e.value)
of "input": of "input":
result.input = e.value result.input = e.value
@ -200,6 +201,8 @@ proc parseSpec*(filename: string): TSpec =
when defined(linux) and sizeof(int) == 8: when defined(linux) and sizeof(int) == 8:
result.useValgrind = parseCfgBool(e.value) result.useValgrind = parseCfgBool(e.value)
result.unjoinable = true result.unjoinable = true
if result.useValgrind:
result.outputCheck = ocSubstr
else: else:
# Windows lacks valgrind. Silly OS. # Windows lacks valgrind. Silly OS.
# Valgrind only supports OSX <= 17.x # Valgrind only supports OSX <= 17.x

View file

@ -449,7 +449,7 @@ proc testSpecHelper(r: var TResults, test: TTest, expected: TSpec, target: TTarg
else: else:
exeCmd = exeFile exeCmd = exeFile
if expected.useValgrind: if expected.useValgrind:
args = exeCmd & args args = @["--error-exitcode=1"] & exeCmd & args
exeCmd = "valgrind" exeCmd = "valgrind"
var (_, buf, exitCode) = execCmdEx2(exeCmd, args, input = expected.input) var (_, buf, exitCode) = execCmdEx2(exeCmd, args, input = expected.input)
# Treat all failure codes from nodejs as 1. Older versions of nodejs used # Treat all failure codes from nodejs as 1. Older versions of nodejs used

View file

@ -1,6 +1,6 @@
discard """ discard """
output: '''leak: true''' output: '''leak: false'''
cmd: '''nim c --gc:arc $file''' cmd: '''nim c --gc:orc $file'''
""" """
type type
@ -19,11 +19,8 @@ proc addX(x: T; child: T) =
proc main(rootName: string) = proc main(rootName: string) =
var root = create() var root = create()
root.data = rootName root.data = rootName
# this implies we do the refcounting wrong. We should leak memory here
# and not create a destruction cycle:
root.addX root root.addX root
let mem = getOccupiedMem() let mem = getOccupiedMem()
main("yeah") main("yeah")
# since we created a retain cycle, we MUST leak memory here:
echo "leak: ", getOccupiedMem() - mem > 0 echo "leak: ", getOccupiedMem() - mem > 0

View file

@ -0,0 +1,70 @@
discard """
output: "MEM 0"
cmd: "nim c --gc:arc $file"
"""
type
RefNode = ref object
le, ri: RefNode
name: char
proc edge0(a, b: RefNode) =
if a.le == nil: a.le = b
else: a.ri = b
proc createNode0(name: char): RefNode =
new result
result.name = name
proc main0 =
let r = createNode0('R')
let c = createNode0('C')
c.edge0 r
type
NodeDesc = object
le, ri: Node
name: char
Node = ref NodeDesc
proc edge(a, b: Node) =
if a.le == nil: a.le = b
else: a.ri = b
proc createNode(name: char): Node =
new result
result.name = name
proc main =
let r = createNode('R')
let c = createNode('C')
c.edge r
type
NodeB = ref NodeBo
NodeBo = object
le, ri: NodeB
name: char
proc edge(a, b: NodeB) =
if a.le == nil: a.le = b
else: a.ri = b
proc createNodeB(name: char): NodeB =
new result
result.name = name
proc mainB =
let r = createNodeB('R')
let c = createNodeB('C')
c.edge r
let memB = getOccupiedMem()
main0()
main()
mainB()
echo "MEM ", getOccupiedMem() - memB

View file

@ -0,0 +1,54 @@
discard """
output: '''asdas
processClient end
false
MEMORY 0
'''
cmd: '''nim c --gc:orc $file'''
"""
type
PAsyncHttpServer = ref object
value: string
PFutureBase = ref object
callback: proc () {.closure.}
value: string
failed: bool
proc accept(server: PAsyncHttpServer): PFutureBase =
new(result)
result.callback = proc () =
discard
server.value = "hahaha"
proc processClient(): PFutureBase =
new(result)
proc serve(server: PAsyncHttpServer): PFutureBase =
iterator serveIter(): PFutureBase {.closure.} =
echo server.value
while true:
var acceptAddrFut = server.accept()
yield acceptAddrFut
var fut = acceptAddrFut.value
var f = processClient()
when true:
f.callback =
proc () =
echo("processClient end")
echo(f.failed)
yield f
var x = serveIter
for i in 0 .. 1:
result = x()
if result.callback != nil:
result.callback()
let mem = getOccupiedMem()
proc main =
discard serve(PAsyncHttpServer(value: "asdas"))
main()
echo "MEMORY ", getOccupiedMem() - mem

View file

@ -4,7 +4,7 @@ discard """
3 3 alloc/dealloc pairs: 0''' 3 3 alloc/dealloc pairs: 0'''
""" """
import core / allocators import system / allocators
import system / ansi_c import system / ansi_c
import random import random

View file

@ -0,0 +1,53 @@
discard """
output: "MEM 0"
cmd: "nim c --gc:orc $file"
"""
type
Node = ref object of RootObj
le, ri: Node
name: char
proc edge(a, b: Node) =
if a.le == nil: a.le = b
else: a.ri = b
proc createNode(name: char): Node =
new result
result.name = name
#[
+---------+ +------+
| | | |
| A +----->+ <------+-------------+
+--+------+ | | | |
| | | | C |
| | R | | |
+--v------+ | | +-------------+
| | | | ^
| B <------+ | |
| | | +--------+
+---------+ | |
+------+
]#
proc main =
let a = createNode('A')
let b = createNode('B')
let r = createNode('R')
let c = createNode('C')
a.edge b
a.edge r
r.edge b
r.edge c
c.edge r
let mem = getOccupiedMem()
main()
echo "MEM ", getOccupiedMem() - mem

View file

@ -0,0 +1,18 @@
discard """
output: "MEM 0"
cmd: "nim c --gc:orc $file"
"""
type
Node = ref object
kids: seq[Node]
data: string
proc main(x: int) =
var n = Node(kids: @[], data: "3" & $x)
let m = n
n.kids.add m
let mem = getOccupiedMem()
main(90)
echo "MEM ", getOccupiedMem() - mem

View file

@ -0,0 +1,64 @@
discard """
output: '''BEGIN
END
END 2
0'''
cmd: '''nim c --gc:orc $file'''
"""
# extracted from thavlak.nim
type
BasicBlock = ref object
inEdges: seq[BasicBlock]
outEdges: seq[BasicBlock]
name: int
proc newBasicBlock(name: int): BasicBlock =
result = BasicBlock(
inEdges: newSeq[BasicBlock](),
outEdges: newSeq[BasicBlock](),
name: name
)
type
Cfg = object
basicBlockMap: seq[BasicBlock]
startNode: BasicBlock
proc newCfg(): Cfg =
result = Cfg(
basicBlockMap: newSeq[BasicBlock](),
startNode: nil)
proc createNode(cfg: var Cfg, name: int): BasicBlock =
if name < cfg.basicBlockMap.len:
result = cfg.basicBlockMap[name]
else:
result = newBasicBlock(name)
cfg.basicBlockMap.setLen name+1
cfg.basicBlockMap[name] = result
proc newBasicBlockEdge(cfg: var Cfg, fromName, toName: int) =
echo "BEGIN"
let fr = cfg.createNode(fromName)
let to = cfg.createNode(toName)
fr.outEdges.add(to)
to.inEdges.add(fr)
proc run(cfg: var Cfg) =
cfg.startNode = cfg.createNode(0) # RC = 2
newBasicBlockEdge(cfg, 0, 1) #
echo "END"
discard cfg.createNode(1)
proc main =
var c = newCfg()
c.run
echo "END 2"
let mem = getOccupiedMem()
main()
echo getOccupiedMem() - mem

View file

@ -13,7 +13,7 @@ true
41 41''' 41 41'''
""" """
import allocators import system / allocators
include system / ansi_c include system / ansi_c
proc main = proc main =

View file

@ -11,7 +11,7 @@ test
''' '''
""" """
import core / allocators import system / allocators
import system / ansi_c import system / ansi_c
import tables import tables
@ -132,5 +132,5 @@ proc xx(xml: string): MyObject =
result.x = xml result.x = xml
defer: echo stream defer: echo stream
discard xx("test") discard xx("test")

View file

@ -1,11 +1,12 @@
discard """ discard """
cmd: '''nim c --newruntime $file''' valgrind: true
cmd: '''nim c --newruntime -d:useMalloc $file'''
output: '''422 422''' output: '''422 422'''
""" """
import strutils, os, std / wordwrap import strutils, os, std / wordwrap
import core / allocators import system / allocators
import system / ansi_c import system / ansi_c
# bug #11004 # bug #11004

View file

@ -0,0 +1,26 @@
discard """
output: '''abcsuffix
xyzsuffix'''
cmd: '''nim c --gc:arc $file'''
"""
proc select(cond: bool; a, b: sink string): string =
if cond:
result = a # moves a into result
else:
result = b # moves b into result
proc test(param: string; cond: bool) =
var x = "abc" & param
var y = "xyz" & param
# possible self-assignment:
x = select(cond, x, y)
echo x
# 'select' must communicate what parameter has been
# consumed. We cannot simply generate:
# (select(...); wasMoved(x); wasMoved(y))
test("suffix", true)
test("suffix", false)

View file

@ -8,7 +8,7 @@ hello
2 2 alloc/dealloc pairs: 0''' 2 2 alloc/dealloc pairs: 0'''
""" """
import core / allocators import system / allocators
import system / ansi_c import system / ansi_c
proc main(): owned(proc()) = proc main(): owned(proc()) =

View file

@ -4,7 +4,7 @@ discard """
line: 49 line: 49
""" """
import core / allocators import system / allocators
import system / ansi_c import system / ansi_c
type type

View file

@ -6,7 +6,7 @@ discard """
import strutils, math import strutils, math
import system / ansi_c import system / ansi_c
import core / allocators import system / allocators
proc mainA = proc mainA =
try: try:

View file

@ -5,7 +5,7 @@ clicked!
1 1 alloc/dealloc pairs: 0''' 1 1 alloc/dealloc pairs: 0'''
""" """
import core / allocators import system / allocators
import system / ansi_c import system / ansi_c
type type

View file

@ -5,7 +5,7 @@ clicked!
6 6 alloc/dealloc pairs: 0''' 6 6 alloc/dealloc pairs: 0'''
""" """
import core / allocators import system / allocators
import system / ansi_c import system / ansi_c
type type

View file

@ -53,11 +53,11 @@ import
type type
PNode = ref TNode PNode = ref TNode
TNode {.final.} = object TNode {.final, acyclic.} = object
left, right: PNode left, right: PNode
i, j: int i, j: int
proc newNode(L, r: PNode): PNode = proc newNode(L, r: sink PNode): PNode =
new(result) new(result)
result.left = L result.left = L
result.right = r result.right = r
@ -166,9 +166,13 @@ proc main() =
var elapsed = epochTime() - t var elapsed = epochTime() - t
printDiagnostics() printDiagnostics()
echo("Completed in " & $elapsed & "ms. Success!") echo("Completed in " & $elapsed & "s. Success!")
when defined(GC_setMaxPause): when defined(GC_setMaxPause):
GC_setMaxPause 2_000 GC_setMaxPause 2_000
when defined(gcDestructors):
let mem = getOccupiedMem()
main() main()
when defined(gcDestructors):
doAssert getOccupiedMem() == mem

View file

@ -394,18 +394,20 @@ proc run(self: var LoopTesterApp) =
echo "Constructing CFG..." echo "Constructing CFG..."
var n = 2 var n = 2
for parlooptrees in 1..10: when not defined(gcOrc):
discard self.cfg.createNode(n + 1) # currently cycle detection is so slow that we disable this part
self.buildConnect(2, n + 1) for parlooptrees in 1..10:
n += 1 discard self.cfg.createNode(n + 1)
for i in 1..100: self.buildConnect(2, n + 1)
var top = n n += 1
n = self.buildStraight(n, 1) for i in 1..100:
for j in 1..25: n = self.buildBaseLoop(n) var top = n
var bottom = self.buildStraight(n, 1) n = self.buildStraight(n, 1)
self.buildConnect n, top for j in 1..25: n = self.buildBaseLoop(n)
n = bottom var bottom = self.buildStraight(n, 1)
self.buildConnect(n, 1) self.buildConnect n, top
n = bottom
self.buildConnect(n, 1)
echo "Performing Loop Recognition\n1 Iteration" echo "Performing Loop Recognition\n1 Iteration"
@ -428,5 +430,11 @@ proc run(self: var LoopTesterApp) =
echo("Total memory available: " & formatSize(getTotalMem()) & " bytes") echo("Total memory available: " & formatSize(getTotalMem()) & " bytes")
echo("Free memory: " & formatSize(getFreeMem()) & " bytes") echo("Free memory: " & formatSize(getFreeMem()) & " bytes")
var l = newLoopTesterApp() proc main =
l.run var l = newLoopTesterApp()
l.run
let mem = getOccupiedMem()
main()
when defined(gcOrc):
doAssert getOccupiedMem() == mem