mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-19 03:24:53 +00:00
`addReexportedEnumFields` forced every exported symbol through `loadSym` and
THEN asked whether it was a non-pure enum type. Almost none are: 34815 symbols
on a 68-module build, of which the enum handling wants the handful that are
types. A sym def is written `(sd <name> <marker> <kind> …)`, so the kind is
three tokens in and needs no decode at all.
addReexportedEnumFields 290ms -> 5ms
export branch 484ms -> 154ms
processTopLevel 1086ms -> 787ms
loadDepClosure 2126ms -> 1978ms
cold --ic:on build 8.69s -> 8.51s (baseline built alongside)
`peekSymKind` mirrors `loadSymFromCursor`'s walk and the two have to change
together, so it is graded rather than trusted. `-d:icPeekKindCheck` compares
every peek against the load it replaces: a full build is 34508 peeks, zero
disagreements, and sabotaging the peek to answer `skProc` where the def says
`skType` fires on the first symbol.
The FIRST sabotage did not fire, and that is the part worth recording. Dropping
a `skip` from the walk lands on a non-`TagLit`, which answers `skUnknown` — the
designed fallback, correct but slower — so the equality assertion never saw it.
A walk that had drifted out of step would therefore look exactly like a clean
run. So the check has a second half: `PeekFallback` counts how often the peek
cannot read the header and `-d:icBNodeProf` reports it beside `PeekKind`. It is
0, which is the claim that the walk is in step; an equality oracle alone could
not make it.
Verified: both configurations build; `tests/ic` 40/40; 67/67 generated `.c`
byte-identical, cursor still identical to `PNode`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMyRHByv7hhaQJ4Pa1bHbE
84 lines
3.1 KiB
Nim
84 lines
3.1 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2026 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## Opt-in instrumentation for the IC backend, enabled with `-d:icBNodeProf`.
|
|
## Off, every template below is `discard` and nothing is linked in.
|
|
##
|
|
## It lives in its own module with NO compiler imports so that any stage can
|
|
## use it without creating a cycle — `bnode` needs it for the accessors,
|
|
## `nifbackend` for the stage phases, `cgen` for what happens per routine.
|
|
##
|
|
## Each backend process appends ONE line to `$NIM_IC_BNODE_PROF` at exit (or to
|
|
## stderr when that is unset), because a `--ic:on` build fans out a process per
|
|
## module per stage and interleaved writes would tear. Use `-d:icNoParallel`
|
|
## when the numbers need to be attributable to a particular module.
|
|
##
|
|
## Counts are for volume, timings for cost, and the two answer different
|
|
## questions: the accessors turned out to be 700k calls worth 8ms, while `info`
|
|
## was 259k calls worth 1.36s. Neither number alone would have found that.
|
|
|
|
when defined(icBNodeProf):
|
|
import std / [envvars, exitprocs, syncio, monotimes]
|
|
from std / times import inNanoseconds
|
|
|
|
type
|
|
ProfSlot* = enum
|
|
pKind, pTagKindHit, pTagKindMiss, pAstChildren, pSkip, pSon, pLen,
|
|
pLastSon, pIterYield, pSym, pTyp, pTypTagLit, pOrigin, pNilType,
|
|
pGenBodyCalls, pInfo, pIfaceExported, pIfaceHidden, pIfaceModules,
|
|
pTopNodes, pExportSyms, pPeekKind, pPeekFallback, pPeekLoaded
|
|
TimeSlot* = enum
|
|
tLoadClosure, tModuleId, tBifLoad, tPosIndex, tTopLevel, tInterfTables,
|
|
tTransform, tHandOff, tGenBody, tAnalyses,
|
|
tSym, tTyp, tInfo, tOrigin, tExportBranch, tResolveSym, tEnumFields
|
|
|
|
var profCounts: array[ProfSlot, int]
|
|
var profNanos: array[TimeSlot, int64]
|
|
var profStart: array[TimeSlot, MonoTime]
|
|
var profArmed = false
|
|
|
|
proc profDump() =
|
|
var line = "BNODEPROF"
|
|
for s in ProfSlot: line.add " " & ($s)[1..^1] & "=" & $profCounts[s]
|
|
for s in TimeSlot: line.add " " & ($s)[1..^1] & "ms=" & $(profNanos[s] div 1_000_000)
|
|
let f = getEnv("NIM_IC_BNODE_PROF")
|
|
if f.len > 0:
|
|
let h = open(f, fmAppend)
|
|
h.writeLine line
|
|
h.close()
|
|
else:
|
|
stderr.writeLine line
|
|
|
|
template armProf() =
|
|
if not profArmed:
|
|
profArmed = true
|
|
addExitProc profDump
|
|
|
|
template prof*(s: ProfSlot; n = 1) =
|
|
armProf()
|
|
inc profCounts[s], n
|
|
template icProfStart*(s: TimeSlot) =
|
|
armProf()
|
|
profStart[s] = getMonoTime()
|
|
template icProfStop*(s: TimeSlot) =
|
|
profNanos[s] += (getMonoTime() - profStart[s]).inNanoseconds
|
|
|
|
template timed*(s: TimeSlot; body: untyped) =
|
|
## Leaf timing. NOT re-entrant, and the phase slots are not disjoint —
|
|
## `tTransform` contains body materialization, `tTyp` reaches `tSym`. Read
|
|
## them as nested, not additive.
|
|
let t0 = getMonoTime()
|
|
body
|
|
profNanos[s] += (getMonoTime() - t0).inNanoseconds
|
|
else:
|
|
template prof*(s: untyped; n = 1) = discard
|
|
template icProfStart*(s: untyped) = discard
|
|
template icProfStop*(s: untyped) = discard
|
|
template timed*(s: untyped; body: untyped) = body
|