diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2019-12-17 17:37:50 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-12-17 17:37:50 +0100 |
commit | 83a736a34a1ebd4bc4d769429880ccb871403ba4 (patch) | |
tree | 1a45de64686622fe9932daafb5345fdd066cab48 | |
parent | 5848f0042c2d6a6dd39d9b8db747f36200c9f543 (diff) | |
download | Nim-83a736a34a1ebd4bc4d769429880ccb871403ba4.tar.gz |
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
45 files changed, 939 insertions, 229 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 208e99c4a..a4868a9e6 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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>" diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 9acf8c079..5fa7ac8d7 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -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, diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index d490bd538..9c3e45697 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -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: + 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: - d = rope("NIM_NIL") + 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: diff --git a/compiler/commands.nim b/compiler/commands.nim index 63b350521..fa94d36e3 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -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}: diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 18fb0f5c8..0acf7fac2 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -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: diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index ea19097b8..1921a1e18 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -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: diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index a906ac181..38f9ee417 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -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: diff --git a/compiler/options.nim b/compiler/options.nim index c880534fd..ed3ee030d 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -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, diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index a04eafee9..6596cfba6 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -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) diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim index 50941535e..d79dd1deb 100644 --- a/compiler/rodimpl.nim +++ b/compiler/rodimpl.nim @@ -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 diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index c87ab21b1..ce0f10298 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -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 diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 4c4899391..1a4204ec6 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -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: diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index e8a5ae038..cf1063fe6 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -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, diff --git a/compiler/types.nim b/compiler/types.nim index db5a7d70e..3a3133247 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -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): diff --git a/doc/manual.rst b/doc/manual.rst index aed50554b..9ce67ec45 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -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 diff --git a/doc/nimc.rst b/doc/nimc.rst index c59653b34..cc8d0875c 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -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)``. diff --git a/lib/system.nim b/lib/system.nim index 81d308073..583161f85 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -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 diff --git a/lib/core/allocators.nim b/lib/system/allocators.nim index 43aae0111..43aae0111 100644 --- a/lib/core/allocators.nim +++ b/lib/system/allocators.nim diff --git a/lib/system/cyclicrefs_v2.nim b/lib/system/cyclicrefs_v2.nim new file mode 100644 index 000000000..4fbb4fc94 --- /dev/null +++ b/lib/system/cyclicrefs_v2.nim @@ -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) diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 330c551c5..60f8f7db6 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -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"): diff --git a/lib/core/runtime_v2.nim b/lib/system/refs_v2.nim index d566a4c69..3033880c3 100644 --- a/lib/core/runtime_v2.nim +++ b/lib/system/refs_v2.nim @@ -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'. diff --git a/lib/core/seqs.nim b/lib/system/seqs_v2.nim index b7f9fb153..b7f9fb153 100644 --- a/lib/core/seqs.nim +++ b/lib/system/seqs_v2.nim diff --git a/lib/core/strs.nim b/lib/system/strs_v2.nim index 3b7a46ff1..3b7a46ff1 100644 --- a/lib/core/strs.nim +++ b/lib/system/strs_v2.nim diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim index cf5e728d7..f3a6f9d77 100644 --- a/lib/system/widestrs.nim +++ b/lib/system/widestrs.nim @@ -18,7 +18,7 @@ type when defined(nimv2): - import core / allocators + import system / allocators type WideCString* = ptr UncheckedArray[Utf16Char] diff --git a/testament/categories.nim b/testament/categories.nim index 55e30f460..98ddd7e10 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -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 diff --git a/testament/specs.nim b/testament/specs.nim index 61820c328..f4d06e093 100644 --- a/testament/specs.nim +++ b/testament/specs.nim @@ -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 diff --git a/testament/testament.nim b/testament/testament.nim index c7aa854b8..976218f43 100644 --- a/testament/testament.nim +++ b/testament/testament.nim @@ -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 diff --git a/tests/destructor/tarc2.nim b/tests/destructor/tarc2.nim index 56dbfe929..bd6343b2f 100644 --- a/tests/destructor/tarc2.nim +++ b/tests/destructor/tarc2.nim @@ -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 diff --git a/tests/destructor/tarctypesections.nim b/tests/destructor/tarctypesections.nim new file mode 100644 index 000000000..da81f1884 --- /dev/null +++ b/tests/destructor/tarctypesections.nim @@ -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 diff --git a/tests/destructor/tasync_prototype_cyclic.nim b/tests/destructor/tasync_prototype_cyclic.nim new file mode 100644 index 000000000..136e0b676 --- /dev/null +++ b/tests/destructor/tasync_prototype_cyclic.nim @@ -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 diff --git a/tests/destructor/tbintree2.nim b/tests/destructor/tbintree2.nim index 15a3c41ec..5f88ffff5 100644 --- a/tests/destructor/tbintree2.nim +++ b/tests/destructor/tbintree2.nim @@ -4,7 +4,7 @@ discard """ 3 3 alloc/dealloc pairs: 0''' """ -import core / allocators +import system / allocators import system / ansi_c import random diff --git a/tests/destructor/tcycle1.nim b/tests/destructor/tcycle1.nim new file mode 100644 index 000000000..c30977433 --- /dev/null +++ b/tests/destructor/tcycle1.nim @@ -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 diff --git a/tests/destructor/tcycle2.nim b/tests/destructor/tcycle2.nim new file mode 100644 index 000000000..c4b297559 --- /dev/null +++ b/tests/destructor/tcycle2.nim @@ -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 diff --git a/tests/destructor/tcycle3.nim b/tests/destructor/tcycle3.nim new file mode 100644 index 000000000..a938ded01 --- /dev/null +++ b/tests/destructor/tcycle3.nim @@ -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 diff --git a/tests/destructor/tgcdestructors.nim b/tests/destructor/tgcdestructors.nim index 7eb865915..4a66b4f77 100644 --- a/tests/destructor/tgcdestructors.nim +++ b/tests/destructor/tgcdestructors.nim @@ -13,7 +13,7 @@ true 41 41''' """ -import allocators +import system / allocators include system / ansi_c proc main = diff --git a/tests/destructor/tnewruntime_misc.nim b/tests/destructor/tnewruntime_misc.nim index 025383565..612d1a116 100644 --- a/tests/destructor/tnewruntime_misc.nim +++ b/tests/destructor/tnewruntime_misc.nim @@ -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") diff --git a/tests/destructor/tnewruntime_strutils.nim b/tests/destructor/tnewruntime_strutils.nim index 54ce4acea..3e4ee27be 100644 --- a/tests/destructor/tnewruntime_strutils.nim +++ b/tests/destructor/tnewruntime_strutils.nim @@ -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 diff --git a/tests/destructor/tselect.nim b/tests/destructor/tselect.nim new file mode 100644 index 000000000..9262b47d4 --- /dev/null +++ b/tests/destructor/tselect.nim @@ -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) diff --git a/tests/destructor/tsimpleclosure.nim b/tests/destructor/tsimpleclosure.nim index 583cc7d88..088f4a95c 100644 --- a/tests/destructor/tsimpleclosure.nim +++ b/tests/destructor/tsimpleclosure.nim @@ -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()) = diff --git a/tests/destructor/tuse_ownedref_after_move.nim b/tests/destructor/tuse_ownedref_after_move.nim index 2108fa01c..31f580db3 100644 --- a/tests/destructor/tuse_ownedref_after_move.nim +++ b/tests/destructor/tuse_ownedref_after_move.nim @@ -4,7 +4,7 @@ discard """ line: 49 """ -import core / allocators +import system / allocators import system / ansi_c type diff --git a/tests/destructor/tv2_raise.nim b/tests/destructor/tv2_raise.nim index dcc3b1b38..409cdead8 100644 --- a/tests/destructor/tv2_raise.nim +++ b/tests/destructor/tv2_raise.nim @@ -6,7 +6,7 @@ discard """ import strutils, math import system / ansi_c -import core / allocators +import system / allocators proc mainA = try: diff --git a/tests/destructor/twidgets.nim b/tests/destructor/twidgets.nim index 1d75a803d..9537748e3 100644 --- a/tests/destructor/twidgets.nim +++ b/tests/destructor/twidgets.nim @@ -5,7 +5,7 @@ clicked! 1 1 alloc/dealloc pairs: 0''' """ -import core / allocators +import system / allocators import system / ansi_c type diff --git a/tests/destructor/twidgets_unown.nim b/tests/destructor/twidgets_unown.nim index 39d3c46df..d594ad54c 100644 --- a/tests/destructor/twidgets_unown.nim +++ b/tests/destructor/twidgets_unown.nim @@ -5,7 +5,7 @@ clicked! 6 6 alloc/dealloc pairs: 0''' """ -import core / allocators +import system / allocators import system / ansi_c type diff --git a/tests/gc/gcbench.nim b/tests/gc/gcbench.nim index 9de558234..7a8addff3 100644 --- a/tests/gc/gcbench.nim +++ b/tests/gc/gcbench.nim @@ -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 diff --git a/tests/gc/thavlak.nim b/tests/gc/thavlak.nim index f90e09c5a..b4cdacf7c 100644 --- a/tests/gc/thavlak.nim +++ b/tests/gc/thavlak.nim @@ -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 |