Nine `PNode`-typed codegen procs become `AnyNode`, all pure readers: `bodyCanRaise` (the consumer of the `canRaise` work), `isAssignedImmediately`, `branchHasTooBigRange`, `hasNoInit`, `reifiedOpenArray`, `skipTrivialIndirections`, `isSimpleExpr`, `isConstClosure` and `fewCmps`. The signatures are the small part. A migrated proc that nothing calls with a `BNode` is not even type-checked, so the substance is `grindPredicates`: every one of them runs on BOTH spellings of the SAME node, at EVERY node of every graded body, inside the walk `grindLockstep` was already doing. 67_721 nodes on the reference target, 0 disagreements. Two things had to be gated, and neither by widening a guard until the run went green. The tolerated `(ht . <sym>)` type difference is benign for the vocabulary check and NOT benign for a predicate that reads `typ` — and because the predicates recurse, one excused node poisons every ancestor's answer too. `grindLockstep` now reports whether a subtree is free of it, and only clean subtrees are graded. `isAssignedImmediately` and `fewCmps` hand `n.typ` to `getSize`/`mapType`, which are total only over types the C backend can lay out. Asked at an arbitrary node they meet a `tyGenericParam` or a `tyAnything` and abort — that is a question with no answer in either spelling, not a disagreement between them. Both are graded FROM THE PARENT, at the position production calls them from. Declarative subtrees are skipped for the same reason, along the boundary `bodyCanRaise` already draws. Both exclusions are counted and printed beside the graded count, so a run that grades nothing cannot pass for a run that grades everything. `tools/icgrind` versions the grind target, because two ways of silently getting no coverage turned up while writing it: the main module's routines are never graded, and `ast2nif` defers only `nkStmtList` bodies, so a one-line `proc f(x: int): int = case x ...` is invisible to the oracle. Shapes added for `branchHasTooBigRange` and `fewCmps` produced exactly zero coverage until both were found by counting rather than assumed. Verified: 215/215 byte-identical `.c` against HEAD on the default path; sabotaging `isAtom` and sabotaging the parent-driven node selection each make the grinder fail on the first body it reaches. Recorded rather than papered over: `isConstClosure` is graded only on its false side — the whole closure contains one `nkClosure` node. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XEF7FJvUkGKvG9LSGuEaNR
2.4 KiB
NIM_IC_BNODE_GRIND target
Input for the differential oracle in compiler/cgen.nim (grindBNode), which
runs every codegen proc that has moved to AnyNode over BOTH the .bif cursor
and the materialised PNode for the same body and requires the same answer.
nim c -d:newIcBackend -o:bin/nim_grind compiler/nim.nim
NIM_IC_BNODE_GRIND=1 bin/nim_grind c --ic:on --nimcache:/tmp/ncgrind \
tools/icgrind/grindme.nim
A disagreement is an internalError naming the proc, the path within the body
and both answers. Each backend process reports its coverage on exit:
BNODEGRIND navHits=… navFallbacks=… navRegistered=… graded=… skipDecl=… skipTyp=…
graded is what the number "0 disagreements" is worth. The two skip counts are
printed beside it on purpose, so a run that grades nothing cannot be mistaken
for a run that grades everything.
What this target is for
The oracle grades whatever the dependency closure contains, so most of its
coverage comes from the standard library for free. This target exists for the
shapes the stdlib closure does NOT produce often enough to exercise both
answers of a predicate — a case branch wider than RangeExpandLimit, a set
literal narrow enough for fewCmps to prefer comparisons, an openArray
parameter (the one shape reifiedOpenArray answers false for).
Two things that silently produce no coverage
Both were found by counting, after adding shapes here that turned out never to be graded at all:
-
The main module's routines are never graded. They are built in-process and never arrive as a deferred body. Anything worth grading has to live in
grindlib.nim, not ingrindme.nim. -
Only
nkStmtListbodies are deferred, so only those can be graded — see the placeholder site inast2nif.loadRoutine. A one-lineproc f(x: int): int = case x ...has annkAsgnbody, is loaded eagerly, and is invisible to the oracle. Every routine here opens with a statement for that reason. Measured on this target: 782 of 1434 bodies reach the grinder.
Known coverage gap
isConstClosure is graded but only ever on its false side: a const closure
(nkClosure(<routine sym>, nil)) does not appear in any graded body of this
closure — the whole run contains exactly one nkClosure node, the real closure
in adder. Adding a shape here that produces one would be worth doing.