ARC related bugfixes and refactorings (#12781)

This commit is contained in:
Andreas Rumpf
2019-12-05 16:59:06 +01:00
committed by Miran
parent 9b0e874687
commit 3fbb3bfd3f
11 changed files with 381 additions and 367 deletions

View File

@@ -1456,7 +1456,7 @@ proc isGCedMem*(t: PType): bool {.inline.} =
result = t.kind in {tyString, tyRef, tySequence} or
t.kind == tyProc and t.callConv == ccClosure
proc propagateToOwner*(owner, elem: PType) =
proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
const HaveTheirOwnEmpty = {tySequence, tyOpt, tySet, tyPtr, tyRef, tyProc}
owner.flags = owner.flags + (elem.flags * {tfHasMeta, tfTriggersCompileTime})
if tfNotNil in elem.flags:
@@ -1472,19 +1472,13 @@ proc propagateToOwner*(owner, elem: PType) =
if elem.isMetaType:
owner.flags.incl tfHasMeta
if tfHasAsgn in elem.flags:
let mask = elem.flags * {tfHasAsgn, tfHasOwned}
if mask != {} and propagateHasAsgn:
let o2 = owner.skipTypes({tyGenericInst, tyAlias, tySink})
if o2.kind in {tyTuple, tyObject, tyArray,
tySequence, tyOpt, tySet, tyDistinct, tyOpenArray, tyVarargs}:
o2.flags.incl tfHasAsgn
owner.flags.incl tfHasAsgn
if tfHasOwned in elem.flags:
let o2 = owner.skipTypes({tyGenericInst, tyAlias, tySink})
if o2.kind in {tyTuple, tyObject, tyArray,
tySequence, tyOpt, tySet, tyDistinct, tyOpenArray, tyVarargs}:
o2.flags.incl tfHasOwned
owner.flags.incl tfHasOwned
o2.flags.incl mask
owner.flags.incl mask
if owner.kind notin {tyProc, tyGenericInst, tyGenericBody,
tyGenericInvocation, tyPtr}:
@@ -1494,11 +1488,11 @@ proc propagateToOwner*(owner, elem: PType) =
# ensure this doesn't bite us in sempass2.
owner.flags.incl tfHasGCedMem
proc rawAddSon*(father, son: PType) =
proc rawAddSon*(father, son: PType; propagateHasAsgn = true) =
when not defined(nimNoNilSeqs):
if isNil(father.sons): father.sons = @[]
father.sons.add(son)
if not son.isNil: propagateToOwner(father, son)
if not son.isNil: propagateToOwner(father, son, propagateHasAsgn)
proc rawAddSonNoPropagationOfTypeFlags*(father, son: PType) =
when not defined(nimNoNilSeqs):

View File

@@ -1041,7 +1041,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
linefmt(p, cpsStmts, "#popSafePoint();$n", [])
genRestoreFrameAfterException(p)
elif 1 < t.len and t[1].kind == nkExceptBranch:
startBlock(p, "if (#getCurrentException()) {$n")
startBlock(p, "if (#nimBorrowCurrentException()) {$n")
else:
startBlock(p)
p.nestedTryStmts[^1].inExcept = true
@@ -1068,7 +1068,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
else:
genTypeInfo(p.module, t[i][j].typ, t[i][j].info)
let memberName = if p.module.compileToCpp: "m_type" else: "Sup.m_type"
appcg(p.module, orExpr, "#isObj(#getCurrentException()->$1, $2)", [memberName, checkFor])
appcg(p.module, orExpr, "#isObj(#nimBorrowCurrentException()->$1, $2)", [memberName, checkFor])
if i > 1: line(p, cpsStmts, "else ")
startBlock(p, "if ($1) {$n", [orExpr])
@@ -1082,7 +1082,14 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
endBlock(p) # end of else block
if i < t.len and t[i].kind == nkFinally:
p.finallySafePoints.add(safePoint)
genSimpleBlock(p, t[i][0])
startBlock(p)
genStmts(p, t[i][0])
# pretend we handled the exception in a 'finally' so that we don't
# re-raise the unhandled one but instead keep the old one (it was
# not popped either):
if not quirkyExceptions and getCompilerProc(p.module.g.graph, "nimLeaveFinally") != nil:
linefmt(p, cpsStmts, "if ($1.status != 0) #nimLeaveFinally();$n", [safePoint])
endBlock(p)
discard pop(p.finallySafePoints)
if not quirkyExceptions:
linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", [safePoint])

View File

@@ -1884,7 +1884,7 @@ proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
if not moduleHasChanged(m.g.graph, m.module):
result = false
elif not equalsFile(code, cfile.cname):
if false:
when false:
#m.config.symbolFiles == readOnlySf: #isDefined(m.config, "nimdiff"):
if fileExists(cfile.cname):
copyFile(cfile.cname.string, cfile.cname.string & ".backup")

View File

@@ -170,6 +170,7 @@ proc genOp(c: Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode =
op = canon.attachedOps[kind]
if op == nil:
#echo dest.typ.id
globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] &
"' operator not found for type " & typeToString(t))
elif op.ast[genericParamsPos].kind != nkEmpty:
@@ -250,22 +251,25 @@ proc destructiveMoveVar(n: PNode; c: var Con): PNode =
# generate: (let tmp = v; reset(v); tmp)
# XXX: Strictly speaking we can only move if there is a ``=sink`` defined
# or if no ``=sink`` is defined and also no assignment.
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
if not hasDestructor(n.typ):
result = copyTree(n)
else:
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
temp.typ = n.typ
var v = newNodeI(nkLetSection, n.info)
let tempAsNode = newSymNode(temp)
var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
temp.typ = n.typ
var v = newNodeI(nkLetSection, n.info)
let tempAsNode = newSymNode(temp)
var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
vpart[0] = tempAsNode
vpart[1] = c.emptyNode
vpart[2] = n
v.add(vpart)
var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
vpart[0] = tempAsNode
vpart[1] = c.emptyNode
vpart[2] = n
v.add(vpart)
result.add v
result.add genWasMoved(skipConv(n), c)
result.add tempAsNode
result.add v
result.add genWasMoved(skipConv(n), c)
result.add tempAsNode
proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
assert s.kind == nkSym and s.sym.kind == skParam
@@ -273,8 +277,12 @@ proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
"` is already consumed at " & toFileLineCol(c. graph.config, s.info))
proc p(n: PNode; c: var Con; consumed = false): PNode
proc pArg(arg: PNode; c: var Con; isSink: bool): PNode
type ProcessMode = enum
normal
consumed
sinkArg
proc p(n: PNode; c: var Con; mode: ProcessMode = normal): PNode
proc moveOrCopy(dest, ri: PNode; c: var Con): PNode
proc isClosureEnv(n: PNode): bool = n.kind == nkSym and n.sym.name.s[0] == ':'
@@ -366,6 +374,10 @@ template handleNested(n: untyped, processCall: untyped) =
branch.add if node.typ == nil: p(node, c) #noreturn
else: processCall
result.add branch
of nkWhen: # This should be a "when nimvm" node.
result = copyTree(n)
template node: untyped = n[1][0]
result[1][0] = processCall
else: assert(false)
proc ensureDestruction(arg: PNode; c: var Con): PNode =
@@ -384,75 +396,6 @@ proc ensureDestruction(arg: PNode; c: var Con): PNode =
else:
result = arg
proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
if isSink:
if arg.kind in nkCallKinds:
# recurse but skip the call expression in order to prevent
# destructor injections: Rule 5.1 is different from rule 5.4!
result = copyNode(arg)
let parameters = arg[0].typ
let L = if parameters != nil: parameters.len else: 0
result.add pArg(arg[0], c, isSink = false)
for i in 1..<arg.len:
result.add pArg(arg[i], c, i < L and isSinkTypeForParam(parameters[i]))
elif arg.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(arg, c)
elif arg.kind in nkLiterals:
# literals are save to share accross ASTs (for now!)
result = arg # literal to sink parameter: nothing to do
elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkClosure}:
# object construction to sink parameter: nothing to do
result = copyTree(arg)
for i in ord(arg.kind in {nkObjConstr, nkClosure})..<arg.len:
if arg[i].kind == nkExprColonExpr:
result[i][1] = pArg(arg[i][1], c, isSink = true)
else:
result[i] = pArg(arg[i], c, isSink = true)
#if arg.kind == nkClosure:
# result = handleClosureCall(result, c)
# Not required here because the nkClosure will be consumed!
elif arg.kind == nkSym and isSinkParam(arg.sym):
# Sinked params can be consumed only once. We need to reset the memory
# to disable the destructor which we have not elided
sinkParamIsLastReadCheck(c, arg)
result = destructiveMoveVar(arg, c)
elif isAnalysableFieldAccess(arg, c.owner) and isLastRead(arg, c):
# it is the last read, can be sinked. We need to reset the memory
# to disable the destructor which we have not elided
result = destructiveMoveVar(arg, c)
elif arg.kind in {nkStmtListExpr, nkBlockExpr, nkBlockStmt, nkIfExpr, nkIfStmt, nkCaseStmt}:
handleNested(arg): pArg(node, c, isSink)
elif arg.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}:
result = copyTree(arg)
result[1] = pArg(arg[1], c, isSink = true)
elif arg.kind in {nkObjDownConv, nkObjUpConv}:
result = copyTree(arg)
result[0] = pArg(arg[0], c, isSink = true)
else:
# an object that is not temporary but passed to a 'sink' parameter
# results in a copy.
result = passCopyToSink(arg, c)
elif arg.kind == nkBracket:
# Treat `f([...])` like `f(...)`
result = copyNode(arg)
for son in arg:
result.add pArg(son, c, isSinkTypeForParam(son.typ))
elif arg.kind in nkCallKinds and arg.typ != nil and hasDestructor(arg.typ):
# produce temp creation
result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
let tmp = getTemp(c, arg.typ, arg.info)
let res = p(arg, c)
var sinkExpr = genSink(c, tmp, res)
sinkExpr.add res
result.add sinkExpr
result.add tmp
c.destroys.add genDestroy(c, tmp)
else:
result = p(arg, c)
proc isCursor(n: PNode): bool =
case n.kind
of nkSym:
@@ -493,154 +436,172 @@ proc cycleCheck(n: PNode; c: var Con) =
message(c.graph.config, n.info, warnCycleCreated, msg)
break
proc p(n: PNode; c: var Con; consumed = false): PNode =
case n.kind
of nkCallKinds:
let parameters = n[0].typ
let L = if parameters != nil: parameters.len else: 0
result = shallowCopy(n)
for i in 1..<n.len:
result[i] = pArg(n[i], c, i < L and isSinkTypeForParam(parameters[i]))
if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
result[0] = copyTree(n[0])
if c.graph.config.selectedGC in {gcHooks, gcDestructors}:
let destroyOld = genDestroy(c, result[1])
result = newTree(nkStmtList, destroyOld, result)
else:
result[0] = pArg(n[0], c, isSink = false)
of nkDiscardStmt: # Small optimization
result = shallowCopy(n)
if n[0].kind != nkEmpty:
result[0] = pArg(n[0], c, false)
else:
result[0] = copyNode(n[0])
of nkBracket:
result = copyTree(n)
for i in 0..<n.len:
# everything that is passed to an array constructor is consumed,
# so these all act like 'sink' parameters:
result[i] = pArg(n[i], c, isSink = true)
if not consumed:
result = ensureDestruction(result, c)
of nkObjConstr:
result = copyTree(n)
for i in 1..<n.len:
# everything that is passed to an object constructor is consumed,
# so these all act like 'sink' parameters:
result[i][1] = pArg(n[i][1], c, isSink = true)
if not consumed:
result = ensureDestruction(result, c)
of nkTupleConstr, nkClosure:
result = copyTree(n)
for i in ord(n.kind == nkClosure)..<n.len:
# everything that is passed to an tuple constructor is consumed,
# so these all act like 'sink' parameters:
if n[i].kind == nkExprColonExpr:
result[i][1] = pArg(n[i][1], c, isSink = true)
proc p(n: PNode; c: var Con; mode: ProcessMode = normal): PNode =
if n.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt, nkIfExpr, nkCaseStmt, nkWhen}:
handleNested(n): p(node, c, mode)
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)
elif n.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkClosure} + nkCallKinds + nkLiterals:
result = p(n, c, consumed)
elif n.kind == nkSym and isSinkParam(n.sym):
# 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)
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)
elif n.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}:
result = copyTree(n)
if n.typ.skipTypes(abstractInst-{tyOwned}).kind != tyOwned and
n[1].typ.skipTypes(abstractInst-{tyOwned}).kind == tyOwned:
# 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].typ = nTyp
else:
result[i] = pArg(n[i], c, isSink = true)
if not consumed:
result = ensureDestruction(result, c)
of nkVarSection, nkLetSection:
# transform; var x = y to var x; x op y where op is a move or copy
result = newNodeI(nkStmtList, n.info)
for it in n:
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)
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
# move the variable declaration to the top of the frame:
c.addTopVar v
# make sure it's destroyed at the end of the proc:
if not isUnpackedTuple(it[0].sym):
c.destroys.add genDestroy(c, v)
if ri.kind == nkEmpty and c.inLoop > 0:
ri = genDefaultCall(v.typ, c, v.info)
if ri.kind != nkEmpty:
let r = moveOrCopy(v, ri, c)
result.add r
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)
v.add itCopy
result.add v
of nkAsgn, nkFastAsgn:
if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda} and
not isCursor(n[0]):
# rule (self-assignment-removal):
if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym:
result = newNodeI(nkEmpty, n.info)
else:
if n[0].kind in {nkDotExpr, nkCheckedFieldExpr}:
cycleCheck(n, c)
result = moveOrCopy(n[0], n[1], c)
result[1] = p(n[1], c, sinkArg)
elif n.kind in {nkObjDownConv, nkObjUpConv}:
result = copyTree(n)
result[0] = p(n[0], c, sinkArg)
else:
result = copyNode(n)
result.add copyTree(n[0])
result.add p(n[1], c)
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)
result = copyNode(n)
result.add call
else:
let tmp = getTemp(c, n[0].typ, n.info)
var m = genCopyNoCheck(c, tmp, n[0])
m.add p(n[0], c)
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)
result.add p(n[0], c)
of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef,
nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt, nkExportStmt,
nkPragma, nkCommentStmt, nkBreakStmt, nkBreakState:
result = n
of nkWhileStmt:
result = copyNode(n)
inc c.inLoop
result.add p(n[0], c)
result.add p(n[1], c)
dec c.inLoop
of nkWhen: # This should be a "when nimvm" node.
result = copyTree(n)
result[1][0] = p(result[1][0], c)
of nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt, nkIfExpr, nkCaseStmt:
handleNested(n): p(node, c, consumed)
# copy objects that are not temporary but passed to a 'sink' parameter
result = passCopyToSink(n, c)
else:
result = shallowCopy(n)
for i in 0..<n.len:
result[i] = p(n[i], c, consumed)
case n.kind
of nkBracket, nkObjConstr, nkTupleConstr, nkClosure:
result = copyTree(n)
for i in ord(n.kind in {nkObjConstr, nkClosure})..<n.len:
let m = if mode == normal: normal
else: sinkArg
if n[i].kind == nkExprColonExpr:
result[i][1] = p(n[i][1], c, m)
else:
result[i] = p(n[i], c, m)
of nkCallKinds:
let parameters = n[0].typ
let L = if parameters != nil: parameters.len else: 0
result = shallowCopy(n)
for i in 1..<n.len:
if i < L and isSinkTypeForParam(parameters[i]):
result[i] = p(n[i], c, sinkArg)
else:
result[i] = p(n[i], c, normal)
if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
result[0] = copyTree(n[0])
if c.graph.config.selectedGC in {gcHooks, gcDestructors}:
let destroyOld = genDestroy(c, result[1])
result = newTree(nkStmtList, destroyOld, result)
else:
result[0] = p(n[0], c, normal)
if mode == normal:
result = ensureDestruction(result, c)
of nkDiscardStmt: # Small optimization
result = shallowCopy(n)
if n[0].kind != nkEmpty:
result[0] = p(n[0], c, normal)
else:
result[0] = copyNode(n[0])
of nkVarSection, nkLetSection:
# transform; var x = y to var x; x op y where op is a move or copy
result = newNodeI(nkStmtList, n.info)
for it in n:
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)
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
# move the variable declaration to the top of the frame:
c.addTopVar v
# make sure it's destroyed at the end of the proc:
if not isUnpackedTuple(it[0].sym):
c.destroys.add genDestroy(c, v)
if ri.kind == nkEmpty and c.inLoop > 0:
ri = genDefaultCall(v.typ, c, v.info)
if ri.kind != nkEmpty:
let r = moveOrCopy(v, ri, c)
result.add r
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)
v.add itCopy
result.add v
of nkAsgn, nkFastAsgn:
if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda} and
not isCursor(n[0]):
# rule (self-assignment-removal):
if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym:
result = newNodeI(nkEmpty, n.info)
else:
if n[0].kind in {nkDotExpr, nkCheckedFieldExpr}:
cycleCheck(n, c)
result = moveOrCopy(n[0], n[1], c)
else:
result = copyNode(n)
result.add copyTree(n[0])
result.add p(n[1], c, 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)
result = copyNode(n)
result.add call
else:
let tmp = getTemp(c, n[0].typ, n.info)
var m = genCopyNoCheck(c, tmp, n[0])
m.add p(n[0], c)
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])
of nkWhileStmt:
result = copyNode(n)
inc c.inLoop
result.add p(n[0], c)
result.add p(n[1], c)
dec c.inLoop
of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef,
nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
nkExportStmt, nkPragma, nkCommentStmt, nkBreakStmt, nkBreakState:
result = n
else:
result = shallowCopy(n)
for i in 0..<n.len:
result[i] = p(n[i], c, mode)
proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
# unfortunately, this needs to be kept consistent with the cases
# we handle in the 'case of' statement below:
const movableNodeKinds = (nkCallKinds + {nkSym, nkTupleConstr, nkClosure, nkObjConstr,
nkBracket, nkBracketExpr, nkNilLit})
case ri.kind
of nkCallKinds:
result = genSink(c, dest, ri)
result.add p(ri, c, consumed = true)
result.add p(ri, c, consumed)
of nkBracketExpr:
if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym):
# unpacking of tuple: move out the elements
result = genSink(c, dest, ri)
result.add p(ri, c, consumed = true)
result.add p(ri, c, consumed)
elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c):
# Rule 3: `=sink`(x, z); wasMoved(z)
var snk = genSink(c, dest, ri)
@@ -648,17 +609,17 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else:
result = genCopy(c, dest, ri)
result.add p(ri, c, consumed = true)
result.add p(ri, c, consumed)
of nkBracket:
# array constructor
if ri.len > 0 and isDangerousSeq(ri.typ):
result = genCopy(c, dest, ri)
else:
result = genSink(c, dest, ri)
result.add p(ri, c, consumed = true)
result.add p(ri, c, consumed)
of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit:
result = genSink(c, dest, ri)
result.add p(ri, c, consumed = true)
result.add p(ri, c, consumed)
of nkSym:
if isSinkParam(ri.sym):
# Rule 3: `=sink`(x, z); wasMoved(z)
@@ -674,18 +635,26 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else:
result = genCopy(c, dest, ri)
result.add p(ri, c, consumed = true)
result.add p(ri, c, consumed)
of nkHiddenSubConv, nkHiddenStdConv, nkConv:
result = moveOrCopy(dest, ri[1], c)
if not sameType(ri.typ, ri[1].typ):
let copyRi = copyTree(ri)
copyRi[1] = result[^1]
result[^1] = copyRi
when false:
result = moveOrCopy(dest, ri[1], c)
if not sameType(ri.typ, ri[1].typ):
let copyRi = copyTree(ri)
copyRi[1] = result[^1]
result[^1] = copyRi
else:
result = genSink(c, dest, ri)
result.add p(ri, c, sinkArg)
of nkObjDownConv, nkObjUpConv:
result = moveOrCopy(dest, ri[0], c)
let copyRi = copyTree(ri)
copyRi[0] = result[^1]
result[^1] = copyRi
when false:
result = moveOrCopy(dest, ri[0], c)
let copyRi = copyTree(ri)
copyRi[0] = result[^1]
result[^1] = copyRi
else:
result = genSink(c, dest, ri)
result.add p(ri, c, sinkArg)
of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt:
handleNested(ri): moveOrCopy(dest, node, c)
else:
@@ -697,7 +666,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else:
result = genCopy(c, dest, ri)
result.add p(ri, c, consumed = true)
result.add p(ri, c, consumed)
proc computeUninit(c: var Con) =
if not c.uninitComputed:

View File

@@ -307,6 +307,8 @@ proc instCopyType*(cl: var TReplTypeVars, t: PType): PType =
if not (t.kind in tyMetaTypes or
(t.kind == tyStatic and t.n == nil)):
result.flags.excl tfInstClearedFlags
else:
result.flags.excl tfHasAsgn
when false:
if newDestructors:
result.assignment = nil
@@ -380,7 +382,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
for i in 1..<t.len:
# if one of the params is not concrete, we cannot do anything
# but we already raised an error!
rawAddSon(result, header[i])
rawAddSon(result, header[i], propagateHasAsgn = false)
if body.kind == tyError:
return

View File

@@ -30,60 +30,6 @@ template payloadSize(cap): int = cap * sizeof(T) + sizeof(int) + sizeof(Allocato
# XXX make code memory safe for overflows in '*'
when false:
# this is currently not part of Nim's type bound operators and so it's
# built into the tracing proc generation just like before.
proc `=trace`[T](s: NimSeqV2[T]) =
for i in 0 ..< s.len: `=trace`(s.data[i])
#[
Keep in mind that C optimizers are bad at detecting the connection
between ``s.p != nil`` and ``s.len != 0`` and that these are intermingled
with user-level code that accesses ``s.len`` only, never ``s.p`` directly.
This means the check for whether ``s.p`` needs to be freed should
be ``s.len == 0`` even though that feels slightly more awkward.
]#
when not defined(nimV2):
proc `=destroy`[T](s: var seq[T]) =
var x = cast[ptr NimSeqV2[T]](addr s)
var p = x.p
if p != nil:
mixin `=destroy`
when not supportsCopyMem(T):
for i in 0..<x.len: `=destroy`(p.data[i])
if p.allocator != nil:
p.allocator.dealloc(p.allocator, p, payloadSize(p.cap))
x.p = nil
x.len = 0
proc `=`[T](x: var seq[T]; y: seq[T]) =
mixin `=destroy`
var a = cast[ptr NimSeqV2[T]](addr x)
var b = cast[ptr NimSeqV2[T]](unsafeAddr y)
if a.p == b.p: return
`=destroy`(x)
a.len = b.len
if b.p != nil:
a.p = cast[type(a.p)](alloc(payloadSize(a.len)))
when supportsCopyMem(T):
if a.len > 0:
copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], a.len * sizeof(T))
else:
for i in 0..<a.len:
a.p.data[i] = b.p.data[i]
proc `=sink`[T](x: var seq[T]; y: seq[T]) =
mixin `=destroy`
var a = cast[ptr NimSeqV2[T]](addr x)
var b = cast[ptr NimSeqV2[T]](unsafeAddr y)
if a.p != nil and a.p != b.p:
`=destroy`(x)
a.len = b.len
a.p = b.p
type
PayloadBase = object
cap: int
@@ -181,36 +127,3 @@ proc setLen[T](s: var seq[T], newlen: Natural) =
if xu.p == nil or xu.p.cap < newlen:
xu.p = cast[typeof(xu.p)](prepareSeqAdd(oldLen, xu.p, newlen - oldLen, sizeof(T)))
xu.len = newlen
when false:
proc resize[T](s: var NimSeqV2[T]) =
let old = s.cap
if old == 0: s.cap = 8
else: s.cap = (s.cap * 3) shr 1
s.data = cast[type(s.data)](realloc(s.data, old * sizeof(T), s.cap * sizeof(T)))
proc reserveSlot[T](x: var NimSeqV2[T]): ptr T =
if x.len >= x.cap: resize(x)
result = addr(x.data[x.len])
inc x.len
template add*[T](x: var NimSeqV2[T]; y: T) =
reserveSlot(x)[] = y
template `[]`*[T](x: NimSeqV2[T]; i: Natural): T =
assert i < x.len
x.data[i]
template `[]=`*[T](x: NimSeqV2[T]; i: Natural; y: T) =
assert i < x.len
x.data[i] = y
proc `@`*[T](elems: sink openArray[T]): NimSeqV2[T] =
result.cap = elems.len
result.len = elems.len
result.data = cast[type(result.data)](alloc(result.cap * sizeof(T)))
when supportsCopyMem(T):
copyMem(result.data, unsafeAddr(elems[0]), result.cap * sizeof(T))
else:
for i in 0..<result.len:
result.data[i] = elems[i]

View File

@@ -3218,6 +3218,9 @@ proc `<`*[T: tuple](x, y: T): bool =
const
usesDestructors = defined(gcDestructors) or defined(gcHooks)
when not usesDestructors:
{.pragma: nodestroy.}
when not defined(nimscript) and hasAlloc:
type
GC_Strategy* = enum ## The strategy the GC should use for the application.
@@ -3671,8 +3674,6 @@ when not defined(JS): #and not defined(nimscript):
prev: PSafePoint # points to next safe point ON THE STACK
status: int
context: C_JmpBuf
hasRaiseAction: bool
raiseAction: proc (e: ref Exception): bool {.closure.}
SafePoint = TSafePoint
when declared(initAllocator):
@@ -3776,11 +3777,15 @@ when not defined(JS): #and not defined(nimscript):
## Retrieves the current exception; if there is none, `nil` is returned.
result = currException
proc nimBorrowCurrentException(): ref Exception {.compilerRtl, inl, benign, nodestroy.} =
# .nodestroy here so that we do not produce a write barrier as the
# C codegen only uses it in a borrowed way:
result = currException
proc getCurrentExceptionMsg*(): string {.inline, benign.} =
## Retrieves the error message that was attached to the current
## exception; if there is none, `""` is returned.
var e = getCurrentException()
return if e == nil: "" else: e.msg
return if currException == nil: "" else: currException.msg
proc setCurrentException*(exc: ref Exception) {.inline, benign.} =
## Sets the current exception.

View File

@@ -103,19 +103,20 @@ proc pushGcFrame*(s: GcFrame) {.compilerRtl, inl.} =
gcFramePtr = s
proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
s.hasRaiseAction = false
s.prev = excHandler
excHandler = s
proc popSafePoint {.compilerRtl, inl.} =
excHandler = excHandler.prev
proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} =
proc pushCurrentException(e: sink(ref Exception)) {.compilerRtl, inl.} =
e.up = currException
currException = e
#showErrorMessage "A"
proc popCurrentException {.compilerRtl, inl.} =
currException = currException.up
#showErrorMessage "B"
proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
# in cpp backend exceptions can pop-up in the different order they were raised, example #5628
@@ -332,28 +333,13 @@ template unhandled(buf, body) =
else:
body
proc raiseExceptionAux(e: ref Exception) =
if localRaiseHook != nil:
if not localRaiseHook(e): return
if globalRaiseHook != nil:
if not globalRaiseHook(e): return
proc nimLeaveFinally() {.compilerRtl.} =
when defined(cpp) and not defined(noCppExceptions):
if e == currException:
{.emit: "throw;".}
else:
pushCurrentException(e)
raiseCounter.inc
if raiseCounter == 0:
raiseCounter.inc # skip zero at overflow
e.raiseId = raiseCounter
{.emit: "`e`->raise();".}
elif defined(nimQuirky):
pushCurrentException(e)
{.emit: "throw;".}
else:
template e: untyped = currException
if excHandler != nil:
if not excHandler.hasRaiseAction or excHandler.raiseAction(e):
pushCurrentException(e)
c_longjmp(excHandler.context, 1)
c_longjmp(excHandler.context, 1)
else:
when hasSomeStackTrace:
var buf = newStringOfCap(2000)
@@ -367,6 +353,7 @@ proc raiseExceptionAux(e: ref Exception) =
unhandled(buf):
showErrorMessage(buf)
quitOrDebug()
`=destroy`(buf)
else:
# ugly, but avoids heap allocations :-)
template xadd(buf, s, slen) =
@@ -392,7 +379,68 @@ proc raiseExceptionAux(e: ref Exception) =
showErrorMessage(tbuf())
quitOrDebug()
proc raiseExceptionEx(e: ref Exception, ename, procname, filename: cstring, line: int) {.compilerRtl.} =
proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
if localRaiseHook != nil:
if not localRaiseHook(e): return
if globalRaiseHook != nil:
if not globalRaiseHook(e): return
when defined(cpp) and not defined(noCppExceptions):
if e == currException:
{.emit: "throw;".}
else:
pushCurrentException(e)
raiseCounter.inc
if raiseCounter == 0:
raiseCounter.inc # skip zero at overflow
e.raiseId = raiseCounter
{.emit: "`e`->raise();".}
elif defined(nimQuirky):
pushCurrentException(e)
else:
if excHandler != nil:
pushCurrentException(e)
c_longjmp(excHandler.context, 1)
else:
when hasSomeStackTrace:
var buf = newStringOfCap(2000)
if e.trace.len == 0: rawWriteStackTrace(buf)
else: add(buf, $e.trace)
add(buf, "Error: unhandled exception: ")
add(buf, e.msg)
add(buf, " [")
add(buf, $e.name)
add(buf, "]\n")
unhandled(buf):
showErrorMessage(buf)
quitOrDebug()
`=destroy`(buf)
else:
# ugly, but avoids heap allocations :-)
template xadd(buf, s, slen) =
if L + slen < high(buf):
copyMem(addr(buf[L]), cstring(s), slen)
inc L, slen
template add(buf, s) =
xadd(buf, s, s.len)
var buf: array[0..2000, char]
var L = 0
if e.trace.len != 0:
add(buf, $e.trace) # gc allocation
add(buf, "Error: unhandled exception: ")
add(buf, e.msg)
add(buf, " [")
xadd(buf, e.name, e.name.len)
add(buf, "]\n")
when defined(nimNoArrayToCstringConversion):
template tbuf(): untyped = addr buf
else:
template tbuf(): untyped = buf
unhandled(tbuf()):
showErrorMessage(tbuf())
quitOrDebug()
proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring,
line: int) {.compilerRtl, nodestroy.} =
if e.name.isNil: e.name = ename
when hasSomeStackTrace:
if e.trace.len == 0:
@@ -406,7 +454,7 @@ proc raiseExceptionEx(e: ref Exception, ename, procname, filename: cstring, line
e.trace.add StackTraceEntry(procname: procname, filename: filename, line: line)
raiseExceptionAux(e)
proc raiseException(e: ref Exception, ename: cstring) {.compilerRtl.} =
proc raiseException(e: sink(ref Exception), ename: cstring) {.compilerRtl.} =
raiseExceptionEx(e, ename, nil, nil, 0)
proc reraiseException() {.compilerRtl.} =

View File

@@ -0,0 +1,29 @@
discard """
cmd: '''nim c --gc:arc $file'''
output: '''0'''
"""
proc other =
raise newException(ValueError, "stuff happening")
proc indirectViaProcCall =
var correct = 0
for i in 1 .. 20:
try:
other()
except:
let x = getCurrentException()
correct += ord(x of ValueError)
doAssert correct == 20
proc direct =
for i in 1 .. 20:
try:
raise newException(ValueError, "stuff happening")
except ValueError:
discard
let mem = getOccupiedMem()
indirectViaProcCall()
direct()
echo getOccupiedMem() - mem

View File

@@ -0,0 +1,47 @@
discard """
outputsub: "no leak: "
cmd: "nim c --gc:arc $file"
"""
# bug #12758
type
TExpr {.inheritable.} = object ## abstract base class for an expression
PLiteral = ref TLiteral
TLiteral = object of TExpr
x: int
op1: string
TPlusExpr = object of TExpr
a, b: ref TExpr
op2: string
method eval(e: ref TExpr): int {.base.} =
# override this base method
quit "to override!"
method eval(e: ref TLiteral): int = return e.x
method eval(e: ref TPlusExpr): int =
# watch out: relies on dynamic binding
return eval(e.a) + eval(e.b)
proc newLit(x: int): ref TLiteral =
new(result)
result.x = x
result.op1 = $getOccupiedMem()
proc newPlus(a, b: ref TExpr): ref TPlusExpr =
new(result)
result.a = a
result.b = b
result.op2 = $getOccupiedMem()
const Limit = when compileOption("gc", "markAndSweep") or compileOption("gc", "boehm"): 5*1024*1024 else: 500_000
for i in 0..100_000:
var s: array[0..11, ref TExpr]
for j in 0..high(s):
s[j] = newPlus(newPlus(newLit(j), newLit(2)), newLit(4))
if eval(s[j]) != j+6:
quit "error: wrong result"
if getOccupiedMem() > Limit: quit("still a leak!")
echo "no leak: ", getOccupiedMem()

View File

@@ -1,7 +1,7 @@
discard """
cmd: '''nim c --newruntime $file'''
output: '''OK 3
5 1'''
5 2'''
"""
import strutils, math