Files
Nim/tools/icgrind
Araq 06b1bf8f9a IC: take the seam into trees and ccgutils
`trees.nim` can import `bnode` — nothing in `bnode`'s import closure reaches
`trees`, checked rather than assumed — so the shared helpers move to `AnyNode`
instead of being reimplemented behind the seam: `getMagic`, `whichPragma`,
`getRoot`, `isDeepConstExpr`, plus `ccgutils.stmtsContainPragma`. That unblocks
three more codegen procs, `canMove`, `notYetAlive` and `ifSwitchSplitPoint`,
which needed them and nothing else.

`stmtsContainPragma` could not simply stay `getPragmaStmt(n, w) != nil`, and
the reason is worth recording because it will recur: a proc that returns a node
OR NIL is the one shape the seam cannot serve. `.bif` spells a missing child as
a `DotToken` *inside* a tree; there is no nil token to hand back as a return
value and a `Cursor` is not nilable. So the predicate is split out — and,
because that leaves two copies of one traversal, `grindPredicates` now asserts
the two agree at every node instead of trusting them to.

Measuring the answers, not just the agreement, again earned its keep. Six of
the new checks came back with a wide spread (`getMagic` 7780 non-`mNone` over
many magics, `getRoot` 19506 non-nil syms compared by identity, `isDeepConstExpr`
7917 true, `notYetAlive` 9653 true). Two came back CONSTANT — `stmtsContainPragma`
false at all 67_721 nodes and `ifSwitchSplitPoint` zero at all 24 — because
nothing in the closure uses `{.linearScanEnd.}` or `{.computedGoto.}`. Both are
now exercised on both answers by shapes added to `tools/icgrind`. A check that
grades a constant is indistinguishable from a passing check in the output, so
this only shows up if the distribution is looked at.

Verified: grind clean over the whole `--ic:on` closure (67_857 nodes, 0
disagreements); the target's `--ic:on` output matches its `nim c` output;
215/215 byte-identical `.c` against HEAD on the default path; all four build
configurations compile.

Sabotaging `bnode.secondSon` — an accessor the lockstep walk does NOT itself
use, since it descends by index — is caught only by this layer, and is: it
fires on `getRoot`, `isDeepConstExpr`, `reifiedOpenArray` and
`skipTrivialIndirections`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEF7FJvUkGKvG9LSGuEaNR
2026-08-30 10:23:43 +02:00
..

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:

  1. 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 in grindme.nim.

  2. Only nkStmtList bodies are deferred, so only those can be graded — see the placeholder site in ast2nif.loadRoutine. A one-line proc f(x: int): int = case x ... has an nkAsgn body, 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.