experiment: =dispose hooks for Nim

This commit is contained in:
araq
2026-02-12 10:08:32 +01:00
parent 5fa11c5686
commit 2272873806
11 changed files with 51 additions and 14 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -764,6 +764,7 @@ type
attachedAsgn,
attachedDup,
attachedSink,
attachedDispose,
attachedTrace,
attachedDeepCopy

View File

@@ -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"):

View File

@@ -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) =

View File

@@ -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:

View File

@@ -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):

View File

@@ -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: [].} =

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 =