From 227287380660bdcf75ce581448678795ca57e643 Mon Sep 17 00:00:00 2001 From: araq Date: Thu, 12 Feb 2026 10:08:32 +0100 Subject: [PATCH] experiment: =dispose hooks for Nim --- compiler/ast.nim | 2 +- compiler/ast2nif.nim | 5 +++++ compiler/astdef.nim | 1 + compiler/ccgtypes.nim | 4 ++++ compiler/liftdestructors.nim | 36 +++++++++++++++++++++++++++++----- compiler/semdata.nim | 4 ++-- lib/system.nim | 1 + lib/system/arc.nim | 2 +- lib/system/orc.nim | 2 +- lib/system/yrc.nim | 6 +++--- tests/destructor/tbintree2.nim | 2 +- 11 files changed, 51 insertions(+), 14 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 5b08ea5e60..112b231460 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -926,7 +926,7 @@ proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode, const AttachedOpToStr*: array[TTypeAttachedOp, string] = [ - "=wasMoved", "=destroy", "=copy", "=dup", "=sink", "=trace", "=deepcopy"] + "=wasMoved", "=destroy", "=dispose", "=copy", "=dup", "=sink", "=trace", "=deepcopy"] proc `$`*(s: PSym): string = if s != nil: diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index 48803e25e6..7e73d6102c 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -497,6 +497,7 @@ proc trExport(w: var Writer; n: PNode) = let replayTag = registerTag("replay") let repConverterTag = registerTag("repconverter") let repDestroyTag = registerTag("repdestroy") +let repDisposeTag = registerTag("repdispose") let repWasMovedTag = registerTag("repwasmoved") let repCopyTag = registerTag("repcopy") let repSinkTag = registerTag("repsink") @@ -677,6 +678,8 @@ proc writeOp(w: var Writer; content: var TokenBuf; op: LogEntry) = case op.op of attachedDestructor: content.addParLe repDestroyTag, NoLineInfo + of attachedDispose: + content.addParLe repDisposeTag, NoLineInfo of attachedAsgn: content.addParLe repCopyTag, NoLineInfo of attachedWasMoved: @@ -1576,6 +1579,8 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag]; t = loadLogOp(c, result.logOps, s, ConverterEntry, attachedTrace, module) elif t.tagId == repDestroyTag: t = loadLogOp(c, result.logOps, s, HookEntry, attachedDestructor, module) + elif t.tagId == repDisposeTag: + t = loadLogOp(c, result.logOps, s, HookEntry, attachedDispose, module) elif t.tagId == repWasMovedTag: t = loadLogOp(c, result.logOps, s, HookEntry, attachedWasMoved, module) elif t.tagId == repCopyTag: diff --git a/compiler/astdef.nim b/compiler/astdef.nim index b9a8aab3e1..5e3cc549d8 100644 --- a/compiler/astdef.nim +++ b/compiler/astdef.nim @@ -764,6 +764,7 @@ type attachedAsgn, attachedDup, attachedSink, + attachedDispose, attachedTrace, attachedDeepCopy diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 98b9ab9a60..685eab31e4 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -1855,6 +1855,10 @@ proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineIn typeEntry.addCast(CPointer): genHook(m, t, info, attachedTrace, typeEntry) + typeEntry.addField(typeInit, name = "disposeImpl"): + typeEntry.addCast(CPointer): + genHook(m, t, info, attachedDispose, typeEntry) + let dispatchMethods = toSeq(getMethodsPerType(m.g.graph, t)) if dispatchMethods.len > 0: typeEntry.addField(typeInit, name = "flags"): diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index 6600561c9c..6adf053519 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -484,6 +484,22 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = else: result = false #result = addDestructorCall(c, t, body, x) + of attachedDispose: + var op = getAttachedOp(c.g, t, c.kind) + if op != nil and sfOverridden in op.flags: + + if op.ast.isGenericRoutine: + # patch generic destructor: + op = instantiateGeneric(c, op, t, t.typeInst) + setAttachedOp(c.g, c.idgen.module, t, attachedDispose, op) + + #markUsed(c.g.config, c.info, op, c.g.usageSym) + onUse(c.info, op) + body.add destructorCall(c, op, x) # fine for `dispose` too! + result = true + else: + result = false + of attachedAsgn, attachedSink, attachedTrace: var op = getAttachedOp(c.g, t, c.kind) if op != nil and sfOverridden in op.flags: @@ -646,6 +662,9 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = # destroy all elements: forallElements(c, t, body, x, y) body.add genBuiltin(c, mDestroy, "destroy", x) + of attachedDispose: + # The mDestroy that the C code generator produces is right for `dispose`: + body.add genBuiltin(c, mDestroy, "destroy", x) of attachedTrace: if canFormAcycle(c.g, t.elemType): # follow all elements: @@ -682,6 +701,9 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = of attachedDestructor: doAssert t.destructor != nil body.add destructorCall(c, t.destructor, x) + of attachedDispose: + # The mDestroy that the C code generator produces is right for `dispose`: + body.add genBuiltin(c, mDestroy, "destroy", x) of attachedTrace: if t.kind != tyString and canFormAcycle(c.g, t.elemType): let op = getAttachedOp(c.g, t, c.kind) @@ -706,7 +728,7 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = doAssert t.destructor != nil moveCall.add destructorCall(c, t.destructor, x) body.add moveCall - of attachedDestructor: + of attachedDestructor, attachedDispose: body.add genBuiltin(c, mDestroy, "destroy", x) of attachedTrace: discard "strings are atomic and have no inner elements that are to trace" @@ -828,6 +850,8 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = of attachedDestructor: body.add genIf(c, cond, actions) of attachedDeepCopy: assert(false, "cannot happen") + of attachedDispose: + discard "the whole point of this exercise! Do not traverse `ref` fields for `=dispose`!" of attachedTrace: if isCyclic: if isFinal(elemType): @@ -920,6 +944,8 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv)) of attachedDestructor: body.add genIf(c, cond, actions) + of attachedDispose: + discard "the whole point of this exercise! Do not traverse `closure` fields for `=dispose`!" of attachedDeepCopy: assert(false, "cannot happen") of attachedTrace: body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, genAddrOf(xenv, c.idgen), y) @@ -950,7 +976,7 @@ proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = else: body.sons.insert(des, 0) of attachedDeepCopy: assert(false, "cannot happen") - of attachedTrace: discard + of attachedTrace, attachedDispose: discard of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = @@ -978,7 +1004,7 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = of attachedDestructor: body.add genIf(c, x, actions) of attachedDeepCopy: assert(false, "cannot happen") - of attachedTrace: discard + of attachedTrace, attachedDispose: discard of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = @@ -1018,7 +1044,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = else: body.sons.insert(des, 0) of attachedDeepCopy: assert(false, "cannot happen") - of attachedTrace: discard + of attachedTrace, attachedDispose: discard of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = @@ -1036,7 +1062,7 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = of attachedDestructor: body.add genIf(c, xx, actions) of attachedDeepCopy: assert(false, "cannot happen") - of attachedTrace: discard + of attachedTrace, attachedDispose: discard of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) = diff --git a/compiler/semdata.nim b/compiler/semdata.nim index a3aae559fd..6bcdb81c03 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -728,10 +728,10 @@ proc analyseIfAddressTakenInCall*(c: PContext, n: PNode, isConverter = false) = proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode = ## Replaces builtin generic hooks with lifted hooks. case kind - of attachedDestructor: + of attachedDestructor, attachedDispose: result = n let t = n[1].typ.skipTypes(abstractVar) - let op = getAttachedOp(c.graph, t, attachedDestructor) + let op = getAttachedOp(c.graph, t, kind) if op != nil: result[0] = newSymNode(op) if op.typ != nil and op.typ.len == 2 and op.typ.firstParamType.kind != tyVar: diff --git a/lib/system.nim b/lib/system.nim index 6104c1b928..88a80d410f 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1674,6 +1674,7 @@ when not defined(js) and defined(nimV2): when defined(nimTypeNames) or defined(nimArcIds) or defined(nimOrcLeakDetector): name: cstring traceImpl: pointer + disposeImpl: pointer typeInfoV1: pointer # for backwards compat, usually nil flags: int when defined(gcDestructors): diff --git a/lib/system/arc.nim b/lib/system/arc.nim index 3ac84be3bb..5994f84ac2 100644 --- a/lib/system/arc.nim +++ b/lib/system/arc.nim @@ -192,7 +192,7 @@ proc nimRawDispose(p: pointer, alignment: int) {.compilerRtl.} = let hdrSize = align(sizeof(RefHeader), alignment) alignedDealloc(p -! hdrSize, alignment) -template `=dispose`*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x), T.alignOf) +template `=disposeHidden`*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x), T.alignOf) #proc dispose*(x: pointer) = nimRawDispose(x) proc nimDestroyAndDispose(p: pointer) {.compilerRtl, quirky, raises: [].} = diff --git a/lib/system/orc.nim b/lib/system/orc.nim index 2b9ce22ec4..846eee4510 100644 --- a/lib/system/orc.nim +++ b/lib/system/orc.nim @@ -302,7 +302,7 @@ proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) = while j.traceStack.len > 0: let (entry, desc) = j.traceStack.pop() let t = head entry[] - entry[] = nil # ensure that the destructor does touch moribund objects! + entry[] = nil # ensure that the destructor does not touch moribund objects! if t.color == col and t.rootIdx == 0: j.toFree.add(t, desc) t.setColor(colBlack) diff --git a/lib/system/yrc.nim b/lib/system/yrc.nim index 9a9920cee8..9d1107b861 100644 --- a/lib/system/yrc.nim +++ b/lib/system/yrc.nim @@ -258,8 +258,8 @@ proc free(s: Cell; desc: PNimTypeV2) {.inline.} = if (s.rc and inRootsFlag) == 0: let p = s +! sizeof(RefHeader) when logOrc: writeCell("free", s, desc) - if desc.destructor != nil: - cast[DestructorProc](desc.destructor)(p) + if desc.disposeImpl != nil: + cast[DestructorProc](desc.disposeImpl)(p) nimRawDispose(p, desc.align) template orcAssert(cond, msg) = @@ -339,7 +339,7 @@ proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) = while j.traceStack.len > 0: let (entry, desc) = j.traceStack.pop() let t = head entry[] - entry[] = nil + #entry[] = nil if t.color == col and (t.rc and inRootsFlag) == 0: j.toFree.add(t, desc) t.setColor(colBlack) diff --git a/tests/destructor/tbintree2.nim b/tests/destructor/tbintree2.nim index d56c2850bf..9959309285 100644 --- a/tests/destructor/tbintree2.nim +++ b/tests/destructor/tbintree2.nim @@ -57,7 +57,7 @@ proc `=destroy`(t: var Tree) {.nodestroy.} = let x = s.pop if x.left != nil: s.add(x.left) if x.right != nil: s.add(x.right) - `=dispose`(x) + `=disposeHidden`(x) `=destroy`(s) proc hasValue(self: var Tree, x: int32): bool =