diff --git a/compiler/ast.nim b/compiler/ast.nim index 208e99c4ab588..a4868a9e6f85c 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 = "" diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 9acf8c079a620..5fa7ac8d7f803 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 d490bd5388357..9c3e45697e677 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 63b350521571d..fa94d36e3406c 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 18fb0f5c89c44..0acf7fac258f6 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 ea19097b8dd63..1921a1e181a7a 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 a906ac1815b35..38f9ee417b3ee 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..= 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 db5a7d70e4030..3a313324776c1 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 aed50554bcd29..9ce67ec45e67f 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 c59653b342004..cc8d0875cf52a 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=`` -command line switch. Note that the total heap size will be +command line switch. Note that the total heap size will be `` * sizeof(float64)``. diff --git a/lib/system.nim b/lib/system.nim index 81d3080737332..583161f857b86 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 similarity index 100% rename from lib/core/allocators.nim rename to lib/system/allocators.nim diff --git a/lib/system/cyclicrefs_v2.nim b/lib/system/cyclicrefs_v2.nim new file mode 100644 index 0000000000000..4fbb4fc94b661 --- /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 330c551c5878d..60f8f7db69610 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 similarity index 79% rename from lib/core/runtime_v2.nim rename to lib/system/refs_v2.nim index d566a4c69600b..3033880c31570 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 similarity index 100% rename from lib/core/seqs.nim rename to lib/system/seqs_v2.nim diff --git a/lib/core/strs.nim b/lib/system/strs_v2.nim similarity index 100% rename from lib/core/strs.nim rename to lib/system/strs_v2.nim diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim index cf5e728d7cf30..f3a6f9d77a5f3 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 55e30f46026e4..98ddd7e10bbc3 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 61820c3286617..f4d06e09305df 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 c7aa854b8bc9e..976218f437793 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 56dbfe929728c..bd6343b2fb099 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 0000000000000..da81f18840ce4 --- /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 0000000000000..136e0b6763676 --- /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 15a3c41ec6bcf..5f88ffff5f525 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 0000000000000..c309774337bc5 --- /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 0000000000000..c4b2975592940 --- /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 0000000000000..a938ded01a8f0 --- /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 7eb865915c01b..4a66b4f77065b 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 025383565dd3e..612d1a11653d8 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 54ce4acea63c6..3e4ee27beb98b 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 0000000000000..9262b47d498dc --- /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 583cc7d880e2b..088f4a95c735b 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 2108fa01c736d..31f580db3f7fa 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 dcc3b1b384f46..409cdead8d24b 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 1d75a803df5f2..9537748e34277 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 39d3c46df84c6..d594ad54c4e92 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 9de558234257f..7a8addff31e6f 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 f90e09c5a928d..b4cdacf7c0308 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