diff options
-rw-r--r-- | compiler/dfa.nim | 2 | ||||
-rw-r--r-- | compiler/injectdestructors.nim | 301 | ||||
-rw-r--r-- | tests/arc/tarcmisc.nim | 38 | ||||
-rw-r--r-- | tests/arc/tcontrolflow.nim | 4 | ||||
-rw-r--r-- | tests/arc/tmovebug.nim | 169 | ||||
-rw-r--r-- | tests/destructor/tmove_objconstr.nim | 1 |
6 files changed, 317 insertions, 198 deletions
diff --git a/compiler/dfa.nim b/compiler/dfa.nim index 2254cd0ff..f3b358d72 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -628,7 +628,7 @@ proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool = # XXX Allow closure deref operations here if we know # the owner controlled the closure allocation? result = n.kind == nkSym and n.sym.owner == owner and - owner.kind != skModule and + sfGlobal notin n.sym.flags and (n.sym.kind != skParam or isSinkParam(n.sym)) # or n.sym.typ.kind == tyVar) # Note: There is a different move analyzer possible that checks for # consume(param.key); param.key = newValue for all paths. Then code like diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index da41b2d5c..ff311f079 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -37,18 +37,12 @@ type final: seq[PNode] # finally section needsTry: bool parent: ptr Scope - escapingSyms: IntSet # a construct like (block: let x = f(); x) - # means that 'x' escapes. We then destroy it - # in the parent's scope (and also allocate it there). - escapingExpr: PNode type Con = object owner: PSym g: ControlFlowGraph - jumpTargets: IntSet graph: ModuleGraph - emptyNode: PNode otherRead: PNode inLoop, inSpawn: int uninit: IntSet # set of uninit'ed vars @@ -72,12 +66,11 @@ proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo): PNode = s.vars.add(sym) result = newSymNode(sym) +const sfSingleUsedTemp = sfExported # For temporaries that we know will only be used once + 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 @@ -124,68 +117,6 @@ proc optimize(s: var Scope) = filterNil(wasMoved) filterNil(final) -type - ToTreeFlag = enum - onlyCareAboutVars, - producesValue - -proc toTree(c: var Con; s: var Scope; ret: PNode; flags: set[ToTreeFlag]): PNode = - proc isComplexStmtListExpr(n: PNode): bool = - n.kind == nkStmtListExpr and (n.len == 0 or n[^1].kind != nkSym) - - #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: - let isExpr = producesValue in flags and not isEmptyType(ret.typ) - var r = PNode(nil) - if isExpr: - result = newNodeIT(nkStmtListExpr, ret.info, ret.typ) - if ret.kind in nkCallKinds or isComplexStmtListExpr(ret): - r = c.getTemp(s, ret.typ, ret.info) - else: - result = newNodeI(nkStmtList, ret.info) - - 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 in flags: - 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: - if r != nil: - if ret.kind == nkStmtListExpr: - # simplify it a bit further by merging the nkStmtListExprs - let last = ret.len - 1 - for i in 0 ..< last: result.add ret[i] - result.add newTree(nkFastAsgn, r, ret[last]) - else: - result.add newTree(nkFastAsgn, r, ret) - for m in s.wasMoved: result.add m - for i in countdown(s.final.high, 0): result.add s.final[i] - result.add r - elif ret.kind == nkStmtListExpr: - # simplify it a bit further by merging the nkStmtListExprs - let last = ret.len - 1 - for i in 0 ..< last: result.add ret[i] - for m in s.wasMoved: result.add m - for i in countdown(s.final.high, 0): result.add s.final[i] - result.add ret[last] - else: - result.add ret - for m in s.wasMoved: result.add m - for i in countdown(s.final.high, 0): result.add s.final[i] - 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 @@ -228,6 +159,7 @@ proc isLastRead(n: PNode; c: var Con): bool = # first we need to search for the instruction that belongs to 'n': var instr = -1 let m = dfa.skipConvDfa(n) + if m.kind == nkSym and sfSingleUsedTemp in m.sym.flags: return true for i in 0..<c.g.len: # This comparison is correct and MUST not be ``instrTargets``: @@ -399,7 +331,7 @@ 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; s: var Scope; dest, ri: PNode, isDecl = false): PNode = +proc genSink(c: var Con; dest, ri: PNode, isDecl = false): PNode = if isUnpackedTuple(dest) or isDecl or (isAnalysableFieldAccess(dest, c.owner) and isFirstWrite(dest, c)) or isNoInit(dest): @@ -427,12 +359,6 @@ proc genCopy(c: var Con; dest, ri: PNode): PNode = c.checkForErrorPragma(t, ri, "=") result = c.genCopyNoCheck(dest, ri) -proc addTopVar(c: var Con; s: var Scope; v: PNode): ptr Scope = - result = addr(s) - while v.sym.id in result.escapingSyms and result.parent != nil: - result = result.parent - result[].vars.add v.sym - 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 @@ -494,7 +420,7 @@ proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode = var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3) vpart[0] = tempAsNode - vpart[1] = c.emptyNode + vpart[1] = newNodeI(nkEmpty, tempAsNode.info) vpart[2] = n v.add(vpart) @@ -553,16 +479,10 @@ proc ensureDestruction(arg, orig: PNode; c: var Con; s: var Scope): PNode = # 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) - if orig == s.escapingExpr and s.parent != nil: - let tmp = c.getTemp(s.parent[], arg.typ, arg.info) - result.add c.genSink(s, tmp, arg, isDecl = true) - result.add tmp - s.parent[].final.add c.genDestroy(tmp) - else: - let tmp = c.getTemp(s, arg.typ, arg.info) - result.add c.genSink(s, tmp, arg, isDecl = true) - result.add tmp - s.final.add c.genDestroy(tmp) + let tmp = c.getTemp(s, arg.typ, arg.info) + result.add c.genSink(tmp, arg, isDecl = true) + result.add tmp + s.final.add c.genDestroy(tmp) else: result = arg @@ -607,73 +527,95 @@ proc cycleCheck(n: PNode; c: var Con) = message(c.graph.config, n.info, warnCycleCreated, msg) break -proc markEscapingVarsRec(n: PNode; s: var Scope) = - case n.kind - of nkSym: - s.escapingSyms.incl n.sym.id - of nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, nkDerefExpr, nkHiddenDeref, - nkAddr, nkHiddenAddr, nkStringToCString, nkCStringToString, nkObjDownConv, - nkObjUpConv: - markEscapingVarsRec(n[0], s) - of nkCast, nkHiddenSubConv, nkHiddenStdConv, nkConv: - markEscapingVarsRec(n[1], s) - of nkTupleConstr, nkBracket, nkPar, nkClosure: - for i in 0 ..< n.len: - markEscapingVarsRec(n[i], s) - of nkObjConstr: - for i in 1 ..< n.len: - markEscapingVarsRec(n[i], s) - of nkStmtList, nkStmtListExpr: - if n.len > 0: - markEscapingVarsRec(n[^1], s) - else: - discard "no arbitrary tree traversal here" - -proc markEscapingVars(n: PNode; s: var Scope) = - markEscapingVarsRec(n, s) - var it = n - while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0: - it = it[^1] - s.escapingExpr = it - proc pVarTopLevel(v: PNode; c: var Con; s: var Scope; ri, res: PNode) = # move the variable declaration to the top of the frame: - let owningScope = c.addTopVar(s, v) + s.vars.add v.sym 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 and s.parent == nil: - c.graph.globalDestructors.add c.genDestroy(v) #No need to genWasMoved here + if sfGlobal in v.sym.flags and s.parent == nil: #XXX: Rethink this logic (see tarcmisc.test2) + c.graph.globalDestructors.add c.genDestroy(v) else: - owningScope[].final.add c.genDestroy(v) + s.final.add c.genDestroy(v) if ri.kind == nkEmpty and c.inLoop > 0: res.add moveOrCopy(v, genDefaultCall(v.typ, c, v.info), c, s, isDecl = true) elif ri.kind != nkEmpty: res.add moveOrCopy(v, ri, c, s, isDecl = true) -template handleNestedTempl(n, processCall: untyped; alwaysStmt: bool) = +proc processScope(c: var Con; s: var Scope; ret: PNode): PNode = + result = newNodeI(nkStmtList, ret.info) + 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 s.wasMoved.len > 0 or s.final.len > 0: + let 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] + if s.needsTry: + result.add newTryFinally(ret, finSection) + else: + result.add ret + result.add finSection + else: + result.add ret + + if s.parent != nil: s.parent[].needsTry = s.parent[].needsTry or s.needsTry + +template processScopeExpr(c: var Con; s: var Scope; ret: PNode, processCall: untyped): PNode = + assert not ret.typ.isEmptyType + var result = newNodeI(nkStmtListExpr, ret.info) + # There is a possibility to do this check: s.wasMoved.len > 0 or s.final.len > 0 + # later and use it to eliminate the temporary when theres no need for it, but its + # tricky because you would have to intercept moveOrCopy at a certain point + let tmp = c.getTemp(s.parent[], ret.typ, ret.info) + tmp.sym.flags.incl sfSingleUsedTemp + let cpy = if ret.typ.hasDestructor: + moveOrCopy(tmp, ret, c, s, isDecl = true) + else: + newTree(nkFastAsgn, tmp, p(ret, c, s, normal)) + + 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 + let 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] + if s.needsTry: + result.add newTryFinally(newTree(nkStmtListExpr, cpy, processCall(tmp, s.parent[])), finSection) + else: + result.add cpy + result.add finSection + result.add processCall(tmp, s.parent[]) + + if s.parent != nil: s.parent[].needsTry = s.parent[].needsTry or s.needsTry + + result + +template handleNestedTempl(n, processCall: untyped, willProduceStmt = false) = template maybeVoid(child, s): untyped = if isEmptyType(child.typ): p(child, c, s, normal) else: processCall(child, s) - let treeFlags = if not isEmptyType(n.typ) and not alwaysStmt: {producesValue} else: {} case n.kind of nkStmtList, nkStmtListExpr: # a statement list does not open a new scope if n.len == 0: return n result = copyNode(n) - if alwaysStmt: result.typ = nil for i in 0..<n.len-1: result.add p(n[i], c, s, normal) result.add maybeVoid(n[^1], s) - markEscapingVars(n[^1], s) of nkCaseStmt: result = copyNode(n) - if alwaysStmt: result.typ = nil result.add p(n[0], c, s, normal) for i in 1..<n.len: let it = n[i] @@ -683,11 +625,11 @@ template handleNestedTempl(n, processCall: untyped; alwaysStmt: bool) = for j in 0 ..< it.len-1: branch[j] = copyTree(it[j]) var ofScope = nestedScope(s) - markEscapingVars(it[^1], ofScope) - let ofResult = maybeVoid(it[^1], ofScope) - branch[^1] = toTree(c, ofScope, ofResult, treeFlags) + branch[^1] = if it[^1].typ.isEmptyType or willProduceStmt: + processScope(c, ofScope, maybeVoid(it[^1], ofScope)) + else: + processScopeExpr(c, ofScope, it[^1], processCall) result.add branch - rememberParent(s, ofScope) of nkWhileStmt: inc c.inLoop @@ -695,61 +637,55 @@ template handleNestedTempl(n, processCall: untyped; alwaysStmt: bool) = result.add p(n[0], c, s, normal) var bodyScope = nestedScope(s) let bodyResult = p(n[1], c, bodyScope, normal) - result.add toTree(c, bodyScope, bodyResult, treeFlags) - rememberParent(s, bodyScope) + result.add processScope(c, bodyScope, bodyResult) dec c.inLoop of nkBlockStmt, nkBlockExpr: result = copyNode(n) - if alwaysStmt: result.typ = nil result.add n[0] var bodyScope = nestedScope(s) - markEscapingVars(n[1], bodyScope) - let bodyResult = processCall(n[1], bodyScope) - result.add toTree(c, bodyScope, bodyResult, treeFlags) - rememberParent(s, bodyScope) + result.add if n[1].typ.isEmptyType or willProduceStmt: + processScope(c, bodyScope, processCall(n[1], bodyScope)) + else: + processScopeExpr(c, bodyScope, n[1], processCall) of nkIfStmt, nkIfExpr: result = copyNode(n) - if alwaysStmt: result.typ = nil 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(c, branchScope, cond, {producesValue, onlyCareAboutVars}) + #Condition needs to be destroyed outside of the condition/branch scope + branch[0] = p(it[0], c, s, normal) - branchScope.parent = addr(s) - markEscapingVars(it[^1], branchScope) - var branchResult = processCall(it[^1], branchScope) - branch[^1] = toTree(c, branchScope, branchResult, treeFlags) + branch[^1] = if it[^1].typ.isEmptyType or willProduceStmt: + processScope(c, branchScope, processCall(it[^1], branchScope)) + else: + processScopeExpr(c, branchScope, it[^1], processCall) result.add branch - rememberParent(s, branchScope) of nkTryStmt: result = copyNode(n) - if alwaysStmt: result.typ = nil var tryScope = nestedScope(s) - markEscapingVars(n[0], tryScope) - var tryResult = maybeVoid(n[0], tryScope) - result.add toTree(c, tryScope, tryResult, treeFlags) - rememberParent(s, tryScope) + result.add if n[0].typ.isEmptyType or willProduceStmt: + processScope(c, tryScope, maybeVoid(n[0], tryScope)) + else: + processScopeExpr(c, tryScope, n[0], maybeVoid) for i in 1..<n.len: 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(c, branchScope, branchResult, treeFlags) + branch[^1] = if it[^1].typ.isEmptyType or willProduceStmt or it.kind == nkFinally: + processScope(c, branchScope, if it.kind == nkFinally: p(it[^1], c, branchScope, normal) + else: processCall(it[^1], branchScope)) + else: + processScopeExpr(c, branchScope, it[^1], processCall) result.add branch - rememberParent(s, branchScope) of nkWhen: # This should be a "when nimvm" node. result = copyTree(n) - if alwaysStmt: result.typ = nil result[1][0] = processCall(n[1][0], s) else: assert(false) @@ -781,7 +717,7 @@ 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, nkTryStmt}: template process(child, s): untyped = p(child, c, s, mode) - handleNestedTempl(n, process, false) + handleNestedTempl(n, process) elif mode == sinkArg: if n.containsConstSeq: # const sequences are not mutable and so we need to pass a copy to the @@ -791,14 +727,10 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode = elif n.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkClosure, nkNilLit} + nkCallKinds + nkLiterals: result = p(n, c, s, consumed) - elif n.kind == nkSym and isSinkParam(n.sym) and isLastRead(n, c): + elif ((n.kind == nkSym and isSinkParam(n.sym)) or isAnalysableFieldAccess(n, c.owner)) 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 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, s) elif n.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}: result = copyTree(n) if n.typ.skipTypes(abstractInst-{tyOwned}).kind != tyOwned and @@ -1028,15 +960,15 @@ 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 = case ri.kind of nkCallKinds: - result = c.genSink(s, dest, p(ri, c, s, consumed), isDecl) + result = c.genSink(dest, p(ri, c, s, consumed), isDecl) of nkBracketExpr: if isUnpackedTuple(ri[0]): # unpacking of tuple: take over the elements - result = c.genSink(s, dest, p(ri, c, s, consumed), isDecl) + result = c.genSink(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 = c.genSink(s, dest, ri, isDecl) + var snk = c.genSink(dest, ri, isDecl) result = newTree(nkStmtList, snk, c.genWasMoved(ri)) else: result = c.genCopy(dest, ri) @@ -1047,34 +979,35 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNod result = c.genCopy(dest, ri) result.add p(ri, c, s, consumed) else: - result = c.genSink(s, dest, p(ri, c, s, consumed), isDecl) + result = c.genSink(dest, p(ri, c, s, consumed), isDecl) of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit: - result = c.genSink(s, dest, p(ri, c, s, consumed), isDecl) + result = c.genSink(dest, p(ri, c, s, consumed), isDecl) of nkSym: if isSinkParam(ri.sym) and isLastRead(ri, c): # Rule 3: `=sink`(x, z); wasMoved(z) - let snk = c.genSink(s, dest, ri, isDecl) + let snk = c.genSink(dest, ri, isDecl) result = newTree(nkStmtList, snk, c.genWasMoved(ri)) 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 = c.genSink(s, dest, ri, isDecl) + let snk = c.genSink(dest, ri, isDecl) result = newTree(nkStmtList, snk, c.genWasMoved(ri)) else: result = c.genCopy(dest, ri) result.add p(ri, c, s, consumed) of nkHiddenSubConv, nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv: - result = c.genSink(s, dest, p(ri, c, s, sinkArg), isDecl) - of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt: + result = c.genSink(dest, p(ri, c, s, sinkArg), isDecl) + of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt: template process(child, s): untyped = moveOrCopy(dest, child, c, s, isDecl) - handleNestedTempl(ri, process, true) + # We know the result will be a stmt so we use that fact to optimize + handleNestedTempl(ri, process, willProduceStmt = true) 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 = c.genSink(s, dest, ri, isDecl) + let snk = c.genSink(dest, ri, isDecl) result = newTree(nkStmtList, snk, c.genWasMoved(ri)) else: result = c.genCopy(dest, ri) @@ -1106,26 +1039,10 @@ proc injectDefaultCalls(n: PNode, c: var Con) = for i in 0..<n.safeLen: injectDefaultCalls(n[i], c) -proc extractDestroysForTemporaries(c: Con, destroys: PNode): PNode = - result = newNodeI(nkStmtList, destroys.info) - for i in 0..<destroys.len: - if destroys[i][1][0].sym.kind in {skTemp, skForVar}: - result.add destroys[i] - destroys[i] = c.emptyNode - proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = if sfGeneratedOp in owner.flags or (owner.kind == skIterator and isInlineIterator(owner.typ)): return n - var c: Con - c.owner = owner - c.graph = g - c.emptyNode = newNodeI(nkEmpty, n.info) - let cfg = constructCfg(owner, n) - shallowCopy(c.g, cfg) - c.jumpTargets = initIntSet() - for i in 0..<c.g.len: - if c.g[i].kind in {goto, fork}: - c.jumpTargets.incl(i+c.g[i].dest) + var c = Con(owner: owner, graph: g, g: constructCfg(owner, n)) dbg: echo "\n### ", owner.name.s, ":\nCFG:" echoCfg(c.g) @@ -1142,7 +1059,7 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = scope.final.add c.genDestroy(params[i]) #if optNimV2 in c.graph.config.globalOptions: # injectDefaultCalls(n, c) - result = toTree(c, scope, body, {}) + result = processScope(c, scope, body) dbg: echo ">---------transformed-to--------->" echo renderTree(result, {renderIds}) diff --git a/tests/arc/tarcmisc.nim b/tests/arc/tarcmisc.nim index b6d9d781d..2d7e6b455 100644 --- a/tests/arc/tarcmisc.nim +++ b/tests/arc/tarcmisc.nim @@ -3,6 +3,11 @@ discard """ 123xyzabc destroyed: false destroyed: false +destroyed2: false +destroyed2: false +destroying variable: 2 +destroying variable: 1 +whiley ends :( 1 (x: "0") (x: "1") @@ -17,7 +22,8 @@ destroyed: false (x: "10") 0 closed -destroying variable +destroying variable: 20 +destroying variable: 10 ''' cmd: "nim c --gc:arc $file" """ @@ -40,11 +46,12 @@ type Variable = ref object value: int proc `=destroy`(self: var typeof(Variable()[])) = - echo "destroying variable" + echo "destroying variable: ",self.value proc newVariable(value: int): Variable = result = Variable() result.value = value + #echo "creating variable: ",result.value proc test(count: int) = var v {.global.} = newVariable(10) @@ -57,6 +64,28 @@ proc test(count: int) = test(3) +proc test2(count: int) = + #block: #XXX: Fails with block currently + var v {.global.} = newVariable(20) + + var count = count - 1 + if count == 0: return + + test2(count) + echo "destroyed2: ", v.isNil + +test2(3) + +proc whiley = + var a = newVariable(1) + while true: + var b = newVariable(2) + if true: raise newException(CatchableError, "test") + +try: + whiley() +except CatchableError: + echo "whiley ends :(" #------------------------------------------------------------------------------ # issue #13810 @@ -209,3 +238,8 @@ proc setParent(self: SimpleLoopB, parent: SimpleLoopB) = var l = SimpleLoopB() l.setParent(l) + + +# bug #14968 +import times +let currentTime = now().utc diff --git a/tests/arc/tcontrolflow.nim b/tests/arc/tcontrolflow.nim index 41f614cb2..80cc2b187 100644 --- a/tests/arc/tcontrolflow.nim +++ b/tests/arc/tcontrolflow.nim @@ -1,12 +1,12 @@ discard """ output: '''begin A elif -destroyed end A +destroyed begin false if -destroyed end false +destroyed begin true if end true diff --git a/tests/arc/tmovebug.nim b/tests/arc/tmovebug.nim index ec0fce9a8..424785ed7 100644 --- a/tests/arc/tmovebug.nim +++ b/tests/arc/tmovebug.nim @@ -40,6 +40,33 @@ sink me (not sink) sinked and not optimized to a bitcopy sinked and not optimized to a bitcopy sinked and not optimized to a bitcopy +(data: @[0, 0]) +(data: @[0, 0]) +(data: @[0, 0]) +(data: @[0, 0]) +(data: @[0, 0]) +(data: @[0, 0]) +(data: @[0, 0]) +100 +hey +hey +(a: "a", b: 2) +ho +(a: "b", b: 3) +(b: "b", a: 2) +ho +(b: "a", a: 3) +hey +break +break +hey +ho +hey +ho +ho +king +live long; long live +king ''' """ @@ -325,3 +352,145 @@ proc update() = for i in 1..3: update() + + +# bug #14961 +type + Foo = object + data: seq[int] + +proc initFoo(len: int): Foo = + result = (let s = newSeq[int](len); Foo(data: s) ) + +var f = initFoo(2) +echo initFoo(2) + +proc initFoo2(len: int) = + echo if true: + let s = newSeq[int](len); Foo(data: s) + else: + let s = newSeq[int](len); Foo(data: s) + +initFoo2(2) + +proc initFoo3(len: int) = + echo (block: + let s = newSeq[int](len); Foo(data: s)) + +initFoo3(2) + +proc initFoo4(len: int) = + echo (let s = newSeq[int](len); Foo(data: s)) + +initFoo4(2) + +proc initFoo5(len: int) = + echo (case true + of true: + let s = newSeq[int](len); Foo(data: s) + of false: + let s = newSeq[int](len); Foo(data: s)) + +initFoo5(2) + +proc initFoo6(len: int) = + echo (block: + try: + let s = newSeq[int](len); Foo(data: s) + finally: discard) + +initFoo6(2) + +proc initFoo7(len: int) = + echo (block: + try: + raise newException(CatchableError, "sup") + let s = newSeq[int](len); Foo(data: s) + except CatchableError: + let s = newSeq[int](len); Foo(data: s) ) + +initFoo7(2) + + +# bug #14902 +iterator zip[T](s: openarray[T]): (T, T) = + var i = 0 + while i < 10: + yield (s[i mod 2], s[i mod 2 + 1]) + inc i + +var lastMem = int.high + +proc leak = + const len = 10 + var x = @[newString(len), newString(len), newString(len)] + + var c = 0 + for (a, b) in zip(x): + let newMem = getOccupiedMem() + assert newMem <= lastMem + lastMem = newMem + c += a.len + echo c + +leak() + + +proc consume(a: sink string) = echo a + +proc weirdScopes = + if (let a = "hey"; a.len > 0): + echo a + + while (let a = "hey"; a.len > 0): + echo a + break + + var a = block: (a: "a", b: 2) + echo a + (discard; a) = (echo "ho"; (a: "b", b: 3)) + echo a + + var b = try: (b: "b", a: 2) + except: raise + echo b + (discard; b) = (echo "ho"; (b: "a", a: 3)) + echo b + + var s = "break" + consume((echo "hey"; s)) + echo s + + echo (block: + var a = "hey" + (echo "hey"; "ho")) + + var b2 = "ho" + echo (block: + var a = "hey" + (echo "hey"; b2)) + echo b2 + + type status = enum + alive + + var king = "king" + echo (block: + var a = "a" + when true: + var b = "b" + case alive + of alive: + try: + var c = "c" + if true: + king + else: + "the abyss" + except: + echo "he ded" + "dead king") + echo "live long; long live" + echo king + +weirdScopes() diff --git a/tests/destructor/tmove_objconstr.nim b/tests/destructor/tmove_objconstr.nim index 0740bd46b..16d0daa22 100644 --- a/tests/destructor/tmove_objconstr.nim +++ b/tests/destructor/tmove_objconstr.nim @@ -2,7 +2,6 @@ discard """ output: '''test created test destroyed 0 -Pony is dying! 1 2 3 |