nimAsgnYrc/nimSinkYrc were emitted for EVERY ref type, ignoring .acyclic.
The barrier defers the dec into a stripe queue, drainStripe hands the cell
to registerLocal, and it enters the collector as a capture ROOT -- so types
annotated precisely to stay out of the cycle collector were traced by it
anyway, through capture/deadness/commit.
Gate that barrier on canFormAcycle (the same predicate ccgtypes.nim:1903
uses to set the descriptor's acyclic flag, so codegen and runtime cannot
disagree) and let acyclic refs fall through to the prompt arc-style path.
nimDecRefIsLastDyn stops forwarding to nimDecRefIsLastCyclicDyn and does a
prompt atomic dec.
No grace period is needed for those cells and that is not an accident: the
collector cannot reach one by traversal (liftdestructors only emits
nimTraceRef when isCyclic) and cannot hold one as a root (roots come only
from registerLocal on a drained dec). The queued dec was the only way in.
This also removes the reason for the earlier nimDecRefIsLastCyclicStatic
workaround for final acyclic types, so that is reverted.
compiler/astdef.nim now emits 97 nimDecRefIsLastDyn + 9 nimDecRefIsLast +
38 nimIncRef and zero nimAsgnYrc/nimDecRefIsLastCyclic*, matching ORC.
Compiler self-compile (--compileOnly compiler/nim.nim): 37.65s -> 6.99s,
against orc 6.96s and arc 6.77s. RSS 743MB vs orc 746MB. yrcbench is
unchanged (0.78s/5.13s CPU) and still passes its leak assertion, so cyclic
collection is unaffected. koch boot -d:release --mm:yrc reaches
'executables are equal: SUCCESS!'.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>