diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2020-07-04 07:37:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-04 07:37:24 +0200 |
commit | 1854d29781aff913ca6892cbf73df91b0399397e (patch) | |
tree | 4a71bc7988c753ef1acab28776edec691c6d27bd /compiler | |
parent | 695154970d839add2fbbe9754e9e638511120729 (diff) | |
download | Nim-1854d29781aff913ca6892cbf73df91b0399397e.tar.gz |
scoped memory management (#14790)
* fixes the regressions * closes #13936 * scope based memory management implemented * enabled tcontrolflow.nim test case * final cleanups
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/ccgcalls.nim | 26 | ||||
-rw-r--r-- | compiler/ccgexprs.nim | 22 | ||||
-rw-r--r-- | compiler/ccgstmts.nim | 24 | ||||
-rw-r--r-- | compiler/cgen.nim | 6 | ||||
-rw-r--r-- | compiler/cgendata.nim | 1 | ||||
-rw-r--r-- | compiler/injectdestructors.nim | 717 | ||||
-rw-r--r-- | compiler/semstmts.nim | 4 | ||||
-rw-r--r-- | compiler/spawn.nim | 4 |
8 files changed, 367 insertions, 437 deletions
diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 80f02b6c3..2c5d306a3 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -644,7 +644,33 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) = pl.add(~"];$n") line(p, cpsStmts, pl) +proc notYetAlive(n: PNode): bool {.inline.} = + let r = getRoot(n) + result = r != nil and r.loc.lode == nil + +proc isInactiveDestructorCall(p: BProc, e: PNode): bool = + #[ Consider this example. + + var :tmpD_3281815 + try: + if true: + return + let args_3280013 = + wasMoved_3281816(:tmpD_3281815) + `=_3280036`(:tmpD_3281815, [1]) + :tmpD_3281815 + finally: + `=destroy_3280027`(args_3280013) + + We want to return early but the 'finally' section is traversed before + the 'let args = ...' statement. We exploit this to generate better + code for 'return'. ]# + result = e.len == 2 and e[0].kind == nkSym and + e[0].sym.name.s == "=destroy" and notYetAlive(e[1].skipAddr) + proc genCall(p: BProc, e: PNode, d: var TLoc) = + if p.withinBlockLeaveActions > 0 and isInactiveDestructorCall(p, e): + return if e[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure: genClosureCall(p, nil, e, d) elif e[0].kind == nkSym and sfInfixCall in e[0].sym.flags: diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 08b9e1ff6..c194ce326 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2066,10 +2066,14 @@ proc skipAddr(n: PNode): PNode = proc genWasMoved(p: BProc; n: PNode) = var a: TLoc - initLocExpr(p, n[1].skipAddr, a) - resetLoc(p, a) - #linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", - # [addrLoc(p.config, a), getTypeDesc(p.module, a.t)]) + let n1 = n[1].skipAddr + if p.withinBlockLeaveActions > 0 and notYetAlive(n1): + discard + else: + initLocExpr(p, n1, a) + resetLoc(p, a) + #linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", + # [addrLoc(p.config, a), getTypeDesc(p.module, a.t)]) proc genMove(p: BProc; n: PNode; d: var TLoc) = var a: TLoc @@ -2593,10 +2597,12 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = else: putLocIntoDest(p, d, sym.loc) of skTemp: - if sym.loc.r == nil: - # we now support undeclared 'skTemp' variables for easier - # transformations in other parts of the compiler: - assignLocalVar(p, n) + when false: + # this is more harmful than helpful. + if sym.loc.r == nil: + # we now support undeclared 'skTemp' variables for easier + # transformations in other parts of the compiler: + assignLocalVar(p, n) if sym.loc.r == nil or sym.loc.t == nil: #echo "FAILED FOR PRCO ", p.prc.name.s #echo renderTree(p.prc.ast, {renderIds}) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 82f1caadc..532272374 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -200,6 +200,7 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = var stack = newSeq[tuple[fin: PNode, inExcept: bool, label: Natural]](0) + inc p.withinBlockLeaveActions for i in 1..howManyTrys: let tryStmt = p.nestedTryStmts.pop if p.config.exc == excSetjmp: @@ -217,6 +218,8 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = if finallyStmt != nil: genStmts(p, finallyStmt[0]) + dec p.withinBlockLeaveActions + # push old elements again: for i in countdown(howManyTrys-1, 0): p.nestedTryStmts.add(stack[i]) @@ -861,10 +864,10 @@ proc genStringCase(p: BProc, t: PNode, d: var TLoc) = genCaseGeneric(p, t, d, "", "if (#eqStrings($1, $2)) goto $3;$n") proc branchHasTooBigRange(b: PNode): bool = - for i in 0..<b.len-1: + for it in b: # last son is block - if (b[i].kind == nkRange) and - b[i][1].intVal - b[i][0].intVal > RangeExpandLimit: + if (it.kind == nkRange) and + it[1].intVal - it[0].intVal > RangeExpandLimit: return true proc ifSwitchSplitPoint(p: BProc, n: PNode): int = @@ -988,9 +991,14 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = let fin = if t[^1].kind == nkFinally: t[^1] else: nil p.nestedTryStmts.add((fin, false, 0.Natural)) - startBlock(p, "try {$n") - expr(p, t[0], d) - endBlock(p) + if t.kind == nkHiddenTryStmt: + lineCg(p, cpsStmts, "try {$n", []) + expr(p, t[0], d) + lineCg(p, cpsStmts, "}$n", []) + else: + startBlock(p, "try {$n") + expr(p, t[0], d) + endBlock(p) # First pass: handle Nim based exceptions: lineCg(p, cpsStmts, "catch (#Exception* T$1_) {$n", [etmp+1]) @@ -1335,13 +1343,13 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) = linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", [safePoint]) else: linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint]) - startBlock(p, "if ($1.status == 0) {$n", [safePoint]) + lineCg(p, cpsStmts, "if ($1.status == 0) {$n", [safePoint]) let fin = if t[^1].kind == nkFinally: t[^1] else: nil p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural)) expr(p, t[0], d) if not quirkyExceptions: linefmt(p, cpsStmts, "#popSafePoint();$n", []) - endBlock(p) + lineCg(p, cpsStmts, "}$n", []) startBlock(p, "else {$n") linefmt(p, cpsStmts, "#popSafePoint();$n", []) genRestoreFrameAfterException(p) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 99bfe9119..86860fbf4 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -1561,7 +1561,7 @@ proc registerModuleToMain(g: BModuleList; m: BModule) = if sfSystemModule in m.module.flags: if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone: g.mainDatInit.add(ropecg(m, "\t#initThreadVarsEmulation();$N", [])) - if m.config.target.targetOS != osStandalone and m.config.selectedGC != gcNone: + if m.config.target.targetOS != osStandalone and m.config.selectedGC notin {gcNone, gcArc, gcOrc}: g.mainDatInit.add(ropecg(m, "\t#initStackBottomWith((void *)&inner);$N", [])) if m.s[cfsInitProc].len > 0: @@ -1666,6 +1666,10 @@ proc genInitCode(m: BModule) = writeSection(preInitProc, cpsInit, m.hcrOn) writeSection(preInitProc, cpsStmts) prc.addf("}$N", []) + when false: + m.initProc.blocks[0].sections[cpsLocals].add m.preInitProc.s(cpsLocals) + m.initProc.blocks[0].sections[cpsInit].prepend m.preInitProc.s(cpsInit) + m.initProc.blocks[0].sections[cpsStmts].prepend m.preInitProc.s(cpsStmts) # add new scope for following code, because old vcc compiler need variable # be defined at the top of the block diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index a20931be4..3384558f8 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -97,6 +97,7 @@ type # requires 'T x = T()' to become 'T x; x = T()' # (yes, C++ is weird like that) withinTryWithExcept*: int # required for goto based exception handling + withinBlockLeaveActions*: int # complex to explain sigConflicts*: CountTable[string] TTypeSeq* = seq[PType] diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index a2669be99..e10686917 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -27,10 +27,101 @@ import lineinfos, parampatterns, sighashes, liftdestructors from trees import exprStructuralEquivalent, getRoot -from algorithm import reverse -const - scopeBasedDestruction = false +type + Scope = object # well we do scope-based memory management. \ + # a scope is comparable to an nkStmtListExpr like + # (try: statements; dest = y(); finally: destructors(); dest) + vars: seq[PSym] + wasMoved: seq[PNode] + final: seq[PNode] # finally section + needsTry: bool + parent: ptr Scope + +proc nestedScope(parent: var Scope): Scope = + Scope(vars: @[], wasMoved: @[], final: @[], needsTry: false, parent: addr(parent)) + +proc rememberParent(parent: var Scope; inner: Scope) {.inline.} = + parent.needsTry = parent.needsTry or inner.needsTry + +proc optimize(s: var Scope) = + # optimize away simple 'wasMoved(x); destroy(x)' pairs. + #[ Unfortunately this optimization is only really safe when no exceptions + are possible, see for example: + + proc main(inp: string; cond: bool) = + if cond: + try: + var s = ["hi", inp & "more"] + for i in 0..4: + echo s + consume(s) + wasMoved(s) + finally: + destroy(x) + + Now assume 'echo' raises, then we shouldn't do the 'wasMoved(s)' + ]# + # XXX: Investigate how to really insert 'wasMoved()' calls! + proc findCorrespondingDestroy(final: seq[PNode]; moved: PNode): int = + # remember that it's destroy(addr(x)) + for i in 0 ..< final.len: + if final[i] != nil and exprStructuralEquivalent(final[i][1].skipAddr, moved, strictSymEquality = true): + return i + return -1 + + var removed = 0 + for i in 0 ..< s.wasMoved.len: + let j = findCorrespondingDestroy(s.final, s.wasMoved[i][1]) + if j >= 0: + s.wasMoved[i] = nil + s.final[j] = nil + inc removed + if removed > 0: + template filterNil(field) = + var m = newSeq[PNode](s.field.len - removed) + var mi = 0 + for i in 0 ..< s.field.len: + if s.field[i] != nil: + m[mi] = s.field[i] + inc mi + assert mi == m.len + s.field = m + + filterNil(wasMoved) + filterNil(final) + +proc toTree(s: var Scope; ret: PNode; onlyCareAboutVars = false): PNode = + if not s.needsTry: optimize(s) + assert ret != nil + if s.vars.len == 0 and s.final.len == 0 and s.wasMoved.len == 0: + # trivial, nothing was done: + result = ret + else: + if isEmptyType(ret.typ): + result = newNodeI(nkStmtList, ret.info) + else: + result = newNodeIT(nkStmtListExpr, ret.info, ret.typ) + + if s.vars.len > 0: + let varSection = newNodeI(nkVarSection, ret.info) + for tmp in s.vars: + varSection.add newTree(nkIdentDefs, newSymNode(tmp), newNodeI(nkEmpty, ret.info), + newNodeI(nkEmpty, ret.info)) + result.add varSection + if onlyCareAboutVars: + result.add ret + s.vars.setLen 0 + elif s.needsTry: + var finSection = newNodeI(nkStmtList, ret.info) + for m in s.wasMoved: finSection.add m + for i in countdown(s.final.high, 0): finSection.add s.final[i] + result.add newTryFinally(ret, finSection) + else: + #assert isEmptyType(ret.typ) + result.add ret + for m in s.wasMoved: result.add m + for i in countdown(s.final.high, 0): result.add s.final[i] type Con = object @@ -38,14 +129,10 @@ type g: ControlFlowGraph jumpTargets: IntSet destroys, topLevelVars: PNode - scopeDestroys: seq[PNode] # used as a stack that pop from - # at strategic places which try to - # mimic the natural scope. graph: ModuleGraph emptyNode: PNode otherRead: PNode - inLoop, inSpawn, hasUnstructuredCf, inDangerousBranch: int - declaredVars: IntSet # variables we already moved to the top level + inLoop, inSpawn: int uninit: IntSet # set of uninit'ed vars uninitComputed: bool @@ -62,8 +149,8 @@ template dbg(body) = if c.owner.name.s == toDebug or toDebug == "always": body -proc p(n: PNode; c: var Con; mode: ProcessMode): PNode -proc moveOrCopy(dest, ri: PNode; c: var Con, isDecl = false): PNode +proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode +proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope; isDecl = false): PNode proc isLastRead(location: PNode; cfg: ControlFlowGraph; otherRead: var PNode; pc, until: int): int = var pc = pc @@ -265,15 +352,6 @@ proc genDestroy(c: Con; dest: PNode): PNode = let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink}) result = genOp(c, t, attachedDestructor, dest, nil) -when false: - proc preventMoveRef(dest, ri: PNode): bool = - let lhs = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink}) - var ri = ri - if ri.kind in nkCallKinds and ri[0].kind == nkSym and ri[0].sym.magic == mUnown: - ri = ri[1] - let rhs = ri.typ.skipTypes({tyGenericInst, tyAlias, tySink}) - result = lhs.kind == tyRef and rhs.kind == tyOwned - proc canBeMoved(c: Con; t: PType): bool {.inline.} = let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) if optOwnedRefs in c.graph.config.globalOptions: @@ -284,8 +362,10 @@ proc canBeMoved(c: Con; t: PType): bool {.inline.} = proc isNoInit(dest: PNode): bool {.inline.} = result = dest.kind == nkSym and sfNoInit in dest.sym.flags -proc genSink(c: var Con; dest, ri: PNode, isDecl = false): PNode = - if isUnpackedTuple(dest) or (isDecl and c.inLoop <= 0) or (isAnalysableFieldAccess(dest, c.owner) and isFirstWrite(dest, c)) or isNoInit(dest): +proc genSink(c: var Con; s: var Scope; dest, ri: PNode, isDecl = false): PNode = + if isUnpackedTuple(dest) or (isDecl and c.inLoop <= 0) or + (isAnalysableFieldAccess(dest, c.owner) and isFirstWrite(dest, c)) or + isNoInit(dest): # optimize sink call into a bitwise memcopy result = newTree(nkFastAsgn, dest, ri) else: @@ -311,34 +391,34 @@ proc genCopy(c: var Con; dest, ri: PNode): PNode = checkForErrorPragma(c, t, ri, "=") result = genCopyNoCheck(c, dest, ri) -proc addTopVar(c: var Con; v: PNode) = - c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode) +proc addTopVar(c: var Con; s: var Scope; v: PNode) = + s.vars.add v.sym -proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode = +proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo): PNode = let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.owner, info) sym.typ = typ + s.vars.add(sym) result = newSymNode(sym) -proc genDiscriminantAsgn(c: var Con; n: PNode): PNode = +proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode = # discriminator is ordinal value that doesn't need sink destroy # but fields within active case branch might need destruction # tmp to support self assignments - let tmp = getTemp(c, n[1].typ, n.info) - c.addTopVar(tmp) + let tmp = getTemp(c, s, n[1].typ, n.info) result = newTree(nkStmtList) - result.add newTree(nkFastAsgn, tmp, p(n[1], c, consumed)) - result.add p(n[0], c, normal) + result.add newTree(nkFastAsgn, tmp, p(n[1], c, s, consumed)) + result.add p(n[0], c, s, normal) - let le = p(n[0], c, normal) + let le = p(n[0], c, s, normal) let leDotExpr = if le.kind == nkCheckedFieldExpr: le[0] else: le let objType = leDotExpr[0].typ if hasDestructor(objType): if objType.attachedOps[attachedDestructor] != nil and sfOverriden in objType.attachedOps[attachedDestructor].flags: - localError(c.graph.config, n.info, errGenerated, """Assignment to discriminant for object's with user defined destructor is not supported, object must have default destructor. + localError(c.graph.config, n.info, errGenerated, """Assignment to discriminant for objects with user defined destructor is not supported, object must have default destructor. It is best to factor out piece of object that needs custom destructor into separate object or not use discriminator assignment""") result.add newTree(nkFastAsgn, le, tmp) return @@ -353,9 +433,7 @@ It is best to factor out piece of object that needs custom destructor into separ notExpr.add newSymNode(createMagic(c.graph, "not", mNot)) notExpr.add cond result.add newTree(nkIfStmt, newTree(nkElifBranch, notExpr, genOp(c, branchDestructor, le))) - result.add newTree(nkFastAsgn, le, tmp) - else: - result.add newTree(nkFastAsgn, le, tmp) + result.add newTree(nkFastAsgn, le, tmp) proc genWasMoved(n: PNode; c: var Con): PNode = result = newNodeI(nkCall, n.info) @@ -369,7 +447,7 @@ proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode = result.add(newSymNode(createMagic(c.graph, "default", mDefault))) result.typ = t -proc destructiveMoveVar(n: PNode; c: var Con): PNode = +proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode = # generate: (let tmp = v; reset(v); tmp) if not hasDestructor(n.typ): result = copyTree(n) @@ -388,28 +466,21 @@ proc destructiveMoveVar(n: PNode; c: var Con): PNode = v.add(vpart) result.add v - result.add genWasMoved(skipConv(n), c) + let wasMovedCall = genWasMoved(skipConv(n), c) + result.add wasMovedCall result.add tempAsNode -proc sinkParamIsLastReadCheck(c: var Con, s: PNode) = - assert s.kind == nkSym and s.sym.kind == skParam - if not isLastRead(s, c): - localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s & - "` is already consumed at " & toFileLineCol(c. graph.config, s.info)) - proc isCapturedVar(n: PNode): bool = let root = getRoot(n) if root != nil: result = root.name.s[0] == ':' -proc passCopyToSink(n: PNode; c: var Con): PNode = +proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode = result = newNodeIT(nkStmtListExpr, n.info, n.typ) - let tmp = getTemp(c, n.typ, n.info) - when not scopeBasedDestruction: - c.addTopVar(tmp) + let tmp = getTemp(c, s, n.typ, n.info) if hasDestructor(n.typ): result.add genWasMoved(tmp, c) var m = genCopy(c, tmp, n) - m.add p(n, c, normal) + m.add p(n, c, s, normal) result.add m if isLValue(n) and not isCapturedVar(n) and n.typ.skipTypes(abstractInst).kind != tyRef and c.inSpawn == 0: message(c.graph.config, n.info, hintPerformance, @@ -418,7 +489,7 @@ proc passCopyToSink(n: PNode; c: var Con): PNode = else: if c.graph.config.selectedGC in {gcArc, gcOrc}: assert(not containsGarbageCollectedRef(n.typ)) - result.add newTree(nkAsgn, tmp, p(n, c, normal)) + result.add newTree(nkAsgn, tmp, p(n, c, s, normal)) # Since we know somebody will take over the produced copy, there is # no need to destroy it. result.add tmp @@ -442,161 +513,24 @@ proc containsConstSeq(n: PNode): bool = if containsConstSeq(son): return true else: discard -proc handleTmpDestroys(c: var Con; body: PNode; t: PType; - oldHasUnstructuredCf, oldTmpDestroysLen: int) = - if c.hasUnstructuredCf == oldHasUnstructuredCf: - # no need for a try-finally statement: - if body.kind == nkStmtList: - for i in countdown(c.scopeDestroys.high, oldTmpDestroysLen): - body.add c.scopeDestroys[i] - elif isEmptyType(t): - var n = newNodeI(nkStmtList, body.info) - n.add body[^1] - for i in countdown(c.scopeDestroys.high, oldTmpDestroysLen): - n.add c.scopeDestroys[i] - body[^1] = n - elif body.kind == nkStmtListExpr and body.len > 0 and body[^1].kind == nkSym: - # special case: Do not translate (x; y; sym) into - # (x; y; tmp = sym; destroy(x); destroy(y); tmp ) - # but into - # (x; y; destroy(x); destroy(y); sym ) - let sym = body[^1] - body[^1] = c.scopeDestroys[^1] - for i in countdown(c.scopeDestroys.high - 1, oldTmpDestroysLen): - body.add c.scopeDestroys[i] - body.add sym - else: - # fun ahead: We have to transform (x; y; E()) into - # (x; y; tmp = E(); destroy(x); destroy(y); tmp ) - let t2 = body[^1].typ - let tmp = getTemp(c, t2, body.info) - when not scopeBasedDestruction: - c.addTopVar(tmp) - # the tmp does not have to be initialized - var n = newNodeIT(nkStmtListExpr, body.info, t2) - n.add newTree(nkFastAsgn, tmp, body[^1]) - for i in countdown(c.scopeDestroys.high, oldTmpDestroysLen): - n.add c.scopeDestroys[i] - n.add tmp - body[^1] = n - #c.scopeDestroys.add genDestroy(c, tmp) - else: - # unstructured control flow was used, use a 'try finally' to ensure - # destruction: - if isEmptyType(t): - var n = newNodeI(nkStmtList, body.info) - for i in countdown(c.scopeDestroys.high, oldTmpDestroysLen): - n.add c.scopeDestroys[i] - body[^1] = newTryFinally(body[^1], n) - else: - # fun ahead: We have to transform (x; y; E()) into - # ((try: tmp = (x; y; E()); finally: destroy(x); destroy(y)); tmp ) - let t2 = body[^1].typ - let tmp = getTemp(c, t2, body.info) - when not scopeBasedDestruction: - c.addTopVar(tmp) - # the tmp does not have to be initialized - var fin = newNodeI(nkStmtList, body.info) - for i in countdown(c.scopeDestroys.high, oldTmpDestroysLen): - fin.add c.scopeDestroys[i] - var n = newNodeIT(nkStmtListExpr, body.info, t2) - n.add newTryFinally(newTree(nkFastAsgn, tmp, body[^1]), fin) - n.add tmp - body[^1] = n - #c.scopeDestroys.add genDestroy(c, tmp) - - c.scopeDestroys.setLen oldTmpDestroysLen - -proc handleNested(n, dest: PNode; c: var Con; mode: ProcessMode): PNode = - template processCall(node: PNode): PNode = - if node.typ == nil or dest == nil: - p(node, c, mode) - else: - moveOrCopy(dest, node, c) - - proc handleScope(n, dest: PNode; t: PType; - takeOver: Natural; c: var Con; mode: ProcessMode): PNode = - let oldHasUnstructuredCf = c.hasUnstructuredCf - let oldTmpDestroysLen = c.scopeDestroys.len - result = shallowCopy(n) - for i in 0..<takeOver: - result[i] = n[i] - let last = n.len - 1 - for i in takeOver..<last: - result[i] = p(n[i], c, normal) - - # if we have an expression producing a temporary, we must - # not destroy it too early: - if isEmptyType(t): - result[last] = processCall(n[last]) - if c.scopeDestroys.len > oldTmpDestroysLen: - handleTmpDestroys(c, result, t, oldHasUnstructuredCf, oldTmpDestroysLen) - else: - setLen(result.sons, last) - if c.scopeDestroys.len > oldTmpDestroysLen: - handleTmpDestroys(c, result, t, oldHasUnstructuredCf, oldTmpDestroysLen) - if result.kind != nkFinally: - result.add processCall(n[last]) - else: - result = newTree(nkStmtListExpr, result, processCall(n[last])) - result.typ = t - - case n.kind - of nkStmtList, nkStmtListExpr: - if n.len == 0: return n - result = shallowCopy(n) - let last = n.len - 1 - for i in 0..<last: - result[i] = p(n[i], c, normal) - result[last] = processCall(n[last]) - # A statement list does not introduce a scope, the AST can - # contain silly nested statement lists. - of nkBlockStmt, nkBlockExpr: - result = handleScope(n, dest, n.typ, 1, c, mode) - of nkIfStmt, nkIfExpr: - result = copyNode(n) - for son in n: - result.add handleScope(son, dest, son[^1].typ, 0, c, mode) - of nkCaseStmt: - result = copyNode(n) - result.add p(n[0], c, normal) - for i in 1..<n.len: - result.add handleScope(n[i], dest, n[i][^1].typ, n[i].len - 1, c, mode) - of nkWhen: # This should be a "when nimvm" node. - result = copyTree(n) - result[1][0] = processCall(n[1][0]) - # handleScope(n[1][0], dest, n[1][0][^1].typ, 0, c, mode) - of nkWhileStmt: - inc c.inLoop - result = handleScope(n, dest, nil, 0, c, mode) - dec c.inLoop - else: assert(false) - -proc ensureDestruction(arg: PNode; c: var Con): PNode = +proc ensureDestruction(arg: PNode; c: var Con; s: var Scope): PNode = # it can happen that we need to destroy expression contructors # like [], (), closures explicitly in order to not leak them. if arg.typ != nil and hasDestructor(arg.typ): # produce temp creation for (fn, env). But we need to move 'env'? # This was already done in the sink parameter handling logic. result = newNodeIT(nkStmtListExpr, arg.info, arg.typ) - let tmp = getTemp(c, arg.typ, arg.info) - when not scopeBasedDestruction: - c.addTopVar(tmp) - result.add genSink(c, tmp, arg, isDecl = true) + + if s.parent != nil: + let tmp = getTemp(c, s.parent[], arg.typ, arg.info) + result.add genSink(c, s, tmp, arg, isDecl = true) result.add tmp - c.destroys.add genDestroy(c, tmp) + s.parent[].final.add genDestroy(c, tmp) else: - # if we're inside a dangerous 'or' or 'and' expression, we - # do need to initialize it. 'elif' is not among this problem - # as we have a separate scope for 'elif' to attach the destructors to. - if c.inDangerousBranch == 0 and c.hasUnstructuredCf == 0: - tmp.sym.flags.incl sfNoInit - c.addTopVar(tmp) - # since we do not initialize these temporaries anymore, we - # use raw assignments instead of =sink: - result.add newTree(nkFastAsgn, tmp, arg) + let tmp = getTemp(c, s, arg.typ, arg.info) + result.add genSink(c, s, tmp, arg, isDecl = true) result.add tmp - c.scopeDestroys.add genDestroy(c, tmp) + s.final.add genDestroy(c, tmp) else: result = arg @@ -641,161 +575,157 @@ proc cycleCheck(n: PNode; c: var Con) = message(c.graph.config, n.info, warnCycleCreated, msg) break -proc pVarTopLevel(v: PNode; c: var Con; ri, res: PNode) = +proc pVarTopLevel(v: PNode; c: var Con; s: var Scope; ri, res: PNode) = # move the variable declaration to the top of the frame: - if not containsOrIncl(c.declaredVars, v.sym.id): - c.addTopVar v + c.addTopVar s, v if isUnpackedTuple(v): if c.inLoop > 0: # unpacked tuple needs reset at every loop iteration res.add newTree(nkFastAsgn, v, genDefaultCall(v.typ, c, v.info)) elif sfThread notin v.sym.flags: # do not destroy thread vars for now at all for consistency. - if sfGlobal in v.sym.flags: + if sfGlobal in v.sym.flags and s.parent == nil: c.graph.globalDestructors.add genDestroy(c, v) else: - c.destroys.add genDestroy(c, v) + s.final.add genDestroy(c, v) if ri.kind == nkEmpty and c.inLoop > 0: - res.add moveOrCopy(v, genDefaultCall(v.typ, c, v.info), c, isDecl = true) + res.add moveOrCopy(v, genDefaultCall(v.typ, c, v.info), c, s, isDecl = true) elif ri.kind != nkEmpty: - res.add moveOrCopy(v, ri, c, isDecl = true) - -proc pVarScoped(v: PNode; c: var Con; ri, res: PNode) = - if not containsOrIncl(c.declaredVars, v.sym.id): - c.addTopVar(v) - if isUnpackedTuple(v): - if c.inLoop > 0: - # unpacked tuple needs reset at every loop iteration - res.add newTree(nkFastAsgn, v, genDefaultCall(v.typ, c, v.info)) - elif {sfGlobal, sfThread} * v.sym.flags == {sfGlobal}: - c.graph.globalDestructors.add genDestroy(c, v) - else: - # We always translate 'var v = f()' into bitcopies. If 'v' is in a loop, - # the destruction at the loop end will free the resources. Other assignments - # will destroy the old value inside 'v'. If we have 'var v' without an initial - # default value we translate it into 'var v = default()'. We translate - # 'var x = someGlobal' into 'var v = default(); `=`(v, someGlobal). The - # lack of copy constructors is really beginning to hurt us. :-( - #if c.inDangerousBranch == 0: v.sym.flags.incl sfNoInit - c.scopeDestroys.add genDestroy(c, v) - if ri.kind == nkEmpty and c.inLoop > 0: - res.add moveOrCopy(v, genDefaultCall(v.typ, c, v.info), c, isDecl = true) - elif ri.kind != nkEmpty: - res.add moveOrCopy(v, ri, c, isDecl = true) + res.add moveOrCopy(v, ri, c, s, isDecl = true) template handleNestedTempl(n: untyped, processCall: untyped) = + template maybeVoid(child, s): untyped = + if isEmptyType(child.typ): p(child, c, s, normal) + else: processCall(child, s) + case n.kind of nkStmtList, nkStmtListExpr: + # a statement list does not open a new scope if n.len == 0: return n result = copyNode(n) for i in 0..<n.len-1: - result.add p(n[i], c, normal) - template node: untyped = n[^1] - result.add processCall + result.add p(n[i], c, s, normal) + result.add maybeVoid(n[^1], s) + + of nkCaseStmt: + result = copyNode(n) + result.add p(n[0], c, s, normal) + for i in 1..<n.len: + let it = n[i] + assert it.kind in {nkOfBranch, nkElse} + + var branch = shallowCopy(it) + for j in 0 ..< it.len-1: + branch[j] = copyTree(it[j]) + var ofScope = nestedScope(s) + let ofResult = maybeVoid(it[^1], ofScope) + branch[^1] = toTree(ofScope, ofResult) + result.add branch + rememberParent(s, ofScope) + + of nkWhileStmt: + inc c.inLoop + result = copyNode(n) + result.add p(n[0], c, s, normal) + var bodyScope = nestedScope(s) + let bodyResult = p(n[1], c, bodyScope, normal) + result.add toTree(bodyScope, bodyResult) + rememberParent(s, bodyScope) + dec c.inLoop + of nkBlockStmt, nkBlockExpr: result = copyNode(n) result.add n[0] - template node: untyped = n[1] - result.add processCall + var bodyScope = nestedScope(s) + let bodyResult = processCall(n[1], bodyScope) + result.add toTree(bodyScope, bodyResult) + rememberParent(s, bodyScope) + of nkIfStmt, nkIfExpr: result = copyNode(n) - for son in n: - var branch = copyNode(son) - if son.kind in {nkElifBranch, nkElifExpr}: - template node: untyped = son[1] - branch.add p(son[0], c, normal) #The condition - branch.add if node.typ == nil: p(node, c, normal) #noreturn - else: processCall - else: - template node: untyped = son[0] - branch.add if node.typ == nil: p(node, c, normal) #noreturn - else: processCall + for i in 0..<n.len: + let it = n[i] + var branch = shallowCopy(it) + var branchScope = nestedScope(s) + branchScope.parent = nil + if it.kind in {nkElifBranch, nkElifExpr}: + let cond = p(it[0], c, branchScope, normal) + branch[0] = toTree(branchScope, cond, onlyCareAboutVars = true) + + branchScope.parent = addr(s) + var branchResult = processCall(it[^1], branchScope) + branch[^1] = toTree(branchScope, branchResult) result.add branch - of nkCaseStmt: + rememberParent(s, branchScope) + + of nkTryStmt: result = copyNode(n) - result.add p(n[0], c, normal) + var tryScope = nestedScope(s) + var tryResult = maybeVoid(n[0], tryScope) + result.add toTree(tryScope, tryResult) + rememberParent(s, tryScope) + for i in 1..<n.len: - var branch: PNode - if n[i].kind == nkOfBranch: - branch = n[i] # of branch conditions are constants - template node: untyped = n[i][^1] - branch[^1] = if node.typ == nil: p(node, c, normal) #noreturn - else: processCall - elif n[i].kind in {nkElifBranch, nkElifExpr}: - branch = copyNode(n[i]) - branch.add p(n[i][0], c, normal) #The condition - template node: untyped = n[i][1] - branch.add if node.typ == nil: p(node, c, normal) #noreturn - else: processCall - else: - branch = copyNode(n[i]) - template node: untyped = n[i][0] - branch.add if node.typ == nil: p(node, c, normal) #noreturn - else: processCall + let it = n[i] + var branch = copyTree(it) + var branchScope = nestedScope(s) + var branchResult = if it.kind == nkFinally: p(it[^1], c, branchScope, normal) + else: processCall(it[^1], branchScope) + branch[^1] = toTree(branchScope, branchResult) result.add branch + rememberParent(s, branchScope) + of nkWhen: # This should be a "when nimvm" node. result = copyTree(n) - template node: untyped = n[1][0] - result[1][0] = processCall - of nkWhileStmt: - inc c.inLoop - result = copyNode(n) - result.add p(n[0], c, normal) - result.add p(n[1], c, normal) - dec c.inLoop + result[1][0] = processCall(n[1][0], s) else: assert(false) -when false: - proc eqTrees*(a, b: PNode): bool = - if a == b: - result = true - elif (a != nil) and (b != nil) and (a.kind == b.kind): - case a.kind - of nkSym: - #result = a.sym == b.sym or (a.sym.kind == skTemp and b.sym.kind == skTemp) - result = true - of nkIdent: result = a.ident.id == b.ident.id - of nkCharLit..nkUInt64Lit: result = a.intVal == b.intVal - of nkFloatLit..nkFloat64Lit: - result = cast[uint64](a.floatVal) == cast[uint64](b.floatVal) - of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal - of nkCommentStmt: result = a.comment == b.comment - of nkEmpty, nkNilLit, nkType: result = true - else: - if a.len == b.len: - for i in 0..<a.len: - if not eqTrees(a[i], b[i]): return - result = true - if not result: - #if a.kind == nkFloat64Lit and b.kind == nkFloat64Lit: - echo "not the same ", a.kind, " ", b.kind - #echo a.floatVal, "##", b.floatVal, "##" - -proc p(n: PNode; c: var Con; mode: ProcessMode): PNode = +proc pRaiseStmt(n: PNode, c: var Con; s: var Scope): PNode = + if optOwnedRefs in c.graph.config.globalOptions and n[0].kind != nkEmpty: + if n[0].kind in nkCallKinds: + let call = p(n[0], c, s, normal) + result = copyNode(n) + result.add call + else: + let tmp = getTemp(c, s, n[0].typ, n.info) + var m = genCopyNoCheck(c, tmp, n[0]) + m.add p(n[0], c, s, normal) + result = newTree(nkStmtList, genWasMoved(tmp, c), m) + var toDisarm = n[0] + if toDisarm.kind == nkStmtListExpr: toDisarm = toDisarm.lastSon + if toDisarm.kind == nkSym and toDisarm.sym.owner == c.owner: + result.add genWasMoved(toDisarm, c) + result.add newTree(nkRaiseStmt, tmp) + else: + result = copyNode(n) + if n[0].kind != nkEmpty: + result.add p(n[0], c, s, sinkArg) + else: + result.add copyNode(n[0]) + s.needsTry = true + +proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode = if n.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt, nkIfExpr, nkCaseStmt, nkWhen, nkWhileStmt}: - when not scopeBasedDestruction: - handleNestedTempl(n): p(node, c, mode) - else: - result = handleNested(n, nil, c, mode) + template process(child, s): untyped = p(child, c, s, mode) + handleNestedTempl(n, process) elif mode == sinkArg: if n.containsConstSeq: # const sequences are not mutable and so we need to pass a copy to the # sink parameter (bug #11524). Note that the string implementation is # different and can deal with 'const string sunk into var'. - result = passCopyToSink(n, c) + result = passCopyToSink(n, c, s) elif n.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkClosure, nkNilLit} + nkCallKinds + nkLiterals: - result = p(n, c, consumed) + result = p(n, c, s, consumed) elif n.kind == nkSym and isSinkParam(n.sym) and isLastRead(n, c): # Sinked params can be consumed only once. We need to reset the memory # to disable the destructor which we have not elided - #sinkParamIsLastReadCheck(c, n) - result = destructiveMoveVar(n, c) + result = destructiveMoveVar(n, c, s) elif isAnalysableFieldAccess(n, c.owner) and isLastRead(n, c): # it is the last read, can be sinkArg. We need to reset the memory # to disable the destructor which we have not elided - result = destructiveMoveVar(n, c) + result = destructiveMoveVar(n, c, s) elif n.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}: result = copyTree(n) if n.typ.skipTypes(abstractInst-{tyOwned}).kind != tyOwned and @@ -803,19 +733,19 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode = # allow conversions from owned to unowned via this little hack: let nTyp = n[1].typ n[1].typ = n.typ - result[1] = p(n[1], c, sinkArg) + result[1] = p(n[1], c, s, sinkArg) result[1].typ = nTyp else: - result[1] = p(n[1], c, sinkArg) + result[1] = p(n[1], c, s, sinkArg) elif n.kind in {nkObjDownConv, nkObjUpConv}: result = copyTree(n) - result[0] = p(n[0], c, sinkArg) + result[0] = p(n[0], c, s, sinkArg) elif n.typ == nil: # 'raise X' can be part of a 'case' expression. Deal with it here: - result = p(n, c, normal) + result = p(n, c, s, normal) else: # copy objects that are not temporary but passed to a 'sink' parameter - result = passCopyToSink(n, c) + result = passCopyToSink(n, c, s) else: case n.kind of nkBracket, nkObjConstr, nkTupleConstr, nkClosure: @@ -837,11 +767,11 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode = result = copyTree(n) for i in ord(n.kind in {nkObjConstr, nkClosure})..<n.len: if n[i].kind == nkExprColonExpr: - result[i][1] = p(n[i][1], c, m) + result[i][1] = p(n[i][1], c, s, m) else: - result[i] = p(n[i], c, m) + result[i] = p(n[i], c, s, m) if mode == normal and isRefConstr: - result = ensureDestruction(result, c) + result = ensureDestruction(result, c, s) of nkCallKinds: let inSpawn = c.inSpawn if n[0].kind == nkSym and n[0].sym.magic == mSpawn: @@ -852,20 +782,22 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode = let parameters = n[0].typ let L = if parameters != nil: parameters.len else: 0 - var isDangerous = false - if n[0].kind == nkSym and n[0].sym.magic in {mOr, mAnd}: - inc c.inDangerousBranch - isDangerous = true + when false: + var isDangerous = false + if n[0].kind == nkSym and n[0].sym.magic in {mOr, mAnd}: + inc c.inDangerousBranch + isDangerous = true result = shallowCopy(n) for i in 1..<n.len: if i < L and (isSinkTypeForParam(parameters[i]) or inSpawn > 0): - result[i] = p(n[i], c, sinkArg) + result[i] = p(n[i], c, s, sinkArg) else: - result[i] = p(n[i], c, normal) + result[i] = p(n[i], c, s, normal) - if isDangerous: - dec c.inDangerousBranch + when false: + if isDangerous: + dec c.inDangerousBranch if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}: result[0] = copyTree(n[0]) @@ -873,15 +805,14 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode = let destroyOld = genDestroy(c, result[1]) result = newTree(nkStmtList, destroyOld, result) else: - result[0] = p(n[0], c, normal) - when scopeBasedDestruction: - if canRaise(n[0]): inc c.hasUnstructuredCf + result[0] = p(n[0], c, s, normal) + if canRaise(n[0]): s.needsTry = true if mode == normal: - result = ensureDestruction(result, c) + result = ensureDestruction(result, c, s) of nkDiscardStmt: # Small optimization result = shallowCopy(n) if n[0].kind != nkEmpty: - result[0] = p(n[0], c, normal) + result[0] = p(n[0], c, s, normal) else: result[0] = copyNode(n[0]) of nkVarSection, nkLetSection: @@ -891,27 +822,24 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode = var ri = it[^1] if it.kind == nkVarTuple and hasDestructor(ri.typ): let x = lowerTupleUnpacking(c.graph, it, c.owner) - result.add p(x, c, consumed) + result.add p(x, c, s, consumed) elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0]): for j in 0..<it.len-2: let v = it[j] if v.kind == nkSym: if sfCompileTime in v.sym.flags: continue - when not scopeBasedDestruction: - pVarTopLevel(v, c, ri, result) - else: - pVarScoped(v, c, ri, result) + pVarTopLevel(v, c, s, ri, result) else: if ri.kind == nkEmpty and c.inLoop > 0: ri = genDefaultCall(v.typ, c, v.info) if ri.kind != nkEmpty: - result.add moveOrCopy(v, ri, c, isDecl = true) + result.add moveOrCopy(v, ri, c, s, isDecl = true) else: # keep the var but transform 'ri': var v = copyNode(n) var itCopy = copyNode(it) for j in 0..<it.len-1: itCopy.add it[j] - itCopy.add p(it[^1], c, normal) + itCopy.add p(it[^1], c, s, normal) v.add itCopy result.add v of nkAsgn, nkFastAsgn: @@ -924,131 +852,99 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode = if n[0].kind in {nkDotExpr, nkCheckedFieldExpr}: cycleCheck(n, c) assert n[1].kind notin {nkAsgn, nkFastAsgn} - result = moveOrCopy(p(n[0], c, mode), n[1], c) + result = moveOrCopy(p(n[0], c, s, mode), n[1], c, s) elif isDiscriminantField(n[0]): - result = genDiscriminantAsgn(c, n) + result = genDiscriminantAsgn(c, s, n) else: result = copyNode(n) - result.add p(n[0], c, mode) - result.add p(n[1], c, consumed) + result.add p(n[0], c, s, mode) + result.add p(n[1], c, s, consumed) of nkRaiseStmt: - if optOwnedRefs in c.graph.config.globalOptions and n[0].kind != nkEmpty: - if n[0].kind in nkCallKinds: - let call = p(n[0], c, normal) - result = copyNode(n) - result.add call - else: - let tmp = getTemp(c, n[0].typ, n.info) - c.addTopVar(tmp) - var m = genCopyNoCheck(c, tmp, n[0]) - m.add p(n[0], c, normal) - result = newTree(nkStmtList, genWasMoved(tmp, c), m) - var toDisarm = n[0] - if toDisarm.kind == nkStmtListExpr: toDisarm = toDisarm.lastSon - if toDisarm.kind == nkSym and toDisarm.sym.owner == c.owner: - result.add genWasMoved(toDisarm, c) - result.add newTree(nkRaiseStmt, tmp) - else: - result = copyNode(n) - if n[0].kind != nkEmpty: - result.add p(n[0], c, sinkArg) - else: - result.add copyNode(n[0]) - inc c.hasUnstructuredCf + result = pRaiseStmt(n, c, s) of nkWhileStmt: - result = handleNested(n, nil, c, mode) + internalError(c.graph.config, n.info, "nkWhileStmt should have been handled earlier") + result = n of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt, nkExportStmt, nkPragma, nkCommentStmt, nkBreakState: result = n of nkBreakStmt: - inc c.hasUnstructuredCf + s.needsTry = true result = n of nkReturnStmt: result = shallowCopy(n) for i in 0..<n.len: - result[i] = p(n[i], c, mode) - inc c.hasUnstructuredCf + result[i] = p(n[i], c, s, mode) + s.needsTry = true of nkCast: result = shallowCopy(n) result[0] = n[0] - result[1] = p(n[1], c, mode) + result[1] = p(n[1], c, s, mode) + of nkCheckedFieldExpr: + result = shallowCopy(n) + result[0] = p(n[0], c, s, mode) + for i in 1..<n.len: + result[i] = n[i] else: result = shallowCopy(n) for i in 0..<n.len: - result[i] = p(n[i], c, mode) + result[i] = p(n[i], c, s, mode) -proc moveOrCopy(dest, ri: PNode; c: var Con, isDecl = false): PNode = +proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNode = case ri.kind of nkCallKinds: - result = genSink(c, dest, p(ri, c, consumed), isDecl) + result = genSink(c, s, dest, p(ri, c, s, consumed), isDecl) of nkBracketExpr: if isUnpackedTuple(ri[0]): # unpacking of tuple: take over the elements - result = genSink(c, dest, p(ri, c, consumed), isDecl) + result = genSink(c, s, dest, p(ri, c, s, consumed), isDecl) elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c) and not aliases(dest, ri): # Rule 3: `=sink`(x, z); wasMoved(z) - var snk = genSink(c, dest, ri, isDecl) + var snk = genSink(c, s, dest, ri, isDecl) result = newTree(nkStmtList, snk, genWasMoved(ri, c)) else: result = genCopy(c, dest, ri) - result.add p(ri, c, consumed) + result.add p(ri, c, s, consumed) of nkBracket: # array constructor if ri.len > 0 and isDangerousSeq(ri.typ): result = genCopy(c, dest, ri) - result.add p(ri, c, consumed) + result.add p(ri, c, s, consumed) else: - result = genSink(c, dest, p(ri, c, consumed), isDecl) + result = genSink(c, s, dest, p(ri, c, s, consumed), isDecl) of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit: - result = genSink(c, dest, p(ri, c, consumed), isDecl) + result = genSink(c, s, dest, p(ri, c, s, consumed), isDecl) of nkSym: if isSinkParam(ri.sym) and isLastRead(ri, c): # Rule 3: `=sink`(x, z); wasMoved(z) - #sinkParamIsLastReadCheck(c, ri) - let snk = genSink(c, dest, ri, isDecl) + let snk = genSink(c, s, dest, ri, isDecl) result = newTree(nkStmtList, snk, genWasMoved(ri, c)) elif ri.sym.kind != skParam and ri.sym.owner == c.owner and isLastRead(ri, c) and canBeMoved(c, dest.typ): # Rule 3: `=sink`(x, z); wasMoved(z) - let snk = genSink(c, dest, ri, isDecl) + let snk = genSink(c, s, dest, ri, isDecl) result = newTree(nkStmtList, snk, genWasMoved(ri, c)) else: result = genCopy(c, dest, ri) - result.add p(ri, c, consumed) - of nkHiddenSubConv, nkHiddenStdConv, nkConv: - when false: - result = moveOrCopy(dest, ri[1], c, isDecl) - if not sameType(ri.typ, ri[1].typ): - let copyRi = copyTree(ri) - copyRi[1] = result[^1] - result[^1] = copyRi - else: - result = genSink(c, dest, p(ri, c, sinkArg), isDecl) - of nkObjDownConv, nkObjUpConv: - when false: - result = moveOrCopy(dest, ri[0], c, isDecl) - let copyRi = copyTree(ri) - copyRi[0] = result[^1] - result[^1] = copyRi - else: - result = genSink(c, dest, p(ri, c, sinkArg), isDecl) + result.add p(ri, c, s, consumed) + of nkHiddenSubConv, nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv: + result = genSink(c, s, dest, p(ri, c, s, sinkArg), isDecl) of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt: - when scopeBasedDestruction: - result = handleNested(ri, dest, c, normal) - else: - handleNestedTempl(ri): moveOrCopy(dest, node, c, isDecl) + template process(child, s): untyped = moveOrCopy(dest, child, c, s, isDecl) + handleNestedTempl(ri, process) + of nkRaiseStmt: + result = pRaiseStmt(ri, c, s) else: if isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c) and canBeMoved(c, dest.typ): # Rule 3: `=sink`(x, z); wasMoved(z) - let snk = genSink(c, dest, ri, isDecl) + let snk = genSink(c, s, dest, ri, isDecl) result = newTree(nkStmtList, snk, genWasMoved(ri, c)) else: result = genCopy(c, dest, ri) - result.add p(ri, c, consumed) + result.add p(ri, c, s, consumed) proc computeUninit(c: var Con) = if not c.uninitComputed: @@ -1102,30 +998,19 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = echo "\n### ", owner.name.s, ":\nCFG:" echoCfg(c.g) echo n + + var scope: Scope + let body = p(n, c, scope, normal) + if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}: let params = owner.typ.n for i in 1..<params.len: let t = params[i].sym.typ if isSinkTypeForParam(t) and hasDestructor(t.skipTypes({tySink})): - c.destroys.add genDestroy(c, params[i]) + scope.final.add genDestroy(c, params[i]) #if optNimV2 in c.graph.config.globalOptions: # injectDefaultCalls(n, c) - let body = p(n, c, normal) - result = newNodeI(nkStmtList, n.info) - if c.topLevelVars.len > 0: - result.add c.topLevelVars - if c.destroys.len > 0 or c.scopeDestroys.len > 0: - reverse c.destroys.sons - var fin: PNode - if owner.kind == skModule: - fin = newTryFinally(body, extractDestroysForTemporaries(c, c.destroys)) - g.globalDestructors.add c.destroys - else: - fin = newTryFinally(body, c.destroys) - for i in countdown(c.scopeDestroys.high, 0): fin[1][0].add c.scopeDestroys[i] - result.add fin - else: - result.add body + result = toTree(scope, body) dbg: echo ">---------transformed-to--------->" echo renderTree(result, {renderIds}) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index a2515357b..c88ff3609 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -229,8 +229,8 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode = a[0][2] = newSymNode(symbol, a[0][2].info) elif a.len == 1: - # count number of ``except: body`` blocks - inc catchAllExcepts + # count number of ``except: body`` blocks + inc catchAllExcepts else: # support ``except KeyError, ValueError, ... : body`` diff --git a/compiler/spawn.nim b/compiler/spawn.nim index 9d837b993..15bed77b0 100644 --- a/compiler/spawn.nim +++ b/compiler/spawn.nim @@ -10,7 +10,7 @@ ## This module implements threadpool's ``spawn``. import ast, types, idents, magicsys, msgs, options, modulegraphs, - lowerings, liftdestructors + lowerings, liftdestructors, renderer from trees import getMagic, getRoot proc callProc(a: PNode): PNode = @@ -321,7 +321,7 @@ proc wrapProcForSpawn*(g: ModuleGraph; owner: PSym; spawnExpr: PNode; retType: P result = newNodeI(nkStmtList, n.info) if n.kind notin nkCallKinds: - localError(g.config, n.info, "'spawn' takes a call expression") + localError(g.config, n.info, "'spawn' takes a call expression; got " & $n) return if optThreadAnalysis in g.config.globalOptions: if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}: |