diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2019-12-05 16:59:06 +0100 |
---|---|---|
committer | Miran <narimiran@disroot.org> | 2019-12-05 16:59:06 +0100 |
commit | 3fbb3bfd3f440c059d6290c12834a38a61da98f2 (patch) | |
tree | b4561f510b23ce4fe2a19e58e15246c5ec5293a0 | |
parent | 9b0e874687177af4f758b70994b3a08f963a75cd (diff) | |
download | Nim-3fbb3bfd3f440c059d6290c12834a38a61da98f2.tar.gz |
ARC related bugfixes and refactorings (#12781)
-rw-r--r-- | compiler/ast.nim | 20 | ||||
-rw-r--r-- | compiler/ccgstmts.nim | 13 | ||||
-rw-r--r-- | compiler/cgen.nim | 2 | ||||
-rw-r--r-- | compiler/injectdestructors.nim | 427 | ||||
-rw-r--r-- | compiler/semtypinst.nim | 4 | ||||
-rw-r--r-- | lib/core/seqs.nim | 87 | ||||
-rw-r--r-- | lib/system.nim | 13 | ||||
-rw-r--r-- | lib/system/excpt.nim | 64 | ||||
-rw-r--r-- | tests/destructor/texceptions.nim | 29 | ||||
-rw-r--r-- | tests/destructor/tgcleak4.nim | 47 | ||||
-rw-r--r-- | tests/destructor/tv2_raise.nim | 2 |
11 files changed, 361 insertions, 347 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 9fbcab144..208e99c4a 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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): diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 59db5cb72..34fb06af8 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -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]) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 12acd7825..a456b7d51 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -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") diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index de723b776..7a8c18f13 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -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) +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[1] = p(n[1], c, sinkArg) + elif n.kind in {nkObjDownConv, nkObjUpConv}: + result = copyTree(n) + result[0] = p(n[0], c, sinkArg) 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) + # copy objects that are not temporary but passed to a 'sink' parameter + result = passCopyToSink(n, c) + else: + 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[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) + 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: - 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) - 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 + 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: - 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) - 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) - else: - result = shallowCopy(n) - for i in 0..<n.len: - result[i] = p(n[i], c, consumed) + 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): + 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: + when false: + result = moveOrCopy(dest, ri[0], c) let copyRi = copyTree(ri) - copyRi[1] = result[^1] + copyRi[0] = result[^1] result[^1] = copyRi - of nkObjDownConv, nkObjUpConv: - 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: diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 786a9e4f8..6d9e9873e 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -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 diff --git a/lib/core/seqs.nim b/lib/core/seqs.nim index 914ca438c..b7f9fb153 100644 --- a/lib/core/seqs.nim +++ b/lib/core/seqs.nim @@ -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] diff --git a/lib/system.nim b/lib/system.nim index cb211e6a9..81d308073 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -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. diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index ef9a6deaf..6e06b10f8 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -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,7 +333,53 @@ template unhandled(buf, body) = else: body -proc raiseExceptionAux(e: ref Exception) = +proc nimLeaveFinally() {.compilerRtl.} = + when defined(cpp) and not defined(noCppExceptions): + {.emit: "throw;".} + else: + template e: untyped = currException + if excHandler != nil: + 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 raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} = if localRaiseHook != nil: if not localRaiseHook(e): return if globalRaiseHook != nil: @@ -351,9 +398,8 @@ proc raiseExceptionAux(e: ref Exception) = pushCurrentException(e) else: if excHandler != nil: - if not excHandler.hasRaiseAction or excHandler.raiseAction(e): - pushCurrentException(e) - c_longjmp(excHandler.context, 1) + pushCurrentException(e) + c_longjmp(excHandler.context, 1) else: when hasSomeStackTrace: var buf = newStringOfCap(2000) @@ -367,6 +413,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 +439,8 @@ proc raiseExceptionAux(e: ref Exception) = showErrorMessage(tbuf()) quitOrDebug() -proc raiseExceptionEx(e: ref Exception, ename, procname, filename: cstring, line: int) {.compilerRtl.} = +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.} = diff --git a/tests/destructor/texceptions.nim b/tests/destructor/texceptions.nim new file mode 100644 index 000000000..335ca23be --- /dev/null +++ b/tests/destructor/texceptions.nim @@ -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 diff --git a/tests/destructor/tgcleak4.nim b/tests/destructor/tgcleak4.nim new file mode 100644 index 000000000..4299c8841 --- /dev/null +++ b/tests/destructor/tgcleak4.nim @@ -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() diff --git a/tests/destructor/tv2_raise.nim b/tests/destructor/tv2_raise.nim index 5795c08a0..dcc3b1b38 100644 --- a/tests/destructor/tv2_raise.nim +++ b/tests/destructor/tv2_raise.nim @@ -1,7 +1,7 @@ discard """ cmd: '''nim c --newruntime $file''' output: '''OK 3 -5 1''' +5 2''' """ import strutils, math |