mirror of
https://github.com/nim-lang/Nim.git
synced 2026-07-12 04:09:34 +00:00
253 lines
11 KiB
Nim
253 lines
11 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2026 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## nifcore-based IC serialization helpers — Stage 1 of porting the IC backend
|
|
## from the old `nifstreams`/`nifcursors` NIF stack to `nifcore` (see
|
|
## `doc/ic_nifcore_port.md`).
|
|
##
|
|
## It hosts:
|
|
## * the process-wide shared `Pool`/`TagPool` that stands in for the old global
|
|
## `nifstreams.pool`,
|
|
## * `writeFileStable`, the content-stable file writer mirroring
|
|
## `nifcursors.writeFile(..., OnlyIfChanged)`,
|
|
## * the first ported writer (`writeSemDeps`), used as the migration spike.
|
|
##
|
|
## No `nifstreams`/`nifcursors` types cross this module's boundary: callers pass
|
|
## plain Nim values (config, ids, string lists), so it can coexist with the
|
|
## still-old-API `ast2nif.nim` during the migration.
|
|
|
|
import std / [syncio, algorithm]
|
|
from std / os import removeFile, moveFile
|
|
import options, pathutils, typekeys
|
|
import "../dist/nimony/src/lib" / [nifcore, nifcoreparse, nifreader, bif]
|
|
|
|
# One shared literals pool + tag pool for the whole process — the nifcore
|
|
# analogue of the old global `nifstreams.pool`. A single shared pool keeps
|
|
# string/symbol/file ids stable across every TokenBuf the IC backend builds,
|
|
# preserving the old global-pool semantics during the migration. (Stage 6 may
|
|
# move to fresh per-file pools for bif's fast path; see doc/ic_nifcore_port.md.)
|
|
let icPool* = newPool()
|
|
let icTags* = newTagPool()
|
|
|
|
proc createIcBuf*(cap = 16): TokenBuf {.inline.} =
|
|
## A `TokenBuf` bound to the shared IC pools.
|
|
createTokenBuf(cap, icPool, icTags)
|
|
|
|
proc tagId*(s: string): TagId {.inline.} =
|
|
## Intern a tag name in the shared tag pool.
|
|
icTags.registerTag(s)
|
|
|
|
type
|
|
IcBuilder* = object
|
|
## A thin nifcore `TokenBuf` builder whose surface is *primitive types only*
|
|
## (strings/ints/floats). It lets the still-old-API `ast2nif.nim` drive a
|
|
## nifcore buffer without any nifcore type crossing the module boundary —
|
|
## the bridge that routes IC output onto the nifcore serializer (Stage 2).
|
|
buf*: TokenBuf
|
|
|
|
proc newIcBuilder*(cap = 16): IcBuilder = IcBuilder(buf: createIcBuf(cap))
|
|
|
|
proc openTag*(b: var IcBuilder; tag: string) {.inline.} = b.buf.openTag(tagId(tag))
|
|
proc closeTag*(b: var IcBuilder) {.inline.} = b.buf.closeTag()
|
|
proc addSymUse*(b: var IcBuilder; s: string) {.inline.} = b.buf.addSymUse(s)
|
|
proc addSymDef*(b: var IcBuilder; s: string) {.inline.} = b.buf.addSymDef(s)
|
|
proc addIdent*(b: var IcBuilder; s: string) {.inline.} = b.buf.addIdent(s)
|
|
proc addStrLit*(b: var IcBuilder; s: string) {.inline.} = b.buf.addStrLit(s)
|
|
proc addIntLit*(b: var IcBuilder; v: int64) {.inline.} = b.buf.addIntLit(v)
|
|
proc addUIntLit*(b: var IcBuilder; v: uint64) {.inline.} = b.buf.addUIntLit(v)
|
|
proc addFloatLit*(b: var IcBuilder; v: float64) {.inline.} = b.buf.addFloatLit(v)
|
|
proc addCharLit*(b: var IcBuilder; c: char) {.inline.} = b.buf.addCharLit(c)
|
|
proc addDotToken*(b: var IcBuilder) {.inline.} = b.buf.addDotToken()
|
|
|
|
proc lineInfo*(b: var IcBuilder; file: string; line, col: int32; comment = "") =
|
|
## Attach line info (+ optional `#comment#`) to the head just emitted. No-op
|
|
## when `file` is empty (matches the old "emit only when info is valid").
|
|
## Strings are interned in the shared pools; the file/comment ids reproduce
|
|
## the old `pool.files`/`pool.strings` entries by string value.
|
|
if file.len == 0: return
|
|
let fid = icPool.filenames.getOrIncl(file)
|
|
let cid = if comment.len > 0: icPool.strings.getOrIncl(comment) else: StrId(0)
|
|
b.buf.appendLineInfo(fid, line, col, cid)
|
|
|
|
proc writeFileStable*(b: var TokenBuf; path: string; onlyIfChanged = false) =
|
|
## Serialize `b` to canonical module NIF text and write it. Mirrors
|
|
## `nifcursors.writeFile`: the module suffix is derived from `path`
|
|
## (`"." & extractModuleSuffix`), and `onlyIfChanged` skips the write when the
|
|
## on-disk bytes already match — the content-stability nifmake's incremental
|
|
## rebuild depends on.
|
|
let content = toModuleString(b, "." & extractModuleSuffix(path))
|
|
if onlyIfChanged:
|
|
let existing =
|
|
try: readFile(path)
|
|
except CatchableError: ""
|
|
if existing == content: return
|
|
writeFile(path, content)
|
|
|
|
proc writeStable*(b: var IcBuilder; path: string; onlyIfChanged = false) {.inline.} =
|
|
writeFileStable(b.buf, path, onlyIfChanged)
|
|
|
|
proc cursorPool*(c: Cursor): Pool {.inline.} = nifcore.pool(c)
|
|
## The literals pool the cursor's buffer was built against. `ast2nif.nim`
|
|
## imports `nifcore` with `except pool` (to keep nifstreams' global `pool`
|
|
## var the writer uses), so the reader reaches a cursor's pool through here —
|
|
## needed once `bif`-loaded buffers carry their OWN fresh pool rather than the
|
|
## shared `icPool`.
|
|
|
|
proc freshModuleCopy(b: var IcBuilder): TokenBuf =
|
|
## Re-home `b.buf` into a PRIVATE, module-local pool via `addSubtree` (which
|
|
## re-interns only the literals/tags this buffer actually uses). `b.buf` is bound
|
|
## to the process-wide shared `icPool`/`icTags`; storing it directly would embed
|
|
## the WHOLE shared pool (correct but huge — see `bif.storeToFile`). The copy's
|
|
## fresh-pool reload reproduces ids verbatim (the bif fresh-pool INVARIANT).
|
|
result = createTokenBuf(b.buf.len, newPool(), newTagPool())
|
|
var c = b.buf.beginRead()
|
|
while c.hasMore:
|
|
addSubtree(result, c)
|
|
skip c
|
|
|
|
proc storeBif*(b: var IcBuilder; path: string; dottedSuffix: string) =
|
|
## Persist the buffer as a compact, self-contained binary NIF (`.bif`).
|
|
var fresh = freshModuleCopy(b)
|
|
bif.store(fresh, path, dottedSuffix)
|
|
|
|
proc storeBifStable*(b: var IcBuilder; path: string; dottedSuffix: string) =
|
|
## Content-stable `bif` write — the binary analogue of `writeFileStable`'s
|
|
## `onlyIfChanged`: only replace `path` when the encoded bytes differ, so an
|
|
## unchanged sidecar keeps its mtime and nifmake prunes the dependent rebuild
|
|
## cascade. Used for the iface/impl cookies + dep sidecars whose byte-stability
|
|
## gates incremental builds. (bif encoding is deterministic for a given buffer
|
|
## under fresh pools, so equal content ⇒ equal bytes.)
|
|
var fresh = freshModuleCopy(b)
|
|
let tmp = path & ".tmp"
|
|
bif.store(fresh, tmp, dottedSuffix)
|
|
let newBytes = readFile(tmp)
|
|
let oldBytes =
|
|
try: readFile(path)
|
|
except CatchableError: ""
|
|
if newBytes == oldBytes:
|
|
removeFile(tmp)
|
|
else:
|
|
moveFile(tmp, path)
|
|
|
|
# --- subtree splicing (shared pool, so a raw subtree copy is exact) ----------
|
|
|
|
proc addAll*(dest: var IcBuilder; src: var IcBuilder) =
|
|
## Append every top-level subtree of `src` into `dest` — the nifcore analogue
|
|
## of the old `dest.add wholeBuffer` splice.
|
|
var c = src.buf.beginRead()
|
|
while c.hasMore:
|
|
addSubtree(dest.buf, c)
|
|
skip c
|
|
|
|
proc addStmtsBody*(dest: var IcBuilder; src: var IcBuilder) =
|
|
## Append the BODY of a `(stmts . . <body> )` builder into `dest`, dropping the
|
|
## wrapper tag and its two leading dot slots (flags/type) — the nifcore
|
|
## analogue of the old `for i in 3 ..< content.len-1: dest.add content[i]`.
|
|
var c = src.buf.beginRead() # at (stmts
|
|
c.into:
|
|
skip c # flags dot
|
|
skip c # type dot
|
|
while c.hasMore:
|
|
addSubtree(dest.buf, c)
|
|
skip c
|
|
|
|
# --- cookie input: a line-info-free logical token list of the module ---------
|
|
# The cookie hashers (ast2nif) need a flat, ParRi-bearing, index-addressable
|
|
# view of the serialized module. nifcore has no ParRi kind and variable-width
|
|
# tokens, so we flatten the buffer here (in the clean nifcore world) into a
|
|
# neutral `CookieTok` list — no nifcore type crosses into ast2nif.
|
|
|
|
type
|
|
CookieKind* = enum
|
|
ckParLe, ckParRi, ckSym, ckSymDef, ckIdent, ckStr, ckInt, ckUInt, ckFloat, ckChar, ckDot
|
|
CookieTok* = object
|
|
kind*: CookieKind
|
|
tag*: string # ckParLe
|
|
name*: string # ckSym / ckSymDef
|
|
sym*: uint32 # ckSym / ckSymDef id (identity key)
|
|
str*: string # ckIdent / ckStr
|
|
ival*: int64
|
|
uval*: uint64
|
|
fval*: float64
|
|
cval*: uint32
|
|
|
|
proc flattenGo(c: var Cursor; b: TokenBuf; acc: var seq[CookieTok]) =
|
|
while c.hasMore:
|
|
case c.kind
|
|
of TagLit:
|
|
acc.add CookieTok(kind: ckParLe, tag: b.tags.tagName(c.cursorTagId))
|
|
c.into:
|
|
flattenGo(c, b, acc)
|
|
acc.add CookieTok(kind: ckParRi)
|
|
of Symbol:
|
|
acc.add CookieTok(kind: ckSym, name: symName(c, b.pool), sym: uint32(symId(c, b.pool)))
|
|
skip c
|
|
of SymbolDef:
|
|
acc.add CookieTok(kind: ckSymDef, name: symName(c, b.pool), sym: uint32(symId(c, b.pool)))
|
|
skip c
|
|
of Ident:
|
|
acc.add CookieTok(kind: ckIdent, str: strVal(c, b.pool)); skip c
|
|
of StrLit:
|
|
acc.add CookieTok(kind: ckStr, str: strVal(c, b.pool)); skip c
|
|
of IntLit:
|
|
acc.add CookieTok(kind: ckInt, ival: intVal(c)); skip c
|
|
of UIntLit:
|
|
acc.add CookieTok(kind: ckUInt, uval: uintVal(c)); skip c
|
|
of FloatLit:
|
|
acc.add CookieTok(kind: ckFloat, fval: floatVal(c)); skip c
|
|
of CharLit:
|
|
acc.add CookieTok(kind: ckChar, cval: uint32(ord(charLit(c)))); skip c
|
|
of DotToken:
|
|
acc.add CookieTok(kind: ckDot); skip c
|
|
else:
|
|
skip c # LineInfoLit / ExtendedSuffix ride on heads, never standalone
|
|
|
|
proc flattenForCookie*(b: var IcBuilder): seq[CookieTok] =
|
|
## Flatten the nifcore module buffer to the cookie hashers' flat token list.
|
|
result = newSeqOfCap[CookieTok](b.buf.len)
|
|
var cur = b.buf.beginRead()
|
|
flattenGo(cur, b.buf, result)
|
|
|
|
proc collectBifStrLits*(path: string): seq[string] =
|
|
## Read a small `(tag "s" "s" …)` bif sidecar (`semdeps`/`edges`) and return every
|
|
## string literal it holds, in order — the binary analogue of the old nifstreams
|
|
## scan that collected `StrLit`s. Keeps nifcore types out of `deps.nim`, which
|
|
## only needs the recorded string list.
|
|
##
|
|
## Uses `loadFromFile` (a full read into owned memory) rather than the mmap-backed
|
|
## `bif.load`, then CLOSES the handle. `bif.load` intentionally leaves the mapping
|
|
## resident for the process lifetime; for the `nim ic` driver that reads these
|
|
## sidecars while `nim m` children rewrite them, a lingering read mapping is a
|
|
## Windows sharing violation: the child's `open(path, fmWrite)` fails with
|
|
## `IOError: cannot open`. These sidecars are tiny, so the zero-copy mmap buys
|
|
## nothing here anyway.
|
|
result = @[]
|
|
var f = open(path, fmRead)
|
|
var m = bif.loadFromFile(f)
|
|
close(f)
|
|
var c = m.buf.beginRead()
|
|
while c.hasMore:
|
|
if c.kind == StrLit: result.add strVal(c)
|
|
inc c
|
|
|
|
proc writeSemDeps*(config: ConfigRef; thisModule: int32; importPaths: seq[string]) =
|
|
## Stage 1 spike: the nifcore port of `ast2nif.writeSemDeps`. Serializes the
|
|
## module's resolved direct imports as `(semdeps "path" ...)`. Byte-identical
|
|
## to the old writer (verified), so `nim ic` build graphs are unaffected.
|
|
let selfSuffix = modname(thisModule, config)
|
|
var paths = importPaths
|
|
sort paths
|
|
var dest = newIcBuilder(4 + 2*paths.len)
|
|
dest.openTag "semdeps"
|
|
for p in paths:
|
|
dest.addStrLit p
|
|
dest.closeTag()
|
|
let path = toGeneratedFile(config, AbsoluteFile(selfSuffix), ".s.deps.bif").string
|
|
storeBifStable(dest, path, "." & extractModuleSuffix(path))
|