injectdestructors fixes and refactor (#14964)
* injectdestructors fixes and refactor * Tiny cleanup * Refactor and expand testcase * Closes #14902 by adding testcase * Better naming * Fix test failures * Misc cleanup * Add testcase for #14968 * Better approach; expand testcases * Optimizations and fixes * Add testcase * typo * Tiny cleanup
This commit is contained in:
parent
f999f916f3
commit
2b0e336c97
6 changed files with 317 additions and 198 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
discard """
|
||||
output: '''test created
|
||||
test destroyed 0
|
||||
Pony is dying!
|
||||
1
|
||||
2
|
||||
3
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue