This commit is contained in:
Andreas Rumpf 2020-07-27 23:27:20 +02:00 • committed by GitHub
commit e6f0d4a5a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 28 deletions

View file

@ -48,6 +48,13 @@ type
const toDebug {.strdefine.} = "" const toDebug {.strdefine.} = ""
proc hasDestructor(c: Con; t: PType): bool {.inline.} =
result = ast.hasDestructor(t)
when toDebug.len > 0:
# for more effective debugging
if not result and c.graph.config.selectedGC in {gcArc, gcOrc}:
assert(not containsGarbageCollectedRef(t))
template dbg(body) = template dbg(body) =
when toDebug.len > 0: when toDebug.len > 0:
if c.owner.name.s == toDebug or toDebug == "always": if c.owner.name.s == toDebug or toDebug == "always":
@ -330,7 +337,7 @@ proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode =
let leDotExpr = if le.kind == nkCheckedFieldExpr: le[0] else: le let leDotExpr = if le.kind == nkCheckedFieldExpr: le[0] else: le
let objType = leDotExpr[0].typ let objType = leDotExpr[0].typ
if hasDestructor(objType): if hasDestructor(c, objType):
if objType.attachedOps[attachedDestructor] != nil and if objType.attachedOps[attachedDestructor] != nil and
sfOverriden in objType.attachedOps[attachedDestructor].flags: sfOverriden in objType.attachedOps[attachedDestructor].flags:
localError(c.graph.config, n.info, errGenerated, """Assignment to discriminant for objects with user defined destructor is not supported, object must have default destructor. localError(c.graph.config, n.info, errGenerated, """Assignment to discriminant for objects with user defined destructor is not supported, object must have default destructor.
@ -364,8 +371,8 @@ proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode = proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
# generate: (let tmp = v; reset(v); tmp) # generate: (let tmp = v; reset(v); tmp)
if not hasDestructor(n.typ): if not hasDestructor(c, n.typ):
assert n.kind != nkSym or not hasDestructor(n.sym.typ) assert n.kind != nkSym or not hasDestructor(c, n.sym.typ)
result = copyTree(n) result = copyTree(n)
else: else:
result = newNodeIT(nkStmtListExpr, n.info, n.typ) result = newNodeIT(nkStmtListExpr, n.info, n.typ)
@ -393,7 +400,7 @@ proc isCapturedVar(n: PNode): bool =
proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode = proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
result = newNodeIT(nkStmtListExpr, n.info, n.typ) result = newNodeIT(nkStmtListExpr, n.info, n.typ)
let tmp = c.getTemp(s, n.typ, n.info) let tmp = c.getTemp(s, n.typ, n.info)
if hasDestructor(n.typ): if hasDestructor(c, n.typ):
result.add c.genWasMoved(tmp) result.add c.genWasMoved(tmp)
var m = c.genCopy(tmp, n) var m = c.genCopy(tmp, n)
m.add p(n, c, s, normal) m.add p(n, c, s, normal)
@ -432,7 +439,7 @@ proc containsConstSeq(n: PNode): bool =
proc ensureDestruction(arg, orig: PNode; c: var Con; s: var Scope): PNode = proc ensureDestruction(arg, orig: PNode; c: var Con; s: var Scope): PNode =
# it can happen that we need to destroy expression contructors # it can happen that we need to destroy expression contructors
# like [], (), closures explicitly in order to not leak them. # like [], (), closures explicitly in order to not leak them.
if arg.typ != nil and hasDestructor(arg.typ): if arg.typ != nil and hasDestructor(c, arg.typ):
# produce temp creation for (fn, env). But we need to move 'env'? # produce temp creation for (fn, env). But we need to move 'env'?
# This was already done in the sink parameter handling logic. # This was already done in the sink parameter handling logic.
result = newNodeIT(nkStmtListExpr, arg.info, arg.typ) result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
@ -521,7 +528,7 @@ template processScopeExpr(c: var Con; s: var Scope; ret: PNode, processCall: unt
# tricky because you would have to intercept moveOrCopy at a certain point # tricky because you would have to intercept moveOrCopy at a certain point
let tmp = c.getTemp(s.parent[], ret.typ, ret.info) let tmp = c.getTemp(s.parent[], ret.typ, ret.info)
tmp.sym.flags.incl sfSingleUsedTemp tmp.sym.flags.incl sfSingleUsedTemp
let cpy = if ret.typ.hasDestructor: let cpy = if hasDestructor(c, ret.typ):
moveOrCopy(tmp, ret, c, s, isDecl = true) moveOrCopy(tmp, ret, c, s, isDecl = true)
else: else:
newTree(nkFastAsgn, tmp, p(ret, c, s, normal)) newTree(nkFastAsgn, tmp, p(ret, c, s, normal))
@ -774,10 +781,10 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode =
result = newNodeI(nkStmtList, n.info) result = newNodeI(nkStmtList, n.info)
for it in n: for it in n:
var ri = it[^1] var ri = it[^1]
if it.kind == nkVarTuple and hasDestructor(ri.typ): if it.kind == nkVarTuple and hasDestructor(c, ri.typ):
let x = lowerTupleUnpacking(c.graph, it, c.owner) let x = lowerTupleUnpacking(c.graph, it, c.owner)
result.add p(x, c, s, consumed) result.add p(x, c, s, consumed)
elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0], c): elif it.kind == nkIdentDefs and hasDestructor(c, it[0].typ) and not isCursor(it[0], c):
for j in 0..<it.len-2: for j in 0..<it.len-2:
let v = it[j] let v = it[j]
if v.kind == nkSym: if v.kind == nkSym:
@ -797,7 +804,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode =
v.add itCopy v.add itCopy
result.add v result.add v
of nkAsgn, nkFastAsgn: of nkAsgn, nkFastAsgn:
if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda} and if hasDestructor(c, n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda} and
not isCursor(n[0], c): not isCursor(n[0], c):
# rule (self-assignment-removal): # rule (self-assignment-removal):
if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym: if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym:
@ -828,7 +835,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode =
result = shallowCopy(n) result = shallowCopy(n)
for i in 0 ..< n.len: for i in 0 ..< n.len:
result[i] = p(n[i], c, s, normal) result[i] = p(n[i], c, s, normal)
if n.typ != nil and hasDestructor(n.typ): if n.typ != nil and hasDestructor(c, n.typ):
if mode == normal: if mode == normal:
result = ensureDestruction(result, n, c, s) result = ensureDestruction(result, n, c, s)
@ -858,7 +865,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode =
result[0] = p(n[0], c, s, normal) result[0] = p(n[0], c, s, normal)
for i in 1 ..< n.len: for i in 1 ..< n.len:
result[i] = n[i] result[i] = n[i]
if mode == sinkArg and hasDestructor(n.typ): if mode == sinkArg and hasDestructor(c, n.typ):
if isAnalysableFieldAccess(n, c.owner) and isLastRead(n, c): if isAnalysableFieldAccess(n, c.owner) and isLastRead(n, c):
s.wasMoved.add c.genWasMoved(n) s.wasMoved.add c.genWasMoved(n)
else: else:
@ -868,7 +875,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode =
result = shallowCopy(n) result = shallowCopy(n)
for i in 0 ..< n.len: for i in 0 ..< n.len:
result[i] = p(n[i], c, s, normal) result[i] = p(n[i], c, s, normal)
if mode == sinkArg and hasDestructor(n.typ): if mode == sinkArg and hasDestructor(c, n.typ):
if isAnalysableFieldAccess(n, c.owner) and isLastRead(n, c): if isAnalysableFieldAccess(n, c.owner) and isLastRead(n, c):
# consider 'a[(g; destroy(g); 3)]', we want to say 'wasMoved(a[3])' # consider 'a[(g; destroy(g); 3)]', we want to say 'wasMoved(a[3])'
# without the junk, hence 'c.genWasMoved(n)' # without the junk, hence 'c.genWasMoved(n)'
@ -1004,7 +1011,7 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
let params = owner.typ.n let params = owner.typ.n
for i in 1..<params.len: for i in 1..<params.len:
let t = params[i].sym.typ let t = params[i].sym.typ
if isSinkTypeForParam(t) and hasDestructor(t.skipTypes({tySink})): if isSinkTypeForParam(t) and hasDestructor(c, t.skipTypes({tySink})):
scope.final.add c.genDestroy(params[i]) scope.final.add c.genDestroy(params[i])
#if optNimV2 in c.graph.config.globalOptions: #if optNimV2 in c.graph.config.globalOptions:
# injectDefaultCalls(n, c) # injectDefaultCalls(n, c)

View file

@ -507,6 +507,8 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
var actions = newNodeI(nkStmtList, c.info) var actions = newNodeI(nkStmtList, c.info)
let elemType = t.lastSon let elemType = t.lastSon
createTypeBoundOps(c.g, c.c, elemType, c.info)
if isFinal(elemType): if isFinal(elemType):
addDestructorCall(c, elemType, actions, genDeref(x, nkDerefExpr)) addDestructorCall(c, elemType, actions, genDeref(x, nkDerefExpr))
actions.add callCodegenProc(c.g, "nimRawDispose", c.info, x) actions.add callCodegenProc(c.g, "nimRawDispose", c.info, x)

View file

@ -84,6 +84,19 @@ proc `<=`(a, b: TLockLevel): bool {.borrow.}
proc `==`(a, b: TLockLevel): bool {.borrow.} proc `==`(a, b: TLockLevel): bool {.borrow.}
proc max(a, b: TLockLevel): TLockLevel {.borrow.} proc max(a, b: TLockLevel): TLockLevel {.borrow.}
proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
if typ == nil: return
when false:
let realType = typ.skipTypes(abstractInst)
if realType.kind == tyRef and
optSeqDestructors in tracked.config.globalOptions:
createTypeBoundOps(tracked.graph, tracked.c, realType.lastSon, info)
createTypeBoundOps(tracked.graph, tracked.c, typ, info)
if (tfHasAsgn in typ.flags) or
optSeqDestructors in tracked.config.globalOptions:
tracked.owner.flags.incl sfInjectDestructors
proc isLocalVar(a: PEffects, s: PSym): bool = proc isLocalVar(a: PEffects, s: PSym): bool =
# and (s.kind != skParam or s.typ.kind == tyOut) # and (s.kind != skParam or s.typ.kind == tyOut)
s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.kind in {skVar, skResult} and sfGlobal notin s.flags and
@ -400,6 +413,7 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
if b[j].isInfixAs(): if b[j].isInfixAs():
assert(b[j][1].kind == nkType) assert(b[j][1].kind == nkType)
catches(tracked, b[j][1].typ) catches(tracked, b[j][1].typ)
createTypeBoundOps(tracked, b[j][2].typ, b[j][2].info)
else: else:
assert(b[j].kind == nkType) assert(b[j].kind == nkType)
catches(tracked, b[j].typ) catches(tracked, b[j].typ)
@ -720,20 +734,6 @@ proc checkRange(c: PEffects; value: PNode; typ: PType) =
checkLe(c, lowBound, value) checkLe(c, lowBound, value)
checkLe(c, value, highBound) checkLe(c, value, highBound)
proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
if typ == nil: return
let realType = typ.skipTypes(abstractInst)
when false:
# XXX fix this in liftdestructors instead
if realType.kind == tyRef and
optSeqDestructors in tracked.config.globalOptions:
createTypeBoundOps(tracked.graph, tracked.c, realType.lastSon, info)
createTypeBoundOps(tracked.graph, tracked.c, typ, info)
if (tfHasAsgn in typ.flags) or
optSeqDestructors in tracked.config.globalOptions:
tracked.owner.flags.incl sfInjectDestructors
proc trackCall(tracked: PEffects; n: PNode) = proc trackCall(tracked: PEffects; n: PNode) =
template gcsafeAndSideeffectCheck() = template gcsafeAndSideeffectCheck() =
if notGcSafe(op) and not importedFromC(a): if notGcSafe(op) and not importedFromC(a):
@ -799,7 +799,6 @@ proc trackCall(tracked: PEffects; n: PNode) =
# check required for 'nim check': # check required for 'nim check':
if n[1].typ.len > 0: if n[1].typ.len > 0:
createTypeBoundOps(tracked, n[1].typ.lastSon, n.info)
createTypeBoundOps(tracked, n[1].typ, n.info) createTypeBoundOps(tracked, n[1].typ, n.info)
# new(x, finalizer): Problem: how to move finalizer into 'createTypeBoundOps'? # new(x, finalizer): Problem: how to move finalizer into 'createTypeBoundOps'?

21
tests/arc/tasyncleak.nim Normal file
View file

@ -0,0 +1,21 @@
discard """
outputsub: "(allocCount: 6013, deallocCount: 6007)"
cmd: "nim c --gc:orc -d:nimAllocStats $file"
"""
import asyncdispatch
# bug #15076
const
# Just to occupy some RAM
BigData = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
proc doNothing(): Future[void] {.async.} =
discard
proc main(): Future[void] {.async.} =
for x in 0 .. 1_000:
await doNothing()
waitFor main()
GC_fullCollect()
echo getAllocStats()