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
parent 5848f0042c
commit 83a736a34a
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
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
# tyArray type represeting a varargs list
tfNoSideEffect, # procedure type does not allow side effects
@@ -537,6 +537,7 @@ type
tfContravariant # contravariant generic param
tfCheckedForDestructor # type was checked for having a destructor.
# If it has one, t.destructor is not nil.
tfAcyclic # object type was annotated as .acyclic
TTypeFlags* = set[TTypeFlag]
@@ -635,7 +636,7 @@ type
mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast,
mNewString, mNewStringOfCap, mParseBiggestFloat,
mMove, mWasMoved, mDestroy,
mDefault, mUnown, mAccessEnv, mReset,
mDefault, mUnown, mAccessEnv, mAccessTypeInfo, mReset,
mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs,
mRef, mPtr, mVar, mDistinct, mVoid, mTuple,
mOrdinal,
@@ -870,6 +871,8 @@ type
attachedDestructor,
attachedAsgn,
attachedSink,
attachedTrace,
attachedDispose,
attachedDeepCopy
TType* {.acyclic.} = object of TIdObj # \
@@ -1298,7 +1301,8 @@ const
UnspecifiedLockLevel* = TLockLevel(-1'i16)
MaxLockLevel* = 1000'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 =
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, {})
else:
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
# finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
# convention at least:
@@ -1584,6 +1584,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
gcUsage(p.config, e)
proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) =
discard cgsym(p.module, "TNimType")
let t = e[1].typ
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)
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) =
case 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 mDestroy: genDestroy(p, e)
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:
localError(p.config, e.info, "invalid context for 'toOpenArray'; " &
"'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)
let dest = skipTypes(n.typ, abstractPtrs)
if optObjCheck in p.options and not isObjLackingTypeField(dest):
var r = rdLoc(a)
var nilCheck: Rope = nil
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)
var nilCheck = Rope(nil)
let r = rdMType(p, a, nilCheck)
let checkFor = if optTinyRtti in p.config.globalOptions:
genTypeInfo2Name(p.module, dest)
else:
genTypeInfo(p.module, dest, n.info)
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])
else:
linefmt(p, cpsStmts, "#chckObj($1.m_type, $2);$n",
linefmt(p, cpsStmts, "#chckObj($1, $2);$n",
[r, checkFor])
if n[0].typ.kind != tyObject:
putIntoDest(p, d, n,

View File

@@ -1273,30 +1273,44 @@ proc genTypeInfo2Name(m: BModule; t: PType): Rope =
it = it[0]
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) =
assert t.kind == tyObject
if incompleteType(t):
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):
proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp): Rope =
let theProc = t.attachedOps[op]
if theProc != nil and not isTrivialProc(theProc):
# 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
# 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,
"the destructor that is turned into a finalizer needs " &
"to have the 'nimcall' calling convention")
genProc(m, t.destructor)
d = t.destructor.loc.r
theProc.name.s & " needs to have the 'nimcall' calling convention")
genProc(m, theProc)
result = theProc.loc.r
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[cfsTypeInit3].addf("$1.destructor = (void*)$2; $1.size = sizeof($3); $1.name = $4;$n", [
name, d, getTypeDesc(m, t), genTypeInfo2Name(m, t)])
let destroyImpl = genHook(m, t, info, attachedDestructor)
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 =
let origType = t
@@ -1333,49 +1347,51 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope =
return prefixTI.rope & result & ")".rope
m.g.typeInfoMarker[sig] = (str: result, owner: owner)
case t.kind
of tyEmpty, tyVoid: result = rope"0"
of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent:
genTypeInfoAuxBase(m, t, t, result, rope"0", info)
of tyStatic:
if t.n != nil: result = genTypeInfo(m, lastSon t, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
of tyUserTypeClasses:
internalAssert m.config, t.isResolvedUserTypeClass
return genTypeInfo(m, t.lastSon, info)
of tyProc:
if t.callConv != ccClosure:
if optTinyRtti in m.config.globalOptions:
genTypeInfoV2(m, t, origType, result, info)
else:
case t.kind
of tyEmpty, tyVoid: result = rope"0"
of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent:
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:
of tyStatic:
if t.n != nil: result = genTypeInfo(m, lastSon t, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
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)
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:
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:
let markerProc = genTraverseProc(m, origType, sig)
m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
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:
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:
genObjectInfo(m, t, origType, result, info)
of tyTuple:
# if t.n != nil: genObjectInfo(m, t, result)
# else:
# BUGFIX: use consistently RTTI without proper field names; otherwise
# results are not deterministic!
genTupleInfo(m, t, origType, result, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
of tyTuple:
# if t.n != nil: genObjectInfo(m, t, result)
# else:
# BUGFIX: use consistently RTTI without proper field names; otherwise
# results are not deterministic!
genTupleInfo(m, t, origType, result, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
if t.attachedOps[attachedDeepCopy] != nil:
genDeepCopyProc(m, t.attachedOps[attachedDeepCopy], result)
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 "markandsweep": result = conf.selectedGC == gcMarkAndSweep
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 "go": result = conf.selectedGC == gcGo
of "none": result = conf.selectedGC == gcNone
@@ -455,8 +456,18 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.selectedGC = gcMarkAndSweep
defineSymbol(conf.symbols, "gcmarkandsweep")
of "destructors", "arc":
conf.selectedGC = gcDestructors
conf.selectedGC = gcArc
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, optTinyRtti
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 =
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:
if c.otherRead == nil: discard isLastRead(ri, c)
checkForErrorPragma(c, t, ri, "=")
@@ -409,7 +409,7 @@ proc isCursor(n: PNode): bool =
result = false
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]
if value.kind == nkClosure:
value = value[1]
@@ -512,7 +512,7 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode =
result[i] = p(n[i], c, normal)
if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
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])
result = newTree(nkStmtList, destroyOld, result)
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.
# This seems to be generally correct but since it's a bit risky it's disabled
# for now.
# XXX This is wrong for the 'hamming' test, so remove this logic again.
let fieldType = if isDefined(c.graph.config, "nimCycleBreaker"):
c.getEnvTypeForOwnerUp(dep, info) #getHiddenParam(dep).typ
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) =
for i in 0..<t.len:
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 =
result = newNodeI(nkDotExpr, x.info, 2)
@@ -58,18 +59,19 @@ proc newAsgnStmt(le, ri: PNode): PNode =
result[1] = ri
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)
proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode) =
case n.kind
of nkSym:
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
c.g.config.selectedGC in {gcDestructors, gcHooks}:
defaultOp(c, f.typ, body, x.dotField(f), y.dotField(f))
c.g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
defaultOp(c, f.typ, body, x.dotField(f), b)
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 nkRecCase:
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.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:
# localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s)
result = newNodeI(nkCall, x.info)
result.add newSymNode(op)
result.add genAddr(g, x)
result.add y
if op.typ.sons[1].kind == tyVar:
result.add genAddr(g, x)
else:
result.add x
if y != nil:
result.add y
proc newOpCall(op: PSym; x: PNode): PNode =
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
({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 =
if c.c != nil and typeInst != nil:
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:
# markUsed(c.g.config, c.info, op, c.g.usageSym)
onUse(c.info, op)
body.add newAsgnCall(c.g, op, x, y)
body.add newHookCall(c.g, op, x, y)
result = true
elif tfHasAsgn in t.flags:
var op: PSym
@@ -189,7 +199,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
#debug(t)
#return false
assert op.ast[genericParamsPos].kind == nkEmpty
body.add newAsgnCall(c.g, op, x, y)
body.add newHookCall(c.g, op, x, y)
result = true
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)
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)
doAssert op != nil
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)
result = true
#result = addDestructorCall(c, t, body, x)
of attachedAsgn:
result = considerAsgnOrSink(c, t, body, x, y, t.assignment)
of attachedSink:
result = considerAsgnOrSink(c, t, body, x, y, t.asink)
of attachedAsgn, attachedSink, attachedTrace:
result = considerAsgnOrSink(c, t, body, x, y, t.attachedOps[c.kind])
of attachedDispose:
result = considerAsgnOrSink(c, t, body, x, nil, t.attachedOps[c.kind])
of attachedDeepCopy:
let op = t.attachedOps[attachedDeepCopy]
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 whileLoop = genWhileLoop(c, i, x)
let elemType = t.lastSon
fillBody(c, elemType, whileLoop[1], x.at(i, elemType),
y.at(i, elemType))
let b = if c.kind == attachedTrace: y else: y.at(i, elemType)
fillBody(c, elemType, whileLoop[1], x.at(i, elemType), b)
addIncStmt(c, whileLoop[1], i)
body.add whileLoop
@@ -330,6 +340,11 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# destroy all elements:
forallElements(c, t, body, x, y)
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) =
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
of attachedAsgn, attachedDeepCopy:
doAssert t.assignment != nil
body.add newAsgnCall(c.g, t.assignment, x, y)
body.add newHookCall(c.g, t.assignment, x, y)
of attachedSink:
# we always inline the move for better performance:
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:
when false:
doAssert t.asink != nil
body.add newAsgnCall(c.g, t.asink, x, y)
body.add newHookCall(c.g, t.asink, x, y)
of attachedDestructor:
doAssert t.destructor != nil
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) =
case c.kind
@@ -370,8 +389,10 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
doAssert t.destructor != nil
moveCall.add destructorCall(c.g, t.destructor, x)
body.add moveCall
of attachedDestructor:
of attachedDestructor, attachedDispose:
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) =
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))
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)
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 newAsgnStmt(x, y)
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 newAsgnStmt(x, y)
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))
body.add genIf(c, cond, actions)
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) =
## 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)
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)
case c.kind
@@ -421,7 +473,10 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedAsgn:
let yenv = genBuiltin(c.g, mAccessEnv, "accessEnv", y)
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 newAsgnStmt(x, y)
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))
body.add genIf(c, cond, actions)
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) =
case c.kind
@@ -451,6 +516,7 @@ proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
else:
body.sons.insert(des, 0)
of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace, attachedDispose: discard
proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
var actions = newNodeI(nkStmtList, c.info)
@@ -473,6 +539,7 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedDestructor:
body.add genIf(c, x, actions)
of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace, attachedDispose: discard
proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
if c.kind == attachedDeepCopy:
@@ -484,7 +551,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
call[1] = y
body.add newAsgnStmt(x, call)
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)
xx.typ = getSysType(c.g, c.info, tyPointer)
case c.kind
@@ -506,6 +573,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
else:
body.sons.insert(des, 0)
of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace, attachedDispose: discard
proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
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:
body.add genIf(c, xx, actions)
of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace, attachedDispose: discard
proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case t.kind
@@ -528,7 +597,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
tyPtr, tyOpt, tyUncheckedArray, tyVar, tyLent:
defaultOp(c, t, body, x, y)
of tyRef:
if c.g.config.selectedGC == gcDestructors:
if c.g.config.selectedGC in {gcArc, gcOrc}:
atomicRefOp(c, t, body, x, y)
elif (optOwnedRefs in c.g.config.globalOptions and
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)
of tyProc:
if t.callConv == ccClosure:
if c.g.config.selectedGC == gcDestructors:
if c.g.config.selectedGC in {gcArc, gcOrc}:
atomicClosureOp(c, t, body, x, y)
else:
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.
discard considerUserDefinedOp(c, t, body, x, y)
elif tfHasAsgn in t.flags:
if c.kind != attachedDestructor:
if c.kind in {attachedAsgn, attachedSink, attachedDeepCopy}:
body.add newSeqCall(c.g, x, y)
forallElements(c, t, body, x, y)
else:
@@ -632,30 +701,40 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
a.asgnForType = typ
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)
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.addParam dest
if kind != attachedDestructor:
if kind notin {attachedDestructor, attachedDispose}:
result.typ.addParam src
# register this operation already:
typ.attachedOps[kind] = result
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
else:
tk = tyNone # no special casing for strings and seqs
case tk
of tySequence:
fillSeqOp(a, typ, body, newSymNode(dest).newDeref, newSymNode(src))
fillSeqOp(a, typ, body, d, newSymNode(src))
of tyString:
fillStrOp(a, typ, body, newSymNode(dest).newDeref, newSymNode(src))
fillStrOp(a, typ, body, d, newSymNode(src))
else:
fillBody(a, typ, body, newSymNode(dest).newDeref, newSymNode(src))
fillBody(a, typ, body, d, newSymNode(src))
var n = newNodeI(nkProcDef, info, bodyPos+1)
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 =
(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)
@@ -731,8 +810,13 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
# 4. We have a custom 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:
for k in attachedDestructor..attachedSink:
for k in attachedDestructor..lastAttached:
if canon.attachedOps[k] == nil:
discard produceSym(g, c, canon, k, info)
else:

View File

@@ -116,7 +116,7 @@ type
cmdJsonScript # compile a .json build file
TStringSeq* = seq[string]
TGCMode* = enum # the selected GC
gcUnselected, gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcDestructors,
gcUnselected, gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcArc, gcOrc,
gcHooks,
gcRefc, gcV2, gcGo
# 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:
noVal(c, it)
if sym.typ == nil: invalidPragma(c, it)
# now: ignored
else: incl(sym.typ.flags, tfAcyclic)
of wShallow:
noVal(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
## avoid recompiling dependent modules.
## - 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

View File

@@ -1601,7 +1601,7 @@ proc borrowCheck(c: PContext, n, le, ri: PNode) =
proc scopedLifetime(c: PContext; ri: PNode): bool {.inline.} =
let n = getRoot(ri, followDeref = false)
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.} =
# 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)
setLen(tracked.guards.s, oldFacts)
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)
of nkTupleConstr:
for i in 0..<n.len:

View File

@@ -1594,44 +1594,52 @@ proc canonType(c: PContext, t: PType): PType =
else:
result = t
proc semOverride(c: PContext, s: PSym, n: PNode) =
proc prevDestructor(c: PContext; prevOp: PSym; obj: PType; info: TLineInfo) =
var msg = "cannot bind another '" & prevOp.name.s & "' to: " & typeToString(obj)
if sfOverriden notin prevOp.flags:
msg.add "; previous declaration was constructed here implicitly: " & (c.config $ prevOp.info)
else:
msg.add "; previous declaration was here: " & (c.config $ prevOp.info)
localError(c.config, n.info, errGenerated, msg)
proc prevDestructor(c: PContext; prevOp: PSym; obj: PType; info: TLineInfo) =
var msg = "cannot bind another '" & prevOp.name.s & "' to: " & typeToString(obj)
if sfOverriden notin prevOp.flags:
msg.add "; previous declaration was constructed here implicitly: " & (c.config $ prevOp.info)
else:
msg.add "; previous declaration was here: " & (c.config $ prevOp.info)
localError(c.config, 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
case name
of "=destroy":
let t = s.typ
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)
bindTypeHook(c, s, n, attachedDestructor)
of "deepcopy", "=deepcopy":
if s.typ.len == 2 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:
localError(c.config, n.info, errGenerated,
"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:
if sfOverriden in s.flags:
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 =
result = false
if typ == nil: return
if tfAcyclic in typ.flags: return
var t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc})
if tfAcyclic in t.flags: return
case t.kind
of tyTuple, tyObject, tyRef, tySequence, tyArray, tyOpenArray, tyVarargs:
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
--------------
The ``acyclic`` pragma applies to type declarations. It is deprecated and
ignored.
The ``acyclic`` pragma can be used for object types to mark them as acyclic
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

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,
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
affects the compiler used by ``koch`` to (re)build Nim.
The default compiler is defined at the top of ``config\nim.cfg``.
Changing this setting affects the compiler used by ``koch`` to (re)build Nim.
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
a file ``panicoverride.nim``.
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>``
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)``.

View File

@@ -3112,11 +3112,13 @@ when not defined(js):
destructor: pointer
size: int
name: cstring
traceImpl: pointer
disposeImpl: pointer
PNimType = ptr TNimType
when defined(nimSeqsV2) and not defined(nimscript):
include "core/strs"
include "core/seqs"
include "system/strs_v2"
include "system/seqs_v2"
{.pop.}
@@ -3139,7 +3141,7 @@ when not defined(JS) and not defined(nimscript):
{.pop.}
when defined(nimV2):
include core/runtime_v2
include system/refs_v2
import system/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) 7178
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):
include "system/alloc"
include "system/cellsets"
when not usesDestructors:
include "system/cellsets"
when not leakDetector and not useCellIds:
sysAssert(sizeof(Cell) == sizeof(FreeCell), "sizeof FreeCell")
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
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
RefHeader = object
rc: int # the object header is now a single RC field.
# we could remove it in non-debug builds for the 'owned ref'
# design but this seems unwise.
Cell = ptr RefHeader
template `+!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) +% s)
@@ -37,8 +51,11 @@ template `+!`(p: pointer, s: int): pointer =
template `-!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) -% s)
template head(p: pointer): ptr RefHeader =
cast[ptr RefHeader](cast[int](p) -% sizeof(RefHeader))
template head(p: pointer): Cell =
cast[Cell](cast[int](p) -% sizeof(RefHeader))
const
traceCollector = defined(traceArc)
var allocs*: int
@@ -56,28 +73,22 @@ proc nimNewObj(size: int): pointer {.compilerRtl.} =
atomicInc allocs
else:
inc allocs
when traceCollector:
cprintf("[Allocated] %p\n", result -! sizeof(RefHeader))
proc nimDecWeakRef(p: pointer) {.compilerRtl, inl.} =
when hasThreadSupport:
atomicDec head(p).rc
else:
dec head(p).rc
dec head(p).rc, rcIncrement
proc nimIncRef(p: pointer) {.compilerRtl, inl.} =
when hasThreadSupport:
atomicInc head(p).rc
else:
inc head(p).rc
#cprintf("[INCREF] %p\n", p)
inc head(p).rc, rcIncrement
#cprintf("[INCREF] %p\n", p)
proc nimRawDispose(p: pointer) {.compilerRtl.} =
when not defined(nimscript):
when traceCollector:
cprintf("[Freed] %p\n", p -! sizeof(RefHeader))
when defined(nimOwnedEnabled):
when hasThreadSupport:
let hasDanglingRefs = atomicLoadN(addr head(p).rc, ATOMIC_RELAXED) != 0
else:
let hasDanglingRefs = head(p).rc != 0
if hasDanglingRefs:
if head(p).rc >= rcIncrement:
cstderr.rawWrite "[FATAL] dangling references exist\n"
quit 1
when defined(useMalloc):
@@ -108,21 +119,19 @@ proc nimDestroyAndDispose(p: pointer) {.compilerRtl.} =
cstderr.rawWrite "has destructor!\n"
nimRawDispose(p)
when defined(gcOrc):
include cyclicrefs_v2
proc nimDecRefIsLast(p: pointer): bool {.compilerRtl, inl.} =
if p != nil:
when hasThreadSupport:
if atomicLoadN(addr head(p).rc, ATOMIC_RELAXED) == 0:
result = true
else:
discard atomicDec(head(p).rc)
var cell = head(p)
if (cell.rc and not rcMask) == 0:
result = true
#cprintf("[DESTROY] %p\n", p)
else:
if head(p).rc == 0:
result = true
#cprintf("[DESTROY] %p\n", p)
else:
dec head(p).rc
# According to Lins it's correct to do nothing else here.
#cprintf("[DeCREF] %p\n", p)
dec cell.rc, rcIncrement
# According to Lins it's correct to do nothing else here.
#cprintf("[DeCREF] %p\n", p)
proc GC_unref*[T](x: ref T) =
## New runtime only supports this operation for 'ref T'.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
discard """
cmd: '''nim c --newruntime $file'''
valgrind: true
cmd: '''nim c --newruntime -d:useMalloc $file'''
output: '''422 422'''
"""
import strutils, os, std / wordwrap
import core / allocators
import system / allocators
import system / ansi_c
# 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'''
"""
import core / allocators
import system / allocators
import system / ansi_c
proc main(): owned(proc()) =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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