From db47363784a22fe2bf9b2b0610fa15e68aa124c3 Mon Sep 17 00:00:00 2001 From: araq Date: Sun, 30 Aug 2026 20:11:36 +0200 Subject: [PATCH] IC: memoize the FileId -> FileIndex resolution, halving a cold `--ic:on` build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `oldLineInfo` resolved every decoded token's line info by copying a path out of the buffer's filename pool and hashing it through `msgs.fileInfoIdx`. Every time — no cache, on a step that runs once per node the decoder loads and once per statement the generator emits. On a 68-module target that is 259k calls at 5.2us. Memoized per pool it is 34ms, and the cold build goes from 21.5s to 10.1s. `revTab` could not serve this: it is keyed by a `FileId` in the WRITER's global `pool.files`, while a decoded token's `FileId` indexes the buffer's OWN filename pool — a fresh one per `bif`-loaded module. So the cache is keyed by (pool, FileId), as a `seq` because ids are small and dense within one pool. `readPool` holds a REFERENCE rather than a raw pointer, and that is the whole safety argument: it keeps the pool alive, so a freed pool cannot be replaced by a new one at the same address and answer from the wrong file table. Verified: `tests/ic` 39/39; the cursor-vs-`PNode` differential grinder clean over ~150k graded nodes on `tools/icgrind` — and confirmed live by sabotaging the pool-identity check, which makes it fire on `info` immediately; all 67 `.c` files an `--ic:on` build generates byte-identical to before; all 216 a non-IC build generates likewise. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FMyRHByv7hhaQJ4Pa1bHbE --- compiler/ast2nif.nim | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index 01fee4fc64..f7f3df194e 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -130,6 +130,16 @@ type revTab: Table[FileId, FileIndex] # reverse mapping for oldLineInfo man: LineInfoManager config: ConfigRef + # The READ direction's cache, which `revTab` cannot serve: `revTab` is keyed + # by a `FileId` in the WRITER's global `pool.files`, while a decoded token's + # `FileId` indexes the buffer's OWN filename pool. So the cache has to be + # keyed by (pool, FileId), and it is a `seq` because `FileId`s are small and + # dense within one pool. `readPool` holds a REFERENCE rather than a raw + # pointer on purpose: it keeps the pool alive, so a freed pool cannot be + # replaced by a new one at the same address and silently answer from the + # wrong file table. + readPool: Pool + readTab: seq[FileIndex] proc newLineInfoWriter(config: ConfigRef): LineInfoWriter = # `fileK` starts invalid so the one-entry cache never collides with a real @@ -178,12 +188,26 @@ proc oldLineInfo(w: var LineInfoWriter; info: NifLineInfo; p: Pool): TLineInfo = ## it to a `TLineInfo`. `info.file` indexes the loaded buffer's OWN filename ## pool `p` (= `cursorPool(n)`), which is the shared `icPool` for a text-parsed ## module but a fresh per-file pool for a `bif`-loaded one. + ## + ## Memoized per pool. Resolving a name costs a string copy out of the pool + ## plus a hash of a full path, and the generator asks for a node's line info + ## on essentially every statement it emits — 259k times on a 68-module build, + ## which was 1.36s of the 1.88s the cursor-driven generator spent. if info.file == NoFile: result = unknownLineInfo else: - let filePath = p.filenames[info.file] - let fileIdx = msgs.fileInfoIdx(w.config, AbsoluteFile filePath) - result = TLineInfo(line: info.line.uint16, col: info.col.int16, fileIndex: fileIdx) + if p != w.readPool: + w.readPool = p + w.readTab = @[] + let id = int(uint32(info.file)) + if id >= w.readTab.len: + let oldLen = w.readTab.len + w.readTab.setLen(id + 1) + for i in oldLen ..< w.readTab.len: w.readTab[i] = astli.InvalidFileIdx + if w.readTab[id] == astli.InvalidFileIdx: + w.readTab[id] = msgs.fileInfoIdx(w.config, AbsoluteFile p.filenames[info.file]) + result = TLineInfo(line: info.line.uint16, col: info.col.int16, + fileIndex: w.readTab[id]) # ------------- Writer ---------------------------------------------------------------