From 6aad72b257ce6782a344f330bb33c06a5a448c45 Mon Sep 17 00:00:00 2001 From: araq Date: Mon, 9 Feb 2026 12:31:49 +0100 Subject: [PATCH] better write barrier, now works as the documenation says it needs to work --- compiler/liftdestructors.nim | 34 +++++++++++++++++++++++++++++++++- lib/system/yrc.nim | 19 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index 7133587249..0f78d5c1c7 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -730,11 +730,33 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = dest[] = source decRef tmp + For YRC the write barrier is more complicated still and must be: + + let tmp = dest + # assignment must come first so that the collector sees the most-recent graph: + atomic: dest[] = source + # Then teach the cycle collector about the changes edge (these use locks, see yrc.nim): + incRef source + decRef tmp + + This is implemented as a single runtime call (nimAsgnYrc / nimSinkYrc). ]# var actions = newNodeI(nkStmtList, c.info) let elemType = t.elementType createTypeBoundOps(c.g, c.c, elemType, c.info, c.idgen) + + # YRC uses dedicated runtime procs for the entire write barrier: + if c.g.config.selectedGC == gcYrc: + case c.kind + of attachedAsgn, attachedDup: + body.add callCodegenProc(c.g, "nimAsgnYrc", c.info, genAddr(c, x), y) + return + of attachedSink: + body.add callCodegenProc(c.g, "nimSinkYrc", c.info, genAddr(c, x), y) + return + else: discard # fall through for destructor, trace, wasMoved + let isCyclic = c.g.config.selectedGC in {gcOrc, gcYrc} and types.canFormAcycle(c.g, elemType) let isInheritableAcyclicRef = c.g.config.selectedGC in {gcOrc, gcYrc} and @@ -819,6 +841,17 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = let xenv = genBuiltin(c, mAccessEnv, "accessEnv", x) xenv.typ = getSysType(c.g, c.info, tyPointer) + # YRC uses dedicated runtime procs for the write barrier on the env pointer: + if c.g.config.selectedGC == gcYrc: + case c.kind + of attachedAsgn, attachedDup: + body.add callCodegenProc(c.g, "nimAsgnYrc", c.info, genAddr(c, xenv), y) + return + of attachedSink: + body.add callCodegenProc(c.g, "nimSinkYrc", c.info, genAddr(c, xenv), y) + return + else: discard # fall through for destructor, trace, wasMoved + let isCyclic = c.g.config.selectedGC in {gcOrc, gcYrc} let tmp = if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}: @@ -852,7 +885,6 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = body.add genIf(c, cond, actions) else: body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv)) - body.add genIf(c, cond, actions) body.add newAsgnStmt(x, y) of attachedDup: diff --git a/lib/system/yrc.nim b/lib/system/yrc.nim index f089f2c6bf..bd981f51be 100644 --- a/lib/system/yrc.nim +++ b/lib/system/yrc.nim @@ -448,6 +448,25 @@ proc unsureAsgnRef(dest: ptr pointer, src: pointer) {.inline.} = dest[] = src if src != nil: nimIncRefCyclic(src, true) +proc nimAsgnYrc(dest: ptr pointer; src: pointer) {.compilerRtl.} = + ## YRC write barrier for ref copy assignment. + ## Atomically stores src into dest, then buffers RC adjustments. + ## Freeing is always done by the cycle collector, never inline. + let tmp = dest[] + atomicStoreN(dest, src, ATOMIC_RELEASE) + if src != nil: + nimIncRefCyclic(src, true) + if tmp != nil: + discard nimDecRefIsLastCyclicDyn(tmp) + +proc nimSinkYrc(dest: ptr pointer; src: pointer) {.compilerRtl.} = + ## YRC write barrier for ref sink (move). No incRef on source. + ## Freeing is always done by the cycle collector, never inline. + let tmp = dest[] + atomicStoreN(dest, src, ATOMIC_RELEASE) + if tmp != nil: + discard nimDecRefIsLastCyclicDyn(tmp) + proc nimMarkCyclic(p: pointer) {.compilerRtl, inl.} = when optimizedOrc: if p != nil: