mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 11:23:40 +00:00
Compare commits
2 Commits
pr_extend_
...
araq-ptype
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd7feabc97 | ||
|
|
bd0de5f9aa |
2
.github/workflows/ci_packages.yml
vendored
2
.github/workflows/ci_packages.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js'
|
||||
uses: actions/setup-node@v7
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
|
||||
2
.github/workflows/ci_publish.yml
vendored
2
.github/workflows/ci_publish.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js'
|
||||
uses: actions/setup-node@v7
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
|
||||
@@ -626,7 +626,14 @@ proc `[]`*(n: PType, i: int): PType {.inline.} =
|
||||
else:
|
||||
n.sonsImpl[i]
|
||||
|
||||
proc `[]=`*(n: PType, i: int; x: PType) {.inline.} =
|
||||
proc replaceSon*(n: PType, i: int; x: PType) {.inline.} =
|
||||
## The single low-level "replace son `i` in place" primitive. All in-place son
|
||||
## mutation funnels through here -- call sites go via `TypeBuilder.setSon`
|
||||
## (`typebuilders.nim`, the reopen/mutable-staging seam); this is its backing.
|
||||
## The `PType.[]=` operators used to do this inline; they were removed so that
|
||||
## son replacement is named and greppable, and the PType->NifCursor swap (where
|
||||
## this becomes a token rewrite over a thawed cursor) touches one proc, not
|
||||
## every caller.
|
||||
if n.state == Partial: loadType(n)
|
||||
if n.kind == tyProc and i > 0:
|
||||
assert n.nImpl[i] != nil and n.nImpl[i].sym != nil
|
||||
@@ -638,9 +645,9 @@ proc `[]`*(n: PType, i: BackwardsIndex): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
n[n.sonsImpl.len - i.int]
|
||||
|
||||
proc `[]=`*(n: PType, i: BackwardsIndex; x: PType) {.inline.} =
|
||||
proc replaceSon*(n: PType, i: BackwardsIndex; x: PType) {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
n[n.sonsImpl.len - i.int] = x
|
||||
replaceSon(n, n.sonsImpl.len - i.int, x)
|
||||
|
||||
proc getDeclPragma*(n: PNode): PNode =
|
||||
## return the `nkPragma` node for declaration `n`, or `nil` if no pragma was found.
|
||||
@@ -1165,7 +1172,7 @@ proc assignType*(dest, src: PType) =
|
||||
dest.sonsImpl[0] = src.sonsImpl[0]
|
||||
else:
|
||||
newSons(dest, src.len)
|
||||
for i in 0..<src.len: dest[i] = src[i]
|
||||
for i in 0..<src.len: replaceSon(dest, i, src[i])
|
||||
|
||||
proc copyType*(t: PType, idgen: IdGenerator, owner: PSym): PType =
|
||||
result = newType(t.kind, idgen, owner)
|
||||
|
||||
@@ -118,24 +118,13 @@ proc toClassSymId*(config: ConfigRef; typeId: ItemId): nifstreams.SymId =
|
||||
|
||||
type
|
||||
LineInfoWriter = object
|
||||
# `fileK`/`fileV` cache the most recently resolved (FileIndex -> FileId) pair,
|
||||
# faster than the hash table. `fileK` MUST be constructed at an invalid
|
||||
# sentinel (see `newLineInfoWriter`), never zero: `FileIndex(0)` is a real file
|
||||
# index, and `fileV` zero-inits to `FileId(0)` == `NoFile`, so a zero `fileK`
|
||||
# would make the first lookup of the module-at-index-0 falsely hit this cache
|
||||
# and return `NoFile` — silently dropping ALL of that module's line info.
|
||||
fileK: FileIndex
|
||||
fileK: FileIndex # remember the current pair, even faster than the hash table
|
||||
fileV: FileId
|
||||
tab: Table[FileIndex, FileId]
|
||||
revTab: Table[FileId, FileIndex] # reverse mapping for oldLineInfo
|
||||
man: LineInfoManager
|
||||
config: ConfigRef
|
||||
|
||||
proc newLineInfoWriter(config: ConfigRef): LineInfoWriter =
|
||||
# `fileK` starts invalid so the one-entry cache never collides with a real
|
||||
# `FileIndex(0)` (see the type's doc comment).
|
||||
LineInfoWriter(config: config, fileK: astli.InvalidFileIdx)
|
||||
|
||||
proc get(w: var LineInfoWriter; key: FileIndex): FileId =
|
||||
if w.fileK == key:
|
||||
result = w.fileV
|
||||
@@ -222,8 +211,9 @@ type
|
||||
decodedFileIndices: HashSet[FileIndex]
|
||||
locals: HashSet[ItemId] # track proc-local symbols
|
||||
inProc: int
|
||||
writtenTypes: seq[PType] # types sealed during a non-owning emit
|
||||
writtenSyms: seq[PSym] # reset afterwards so their owner can keep using them
|
||||
writtenTypes: seq[PType] # types sealed during this emit; under ideActive
|
||||
writtenSyms: seq[PSym] # they are reset to Complete afterwards so nimsuggest
|
||||
# can keep mutating its still-live query targets
|
||||
writtenPackages: HashSet[string]
|
||||
depSuffixes: HashSet[string] # module suffixes already emitted as `(import ...)` deps
|
||||
emittedBackendTypes: HashSet[(int32, int32)] # backend-local types already def'd this
|
||||
@@ -472,9 +462,6 @@ proc writeNode(w: var Writer; dest: var IcBuilder; n: PNode; forAst = false)
|
||||
proc writeType(w: var Writer; dest: var IcBuilder; typ: PType)
|
||||
proc writeSym(w: var Writer; dest: var IcBuilder; sym: PSym)
|
||||
|
||||
func restoresWrittenState(config: ConfigRef): bool {.inline.} =
|
||||
config.ideActive or optGenBif in config.globalOptions
|
||||
|
||||
proc writeLoc(w: var Writer; dest: var IcBuilder; loc: TLoc) =
|
||||
dest.addIdent toNifTag(loc.k)
|
||||
dest.addIdent toNifTag(loc.storage)
|
||||
@@ -571,7 +558,7 @@ proc writeType(w: var Writer; dest: var IcBuilder; typ: PType) =
|
||||
# module (or nowhere), leaving dangling references (e.g. `symbol has no
|
||||
# offset` for a `pointer` type whose itemId.module drifted away).
|
||||
typ.state = Sealed
|
||||
if restoresWrittenState(w.infos.config): w.writtenTypes.add typ
|
||||
if w.infos.config.ideActive: w.writtenTypes.add typ
|
||||
writeTypeDef(w, dest, typ)
|
||||
else:
|
||||
dest.addSymUse pool.syms.getOrIncl(nifTypeName(w, typ)), NoLineInfo
|
||||
@@ -736,7 +723,7 @@ proc writeSym(w: var Writer; dest: var IcBuilder; sym: PSym) =
|
||||
dest.addSymUse pool.syms.getOrIncl(w.toNifSymName(sym)), NoLineInfo
|
||||
elif shouldWriteSymDef(w, sym):
|
||||
sym.state = Sealed
|
||||
if restoresWrittenState(w.infos.config): w.writtenSyms.add sym
|
||||
if w.infos.config.ideActive: w.writtenSyms.add sym
|
||||
writeSymDef(w, dest, sym)
|
||||
else:
|
||||
# NIF has direct support for symbol references so we don't need to use a tag here,
|
||||
@@ -771,7 +758,7 @@ proc writeSymNode(w: var Writer; dest: var IcBuilder; n: PNode; sym: PSym) =
|
||||
else: shouldWriteSymDef(w, sym)
|
||||
if wantDef:
|
||||
if not sym.itemId.isBackendMinted and not isField: sym.state = Sealed
|
||||
if restoresWrittenState(w.infos.config): w.writtenSyms.add sym
|
||||
if w.infos.config.ideActive: w.writtenSyms.add sym
|
||||
if nodeTyp != n.sym.typImpl:
|
||||
dest.buildTree hiddenTypeTag, trLineInfo(w, n.info):
|
||||
writeType(w, dest, nodeTyp)
|
||||
@@ -896,16 +883,6 @@ var reexpModTag = registerTag("reexpmod")
|
||||
var offerTag = registerTag("offer")
|
||||
var typeOfferTag = registerTag("toffer")
|
||||
var modulesrcTag = registerTag("modulesrc")
|
||||
var expansionTag = registerTag("expansion")
|
||||
# `(sig <symUse @src>)*` — signature occurrences (parameter names and the symbols
|
||||
# in their type expressions). A semchecked routine's params are dropped from the
|
||||
# serialized AST (`skipParams`) and reconstructed from `s.typ`, which holds the
|
||||
# RESOLVED type — so the source parameter names and the written type names (e.g.
|
||||
# an alias `Stream`, not `StreamObj`) carry no position in the module body. Like
|
||||
# the `expansion` records, these are teed into the `deps` side-channel: the loader
|
||||
# skips the tag, but `idetools` scans every Symbol token, so goto-def / find-usages
|
||||
# work on signatures.
|
||||
var sigTag = registerTag("sig")
|
||||
# `(unusedid <int>)` — the module's first FREE itemId after the frontend
|
||||
# (`.s.bif`) or the lower stage (`.t.bif`). The backend seeds its per-module
|
||||
# sym/type counters here so freshly-minted backend ids (closure envs, RTTI
|
||||
@@ -946,51 +923,6 @@ proc registerNifAstTags*() =
|
||||
offerTag = registerTag("offer")
|
||||
typeOfferTag = registerTag("toffer")
|
||||
modulesrcTag = registerTag("modulesrc")
|
||||
expansionTag = registerTag("expansion")
|
||||
sigTag = registerTag("sig")
|
||||
|
||||
proc emitSigOccurrences(w: var Writer; n: PNode) =
|
||||
## Record every `nkSym` in a routine-signature subtree (parameter names and the
|
||||
## symbols inside their type expressions, incl. the return type) as a `(sig ...)`
|
||||
## occurrence in the `deps` side-channel, carrying the SOURCE position. Called on
|
||||
## the params AST that `skipParams` is about to drop, so tooling keeps a
|
||||
## positioned token for each signature symbol without changing the module body
|
||||
## the loader / backend actually consume.
|
||||
if n == nil: return
|
||||
if n.kind == nkSym:
|
||||
w.deps.addParLe sigTag, NoLineInfo
|
||||
w.deps.addSymUse pool.syms.getOrIncl(w.toNifSymName(n.sym)), trLineInfo(w, n.info)
|
||||
w.deps.addParRi
|
||||
else:
|
||||
for i in 0 ..< n.safeLen: emitSigOccurrences(w, n[i])
|
||||
|
||||
proc emitFwdDecl(w: var Writer; n: PNode; sym: PSym) =
|
||||
## A routine's forward declaration (`proc foo(...)` with no body, later followed
|
||||
## by `proc foo(...) = ...`) is a distinct top-level node, but the routine has a
|
||||
## SINGLE `sdef`, emitted at the IMPLEMENTATION site (`sym.infoImpl`) — so the
|
||||
## prototype's own position would otherwise vanish from the `.bif`. Tee it into
|
||||
## the `deps` side-channel as a POSITIONED `(sig @proto <symDef>)`: the loader
|
||||
## skips the `sig` tag (processTopLevel), but `idetools.scanDef` finds the
|
||||
## `SymbolDef` and reports the enclosing tag's line info — so a `--def` on a
|
||||
## forward-declared proc returns TWO results (prototype + implementation), which
|
||||
## is desired. Safe against symbol resolution: the loader rebuilds its name->pos
|
||||
## table from the CONTENT body (`buildPosIndex`, written after `deps`, last write
|
||||
## wins) so the real `sdef` still resolves; the extra on-disk index entry has no
|
||||
## resolution consumer. The prototype's signature symbols (param names and the
|
||||
## symbols in their type expressions) are teed too, positioned at the prototype,
|
||||
## exactly as `emitSigOccurrences` records them for the implementation.
|
||||
# The `SymbolDef` carries the prototype line info too (not just the enclosing
|
||||
# tag): `scanDef` reads the position from the tag, but pass-1 `findPos` matches
|
||||
# a token by its OWN line info, so this is what makes a query issued AT the
|
||||
# prototype position resolve the symbol.
|
||||
let protoInfo = trLineInfo(w, n[namePos].info)
|
||||
let sid = pool.syms.getOrIncl(w.toNifSymName(sym))
|
||||
w.deps.addParLe sigTag, protoInfo
|
||||
w.deps.addSymDef sid, protoInfo # scanDef reports this as a def
|
||||
w.deps.addSymUse sid, protoInfo # findPos (pass 1) / scanUses match a Symbol use
|
||||
w.deps.addParRi
|
||||
if sfFromGeneric notin sym.flagsImpl and paramsPos < n.safeLen:
|
||||
emitSigOccurrences(w, n[paramsPos])
|
||||
|
||||
proc writeNode(w: var Writer; dest: var IcBuilder; n: PNode; forAst = false) =
|
||||
if n == nil:
|
||||
@@ -1065,16 +997,7 @@ proc writeNode(w: var Writer; dest: var IcBuilder; n: PNode; forAst = false) =
|
||||
# For top-level named routines (not forAst), just write the symbol.
|
||||
# The full AST will be stored in the symbol's sdef.
|
||||
if not forAst and n[namePos].kind == nkSym:
|
||||
let s = n[namePos].sym
|
||||
writeSym(w, dest, s)
|
||||
# A forward declaration is a SECOND top-level node for `s` (body-less here;
|
||||
# the real body — and the lone sdef — lands at the implementation). Tee the
|
||||
# prototype's own position so goto-def / find-usages surface it as well.
|
||||
let impl = s.astImpl
|
||||
if n.safeLen > bodyPos and n[bodyPos].kind == nkEmpty and
|
||||
impl != nil and impl != n and
|
||||
impl.safeLen > bodyPos and impl[bodyPos].kind != nkEmpty:
|
||||
emitFwdDecl(w, n, s)
|
||||
writeSym(w, dest, n[namePos].sym)
|
||||
else:
|
||||
# Writing AST inside sdef or anonymous proc: write full structure
|
||||
inc w.inProc
|
||||
@@ -1095,13 +1018,6 @@ proc writeNode(w: var Writer; dest: var IcBuilder; n: PNode; forAst = false) =
|
||||
w.withNode dest, ast:
|
||||
for i in 0 ..< ast.len:
|
||||
if i == paramsPos and skipParams:
|
||||
# The dropped params still hold the source positions and the WRITTEN
|
||||
# type names (before alias/type resolution); tee them into the `deps`
|
||||
# side-channel for goto-def / find-usages (see `emitSigOccurrences`).
|
||||
# Skip generic INSTANCES: their param syms are instance-specific, and
|
||||
# the generic's own signature already records the source occurrences.
|
||||
if sfFromGeneric notin n[namePos].sym.flagsImpl:
|
||||
emitSigOccurrences(w, ast[i])
|
||||
# Parameters are redundant with s.typ.n (and re-emitting their syms
|
||||
# is dangerous for generic instances — we do not adapt the symbols
|
||||
# properly). Emit an `nkEmpty` placeholder rather than a dot token:
|
||||
@@ -1632,9 +1548,8 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
genericParamsCount: int]] = @[];
|
||||
typeOffers: seq[tuple[generic: PSym; inst: PType]] = @[];
|
||||
resolvedImportDeps: seq[FileIndex] = @[];
|
||||
firstUnusedId: int32 = 0;
|
||||
expansions: seq[(PSym, TLineInfo)] = @[]) =
|
||||
var w = Writer(infos: newLineInfoWriter(config), currentModule: thisModule)
|
||||
firstUnusedId: int32 = 0) =
|
||||
var w = Writer(infos: LineInfoWriter(config: config), currentModule: thisModule)
|
||||
w.deps = newIcBuilder(64)
|
||||
var content = newIcBuilder(300)
|
||||
|
||||
@@ -1711,17 +1626,6 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
w.deps.addStrLit toFullPath(config, FileIndex(thisModule))
|
||||
w.deps.addParRi
|
||||
|
||||
# Template/macro expansions leave no trace in the sem'checked AST, so record
|
||||
# each as `(expansion <symUse @call-site>)`: a `Symbol` use of the expanded
|
||||
# routine carrying the ORIGINAL call-site line info. The loader skips the tag
|
||||
# (processTopLevel), but `idetools` scans every `Symbol` token in the buffer,
|
||||
# so this restores "find usages / goto-def" for templates and macros.
|
||||
for (sym, info) in expansions:
|
||||
if sym == nil: continue
|
||||
w.deps.addParLe expansionTag, NoLineInfo
|
||||
w.deps.addSymUse pool.syms.getOrIncl(w.toNifSymName(sym)), trLineInfo(w, info)
|
||||
w.deps.addParRi
|
||||
|
||||
# Generic TYPE-instance OFFERS: the `tyGenericInst` types this module created
|
||||
# (e.g. `HashArray[8192, Gwei]`). Non-IC keeps ONE such instance in the global
|
||||
# `typeInstCache`, so a structural bound computed at the first instantiation
|
||||
@@ -1790,15 +1694,17 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
let s = op.sym
|
||||
if s.state != Sealed:
|
||||
s.state = Sealed
|
||||
if restoresWrittenState(config): w.writtenSyms.add s
|
||||
if config.ideActive: w.writtenSyms.add s
|
||||
writeSymDef w, dest, s
|
||||
|
||||
dest.addParRi()
|
||||
|
||||
# Nimsuggest and normal code generation reuse these symbols/types as live,
|
||||
# mutable targets. Sealing is only needed for intra-emit dedup; once the NIF
|
||||
# is built, un-seal them. The guard stays in force for a real `nim m` build.
|
||||
if restoresWrittenState(config):
|
||||
# nimsuggest reuses these symbols/types as live, mutable query targets (sem
|
||||
# re-runs, usage tracking, flag updates). Sealing is only needed for intra-emit
|
||||
# dedup; once the NIF is built, un-seal so suggest can keep mutating them
|
||||
# (matches `loadedState` loading Complete under ideActive). The `Sealed` guard
|
||||
# stays in force for a real `nim m`/`nim nifc` build.
|
||||
if config.ideActive:
|
||||
for s in w.writtenSyms:
|
||||
if s.state == Sealed: s.state = Complete
|
||||
for t in w.writtenTypes:
|
||||
@@ -1918,7 +1824,7 @@ type
|
||||
|
||||
proc createDecodeContext*(config: ConfigRef; cache: IdentCache): DecodeContext =
|
||||
## Supposed to be a global variable
|
||||
result = DecodeContext(infos: newLineInfoWriter(config), cache: cache)
|
||||
result = DecodeContext(infos: LineInfoWriter(config: config), cache: cache)
|
||||
|
||||
var loadStatsInit {.threadvar.}: int # 0=unknown 1=on 2=off
|
||||
var statsCtxPtr {.threadvar.}: ptr DecodeContext
|
||||
@@ -3345,14 +3251,6 @@ proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag]
|
||||
# self-identification record for the standalone include-graph scanner;
|
||||
# not needed by the loader, just skip past it.
|
||||
skip cur
|
||||
elif tagIs(cur, "expansion"):
|
||||
# template/macro expansion usage record for tooling (`idetools` scans it
|
||||
# as a `Symbol` use); the loader itself needs nothing from it.
|
||||
skip cur
|
||||
elif tagIs(cur, "sig"):
|
||||
# signature-symbol occurrence record for tooling (`idetools` scans it as a
|
||||
# `Symbol` use); the loader itself needs nothing from it.
|
||||
skip cur
|
||||
elif tagIs(cur, "implementation"):
|
||||
cont = false
|
||||
elif LoadFullAst in flags or tagIs(cur, toNifTag(nkLetSection)) or
|
||||
@@ -3414,7 +3312,7 @@ proc writeLoweredModule*(c: var DecodeContext; config: ConfigRef;
|
||||
# types/globals/params/locals stay Complete and emit real defs (the `.t.nif` is
|
||||
# the sole source the cg stage reads — no `.s.nif` fallback for them).
|
||||
sealLoadedRoutines(c)
|
||||
var w = Writer(infos: newLineInfoWriter(config), currentModule: thisModule)
|
||||
var w = Writer(infos: LineInfoWriter(config: config), currentModule: thisModule)
|
||||
w.deps = newIcBuilder(64)
|
||||
w.inProc = 1
|
||||
w.lowering = true
|
||||
@@ -3540,3 +3438,4 @@ when isMainModule:
|
||||
echo obj.name, " ", obj.module, " ", obj.count
|
||||
let objb = parseSymName("abcdef.0121")
|
||||
echo objb.name, " ", objb.module, " ", objb.count
|
||||
|
||||
|
||||
@@ -3143,12 +3143,6 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
|
||||
localError(p.config, e.info,
|
||||
"for --mm:arc|atomicArc|orc 'deepcopy' support has to be enabled with --deepcopy:on")
|
||||
|
||||
let typ = e[1].typ.skipTypes({tyVar, tyRef, tyGenericInst, tyTypeDesc,
|
||||
tyAlias, tyInferred, tySink, tyLent, tyOwned})
|
||||
if hasDisabledAsgn(p.module.g.graph, typ):
|
||||
localError(p.config, e.info,
|
||||
"'deepCopy' is not available for type <" & typeToString(typ) & ">")
|
||||
|
||||
let x = if e[1].kind in {nkAddr, nkHiddenAddr}: e[1][0] else: e[1]
|
||||
var a = initLocExpr(p, x)
|
||||
var b = initLocExpr(p, e[2])
|
||||
|
||||
@@ -509,7 +509,6 @@ proc parseCommand*(command: string): Command =
|
||||
of "nifc": cmdNifC # generate C from NIF files
|
||||
of "ic": cmdIc # generate .build.nif for nifmake
|
||||
of "icconfig": cmdIcConfig # produce the precompiled config artifact
|
||||
of "track": cmdTrack # IDE goto-def / find-usages over `nim ic`'s NIF output
|
||||
else: cmdUnknown
|
||||
|
||||
proc setCmd*(conf: ConfigRef, cmd: Command) =
|
||||
@@ -826,8 +825,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
localError(conf, info, "expected nim|cpp but found " & arg)
|
||||
of "compress":
|
||||
conf.globalOptions.incl optCompress
|
||||
of "genbif":
|
||||
processOnOffSwitchG(conf, {optGenBif}, arg, pass, info)
|
||||
of "g": # alias for --debugger:native
|
||||
conf.globalOptions.incl optCDebug
|
||||
conf.options.incl optLineDir
|
||||
@@ -1327,16 +1324,8 @@ proc processArgument*(pass: TCmdLinePass; p: OptParser;
|
||||
# support UNIX style filenames everywhere for portable build scripts:
|
||||
if config.projectName.len == 0:
|
||||
config.projectName = unixToNativePath(p.key)
|
||||
if config.cmd == cmdTrack:
|
||||
# `nim track PROJ --def:...`: unlike a normal command (where everything
|
||||
# after the project file is passed to the compiled program), `track`
|
||||
# accepts its IDE-query switches AFTER the project — the natural,
|
||||
# nimsuggest-like invocation form. So don't swallow the rest of the line
|
||||
# into `arguments`; keep parsing the remaining tokens as switches.
|
||||
result = false
|
||||
else:
|
||||
config.arguments = cmdLineRest(p)
|
||||
result = true
|
||||
config.arguments = cmdLineRest(p)
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
inc argsCount
|
||||
|
||||
@@ -590,98 +590,6 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
|
||||
elif t.kind == ParRi: dec depth
|
||||
t = next(s)
|
||||
|
||||
proc collectIncludeNames(depsPath: string; names: var seq[string]) =
|
||||
## Lightweight scan of a `.deps.nif` prelude: collect the raw path text of
|
||||
## every entry inside an `(include ...)` node (idents like `semexprs`, string
|
||||
## literals like `"system/mmdisp"`, and the leaves of `a/b` path infixes).
|
||||
## Liberal by design — it also picks up entries under a statically-false
|
||||
## `(when ...)`; that is harmless for the only caller (`includerSbifs`), whose
|
||||
## over-collection just costs an extra, result-free bif scan downstream.
|
||||
if not fileExists(depsPath): return
|
||||
var s = nifstreams.open(depsPath)
|
||||
defer: nifstreams.close(s)
|
||||
discard processDirectives(s.r)
|
||||
var depth = 0
|
||||
var includeDepth = 0 # the `depth` at which the current `(include` opened; 0 = not inside one
|
||||
var t = next(s)
|
||||
while t.kind != EofToken:
|
||||
case t.kind
|
||||
of ParLe:
|
||||
inc depth
|
||||
if includeDepth == 0 and pool.tags[t.tagId] == "include":
|
||||
includeDepth = depth
|
||||
of ParRi:
|
||||
if includeDepth != 0 and depth == includeDepth:
|
||||
includeDepth = 0
|
||||
dec depth
|
||||
of Ident, StringLit:
|
||||
if includeDepth != 0:
|
||||
names.add pool.strings[t.litId]
|
||||
else: discard
|
||||
t = next(s)
|
||||
|
||||
proc entryStemBase(roots: seq[string]; name: string): (string, string) =
|
||||
## Resolve include entry `name` to (deps-stem, base-name); ("","") if unfound.
|
||||
for r in roots:
|
||||
let p = r / name.addFileExt("nim")
|
||||
if fileExists(p):
|
||||
return (moduleSuffix(p, []), splitFile(p).name)
|
||||
result = ("", "")
|
||||
|
||||
proc includerSbifs*(conf: ConfigRef; targetFile: AbsoluteFile): seq[string] =
|
||||
## For an include file `targetFile`, return the `.s.bif` paths of every module
|
||||
## that includes it — directly OR transitively (following the include chain
|
||||
## `module -> incA -> incB -> targetFile`). `nim track` uses this to avoid
|
||||
## loading and scanning every module bif: an include file has no bif of its
|
||||
## own, so its type-checked tokens live in the *including* module's bif. Only
|
||||
## the small `.deps.nif` preludes are read here, never a `.s.bif`.
|
||||
const depsExt = ".deps.nif"
|
||||
let nc = getNimcacheDir(conf).string
|
||||
|
||||
# Candidate roots for resolving an `(include X)` entry to a real file, so its
|
||||
# module suffix (== its own deps-file stem) can be computed. Include entries
|
||||
# carry any sub-path (`system/mmdisp`), so the file's *directory* roots suffice:
|
||||
# the target's own dir, the project dir, and the search paths cover the
|
||||
# compiler, the stdlib and typical single-tree projects.
|
||||
var roots: seq[string] = @[parentDir(targetFile.string)]
|
||||
if conf.projectPath.string.len > 0: roots.add conf.projectPath.string
|
||||
for sp in conf.searchPaths: roots.add sp.string
|
||||
|
||||
# One pass over every prelude builds the reverse include graph, keyed by base
|
||||
# file name: `includedBy[b]` = deps stems whose owner directly `include`s a
|
||||
# file named `b`. `stemBase` maps an include-only file's deps stem back to its
|
||||
# own base name, so the walk can climb through nested includes.
|
||||
var includedBy = initTable[string, seq[string]]()
|
||||
var stemBase = initTable[string, string]()
|
||||
for depsPath in walkFiles(nc / "*" & depsExt):
|
||||
let base = extractFilename(depsPath)
|
||||
if base.endsWith(".p" & depsExt): continue # `.p.deps.nif` twin
|
||||
let ownerStem = base[0 ..< base.len - depsExt.len]
|
||||
var names: seq[string] = @[]
|
||||
collectIncludeNames(depsPath, names)
|
||||
for n in names:
|
||||
let (childStem, childBase) = entryStemBase(roots, n)
|
||||
if childBase.len == 0: continue
|
||||
includedBy.mgetOrPut(childBase, @[]).add ownerStem
|
||||
stemBase[childStem] = childBase # this child's stem -> its base name
|
||||
|
||||
# Walk UP from the target: a deps stem that includes the current base name is
|
||||
# either a module (has a `.s.bif` -> collect it) or itself an include file
|
||||
# (recurse via its own base name).
|
||||
result = @[]
|
||||
var seenBase = initHashSet[string]()
|
||||
var work = @[splitFile(targetFile.string).name]
|
||||
while work.len > 0:
|
||||
let b = work.pop()
|
||||
if seenBase.containsOrIncl(b): continue
|
||||
for stem in includedBy.getOrDefault(b):
|
||||
let sbif = nc / stem & ".s.bif"
|
||||
if fileExists(sbif):
|
||||
if sbif notin result: result.add sbif # module owner
|
||||
else:
|
||||
let ob = stemBase.getOrDefault(stem) # include-only owner: climb higher
|
||||
if ob.len > 0: work.add ob
|
||||
|
||||
proc traverseDeps(c: var DepContext; pair: FilePair; current: Node) =
|
||||
## Process a module: run nifler and read deps
|
||||
if not runNifler(c, pair.nimFile):
|
||||
@@ -1226,12 +1134,8 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string
|
||||
|
||||
b.endTree() # stmts
|
||||
|
||||
proc commandIc*(conf: ConfigRef; frontendOnly = false) =
|
||||
## Main entry point for `nim ic`. With `frontendOnly` (used by `nim track` for
|
||||
## IDE queries) it runs only Phase 1 — the incremental nifler + `nim m`
|
||||
## frontend that writes every module's `.s.bif` — and skips the whole-program
|
||||
## backend (`nim nifc` -> C -> link), which a goto-def / find-usages scan does
|
||||
## not need.
|
||||
proc commandIc*(conf: ConfigRef) =
|
||||
## Main entry point for `nim ic`
|
||||
when not defined(nimKochBootstrap):
|
||||
let nifler = findNifler()
|
||||
if nifler.len == 0:
|
||||
@@ -1371,12 +1275,10 @@ proc commandIc*(conf: ConfigRef; frontendOnly = false) =
|
||||
if nifmake.len == 0:
|
||||
rawMessage(conf, hintSuccess, "run:" & " nifmake run" & parallel & " " & buildFile)
|
||||
# without nifmake we can only print the manual commands; emit the
|
||||
# backend's too (best effort — discovery cannot run) and stop. An IDE
|
||||
# query (`frontendOnly`) needs no backend, so skip it there.
|
||||
if not frontendOnly:
|
||||
let backendFile = generateBackendBuildFile(c, forwardedArgs)
|
||||
rawMessage(conf, hintSuccess, "generated: " & backendFile)
|
||||
rawMessage(conf, hintSuccess, "run:" & " nifmake run" & parallel & " " & backendFile)
|
||||
# backend's too (best effort — discovery cannot run) and stop.
|
||||
let backendFile = generateBackendBuildFile(c, forwardedArgs)
|
||||
rawMessage(conf, hintSuccess, "generated: " & backendFile)
|
||||
rawMessage(conf, hintSuccess, "run:" & " nifmake run" & parallel & " " & backendFile)
|
||||
return
|
||||
let cmd = quoteShell(nifmake) & " run" & parallel & " " & quoteShell(buildFile)
|
||||
rawMessage(conf, hintExecuting, cmd)
|
||||
@@ -1420,9 +1322,7 @@ proc commandIc*(conf: ConfigRef; frontendOnly = false) =
|
||||
# Phase 2 — backend (whole-program `nim nifc`), run once over the now-final
|
||||
# graph. Kept a separate nifmake run so backend rebuilds are decided purely
|
||||
# by nifmake's input mtimes, independent of frontend discovery.
|
||||
# An IDE query (`frontendOnly`) stops after Phase 1: the `.s.bif` it scans
|
||||
# are all produced by the frontend; codegen + link would be wasted work.
|
||||
if frontendOk and not frontendOnly:
|
||||
if frontendOk:
|
||||
let backendFile = generateBackendBuildFile(c, forwardedArgs)
|
||||
rawMessage(conf, hintSuccess, "generated: " & backendFile)
|
||||
let cmd = quoteShell(nifmake) & " run" & parallel & " " & quoteShell(backendFile)
|
||||
|
||||
@@ -260,26 +260,17 @@ proc ensureIcConfig*(conf: ConfigRef) =
|
||||
if not fileExists(outPath) or sourcesChanged(outPath):
|
||||
createDir(cacheDir)
|
||||
# Re-invoke ourselves as the config producer: reuse this process's command
|
||||
# line, dropping the command argument (`ic`/`track`) in favour of `icconfig`
|
||||
# and the explicit output path. Every switch must land BEFORE the project
|
||||
# file, because anything after the project is swallowed into
|
||||
# `config.arguments` by `cmdLineRest` (and a non-empty `arguments` without
|
||||
# `--run` is a hard error). Callers may legitimately put switches after the
|
||||
# project — `nim track PROJ --def:...` — so we re-order rather than replay
|
||||
# verbatim: all `-`-prefixed switches first (in encounter order), then the
|
||||
# non-switch project token(s). The producer re-reads `nim.cfg` itself.
|
||||
# line, dropping the command argument (`ic`) in favour of `icconfig` and the
|
||||
# explicit output path, both BEFORE the project file (anything after the
|
||||
# project is swallowed into `config.arguments` by `cmdLineRest`). The
|
||||
# producer re-reads `nim.cfg` itself.
|
||||
var pargs = @["icconfig", "--icConfigOut:" & outPath]
|
||||
var rest: seq[string] = @[]
|
||||
var droppedCmd = false
|
||||
for a in commandLineParams():
|
||||
if a.len == 0: continue
|
||||
if a[0] == '-':
|
||||
pargs.add a
|
||||
elif not droppedCmd:
|
||||
droppedCmd = true # drop the original command token (`ic`/`track`)
|
||||
if not droppedCmd and a.len > 0 and a[0] != '-':
|
||||
droppedCmd = true # drop the original command token (`ic`)
|
||||
else:
|
||||
rest.add a # project file (and any further non-switch tokens) go last
|
||||
for a in rest: pargs.add a
|
||||
pargs.add a
|
||||
let p = startProcess(getAppFilename(), args = pargs,
|
||||
options = {poStdErrToStdOut})
|
||||
let outp = p.outputStream.readAll()
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2026 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## NIF-based goto-definition / find-all-usages for `nim track`.
|
||||
##
|
||||
## This is the mainline-Nim port of nimony's `idetools.nim`. It answers a
|
||||
## `--def:FILE,LINE,COL` / `--usages:FILE,LINE,COL` query by *scanning the
|
||||
## `.s.bif` files* (binary NIF, see `dist/nimony/src/lib/bif.nim`) that the
|
||||
## preceding `nim ic` frontend (`nim track`) emitted into the nimcache directory
|
||||
## — NOT by re-running sem. NIF distinguishes a definition (`SymbolDef` token) from a use
|
||||
## (`Symbol` token) syntactically, so goto-def / find-uses become plain token
|
||||
## scans over type-checked NIF, which is more reliable than the classic PSym
|
||||
## engine because generics and macros are type-checked in the NIF too.
|
||||
##
|
||||
## Two passes (mirroring nimony's `usages`):
|
||||
## 1. Load the queried module's `.s.bif` and find the `Symbol`/`SymbolDef`
|
||||
## token whose line info + identifier length contains `conf.m.trackPos`.
|
||||
## That yields the mangled symbol NAME and whether it is global (>= 2 dots).
|
||||
## 2. `--usages`: emit every `Symbol` (use) token; `--def`: every `SymbolDef`.
|
||||
## A global symbol is scanned across every module `.s.bif`; a local one only
|
||||
## within the queried module.
|
||||
##
|
||||
## IMPORTANT porting note: `bif.load` mints FRESH per-file pools, so a `SymId`
|
||||
## from module A's buffer is meaningless in module B's. The cross-module match is
|
||||
## therefore by the mangled NAME string, never by `SymId` (nimony can compare ids
|
||||
## because it parses every text NIF into one shared global pool; we cannot).
|
||||
|
||||
import std / [os, strutils, sets]
|
||||
import options, msgs, pathutils
|
||||
import lineinfos as astli
|
||||
import ast2nif # toNifFilename
|
||||
from deps import includerSbifs # deps-guided include-file lookup
|
||||
import "../dist/nimony/src/lib/nifcore"
|
||||
from "../dist/nimony/src/lib" / bif import load, BifModule, containsSym
|
||||
|
||||
proc identLen(name: string): int =
|
||||
## Length of the displayed identifier: the run before the first `.` of a
|
||||
## mangled NIF name (`ident.disamb[.moduleSuffix]`). Bounds the column match.
|
||||
let d = name.find('.')
|
||||
result = if d < 0: name.len else: d
|
||||
|
||||
proc isGlobalName(name: string): bool =
|
||||
## A global symbol carries `ident.disamb.moduleSuffix` (>= 2 dots); a local at
|
||||
## most `ident.disamb` (<= 1 dot). `moduleSuffix` is a dot-free hash, so a raw
|
||||
## dot count is equivalent to nifbuilder's suffix-compressed test for our use.
|
||||
var dots = 0
|
||||
for i in 1 ..< name.len:
|
||||
if name[i] == '.': inc dots
|
||||
result = dots >= 2
|
||||
|
||||
proc posMatch(c: Cursor; conf: ConfigRef; target: TLineInfo; tokenLen: int): bool =
|
||||
## True when `target` (the queried position) falls within the identifier span
|
||||
## of the Symbol/SymbolDef token at `c`. Mirrors nimony's `lineInfoMatch`; the
|
||||
## filename is resolved through the loaded buffer's own pool (fresh per file),
|
||||
## then mapped to a `FileIndex` exactly like `ast2nif.oldLineInfo`.
|
||||
let li = rawLineInfo(c)
|
||||
if not li.isValid: return false
|
||||
if li.line.int != target.line.int: return false
|
||||
let f = fileInfoIdx(conf, AbsoluteFile lineInfoFile(c))
|
||||
if f != target.fileIndex: return false
|
||||
if target.col.int < li.col.int: return false
|
||||
if target.col.int > li.col.int + tokenLen: return false
|
||||
result = true
|
||||
|
||||
const sep = '\t'
|
||||
|
||||
proc formatSuggest(s: Suggest): string =
|
||||
## Reproduce `suggest.$Suggest` for the `ideDef`/`ideUse` sections without
|
||||
## importing `suggest` (which would create an import cycle). Layout:
|
||||
## `section⭾symkind⭾qualifiedPath⭾forth⭾filePath⭾line⭾column⭾⭾quality`.
|
||||
## symkind is always `skUnknown` here — the raw NIF scan has no PSym to give a
|
||||
## real kind (like nimony's `foundSymbol`, which leaves it empty).
|
||||
result = $s.section
|
||||
result.add sep
|
||||
result.add "skUnknown"
|
||||
result.add sep
|
||||
if s.qualifiedPath.len != 0:
|
||||
result.add s.qualifiedPath.join(".")
|
||||
result.add sep
|
||||
result.add s.forth
|
||||
result.add sep
|
||||
result.add s.filePath
|
||||
result.add sep
|
||||
result.add $s.line
|
||||
result.add sep
|
||||
result.add $s.column
|
||||
result.add sep # empty doc field (docgen is off outside nimsuggest)
|
||||
if s.version == 0 or s.version == 3:
|
||||
result.add sep
|
||||
result.add $s.quality
|
||||
|
||||
proc emit(conf: ConfigRef; c: Cursor; section: IdeCmd; name: string;
|
||||
seen: var HashSet[string]) =
|
||||
## Report one hit as a nimsuggest-compatible result (routed through the
|
||||
## structured-output hook / `--stdout`). We only have the mangled name + line
|
||||
## info from the raw NIF, so symkind/type are left empty — like nimony's
|
||||
## `foundSymbol`. `seen` deduplicates: the same source location can back
|
||||
## several NIF `Symbol` tokens (e.g. a call argument re-emitted in a lowered
|
||||
## form), which must surface as one hit.
|
||||
let li = rawLineInfo(c)
|
||||
if not li.isValid: return
|
||||
let key = $section.int & ":" & lineInfoFile(c) & ":" & $li.line.int & ":" & $li.col.int
|
||||
if seen.containsOrIncl(key):
|
||||
return # already reported this location for this section
|
||||
let s = Suggest(section: section,
|
||||
qualifiedPath: @[name[0 ..< identLen(name)]],
|
||||
filePath: lineInfoFile(c),
|
||||
line: li.line.int,
|
||||
column: li.col.int,
|
||||
tokenLen: identLen(name),
|
||||
forth: "",
|
||||
symkind: 0'u8,
|
||||
quality: 100,
|
||||
version: conf.suggestVersion)
|
||||
if conf.suggestionResultHook != nil:
|
||||
conf.suggestionResultHook(s)
|
||||
else:
|
||||
conf.suggestWriteln(formatSuggest(s))
|
||||
|
||||
proc tokenSymId(c: Cursor): SymId {.inline.} =
|
||||
## SymId (in the cursor's own per-file pool) of a `Symbol`/`SymbolDef` token,
|
||||
## or `SymId(0)` for an inline-encoded one — which is never our search target:
|
||||
## a mangled name (`ident.disamb.suffix`) is always longer than
|
||||
## `StrInlineMaxLen`, so every occurrence of the symbol we look for is stored by
|
||||
## pool id, decoded here with a shift and no string materialization.
|
||||
if isInlineLit(c): SymId(0) else: SymId(combinedPayload(c) shr 1)
|
||||
|
||||
template symMatches(c: Cursor): bool =
|
||||
## True when the token at `c` is the searched symbol. The fast path is a pure
|
||||
## integer compare against `targetSym` (the symbol's id in THIS module's pool,
|
||||
## resolved once per file by the caller). `targetSym == 0` means the name is not
|
||||
## representable as a pool id (a rare <=3-byte local): fall back to a string
|
||||
## compare, correct for both inline and pooled encodings.
|
||||
(if targetSym != SymId(0): tokenSymId(c) == targetSym else: symName(c) == targetName)
|
||||
|
||||
proc scanUses(conf: ConfigRef; m: var BifModule; targetSym: SymId; targetName: string;
|
||||
seen: var HashSet[string]) =
|
||||
## `--usages`: report every `Symbol` (use) occurrence with valid line info.
|
||||
if m.buf.len == 0: return
|
||||
var c = m.buf.beginRead()
|
||||
while c.hasMore:
|
||||
if c.kind == Symbol and symMatches(c) and rawLineInfo(c).isValid:
|
||||
emit(conf, c, ideUse, targetName, seen)
|
||||
inc c
|
||||
c.endRead()
|
||||
|
||||
proc scanDef(conf: ConfigRef; m: var BifModule; targetSym: SymId; targetName: string;
|
||||
seen: var HashSet[string]) =
|
||||
## `--def`: report the declaration of the target symbol if this module owns it
|
||||
## (has its `SymbolDef`). The `SymbolDef` token itself carries no line info; the
|
||||
## declaration location lives on the *enclosing tag* (e.g. `(sd @file:line:col`,
|
||||
## like `bif.buildIndex`'s `mostRecentTagPos`). When that tag has no line info
|
||||
## either, fall back to the declaration-site `Symbol` occurrence — but only in
|
||||
## the owning module, so a plain user of the symbol is never reported as a def.
|
||||
if m.buf.len == 0: return
|
||||
var c = m.buf.beginRead()
|
||||
var mostRecentTagPos = 0
|
||||
var sawDef = false
|
||||
var emitted = false
|
||||
var fallbackPos = -1
|
||||
while c.hasMore:
|
||||
case c.kind
|
||||
of TagLit:
|
||||
mostRecentTagPos = cursorToPosition(m.buf, c)
|
||||
inc c
|
||||
of SymbolDef:
|
||||
if symMatches(c):
|
||||
sawDef = true
|
||||
var tc = cursorAt(m.buf, mostRecentTagPos)
|
||||
if rawLineInfo(tc).isValid:
|
||||
emit(conf, tc, ideDef, targetName, seen)
|
||||
emitted = true
|
||||
tc.endRead()
|
||||
inc c
|
||||
of Symbol:
|
||||
if fallbackPos < 0 and symMatches(c) and rawLineInfo(c).isValid:
|
||||
fallbackPos = cursorToPosition(m.buf, c)
|
||||
inc c
|
||||
else:
|
||||
inc c
|
||||
c.endRead()
|
||||
if sawDef and not emitted and fallbackPos >= 0:
|
||||
var fc = cursorAt(m.buf, fallbackPos)
|
||||
emit(conf, fc, ideDef, targetName, seen)
|
||||
fc.endRead()
|
||||
|
||||
proc scanBuf(conf: ConfigRef; m: var BifModule; section: IdeCmd;
|
||||
targetSym: SymId; targetName: string; seen: var HashSet[string]) =
|
||||
## Emit hits for the target symbol in `m` per the query kind. `ideDus`
|
||||
## (`--defusages`) reports both the definition and every usage.
|
||||
if section in {ideDef, ideDus}:
|
||||
scanDef(conf, m, targetSym, targetName, seen)
|
||||
if section in {ideUse, ideDus}:
|
||||
scanUses(conf, m, targetSym, targetName, seen)
|
||||
|
||||
proc findPos(conf: ConfigRef; m: var BifModule; target: TLineInfo;
|
||||
foundName: var string): bool =
|
||||
## Scan `m` for the `Symbol`/`SymbolDef` token covering the queried position
|
||||
## `target` and set `foundName` to its mangled name. Returns true on a hit.
|
||||
if m.buf.len == 0: return false
|
||||
var c = m.buf.beginRead()
|
||||
result = false
|
||||
while c.hasMore:
|
||||
let k = c.kind
|
||||
if k == Symbol or k == SymbolDef:
|
||||
let nm = symName(c)
|
||||
if posMatch(c, conf, target, identLen(nm)):
|
||||
foundName = nm
|
||||
result = true
|
||||
break
|
||||
inc c
|
||||
c.endRead()
|
||||
|
||||
proc runIdeQuery*(conf: ConfigRef) =
|
||||
## Entry point: called from `main.nim` after `commandCheck` when a
|
||||
## `--def`/`--usages` query is active. Assumes the check just emitted the
|
||||
## project's `.s.bif` files into `getNimcacheDir(conf)`.
|
||||
let section = conf.ideCmd
|
||||
if section notin {ideDef, ideUse, ideDus}: return
|
||||
let target = conf.m.trackPos
|
||||
if target.fileIndex.int32 < 0: return
|
||||
|
||||
# Pass 1: position -> symbol. Try the queried file's own module bif first (the
|
||||
# fast path when the position is inside a real module). An include file has no
|
||||
# module bif of its own — its tokens live in the *including* module's bif with
|
||||
# include-file line info — so when the direct lookup misses, consult the
|
||||
# `.deps.nif` preludes (`includerSbifs`) to load only the module(s) that
|
||||
# include the queried file (directly or transitively), never every bif in the
|
||||
# nimcache. `ownerFile` is the bif that owns the hit.
|
||||
let modFile = toNifFilename(conf, target.fileIndex)
|
||||
var foundName = ""
|
||||
var ownerFile = ""
|
||||
if fileExists(modFile):
|
||||
var qm = load(modFile)
|
||||
if findPos(conf, qm, target, foundName):
|
||||
ownerFile = modFile
|
||||
if foundName.len == 0:
|
||||
for cand in includerSbifs(conf, toFullPath(conf, target.fileIndex).AbsoluteFile):
|
||||
if cand == modFile: continue
|
||||
var m = load(cand)
|
||||
if findPos(conf, m, target, foundName):
|
||||
ownerFile = cand
|
||||
break
|
||||
if foundName.len == 0: return
|
||||
|
||||
# Pass 2: emit definition / usages. `seen` spans every module so a location is
|
||||
# reported once even when scanned across the whole nimcache.
|
||||
#
|
||||
# Cross-file matching is by SymId, not by decoding every token's name. Two
|
||||
# filters keep it cheap:
|
||||
# 1. `bif.containsSym` — a sym-table-only probe that reads just the small
|
||||
# trailing pools, NOT the token block or any `BiTable`. A module that never
|
||||
# references the symbol is rejected here without a full `load` (no pools
|
||||
# built, no token block mapped) — so a query whose symbol lives in a few
|
||||
# modules no longer pays to load the whole nimcache.
|
||||
# 2. For a module that does contain it, `bif.load` mints a fresh per-file pool,
|
||||
# so the name is resolved to THIS file's SymId once via `getKeyId`; the scan
|
||||
# then compares integer ids per token instead of materializing a string for
|
||||
# each (see `symMatches`).
|
||||
var seen = initHashSet[string]()
|
||||
if isGlobalName(foundName):
|
||||
for f in walkFiles((getNimcacheDir(conf).string) / "*.s.bif"):
|
||||
if not containsSym(f, foundName): continue
|
||||
var m = load(f)
|
||||
let tid = m.buf.pool.syms.getKeyId(foundName)
|
||||
if tid != SymId(0):
|
||||
scanBuf(conf, m, section, tid, foundName, seen)
|
||||
else:
|
||||
# Local symbol: its mangled name is not unique across modules, so restrict
|
||||
# the scan to the module it lives in (the one that owns the queried position).
|
||||
var qm = load(ownerFile)
|
||||
let tid = qm.buf.pool.syms.getKeyId(foundName)
|
||||
scanBuf(conf, qm, section, tid, foundName, seen)
|
||||
@@ -144,18 +144,19 @@ proc lowerSwap*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PNod
|
||||
result.add newFastAsgnStmt(n[2], tempAsNode)
|
||||
|
||||
proc createObj*(g: ModuleGraph; idgen: IdGenerator; owner: PSym, info: TLineInfo; final=true): PType =
|
||||
result = newType(tyObject, idgen, owner)
|
||||
var b = openType(tyObject, idgen, owner)
|
||||
if final:
|
||||
rawAddSon(result, nil)
|
||||
incl result, tfFinal
|
||||
b.addRaw nil
|
||||
b.incl tfFinal
|
||||
else:
|
||||
rawAddSon(result, getCompilerProc(g, "RootObj").typ)
|
||||
result.n = newNodeI(nkRecList, info)
|
||||
b.addRaw getCompilerProc(g, "RootObj").typ
|
||||
b.setN newNodeI(nkRecList, info)
|
||||
let s = newSym(skType, getIdent(g.cache, "Env_" & toFilename(g.config, info) & "_" & $owner.name.s),
|
||||
idgen, owner, info, owner.options)
|
||||
incl s.flagsImpl, sfAnon
|
||||
b.setSym s
|
||||
result = finish b
|
||||
s.typ = result
|
||||
result.sym = s
|
||||
|
||||
template fieldCheck {.dirty.} =
|
||||
when false:
|
||||
@@ -163,13 +164,6 @@ template fieldCheck {.dirty.} =
|
||||
echo "missed field ", field.name.s
|
||||
writeStackTrace()
|
||||
|
||||
proc rawAddField*(obj: PType; field: PSym) =
|
||||
assert field.kind == skField
|
||||
field.position = obj.n.len
|
||||
obj.n.add newSymNode(field)
|
||||
propagateToOwner(obj, field.typ)
|
||||
fieldCheck()
|
||||
|
||||
proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
|
||||
# returns a[].field as a node
|
||||
assert field.kind == skField
|
||||
@@ -238,40 +232,70 @@ proc lookupCapturedField(n: PNode, s: PSym): PSym =
|
||||
result = n.sym
|
||||
else: discard
|
||||
|
||||
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
|
||||
# Idempotent w.r.t. the captured symbol (mirrors `addUniqueField`): re-lifting
|
||||
# a LOADED routine re-derives its transformed body (never serialized under IC)
|
||||
# and re-captures the same locals, but the env object loaded from the NIF
|
||||
# already carries their fields. Re-adding would duplicate the field and, worse,
|
||||
# mutate a Sealed loaded type via `propagateToOwner` (the `t.state != Sealed`
|
||||
# crash). Return the existing field instead.
|
||||
let existing = lookupInRecord(obj.n, s.itemId)
|
||||
if existing != nil:
|
||||
return existing
|
||||
# Re-lifting a LOADED routine during a VM transform (its transformed body is
|
||||
# re-derived per process, never serialized) re-captures the same locals, but
|
||||
# for a macro-generated gensym (e.g. libp2p `p2pProtocolBackendImpl`'s
|
||||
# `msgVar`) its process-local id diverges from the one baked into the loaded
|
||||
# env field, so the id match above misses. Reuse the existing same-named field
|
||||
# rather than appending a divergent duplicate, which keeps the re-derived
|
||||
# closure consistent (else a stale `:env` access reaches `cannotEval`).
|
||||
# Confined to a loaded (Sealed) env: in a freshly built env ids are consistent,
|
||||
# and two distinct same-named captures legitimately get distinct fields there.
|
||||
if obj.state == Sealed:
|
||||
let byName = lookupCapturedField(obj.n, s)
|
||||
if byName != nil:
|
||||
return byName
|
||||
# Genuinely new field. Under IC the env may be a loaded Sealed type whose
|
||||
# transform-time mutation is process-local (the body is discarded after the
|
||||
# macro runs), so downgrade it to mutable instead of crashing on
|
||||
# `t.state != Sealed` (mirrors `markAsClosure`).
|
||||
type
|
||||
ObjectBuilder* = object
|
||||
## Extends an existing object type with record fields -- the deferred
|
||||
## object-BODY counterpart to `typebuilders.TypeBuilder`. The object's
|
||||
## identity is fixed (a shell from `createObj` or a type loaded from NIF);
|
||||
## only its `nkRecList` body grows, possibly after thawing a loaded Sealed
|
||||
## type. Lives here rather than in `typebuilders.nim` because it needs the
|
||||
## record-walk reuse lookups above; hoist it once those move.
|
||||
## See `doc/ic_type_body_builder.md` for the NIF-cursor migration story.
|
||||
obj {.cursor.}: PType
|
||||
cache {.cursor.}: IdentCache
|
||||
idgen {.cursor.}: IdGenerator
|
||||
|
||||
proc reopenObject*(obj: PType; cache: IdentCache; idgen: IdGenerator): ObjectBuilder {.inline.} =
|
||||
## Positions a builder to append fields to `obj`, keeping its identity. Does
|
||||
## not thaw yet: the idempotency lookups must observe the pre-thaw `Sealed`
|
||||
## state first (see `findField`).
|
||||
ObjectBuilder(obj: obj, cache: cache, idgen: idgen)
|
||||
|
||||
proc findField*(b: ObjectBuilder; s: PSym; byName: bool): PSym =
|
||||
## The idempotency lookup, load-bearing for correctness (not a fast path):
|
||||
## re-lifting a LOADED routine re-derives its transformed body per process and
|
||||
## re-captures the same locals, but the loaded env already carries their
|
||||
## fields -- re-adding would duplicate and mutate Sealed memory.
|
||||
##
|
||||
## By derived item id first. Then, for a loaded (`Sealed`) body and when
|
||||
## `byName`, by the stable name+position key: a macro-generated gensym (e.g.
|
||||
## libp2p `p2pProtocolBackendImpl`'s `msgVar`) has a process-local id that
|
||||
## diverges from the one baked into the loaded env field, so the id match
|
||||
## misses; the same-named field is reused instead of appending a divergent
|
||||
## duplicate (else a stale `:env` access reaches `cannotEval`). A freshly
|
||||
## built env keeps consistent ids, so two same-named captures there
|
||||
## legitimately get distinct fields -- hence the `Sealed`-only gate.
|
||||
result = lookupInRecord(b.obj.n, s.itemId)
|
||||
if result != nil: return
|
||||
if byName and b.obj.state == Sealed:
|
||||
result = lookupCapturedField(b.obj.n, s)
|
||||
|
||||
proc appendField*(b: var ObjectBuilder; field: PSym) =
|
||||
## Low-level append of a prebuilt `skField` (replaces `rawAddField`): set its
|
||||
## position, append it, fold its type into the object.
|
||||
assert field.kind == skField
|
||||
let obj = b.obj
|
||||
field.position = obj.n.len
|
||||
obj.n.add newSymNode(field)
|
||||
propagateToOwner(obj, field.typ)
|
||||
fieldCheck()
|
||||
|
||||
proc captureField*(b: var ObjectBuilder; s: PSym): PSym {.discardable.} =
|
||||
## Idempotent capture of local `s` (= `addField`). On a `findField` miss,
|
||||
## thaws the env if needed then mints the field. Under IC the env may be a
|
||||
## loaded Sealed type whose transform-time mutation is process-local (the body
|
||||
## is discarded after the macro runs), so `unsealForTransform` downgrades it to
|
||||
## mutable instead of crashing on `t.state != Sealed` (mirrors `markAsClosure`).
|
||||
result = b.findField(s, byName = true)
|
||||
if result != nil: return
|
||||
let obj = b.obj
|
||||
unsealForTransform(obj)
|
||||
# because of 'gensym' support, we have to mangle the name with its ID.
|
||||
# This is hacky but the clean solution is much more complex than it looks.
|
||||
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len),
|
||||
idgen, s.owner, s.info, s.options)
|
||||
var field = newSym(skField, getIdent(b.cache, s.name.s & $obj.n.len),
|
||||
b.idgen, s.owner, s.info, s.options)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
let t = skipIntLit(s.typ, idgen)
|
||||
let t = skipIntLit(s.typ, b.idgen)
|
||||
field.typ = t
|
||||
if s.kind in {skLet, skVar, skField, skForVar}:
|
||||
#field.bitsize = s.bitsize
|
||||
@@ -285,19 +309,44 @@ proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym
|
||||
fieldCheck()
|
||||
result = field
|
||||
|
||||
proc captureUniqueField*(b: var ObjectBuilder; s: PSym): PSym {.discardable.} =
|
||||
## `addUniqueField`: idempotent by item id ONLY (no name fallback, no thaw,
|
||||
## no alignment/flag copy).
|
||||
result = b.findField(s, byName = false)
|
||||
if result != nil: return
|
||||
let obj = b.obj
|
||||
var field = newSym(skField, getIdent(b.cache, s.name.s & $obj.n.len),
|
||||
b.idgen, s.owner, s.info, s.options)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
let t = skipIntLit(s.typ, b.idgen)
|
||||
field.typ = t
|
||||
assert t.kind != tyTyped
|
||||
propagateToOwner(obj, t)
|
||||
field.position = obj.n.len
|
||||
obj.n.add newSymNode(field)
|
||||
result = field
|
||||
|
||||
proc finishObject*(b: sink ObjectBuilder) {.inline.} =
|
||||
## Publish the completed body. A no-op today (the thawed env stays `Complete`,
|
||||
## process-local, never re-serialized); the seam where the NIF backend will
|
||||
## `beginRead` the record buffer into a read-only cursor and republish it
|
||||
## under the object's SymId.
|
||||
discard
|
||||
|
||||
proc rawAddField*(obj: PType; field: PSym) =
|
||||
var b = reopenObject(obj, nil, nil) # prebuilt field: cache/idgen unused
|
||||
b.appendField(field)
|
||||
finishObject b
|
||||
|
||||
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
|
||||
var b = reopenObject(obj, cache, idgen)
|
||||
result = b.captureField(s)
|
||||
finishObject b
|
||||
|
||||
proc addUniqueField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym {.discardable.} =
|
||||
result = lookupInRecord(obj.n, s.itemId)
|
||||
if result == nil:
|
||||
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), idgen,
|
||||
s.owner, s.info, s.options)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
let t = skipIntLit(s.typ, idgen)
|
||||
field.typ = t
|
||||
assert t.kind != tyTyped
|
||||
propagateToOwner(obj, t)
|
||||
field.position = obj.n.len
|
||||
obj.n.add newSymNode(field)
|
||||
result = field
|
||||
var b = reopenObject(obj, cache, idgen)
|
||||
result = b.captureUniqueField(s)
|
||||
finishObject b
|
||||
|
||||
proc newDotExpr*(obj, b: PSym): PNode =
|
||||
result = newNodeI(nkDotExpr, obj.info)
|
||||
@@ -385,8 +434,9 @@ proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode =
|
||||
proc genAddrOf*(n: PNode; idgen: IdGenerator; typeKind = tyPtr): PNode =
|
||||
result = newNodeI(nkAddr, n.info, 1)
|
||||
result[0] = n
|
||||
result.typ = newType(typeKind, idgen, n.typ.owner)
|
||||
result.typ.rawAddSon(n.typ)
|
||||
var b = openType(typeKind, idgen, n.typ.owner)
|
||||
b.addRaw n.typ
|
||||
result.typ = finish b
|
||||
|
||||
proc genDeref*(n: PNode; k = nkHiddenDeref): PNode =
|
||||
result = newNodeIT(k, n.info,
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
|
||||
import
|
||||
ast, msgs, platform, idents,
|
||||
modulegraphs, lineinfos, types
|
||||
modulegraphs, lineinfos, types, typebuilders
|
||||
|
||||
export createMagic
|
||||
export typebuilders
|
||||
|
||||
proc nilOrSysInt*(g: ModuleGraph): PType = g.sysTypes[tyInt]
|
||||
|
||||
@@ -91,24 +92,13 @@ proc getFloatLitType*(g: ModuleGraph; literal: PNode): PType =
|
||||
result = newSysType(g, tyFloat, size=8)
|
||||
result.n = literal
|
||||
|
||||
proc skipIntLit*(t: PType; id: IdGenerator): PType {.inline.} =
|
||||
if t.n != nil and t.kind in {tyInt, tyFloat}:
|
||||
result = copyType(t, id, t.owner)
|
||||
result.n = nil
|
||||
else:
|
||||
result = t
|
||||
|
||||
proc addSonSkipIntLit*(father, son: PType; id: IdGenerator) =
|
||||
let s = son.skipIntLit(id)
|
||||
father.add(s)
|
||||
propagateToOwner(father, s)
|
||||
|
||||
proc makeVarType*(owner: PSym; baseType: PType; idgen: IdGenerator; kind = tyVar): PType =
|
||||
if baseType.kind == kind:
|
||||
result = baseType
|
||||
else:
|
||||
result = newType(kind, idgen, owner)
|
||||
addSonSkipIntLit(result, baseType, idgen)
|
||||
var b = openType(kind, idgen, owner)
|
||||
b.add baseType
|
||||
result = finish b
|
||||
|
||||
proc getCompilerProc*(g: ModuleGraph; name: string): PSym =
|
||||
let ident = getIdent(g.cache, name)
|
||||
@@ -158,8 +148,9 @@ proc getMagicEqSymForType*(g: ModuleGraph; t: PType; info: TLineInfo): PSym =
|
||||
"can't find magic equals operator for type kind " & $t.kind)
|
||||
|
||||
proc makePtrType*(baseType: PType; idgen: IdGenerator): PType =
|
||||
result = newType(tyPtr, idgen, baseType.owner)
|
||||
addSonSkipIntLit(result, baseType, idgen)
|
||||
var b = openType(tyPtr, idgen, baseType.owner)
|
||||
b.add baseType
|
||||
result = finish b
|
||||
|
||||
proc makeAddr*(n: PNode; idgen: IdGenerator): PNode =
|
||||
if n.kind == nkHiddenAddr:
|
||||
|
||||
@@ -34,7 +34,6 @@ from icconfig import produceIcConfig
|
||||
when not defined(nimKochBootstrap):
|
||||
import nifbackend
|
||||
import deps
|
||||
import idetools
|
||||
|
||||
when not defined(leanCompiler):
|
||||
import docgen
|
||||
@@ -417,20 +416,6 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
for it in conf.searchPaths: msgWriteln(conf, it.string)
|
||||
of cmdCheck:
|
||||
commandCheck(graph)
|
||||
of cmdTrack:
|
||||
# `nim track --def:/--usages:/--track:` — IDE goto-definition / find-usages.
|
||||
# Runs `nim ic`'s incremental frontend (nifler + per-module `nim m`, so only
|
||||
# changed modules recompile and each writes a faithful, VM-executed `.s.bif`
|
||||
# — covering stdlib too), then scans those NIF files (idetools.runIdeQuery).
|
||||
# Shares the `nim ic` nimcache dir, so a prior `nim ic` build is reused.
|
||||
setUseIc(true)
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
when not defined(nimKochBootstrap):
|
||||
commandIc(conf, frontendOnly = true)
|
||||
runIdeQuery(conf)
|
||||
else:
|
||||
rawMessage(conf, errGenerated, "nim track not available in bootstrap build")
|
||||
of cmdM:
|
||||
# cmdM uses NIF files, not ROD files
|
||||
graph.config.symbolFiles = disabledSf
|
||||
|
||||
@@ -177,11 +177,6 @@ type
|
||||
|
||||
procGlobals*: seq[PNode]
|
||||
nifReplayActions*: Table[int32, seq[PNode]] # module position -> replay actions for NIF
|
||||
nifExpansions*: Table[int32, seq[(PSym, TLineInfo)]]
|
||||
# module position -> (template/macro sym, call-site info) for every expansion
|
||||
# in that module. Templates/macros leave no trace in the sem'checked AST, so
|
||||
# this side-channel (written into the `.bif`, see ast2nif) is what lets
|
||||
# `nim track --usages`/`--def` find them. Populated by `rememberExpansion`.
|
||||
cachedMods: IntSet
|
||||
hookClosure: IntSet # modules whose serialized hooks were already registered
|
||||
|
||||
|
||||
154
compiler/nifcmain.nim
Normal file
154
compiler/nifcmain.nim
Normal file
@@ -0,0 +1,154 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2026 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Backend-only driver for `nim ic`'s C-generation stages (the `nifc` command:
|
||||
## `--icBackendStage:lower|cg|merge|emit|link`). This produces the separate
|
||||
## `bin/nifc` binary that `deps.nim` invokes per rule instead of re-entering the
|
||||
## monolithic `nim` compiler.
|
||||
##
|
||||
## Crucially, it imports NEITHER `main`/`pipelines` NOR `cmdlinehelper`/`nimconf`.
|
||||
## Those are the only two edges that pull the frontend semantic analyzer (`sem`)
|
||||
## and the NimScript VM (`scriptconfig`) into the ordinary compiler binary. The
|
||||
## backend graph (`nifbackend` -> `cgen`/`ast2nif`/`transf`/`injectdestructors`/
|
||||
## `modulegraphs`) is entirely sem-free, and config is replayed sem-free from the
|
||||
## precompiled `.cfg.nif` via `icconfig.applyIcConfig` (no file read, no VM run).
|
||||
##
|
||||
## Keeping `sem` out of the closure is the prerequisite for building this binary
|
||||
## with `-d:nimBackend`, under which `astdef` swaps `PNode` to a cursor-backed
|
||||
## value representation: `sem`'s pervasive `PNode(kind: ...)` literal construction
|
||||
## could not compile against such a type, but it is no longer linked here.
|
||||
|
||||
import std/[os, parseopt, strutils]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import
|
||||
commands, options, msgs, extccomp, idents, lineinfos,
|
||||
pathutils, modulegraphs, condsyms, platform, modules
|
||||
import "../dist/checksums/src/checksums/sha1"
|
||||
|
||||
from ast import setUseIc
|
||||
from ast2nif import registerNifAstTags
|
||||
import icconfig
|
||||
import nifbackend
|
||||
|
||||
proc hashMainCompilationParams(conf: ConfigRef): string =
|
||||
## Mirrors `main.hashMainCompilationParams` (inlined to avoid importing `main`,
|
||||
## which pulls in `pipelines`/`sem`).
|
||||
var state = newSha1State()
|
||||
state.update os.getAppFilename()
|
||||
state.update conf.commandLine
|
||||
state.update $conf.projectFull
|
||||
result = $SecureHash(state.finalize())
|
||||
|
||||
proc setOutFile(conf: ConfigRef) =
|
||||
## Mirrors `main.setOutFile` (inlined, same reason).
|
||||
if conf.outFile.isEmpty:
|
||||
var base = conf.projectName
|
||||
if optUseNimcache in conf.globalOptions:
|
||||
base.add "_" & hashMainCompilationParams(conf)
|
||||
let targetName =
|
||||
if optGenDynLib in conf.globalOptions:
|
||||
platform.OS[conf.target.targetOS].dllFrmt % base
|
||||
elif optGenStaticLib in conf.globalOptions:
|
||||
(if conf.target.targetOS == osWindows: "$1.lib" else: "lib$1.a") % base
|
||||
else: base & platform.OS[conf.target.targetOS].exeExt
|
||||
conf.outFile = RelativeFile targetName
|
||||
|
||||
proc addCmdPrefix(result: var string, kind: CmdLineKind) =
|
||||
case kind
|
||||
of cmdLongOption: result.add "--"
|
||||
of cmdShortOption: result.add "-"
|
||||
of cmdArgument, cmdEnd: discard
|
||||
|
||||
proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
|
||||
## Slim copy of `nim.processCmdLine` (no nimble-lock probing, no stdin project):
|
||||
## the `nifc` child is always launched by `deps.nim` with an explicit project
|
||||
## NIF and forwarded switches.
|
||||
var p = parseopt.initOptParser(cmd)
|
||||
var argsCount = 0
|
||||
config.commandLine.setLen 0
|
||||
while true:
|
||||
parseopt.next(p)
|
||||
case p.kind
|
||||
of cmdEnd: break
|
||||
of cmdLongOption, cmdShortOption:
|
||||
config.commandLine.add " "
|
||||
config.commandLine.addCmdPrefix p.kind
|
||||
config.commandLine.add p.key.quoteShell
|
||||
if p.val.len > 0:
|
||||
config.commandLine.add ':'
|
||||
config.commandLine.add p.val.quoteShell
|
||||
processSwitch(pass, p, config)
|
||||
of cmdArgument:
|
||||
config.commandLine.add " "
|
||||
config.commandLine.add p.key.quoteShell
|
||||
if processArgument(pass, p, argsCount, config): break
|
||||
|
||||
proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
# NIF tag registration must run before any NIF read/write, independent of
|
||||
# module init order (see ast2nif.registerNifAstTags).
|
||||
registerNifAstTags()
|
||||
condsyms.initDefines(conf.symbols)
|
||||
defineSymbol(conf.symbols, "nim_compiler")
|
||||
|
||||
if paramCount() == 0:
|
||||
rawMessage(conf, errGenerated, "nifc: no arguments (expected a NIF project)")
|
||||
return
|
||||
|
||||
# Pass 1: learn the command (`nifc`), the project NIF, and switches including
|
||||
# `--icPreparsedConfig` (needed before config replay below).
|
||||
processCmdLine(passCmd1, "", conf)
|
||||
if conf.projectName != "":
|
||||
setFromProjectName(conf, conf.projectName)
|
||||
else:
|
||||
conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile getCurrentDir())
|
||||
|
||||
var graph = newModuleGraph(cache, conf)
|
||||
|
||||
# Sem-free config: replay the precompiled `.cfg.nif` produced once by the
|
||||
# `nim icconfig` process. No `nimconf`, no `scriptconfig`, no VM. A missing or
|
||||
# format-incompatible artifact is fatal here (unlike the frontend, this binary
|
||||
# has no fallback config parser on purpose).
|
||||
setDefaultLibpath(conf)
|
||||
if conf.icPreparsedConfig.len == 0 or not applyIcConfig(conf, conf.icPreparsedConfig):
|
||||
rawMessage(conf, errGenerated,
|
||||
"nifc backend requires a valid precompiled config (--icPreparsedConfig)")
|
||||
return
|
||||
if conf.backend != backendJs: extccomp.initVars(conf)
|
||||
|
||||
# Pass 2: command-line switches override the replayed config.
|
||||
processCmdLine(passCmd2, "", conf)
|
||||
|
||||
if conf.selectedGC == gcUnselected:
|
||||
initOrcDefines(conf)
|
||||
|
||||
if conf.cmd != cmdNifC:
|
||||
rawMessage(conf, errGenerated, "nifc: only the 'nifc' command is supported")
|
||||
return
|
||||
|
||||
# cmdNifC arm, mirroring `main.mainCommand`:
|
||||
setUseIc(true)
|
||||
excl conf.features, Feature.vtables
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
|
||||
# `main.commandNifC` body, inlined:
|
||||
extccomp.initVars(conf)
|
||||
if not extccomp.ccHasSaneOverflow(conf):
|
||||
conf.symbols.defineSymbol("nimEmulateOverflowChecks")
|
||||
nifbackend.generateCode(graph, conf.projectMainIdx)
|
||||
|
||||
when compileOption("gc", "refc"):
|
||||
GC_disableMarkAndSweep()
|
||||
|
||||
let conf = newConfigRef()
|
||||
handleCmdLine(newIdentCache(), conf)
|
||||
msgQuit(int8(conf.errorCounter > 0))
|
||||
@@ -120,7 +120,7 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
# so `loadConfigs` replays it instead of re-parsing the `nim.cfg` chain — the
|
||||
# driver runs on the exact same config its children will. See icconfig.nim.
|
||||
when not defined(nimKochBootstrap):
|
||||
if conf.cmd in {cmdIc, cmdTrack}:
|
||||
if conf.cmd == cmdIc:
|
||||
ensureIcConfig(conf)
|
||||
|
||||
var graph = newModuleGraph(cache, conf)
|
||||
@@ -134,7 +134,7 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
if conf.selectedGC == gcUnselected:
|
||||
if conf.backend in {backendC, backendCpp, backendObjc} or
|
||||
(conf.cmd in cmdDocLike and conf.backend != backendJs) or
|
||||
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM, cmdTrack}:
|
||||
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM}:
|
||||
initOrcDefines(conf)
|
||||
|
||||
if conf.selectedStrings == stringSso and
|
||||
|
||||
@@ -29,7 +29,7 @@ const
|
||||
|
||||
nimEnableCovariance* = defined(nimEnableCovariance)
|
||||
|
||||
icFormatVersion* = "30"
|
||||
icFormatVersion* = "29"
|
||||
## Version of the IC cache format (the sem-NIF module layout written by
|
||||
## ast2nif.nim plus the iface/impl/edges side files). Bump it whenever
|
||||
## that layout changes: `commandIc` wipes a nimcache whose `ic.version`
|
||||
@@ -140,7 +140,6 @@ type # please make sure we have under 32 options
|
||||
optDocRaw # for documentation: Don't render markdown for JSON output
|
||||
optItaniumMangle # mangling follows the Itanium spec
|
||||
optCompress # turn on AST compression by converting it to NIF
|
||||
optGenBif # generate semantic BIF alongside ordinary code generation
|
||||
optWithinConfigSystem # we still compile within the configuration system
|
||||
|
||||
TGlobalOptions* = set[TGlobalOption]
|
||||
@@ -206,7 +205,6 @@ type
|
||||
cmdNifC # generate C code from NIF files
|
||||
cmdIc # generate .build.nif for nifmake
|
||||
cmdIcConfig # `nim ic`'s precompiled-config producer (writes ic_config.cfg.nif)
|
||||
cmdTrack # `nim track --def/--usages`: IC frontend build + NIF scan for IDE queries
|
||||
|
||||
const
|
||||
cmdBackends* = {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
|
||||
|
||||
@@ -167,8 +167,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
s = stream
|
||||
graph.interactive = stream.kind == llsStdIn
|
||||
var topLevelStmts =
|
||||
if {optCompress, optGenBif} * graph.config.globalOptions != {} or
|
||||
graph.config.cmd == cmdM:
|
||||
if optCompress in graph.config.globalOptions or graph.config.cmd == cmdM:
|
||||
newNodeI(nkStmtList, module.info)
|
||||
else:
|
||||
nil
|
||||
@@ -256,7 +255,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
graph.config.cmd == cmdM and graph.config.errorCounter == 0 and
|
||||
graph.config.m.fileInfos[module.position].dirtyFile.isEmpty
|
||||
else:
|
||||
({optCompress, optGenBif} * graph.config.globalOptions != {}) or
|
||||
(optCompress in graph.config.globalOptions) or
|
||||
(graph.config.cmd == cmdM and
|
||||
(sfMainModule in module.flags or
|
||||
(graph.config.icGroup.len > 0 and
|
||||
@@ -318,12 +317,9 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
# the backend seeds its id minting ABOVE this so closure envs / RTTI hooks
|
||||
# never share a `toId` with a frontend sym/type. See ast2nif `(unusedid)`.
|
||||
let firstUnusedId = max(idgen.symId, idgen.typeId)
|
||||
var expansions: seq[(PSym, TLineInfo)] = @[]
|
||||
discard graph.nifExpansions.take(module.position.int32, expansions)
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog,
|
||||
replayActions, implDeps, reexportedModuleSyms(graph, module),
|
||||
genericOffers, typeOffers, resolvedImportDeps, firstUnusedId,
|
||||
expansions)
|
||||
genericOffers, typeOffers, resolvedImportDeps, firstUnusedId)
|
||||
# The module's REAL direct imports (incl. macro-generated) for `nim ic`'s
|
||||
# graph re-derivation; see ast2nif.writeSemDeps / semdata.addImportFileDep.
|
||||
var semDepPaths: seq[string] = @[]
|
||||
|
||||
@@ -108,16 +108,6 @@ proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode =
|
||||
markUsed(c, a.info, a[0].sym)
|
||||
|
||||
|
||||
template isAutoReturnType(t: PType): bool =
|
||||
# `auto` return types are copied and marked so they are not generic params.
|
||||
t.kind == tyAnything and tfRetType in t.flags
|
||||
|
||||
template isUnresolvedAutoReturnType(c: PContext; t: PType): bool =
|
||||
# During return-type inference a recursive call has the routine's exact
|
||||
# `auto` placeholder type. It contributes no type information of its own.
|
||||
c.p != nil and c.p.owner != nil and c.p.owner.typ != nil and
|
||||
c.p.owner.typ.returnType == t and isAutoReturnType(t)
|
||||
|
||||
proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
|
||||
if arg.typ.isNil:
|
||||
localError(c.config, arg.info, "expression has no type: " &
|
||||
@@ -135,10 +125,6 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
|
||||
if sameType(ch.typ.skipTypes({tyVar, tyLent}), formal):
|
||||
return ch
|
||||
typeMismatch(c.config, info, formal, arg.typ, arg)
|
||||
elif isUnresolvedAutoReturnType(c, arg.typ):
|
||||
# A concrete sibling branch supplies the missing type for this branch.
|
||||
result = arg
|
||||
changeType(c, result, formal, check=true)
|
||||
else:
|
||||
result = indexTypesMatch(c, formal, arg.typ, arg)
|
||||
if result == nil:
|
||||
@@ -172,10 +158,8 @@ proc commonType*(c: PContext; x, y: PType): PType =
|
||||
var a = skipTypes(x, {tyGenericInst, tyAlias, tySink})
|
||||
var b = skipTypes(y, {tyGenericInst, tyAlias, tySink})
|
||||
result = x
|
||||
# Recursive calls cannot contribute to their own `auto` return type, so let
|
||||
# the other branch determine the common type when it has concrete evidence.
|
||||
if a.kind in {tyUntyped, tyNil} or isUnresolvedAutoReturnType(c, a): result = y
|
||||
elif b.kind in {tyUntyped, tyNil} or isUnresolvedAutoReturnType(c, b): result = x
|
||||
if a.kind in {tyUntyped, tyNil}: result = y
|
||||
elif b.kind in {tyUntyped, tyNil}: result = x
|
||||
elif a.kind == tyTyped: result = a
|
||||
elif b.kind == tyTyped: result = b
|
||||
elif a.kind == tyTypeDesc:
|
||||
@@ -199,7 +183,8 @@ proc commonType*(c: PContext; x, y: PType): PType =
|
||||
nt = copyType(a, c.idgen, a.owner)
|
||||
copyTypeProps(c.graph, c.idgen.module, nt, a)
|
||||
|
||||
nt[i] = if aEmpty: bb else: aa
|
||||
var ntb = reopen(nt)
|
||||
ntb.setSon(i, if aEmpty: bb else: aa)
|
||||
if not nt.isNil: result = nt
|
||||
#elif b[idx].kind == tyEmpty: return x
|
||||
elif a.kind == tyRange and b.kind == tyRange:
|
||||
@@ -304,14 +289,6 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
|
||||
result = copySym(result)
|
||||
result.ast = n.sym.ast
|
||||
put(c.p, n.sym, result)
|
||||
if result.state == Sealed:
|
||||
# the symbol was loaded from another module's NIF cache (e.g. a param
|
||||
# symbol spliced out of an imported proc type by a `typed` macro) and is
|
||||
# therefore immutable; the caller re-owns it and assigns its type/flags,
|
||||
# so hand back a fresh, mutable copy owned by the current module instead.
|
||||
let fresh = copySym(result, c.idgen)
|
||||
fresh.ast = result.ast
|
||||
result = fresh
|
||||
# when there is a nested proc inside a template, semtmpl
|
||||
# will assign a wrong owner during the first pass over the
|
||||
# template; we must fix it here: see #909
|
||||
@@ -600,12 +577,10 @@ const
|
||||
|
||||
proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
|
||||
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
|
||||
let info = getCallLineInfo(n)
|
||||
# the callee identifier's position is the usage site tooling expects (matches
|
||||
# `markUsed` below), not the whole-call `nOrig.info`.
|
||||
rememberExpansion(c, info, sym)
|
||||
rememberExpansion(c, nOrig.info, sym)
|
||||
pushInfoContext(c.config, nOrig.info, sym.detailedInfo)
|
||||
|
||||
let info = getCallLineInfo(n)
|
||||
markUsed(c, info, sym)
|
||||
onUse(info, sym)
|
||||
if sym == c.p.owner:
|
||||
|
||||
@@ -924,11 +924,6 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
|
||||
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
|
||||
if finalCallee.magic notin {mArrGet, mArrPut}:
|
||||
result.typ = finalCallee.typ.returnType
|
||||
# Remember that this body contains a self-call still sharing its unresolved
|
||||
# `auto` placeholder; a later concrete return must resolve that placeholder.
|
||||
if c.p != nil and result.typ != nil and finalCallee == c.p.owner and
|
||||
isAutoReturnType(result.typ):
|
||||
c.p.hasUnresolvedAutoCall = true
|
||||
updateDefaultParams(c, result)
|
||||
|
||||
proc canDeref(n: PNode): bool {.inline.} =
|
||||
|
||||
@@ -17,7 +17,9 @@ when defined(nimPreviewSlimSystem):
|
||||
import
|
||||
options, ast, msgs, idents, renderer,
|
||||
magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable,
|
||||
types, lowerings, trees, parampatterns, astalgo
|
||||
types, lowerings, trees, parampatterns, astalgo, typebuilders
|
||||
|
||||
export typebuilders
|
||||
|
||||
type
|
||||
TOptionEntry* = object # entries to put on a stack for pragma parsing
|
||||
@@ -43,7 +45,6 @@ type
|
||||
mapping*: SymMapping
|
||||
caseContext*: seq[tuple[n: PNode, idx: int]]
|
||||
localBindStmts*: seq[PNode]
|
||||
hasUnresolvedAutoCall*: bool # a self-call still uses the `auto` return placeholder
|
||||
|
||||
TMatchedConcept* = object
|
||||
candidateType*: PType
|
||||
@@ -383,9 +384,8 @@ proc addImportFileDep*(c: PContext; f: FileIndex) =
|
||||
if f notin deps[]: deps[].add f
|
||||
|
||||
proc addPragmaComputation*(c: PContext; n: PNode) =
|
||||
# Also store whenever the semchecked module is serialized to NIF/BIF.
|
||||
if {optCompress, optGenBif} * c.config.globalOptions != {} or
|
||||
c.config.cmd == cmdM:
|
||||
# Also store for NIF-based IC (cmdM mode or optCompress)
|
||||
if optCompress in c.config.globalOptions or c.config.cmd == cmdM:
|
||||
addNifReplayAction(c.graph, c.module.position.int32, n)
|
||||
|
||||
proc inclSym(sq: var seq[PSym], s: PSym): bool =
|
||||
@@ -447,8 +447,14 @@ proc addToLib*(lib: PLib, sym: PSym) =
|
||||
proc newTypeS*(kind: TTypeKind; c: PContext; son: sink PType = nil): PType =
|
||||
result = newType(kind, c.idgen, getCurrOwner(c), son = son)
|
||||
|
||||
proc openType*(c: PContext; kind: TTypeKind): TypeBuilder {.inline.} =
|
||||
## `PContext`-flavored `openType`: the type is owned by the current owner.
|
||||
openType(kind, c.idgen, getCurrOwner(c))
|
||||
|
||||
proc makePtrType*(owner: PSym, baseType: PType; idgen: IdGenerator): PType =
|
||||
result = newType(tyPtr, idgen, owner, skipIntLit(baseType, idgen))
|
||||
var b = openType(tyPtr, idgen, owner)
|
||||
b.addKeep skipIntLit(baseType, idgen) # son= fast path: skip int-lit, no propagate
|
||||
result = finish b
|
||||
|
||||
proc makePtrType*(c: PContext, baseType: PType): PType =
|
||||
makePtrType(getCurrOwner(c), baseType, c.idgen)
|
||||
@@ -461,27 +467,33 @@ proc makeTypeWithModifier*(c: PContext,
|
||||
if modifier in {tyVar, tyLent, tyTypeDesc} and baseType.kind == modifier:
|
||||
result = baseType
|
||||
else:
|
||||
result = newTypeS(modifier, c, skipIntLit(baseType, c.idgen))
|
||||
var b = openType(c, modifier)
|
||||
b.addKeep skipIntLit(baseType, c.idgen)
|
||||
result = finish b
|
||||
|
||||
proc makeVarType*(c: PContext, baseType: PType; kind = tyVar): PType =
|
||||
if baseType.kind == kind:
|
||||
result = baseType
|
||||
else:
|
||||
result = newTypeS(kind, c, skipIntLit(baseType, c.idgen))
|
||||
var b = openType(c, kind)
|
||||
b.addKeep skipIntLit(baseType, c.idgen)
|
||||
result = finish b
|
||||
|
||||
proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
|
||||
let typedesc = newTypeS(tyTypeDesc, c)
|
||||
incl typedesc.flagsImpl, tfCheckedForDestructor
|
||||
internalAssert(c.config, typ != nil)
|
||||
typedesc.addSonSkipIntLit(typ, c.idgen)
|
||||
var b = openType(c, tyTypeDesc)
|
||||
b.incl tfCheckedForDestructor
|
||||
b.add typ
|
||||
let typedesc = finish b
|
||||
let sym = newSym(skType, c.cache.idAnon, c.idgen, getCurrOwner(c), info,
|
||||
c.config.options).linkTo(typedesc)
|
||||
result = newSymNode(sym, info)
|
||||
|
||||
proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
|
||||
result = newTypeS(tyFromExpr, c)
|
||||
assert n != nil
|
||||
result.n = n
|
||||
var b = openType(c, tyFromExpr)
|
||||
b.setN n
|
||||
result = finish b
|
||||
|
||||
when false:
|
||||
proc newTypeWithSons*(owner: PSym, kind: TTypeKind, sons: seq[PType];
|
||||
@@ -495,42 +507,49 @@ when false:
|
||||
proc makeStaticExpr*(c: PContext, n: PNode): PNode =
|
||||
result = newNodeI(nkStaticExpr, n.info)
|
||||
result.sons = @[n]
|
||||
result.typ = if n.typ != nil and n.typ.kind == tyStatic: n.typ
|
||||
else: newTypeS(tyStatic, c, n.typ)
|
||||
result.typ =
|
||||
if n.typ != nil and n.typ.kind == tyStatic: n.typ
|
||||
else:
|
||||
var b = openType(c, tyStatic)
|
||||
b.addKeep n.typ
|
||||
finish b
|
||||
|
||||
proc makeAndType*(c: PContext, t1, t2: PType): PType =
|
||||
result = newTypeS(tyAnd, c)
|
||||
result.rawAddSon t1
|
||||
result.rawAddSon t2
|
||||
propagateToOwner(result, t1)
|
||||
propagateToOwner(result, t2)
|
||||
result.flagsImpl.incl((t1.flags + t2.flags) * {tfHasStatic})
|
||||
result.flagsImpl.incl tfHasMeta
|
||||
var b = openType(c, tyAnd)
|
||||
b.addRaw t1
|
||||
b.addRaw t2
|
||||
b.propagateFrom t1
|
||||
b.propagateFrom t2
|
||||
b.incl((t1.flags + t2.flags) * {tfHasStatic})
|
||||
b.incl tfHasMeta
|
||||
result = finish b
|
||||
|
||||
proc makeOrType*(c: PContext, t1, t2: PType): PType =
|
||||
var b = openType(c, tyOr)
|
||||
if t1.kind != tyOr and t2.kind != tyOr:
|
||||
result = newTypeS(tyOr, c)
|
||||
result.rawAddSon t1
|
||||
result.rawAddSon t2
|
||||
b.addRaw t1
|
||||
b.addRaw t2
|
||||
else:
|
||||
result = newTypeS(tyOr, c)
|
||||
template addOr(t1) =
|
||||
if t1.kind == tyOr:
|
||||
for x in t1.kids: result.rawAddSon x
|
||||
for x in t1.kids: b.addRaw x
|
||||
else:
|
||||
result.rawAddSon t1
|
||||
b.addRaw t1
|
||||
addOr(t1)
|
||||
addOr(t2)
|
||||
propagateToOwner(result, t1)
|
||||
propagateToOwner(result, t2)
|
||||
result.incl((t1.flags + t2.flags) * {tfHasStatic})
|
||||
result.incl tfHasMeta
|
||||
b.propagateFrom t1
|
||||
b.propagateFrom t2
|
||||
b.incl((t1.flags + t2.flags) * {tfHasStatic})
|
||||
b.incl tfHasMeta
|
||||
result = finish b
|
||||
|
||||
proc makeNotType*(c: PContext, t1: PType): PType =
|
||||
result = newTypeS(tyNot, c, son = t1)
|
||||
propagateToOwner(result, t1)
|
||||
result.flagsImpl.incl(t1.flags * {tfHasStatic})
|
||||
result.flagsImpl.incl tfHasMeta
|
||||
var b = openType(c, tyNot)
|
||||
b.addKeep t1
|
||||
b.propagateFrom t1
|
||||
b.incl(t1.flags * {tfHasStatic})
|
||||
b.incl tfHasMeta
|
||||
result = finish b
|
||||
|
||||
proc nMinusOne(c: PContext; n: PNode): PNode =
|
||||
result = newTreeI(nkCall, n.info, newSymNode(getSysMagic(c.graph, n.info, "pred", mPred)), n)
|
||||
@@ -538,19 +557,22 @@ proc nMinusOne(c: PContext; n: PNode): PNode =
|
||||
# Remember to fix the procs below this one when you make changes!
|
||||
proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType =
|
||||
let intType = getSysType(c.graph, n.info, tyInt)
|
||||
result = newTypeS(tyRange, c, son = intType)
|
||||
var b = openType(c, tyRange)
|
||||
b.addKeep intType
|
||||
if n.typ != nil and n.typ.n == nil:
|
||||
result.incl tfUnresolved
|
||||
result.n = newTreeI(nkRange, n.info, newIntTypeNode(0, intType),
|
||||
b.incl tfUnresolved
|
||||
b.setN newTreeI(nkRange, n.info, newIntTypeNode(0, intType),
|
||||
makeStaticExpr(c, nMinusOne(c, n)))
|
||||
result = finish b
|
||||
|
||||
template rangeHasUnresolvedStatic*(t: PType): bool =
|
||||
tfUnresolved in t.flags
|
||||
|
||||
proc errorType*(c: PContext): PType =
|
||||
## creates a type representing an error state
|
||||
result = newTypeS(tyError, c)
|
||||
result.flagsImpl.incl tfCheckedForDestructor
|
||||
var b = openType(c, tyError)
|
||||
b.incl tfCheckedForDestructor
|
||||
result = finish b
|
||||
|
||||
proc errorNode*(c: PContext, n: PNode): PNode =
|
||||
result = newNodeI(nkEmpty, n.info)
|
||||
@@ -587,9 +609,10 @@ proc makeRangeType*(c: PContext; first, last: BiggestInt;
|
||||
var n = newNodeI(nkRange, info)
|
||||
n.add newIntTypeNode(first, intType)
|
||||
n.add newIntTypeNode(last, intType)
|
||||
result = newTypeS(tyRange, c)
|
||||
result.n = n
|
||||
addSonSkipIntLit(result, intType, c.idgen) # basetype of range
|
||||
var b = openType(c, tyRange)
|
||||
b.setN n
|
||||
b.add intType # basetype of range
|
||||
result = finish b
|
||||
|
||||
proc isSelf*(t: PType): bool {.inline.} =
|
||||
## Is this the magical 'Self' type from concepts?
|
||||
@@ -599,8 +622,10 @@ proc makeTypeDesc*(c: PContext, typ: PType): PType =
|
||||
if typ.kind == tyTypeDesc and not isSelf(typ):
|
||||
result = typ
|
||||
else:
|
||||
result = newTypeS(tyTypeDesc, c, skipIntLit(typ, c.idgen))
|
||||
incl result, tfCheckedForDestructor
|
||||
var b = openType(c, tyTypeDesc)
|
||||
b.addKeep skipIntLit(typ, c.idgen)
|
||||
b.incl tfCheckedForDestructor
|
||||
result = finish b
|
||||
|
||||
proc symFromType*(c: PContext; t: PType, info: TLineInfo): PSym =
|
||||
if t.sym != nil: return t.sym
|
||||
@@ -670,15 +695,7 @@ proc rememberExpansion*(c: PContext; info: TLineInfo; expandedSym: PSym) =
|
||||
## ("find all usages of this template" would not work). We need special
|
||||
## logic to remember macro/template expansions. This is done here and
|
||||
## delegated to the "NIF" file mechanism.
|
||||
##
|
||||
## We only bother when a NIF file is actually going to be written (IC / `nim m`,
|
||||
## `--compress`, semantic BIF output, or a running suggestion engine); a plain
|
||||
## `nim c` throws the record away, so recording it would be pure overhead.
|
||||
if info.fileIndex == InvalidFileIdx: return
|
||||
if c.config.cmd == cmdM or
|
||||
{optCompress, optGenBif} * c.config.globalOptions != {} or
|
||||
c.config.ideActive:
|
||||
c.graph.nifExpansions.mgetOrPut(c.module.position.int32, @[]).add (expandedSym, info)
|
||||
discard "XXX To implement"
|
||||
|
||||
const
|
||||
errVarForOutParamNeededX = "for a 'var' type a variable needs to be passed; but '$1' is immutable"
|
||||
|
||||
@@ -26,15 +26,13 @@ const
|
||||
|
||||
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
|
||||
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
|
||||
let info = getCallLineInfo(n)
|
||||
# `info` (the callee identifier's position, not the whole call node) is what
|
||||
# tooling wants to see as the usage site — matches `markUsed` below.
|
||||
rememberExpansion(c, info, s)
|
||||
rememberExpansion(c, n.info, s)
|
||||
# IC: this expands `s`'s body into the current module's sem, so the module
|
||||
# depends on that body — record a NeedsImpl (strong) edge to `s`'s module.
|
||||
# The iface cookie hashes only signatures now, so a template body edit moves
|
||||
# only the impl cookie, and just the modules that expanded it re-sem.
|
||||
recordIcImplDep(c.graph, s)
|
||||
let info = getCallLineInfo(n)
|
||||
markUsed(c, info, s)
|
||||
onUse(info, s)
|
||||
# Note: This is n.info on purpose. It prevents template from creating an info
|
||||
@@ -2125,15 +2123,6 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
|
||||
internalAssert c.config, c.p.resultSym != nil
|
||||
# Make sure the type is valid for the result variable
|
||||
typeAllowedCheck(c, n.info, rhsTyp, skResult)
|
||||
# Earlier self-calls retain the old placeholder pointer. Resolve it
|
||||
# in place as an alias before the routine switches to the concrete
|
||||
# type, so those already-typed calls see the inferred type too.
|
||||
if c.p.hasUnresolvedAutoCall and not rhsTyp.isMetaType and
|
||||
isAutoReturnType(lhs.sym.typ):
|
||||
let resolved = newTypeS(tyAlias, c)
|
||||
rawAddSon(resolved, rhsTyp)
|
||||
assignType(lhs.sym.typ, resolved)
|
||||
c.p.hasUnresolvedAutoCall = false
|
||||
lhs.typ = rhsTyp
|
||||
c.p.resultSym.typ = rhsTyp
|
||||
c.p.owner.typ.setReturnType rhsTyp
|
||||
@@ -2205,11 +2194,7 @@ proc semProcBody(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
||||
" flags=", c.p.resultSym.typ.flags,
|
||||
" uid=", c.p.resultSym.typ.uniqueId.module, ".", c.p.resultSym.typ.uniqueId.item,
|
||||
" state=", c.p.resultSym.typ.state
|
||||
# With no concrete return, the recursive placeholder is still circular.
|
||||
if c.p.hasUnresolvedAutoCall:
|
||||
localError(c.config, c.p.resultSym.info, errCannotInferReturnType %
|
||||
c.p.owner.name.s)
|
||||
elif isEmptyType(result.typ):
|
||||
if isEmptyType(result.typ):
|
||||
# we inferred a 'void' return type:
|
||||
c.p.resultSym.typ = errorType(c)
|
||||
c.p.owner.typ.setReturnType nil
|
||||
@@ -2265,7 +2250,8 @@ proc semYield(c: PContext, n: PNode): PNode =
|
||||
|
||||
if resultTypeIsInferrable(restype):
|
||||
let inferred = n[0].typ
|
||||
iterType[0] = inferred
|
||||
var b = reopen(iterType)
|
||||
b.setSon(0, inferred)
|
||||
if c.p.resultSym != nil:
|
||||
c.p.resultSym.typ = inferred
|
||||
else:
|
||||
|
||||
@@ -24,7 +24,7 @@ when defined(nimPreviewSlimSystem):
|
||||
proc errorType*(g: ModuleGraph): PType =
|
||||
## creates a type representing an error state
|
||||
result = newType(tyError, g.idgen, g.owners[^1])
|
||||
result.flagsImpl.incl tfCheckedForDestructor
|
||||
result.incl tfCheckedForDestructor
|
||||
|
||||
proc getIntLitTypeG(g: ModuleGraph; literal: PNode; idgen: IdGenerator): PType =
|
||||
# we cache some common integer literal types for performance:
|
||||
|
||||
@@ -436,7 +436,8 @@ proc semUnown(c: PContext; n: PNode): PNode =
|
||||
result = copyType(t, c.idgen, t.owner)
|
||||
copyTypeProps(c.graph, c.idgen.module, result, t)
|
||||
|
||||
result[^1] = b
|
||||
var rb = reopen(result)
|
||||
rb.setSon(^1, b)
|
||||
result.excl tfHasOwned
|
||||
else:
|
||||
result = t
|
||||
|
||||
@@ -1784,18 +1784,13 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode; s: PSym = nil) =
|
||||
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
||||
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
|
||||
|
||||
let forbidsSpec = effectSpec(n, wForbids)
|
||||
let tagsSpec = effectSpec(n, wTags)
|
||||
if not isNil(tagsSpec):
|
||||
effects[tagEffects] = tagsSpec
|
||||
elif not isNil(forbidsSpec):
|
||||
# `.forbids` without `.tags` still declares a known empty tag set.
|
||||
# Leaving this as nil would mean "unknown tags", which later widens
|
||||
# indirect calls to `RootEffect`.
|
||||
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
||||
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
||||
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
||||
|
||||
let forbidsSpec = effectSpec(n, wForbids)
|
||||
if not isNil(forbidsSpec):
|
||||
effects[forbiddenEffects] = forbidsSpec
|
||||
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
||||
|
||||
@@ -1152,10 +1152,12 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
||||
case iter[i].kind
|
||||
of tyVar:
|
||||
mutable = true
|
||||
iter[i] = iter[i].skipTypes({tyVar})
|
||||
var b = reopen(iter)
|
||||
b.setSon(i, iter[i].skipTypes({tyVar}))
|
||||
of tyLent:
|
||||
isLent = true
|
||||
iter[i] = iter[i].skipTypes({tyLent})
|
||||
var b = reopen(iter)
|
||||
b.setSon(i, iter[i].skipTypes({tyLent}))
|
||||
else: discard
|
||||
|
||||
if n[i].len-1 != iter[i].len:
|
||||
@@ -1680,7 +1682,8 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
|
||||
# object might have been assumed to be final
|
||||
if tfInheritable in oldFlags and tfFinal in body.flags:
|
||||
excl(body, tfFinal)
|
||||
s.typ[^1] = body
|
||||
var b = reopen(s.typ)
|
||||
b.setSon(^1, body)
|
||||
if tfCovariant in s.typ.flags:
|
||||
checkCovariantParamsUsages(c, s.typ)
|
||||
# XXX: This is a temporary limitation:
|
||||
@@ -2892,8 +2895,7 @@ proc incMod(c: PContext, n: PNode, it: PNode, includeStmtResult, resolvedIncStmt
|
||||
proc evalInclude(c: PContext, n: PNode): PNode =
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
var resolvedIncStmt: PNode = nil
|
||||
if {optCompress, optGenBif} * c.config.globalOptions != {} or
|
||||
c.config.cmd == cmdM:
|
||||
if optCompress in c.config.globalOptions or c.config.cmd == cmdM:
|
||||
# New resolve the include filenames to string literals that contain absolute paths,
|
||||
# nicer for IC:
|
||||
resolvedIncStmt = newNodeI(nkIncludeStmt, n.info)
|
||||
|
||||
@@ -44,6 +44,29 @@ proc reusePrev(prev: PType): bool {.inline.} =
|
||||
# partial object marks sym as `sfForward`
|
||||
(sfForward in prev.sym.flags or prev.sym.magic != mNone)))
|
||||
|
||||
proc openType(c: PContext; kind: TTypeKind; prev: PType): TypeBuilder =
|
||||
## Prev-aware `openType`. This folds the identity decision into one place: for
|
||||
## a forward-declared / partial `prev` the reserved *name* is kept and its
|
||||
## *tree structure* is rebuilt in place; otherwise a fresh type (and identity)
|
||||
## is minted -- at the same sequence point as `newTypeS`, so type ids stay
|
||||
## byte-identical. Callers become the uniform "build structure, then `finish`".
|
||||
if reusePrev(prev):
|
||||
if prev.kind == tyForward: prev.kind = kind
|
||||
result = reopen(prev, c.idgen)
|
||||
else:
|
||||
result = openType(c, kind)
|
||||
|
||||
proc openPair(c: PContext; kind: TTypeKind; prev: PType): TypePairBuilder =
|
||||
## Prev-aware deferred (`TypePair`) open -- the deferred analogue of the
|
||||
## prev-aware `openType` above, for types whose identity is published before
|
||||
## their body is finished. Keeps a forward/partial `prev`'s reserved name,
|
||||
## else mints a fresh identity at the same sequence point as `newTypeS`.
|
||||
if reusePrev(prev):
|
||||
if prev.kind == tyForward: prev.kind = kind
|
||||
result = reopenPair(prev, c.idgen)
|
||||
else:
|
||||
result = openPair(kind, c.idgen, getCurrOwner(c))
|
||||
|
||||
proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext, son: sink PType): PType =
|
||||
if reusePrev(prev):
|
||||
result = prev
|
||||
@@ -54,16 +77,15 @@ proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext, son: sink PType):
|
||||
#if kind == tyError: result.flags.incl tfCheckedForDestructor
|
||||
|
||||
proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType =
|
||||
if reusePrev(prev):
|
||||
result = prev
|
||||
if result.kind == tyForward: result.kind = kind
|
||||
else:
|
||||
result = newTypeS(kind, c)
|
||||
# centralized on the `openType` chokepoint: keep the reserved name of a
|
||||
# forward/partial `prev`, else mint a fresh identity.
|
||||
finish openType(c, kind, prev)
|
||||
|
||||
proc newConstraint(c: PContext, k: TTypeKind): PType =
|
||||
result = newTypeS(tyBuiltInTypeClass, c)
|
||||
result.incl tfCheckedForDestructor
|
||||
result.addSonSkipIntLit(newTypeS(k, c), c.idgen)
|
||||
var b = openType(c, tyBuiltInTypeClass)
|
||||
b.incl tfCheckedForDestructor
|
||||
b.add newTypeS(k, c)
|
||||
result = finish b
|
||||
|
||||
proc skipGenericPrev(prev: PType): PType =
|
||||
result = prev
|
||||
@@ -98,15 +120,19 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||
counterSet = initPackedSet[BiggestInt]()
|
||||
counter = 0
|
||||
base = nil
|
||||
result = newOrPrevType(tyEnum, prev, c)
|
||||
result.n = newNodeI(nkEnumTy, n.info)
|
||||
var b = openType(c, tyEnum, prev)
|
||||
b.setN newNodeI(nkEnumTy, n.info)
|
||||
checkMinSonsLen(n, 1, c.config)
|
||||
if n[0].kind != nkEmpty:
|
||||
base = semTypeNode(c, n[0][0], nil)
|
||||
if base.kind != tyEnum:
|
||||
localError(c.config, n[0].info, "inheritance only works with an enum")
|
||||
counter = toInt64(lastOrd(c.config, base)) + 1
|
||||
rawAddSon(result, base)
|
||||
b.addRaw base
|
||||
result = finish b
|
||||
# the type each enum field belongs to; a field referring to its own enum is
|
||||
# the canonical self-reference that will migrate to an id-based `TypePair.id`.
|
||||
let self = typePair(result)
|
||||
let isPure = result.sym != nil and sfPure in result.sym.flags
|
||||
var symbols: TStrTable = initStrTable()
|
||||
var hasNull = false
|
||||
@@ -176,7 +202,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||
elif counterSet.containsOrIncl(counter):
|
||||
localError(c.config, n[i].info, errDuplicateAliasInEnumX % e.name.s)
|
||||
|
||||
e.typ = result
|
||||
e.typ = self.decl
|
||||
e.position = int(counter)
|
||||
let symNode = newSymNode(e)
|
||||
if identToReplace != nil and c.config.cmd notin cmdDocLike:
|
||||
@@ -216,10 +242,11 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||
setToStringProc(c.graph, result, genEnumToStrProc(result, n.info, c.graph, c.idgen))
|
||||
|
||||
proc semSet(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = newOrPrevType(tySet, prev, c)
|
||||
var b = openType(c, tySet, prev)
|
||||
if n.len == 2 and n[1].kind != nkEmpty:
|
||||
var base = semTypeNode(c, n[1], nil)
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
b.add base
|
||||
result = finish b
|
||||
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
|
||||
if base.kind notin {tyGenericParam, tyGenericInvocation}:
|
||||
if base.kind == tyForward:
|
||||
@@ -230,45 +257,49 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
|
||||
localError(c.config, n.info, errSetTooBig)
|
||||
else:
|
||||
localError(c.config, n.info, errXExpectsOneTypeParam % "set")
|
||||
addSonSkipIntLit(result, errorType(c), c.idgen)
|
||||
b.add errorType(c)
|
||||
result = finish b
|
||||
|
||||
proc semContainerArg(c: PContext; n: PNode, kindStr: string; result: PType) =
|
||||
proc semContainerArg(c: PContext; n: PNode, kindStr: string; b: var TypeBuilder) =
|
||||
if n.len == 2:
|
||||
var base = semTypeNode(c, n[1], nil)
|
||||
if base.kind == tyVoid:
|
||||
localError(c.config, n.info, errTIsNotAConcreteType % typeToString(base))
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
b.add base
|
||||
else:
|
||||
localError(c.config, n.info, errXExpectsOneTypeParam % kindStr)
|
||||
addSonSkipIntLit(result, errorType(c), c.idgen)
|
||||
b.add errorType(c)
|
||||
|
||||
proc semContainer(c: PContext, n: PNode, kind: TTypeKind, kindStr: string,
|
||||
prev: PType): PType =
|
||||
result = newOrPrevType(kind, prev, c)
|
||||
semContainerArg(c, n, kindStr, result)
|
||||
var b = openType(c, kind, prev)
|
||||
semContainerArg(c, n, kindStr, b)
|
||||
result = finish b
|
||||
|
||||
proc semVarargs(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = newOrPrevType(tyVarargs, prev, c)
|
||||
var b = openType(c, tyVarargs, prev)
|
||||
if n.len == 2 or n.len == 3:
|
||||
var base = semTypeNode(c, n[1], nil)
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
b.add base
|
||||
if n.len == 3:
|
||||
result.n = newIdentNode(considerQuotedIdent(c, n[2]), n[2].info)
|
||||
b.setN newIdentNode(considerQuotedIdent(c, n[2]), n[2].info)
|
||||
else:
|
||||
localError(c.config, n.info, errXExpectsOneTypeParam % "varargs")
|
||||
addSonSkipIntLit(result, errorType(c), c.idgen)
|
||||
b.add errorType(c)
|
||||
result = finish b
|
||||
|
||||
proc semVarOutType(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType =
|
||||
if n.len == 1:
|
||||
result = newOrPrevType(tyVar, prev, c)
|
||||
result.flags = flags
|
||||
var b = openType(c, tyVar, prev)
|
||||
b.setFlags flags
|
||||
var base = semTypeNode(c, n[0], nil)
|
||||
if base.kind == tyTypeDesc and not isSelf(base):
|
||||
base = base[0]
|
||||
if base.kind == tyVar:
|
||||
localError(c.config, n.info, "type 'var var' is not allowed")
|
||||
base = base[0]
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
b.add base
|
||||
result = finish b
|
||||
else:
|
||||
result = newConstraint(c, tyVar)
|
||||
|
||||
@@ -379,31 +410,38 @@ proc isRecursiveType*(t: PType): bool =
|
||||
var cycleDetector = initIntSet()
|
||||
isRecursiveType(t, cycleDetector)
|
||||
|
||||
proc addSonSkipIntLitChecked(c: PContext; father, son: PType; it: PNode, id: IdGenerator) =
|
||||
let s = son.skipIntLit(id)
|
||||
father.add(s)
|
||||
proc addSonSkipIntLitChecked(c: PContext; b: var TypeBuilder; son: PType; it: PNode) =
|
||||
let s = son.skipIntLit(c.idgen)
|
||||
b.addKeep s
|
||||
if isRecursiveType(s):
|
||||
localError(c.config, it.info, "illegal recursion in type '" & typeToString(s) & "'")
|
||||
else:
|
||||
propagateToOwner(father, s)
|
||||
b.propagateFrom s
|
||||
|
||||
proc semDistinct(c: PContext, n: PNode, prev: PType): PType =
|
||||
if n.len == 0: return newConstraint(c, tyDistinct)
|
||||
if prevIsKind(prev, tyDistinct):
|
||||
# the symbol already has a distinct type (likely resem), don't create a new type
|
||||
return skipGenericPrev(prev)
|
||||
result = newOrPrevType(tyDistinct, prev, c)
|
||||
addSonSkipIntLitChecked(c, result, semTypeNode(c, n[0], nil), n[0], c.idgen)
|
||||
if n.len > 1: result.n = n[1]
|
||||
var b = openType(c, tyDistinct, prev)
|
||||
addSonSkipIntLitChecked(c, b, semTypeNode(c, n[0], nil), n[0])
|
||||
if n.len > 1: b.setN n[1]
|
||||
result = finish b
|
||||
|
||||
proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
|
||||
assert isRange(n)
|
||||
checkSonsLen(n, 3, c.config)
|
||||
result = newOrPrevType(tyRange, prev, c)
|
||||
result.n = newNodeI(nkRange, n.info)
|
||||
# Deferred build: a *valid* tyRange must exist before the throwing
|
||||
# `semExprWithType` below (bug #6895), so its base type is minted as an
|
||||
# `errorType` placeholder son up front and back-patched via `setSon(0, …)`
|
||||
# once the real bounds are known. The `.n` (nkRange bound exprs) and flags
|
||||
# stay direct pokes on the live shell, as in `semProcTypeNode`.
|
||||
var rb = openPair(c, tyRange, prev)
|
||||
rb.setN newNodeI(nkRange, n.info)
|
||||
# always create a 'valid' range type, but overwrite it later
|
||||
# because 'semExprWithType' can raise an exception. See bug #6895.
|
||||
addSonSkipIntLit(result, errorType(c), c.idgen)
|
||||
rb.add errorType(c)
|
||||
result = rb.pair.decl
|
||||
|
||||
if (n[1].kind == nkEmpty) or (n[2].kind == nkEmpty):
|
||||
localError(c.config, n.info, "range is empty")
|
||||
@@ -444,7 +482,9 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
|
||||
if weakLeValue(result.n[0], result.n[1]) == impNo:
|
||||
localError(c.config, n.info, "range is empty")
|
||||
|
||||
result[0] = rangeT[0]
|
||||
# overwrite the placeholder son minted above with the real base type, then seal
|
||||
rb.setSon(0, rangeT[0])
|
||||
result = finishPair(rb).decl
|
||||
|
||||
proc semRange(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = nil
|
||||
@@ -557,29 +597,33 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
|
||||
# ensure we only construct a tyArray when there was no error (bug #3048):
|
||||
# bug #6682: Do not propagate initialization requirements etc for the
|
||||
# index type:
|
||||
result = newOrPrevType(tyArray, prev, c, indx)
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
var b = openType(c, tyArray, prev)
|
||||
b.addKeep indx
|
||||
b.add base
|
||||
result = finish b
|
||||
else:
|
||||
localError(c.config, n.info, errArrayExpectsTwoTypeParams)
|
||||
result = newOrPrevType(tyError, prev, c)
|
||||
|
||||
proc semIterableType(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = newOrPrevType(tyIterable, prev, c)
|
||||
var b = openType(c, tyIterable, prev)
|
||||
if n.len == 2:
|
||||
let base = semTypeNode(c, n[1], nil)
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
b.add base
|
||||
result = finish b
|
||||
else:
|
||||
localError(c.config, n.info, errXExpectsOneTypeParam % "iterable")
|
||||
result = newOrPrevType(tyError, prev, c)
|
||||
|
||||
proc semOrdinal(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = newOrPrevType(tyOrdinal, prev, c)
|
||||
var b = openType(c, tyOrdinal, prev)
|
||||
if n.len == 2:
|
||||
var base = semTypeNode(c, n[1], nil)
|
||||
if base.kind != tyGenericParam:
|
||||
if not isOrdinalType(base):
|
||||
localError(c.config, n[1].info, errOrdinalTypeExpected % typeToString(base, preferDesc))
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
b.add base
|
||||
result = finish b
|
||||
else:
|
||||
localError(c.config, n.info, errXExpectsOneTypeParam % "ordinal")
|
||||
result = newOrPrevType(tyError, prev, c)
|
||||
@@ -587,10 +631,11 @@ proc semOrdinal(c: PContext, n: PNode, prev: PType): PType =
|
||||
proc semAnonTuple(c: PContext, n: PNode, prev: PType): PType =
|
||||
if n.len == 0:
|
||||
localError(c.config, n.info, errTypeExpected)
|
||||
result = newOrPrevType(tyTuple, prev, c)
|
||||
var b = openType(c, tyTuple, prev)
|
||||
for it in n:
|
||||
let t = semTypeNode(c, it, nil)
|
||||
addSonSkipIntLitChecked(c, result, t, it, c.idgen)
|
||||
addSonSkipIntLitChecked(c, b, t, it)
|
||||
result = finish b
|
||||
|
||||
proc firstRange(config: ConfigRef, t: PType): PNode =
|
||||
if t.skipModifier().kind in tyFloat..tyFloat64:
|
||||
@@ -601,8 +646,12 @@ proc firstRange(config: ConfigRef, t: PType): PNode =
|
||||
|
||||
proc semTuple(c: PContext, n: PNode, prev: PType): PType =
|
||||
var typ: PType
|
||||
result = newOrPrevType(tyTuple, prev, c)
|
||||
result.n = newNodeI(nkRecList, n.info)
|
||||
# Deferred build: the tuple's identity is handed to `semFieldDefault` (which
|
||||
# propagates each default field's type into the owner) while its fields/sons
|
||||
# are still being appended -- so it goes through `TypePairBuilder`, publishing
|
||||
# `rb.pair` mid-build rather than `openType ... finish`.
|
||||
var rb = openPair(c, tyTuple, prev)
|
||||
rb.setN newNodeI(nkRecList, n.info)
|
||||
var check = initIntSet()
|
||||
var counter = 0
|
||||
for i in ord(n.kind == nkBracketExpr)..<n.len:
|
||||
@@ -612,7 +661,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
|
||||
var hasDefaultField = a[^1].kind != nkEmpty
|
||||
if hasDefaultField:
|
||||
typ = if a[^2].kind != nkEmpty: semTypeNode(c, a[^2], nil) else: nil
|
||||
typ = semFieldDefault(c, result, typ, a)
|
||||
typ = semFieldDefault(c, rb.pair.decl, typ, a)
|
||||
elif a[^2].kind != nkEmpty:
|
||||
typ = semTypeNode(c, a[^2], nil)
|
||||
if c.graph.config.isDefined("nimPreviewRangeDefault") and typ.skipTypes(abstractInst).kind == tyRange:
|
||||
@@ -633,11 +682,12 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
|
||||
if hasDefaultField:
|
||||
fSym.sym.ast = a[^1]
|
||||
fSym.sym.ast.flags.incl nfSkipFieldChecking
|
||||
result.n.add fSym
|
||||
addSonSkipIntLit(result, typ, c.idgen)
|
||||
rb.addRecField fSym
|
||||
rb.add typ
|
||||
styleCheckDef(c, a[j].info, field)
|
||||
onDef(field.info, field)
|
||||
if result.n.len == 0: result.n = nil
|
||||
if rb.pair.decl.n.len == 0: rb.setN nil
|
||||
result = finishPair(rb).decl
|
||||
if isRecursiveStructuralType(result):
|
||||
localError(c.config, n.info, errIllegalRecursionInTypeX % typeToString(result))
|
||||
|
||||
@@ -1117,17 +1167,24 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
|
||||
base = nil
|
||||
realBase = nil
|
||||
if n.kind != nkObjectTy: internalError(c.config, n.info, "semObjectNode")
|
||||
result = newOrPrevType(tyObject, prev, c)
|
||||
# Deferred build: the object's identity is published to `forwardTypeUpdates`
|
||||
# (a retry pass), to `semRecordNodeAux` (field sem may reference the object
|
||||
# itself), and to the pragma dummy sym -- all before its body is complete. The
|
||||
# son-tree (base son) + initial `.n` (nkRecList) allocation + seal go through
|
||||
# the builder; field growth (via `semRecordNodeAux` into `result.n`) and flags
|
||||
# stay direct pokes on the live shell, as in `semProcTypeNode`/`semRangeAux`.
|
||||
var rb = openPair(c, tyObject, prev)
|
||||
result = rb.pair.decl
|
||||
if needsForwardUpdate:
|
||||
# if the inherited object is a forward type,
|
||||
# the entire object needs to be checked again
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) # we retry in the final pass
|
||||
rawAddSon(result, realBase)
|
||||
rb.addRaw realBase
|
||||
if realBase == nil and tfInheritable in flags:
|
||||
result.incl tfInheritable
|
||||
if tfAcyclic in flags: result.incl tfAcyclic
|
||||
if result.n.isNil:
|
||||
result.n = newNodeI(nkRecList, n.info)
|
||||
rb.setN newNodeI(nkRecList, n.info)
|
||||
else:
|
||||
# partial object so add things to the check
|
||||
if not tryAddInheritedFields(c, check, pos, result, n, isPartial = true):
|
||||
@@ -1143,6 +1200,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
|
||||
incl(result, tfFinal)
|
||||
if c.inGenericContext == 0 and computeRequiresInit(c, result):
|
||||
result.incl tfRequiresInit
|
||||
result = finishPair(rb).decl # seal the deferred object build
|
||||
|
||||
proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
|
||||
if n.len < 1:
|
||||
@@ -1163,7 +1221,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
|
||||
t = t.base
|
||||
if t.kind == tyVoid:
|
||||
localError(c.config, n.info, "type '$1 void' is not allowed" % kind.toHumanStr)
|
||||
result = newOrPrevType(kind, prev, c)
|
||||
var b = openType(c, kind, prev)
|
||||
var isNilable = false
|
||||
var wrapperKind = tyNone
|
||||
# check every except the last is an object:
|
||||
@@ -1179,23 +1237,26 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
|
||||
elif region.skipTypes({tyGenericInst, tyAlias, tySink}).kind notin {
|
||||
tyError, tyObject}:
|
||||
message c.config, n[i].info, errGenerated, "region needs to be an object type"
|
||||
addSonSkipIntLit(result, region, c.idgen)
|
||||
b.add region
|
||||
else:
|
||||
message(c.config, n.info, warnDeprecated, "region for pointer types is deprecated")
|
||||
addSonSkipIntLit(result, region, c.idgen)
|
||||
addSonSkipIntLit(result, t, c.idgen)
|
||||
b.add region
|
||||
b.add t
|
||||
result = finish b
|
||||
if tfPartial in result.flags:
|
||||
if result.elementType.kind == tyObject: incl(result.elementType, tfPartial)
|
||||
# if not isNilable: result.flags.incl tfNotNil
|
||||
case wrapperKind
|
||||
of tyOwned:
|
||||
if optOwnedRefs in c.config.globalOptions:
|
||||
let t = newTypeS(tyOwned, c, result)
|
||||
t.incl tfHasOwned
|
||||
result = t
|
||||
var wrap = openType(c, tyOwned)
|
||||
wrap.addKeep result
|
||||
wrap.incl tfHasOwned
|
||||
result = finish wrap
|
||||
of tySink:
|
||||
let t = newTypeS(tySink, c, result)
|
||||
result = t
|
||||
var wrap = openType(c, tySink)
|
||||
wrap.addKeep result
|
||||
result = finish wrap
|
||||
else: discard
|
||||
if result.kind == tyRef and
|
||||
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc} and
|
||||
@@ -1299,7 +1360,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
let base = (if lifted != nil: lifted else: paramType.base)
|
||||
if base.isMetaType and procKind == skMacro:
|
||||
localError(c.config, info, errMacroBodyDependsOnGenericTypes % paramName)
|
||||
result = addImplicitGeneric(c, newTypeS(tyStatic, c, base),
|
||||
var b = openType(c, tyStatic)
|
||||
b.addKeep base
|
||||
result = addImplicitGeneric(c, finish b,
|
||||
paramTypId, info, genericParams, paramName)
|
||||
if result != nil: result.incl({tfHasStatic, tfUnresolved})
|
||||
|
||||
@@ -1311,9 +1374,10 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
paramTypId.id == getIdent(c.cache, "type").id):
|
||||
# XXX Why doesn't this check for tyTypeDesc instead?
|
||||
paramTypId = nil
|
||||
let t = newTypeS(tyTypeDesc, c, paramType.base)
|
||||
incl t, tfCheckedForDestructor
|
||||
result = addImplicitGeneric(c, t, paramTypId, info, genericParams, paramName)
|
||||
var b = openType(c, tyTypeDesc)
|
||||
b.addKeep paramType.base
|
||||
b.incl tfCheckedForDestructor
|
||||
result = addImplicitGeneric(c, finish b, paramTypId, info, genericParams, paramName)
|
||||
else:
|
||||
result = nil
|
||||
of tyDistinct:
|
||||
@@ -1327,7 +1391,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
for i in 0..<paramType.len:
|
||||
let t = recurse(paramType[i])
|
||||
if t != nil:
|
||||
paramType[i] = t
|
||||
var b = reopen(paramType)
|
||||
b.setSon(i, t)
|
||||
result = paramType
|
||||
|
||||
of tyAlias, tyOwned:
|
||||
@@ -1342,9 +1407,12 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
# Maybe there is another better place to associate
|
||||
# the seq type class with the seq identifier.
|
||||
if paramType.kind == tySequence and paramType.elementType.kind == tyNone:
|
||||
let typ = newTypeS(tyBuiltInTypeClass, c,
|
||||
newTypeS(paramType.kind, c))
|
||||
result = addImplicitGeneric(c, typ, paramTypId, info, genericParams, paramName)
|
||||
# allocate the inner son first so the type-id order matches the old
|
||||
# argument-evaluation order (inner before outer).
|
||||
let inner = newTypeS(paramType.kind, c)
|
||||
var b = openType(c, tyBuiltInTypeClass)
|
||||
b.addKeep inner
|
||||
result = addImplicitGeneric(c, finish b, paramTypId, info, genericParams, paramName)
|
||||
else:
|
||||
result = nil
|
||||
for i in 0..<paramType.len:
|
||||
@@ -1352,32 +1420,38 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
globalError(c.config, info, errIllegalRecursionInTypeX % typeToString(paramType))
|
||||
var lifted = recurse(paramType[i])
|
||||
if lifted != nil:
|
||||
paramType[i] = lifted
|
||||
var b = reopen(paramType)
|
||||
b.setSon(i, lifted)
|
||||
result = paramType
|
||||
|
||||
of tyGenericBody:
|
||||
result = newTypeS(tyGenericInvocation, c)
|
||||
result.rawAddSon(paramType)
|
||||
# A user-type-class body instantiates to a tyUserTypeClassInst, everything
|
||||
# else to a tyGenericInvocation. The kind is decided up front (from the
|
||||
# already-complete `paramType`), so the builder opens with the final tag
|
||||
# rather than the old mint-as-invocation-then-mutate-kind dance.
|
||||
let isUserTypeClass = paramType.typeBodyImpl.kind == tyUserTypeClass
|
||||
var b = openType(c, if isUserTypeClass: tyUserTypeClassInst else: tyGenericInvocation)
|
||||
b.addRaw paramType
|
||||
|
||||
for i in 0..<paramType.len - 1:
|
||||
if paramType[i].kind == tyStatic:
|
||||
var staticCopy = paramType[i].exactReplica(c.idgen)
|
||||
staticCopy.incl tfInferrableStatic
|
||||
result.rawAddSon staticCopy
|
||||
b.addRaw staticCopy
|
||||
else:
|
||||
result.rawAddSon newTypeS(tyAnything, c)
|
||||
b.addRaw newTypeS(tyAnything, c)
|
||||
|
||||
if paramType.typeBodyImpl.kind == tyUserTypeClass:
|
||||
result.kind = tyUserTypeClassInst
|
||||
result.rawAddSon paramType.typeBodyImpl
|
||||
return addImplicitGeneric(c, result, paramTypId, info, genericParams, paramName)
|
||||
if isUserTypeClass:
|
||||
b.addRaw paramType.typeBodyImpl
|
||||
return addImplicitGeneric(c, finish b, paramTypId, info, genericParams, paramName)
|
||||
|
||||
result = finish b
|
||||
let x = instGenericContainer(c, paramType.sym.info, result,
|
||||
allowMetaTypes = true)
|
||||
result = newTypeS(tyCompositeTypeClass, c)
|
||||
result.rawAddSon paramType
|
||||
result.rawAddSon x
|
||||
result = addImplicitGeneric(c, result, paramTypId, info, genericParams, paramName)
|
||||
var cb = openType(c, tyCompositeTypeClass)
|
||||
cb.addRaw paramType
|
||||
cb.addRaw x
|
||||
result = addImplicitGeneric(c, finish cb, paramTypId, info, genericParams, paramName)
|
||||
|
||||
of tyGenericInst:
|
||||
result = nil
|
||||
@@ -1391,7 +1465,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
for i in 1..<paramType.len-1:
|
||||
var lifted = recurse(paramType[i])
|
||||
if lifted != nil:
|
||||
paramType[i] = lifted
|
||||
var b = reopen(paramType)
|
||||
b.setSon(i, lifted)
|
||||
result = paramType
|
||||
result.last.shouldHaveMeta
|
||||
if paramType.isConcept:
|
||||
@@ -1408,7 +1483,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
for i in 1..<paramType.len:
|
||||
#if paramType[i].kind != tyTypeDesc:
|
||||
let lifted = recurse(paramType[i])
|
||||
if lifted != nil: paramType[i] = lifted
|
||||
if lifted != nil:
|
||||
var b = reopen(paramType)
|
||||
b.setSon(i, lifted)
|
||||
|
||||
let body = paramType.base
|
||||
if body.kind in {tyForward, tyError}:
|
||||
@@ -1469,7 +1546,14 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
|
||||
# for historical reasons (code grows) this is invoked for parameter
|
||||
# lists too and then 'isType' is false.
|
||||
checkMinSonsLen(n, 1, c.config)
|
||||
# Deferred build: `newProcType` opens the shell with a nil return-type slot
|
||||
# (son 0) and an effect-list `.n`; params are appended as interleaved son +
|
||||
# `.n` entries below, and son 0 is back-patched once the return type is known.
|
||||
# `openType ... finish` cannot model the placeholder-then-backpatch, so the
|
||||
# son tree grows through a `TypePairBuilder` reopened on the shell. Flags and
|
||||
# `.n.typ` remain direct pokes on the live shell (`result` == `rb.pair.decl`).
|
||||
result = newProcType(c, n.info, prev)
|
||||
var rb = reopenPair(result, c.idgen)
|
||||
var check = initIntSet()
|
||||
var counter = 0
|
||||
template isCurrentlyGeneric: bool =
|
||||
@@ -1553,8 +1637,11 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
|
||||
# which will prevent other types from matching - clearly a very
|
||||
# surprising behavior. We must instead fix the expected type of
|
||||
# the proc to be the unbound typedesc type:
|
||||
typ = newTypeS(tyTypeDesc, c, newTypeS(tyNone, c))
|
||||
typ.incl tfCheckedForDestructor
|
||||
let none = newTypeS(tyNone, c)
|
||||
var b = openType(c, tyTypeDesc)
|
||||
b.addKeep none
|
||||
b.incl tfCheckedForDestructor
|
||||
typ = finish b
|
||||
|
||||
elif def.typ != nil and def.typ.kind != tyFromExpr: # def.typ can be void
|
||||
# if def.typ != nil and def.typ.kind != tyNone:
|
||||
@@ -1604,8 +1691,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
|
||||
inc(counter)
|
||||
if def != nil and def.kind != nkEmpty:
|
||||
arg.ast = copyTree(def)
|
||||
result.n.add newSymNode(arg)
|
||||
rawAddSon(result, finalType)
|
||||
rb.addRecField newSymNode(arg)
|
||||
rb.addRaw finalType
|
||||
addParamOrResult(c, arg, kind)
|
||||
styleCheckDef(c, a[j].info, arg)
|
||||
onDef(a[j].info, arg)
|
||||
@@ -1665,7 +1752,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
|
||||
# we don't need to change the return type to iter[T]
|
||||
result.incl tfIterator
|
||||
# XXX Would be nice if we could get rid of this
|
||||
result[0] = r
|
||||
rb.setSon(0, r)
|
||||
let oldFlags = result.flags
|
||||
propagateToOwner(result, r)
|
||||
if oldFlags != result.flags:
|
||||
@@ -1683,6 +1770,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
|
||||
n.sym.transitionGenericParamToType()
|
||||
n.sym.typ.excl tfWildcard
|
||||
|
||||
result = finishPair(rb).decl # seal the deferred proc-type build
|
||||
|
||||
proc semStmtListType(c: PContext, n: PNode, prev: PType): PType =
|
||||
checkMinSonsLen(n, 1, c.config)
|
||||
for i in 0..<n.len - 1:
|
||||
@@ -1751,24 +1840,32 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
var t = s.typ.skipTypes({tyAlias})
|
||||
if t.kind == tyCompositeTypeClass and t.base.kind == tyGenericBody:
|
||||
t = t.base
|
||||
result = newOrPrevType(tyGenericInvocation, prev, c)
|
||||
addSonSkipIntLit(result, t, c.idgen)
|
||||
# Deferred build: the tyGenericInvocation's identity is published to
|
||||
# `forwardTypeUpdates` (a retry pass) and consumed by `instGenericContainer`,
|
||||
# both only after its arg sons are appended. The son-tree goes through one
|
||||
# deferred `rb`; `result` stays the live shell (later branches may replace it
|
||||
# with an error/forward type or the instantiated container). Sealed once the
|
||||
# args are in, before any consumer reads it.
|
||||
var rb = openPair(c, tyGenericInvocation, prev)
|
||||
result = rb.pair.decl
|
||||
rb.add t
|
||||
|
||||
template addToResult(typ, skip) =
|
||||
|
||||
if typ.isNil:
|
||||
internalAssert c.config, false
|
||||
rawAddSon(result, typ)
|
||||
rb.addRaw typ
|
||||
else:
|
||||
if skip:
|
||||
addSonSkipIntLit(result, typ, c.idgen)
|
||||
rb.add typ
|
||||
else:
|
||||
rawAddSon(result, makeRangeWithStaticExpr(c, typ.n))
|
||||
rb.addRaw makeRangeWithStaticExpr(c, typ.n)
|
||||
|
||||
if t.kind == tyForward:
|
||||
for i in 1..<n.len:
|
||||
var elem = semGenericParamInInvocation(c, n[i])
|
||||
addToResult(elem, true)
|
||||
result = finishPair(rb).decl # seal the deferred invocation build
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
|
||||
return
|
||||
elif t.kind != tyGenericBody:
|
||||
@@ -1816,6 +1913,8 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
if typ.kind == tyForward:
|
||||
hasForwardTypeParam = true
|
||||
|
||||
result = finishPair(rb).decl # seal the deferred invocation build (args complete)
|
||||
|
||||
if isConcrete:
|
||||
if s.ast == nil and s.typ.kind != tyCompositeTypeClass:
|
||||
# XXX: What kind of error is this? is it still relevant?
|
||||
@@ -1870,9 +1969,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType =
|
||||
if prev != nil and (prev.kind == tyGenericBody or
|
||||
typeExpr.kind in {tyObject, tyEnum, tyDistinct, tyForward, tyGenericBody}):
|
||||
result = newTypeS(tyAlias, c)
|
||||
result.rawAddSon typeExpr
|
||||
result.sym = prev.sym
|
||||
var b = openType(c, tyAlias)
|
||||
b.addRaw typeExpr
|
||||
b.setSym prev.sym
|
||||
result = finish b
|
||||
if prev.kind != tyGenericBody:
|
||||
assignType(prev, result)
|
||||
else:
|
||||
@@ -1880,9 +1980,10 @@ proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType =
|
||||
|
||||
proc fixupTypeOf(c: PContext, prev: PType, typ: PType) =
|
||||
if prev != nil:
|
||||
let result = newTypeS(tyAlias, c)
|
||||
result.rawAddSon typ
|
||||
result.sym = prev.sym
|
||||
var b = openType(c, tyAlias)
|
||||
b.addRaw typ
|
||||
b.setSym prev.sym
|
||||
let result = finish b
|
||||
if prev.kind != tyGenericBody:
|
||||
assignType(prev, result)
|
||||
|
||||
@@ -1950,7 +2051,9 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
|
||||
inherited = n[2]
|
||||
|
||||
var owner = getCurrOwner(c)
|
||||
var candidateTypeSlot = newTypeS(tyAlias, c, c.errorType)
|
||||
var slotB = openType(c, tyAlias)
|
||||
slotB.addKeep c.errorType
|
||||
var candidateTypeSlot = finish slotB
|
||||
result = newOrPrevType(tyUserTypeClass, prev, c, son = candidateTypeSlot)
|
||||
result.incl tfCheckedForDestructor
|
||||
result.n = n
|
||||
@@ -2070,10 +2173,11 @@ proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym =
|
||||
result = errorSym(c, n)
|
||||
|
||||
proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType =
|
||||
result = newOrPrevType(tyStatic, prev, c)
|
||||
var b = openType(c, tyStatic, prev)
|
||||
var base = semTypeNode(c, childNode, nil).skipTypes({tyTypeDesc, tyAlias})
|
||||
result.rawAddSon(base)
|
||||
result.incl tfHasStatic
|
||||
b.addRaw base
|
||||
b.incl tfHasStatic
|
||||
result = finish b
|
||||
|
||||
proc semTypeOfImpl(c: PContext; n: PNode): PNode =
|
||||
var m = BiggestInt 1 # typeOfIter
|
||||
@@ -2397,20 +2501,23 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
let old = result
|
||||
result = copyType(result, c.idgen, getCurrOwner(c))
|
||||
copyTypeProps(c.graph, c.idgen.module, result, old)
|
||||
var b = reopen(result, c.idgen)
|
||||
for i in 1..<n.len:
|
||||
result.rawAddSon(semTypeNode(c, n[i], nil))
|
||||
b.addRaw(semTypeNode(c, n[i], nil))
|
||||
of mDistinct:
|
||||
checkSonsLen(n, 2, c.config)
|
||||
result = newOrPrevType(tyDistinct, prev, c)
|
||||
addSonSkipIntLit(result, semTypeNode(c, n[1], nil), c.idgen)
|
||||
var b = openType(c, tyDistinct, prev)
|
||||
b.add semTypeNode(c, n[1], nil)
|
||||
result = finish b
|
||||
of mVar:
|
||||
checkSonsLen(n, 2, c.config)
|
||||
result = newOrPrevType(tyVar, prev, c)
|
||||
var b = openType(c, tyVar, prev)
|
||||
var base = semTypeNode(c, n[1], nil)
|
||||
if base.kind in {tyVar, tyLent}:
|
||||
localError(c.config, n.info, "type 'var var' is not allowed")
|
||||
base = base[0]
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
b.add base
|
||||
result = finish b
|
||||
of mRef: result = semAnyRef(c, n, tyRef, prev)
|
||||
of mPtr: result = semAnyRef(c, n, tyPtr, prev)
|
||||
of mTuple: result = semTuple(c, n, prev)
|
||||
@@ -2506,7 +2613,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
of nkProcTy, nkIteratorTy:
|
||||
if n.len == 0 or n[0].kind == nkEmpty:
|
||||
# 0 length or empty param list with possible pragmas imply typeclass
|
||||
result = newTypeS(tyBuiltInTypeClass, c)
|
||||
var b = openType(c, tyBuiltInTypeClass)
|
||||
let child = newTypeS(tyProc, c)
|
||||
if n.kind == nkIteratorTy:
|
||||
child.incl tfIterator
|
||||
@@ -2518,7 +2625,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
s.typ = child
|
||||
# for now only call convention pragmas supported in proc typeclass
|
||||
pragma(c, s, n[1], {FirstCallConv..LastCallConv})
|
||||
result.addSonSkipIntLit(child, c.idgen)
|
||||
b.add child
|
||||
result = finish b
|
||||
else:
|
||||
let symKind = if n.kind == nkIteratorTy: skIterator else: skProc
|
||||
result = semProcTypeWithScope(c, n, prev, symKind)
|
||||
@@ -2647,7 +2755,9 @@ proc processMagicType(c: PContext, m: PSym) =
|
||||
else: localError(c.config, m.info, errTypeExpected)
|
||||
|
||||
proc semGenericConstraints(c: PContext, x: PType): PType =
|
||||
result = newTypeS(tyGenericParam, c, x)
|
||||
var b = openType(c, tyGenericParam)
|
||||
b.addKeep x
|
||||
result = finish b
|
||||
|
||||
proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
|
||||
|
||||
@@ -2674,8 +2784,11 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
|
||||
if typ.kind != tyStatic or typ.len == 0:
|
||||
if typ.kind == tyTypeDesc:
|
||||
if typ.elementType.kind == tyNone:
|
||||
typ = newTypeS(tyTypeDesc, c, newTypeS(tyNone, c))
|
||||
incl typ, tfCheckedForDestructor
|
||||
let none = newTypeS(tyNone, c)
|
||||
var b = openType(c, tyTypeDesc)
|
||||
b.addKeep none
|
||||
b.incl tfCheckedForDestructor
|
||||
typ = finish b
|
||||
else:
|
||||
typ = semGenericConstraints(c, typ)
|
||||
|
||||
@@ -2683,7 +2796,9 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
|
||||
def = semConstExpr(c, def)
|
||||
if typ == nil:
|
||||
if def.typ.kind != tyTypeDesc:
|
||||
typ = newTypeS(tyStatic, c, def.typ)
|
||||
var b = openType(c, tyStatic)
|
||||
b.addKeep def.typ
|
||||
typ = finish b
|
||||
else:
|
||||
# the following line fixes ``TV2*[T:SomeNumber=TR] = array[0..1, T]``
|
||||
# from manyloc/named_argument_bug/triengine:
|
||||
|
||||
@@ -57,12 +57,17 @@ proc searchInstTypes*(g: ModuleGraph; key: PType): PType =
|
||||
|
||||
return inst
|
||||
|
||||
proc cacheTypeInst(c: PContext; inst: PType) =
|
||||
let gt = inst[0]
|
||||
proc cacheTypeInst(c: PContext; inst: TypePair) =
|
||||
# Publishes an in-progress instance under its name, for recursive
|
||||
# instantiations. Takes the (identity, tree) pair rather than a bare `PType`:
|
||||
# the cache key is derived from the generic head's identity, and only the
|
||||
# instance's identity is registered -- today via `inst.decl`, under NIF via
|
||||
# `inst.id`.
|
||||
let gt = inst.decl[0]
|
||||
let t = if gt.kind == tyGenericBody: gt.typeBodyImpl else: gt
|
||||
if t.kind in {tyStatic, tyError, tyGenericParam} + tyTypeClasses:
|
||||
return
|
||||
addToGenericCache(c, gt.sym, inst)
|
||||
addToGenericCache(c, gt.sym, inst.decl)
|
||||
|
||||
type
|
||||
TReplTypeVars* = object
|
||||
@@ -466,7 +471,8 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
x = lookupTypeVar(cl, x)
|
||||
if x != nil:
|
||||
if header == t: header = instCopyType(cl, t)
|
||||
header[i] = x
|
||||
var hb = reopen(header)
|
||||
hb.setSon(i, x)
|
||||
propagateToOwner(header, x)
|
||||
else:
|
||||
# Under IC `t` may be a loaded dep type (Sealed/immutable); mutating it
|
||||
@@ -493,16 +499,17 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
# the generic body's module (`t.genericHead.owner`) has no business owning a
|
||||
# type that references instantiation-site types — that is the IC parent->child
|
||||
# heap leak the write-barrier surfaces.
|
||||
result = newType(tyGenericInst, cl.c.idgen, cl.c.module, son = header.genericHead)
|
||||
result.flags = header.flags
|
||||
var rb = openPair(tyGenericInst, cl.c.idgen, cl.c.module, son = header.genericHead)
|
||||
rb.setFlags header.flags
|
||||
# be careful not to propagate unnecessary flags here (don't use rawAddSon)
|
||||
# ugh need another pass for deeply recursive generic types (e.g. PActor)
|
||||
# we need to add the candidate here, before it's fully instantiated for
|
||||
# recursive instantions:
|
||||
# recursive instantions: publish the instance's *identity* (`rb.pair`) while
|
||||
# its body is still open, so recursive instantiations find it under its name.
|
||||
if not cl.allowMetaTypes:
|
||||
cacheTypeInst(cl.c, result)
|
||||
cacheTypeInst(cl.c, rb.pair)
|
||||
else:
|
||||
cl.localCache[t.itemId] = result
|
||||
cl.localCache[t.itemId] = rb.pair.decl
|
||||
|
||||
let oldSkipTypedesc = cl.skipTypedesc
|
||||
cl.skipTypedesc = true
|
||||
@@ -516,17 +523,18 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
else:
|
||||
header[i]
|
||||
assert x.kind != tyGenericInvocation
|
||||
header[i] = x
|
||||
var hb = reopen(header)
|
||||
hb.setSon(i, x)
|
||||
propagateToOwner(header, x)
|
||||
cl.typeMap.put(body[i-1], x)
|
||||
|
||||
for i in FirstGenericParamAt..<t.kidsLen:
|
||||
# if one of the params is not concrete, we cannot do anything
|
||||
# but we already raised an error!
|
||||
rawAddSon(result, header[i], propagateHasAsgn = false)
|
||||
rb.addRaw(header[i], propagateHasAsgn = false)
|
||||
|
||||
if body.kind == tyError:
|
||||
return
|
||||
return finishPair(rb).decl
|
||||
|
||||
let bbody = last body
|
||||
var newbody = replaceTypeVarsT(cl, bbody, isInstValue = true)
|
||||
@@ -538,7 +546,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
# builtin like `int` when the generic's body is computed by a macro) and is
|
||||
# immutable under IC. Skip the in-place flag accumulation on the shared
|
||||
# type; the instance `result` still receives the flags below.
|
||||
result.flags = result.flags + newbodyFlags - tfInstClearedFlags
|
||||
rb.setFlags(rb.flags + newbodyFlags - tfInstClearedFlags)
|
||||
|
||||
setToPreviousLayer(cl.typeMap)
|
||||
|
||||
@@ -549,7 +557,8 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
# handleGenericInvocation will handle the alias-to-alias-to-alias case
|
||||
if newbody.isGenericAlias: newbody = newbody.skipGenericAlias
|
||||
|
||||
rawAddSon(result, newbody)
|
||||
rb.addRaw newbody
|
||||
result = finishPair(rb).decl
|
||||
checkPartialConstructedType(cl.c.config, cl.info, newbody)
|
||||
if not cl.allowMetaTypes:
|
||||
let dc = cl.c.graph.getAttachedOp(newbody, attachedDeepCopy)
|
||||
@@ -614,10 +623,11 @@ proc eraseTupleVoidFields*(t: PType) =
|
||||
if t.n[i].kind == nkRecList or t[i].kind == tyVoid:
|
||||
# found first void field, compact from here
|
||||
var pos = i
|
||||
var b = reopen(t)
|
||||
for j in i+1..<t.kidsLen:
|
||||
if t[j].kind != tyVoid and j < t.n.len and t.n[j].kind != nkRecList:
|
||||
t.n[pos] = t.n[j]
|
||||
t[pos] = t[j]
|
||||
b.setSon(pos, t[j])
|
||||
if t.n[pos].kind == nkSym:
|
||||
t.n[pos].sym.position = pos
|
||||
inc pos
|
||||
@@ -627,11 +637,12 @@ proc eraseTupleVoidFields*(t: PType) =
|
||||
break
|
||||
|
||||
proc skipIntLiteralParams*(t: PType; idgen: IdGenerator) =
|
||||
var b = reopen(t)
|
||||
for i, p in t.ikids:
|
||||
if p == nil: continue
|
||||
let skipped = p.skipIntLit(idgen)
|
||||
if skipped != p:
|
||||
t[i] = skipped
|
||||
b.setSon(i, skipped)
|
||||
if i > 0: t.n[i].sym.typ = skipped
|
||||
|
||||
# when the typeof operator is used on a static input
|
||||
@@ -769,11 +780,12 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
bailout()
|
||||
result = instCopyType(cl, t)
|
||||
cl.localCache[t.itemId] = result
|
||||
var b = reopen(result)
|
||||
for i in FirstGenericParamAt..<result.kidsLen:
|
||||
var r = result[i]
|
||||
if r != nil:
|
||||
r = replaceTypeVarsT(cl, r)
|
||||
result[i] = r
|
||||
b.setSon(i, r)
|
||||
propagateToOwner(result, r)
|
||||
result.n = replaceTypeVarsN(cl, result.n)
|
||||
if not cl.allowMetaTypes and result.n != nil and
|
||||
@@ -785,8 +797,9 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
bailout()
|
||||
result = instCopyType(cl, t)
|
||||
cl.localCache[t.itemId] = result
|
||||
var b = reopen(result)
|
||||
for i in FirstGenericParamAt..<result.kidsLen:
|
||||
result[i] = replaceTypeVarsT(cl, result[i])
|
||||
b.setSon(i, replaceTypeVarsT(cl, result[i]))
|
||||
propagateToOwner(result, result.last)
|
||||
|
||||
else:
|
||||
@@ -802,6 +815,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
cl.localCache[t.itemId] = result
|
||||
let propagateInstValue = isInstValue and isRefPtrObject(t)
|
||||
|
||||
var b = reopen(result)
|
||||
for i, resulti in result.ikids:
|
||||
if resulti != nil:
|
||||
if resulti.kind == tyGenericBody and not cl.allowMetaTypes:
|
||||
@@ -817,7 +831,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
if r2.kind in {tyPtr, tyRef}:
|
||||
r = skipTypes(r2, {tyPtr, tyRef})
|
||||
if result.kind != tyProc or i == 0:
|
||||
result[i] = r
|
||||
b.setSon(i, r)
|
||||
if result.kind != tyArray or i != 0:
|
||||
propagateToOwner(result, r)
|
||||
# bug #4677: Do not instantiate effect lists
|
||||
|
||||
@@ -274,7 +274,6 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
c.hashTree(t.n, {}, conf)
|
||||
of tyTuple:
|
||||
c &= char(t.kind)
|
||||
c &= t.len
|
||||
if t.n != nil and CoType notin flags:
|
||||
for i in 0..<t.n.len:
|
||||
assert(t.n[i].kind == nkSym)
|
||||
|
||||
@@ -884,11 +884,13 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
|
||||
|
||||
openScope(c)
|
||||
matchedConceptContext.candidateType = a
|
||||
typeClass[0][0] = a
|
||||
var tcb = reopen(typeClass[0])
|
||||
tcb.setSon(0, a)
|
||||
c.matchedConcept = addr(matchedConceptContext)
|
||||
defer:
|
||||
c.matchedConcept = prevMatchedConcept
|
||||
typeClass[0][0] = prevCandidateType
|
||||
var tcb2 = reopen(typeClass[0])
|
||||
tcb2.setSon(0, prevCandidateType)
|
||||
closeScope(c)
|
||||
|
||||
var typeParams: seq[(PSym, PType)] = @[]
|
||||
|
||||
231
compiler/typebuilders.nim
Normal file
231
compiler/typebuilders.nim
Normal file
@@ -0,0 +1,231 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2026 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## `TypeBuilder`: a small, TokenBuf-shaped surface for constructing a `PType`.
|
||||
##
|
||||
## The compiler builds types by creating a mutable `TType` and then poking its
|
||||
## sons/flags/`n` field into place at various call sites. This module funnels
|
||||
## that construction through a builder object instead:
|
||||
##
|
||||
## ```nim
|
||||
## var b = openType(kind, idgen, owner) # or openType(c, kind) with a PContext
|
||||
## b.incl someFlag
|
||||
## b.add someSon # skip int-lit + propagate flags
|
||||
## b.setN someNode
|
||||
## result = finish b
|
||||
## ```
|
||||
##
|
||||
## Today the backing store is a mutable `TType`, so `finish` simply returns it
|
||||
## and there is no runtime cost. The point of routing construction through the
|
||||
## builder is that the backing store can later become a nifcore `TokenBuf` --
|
||||
## with `openType` becoming `openTag`, the `add*` family becoming token/subtree
|
||||
## appends, and `finish` becoming `beginRead` yielding a read-only cursor --
|
||||
## *without touching any call site*.
|
||||
##
|
||||
## Contract: fully configure the type between `openType` and `finish`, and treat
|
||||
## the `finish` result as immutable. Types that need their identity before their
|
||||
## body is complete (recursive / deferred object types, e.g. `semEnum` and
|
||||
## object bodies) are out of scope and keep using the direct `newType` /
|
||||
## `rawAddSon` API for now; they will be modeled via symbol indirection later.
|
||||
|
||||
import ast
|
||||
from itemids import ItemId
|
||||
|
||||
type
|
||||
SymId* = ItemId
|
||||
## The stable identity of a type. Today an `ItemId`; this alias marks every
|
||||
## place that will migrate to a content-based nifcore `SymId` later (once
|
||||
## stable, content-derived names land -- so generic instances dedup across
|
||||
## modules and processes). Keeping the alias means that migration is a
|
||||
## one-line change here rather than a churn across call sites.
|
||||
TypePair* = object
|
||||
## A type as an (identity, tree) pair: `id` names it, `decl` is its tree.
|
||||
## Today `decl.itemId == id`, so the pair is a thin, forward-looking handle
|
||||
## -- the handle a *named* type's body uses to refer to itself (owner /
|
||||
## recursive references). Its real payoff arrives when `decl` becomes a
|
||||
## nameless `NifCursor` and those self/forward references go through `id`.
|
||||
id*: SymId
|
||||
decl*: PType
|
||||
|
||||
proc typePair*(t: PType): TypePair {.inline.} =
|
||||
TypePair(id: t.itemId, decl: t)
|
||||
|
||||
proc skipIntLit*(t: PType; id: IdGenerator): PType {.inline.} =
|
||||
if t.n != nil and t.kind in {tyInt, tyFloat}:
|
||||
result = copyType(t, id, t.owner)
|
||||
result.n = nil
|
||||
else:
|
||||
result = t
|
||||
|
||||
proc addSonSkipIntLit*(father, son: PType; id: IdGenerator) =
|
||||
let s = son.skipIntLit(id)
|
||||
father.add(s)
|
||||
propagateToOwner(father, s)
|
||||
|
||||
type
|
||||
TypeBuilder* = object
|
||||
t: PType
|
||||
idgen {.cursor.}: IdGenerator
|
||||
## non-owning: the id generator outlives every builder (it lives for the
|
||||
## whole compilation), so it must not be reference-counted here.
|
||||
|
||||
proc openType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym): TypeBuilder {.inline.} =
|
||||
## Begins a fresh type of the given `kind`. Mirrors `newType`.
|
||||
TypeBuilder(t: newType(kind, idgen, owner), idgen: idgen)
|
||||
|
||||
proc add*(b: var TypeBuilder; son: PType) {.inline.} =
|
||||
## Adds a son, skipping an int-literal wrapper and propagating type flags to
|
||||
## the owner. Mirrors `addSonSkipIntLit` -- the common case.
|
||||
addSonSkipIntLit(b.t, son, b.idgen)
|
||||
|
||||
proc addRaw*(b: var TypeBuilder; son: PType; propagateHasAsgn = true) {.inline.} =
|
||||
## Adds a son verbatim (no int-lit skip) but still propagates type flags.
|
||||
## Mirrors `rawAddSon`.
|
||||
rawAddSon(b.t, son, propagateHasAsgn)
|
||||
|
||||
proc addKeep*(b: var TypeBuilder; son: PType) {.inline.} =
|
||||
## Adds a son verbatim: no int-lit skip and no flag propagation. Mirrors the
|
||||
## `newType(..., son = x)` fast path -- including that a nil son is skipped
|
||||
## (produces a childless type) rather than added.
|
||||
if son != nil: b.t.add son
|
||||
|
||||
proc reopen*(t: PType; idgen: IdGenerator): TypeBuilder {.inline.} =
|
||||
## Continues building an *existing* type in place, preserving its identity
|
||||
## (`itemId`). Used to bind a freshly-built structure onto the reserved name
|
||||
## of a forward-declared / partial type -- the "distinguish name from tree"
|
||||
## case: the name (`t`) stays, only its tree structure is (re)built.
|
||||
TypeBuilder(t: t, idgen: idgen)
|
||||
|
||||
proc reopen*(t: PType): TypeBuilder {.inline.} =
|
||||
## Reopens an existing type purely to *transform* its sons in place (see
|
||||
## `setSon`), without adding fresh ones -- so no `idgen` is needed. This is the
|
||||
## "mutable staging buffer" seam for son-replacement: today it is in-place
|
||||
## mutation of `t`; under NIF `reopen` thaws `t`'s sealed cursor into a mutable
|
||||
## buffer, `setSon` rewrites a token, and the buffer is re-sealed. Distinct from
|
||||
## the id-minting `reopen(t, idgen)` used to (re)build a forward type's body.
|
||||
TypeBuilder(t: t, idgen: nil)
|
||||
|
||||
proc setSon*(b: var TypeBuilder; i: int; son: PType) {.inline.} =
|
||||
## Replaces son `i` of a reopened type -- the in-place transform seam. Mirrors
|
||||
## the old `PType.[]=` (via `ast.replaceSon`), including the `tyProc` return/
|
||||
## param slot handling. Distinct from `add` (append a new son) and from the
|
||||
## whole-list `ast.setSon(dest, son)`. Under NIF this is a token rewrite in the
|
||||
## buffer thawed by `reopen`.
|
||||
replaceSon(b.t, i, son)
|
||||
|
||||
proc setSon*(b: var TypeBuilder; i: BackwardsIndex; son: PType) {.inline.} =
|
||||
replaceSon(b.t, i, son)
|
||||
|
||||
proc setN*(b: var TypeBuilder; n: PNode) {.inline.} =
|
||||
b.t.n = n
|
||||
|
||||
proc setFlags*(b: var TypeBuilder; flags: TTypeFlags) {.inline.} =
|
||||
## Replaces the whole flag set (assignment, not union). Mirrors `t.flags = x`.
|
||||
b.t.flags = flags
|
||||
|
||||
proc incl*(b: var TypeBuilder; flag: TTypeFlag) {.inline.} =
|
||||
b.t.incl flag
|
||||
|
||||
proc incl*(b: var TypeBuilder; flags: TTypeFlags) {.inline.} =
|
||||
b.t.incl flags
|
||||
|
||||
proc propagateFrom*(b: var TypeBuilder; son: PType; propagateHasAsgn = true) {.inline.} =
|
||||
## Propagates a son type's properties (flags, owner) into the type under
|
||||
## construction. Mirrors a bare `propagateToOwner(result, son)`.
|
||||
propagateToOwner(b.t, son, propagateHasAsgn)
|
||||
|
||||
proc setCallConv*(b: var TypeBuilder; cc: TCallingConvention) {.inline.} =
|
||||
b.t.callConv = cc
|
||||
|
||||
proc setSym*(b: var TypeBuilder; s: PSym) {.inline.} =
|
||||
b.t.sym = s
|
||||
|
||||
template finish*(b: TypeBuilder): PType =
|
||||
## Hands out the constructed type. A template so it collapses to a bare field
|
||||
## read with no call/move/destroy overhead over the old direct construction.
|
||||
## Later this becomes `beginRead`, yielding a read-only cursor.
|
||||
b.t
|
||||
|
||||
type
|
||||
TypePairBuilder* = object
|
||||
## The *deferred* construction seam: like `TypeBuilder`, but its identity is
|
||||
## published -- cached, stashed for a later pass, or handed to a recursive
|
||||
## sem call -- *before* its body is finished. Recursive generic
|
||||
## instantiation needs the in-progress instance to be findable under its
|
||||
## name while its sons are still being appended; `TypeBuilder` cannot model
|
||||
## that because `finish` is the seal point and nothing may be appended after
|
||||
## it. `TypePairBuilder` can, because the thing it hands out early is a
|
||||
## `TypePair` -- an (identity, tree) pair -- and early consumers take only
|
||||
## its `id`.
|
||||
##
|
||||
## Contract: whatever observes `pair` before `finishPair` must rely on
|
||||
## `pair.id` (the name) alone -- never the son count or son contents of the
|
||||
## still-open `decl`. Today `decl` is the growing `PType` and `decl.itemId
|
||||
## == id`, so this holds trivially; under NIF `id` is a `SymId` valid the
|
||||
## instant the shell exists and `decl` is the open `TokenBuf`, sealed into a
|
||||
## read-only cursor by `finishPair`.
|
||||
t: PType
|
||||
idgen {.cursor.}: IdGenerator
|
||||
|
||||
proc openPair*(kind: TTypeKind; idgen: IdGenerator; owner: PSym;
|
||||
son: sink PType = nil): TypePairBuilder {.inline.} =
|
||||
## Mints the shell (optionally with `son0` already set -- e.g. the generic
|
||||
## head for `tyGenericInst`). Mirrors `newType(kind, idgen, owner, son)`. The
|
||||
## `pair` is publishable the moment this returns.
|
||||
TypePairBuilder(t: newType(kind, idgen, owner, son), idgen: idgen)
|
||||
|
||||
proc reopenPair*(t: PType; idgen: IdGenerator): TypePairBuilder {.inline.} =
|
||||
## Continues building an *existing* (forward-declared / partial) type as a
|
||||
## deferred pair, preserving its identity. The deferred analogue of
|
||||
## `reopen(t, idgen)`; its `pair` is publishable immediately.
|
||||
TypePairBuilder(t: t, idgen: idgen)
|
||||
|
||||
proc pair*(b: TypePairBuilder): TypePair {.inline.} =
|
||||
## The publishable (identity, tree) handle -- cache it / stash it / thread it
|
||||
## through recursive sem *before* the body is complete. Only `pair.id` may be
|
||||
## relied upon by those early consumers.
|
||||
typePair(b.t)
|
||||
|
||||
proc add*(b: var TypePairBuilder; son: PType) {.inline.} =
|
||||
addSonSkipIntLit(b.t, son, b.idgen)
|
||||
|
||||
proc addRaw*(b: var TypePairBuilder; son: PType; propagateHasAsgn = true) {.inline.} =
|
||||
## Appends a son verbatim while the body is open. Mirrors `rawAddSon` -- the
|
||||
## incremental-append step of the deferred build.
|
||||
rawAddSon(b.t, son, propagateHasAsgn)
|
||||
|
||||
proc setSon*(b: var TypePairBuilder; i: int; son: PType) {.inline.} =
|
||||
replaceSon(b.t, i, son)
|
||||
|
||||
proc setN*(b: var TypePairBuilder; n: PNode) {.inline.} =
|
||||
b.t.n = n
|
||||
|
||||
proc addRecField*(b: var TypePairBuilder; fieldNode: PNode) {.inline.} =
|
||||
## Appends a field entry to the type's record list (`.n`), the way tuple /
|
||||
## object / proc bodies grow their `nkRecList` / `nkFormalParams`. Mirrors
|
||||
## `t.n.add fieldNode`, and pairs with `add`/`addRaw` for the parallel son.
|
||||
b.t.n.add fieldNode
|
||||
|
||||
proc flags*(b: TypePairBuilder): TTypeFlags {.inline.} =
|
||||
## Reads the shell's current flags (they may have accumulated via `addRaw`'s
|
||||
## propagation since the last `setFlags`).
|
||||
b.t.flags
|
||||
|
||||
proc setFlags*(b: var TypePairBuilder; flags: TTypeFlags) {.inline.} =
|
||||
b.t.flags = flags
|
||||
|
||||
proc incl*(b: var TypePairBuilder; flag: TTypeFlag) {.inline.} =
|
||||
b.t.incl flag
|
||||
|
||||
proc finishPair*(b: sink TypePairBuilder): TypePair {.inline.} =
|
||||
## Seals the deferred build. Today returns the pair unchanged; under NIF this
|
||||
## is `beginRead` -- the open `TokenBuf` becomes a read-only cursor, still
|
||||
## reachable through `pair.id`, so recursive references bound to the name now
|
||||
## resolve to the sealed tree.
|
||||
typePair(b.t)
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import
|
||||
ast, astalgo, trees, msgs, platform, renderer, options,
|
||||
lineinfos, int128, modulegraphs, astmsgs, wordrecg
|
||||
lineinfos, int128, modulegraphs, astmsgs, wordrecg, typebuilders
|
||||
|
||||
import std/[intsets, strutils]
|
||||
|
||||
@@ -1267,7 +1267,8 @@ proc baseOfDistinct*(t: PType; g: ModuleGraph; idgen: IdGenerator): PType =
|
||||
parent = it
|
||||
it = it.elementType
|
||||
if it.kind == tyDistinct and parent != nil:
|
||||
parent[0] = it[0]
|
||||
var b = reopen(parent)
|
||||
b.setSon(0, it[0])
|
||||
|
||||
proc safeInheritanceDiff*(a, b: PType): int =
|
||||
# same as inheritanceDiff but checks for tyError:
|
||||
@@ -1444,7 +1445,8 @@ proc takeType*(formal, arg: PType; g: ModuleGraph; idgen: IdGenerator): PType =
|
||||
arg.isEmptyContainer:
|
||||
let a = copyType(arg.skipTypes({tyGenericInst, tyAlias}), idgen, arg.owner)
|
||||
copyTypeProps(g, idgen.module, a, arg)
|
||||
a[ord(arg.kind == tyArray)] = formal[0]
|
||||
var b = reopen(a)
|
||||
b.setSon(ord(arg.kind == tyArray), formal[0])
|
||||
result = a
|
||||
elif formal.kind in {tyTuple, tySet} and arg.kind == formal.kind:
|
||||
result = formal
|
||||
|
||||
@@ -851,26 +851,14 @@ proc genBinaryStmt(c: PCtx; n: PNode; opc: TOpcode) =
|
||||
c.freeTemp(tmp)
|
||||
c.freeTemp(dest)
|
||||
|
||||
proc genMutatingValue(c: PCtx; n: PNode): TRegister =
|
||||
## Loads the value of an in-place mutation target while keeping it attached to
|
||||
## its original storage. Compound lvalues must be resolved through their
|
||||
## address: a normal value load can return a detached copy (for example, when
|
||||
## indexing a broadcast default array).
|
||||
if needsAsgnPatch(n):
|
||||
let address = c.genx(n, {gfNodeAddr})
|
||||
result = c.getTemp(n.typ)
|
||||
c.gABC(n, opcLdDeref, result, address)
|
||||
c.freeTemp(address)
|
||||
else:
|
||||
result = c.genx(n)
|
||||
|
||||
proc genBinaryStmtVar(c: PCtx; n: PNode; opc: TOpcode) =
|
||||
var x = n[1]
|
||||
if x.kind in {nkAddr, nkHiddenAddr}: x = x[0]
|
||||
let
|
||||
dest = c.genMutatingValue(x)
|
||||
dest = c.genx(x)
|
||||
tmp = c.genx(n[2])
|
||||
c.gABC(n, opc, dest, tmp, 0)
|
||||
#c.genAsgnPatch(n[1], dest)
|
||||
c.freeTemp(tmp)
|
||||
c.freeTemp(dest)
|
||||
|
||||
@@ -1174,7 +1162,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}, m: TMag
|
||||
|
||||
of mIncl, mExcl:
|
||||
unused(c, n, dest)
|
||||
var d = c.genMutatingValue(n[1])
|
||||
var d = c.genx(n[1])
|
||||
var tmp = c.genx(n[2])
|
||||
c.genSetType(n[1], d)
|
||||
c.gABC(n, if m == mIncl: opcIncl else: opcExcl, d, tmp)
|
||||
|
||||
@@ -21,7 +21,6 @@ Advanced commands:
|
||||
see also: --dump.format:json (useful with: `| jq`)
|
||||
//check checks the project for syntax and semantics
|
||||
(can be combined with --defusages)
|
||||
//track goto-definition / find-usages via `nim ic`
|
||||
|
||||
Runtime checks (see -x):
|
||||
--objChecks:on|off turn obj conversion checks on|off
|
||||
@@ -34,8 +33,6 @@ Runtime checks (see -x):
|
||||
--infChecks:on|off turn Inf checks on|off
|
||||
|
||||
Advanced options:
|
||||
--def:FILE,LINE,COL find the definition of the symbol at the position
|
||||
--usages:FILE,LINE,COL find all usages of the symbol at the position
|
||||
--defusages:FILE,LINE,COL
|
||||
find the definition and all usages of a symbol
|
||||
-o:FILE, --out:FILE set the output filename
|
||||
@@ -122,7 +119,6 @@ Advanced options:
|
||||
--lineDir:on|off generation of #line directive on|off
|
||||
--embedsrc:on|off embeds the original source code as comments
|
||||
in the generated output
|
||||
--genBif:on|off generate per-module semantic BIF metadata in nimcache
|
||||
--tlsEmulation:on|off turn thread local storage emulation on|off
|
||||
--implicitStatic:on|off turn implicit compile time evaluation on|off
|
||||
--trmacros:on|off turn term rewriting macros on|off
|
||||
|
||||
10
doc/ic.md
10
doc/ic.md
@@ -39,16 +39,6 @@ debugging a build).
|
||||
Artifacts (the NIF zoo)
|
||||
=======================
|
||||
|
||||
Semantic BIF from regular builds
|
||||
--------------------------------
|
||||
|
||||
``--genBif:on`` makes a regular compiler invocation write each semantically
|
||||
checked module as ``<suffix>.s.bif`` under the build's nimcache directory. This
|
||||
reuses the semantic artifact format used by IC without enabling incremental
|
||||
compilation or changing how the program is generated and linked. Tools such as
|
||||
language servers, debuggers, and binding generators can request these artifacts
|
||||
when they need resolved symbols and types from an ordinary build.
|
||||
|
||||
Per module ``<suffix>`` (a content hash of the path; see *NIF symbols* below),
|
||||
under the nimcache directory:
|
||||
|
||||
|
||||
8
koch.nim
8
koch.nim
@@ -11,16 +11,16 @@
|
||||
|
||||
const
|
||||
# examples of possible values for repos: Head, ea82b54
|
||||
NimbleStableCommit = "a399f502dec7ffcd905c1cf54b13274ad990bada" # 0.24.1
|
||||
NimbleStableCommit = "42ef70c2102a942c46f13eb76872326edd525cec" # 0.22.3
|
||||
AtlasStableCommit = "aa6fb162006f3015aa84c4305e15cb4d230f5ad6" # 0.14.7
|
||||
ChecksumsStableCommit = "5c132cd332cce5d64a0da9ac3e4c9664313dccb4" # 0.2.2
|
||||
SatStableCommit = "9d52513b3c68bfb929dbd687d4fb2836cfee6936"
|
||||
|
||||
NimonyStableCommit = "f831b953d7c21d9a4b11d0042039e7f84d7c8dc9" # unversioned \
|
||||
NimonyStableCommit = "6f9ac6655dc6724ae4e5ccb93b8123c18d54391a" # unversioned \
|
||||
# Note that Nimony uses Nim as a git submodule but we don't want to install
|
||||
# Nimony's dependency to Nim as we are Nim. So a `git clone` without --recursive
|
||||
# is **required** here.
|
||||
# Commit from 2026-07-10 -- stable .bif file format
|
||||
# Commit from 2026-07-03 -- .bif files are memory mapped too
|
||||
|
||||
# examples of possible values for fusion: #head, #ea82b54, 1.2.3
|
||||
FusionStableHash = "#562467452b32cb7a97410ea177f083e6d8405734"
|
||||
@@ -619,7 +619,7 @@ proc runIcTestFile(inp: string) =
|
||||
const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils",
|
||||
"tcompiletimeglobal", "tsighashstable", "tpureenum", "tgenericoffer",
|
||||
"tconverterreexport", "ttypeoffer", "ttransitiveoffer",
|
||||
"tmodsymref", "tmethupref", "temit", "ttraitparam"]
|
||||
"tmodsymref", "tmethupref", "temit"]
|
||||
|
||||
proc icTest(args: string) =
|
||||
temp("")
|
||||
|
||||
@@ -804,24 +804,6 @@ when defined(gcDestructors):
|
||||
sysAssert c.next == nil, "c.next pointer must be nil"
|
||||
atomicPrepend a.sharedFreeListBigChunks, c
|
||||
|
||||
proc takeFromSharedFreeListBigChunks(a: var MemRegion): PBigChunk {.inline.} =
|
||||
when hasThreadSupport:
|
||||
while true:
|
||||
result = atomicLoadN(addr a.sharedFreeListBigChunks, ATOMIC_ACQUIRE)
|
||||
if result == nil:
|
||||
break
|
||||
let next = result.next.loada
|
||||
var expected = result
|
||||
if atomicCompareExchangeN(addr a.sharedFreeListBigChunks, addr expected, next,
|
||||
weak = true, ATOMIC_ACQUIRE, ATOMIC_RELAXED):
|
||||
result.next.storea nil
|
||||
break
|
||||
else:
|
||||
result = a.sharedFreeListBigChunks
|
||||
if result != nil:
|
||||
a.sharedFreeListBigChunks = result.next
|
||||
result.next = nil
|
||||
|
||||
proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell; size: int) {.inline.} =
|
||||
atomicPrepend c.owner.sharedFreeLists[size], f
|
||||
|
||||
@@ -845,14 +827,21 @@ when defined(gcDestructors):
|
||||
inc(c.free, total)
|
||||
dec(a.occ, total)
|
||||
|
||||
proc freeDeferredObjects(a: var MemRegion) =
|
||||
# Pop only as many nodes as we can process. Detaching the entire list and
|
||||
# re-enqueuing its unprocessed tail through atomicPrepend would overwrite
|
||||
# that tail's next pointer and lose the rest of the list.
|
||||
for _ in 0..MaxSteps:
|
||||
let it = takeFromSharedFreeListBigChunks(a)
|
||||
proc freeDeferredObjects(a: var MemRegion; root: PBigChunk) =
|
||||
var it = root
|
||||
var maxIters = MaxSteps # make it time-bounded
|
||||
while true:
|
||||
let rest = it.next.loada
|
||||
it.next.storea nil
|
||||
deallocBigChunk(a, cast[PBigChunk](it))
|
||||
if maxIters == 0:
|
||||
if rest != nil:
|
||||
addToSharedFreeListBigChunks(a, rest)
|
||||
sysAssert a.sharedFreeListBigChunks != nil, "re-enqueing failed"
|
||||
break
|
||||
it = rest
|
||||
dec maxIters
|
||||
if it == nil: break
|
||||
deallocBigChunk(a, it)
|
||||
|
||||
when defined(heaptrack):
|
||||
const heaptrackLib =
|
||||
@@ -980,7 +969,13 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
|
||||
trackSize(c.size)
|
||||
else:
|
||||
when defined(gcDestructors):
|
||||
freeDeferredObjects(a)
|
||||
when hasThreadSupport:
|
||||
let deferredFrees = atomicExchangeN(addr a.sharedFreeListBigChunks, nil, ATOMIC_RELAXED)
|
||||
else:
|
||||
let deferredFrees = a.sharedFreeListBigChunks
|
||||
a.sharedFreeListBigChunks = nil
|
||||
if deferredFrees != nil:
|
||||
freeDeferredObjects(a, deferredFrees)
|
||||
|
||||
# For big chunks with custom alignment, allocate extra space.
|
||||
# Since chunks are page-aligned, the needed padding is a compile-time
|
||||
@@ -1402,4 +1397,4 @@ template instantiateForRegion(allocator: untyped) {.dirty.} =
|
||||
#sharedMemStatsShared(sharedHeap.currMem - sharedHeap.freeMem)
|
||||
{.pop.}
|
||||
|
||||
{.pop.}
|
||||
{.pop.}
|
||||
@@ -36,12 +36,7 @@ type
|
||||
rc: int # the object header is now a single RC field.
|
||||
# we could remove it in non-debug builds for the 'owned ref'
|
||||
# design but this seems unwise.
|
||||
when defined(gcYrc):
|
||||
rootIdx: int64 # the collector's claim word: collection tag or epoch
|
||||
# stamp packed with the dense capture index. Explicitly
|
||||
# 64 bit so that 32-bit targets run the same concurrent
|
||||
# claim and epoch-stamp algorithms
|
||||
elif defined(gcOrc):
|
||||
when defined(gcOrc) or defined(gcYrc):
|
||||
rootIdx: int # thanks to this we can delete potential cycle roots
|
||||
# in O(1) without doubly linked lists
|
||||
when defined(nimArcDebug) or defined(nimArcIds):
|
||||
|
||||
@@ -892,6 +892,9 @@ when not defined(useNimRtl):
|
||||
"API usage error: GC_enable called but GC is already enabled")
|
||||
dec(gch.recGcLock)
|
||||
|
||||
proc GC_setStrategy(strategy: GC_Strategy) =
|
||||
discard
|
||||
|
||||
proc GC_enableMarkAndSweep() =
|
||||
gch.cycleThreshold = InitialCycleThreshold
|
||||
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
when not usesDestructors:
|
||||
{.pragma: nodestroy.}
|
||||
|
||||
when hasAlloc:
|
||||
type
|
||||
GC_Strategy* = enum ## The strategy the GC should use for the application.
|
||||
gcThroughput, ## optimize for throughput
|
||||
gcResponsiveness, ## optimize for responsiveness (default)
|
||||
gcOptimizeTime, ## optimize for speed
|
||||
gcOptimizeSpace ## optimize for memory footprint
|
||||
|
||||
when hasAlloc and not defined(js) and not usesDestructors:
|
||||
proc GC_disable*() {.rtl, inl, gcsafe, raises: [].}
|
||||
## Disables the GC. If called `n` times, `n` calls to `GC_enable`
|
||||
@@ -58,6 +66,9 @@ when hasAlloc and defined(js):
|
||||
template GC_fullCollect* =
|
||||
{.warning: "GC_fullCollect is a no-op in JavaScript".}
|
||||
|
||||
template GC_setStrategy* =
|
||||
{.warning: "GC_setStrategy is a no-op in JavaScript".}
|
||||
|
||||
template GC_enableMarkAndSweep* =
|
||||
{.warning: "GC_enableMarkAndSweep is a no-op in JavaScript".}
|
||||
|
||||
|
||||
@@ -491,6 +491,8 @@ when not defined(useNimRtl):
|
||||
"API usage error: GC_enable called but GC is already enabled")
|
||||
dec(gch.recGcLock)
|
||||
|
||||
proc GC_setStrategy(strategy: GC_Strategy) = discard
|
||||
|
||||
proc GC_enableMarkAndSweep() =
|
||||
gch.cycleThreshold = InitialThreshold
|
||||
|
||||
|
||||
@@ -415,6 +415,7 @@ when hasThreadSupport:
|
||||
proc GC_disable() = discard
|
||||
proc GC_enable() = discard
|
||||
proc GC_fullCollect() = discard
|
||||
proc GC_setStrategy(strategy: GC_Strategy) = discard
|
||||
proc GC_enableMarkAndSweep() = discard
|
||||
proc GC_disableMarkAndSweep() = discard
|
||||
proc GC_getStatistics(): string = return ""
|
||||
|
||||
@@ -76,6 +76,7 @@ when not defined(useNimRtl):
|
||||
proc GC_disable() = boehmGC_disable()
|
||||
proc GC_enable() = boehmGC_enable()
|
||||
proc GC_fullCollect() = boehmGCfullCollect()
|
||||
proc GC_setStrategy(strategy: GC_Strategy) = discard
|
||||
proc GC_enableMarkAndSweep() = discard
|
||||
proc GC_disableMarkAndSweep() = discard
|
||||
proc GC_getStatistics(): string = return ""
|
||||
|
||||
@@ -12,6 +12,7 @@ proc GC_disable() = discard
|
||||
proc GC_enable() = discard
|
||||
proc go_gc() {.importc: "go_gc", dynlib: goLib.}
|
||||
proc GC_fullCollect() = go_gc()
|
||||
proc GC_setStrategy(strategy: GC_Strategy) = discard
|
||||
proc GC_enableMarkAndSweep() = discard
|
||||
proc GC_disableMarkAndSweep() = discard
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ when not defined(gcOrc) and not defined(gcYrc):
|
||||
proc GC_enableMarkAndSweep() = discard
|
||||
proc GC_disableMarkAndSweep() = discard
|
||||
|
||||
proc GC_setStrategy(strategy: GC_Strategy) = discard
|
||||
|
||||
proc getOccupiedMem(): int = discard
|
||||
proc getFreeMem(): int = discard
|
||||
proc getTotalMem(): int = discard
|
||||
|
||||
@@ -8,6 +8,7 @@ proc initGC() = discard
|
||||
proc GC_disable() = discard
|
||||
proc GC_enable() = discard
|
||||
proc GC_fullCollect() = discard
|
||||
proc GC_setStrategy(strategy: GC_Strategy) = discard
|
||||
proc GC_enableMarkAndSweep() = discard
|
||||
proc GC_disableMarkAndSweep() = discard
|
||||
proc GC_getStatistics(): string = return ""
|
||||
|
||||
@@ -25,55 +25,31 @@ when defined(gcYrc):
|
||||
HasCollectorLock
|
||||
Collecting
|
||||
|
||||
AlignedCounter = object
|
||||
## one counter per cache line to avoid false sharing between stripes
|
||||
c {.align: 64.}: int
|
||||
AlignedRwLock = object
|
||||
## One RwLock per cache line. {.align: 64.} causes the compiler to round
|
||||
## the struct size up to 64 bytes, so consecutive array elements never
|
||||
## share a cache line (sizeof(RwLock) = 56 on Linux x86_64 → 8 byte pad).
|
||||
lock {.align: 64.}: RwLock
|
||||
|
||||
# Asymmetric two-class exclusion: seq structure mutations and collections
|
||||
# exclude each other, but seq ops run concurrently with seq ops and
|
||||
# collections run concurrently with collections. This replaces the old
|
||||
# RwLock scheme (which allowed only ONE collector, serializing parallel
|
||||
# collection) and also sidesteps POSIX's requirement that a rwlock be
|
||||
# unlocked by its acquiring thread.
|
||||
var
|
||||
gSeqActive: array[NumLockStripes, AlignedCounter] # in-flight seq ops
|
||||
gGcActive: int # active collections
|
||||
gYrcLocks: array[NumLockStripes, AlignedRwLock]
|
||||
var
|
||||
lockState {.threadvar.}: YrcLockState
|
||||
|
||||
proc getYrcStripe(): int {.inline.} =
|
||||
## Map this thread to one of the NumLockStripes counter stripes.
|
||||
## Map this thread to one of the NumLockStripes RwLock stripes.
|
||||
## getThreadId() is already cached thread-locally in threadids.nim.
|
||||
getThreadId() and (NumLockStripes - 1)
|
||||
|
||||
proc acquireMutatorLock() {.compilerRtl, inl.} =
|
||||
if lockState == HasNoLock:
|
||||
let s = getYrcStripe()
|
||||
while true:
|
||||
# SEQ_CST inc-then-check pairs with the collector's SEQ_CST
|
||||
# inc-then-drain (Dekker-style store/load ordering)
|
||||
discard atomicFetchAdd(addr gSeqActive[s].c, 1, ATOMIC_SEQ_CST)
|
||||
if atomicLoadN(addr gGcActive, ATOMIC_SEQ_CST) == 0: break
|
||||
discard atomicFetchSub(addr gSeqActive[s].c, 1, ATOMIC_SEQ_CST)
|
||||
while atomicLoadN(addr gGcActive, ATOMIC_ACQUIRE) != 0:
|
||||
discard
|
||||
acquireRead gYrcLocks[getYrcStripe()].lock
|
||||
lockState = HasMutatorLock
|
||||
|
||||
proc releaseMutatorLock() {.compilerRtl, inl.} =
|
||||
if lockState == HasMutatorLock:
|
||||
lockState = HasNoLock
|
||||
discard atomicFetchSub(addr gSeqActive[getYrcStripe()].c, 1, ATOMIC_SEQ_CST)
|
||||
|
||||
proc yrcGcFenceEnter() =
|
||||
## A collection announces itself and waits for in-flight seq structure
|
||||
## mutations to drain. Multiple collections may hold the fence at once.
|
||||
discard atomicFetchAdd(addr gGcActive, 1, ATOMIC_SEQ_CST)
|
||||
for s in 0 ..< NumLockStripes:
|
||||
while atomicLoadN(addr gSeqActive[s].c, ATOMIC_SEQ_CST) > 0:
|
||||
discard
|
||||
|
||||
proc yrcGcFenceExit() =
|
||||
discard atomicFetchSub(addr gGcActive, 1, ATOMIC_SEQ_CST)
|
||||
releaseRead gYrcLocks[getYrcStripe()].lock
|
||||
|
||||
template yrcMutatorLock*(t: typedesc; body: untyped) =
|
||||
{.noSideEffect.}:
|
||||
@@ -95,6 +71,23 @@ when defined(gcYrc):
|
||||
{.noSideEffect.}:
|
||||
releaseMutatorLock()
|
||||
|
||||
template yrcCollectorLock(body: untyped) =
|
||||
if lockState == HasMutatorLock: releaseMutatorLock()
|
||||
let prevState = lockState
|
||||
let hadToAcquire = prevState < HasCollectorLock
|
||||
if hadToAcquire:
|
||||
# Acquire all stripes in ascending order — the only thread ever holding
|
||||
# multiple write locks is the collector, so there is no lock-order cycle.
|
||||
for yrcI in 0..<NumLockStripes:
|
||||
acquireWrite(gYrcLocks[yrcI].lock)
|
||||
lockState = HasCollectorLock
|
||||
try:
|
||||
body
|
||||
finally:
|
||||
if hadToAcquire:
|
||||
for yrcI in 0..<NumLockStripes:
|
||||
releaseWrite(gYrcLocks[yrcI].lock)
|
||||
lockState = prevState
|
||||
|
||||
else:
|
||||
template yrcMutatorLock*(t: typedesc; body: untyped) =
|
||||
|
||||
@@ -223,9 +223,8 @@ proc addChar(s: NimString, c: char): NimString =
|
||||
proc appendString(dest, src: NimString) {.compilerproc, inline.} =
|
||||
## Raw, does not prepare `dest` space for copying
|
||||
if src != nil:
|
||||
copyMem(addr(dest.data[dest.len]), addr(src.data), src.len)
|
||||
copyMem(addr(dest.data[dest.len]), addr(src.data), src.len + 1)
|
||||
inc(dest.len, src.len)
|
||||
dest.data[dest.len] = '\0'
|
||||
|
||||
proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} =
|
||||
## Sets the `s` length to `newLen` zeroing memory on growth.
|
||||
|
||||
@@ -27,10 +27,6 @@ else:
|
||||
template afterThreadRuns() =
|
||||
for i in countdown(nimThreadDestructionHandlers.len-1, 0):
|
||||
nimThreadDestructionHandlers[i]()
|
||||
when declared(nimYrcThreadTeardown):
|
||||
# YRC: spill this thread's candidate roots so its garbage remains
|
||||
# collectible after the thread is gone
|
||||
nimYrcThreadTeardown()
|
||||
|
||||
proc onThreadDestruction*(handler: proc () {.closure, gcsafe, raises: [].}) =
|
||||
## Registers a *thread local* handler that is called at the thread's
|
||||
|
||||
1471
lib/system/yrc.nim
1471
lib/system/yrc.nim
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,594 +0,0 @@
|
||||
/-
|
||||
Tarjan-based deadness computation — correctness proof
|
||||
=====================================================
|
||||
Self-contained, no Mathlib. Checked with Lean 4 (v4.32.0).
|
||||
|
||||
Companion to yrc_proof.lean; models the NOVEL part of yrc.nim's
|
||||
collector: cycle detection via a single Tarjan SCC traversal plus one
|
||||
linear reverse scan over the condensation, replacing Bacon-style trial
|
||||
deletion (three traversals: markGray / scan / collectWhite).
|
||||
|
||||
## The algorithm (capture / computeDeadness in yrc.nim)
|
||||
|
||||
`capture` runs an iterative Tarjan DFS from the candidate roots. Each
|
||||
visited cell is claimed (dense index in the header), its rc word is
|
||||
snapshotted, and every traversed slot contributes one edge record.
|
||||
SCCs are numbered 0, 1, 2, … in POP (completion) order. Tarjan's
|
||||
invariant: when an SCC is completed, every SCC it points to was
|
||||
completed earlier — so every condensation cross edge goes from a
|
||||
HIGHER SCC id to a LOWER one ("sinks first").
|
||||
|
||||
`computeDeadness` then makes ONE pass s = nScc−1 … 0 (sources before
|
||||
sinks, since in-edges come from higher ids):
|
||||
|
||||
ext(s) = sumRefs(s) − internal(s) − deadIn(s)
|
||||
if not forcedLive(s) and ext(s) == 0:
|
||||
s is DEAD; for each cross edge s → t: deadIn(t) += 1
|
||||
else:
|
||||
s is LIVE; for each cross edge s → t: forcedLive(t) := true
|
||||
|
||||
where sumRefs(s) = Σ rc over members, internal(s) = # captured edges
|
||||
within s, and forcedLive is seeded from cells still registered in the
|
||||
roots buffer (inRootsFlag).
|
||||
|
||||
## What we prove
|
||||
|
||||
Fix the SPEC of liveness on the condensation: an SCC is live iff it
|
||||
has an external reference, a roots-buffer seed, or a captured cross
|
||||
edge from a live SCC (`LiveScc`, an inductive definition).
|
||||
|
||||
1. `scan_dead_iff_not_live` — any deadness assignment satisfying the
|
||||
scan's per-SCC equation (well-defined thanks to the sinks-first
|
||||
edge order) marks an SCC dead IFF it is not live. Soundness AND
|
||||
completeness in one theorem: the single reverse scan computes the
|
||||
garbage set EXACTLY on the captured snapshot.
|
||||
2. `impl_fixpoint_is_spec` — the implementation's ARITHMETIC form
|
||||
(ext = sumRefs − internal − deadIn with forcedLive propagation) is
|
||||
the same equation, given rc-exactness (sumRefs = external +
|
||||
internal + cross-in; established by merge + commit validation, see
|
||||
yrc_proof.lean §4).
|
||||
3. Cell-level bridge: `tarjan_sound` — cells of dead SCCs are
|
||||
unreachable in the snapshot; `tarjan_complete` — every captured
|
||||
garbage cell IS marked dead (this needs strong connectivity of the
|
||||
SCCs and exactness of the external counts; Bacon needs his second
|
||||
and third traversals for the same guarantee).
|
||||
4. `demotion_closure_sound` — validate-time demotion (an SCC dropped
|
||||
from the dead set because a mutator dirtied it) must PROPAGATE
|
||||
along captured cross edges: the freed set stays closed only if the
|
||||
demoted set is successor-closed within the dead set. A demoted SCC
|
||||
survives with its out-edges intact, so any still-dead target would
|
||||
be freed while a surviving cell points at it.
|
||||
|
||||
## What is assumed (and where it is discharged)
|
||||
|
||||
• The sinks-first edge order (`horder`) — Tarjan's classical
|
||||
invariant; the DFS itself is not modeled.
|
||||
• rc-exactness (`hcount`) — discharged operationally by yrc_proof §4
|
||||
(merge + dirty check + rc-word recheck).
|
||||
• That `capture` records exactly the heap edges among captured cells
|
||||
and that SCC members are mutually reachable (`h_edge_resp`,
|
||||
`h_conn`, `h_cross_real`) — properties of the traversal + Tarjan.
|
||||
-/
|
||||
|
||||
abbrev Obj := Nat
|
||||
|
||||
/-! ## §1 Descending induction
|
||||
|
||||
The scan processes higher SCC ids first; every recursive dependency
|
||||
of `dead s` is on some `u > s`. This induction principle is the
|
||||
well-definedness of the whole scheme. -/
|
||||
|
||||
theorem descending_induction {n : Nat} (P : Fin n → Prop)
|
||||
(step : ∀ s : Fin n, (∀ u : Fin n, s < u → P u) → P s) :
|
||||
∀ s, P s := by
|
||||
have key : ∀ k, ∀ s : Fin n, n - s.val ≤ k → P s := by
|
||||
intro k
|
||||
induction k with
|
||||
| zero =>
|
||||
intro s hs
|
||||
have := s.isLt
|
||||
omega
|
||||
| succ k ih =>
|
||||
intro s _
|
||||
apply step
|
||||
intro u hu
|
||||
apply ih
|
||||
have h1 := u.isLt
|
||||
have h2 : s.val < u.val := hu
|
||||
omega
|
||||
intro s
|
||||
exact key n s (by omega)
|
||||
|
||||
/-! ## §2 The condensation and the liveness spec
|
||||
|
||||
`edges` are the captured condensation cross edges (with multiplicity:
|
||||
one entry per traversed slot, exactly like cap.edges bucketed into
|
||||
crossTgt). `extRefs s` counts references into SCC `s` from OUTSIDE
|
||||
the capture: stack refs, uncaptured heap cells, other collections'
|
||||
partitions — everything in Σrc not explained by captured edges.
|
||||
`seed s` is the inRootsFlag forcedLive seeding. -/
|
||||
|
||||
section Condensation
|
||||
|
||||
variable {n : Nat}
|
||||
variable (edges : List (Fin n × Fin n))
|
||||
variable (extRefs : Fin n → Nat)
|
||||
variable (seed : Fin n → Bool)
|
||||
|
||||
/-- The SPEC: an SCC is live iff something external anchors it —
|
||||
directly or through a chain of captured cross edges. -/
|
||||
inductive LiveScc : Fin n → Prop where
|
||||
| ext (s : Fin n) : 0 < extRefs s → LiveScc s
|
||||
| root (s : Fin n) : seed s = true → LiveScc s
|
||||
| pred (u s : Fin n) : (u, s) ∈ edges → LiveScc u → LiveScc s
|
||||
|
||||
/-- The per-SCC equation the reverse scan establishes: dead iff no
|
||||
external refs, no seed, and ALL cross predecessors dead. (The
|
||||
sinks-first order makes this a valid definition: every predecessor
|
||||
has a higher id and is decided first — see `descending_induction`;
|
||||
without that order the "definition" would be circular.) -/
|
||||
def ScanEq (dead : Fin n → Bool) : Prop :=
|
||||
∀ s, dead s = true ↔
|
||||
(extRefs s = 0 ∧ seed s = false ∧
|
||||
∀ e ∈ edges, e.2 = s → dead e.1 = true)
|
||||
|
||||
/-- Live SCCs are never marked dead (soundness direction). -/
|
||||
theorem live_not_dead (dead : Fin n → Bool)
|
||||
(hfix : ScanEq edges extRefs seed dead) :
|
||||
∀ s, LiveScc edges extRefs seed s → dead s ≠ true := by
|
||||
intro s hl
|
||||
induction hl with
|
||||
| ext s h =>
|
||||
intro hd
|
||||
have := ((hfix s).mp hd).1
|
||||
omega
|
||||
| root s h =>
|
||||
intro hd
|
||||
have := ((hfix s).mp hd).2.1
|
||||
rw [h] at this
|
||||
cases this
|
||||
| pred u s hmem _ ih =>
|
||||
intro hd
|
||||
exact ih (((hfix s).mp hd).2.2 (u, s) hmem rfl)
|
||||
|
||||
/-- Non-live SCCs are always marked dead (completeness direction) —
|
||||
by descending induction along the scan order. -/
|
||||
theorem not_live_dead
|
||||
(horder : ∀ e ∈ edges, e.2 < e.1)
|
||||
(dead : Fin n → Bool)
|
||||
(hfix : ScanEq edges extRefs seed dead) :
|
||||
∀ s, ¬ LiveScc edges extRefs seed s → dead s = true := by
|
||||
refine descending_induction
|
||||
(fun s => ¬ LiveScc edges extRefs seed s → dead s = true) ?_
|
||||
intro s ihs hnl
|
||||
rw [hfix]
|
||||
refine ⟨?_, ?_, ?_⟩
|
||||
· cases Nat.eq_zero_or_pos (extRefs s) with
|
||||
| inl h => exact h
|
||||
| inr h => exact absurd (LiveScc.ext s h) hnl
|
||||
· cases hsd : seed s with
|
||||
| false => rfl
|
||||
| true => exact absurd (LiveScc.root s hsd) hnl
|
||||
· intro e he hes
|
||||
have hlt : s < e.1 := by
|
||||
have := horder e he
|
||||
rw [hes] at this
|
||||
exact this
|
||||
apply ihs e.1 hlt
|
||||
intro hlu
|
||||
have hmem : (e.1, s) ∈ edges := by
|
||||
rw [← hes]
|
||||
simpa using he
|
||||
exact hnl (LiveScc.pred e.1 s hmem hlu)
|
||||
|
||||
/-- **Main condensation theorem**: the single reverse scan computes
|
||||
EXACTLY the non-live SCCs. One Tarjan DFS + one linear scan replace
|
||||
Bacon's three graph traversals, with no loss of precision on the
|
||||
snapshot. -/
|
||||
theorem scan_dead_iff_not_live
|
||||
(horder : ∀ e ∈ edges, e.2 < e.1)
|
||||
(dead : Fin n → Bool)
|
||||
(hfix : ScanEq edges extRefs seed dead) :
|
||||
∀ s, dead s = true ↔ ¬ LiveScc edges extRefs seed s := by
|
||||
intro s
|
||||
constructor
|
||||
· intro hd hl
|
||||
exact live_not_dead edges extRefs seed dead hfix s hl hd
|
||||
· exact not_live_dead edges extRefs seed horder dead hfix s
|
||||
|
||||
/-! ## §3 The implementation's arithmetic form
|
||||
|
||||
computeDeadness does not test "all predecessors dead" directly; it
|
||||
maintains ext(s) = sumRefs(s) − internal(s) − deadIn(s) and a
|
||||
forcedLive flag pushed along cross edges of live SCCs. We show this
|
||||
is the same equation, given rc-exactness:
|
||||
|
||||
sumRefs s = extRefs s + internal s + (# cross edges into s).
|
||||
|
||||
ext(s) = 0 then says extRefs s = 0 AND every cross in-edge came from
|
||||
a dead predecessor; ¬forcedLive says no seed and no LIVE predecessor
|
||||
pushed the flag — together exactly `ScanEq`. -/
|
||||
|
||||
def inCount (s : Fin n) : Nat :=
|
||||
edges.countP (fun e => e.2 == s)
|
||||
|
||||
def deadInCount (dead : Fin n → Bool) (s : Fin n) : Nat :=
|
||||
edges.countP (fun e => e.2 == s && dead e.1)
|
||||
|
||||
/-- countP is monotone under pointwise implication. -/
|
||||
theorem countP_le_of_imp {α : Type} (l : List α) (p q : α → Bool)
|
||||
(himp : ∀ x ∈ l, p x = true → q x = true) :
|
||||
l.countP p ≤ l.countP q := by
|
||||
induction l with
|
||||
| nil => simp
|
||||
| cons a l ih =>
|
||||
have iht := ih (fun x hx => himp x (List.mem_cons_of_mem a hx))
|
||||
by_cases hpa : p a = true
|
||||
· have hqa := himp a (by simp) hpa
|
||||
simp [hpa, hqa]
|
||||
omega
|
||||
· simp only [List.countP_cons]
|
||||
have : p a = false := by
|
||||
cases h : p a
|
||||
· rfl
|
||||
· exact absurd h hpa
|
||||
simp [this]
|
||||
omega
|
||||
|
||||
/-- If a stronger predicate matches as often as a weaker one, they
|
||||
agree on every element. -/
|
||||
theorem countP_eq_forces_all {α : Type} (l : List α) (p q : α → Bool)
|
||||
(himp : ∀ x ∈ l, q x = true → p x = true)
|
||||
(heq : l.countP p = l.countP q) :
|
||||
∀ x ∈ l, p x = true → q x = true := by
|
||||
induction l with
|
||||
| nil => intro x hx; cases hx
|
||||
| cons a l ih =>
|
||||
have himpt : ∀ x ∈ l, q x = true → p x = true :=
|
||||
fun x hx => himp x (List.mem_cons_of_mem a hx)
|
||||
have hmono := countP_le_of_imp l q p himpt
|
||||
intro x hx hpx
|
||||
simp only [List.countP_cons] at heq
|
||||
cases List.mem_cons.mp hx with
|
||||
| inl hxa =>
|
||||
subst hxa
|
||||
cases hqx : q x with
|
||||
| true => rfl
|
||||
| false =>
|
||||
exfalso
|
||||
simp [hpx, hqx] at heq
|
||||
omega
|
||||
| inr hxl =>
|
||||
have hqa_pa : (if q a = true then 1 else 0) ≤ (if p a = true then 1 else 0) := by
|
||||
by_cases hq : q a = true
|
||||
· simp [hq, himp a (by simp) hq]
|
||||
· simp [hq]
|
||||
have heqt : l.countP p = l.countP q := by
|
||||
by_cases hq : q a = true
|
||||
· simp [hq, himp a (by simp) hq] at heq
|
||||
omega
|
||||
· have hqf : q a = false := by
|
||||
cases h : q a
|
||||
· rfl
|
||||
· exact absurd h hq
|
||||
by_cases hp : p a = true
|
||||
· simp [hp, hqf] at heq
|
||||
omega
|
||||
· have hpf : p a = false := by
|
||||
cases h : p a
|
||||
· rfl
|
||||
· exact absurd h hp
|
||||
simp [hpf, hqf] at heq
|
||||
omega
|
||||
exact ih himpt heqt x hxl hpx
|
||||
|
||||
/-- If all cross predecessors of `s` are dead, deadIn equals the full
|
||||
in-count (and vice versa). -/
|
||||
theorem deadIn_eq_inCount_iff (dead : Fin n → Bool) (s : Fin n) :
|
||||
deadInCount edges dead s = inCount edges s ↔
|
||||
(∀ e ∈ edges, e.2 = s → dead e.1 = true) := by
|
||||
constructor
|
||||
· intro heq e he hes
|
||||
have himp : ∀ x ∈ edges, (fun e => e.2 == s && dead e.1) x = true →
|
||||
(fun e => e.2 == s) x = true := by
|
||||
intro x _ hx
|
||||
simp only [Bool.and_eq_true] at hx
|
||||
exact hx.1
|
||||
have := countP_eq_forces_all edges
|
||||
(fun e => e.2 == s) (fun e => e.2 == s && dead e.1)
|
||||
himp heq.symm e he
|
||||
have hbeq : (e.2 == s) = true := by
|
||||
simp [hes]
|
||||
have := this hbeq
|
||||
simp only [Bool.and_eq_true] at this
|
||||
exact this.2
|
||||
· intro hall
|
||||
unfold deadInCount inCount
|
||||
apply List.countP_congr
|
||||
intro e he
|
||||
by_cases hes : e.2 = s
|
||||
· simp [hes, hall e he hes]
|
||||
· have : (e.2 == s) = false := by
|
||||
simp [hes]
|
||||
simp [this]
|
||||
|
||||
/-- The implementation's per-SCC decision, verbatim from
|
||||
computeDeadness: NOT forced (no seed, no live predecessor pushed
|
||||
the flag) and ext = sumRefs − internal − deadIn = 0 (stated
|
||||
subtraction-free). -/
|
||||
def ImplEq (sumRefs internal : Fin n → Nat) (dead : Fin n → Bool) : Prop :=
|
||||
∀ s, dead s = true ↔
|
||||
(¬ (seed s = true ∨ ∃ e ∈ edges, e.2 = s ∧ dead e.1 = false) ∧
|
||||
sumRefs s = internal s + deadInCount edges dead s)
|
||||
|
||||
/-- **The arithmetic is the spec**: under rc-exactness, the
|
||||
implementation's equation is `ScanEq`, so `scan_dead_iff_not_live`
|
||||
applies to computeDeadness as written. -/
|
||||
theorem impl_fixpoint_is_spec
|
||||
(sumRefs internal : Fin n → Nat) (dead : Fin n → Bool)
|
||||
(hcount : ∀ s, sumRefs s = extRefs s + internal s + inCount edges s)
|
||||
(himpl : ImplEq edges seed sumRefs internal dead) :
|
||||
ScanEq edges extRefs seed dead := by
|
||||
intro s
|
||||
rw [himpl s]
|
||||
constructor
|
||||
· rintro ⟨hnf, harith⟩
|
||||
have hor := hnf
|
||||
rw [not_or] at hor
|
||||
obtain ⟨hseed, hnopred⟩ := hor
|
||||
have hseedf : seed s = false := by
|
||||
cases h : seed s
|
||||
· rfl
|
||||
· exact absurd h hseed
|
||||
have hall : ∀ e ∈ edges, e.2 = s → dead e.1 = true := by
|
||||
intro e he hes
|
||||
cases h : dead e.1 with
|
||||
| true => rfl
|
||||
| false => exact absurd ⟨e, he, hes, h⟩ hnopred
|
||||
have hdc := (deadIn_eq_inCount_iff edges dead s).mpr hall
|
||||
have hc := hcount s
|
||||
refine ⟨by omega, hseedf, hall⟩
|
||||
· rintro ⟨hext, hseedf, hall⟩
|
||||
have hdc := (deadIn_eq_inCount_iff edges dead s).mpr hall
|
||||
refine ⟨?_, ?_⟩
|
||||
· rw [not_or]
|
||||
refine ⟨by simp [hseedf], ?_⟩
|
||||
rintro ⟨e, he, hes, hdf⟩
|
||||
rw [hall e he hes] at hdf
|
||||
cases hdf
|
||||
· have hc := hcount s
|
||||
omega
|
||||
|
||||
end Condensation
|
||||
|
||||
/-! ## §4 Cell-level correctness
|
||||
|
||||
Bridge from the condensation to the actual heap snapshot. `extRef`
|
||||
covers every reference source outside the capture: mutator stacks,
|
||||
the roots buffer, uncaptured heap cells' slots that the arithmetic
|
||||
cannot explain, and other collections' partitions (cross-collection
|
||||
edges — this is the SCC-side view of `cross_target_live` in
|
||||
yrc_proof.lean §5). -/
|
||||
|
||||
structure CellGraph where
|
||||
edge : Obj → Obj → Prop
|
||||
extRef : Obj → Prop
|
||||
|
||||
/-- A cell is live iff an external reference anchors it through heap
|
||||
edges (the cell-level ground truth; `anchored` of yrc_proof.lean). -/
|
||||
inductive CellLive (g : CellGraph) : Obj → Prop where
|
||||
| ext (x : Obj) : g.extRef x → CellLive g x
|
||||
| step (x y : Obj) : CellLive g x → g.edge x y → CellLive g y
|
||||
|
||||
/-- Paths through heap edges, used to move liveness around inside an
|
||||
SCC (Tarjan guarantees SCC members are mutually reachable). -/
|
||||
inductive EdgePath (g : CellGraph) : Obj → Obj → Prop where
|
||||
| refl (x : Obj) : EdgePath g x x
|
||||
| step (x y z : Obj) : EdgePath g x y → g.edge y z → EdgePath g x z
|
||||
|
||||
theorem cellLive_along_path (g : CellGraph) (u v : Obj)
|
||||
(hl : CellLive g u) (hp : EdgePath g u v) : CellLive g v := by
|
||||
induction hp with
|
||||
| refl => exact hl
|
||||
| step _ _ _ hedge ih => exact CellLive.step _ _ ih hedge
|
||||
|
||||
section CellBridge
|
||||
|
||||
variable {n : Nat}
|
||||
variable (g : CellGraph)
|
||||
variable (edges : List (Fin n × Fin n))
|
||||
variable (extRefs : Fin n → Nat)
|
||||
variable (seed : Fin n → Bool)
|
||||
variable (captured : Obj → Prop)
|
||||
variable (scc : Obj → Fin n)
|
||||
|
||||
/-- Any live captured cell sits in a live SCC.
|
||||
Premises are properties of `capture`:
|
||||
* `h_edge_resp` — every heap edge between captured cells was
|
||||
recorded (same SCC → internal; different → cross edge);
|
||||
* `h_closed` — an edge from an UNCAPTURED cell is unexplained by
|
||||
the captured arithmetic, so it lands in extRefs;
|
||||
* `h_ext` — direct external refs (stacks, roots buffer, foreign
|
||||
partitions) are counted in extRefs. -/
|
||||
theorem captured_live_scc
|
||||
(h_edge_resp : ∀ u v, captured u → captured v → g.edge u v →
|
||||
scc u = scc v ∨ (scc u, scc v) ∈ edges)
|
||||
(h_closed : ∀ u v, captured v → g.edge u v → ¬ captured u →
|
||||
0 < extRefs (scc v))
|
||||
(h_ext : ∀ v, captured v → g.extRef v → 0 < extRefs (scc v)) :
|
||||
∀ x, CellLive g x → captured x →
|
||||
LiveScc edges extRefs seed (scc x) := by
|
||||
intro x hl
|
||||
induction hl with
|
||||
| ext x h =>
|
||||
intro hc
|
||||
exact LiveScc.ext _ (h_ext x hc h)
|
||||
| step u v hu hedge ih =>
|
||||
intro hcv
|
||||
by_cases hcu : captured u
|
||||
· cases h_edge_resp u v hcu hcv hedge with
|
||||
| inl heq => rw [← heq]; exact ih hcu
|
||||
| inr hmem => exact LiveScc.pred _ _ hmem (ih hcu)
|
||||
· exact LiveScc.ext _ (h_closed u v hcv hedge hcu)
|
||||
|
||||
/-- **Soundness**: every cell of a dead SCC is unanchored in the
|
||||
snapshot — freeing it is justified by yrc_proof.lean §1
|
||||
(`yrc_safety`) + §3 (stability through the commit window). -/
|
||||
theorem tarjan_sound
|
||||
(dead : Fin n → Bool)
|
||||
(hfix : ScanEq edges extRefs seed dead)
|
||||
(h_edge_resp : ∀ u v, captured u → captured v → g.edge u v →
|
||||
scc u = scc v ∨ (scc u, scc v) ∈ edges)
|
||||
(h_closed : ∀ u v, captured v → g.edge u v → ¬ captured u →
|
||||
0 < extRefs (scc v))
|
||||
(h_ext : ∀ v, captured v → g.extRef v → 0 < extRefs (scc v)) :
|
||||
∀ x, captured x → dead (scc x) = true → ¬ CellLive g x := by
|
||||
intro x hc hd hl
|
||||
exact live_not_dead edges extRefs seed dead hfix (scc x)
|
||||
(captured_live_scc g edges extRefs seed captured scc
|
||||
h_edge_resp h_closed h_ext x hl hc) hd
|
||||
|
||||
/-- Every cell of a live SCC is genuinely live. Needs the converse
|
||||
premises: external counts are EXACT (no phantom refs — deferred
|
||||
decs inflate rc, so in the running system this holds only after
|
||||
the merge; overcounts delay collection by a round, they never
|
||||
cause a wrong free), cross edges are real edges, and SCC members
|
||||
are mutually reachable (Tarjan). -/
|
||||
theorem live_scc_cells_live
|
||||
(h_ext_exact : ∀ s : Fin n, 0 < extRefs s →
|
||||
∃ v, captured v ∧ scc v = s ∧ g.extRef v)
|
||||
(h_seed_exact : ∀ s : Fin n, seed s = true →
|
||||
∃ v, captured v ∧ scc v = s ∧ g.extRef v)
|
||||
(h_cross_real : ∀ (u s : Fin n), (u, s) ∈ edges →
|
||||
∃ cu cv, captured cu ∧ captured cv ∧ scc cu = u ∧ scc cv = s ∧
|
||||
g.edge cu cv)
|
||||
(h_conn : ∀ u v, captured u → captured v → scc u = scc v →
|
||||
EdgePath g u v) :
|
||||
∀ s, LiveScc edges extRefs seed s →
|
||||
∀ x, captured x → scc x = s → CellLive g x := by
|
||||
intro s hl
|
||||
induction hl with
|
||||
| ext s h =>
|
||||
intro x hc hs
|
||||
obtain ⟨v, hcv, hsv, hev⟩ := h_ext_exact s h
|
||||
exact cellLive_along_path g v x (CellLive.ext v hev)
|
||||
(h_conn v x hcv hc (by rw [hsv, hs]))
|
||||
| root s h =>
|
||||
intro x hc hs
|
||||
obtain ⟨v, hcv, hsv, hev⟩ := h_seed_exact s h
|
||||
exact cellLive_along_path g v x (CellLive.ext v hev)
|
||||
(h_conn v x hcv hc (by rw [hsv, hs]))
|
||||
| pred u s hmem _ ih =>
|
||||
intro x hc hs
|
||||
obtain ⟨cu, cv, hccu, hccv, hscu, hscv, he⟩ := h_cross_real u s hmem
|
||||
have hculive : CellLive g cu := ih cu hccu hscu
|
||||
exact cellLive_along_path g cv x (CellLive.step cu cv hculive he)
|
||||
(h_conn cv x hccv hc (by rw [hscv, hs]))
|
||||
|
||||
/-- **Completeness**: every captured garbage cell is marked dead — the
|
||||
scan collects ALL cycles reachable from the candidate set in one
|
||||
round (on the snapshot; concurrent inflation only defers). -/
|
||||
theorem tarjan_complete
|
||||
(horder : ∀ e ∈ edges, e.2 < e.1)
|
||||
(dead : Fin n → Bool)
|
||||
(hfix : ScanEq edges extRefs seed dead)
|
||||
(h_ext_exact : ∀ s : Fin n, 0 < extRefs s →
|
||||
∃ v, captured v ∧ scc v = s ∧ g.extRef v)
|
||||
(h_seed_exact : ∀ s : Fin n, seed s = true →
|
||||
∃ v, captured v ∧ scc v = s ∧ g.extRef v)
|
||||
(h_cross_real : ∀ (u s : Fin n), (u, s) ∈ edges →
|
||||
∃ cu cv, captured cu ∧ captured cv ∧ scc cu = u ∧ scc cv = s ∧
|
||||
g.edge cu cv)
|
||||
(h_conn : ∀ u v, captured u → captured v → scc u = scc v →
|
||||
EdgePath g u v) :
|
||||
∀ x, captured x → ¬ CellLive g x → dead (scc x) = true := by
|
||||
intro x hc hnl
|
||||
apply not_live_dead edges extRefs seed horder dead hfix
|
||||
intro hl
|
||||
exact hnl (live_scc_cells_live g edges extRefs seed captured scc
|
||||
h_ext_exact h_seed_exact h_cross_real h_conn (scc x) hl x hc rfl)
|
||||
|
||||
end CellBridge
|
||||
|
||||
/-! ## §5 Validate-time demotion must propagate
|
||||
|
||||
validateDead demotes a dead SCC when a mutator dirtied it (queue
|
||||
entry or changed rc word). A demoted SCC becomes a survivor: its
|
||||
slots are NOT nil'd at commit, so its captured out-edges remain in
|
||||
the heap. If a cross target of a demoted SCC stayed in the dead set,
|
||||
the commit would free a cell that a surviving cell still points to —
|
||||
deadIn had explained that edge away under the assumption that the
|
||||
predecessor dies too.
|
||||
|
||||
Minimal instance of the hazard: two SCCs, one edge 1 → 0, both
|
||||
computed dead (ext = 0 for both; SCC 0's only reference comes from
|
||||
SCC 1, subtracted as deadIn). Demote SCC 1 alone, and the freed set
|
||||
{0} has a live in-edge from the surviving SCC 1.
|
||||
|
||||
The theorem below states the repair: if the demoted set `K` is
|
||||
successor-closed within the dead set (demoting s also demotes every
|
||||
dead t with a captured edge s → t, transitively — one countdown pass
|
||||
suffices because edges go from higher to lower ids), then the freed
|
||||
set F = dead ∖ K is predecessor-closed: every captured edge into F
|
||||
comes from F. Combined with extRefs = 0 and no seed (ScanEq) this
|
||||
makes F closed in the sense of yrc_proof.lean §3, so freeing F is
|
||||
covered by `commit_free_safe` there. -/
|
||||
|
||||
theorem demotion_closure_sound {n : Nat}
|
||||
(edges : List (Fin n × Fin n))
|
||||
(extRefs : Fin n → Nat) (seed : Fin n → Bool)
|
||||
(dead : Fin n → Bool)
|
||||
(hfix : ScanEq edges extRefs seed dead)
|
||||
(K : Fin n → Prop) -- the demoted SCCs
|
||||
(hK_closed : ∀ e ∈ edges, K e.1 → dead e.2 = true → K e.2) :
|
||||
-- every captured edge into the freed set comes from the freed set
|
||||
∀ e ∈ edges, (dead e.2 = true ∧ ¬ K e.2) →
|
||||
(dead e.1 = true ∧ ¬ K e.1) := by
|
||||
intro e he ⟨hd2, hk2⟩
|
||||
have hd1 : dead e.1 = true :=
|
||||
((hfix e.2).mp hd2).2.2 e he rfl
|
||||
refine ⟨hd1, ?_⟩
|
||||
intro hk1
|
||||
exact hk2 (hK_closed e he hk1 hd2)
|
||||
|
||||
/-- Without successor-closure the guarantee genuinely fails: in the
|
||||
two-SCC instance above, demoting only SCC 1 leaves the freed set
|
||||
{0} with an in-edge from a survivor. (Concrete witness, checked by
|
||||
`decide`-style evaluation.) -/
|
||||
example :
|
||||
let edges : List (Fin 2 × Fin 2) := [(1, 0)]
|
||||
let dead : Fin 2 → Bool := fun _ => true
|
||||
let K : Fin 2 → Prop := fun s => s = 1 -- demote only SCC 1
|
||||
-- ScanEq holds for `dead` (both SCCs legitimately computed dead) …
|
||||
ScanEq edges (fun _ => 0) (fun _ => false) dead ∧
|
||||
-- … yet the freed set {0} has an in-edge from surviving SCC 1:
|
||||
((1, 0) ∈ edges ∧ dead 0 = true ∧ ¬ K 0 ∧ K 1) := by
|
||||
refine ⟨?_, ?_⟩
|
||||
· intro s
|
||||
simp
|
||||
· refine ⟨by simp, rfl, by simp, rfl⟩
|
||||
|
||||
/-! ## Summary (all QED, no sorry)
|
||||
|
||||
* `descending_induction` — the sinks-first SCC numbering makes the
|
||||
reverse scan a well-founded definition.
|
||||
* `scan_dead_iff_not_live` — the scan marks an SCC dead iff it is
|
||||
not externally anchored: exact garbage identification in ONE
|
||||
linear pass over the condensation.
|
||||
* `impl_fixpoint_is_spec` — the implementation's arithmetic
|
||||
(ext = sumRefs − internal − deadIn, forcedLive propagation) is
|
||||
that same equation under rc-exactness.
|
||||
* `tarjan_sound` / `tarjan_complete` — at the cell level: dead cells
|
||||
are unanchored (frees are safe) and unanchored captured cells are
|
||||
freed (nothing is missed on the snapshot).
|
||||
* `demotion_closure_sound` + counterexample — demotion is sound iff
|
||||
it propagates along captured cross edges to still-dead targets;
|
||||
a lone demotion can leave the freed set with a surviving
|
||||
predecessor.
|
||||
|
||||
Not modeled: the Tarjan DFS itself (its two classical invariants —
|
||||
SCC partition and sinks-first emission — enter as premises), the
|
||||
iterative traceStack encoding, crossPend (cross-collection edges are
|
||||
folded into `extRefs`, justified by yrc_proof.lean §5), and the
|
||||
temporal validity of rc-exactness (yrc_proof.lean §4).
|
||||
-/
|
||||
@@ -1156,14 +1156,13 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
|
||||
graph.suggestResult(s.sym, s.sym.info)
|
||||
of ideType:
|
||||
let s = graph.findSymData(file, line, col)
|
||||
if not s.isNil and s.sym.typ != nil:
|
||||
if not s.isNil:
|
||||
let typeSym = s.sym.typ.sym
|
||||
if typeSym != nil:
|
||||
graph.suggestResult(typeSym, typeSym.info, ideType)
|
||||
elif s.sym.typ.len != 0 and s.sym.typ[0] != nil:
|
||||
elif s.sym.typ.len != 0:
|
||||
let genericType = s.sym.typ[0].sym
|
||||
if genericType != nil:
|
||||
graph.suggestResult(genericType, genericType.info, ideType)
|
||||
graph.suggestResult(genericType, genericType.info, ideType)
|
||||
of ideUse, ideDus:
|
||||
let symbol = graph.findSymData(file, line, col)
|
||||
if not symbol.isNil:
|
||||
|
||||
@@ -20,13 +20,6 @@ echo fo#[!]#oGeneric.bar
|
||||
# bad type
|
||||
echo unde#[!]#fined
|
||||
|
||||
# type of a void proc: typ[0] (return type) is nil, must not crash
|
||||
var s = ""
|
||||
s.a#[!]#dd('x')
|
||||
|
||||
# type of a module symbol: typ is nil, must not crash
|
||||
import std/str#[!]#utils
|
||||
|
||||
discard """
|
||||
$nimsuggest --v3 --tester $file
|
||||
>type $1
|
||||
@@ -36,6 +29,4 @@ type skType tv3_typeDefinition.Foo2 Foo2 $file 11 2 "" 100
|
||||
>type $3
|
||||
type skType tv3_typeDefinition.FooGeneric FooGeneric $file 14 2 "" 100
|
||||
>type $4
|
||||
>type $5
|
||||
>type $6
|
||||
"""
|
||||
|
||||
@@ -36,4 +36,4 @@ proc main() =
|
||||
main()
|
||||
GC_fullCollect()
|
||||
when not defined(useMalloc):
|
||||
echo getOccupiedMem() < 10 * 1024 * 1024, " peak memory: ", getMaxMem() < 12 * 1024 * 1024
|
||||
echo getOccupiedMem() < 10 * 1024 * 1024, " peak memory: ", getMaxMem() < 10 * 1024 * 1024
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
discard """
|
||||
targets: "c cpp"
|
||||
output: "13"
|
||||
"""
|
||||
|
||||
# bug #25883: C codegen assigns same type hash to tuples with different nesting
|
||||
# but identical flattened content.
|
||||
# ((Int[1], Int[2]), Int[13], Int[14]) and ((Int[1], Int[2], Int[13]), Int[14])
|
||||
# must get distinct C type names.
|
||||
|
||||
type
|
||||
Int[V: static int] = object
|
||||
|
||||
proc main() =
|
||||
var b = ((1, 2), 13, 14)
|
||||
var c = ((1, 2, 13), 14)
|
||||
echo c[0][2]
|
||||
|
||||
main()
|
||||
|
||||
block:
|
||||
type
|
||||
Int[V: static int] = object
|
||||
Layout[Sh, St] = object
|
||||
shape: Sh
|
||||
stride: St
|
||||
|
||||
func makeB(): auto =
|
||||
Layout[((Int[2], Int[3]), Int[5], Int[7]), ((Int[1], Int[2]), Int[6], Int[30])](
|
||||
shape: ((Int[2](), Int[3]()), Int[5](), Int[7]()),
|
||||
stride: ((Int[1](), Int[2]()), Int[6](), Int[30]())
|
||||
)
|
||||
|
||||
func makeC(): auto =
|
||||
Layout[((Int[2], Int[3], Int[5]), Int[7]), ((Int[1], Int[2], Int[6]), Int[30])](
|
||||
shape: ((Int[2](), Int[3](), Int[5]()), Int[7]()),
|
||||
stride: ((Int[1](), Int[2](), Int[6]()), Int[30]())
|
||||
)
|
||||
|
||||
|
||||
proc main() =
|
||||
let b = makeB()
|
||||
let c = makeC()
|
||||
|
||||
main()
|
||||
@@ -1,14 +0,0 @@
|
||||
discard """
|
||||
output: "ok"
|
||||
targets: "c"
|
||||
matrix: "--genBif:on"
|
||||
"""
|
||||
|
||||
import std/[compilesettings, os]
|
||||
|
||||
let cache = querySetting(nimcacheDir)
|
||||
var hasSemanticBif = false
|
||||
for path in walkFiles(cache / "*.s.bif"):
|
||||
hasSemanticBif = true
|
||||
doAssert hasSemanticBif
|
||||
echo "ok"
|
||||
@@ -1,28 +0,0 @@
|
||||
type
|
||||
NestedPoll = object of RootEffect
|
||||
|
||||
CallbackFunc = proc(arg: pointer) {.gcsafe, raises: [], forbids: [NestedPoll].}
|
||||
TaggedCallbackFunc = proc(arg: pointer) {.gcsafe, raises: [], tags: [], forbids: [NestedPoll].}
|
||||
|
||||
InternalAsyncCallback = object
|
||||
fn: CallbackFunc
|
||||
|
||||
TaggedInternalAsyncCallback = object
|
||||
fn: TaggedCallbackFunc
|
||||
|
||||
proc closeSocket(aftercb: CallbackFunc = nil) =
|
||||
proc continuation(udata: pointer) =
|
||||
aftercb(nil)
|
||||
|
||||
let acb = InternalAsyncCallback(fn: continuation)
|
||||
discard acb
|
||||
|
||||
proc closeSocketTagged(aftercb: TaggedCallbackFunc = nil) =
|
||||
proc continuation(udata: pointer) =
|
||||
aftercb(nil)
|
||||
|
||||
let acb = TaggedInternalAsyncCallback(fn: continuation)
|
||||
discard acb
|
||||
|
||||
closeSocket()
|
||||
closeSocketTagged()
|
||||
@@ -1,9 +0,0 @@
|
||||
discard """
|
||||
errormsg: "cannot infer the return type of 'foo'"
|
||||
line: 6
|
||||
"""
|
||||
|
||||
proc foo(n: int): auto =
|
||||
return foo(n + 1)
|
||||
|
||||
discard foo(0)
|
||||
@@ -1,12 +0,0 @@
|
||||
discard """
|
||||
errormsg: "cannot infer the return type of 'foo'"
|
||||
line: 6
|
||||
"""
|
||||
|
||||
proc foo(n: int): auto =
|
||||
if n > 0:
|
||||
foo(n - 1)
|
||||
else:
|
||||
foo(n + 1)
|
||||
|
||||
discard foo(1)
|
||||
@@ -1,9 +0,0 @@
|
||||
discard """
|
||||
errormsg: "cannot infer the return type of 'foo'"
|
||||
line: 6
|
||||
"""
|
||||
|
||||
proc foo[T](x: T): auto =
|
||||
foo(x)
|
||||
|
||||
discard foo(1)
|
||||
@@ -1,34 +0,0 @@
|
||||
import std/macros
|
||||
|
||||
type
|
||||
Chunk* = ref object
|
||||
x*: int32
|
||||
ChunkTrait* = distinct tuple[
|
||||
loaded: proc(self: pointer, chunk: Chunk)
|
||||
]
|
||||
|
||||
macro makeVTable*(traitType: typedesc): untyped =
|
||||
## Splice the param symbols out of an imported proc type into a fresh proc
|
||||
## type nested in a fresh tuple type. Under `nim ic` the imported trait is
|
||||
## loaded from a NIF cache, so its param symbols are `Sealed`; re-owning them
|
||||
## in `semProcTypeNode` used to trip `ast.nim` `s.state != Sealed`.
|
||||
var t = traitType.getTypeInst[1].getTypeImpl
|
||||
if t.kind == nnkDistinctTy: t = t[0]
|
||||
let formalParams = t[0][1][0]
|
||||
var bridgeParams = nnkFormalParams.newTree(formalParams[0].copyNimTree)
|
||||
bridgeParams.add nnkIdentDefs.newTree(ident"p", ident"pointer", newEmptyNode())
|
||||
for j in 2 ..< formalParams.len:
|
||||
bridgeParams.add formalParams[j].copyNimTree
|
||||
let vtType = nnkTupleTy.newTree(
|
||||
nnkIdentDefs.newTree(ident"m0",
|
||||
nnkProcTy.newTree(bridgeParams, nnkPragma.newTree(ident"nimcall")),
|
||||
newEmptyNode()))
|
||||
let vtName = genSym(nskType, "VT")
|
||||
let vtVar = genSym(nskVar, "vt")
|
||||
result = nnkStmtList.newTree(
|
||||
nnkTypeSection.newTree(
|
||||
nnkTypeDef.newTree(vtName, newEmptyNode(), vtType)),
|
||||
nnkVarSection.newTree(
|
||||
nnkIdentDefs.newTree(
|
||||
nnkPragmaExpr.newTree(vtVar, nnkPragma.newTree(ident"used")),
|
||||
vtName, newEmptyNode())))
|
||||
@@ -1,14 +0,0 @@
|
||||
discard """
|
||||
output: '''ok'''
|
||||
"""
|
||||
|
||||
# Regression test: a `typed` macro in an imported module splices param symbols
|
||||
# out of an imported proc type into a freshly semchecked proc type. Under
|
||||
# `nim ic` those param symbols are loaded `Sealed` from the NIF cache; reusing
|
||||
# them in `newSymG`/`semProcTypeNode` used to fail `ast.nim` `s.state != Sealed`.
|
||||
|
||||
import mtraitparam
|
||||
|
||||
makeVTable(ChunkTrait)
|
||||
|
||||
echo "ok"
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
|
||||
proc byReturn(n: int): auto =
|
||||
if n < 5:
|
||||
return byReturn(n + 1)
|
||||
else:
|
||||
return 9
|
||||
|
||||
proc byResult(n: int): auto =
|
||||
if n < 5:
|
||||
result = byResult(n + 1)
|
||||
else:
|
||||
result = 9
|
||||
|
||||
proc byExpression(n: int): auto =
|
||||
if n < 5:
|
||||
byExpression(n + 1)
|
||||
else:
|
||||
9
|
||||
|
||||
proc generic[T](x: T; n: int): auto =
|
||||
if n < 5:
|
||||
return generic(x, n + 1)
|
||||
else:
|
||||
return x
|
||||
|
||||
proc concreteFirst(n: int): auto =
|
||||
if n >= 5:
|
||||
return 9
|
||||
else:
|
||||
return concreteFirst(n + 1)
|
||||
|
||||
proc multipleRecursiveBranches(n: int): auto =
|
||||
if n < 0:
|
||||
return multipleRecursiveBranches(n + 1)
|
||||
elif n < 5:
|
||||
return multipleRecursiveBranches(n + 1)
|
||||
else:
|
||||
return 9
|
||||
|
||||
doAssert byReturn(3) == 9
|
||||
doAssert byResult(3) == 9
|
||||
doAssert byExpression(3) == 9
|
||||
doAssert generic("ok", 3) == "ok"
|
||||
doAssert concreteFirst(3) == 9
|
||||
doAssert multipleRecursiveBranches(-1) == 9
|
||||
@@ -1,12 +0,0 @@
|
||||
discard """
|
||||
matrix: "--mm:refc; --mm:orc --deepcopy:on"
|
||||
errormsg: "'deepCopy' is not available for type <NoCopy>"
|
||||
file: "system.nim"
|
||||
"""
|
||||
|
||||
type NoCopy = object
|
||||
|
||||
proc `=copy`(a: var NoCopy; b: NoCopy) {.error.}
|
||||
|
||||
var a = new NoCopy
|
||||
var b = deepCopy(a)
|
||||
@@ -1,15 +0,0 @@
|
||||
discard """
|
||||
matrix: "--mm:refc; --mm:orc --deepcopy:on"
|
||||
errormsg: "'deepCopy' is not available for type <Container>"
|
||||
file: "system.nim"
|
||||
"""
|
||||
|
||||
type
|
||||
NoCopy = object
|
||||
Container = object
|
||||
value: NoCopy
|
||||
|
||||
proc `=copy`(a: var NoCopy; b: NoCopy) {.error.}
|
||||
|
||||
var a = new Container
|
||||
var b = deepCopy(a)
|
||||
@@ -1,40 +0,0 @@
|
||||
discard """
|
||||
matrix: "--mm:arc; --mm:orc"
|
||||
"""
|
||||
|
||||
import std/[atomics, typedthreads]
|
||||
|
||||
const numChunks = 23 # More than the allocator's bounded drain can process.
|
||||
|
||||
var
|
||||
pointers: array[numChunks, pointer]
|
||||
allocated: Atomic[bool]
|
||||
continueAllocating: Atomic[bool]
|
||||
|
||||
proc allocPointers() {.thread.} =
|
||||
for i in 0..<pointers.len:
|
||||
pointers[i] = allocShared(8192)
|
||||
allocated.store(true, moRelease)
|
||||
|
||||
while not continueAllocating.load(moAcquire):
|
||||
discard
|
||||
|
||||
# The first allocation drains MaxSteps + 1 chunks. The second allocation
|
||||
# must still be able to find and drain the remainder.
|
||||
for _ in 0..1:
|
||||
let p = allocShared(8192)
|
||||
deallocShared(p)
|
||||
|
||||
doAssert getOccupiedMem() == 0
|
||||
|
||||
var thread: Thread[void]
|
||||
createThread(thread, allocPointers)
|
||||
|
||||
while not allocated.load(moAcquire):
|
||||
discard
|
||||
|
||||
for p in pointers:
|
||||
deallocShared(p)
|
||||
continueAllocating.store(true, moRelease)
|
||||
|
||||
joinThread(thread)
|
||||
@@ -1,101 +0,0 @@
|
||||
type
|
||||
Container = object
|
||||
numbers: seq[int]
|
||||
text: string
|
||||
chars: set[char]
|
||||
|
||||
Variant = object
|
||||
case enabled: bool
|
||||
of false:
|
||||
numbers: seq[int]
|
||||
else:
|
||||
discard
|
||||
|
||||
Index = enum
|
||||
index0, index1, index2, index3, index4, index5, index6, index7,
|
||||
index8, index9, index10, index11, index12, index13, index14, index15,
|
||||
index16, index17, index18, index19, index20, index21, index22, index23,
|
||||
index24, index25, index26, index27, index28, index29, index30, index31,
|
||||
index32
|
||||
|
||||
Outer = object
|
||||
values: array[33, seq[int]]
|
||||
|
||||
proc directSeq(): array[33, seq[int]] =
|
||||
result[32].add 1
|
||||
|
||||
proc directStringChar(): array[33, string] =
|
||||
result[32].add 'a'
|
||||
|
||||
proc directStringString(): array[33, string] =
|
||||
result[32].add "ab"
|
||||
|
||||
proc directSet(): array[33, set[char]] =
|
||||
result[32].incl 'a'
|
||||
|
||||
proc fieldSeq(): array[33, Container] =
|
||||
result[32].numbers.add 1
|
||||
|
||||
proc fieldStringChar(): array[33, Container] =
|
||||
result[32].text.add 'a'
|
||||
|
||||
proc fieldStringString(): array[33, Container] =
|
||||
result[32].text.add "ab"
|
||||
|
||||
proc fieldSet(): array[33, Container] =
|
||||
result[32].chars.incl 'a'
|
||||
|
||||
proc nestedSeq(): array[33, array[33, seq[int]]] =
|
||||
result[32][32].add 1
|
||||
|
||||
proc checkedFieldSeq(): array[33, Variant] =
|
||||
result[32].numbers.add 1
|
||||
|
||||
proc enumIndexSeq(): array[Index, seq[int]] =
|
||||
result[index32].add 1
|
||||
|
||||
proc rangeIndexSeq(): array[10..42, seq[int]] =
|
||||
result[42].add 1
|
||||
|
||||
proc firstIndexSeq(): array[33, seq[int]] =
|
||||
result[0].add 1
|
||||
|
||||
proc middleIndexSeq(): array[33, seq[int]] =
|
||||
result[16].add 1
|
||||
|
||||
proc objectArraySeq(): Outer =
|
||||
result.values[32].add 1
|
||||
|
||||
proc singleEvaluation(): tuple[values: array[33, seq[int]], evaluations: int] =
|
||||
var evaluations = 0
|
||||
proc index(): int =
|
||||
inc evaluations
|
||||
32
|
||||
result.values[index()].add 1
|
||||
result.evaluations = evaluations
|
||||
|
||||
proc test =
|
||||
let direct = directSeq()
|
||||
doAssert direct[0].len == 0
|
||||
doAssert direct[31].len == 0
|
||||
doAssert direct[32] == @[1]
|
||||
doAssert directStringChar()[32] == "a"
|
||||
doAssert directStringString()[32] == "ab"
|
||||
doAssert 'a' in directSet()[32]
|
||||
doAssert fieldSeq()[32].numbers == @[1]
|
||||
doAssert fieldStringChar()[32].text == "a"
|
||||
doAssert fieldStringString()[32].text == "ab"
|
||||
doAssert 'a' in fieldSet()[32].chars
|
||||
doAssert nestedSeq()[32][32] == @[1]
|
||||
doAssert checkedFieldSeq()[32].numbers == @[1]
|
||||
doAssert enumIndexSeq()[index32] == @[1]
|
||||
doAssert rangeIndexSeq()[42] == @[1]
|
||||
doAssert firstIndexSeq()[0] == @[1]
|
||||
doAssert middleIndexSeq()[16] == @[1]
|
||||
doAssert objectArraySeq().values[32] == @[1]
|
||||
let evaluated = singleEvaluation()
|
||||
doAssert evaluated.values[32] == @[1]
|
||||
doAssert evaluated.evaluations == 1
|
||||
|
||||
static: test()
|
||||
test()
|
||||
@@ -1,84 +0,0 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# Deterministic port of dumpster's `fuzz` test
|
||||
# (https://claytonwramsey.com/blog/dumpster/): drive a mutable object graph
|
||||
# through a long random sequence of node/edge inserts and removals, then drop
|
||||
# every root and assert that *every allocation ever made is destroyed exactly
|
||||
# once* -- no leak (count 0) and no double free (count > 1). The graph grows
|
||||
# thick with overlapping and self cycles, so only the cycle collector can wind
|
||||
# it down. A fixed LCG seed makes the shape reproducible across runs.
|
||||
|
||||
type
|
||||
DropCount = object
|
||||
id: int
|
||||
live: bool # false in any moved-from temporary -> never miscounts
|
||||
Node = ref object
|
||||
refs: seq[Node]
|
||||
dc: DropCount
|
||||
|
||||
var counts: seq[int] # counts[id] == times allocation `id` was destroyed
|
||||
|
||||
proc `=destroy`(x: DropCount) =
|
||||
if x.live: inc counts[x.id]
|
||||
|
||||
var nextId = 0
|
||||
proc newNode(): Node =
|
||||
counts.add 0
|
||||
result = Node(refs: @[], dc: DropCount(id: nextId, live: true))
|
||||
inc nextId
|
||||
|
||||
# `child` is a by-value borrow (dumpster's `Gc::clone`): storing it copies the
|
||||
# reference, leaving the caller's root slot still owning. Using `.refs.add`
|
||||
# directly would move the root at its last read and change the graph shape.
|
||||
proc link(parent, child: Node) = parent.refs.add child
|
||||
|
||||
# Small fixed-seed LCG (Numerical Recipes constants) for reproducible shape.
|
||||
var rngState: uint32 = 12345
|
||||
proc rnd(n: int): int =
|
||||
rngState = rngState * 1664525'u32 + 1013904223'u32
|
||||
int((rngState shr 16) mod uint32(n))
|
||||
|
||||
proc run =
|
||||
const N = 20_000
|
||||
var roots: seq[Node]
|
||||
for i in 0 ..< 50: roots.add newNode()
|
||||
|
||||
for _ in 0 ..< N:
|
||||
if roots.len == 0: roots.add newNode()
|
||||
case rnd(4)
|
||||
of 0: # allocate a fresh root
|
||||
roots.add newNode()
|
||||
of 1: # add edge from -> to (may self-loop)
|
||||
let a = rnd(roots.len)
|
||||
let b = rnd(roots.len)
|
||||
link(roots[a], roots[b])
|
||||
of 2: # drop a root handle (swap-remove)
|
||||
let i = rnd(roots.len)
|
||||
roots[i] = roots[roots.high]
|
||||
roots.setLen roots.len - 1
|
||||
else: # drop one outgoing edge of a root
|
||||
let a = rnd(roots.len)
|
||||
if roots[a].refs.len > 0:
|
||||
let j = rnd(roots[a].refs.len)
|
||||
roots[a].refs[j] = roots[a].refs[roots[a].refs.high]
|
||||
roots[a].refs.setLen roots[a].refs.len - 1
|
||||
|
||||
roots.setLen 0 # release every remaining root
|
||||
GC_fullCollect()
|
||||
GC_fullCollect()
|
||||
|
||||
run()
|
||||
|
||||
var missing = 0
|
||||
for id in 0 ..< nextId:
|
||||
if counts[id] != 1:
|
||||
inc missing
|
||||
doAssert missing == 0, "graph not fully reclaimed: " & $missing & " of " &
|
||||
$nextId & " allocations leaked or double-freed"
|
||||
echo "ok"
|
||||
@@ -1,33 +0,0 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# Memory must stay bounded while creating cyclic garbage forever: the
|
||||
# collector has to keep pace with allocation. A leak shows up as unbounded
|
||||
# peak occupancy, which the assertion below catches.
|
||||
|
||||
type Node = ref object
|
||||
next: Node
|
||||
data: seq[int]
|
||||
|
||||
proc mk(n: int) =
|
||||
var h = Node(data: newSeq[int](4))
|
||||
var c = h
|
||||
for i in 1 ..< n:
|
||||
c.next = Node(data: newSeq[int](4))
|
||||
c = c.next
|
||||
c.next = h
|
||||
|
||||
var peak = 0
|
||||
for round in 0 ..< 30:
|
||||
for i in 0 ..< 10_000:
|
||||
mk(8)
|
||||
let occ = getOccupiedMem()
|
||||
if occ > peak: peak = occ
|
||||
doAssert peak < 64 * 1024 * 1024, "memory exploded: leak"
|
||||
GC_fullCollect()
|
||||
echo "ok"
|
||||
@@ -1,26 +0,0 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "done"
|
||||
valgrind: "leaks"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# Smallest possible cycle: a three-node ring that is dead the instant `mk`
|
||||
# returns. GC_fullCollect must reclaim it without touching freed memory.
|
||||
|
||||
type Node = ref object
|
||||
next: Node
|
||||
|
||||
proc mk =
|
||||
let a = Node()
|
||||
let b = Node()
|
||||
let c = Node()
|
||||
a.next = b
|
||||
b.next = c
|
||||
c.next = a
|
||||
|
||||
mk()
|
||||
GC_fullCollect()
|
||||
echo "done"
|
||||
@@ -1,74 +0,0 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
valgrind: "leaks"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# The "parallel_loop" complex graph from Clayton Ramsey's `dumpster` collector
|
||||
# (https://claytonwramsey.com/blog/dumpster/). Four allocations form a single
|
||||
# SCC built from two *overlapping* cycles that share nodes 1 and 4:
|
||||
#
|
||||
# 1 -> 4 4 -> 2, 4 -> 3 2 -> 1, 3 -> 1
|
||||
#
|
||||
# so 1->4->2->1 and 1->4->3->1 traverse the same 1 and 4. Every node keeps a
|
||||
# nonzero refcount from *inside* the SCC, so plain reference counting can never
|
||||
# free any of them; only cycle collection can, and only once the last external
|
||||
# handle is gone. We drop the four root handles one at a time and assert that
|
||||
# nothing is reclaimed until the final drop, then all four die together -- the
|
||||
# exact assertion sequence dumpster's test makes.
|
||||
|
||||
type
|
||||
# A field whose destructor bumps a per-node counter when the cell is freed;
|
||||
# `slot` is nil in any moved-from temporary, so those don't miscount.
|
||||
DropCount = object
|
||||
slot: ptr int
|
||||
Node = ref object
|
||||
refs: seq[Node]
|
||||
dc: DropCount
|
||||
|
||||
proc `=destroy`(x: DropCount) =
|
||||
if x.slot != nil: inc x.slot[]
|
||||
|
||||
# Add an edge parent -> child. `child` is a by-value borrow, so the caller's
|
||||
# handle keeps owning its reference -- this is Nim's equivalent of dumpster's
|
||||
# `Gc::clone`. Building edges with `g1.refs.add g2` instead would *move* g2 at
|
||||
# its last read and silently collapse the graph's root set.
|
||||
proc link(parent, child: Node) = parent.refs.add child
|
||||
|
||||
# drops[0] is unused; nodes are 1..4 to mirror the blog's gc1..gc4. The four
|
||||
# handles live in an array so each stays an independent, still-owning root.
|
||||
var drops: array[5, int]
|
||||
|
||||
proc scenario =
|
||||
var g: array[1..4, Node]
|
||||
for i in 1..4: g[i] = Node(dc: DropCount(slot: addr drops[i]))
|
||||
link(g[2], g[1]) # 2 -> 1
|
||||
link(g[3], g[1]) # 3 -> 1
|
||||
link(g[4], g[2]) # 4 -> 2
|
||||
link(g[4], g[3]) # 4 -> 3
|
||||
link(g[1], g[4]) # 1 -> 4 (closes both cycles)
|
||||
|
||||
GC_fullCollect()
|
||||
doAssert drops == [0, 0, 0, 0, 0], "nothing dead yet"
|
||||
|
||||
g[1] = nil # node1 still held by node2 and node3
|
||||
GC_fullCollect()
|
||||
doAssert drops == [0, 0, 0, 0, 0], "dropping root 1 frees nothing"
|
||||
|
||||
g[2] = nil # node2 still held by node4
|
||||
GC_fullCollect()
|
||||
doAssert drops == [0, 0, 0, 0, 0], "dropping root 2 frees nothing"
|
||||
|
||||
g[3] = nil # node3 still held by node4
|
||||
GC_fullCollect()
|
||||
doAssert drops == [0, 0, 0, 0, 0], "dropping root 3 frees nothing"
|
||||
|
||||
g[4] = nil # last external handle gone: the whole SCC is garbage
|
||||
GC_fullCollect()
|
||||
doAssert drops == [0, 1, 1, 1, 1], "the full cycle is reclaimed at once"
|
||||
|
||||
scenario()
|
||||
echo "ok"
|
||||
@@ -1,33 +0,0 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
valgrind: "leaks"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# Exercise the manual collection API: disable automatic collections, build a
|
||||
# batch of dead cycles, then reclaim them in halves via GC_partialCollect and
|
||||
# confirm the pending count shrinks accordingly.
|
||||
|
||||
type Node = ref object
|
||||
next: Node
|
||||
|
||||
proc mk(n: int) =
|
||||
var h = Node()
|
||||
var c = h
|
||||
for i in 1 ..< n: (c.next = Node(); c = c.next)
|
||||
c.next = h
|
||||
|
||||
GC_disableOrc() # no automatic collections; exercise the partial API
|
||||
for i in 0 ..< 300: mk(4)
|
||||
let pending = GC_prepareOrc()
|
||||
doAssert pending > 0
|
||||
GC_partialCollect(pending div 2) # collect only the upper half
|
||||
let remaining = GC_prepareOrc()
|
||||
doAssert remaining <= pending div 2, $remaining & " vs " & $pending
|
||||
GC_partialCollect(0) # collect the rest
|
||||
doAssert GC_prepareOrc() == 0
|
||||
GC_fullCollect()
|
||||
echo "ok"
|
||||
@@ -1,60 +0,0 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
valgrind: "leaks"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# Functional test for the Tarjan-based collector: doubly-linked dead rings,
|
||||
# self-referential cells, and one surviving ring whose integrity is checked
|
||||
# after a full collect.
|
||||
|
||||
type
|
||||
Node = ref object
|
||||
next: Node
|
||||
prev: Node
|
||||
data: string
|
||||
|
||||
proc makeRing(n: int): Node =
|
||||
result = Node(data: "head")
|
||||
var cur = result
|
||||
for i in 1 ..< n:
|
||||
let x = Node(data: $i)
|
||||
cur.next = x
|
||||
x.prev = cur
|
||||
cur = x
|
||||
cur.next = result
|
||||
result.prev = cur
|
||||
|
||||
proc dropRings =
|
||||
for i in 0 ..< 2000:
|
||||
discard makeRing(10) # dead immediately
|
||||
|
||||
proc keepOne: Node =
|
||||
for i in 0 ..< 100:
|
||||
discard makeRing(5)
|
||||
result = makeRing(7) # survives
|
||||
|
||||
proc selfRef =
|
||||
type S = ref object
|
||||
self: S
|
||||
buf: seq[int]
|
||||
for i in 0 ..< 500:
|
||||
let s = S(buf: newSeq[int](8))
|
||||
s.self = s
|
||||
|
||||
dropRings()
|
||||
selfRef()
|
||||
let keep = keepOne()
|
||||
GC_fullCollect()
|
||||
doAssert keep.data == "head"
|
||||
var cnt = 0
|
||||
var it = keep
|
||||
while true:
|
||||
inc cnt
|
||||
it = it.next
|
||||
if it == keep: break
|
||||
doAssert cnt == 7, "live ring corrupted: " & $cnt
|
||||
echo "ok"
|
||||
@@ -1,72 +0,0 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# Concurrent stress for the lock-free SATB collector: mutator threads rewire
|
||||
# live cyclic structures (constant dirty traffic + capture aborts) and churn
|
||||
# garbage cycles while a dedicated thread runs back-to-back collections. Live
|
||||
# data corruption or a lost object trips a doAssert / a growing residual.
|
||||
|
||||
import std/typedthreads
|
||||
|
||||
type Node = ref object
|
||||
next: Node # ring structure, stable
|
||||
payload: Node # rewired constantly -> candidates + dirty SCCs
|
||||
id: int
|
||||
|
||||
const NWorkers = 3
|
||||
const Iters = 400_000
|
||||
const RingLen = 64
|
||||
|
||||
var stopFlag: bool
|
||||
var done: array[NWorkers, int]
|
||||
|
||||
proc mkRing(tag: int): seq[Node] =
|
||||
result = newSeq[Node](RingLen)
|
||||
for i in 0 ..< RingLen: result[i] = Node(id: tag + i)
|
||||
for i in 0 ..< RingLen:
|
||||
result[i].next = result[(i+1) mod RingLen]
|
||||
result[i].payload = result[(i*13+7) mod RingLen]
|
||||
|
||||
proc verify(ring: seq[Node]; tag: int) =
|
||||
for i in 0 ..< RingLen:
|
||||
doAssert ring[i].id == tag + i, "node corrupted"
|
||||
doAssert ring[i].next.id == tag + (i+1) mod RingLen, "ring broken"
|
||||
doAssert ring[i].payload.id >= tag and ring[i].payload.id < tag + RingLen,
|
||||
"payload points outside ring: live data corrupted"
|
||||
|
||||
proc worker(tid: int) {.thread.} =
|
||||
var tag = tid * 1_000_000
|
||||
var ring = mkRing(tag)
|
||||
for i in 0 ..< Iters:
|
||||
# lock-free barrier hot path: rewire a payload edge inside the live ring.
|
||||
# decs the old target (rc > 0) -> candidate; collections capture the live
|
||||
# ring concurrently and must rescue or abort, never free it.
|
||||
ring[i mod RingLen].payload = ring[(i * 7 + 3) mod RingLen]
|
||||
if (i and 8191) == 0:
|
||||
verify(ring, tag)
|
||||
if (i and 32767) == 0:
|
||||
inc tag, RingLen
|
||||
ring = mkRing(tag) # old ring becomes a garbage cycle tangle
|
||||
verify(ring, tag)
|
||||
done[tid] = 1
|
||||
|
||||
proc collector() {.thread.} =
|
||||
while not stopFlag:
|
||||
GC_runOrc()
|
||||
|
||||
var th: array[NWorkers, Thread[int]]
|
||||
var col: Thread[void]
|
||||
createThread(col, collector)
|
||||
for i in 0 ..< NWorkers: createThread(th[i], worker, i)
|
||||
joinThreads(th)
|
||||
stopFlag = true
|
||||
joinThread(col)
|
||||
for i in 0 ..< NWorkers: doAssert done[i] == 1
|
||||
GC_fullCollect()
|
||||
GC_fullCollect()
|
||||
echo "ok"
|
||||
@@ -1,60 +0,0 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# N threads each churn garbage cycles while maintaining one live ring that is
|
||||
# verified continuously and replaced, plus explicit GC_runOrc collections from
|
||||
# every thread. Corruption of live data trips a doAssert.
|
||||
|
||||
import std/typedthreads
|
||||
|
||||
type Node = ref object
|
||||
next: Node
|
||||
prev: Node
|
||||
id: int
|
||||
|
||||
const NThreads = 4
|
||||
const Iters = 30_000
|
||||
|
||||
proc mkRing(n, tag: int): Node =
|
||||
result = Node(id: tag)
|
||||
var c = result
|
||||
for i in 1 ..< n:
|
||||
let x = Node(id: tag + i)
|
||||
c.next = x
|
||||
x.prev = c
|
||||
c = x
|
||||
c.next = result
|
||||
result.prev = c
|
||||
|
||||
proc checkRing(r: Node; n, tag: int) =
|
||||
var c = r
|
||||
for i in 0 ..< n:
|
||||
doAssert c.id == tag + i, "ring corrupted!"
|
||||
c = c.next
|
||||
doAssert c == r, "ring not closed!"
|
||||
|
||||
var results: array[NThreads, int]
|
||||
|
||||
proc worker(tid: int) {.thread.} =
|
||||
var keep = mkRing(5, tid * 1000)
|
||||
for i in 0 ..< Iters:
|
||||
discard mkRing(3 + (i and 7), 999999) # garbage
|
||||
if (i and 255) == 0:
|
||||
checkRing(keep, 5, tid * 1000)
|
||||
keep = mkRing(5, tid * 1000) # old keep becomes garbage
|
||||
if (i and 1023) == 0:
|
||||
GC_runOrc() # explicit collections from all threads
|
||||
checkRing(keep, 5, tid * 1000)
|
||||
results[tid] = 1
|
||||
|
||||
var th: array[NThreads, Thread[int]]
|
||||
for i in 0 ..< NThreads: createThread(th[i], worker, i)
|
||||
joinThreads(th)
|
||||
for i in 0 ..< NThreads: doAssert results[i] == 1
|
||||
GC_fullCollect()
|
||||
echo "ok"
|
||||
@@ -52,7 +52,7 @@ proc updateSubmodules*(dir: string, allowBundled = false) =
|
||||
let oldDir = getCurrentDir()
|
||||
setCurrentDir(dir)
|
||||
try:
|
||||
exec "git submodule update --init --recursive"
|
||||
exec "git submodule update --init"
|
||||
finally:
|
||||
setCurrentDir(oldDir)
|
||||
elif allowBundled:
|
||||
|
||||
Reference in New Issue
Block a user