mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 03:13:41 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5bf6d55d6 | ||
|
|
6eef0cc2d5 | ||
|
|
f8e470eb57 | ||
|
|
f5d9e7a207 | ||
|
|
acb9b3a4f1 | ||
|
|
7171e6f01f | ||
|
|
9d7c0cc683 | ||
|
|
c292ab987b | ||
|
|
587f90a816 | ||
|
|
8ad1d106ec | ||
|
|
67707a54b5 | ||
|
|
0f751695e4 | ||
|
|
9db9b8ce57 | ||
|
|
b44d373b7d | ||
|
|
7fa006c4e5 | ||
|
|
1376052519 | ||
|
|
13d152a4d1 | ||
|
|
c620adcfce | ||
|
|
eaa4b342be | ||
|
|
0448557bfe | ||
|
|
07685f79e0 | ||
|
|
f5c43ad759 | ||
|
|
48621c217f | ||
|
|
4d0663096c | ||
|
|
e942da94b5 | ||
|
|
b6842c144d | ||
|
|
b000d4a32a | ||
|
|
7a5e35c83e | ||
|
|
2d148edeb8 | ||
|
|
d9e28aac8e | ||
|
|
c84764a097 | ||
|
|
1d7510dff0 | ||
|
|
9b80b2e868 | ||
|
|
f5930d0bb3 | ||
|
|
4497d89267 | ||
|
|
f959a02037 | ||
|
|
3c6449dbdd | ||
|
|
f1ff8b6d9e | ||
|
|
46259cd0b8 | ||
|
|
4b374eb0a6 | ||
|
|
c8e805a2fa | ||
|
|
73986c03a1 | ||
|
|
88a18de44f | ||
|
|
7813bd8b92 | ||
|
|
645e131739 | ||
|
|
7d2f28b046 | ||
|
|
f4dd00c4cc | ||
|
|
3e2cea21ed | ||
|
|
cfa769fefc |
2
.github/workflows/ci_packages.yml
vendored
2
.github/workflows/ci_packages.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
DEBIAN_FRONTEND='noninteractive' \
|
||||
sudo apt-get install --no-install-recommends -yq \
|
||||
libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev \
|
||||
valgrind libc6-dbg libblas-dev liblapack-dev libpcre2-dev xorg-dev
|
||||
valgrind libc6-dbg libblas-dev liblapack-dev libpcre3 xorg-dev
|
||||
- name: 'Install dependencies (macOS)'
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install boehmgc make sfml gtk+3
|
||||
|
||||
17
changelog.md
17
changelog.md
@@ -43,6 +43,15 @@ parameter and result types, not just their source-level shape. Use
|
||||
|
||||
[//]: # "Additions:"
|
||||
|
||||
- Added `system.readRawDataStable`, a companion to `readRawData` that returns a
|
||||
raw `ptr UncheckedArray[char]` into a string's character data which stays valid
|
||||
across moves and copies of the string value. It is available under every string
|
||||
implementation (refc, ARC/ORC and `--strings:sso`) with the same signature, so
|
||||
code can pin an interior buffer pointer today and be ready for `--strings:sso`
|
||||
without `when declared` guards. Under `--strings:sso` it promotes a small inline
|
||||
string to its heap representation first; under the other implementations the data
|
||||
is already heap-resident, so it is equivalent to `readRawData`.
|
||||
|
||||
- `setutils.symmetricDifference` along with its operator version
|
||||
`` setutils.`-+-` `` and in-place version `setutils.toggle` have been added
|
||||
to more efficiently calculate the symmetric difference of bitsets.
|
||||
@@ -72,18 +81,22 @@ parameter and result types, not just their source-level shape. Use
|
||||
|
||||
- `std/nre2` is added to replace deprecated NRE.
|
||||
|
||||
- `system.typeof` adds a new parameter `modifierMode` to specify how type modifiers are handled.
|
||||
|
||||
[//]: # "Changes:"
|
||||
|
||||
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.
|
||||
- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function.
|
||||
- `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation.
|
||||
- `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`.
|
||||
- `std/re` and `std/nre` now use PCRE2. They remain deprecated;
|
||||
use https://github.com/nitely/nim-regex or `std/nre2`.
|
||||
- `std/re` and `std/nre` are deprecated as PCRE library is obsolete.
|
||||
Use https://github.com/nitely/nim-regex or `std/nre2`.
|
||||
See: https://github.com/nim-lang/Nim/issues/23668.
|
||||
- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style
|
||||
terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``)
|
||||
works without single-quoting.
|
||||
- `std/uri`: The `?` operator now appends query parameters to an existing query
|
||||
string instead of replacing it. Fixes [#19782](https://github.com/nim-lang/Nim/issues/19782).
|
||||
|
||||
## Language changes
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@ proc setupProgram*(config: ConfigRef; cache: IdentCache) =
|
||||
when not defined(nimKochBootstrap):
|
||||
program = createDecodeContext(config, cache)
|
||||
|
||||
proc setIcMainModule*(fileIdx: FileIndex) =
|
||||
## Tells the IC loader which module is being compiled fresh, so that
|
||||
## re-exports of that module's symbols by dependencies are not loaded as
|
||||
## duplicate stubs.
|
||||
when not defined(nimKochBootstrap):
|
||||
ast2nif.setMainModule(program, fileIdx)
|
||||
|
||||
template loadSym(s: PSym) =
|
||||
## Loads a symbol from NIF file if it's in Partial state.
|
||||
when not defined(nimKochBootstrap):
|
||||
@@ -70,6 +77,16 @@ proc backendEnsureMutable*(t: PType) {.inline.} =
|
||||
# ^ IC review this later
|
||||
if t.state == Partial: loadType(t)
|
||||
|
||||
proc unsealForTransform*(t: PType) {.inline.} =
|
||||
## The transformer/lambda lifting also run inside `nim m` when the VM
|
||||
## compiles a LOADED routine (macro evaluation, `getImpl`). Their mutations
|
||||
## are process-local — transformed bodies are never written back to a NIF —
|
||||
## so downgrade the loaded type to mutable, mirroring the `cmdNifC` loader
|
||||
## which loads everything `Complete` for exactly this reason (see
|
||||
## `ast2nif.loadedState`).
|
||||
if t.state == Partial: loadType(t)
|
||||
if t.state == Sealed: t.state = Complete
|
||||
|
||||
proc owner*(s: PSym): PSym {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.ownerFieldImpl
|
||||
@@ -221,7 +238,10 @@ proc position*(s: PSym): int {.inline.} =
|
||||
result = s.positionImpl
|
||||
|
||||
proc `position=`*(s: PSym, val: int) {.inline.} =
|
||||
assert s.state != Sealed
|
||||
# No `Sealed` guard: the VM reuses `position` as a register slot while compiling
|
||||
# a macro for execution (see `vmgen.genGenericParams`), which under IC may be a
|
||||
# macro loaded from a NIF file. The macro is run, not code-generated, so this
|
||||
# scratch mutation is harmless.
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.positionImpl = val
|
||||
|
||||
@@ -445,9 +465,13 @@ var gconfig {.threadvar.}: Gconfig
|
||||
proc setUseIc*(useIc: bool) = gconfig.useIc = useIc
|
||||
|
||||
proc comment*(n: PNode): string =
|
||||
if nfHasComment in n.flags and not gconfig.useIc:
|
||||
# IC doesn't track comments, see `packed_ast`, so this could fail
|
||||
result = gconfig.comments[n.nodeId]
|
||||
if nfHasComment in n.flags:
|
||||
# NIF-based IC doesn't serialize comments, but the comment table is keyed by
|
||||
# the node's address (`nodeId`), which is unique among live nodes; a loaded
|
||||
# node that carries `nfHasComment` simply has no entry here (its comment was
|
||||
# set in another process), so `getOrDefault` safely returns "" for it while
|
||||
# in-process VM macro nodes (e.g. newCommentStmtNode) still round-trip.
|
||||
result = gconfig.comments.getOrDefault(n.nodeId)
|
||||
else:
|
||||
result = ""
|
||||
|
||||
@@ -478,13 +502,6 @@ proc getPIdent*(a: PNode): PIdent {.inline.} =
|
||||
of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: a.sons[0].sym.name
|
||||
else: nil
|
||||
|
||||
const
|
||||
moduleShift = when defined(cpu32): 20 else: 24
|
||||
|
||||
template toId*(a: ItemId): int =
|
||||
let x = a
|
||||
(x.module.int shl moduleShift) + x.item.int
|
||||
|
||||
template id*(a: PType | PSym): int = toId(a.itemId)
|
||||
|
||||
type
|
||||
@@ -493,28 +510,44 @@ type
|
||||
symId*: int32
|
||||
typeId*: int32
|
||||
sealed*: bool
|
||||
backendMinted*: bool
|
||||
disambTable*: CountTable[PIdent]
|
||||
|
||||
const
|
||||
PackageModuleId* = -3'i32
|
||||
|
||||
proc idGeneratorFromModule*(m: PSym): IdGenerator =
|
||||
assert m.kind == skModule
|
||||
result = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0, disambTable: initCountTable[PIdent]())
|
||||
result.disambTable.inc m.name
|
||||
|
||||
proc idGeneratorForBackend*(m: PSym): IdGenerator =
|
||||
## Like `idGeneratorFromModule`, but for IC codegen (`nim nifc`): symbols and
|
||||
## types minted fresh during codegen (transf labels/temps, lifted hooks, type
|
||||
## copies) must not collide with the itemIds the NIF loader synthesizes for
|
||||
## lazily-loaded symbols/types of the same module — those come from a
|
||||
## per-module load-order counter that keeps running while codegen mints its
|
||||
## own ids. A collision corrupts itemId-keyed tables, e.g. `transf`'s inline
|
||||
## iterator mapping then substitutes a random loaded sym (a call's callee)
|
||||
## with a `:tmp` block label. Backend-minted ids carry a marker bit in the
|
||||
## module half (see `itemids.backendItemId`), so the two id spaces are
|
||||
## disjoint by construction.
|
||||
assert m.kind == skModule
|
||||
result = IdGenerator(module: m.itemId.module, symId: 0, typeId: 0,
|
||||
backendMinted: true, disambTable: initCountTable[PIdent]())
|
||||
result.disambTable.inc m.name
|
||||
|
||||
proc idGeneratorForPackage*(nextIdWillBe: int32): IdGenerator =
|
||||
result = IdGenerator(module: PackageModuleId, symId: nextIdWillBe - 1'i32, typeId: 0, disambTable: initCountTable[PIdent]())
|
||||
|
||||
proc nextSymId(x: IdGenerator): ItemId {.inline.} =
|
||||
assert(not x.sealed)
|
||||
inc x.symId
|
||||
result = ItemId(module: x.module, item: x.symId)
|
||||
result = if x.backendMinted: backendItemId(x.module, x.symId)
|
||||
else: itemId(x.module, x.symId)
|
||||
|
||||
proc nextTypeId*(x: IdGenerator): ItemId {.inline.} =
|
||||
assert(not x.sealed)
|
||||
inc x.typeId
|
||||
result = ItemId(module: x.module, item: x.typeId)
|
||||
result = if x.backendMinted: backendItemId(x.module, x.typeId)
|
||||
else: itemId(x.module, x.typeId)
|
||||
|
||||
when false:
|
||||
proc nextId*(x: IdGenerator): ItemId {.inline.} =
|
||||
@@ -1043,6 +1076,11 @@ proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType
|
||||
if result.itemId.module == 55 and result.itemId.item == 2:
|
||||
echo "KNID ", kind
|
||||
writeStackTrace()
|
||||
when defined(icDbg):
|
||||
if kind == tyOpenArray:
|
||||
echo "NEWTYPE openArray id=", id.module, ".", id.item,
|
||||
" owner=", (if owner != nil: owner.name.s else: "nil")
|
||||
echo getStackTrace()
|
||||
|
||||
proc setSons*(dest: PType; sons: sink seq[PType]) {.inline.} =
|
||||
assert dest.kind != tyProc or sons.len <= 1
|
||||
@@ -1105,10 +1143,19 @@ proc copyType*(t: PType, idgen: IdGenerator, owner: PSym): PType =
|
||||
assignType(result, t)
|
||||
result.symImpl = t.sym # backend-info should not be copied
|
||||
|
||||
proc exactReplica*(t: PType): PType =
|
||||
proc exactReplica*(t: PType; idgen: IdGenerator): PType =
|
||||
## Replica that KEEPS `itemId` — the generic-param binding tables
|
||||
## (`LayeredIdTable`) key on it, so the copy must keep matching its
|
||||
## original — but mints a FRESH `uniqueId`: uniqueId is the SERIALIZATION
|
||||
## identity (NIF type names key on it) and must be unique per instance.
|
||||
## Replicas sharing the original's uniqueId serialized as duplicate defs
|
||||
## under one NIF name; the loader collapsed them into a single type,
|
||||
## losing their flag differences (use-site `tfUnresolved` typedescs) or
|
||||
## their structure (meta instance bodies shadowing a generic's canonical
|
||||
## body).
|
||||
result = PType(kind: t.kind, ownerFieldImpl: t.owner, sizeImpl: defaultSize,
|
||||
alignImpl: defaultAlignment, itemId: t.itemId,
|
||||
uniqueId: t.uniqueId)
|
||||
uniqueId: nextTypeId(idgen))
|
||||
assignType(result, t)
|
||||
result.symImpl = t.sym # backend-info should not be copied
|
||||
|
||||
@@ -1271,6 +1318,9 @@ proc transitionNoneToSym*(n: PNode) =
|
||||
transitionNodeKindCommon(nkSym)
|
||||
|
||||
template transitionSymKindCommon*(k: TSymKind) =
|
||||
# Under IC the symbol may still be an unloaded stub (`skStub`); materialise it
|
||||
# first so its kind-specific fields (read below as `obj.*`) actually exist.
|
||||
if s.state == Partial: loadSym(s)
|
||||
let obj {.inject.} = s[]
|
||||
s[] = TSym(kindImpl: k, itemId: obj.itemId, magicImpl: obj.magicImpl, typImpl: obj.typImpl, name: obj.name,
|
||||
infoImpl: obj.infoImpl, ownerFieldImpl: obj.ownerFieldImpl, flagsImpl: obj.flagsImpl, astImpl: obj.astImpl,
|
||||
@@ -1647,9 +1697,13 @@ proc canRaise*(fn: PNode): bool =
|
||||
if fn.typ.n[0].kind == nkSym:
|
||||
result = false
|
||||
else:
|
||||
# A proc-typed value with no explicit raises slot still has
|
||||
# unspecified effects, which sempass2 treats conservatively.
|
||||
# Codegen needs to do the same in order to keep goto-exception
|
||||
# checks after indirect/closure calls.
|
||||
result = ((fn.typ.n[0].len < effectListLen) or
|
||||
(fn.typ.n[0][exceptionEffects] != nil and
|
||||
fn.typ.n[0][exceptionEffects].safeLen > 0))
|
||||
fn.typ.n[0][exceptionEffects] == nil or
|
||||
fn.typ.n[0][exceptionEffects].safeLen > 0)
|
||||
else:
|
||||
result = false
|
||||
|
||||
|
||||
1040
compiler/ast2nif.nim
1040
compiler/ast2nif.nim
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,9 @@ export int128
|
||||
import nodekinds
|
||||
export nodekinds
|
||||
|
||||
import itemids
|
||||
export itemids
|
||||
|
||||
type
|
||||
TCallingConvention* = enum
|
||||
ccNimCall = "nimcall" # nimcall, also the default
|
||||
@@ -571,23 +574,6 @@ const
|
||||
generatedMagics* = {mNone, mIsolate, mFinished, mOpenArrayToSeq}
|
||||
## magics that are generated as normal procs in the backend
|
||||
|
||||
type
|
||||
ItemId* = object
|
||||
module*: int32
|
||||
item*: int32
|
||||
|
||||
proc `$`*(x: ItemId): string =
|
||||
"(module: " & $x.module & ", item: " & $x.item & ")"
|
||||
|
||||
proc `==`*(a, b: ItemId): bool {.inline.} =
|
||||
a.item == b.item and a.module == b.module
|
||||
|
||||
proc hash*(x: ItemId): Hash =
|
||||
var h: Hash = hash(x.module)
|
||||
h = h !& hash(x.item)
|
||||
result = !$h
|
||||
|
||||
|
||||
type
|
||||
PNode* = ref TNode
|
||||
TNodeSeq* = seq[PNode]
|
||||
@@ -1000,7 +986,8 @@ proc newStrNode*(strVal: string; info: TLineInfo): PNode =
|
||||
|
||||
type
|
||||
LogEntryKind* = enum
|
||||
HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry
|
||||
HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry,
|
||||
PureEnumEntry
|
||||
LogEntry* = object
|
||||
kind*: LogEntryKind
|
||||
op*: TTypeAttachedOp
|
||||
|
||||
@@ -43,13 +43,13 @@ proc flagsToStr[T](flags: set[T]): string =
|
||||
proc lineInfoToStr*(conf: ConfigRef; info: TLineInfo): string =
|
||||
result = "["
|
||||
result.addYamlString(toFilename(conf, info))
|
||||
result.addf ", $1, $2]", [toLinenumber(info), toColumn(info)]
|
||||
result.addf ", $1, $2]", toLinenumber(info), toColumn(info)
|
||||
|
||||
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; indent, maxRecDepth: int)
|
||||
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; indent, maxRecDepth: int)
|
||||
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; indent, maxRecDepth: int)
|
||||
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; nl: bool, indent, maxRecDepth: int)
|
||||
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; nl: bool, indent, maxRecDepth: int)
|
||||
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; nl: bool, indent, maxRecDepth: int)
|
||||
|
||||
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; indent: int; maxRecDepth: int) =
|
||||
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; nl: bool, indent: int; maxRecDepth: int) =
|
||||
if n == nil:
|
||||
res.add("null")
|
||||
elif containsOrIncl(marker, n.id):
|
||||
@@ -57,10 +57,12 @@ proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet;
|
||||
else:
|
||||
let istr = spaces(indent * 4)
|
||||
|
||||
if nl:
|
||||
res.addf("\n$1", istr)
|
||||
res.addf("kind: $1", [makeYamlString($n.kind)])
|
||||
res.addf("\n$1name: $2", [istr, makeYamlString(n.name.s)])
|
||||
res.addf("\n$1typ: ", [istr])
|
||||
res.typeToYamlAux(conf, n.typ, marker, indent + 1, maxRecDepth - 1)
|
||||
res.typeToYamlAux(conf, n.typ, marker, true, indent + 1, maxRecDepth - 1)
|
||||
if conf != nil:
|
||||
# if we don't pass the config, we probably don't care about the line info
|
||||
res.addf("\n$1info: $2", [istr, lineInfoToStr(conf, n.info)])
|
||||
@@ -68,7 +70,7 @@ proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet;
|
||||
res.addf("\n$1flags: $2", [istr, flagsToStr(n.flags)])
|
||||
res.addf("\n$1magic: $2", [istr, makeYamlString($n.magic)])
|
||||
res.addf("\n$1ast: ", [istr])
|
||||
res.treeToYamlAux(conf, n.ast, marker, indent + 1, maxRecDepth - 1)
|
||||
res.treeToYamlAux(conf, n.ast, marker, true, indent + 1, maxRecDepth - 1)
|
||||
res.addf("\n$1options: $2", [istr, flagsToStr(n.options)])
|
||||
res.addf("\n$1position: $2", [istr, $n.position])
|
||||
res.addf("\n$1k: $2", [istr, makeYamlString($n.loc.k)])
|
||||
@@ -76,53 +78,57 @@ proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet;
|
||||
if card(n.loc.flags) > 0:
|
||||
res.addf("\n$1flags: $2", [istr, makeYamlString($n.loc.flags)])
|
||||
res.addf("\n$1snippet: $2", [istr, n.loc.snippet])
|
||||
res.addf("\n$1lode: $2", [istr])
|
||||
res.treeToYamlAux(conf, n.loc.lode, marker, indent + 1, maxRecDepth - 1)
|
||||
res.addf("\n$1lode: ", [istr])
|
||||
res.treeToYamlAux(conf, n.loc.lode, marker, true, indent + 1, maxRecDepth - 1)
|
||||
|
||||
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; indent: int; maxRecDepth: int) =
|
||||
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; nl: bool, indent: int; maxRecDepth: int) =
|
||||
if n == nil:
|
||||
res.add("null")
|
||||
elif containsOrIncl(marker, n.id):
|
||||
res.addf "\"$1 @$2\"" % [$n.kind, strutils.toHex(cast[uint](n), sizeof(n) * 2)]
|
||||
else:
|
||||
let istr = spaces(indent * 4)
|
||||
if nl:
|
||||
res.addf("\n$1", istr)
|
||||
res.addf("kind: $2", [istr, makeYamlString($n.kind)])
|
||||
res.addf("\n$1sym: ")
|
||||
res.symToYamlAux(conf, n.sym, marker, indent + 1, maxRecDepth - 1)
|
||||
res.addf("\n$1n: ")
|
||||
res.treeToYamlAux(conf, n.n, marker, indent + 1, maxRecDepth - 1)
|
||||
res.addf("\n$1sym: ", istr)
|
||||
res.symToYamlAux(conf, n.sym, marker, true, indent + 1, maxRecDepth - 1)
|
||||
res.addf("\n$1n: ", istr)
|
||||
res.treeToYamlAux(conf, n.n, marker, true, indent + 1, maxRecDepth - 1)
|
||||
if card(n.flags) > 0:
|
||||
res.addf("\n$1flags: $2", [istr, flagsToStr(n.flags)])
|
||||
res.addf("\n$1callconv: $2", [istr, makeYamlString($n.callConv)])
|
||||
res.addf("\n$1size: $2", [istr, $(n.size)])
|
||||
res.addf("\n$1align: $2", [istr, $(n.align)])
|
||||
if n.hasElementType:
|
||||
res.addf("\n$1sons:")
|
||||
res.addf("\n$1sons:", istr)
|
||||
for a in n.kids:
|
||||
res.addf("\n - ")
|
||||
res.typeToYamlAux(conf, a, marker, indent + 1, maxRecDepth - 1)
|
||||
res.addf("\n$1 - ", istr)
|
||||
res.typeToYamlAux(conf, a, marker, false, indent + 1, maxRecDepth - 1)
|
||||
|
||||
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; indent: int;
|
||||
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; nl: bool, indent: int;
|
||||
maxRecDepth: int) =
|
||||
if n == nil:
|
||||
res.add("null")
|
||||
else:
|
||||
var istr = spaces(indent * 4)
|
||||
if nl:
|
||||
res.addf("\n$1", istr)
|
||||
res.addf("kind: $1" % [makeYamlString($n.kind)])
|
||||
|
||||
if maxRecDepth != 0:
|
||||
if conf != nil:
|
||||
res.addf("\n$1info: $2", [istr, lineInfoToStr(conf, n.info)])
|
||||
case n.kind
|
||||
of nkCharLit .. nkInt64Lit:
|
||||
of nkCharLit .. nkUInt64Lit:
|
||||
res.addf("\n$1intVal: $2", [istr, $(n.intVal)])
|
||||
of nkFloatLit, nkFloat32Lit, nkFloat64Lit:
|
||||
of nkFloatLit .. nkFloat128Lit:
|
||||
res.addf("\n$1floatVal: $2", [istr, n.floatVal.toStrMaxPrecision])
|
||||
of nkStrLit .. nkTripleStrLit:
|
||||
res.addf("\n$1strVal: $2", [istr, makeYamlString(n.strVal)])
|
||||
of nkSym:
|
||||
res.addf("\n$1sym: ", [istr])
|
||||
res.symToYamlAux(conf, n.sym, marker, indent + 1, maxRecDepth)
|
||||
res.symToYamlAux(conf, n.sym, marker, true, indent + 1, maxRecDepth)
|
||||
of nkIdent:
|
||||
if n.ident != nil:
|
||||
res.addf("\n$1ident: $2", [istr, makeYamlString(n.ident.s)])
|
||||
@@ -133,22 +139,22 @@ proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSe
|
||||
res.addf("\n$1sons: ", [istr])
|
||||
for i in 0 ..< n.len:
|
||||
res.addf("\n$1 - ", [istr])
|
||||
res.treeToYamlAux(conf, n[i], marker, indent + 1, maxRecDepth - 1)
|
||||
res.treeToYamlAux(conf, n[i], marker, false, indent + 1, maxRecDepth - 1)
|
||||
if n.typ != nil:
|
||||
res.addf("\n$1typ: ", [istr])
|
||||
res.typeToYamlAux(conf, n.typ, marker, indent + 1, maxRecDepth)
|
||||
res.typeToYamlAux(conf, n.typ, marker, true, indent + 1, maxRecDepth)
|
||||
|
||||
proc treeToYaml*(conf: ConfigRef; n: PNode; indent: int = 0; maxRecDepth: int = -1): string =
|
||||
var marker = initIntSet()
|
||||
result = newStringOfCap(1024)
|
||||
result.treeToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
result.treeToYamlAux(conf, n, marker, false, indent, maxRecDepth)
|
||||
|
||||
proc typeToYaml*(conf: ConfigRef; n: PType; indent: int = 0; maxRecDepth: int = -1): string =
|
||||
var marker = initIntSet()
|
||||
result = newStringOfCap(1024)
|
||||
result.typeToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
result.typeToYamlAux(conf, n, marker, false, indent, maxRecDepth)
|
||||
|
||||
proc symToYaml*(conf: ConfigRef; n: PSym; indent: int = 0; maxRecDepth: int = -1): string =
|
||||
var marker = initIntSet()
|
||||
result = newStringOfCap(1024)
|
||||
result.symToYamlAux(conf, n, marker, indent, maxRecDepth)
|
||||
result.symToYamlAux(conf, n, marker, false, indent, maxRecDepth)
|
||||
|
||||
@@ -394,7 +394,7 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n
|
||||
# variable. Thus, we create a temporary pointer variable instead.
|
||||
let needsIndirect = mapType(p.config, n[0].typ, mapTypeChooser(n[0]) == skParam) != ctArray
|
||||
if needsIndirect:
|
||||
n.typ = n.typ.exactReplica
|
||||
n.typ = n.typ.exactReplica(p.module.idgen)
|
||||
n.typ.incl tfVarIsPtr
|
||||
a = initLocExprSingleUse(p, n)
|
||||
a = withTmpIfNeeded(p, a, needsTmp)
|
||||
@@ -909,6 +909,16 @@ proc isInactiveDestructorCall(p: BProc, e: PNode): bool =
|
||||
proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
if p.withinBlockLeaveActions > 0 and isInactiveDestructorCall(p, ri):
|
||||
return
|
||||
when defined(icDbgHash):
|
||||
if ri[0].typ == nil:
|
||||
echo "NILCALLEE kind=", ri[0].kind,
|
||||
" sym=", (if ri[0].kind == nkSym: ri[0].sym.name.s else: "-"),
|
||||
" symKind=", (if ri[0].kind == nkSym: $ri[0].sym.kind else: "-"),
|
||||
" flags=", (if ri[0].kind == nkSym: $ri[0].sym.flags else: "-"),
|
||||
" lazy=", nfLazyType in ri[0].flags,
|
||||
" inProc=", (if p.prc != nil: p.prc.name.s else: "NIL"),
|
||||
" module=", p.module.module.name.s
|
||||
raiseAssert "nil callee type, see NILCALLEE above"
|
||||
if ri[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
|
||||
genClosureCall(p, le, ri, d)
|
||||
elif ri[0].kind == nkSym and sfInfixCall in ri[0].sym.flags:
|
||||
|
||||
@@ -1904,7 +1904,9 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
var tmp: TLoc = default(TLoc)
|
||||
var r: Rope
|
||||
let needsZeroMem = p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcYrc} or nfAllFieldsSet notin e.flags
|
||||
let needsZeroMem =
|
||||
nfAllFieldsSet notin e.flags or
|
||||
(optSeqDestructors notin p.config.globalOptions and containsGarbageCollectedRef(t))
|
||||
if useTemp:
|
||||
tmp = getTemp(p, t)
|
||||
r = rdLoc(tmp)
|
||||
@@ -3489,7 +3491,23 @@ proc genConstDefinition(q: BModule; p: BProc; sym: PSym) =
|
||||
data.addDeclWithVisibility(Private):
|
||||
data.addVarWithInitializer(Local, actualConstName, typ = td):
|
||||
genBracedInit(q.initProc, sym.astdef, isConst = true, sym.typ, data)
|
||||
q.s[cfsData].add(extract(data))
|
||||
if q.config.cmd == cmdNifC:
|
||||
# Each `cg` process that demands this const emits its definition
|
||||
# (emit-everywhere). Always declare it first (the data analogue of a proc
|
||||
# prototype) so a TU whose copy the merge stage drops still has a valid
|
||||
# declaration; wrap the definition as a droppable `'d'` unit the merge
|
||||
# stage assigns to a single owner.
|
||||
let cname = stripCnifMarks(actualConstName)
|
||||
var decl = newBuilder("")
|
||||
decl.addDeclWithVisibility(Extern):
|
||||
decl.addVar(kind = Local, name = actualConstName, typ = td)
|
||||
q.s[cfsData].add(extract(decl))
|
||||
q.s[cfsData].add(cnifDefDirective(cname, "d", icNifName(q, sym)))
|
||||
q.s[cfsData].add(extract(data))
|
||||
q.s[cfsData].add(cnifEndDefs())
|
||||
q.icDataDefs.add (cname, icNifName(q, sym))
|
||||
else:
|
||||
q.s[cfsData].add(extract(data))
|
||||
if q.hcrOn:
|
||||
# generate the global pointer with the real name
|
||||
q.s[cfsVars].addVar(kind = Global, name = sym.loc.snippet,
|
||||
@@ -3553,6 +3571,17 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
of skProc, skConverter, skIterator, skFunc:
|
||||
#if sym.kind == skIterator:
|
||||
# echo renderTree(sym.getBody, {renderIds})
|
||||
if p.config.cmd == cmdNifC and
|
||||
(isGenericRoutineStrict(sym) or sfCompileTime in sym.flags or
|
||||
(sym.kind == skIterator and sym.typ.callConv == ccInline)):
|
||||
# Under IC a module's top-level routine definitions are serialized as bare
|
||||
# symbol references that reappear in the loaded statement list. Uninstantiated
|
||||
# generic routines (incl. those with type-class params like `tuple`) and
|
||||
# `.compileTime` routines have no run-time code, so skip them here.
|
||||
# Inline iterators likewise have no standalone code — they are always inlined
|
||||
# at their for-loop call sites by the transformer (only closure iterators get
|
||||
# a standalone C function), so a bare serialized def reference is a no-op.
|
||||
return
|
||||
if sfCompileTime in sym.flags:
|
||||
localError(p.config, n.info, "request to generate code for .compileTime proc: " &
|
||||
sym.name.s)
|
||||
@@ -3627,6 +3656,11 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
# echo renderTree(p.prc.ast, {renderIds})
|
||||
internalError(p.config, n.info, "expr: param not init " & sym.name.s & "_" & $sym.id)
|
||||
putLocIntoDest(p, d, sym.loc)
|
||||
of skTemplate, skMacro:
|
||||
# Under IC a module's top-level template/macro definitions are serialized as
|
||||
# bare symbol references (only their interface matters), so they reappear in
|
||||
# the loaded statement list. They are compile-time only and produce no code.
|
||||
discard
|
||||
else: internalError(p.config, n.info, "expr(" & $sym.kind & "); unknown symbol")
|
||||
of nkNilLit:
|
||||
if not isEmptyType(n.typ):
|
||||
|
||||
@@ -1237,6 +1237,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
else:
|
||||
scope = initScope(p.s(cpsStmts))
|
||||
# we handled the error:
|
||||
linefmt(p, cpsStmts, "T$1_ = nullptr;$n", [etmp])
|
||||
expr(p, t[i][0], d)
|
||||
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
|
||||
endBlockWith(p):
|
||||
@@ -1985,4 +1986,9 @@ proc genStmts(p: BProc, t: PNode) =
|
||||
if isPush: pushInfoContext(p.config, t.info)
|
||||
expr(p, t, a)
|
||||
if isPush: popInfoContext(p.config)
|
||||
internalAssert p.config, a.k in {locNone, locTemp, locLocalVar, locExpr}
|
||||
# A bare `nkSym` statement is how IC serializes a definition that lives inside a
|
||||
# top-level block (e.g. a nested `proc`/`var`): codegen emits the definition and
|
||||
# leaves the symbol's own location in `a` (e.g. `locProc`), which is discarded
|
||||
# here, so the value-sanity check below does not apply to it.
|
||||
internalAssert p.config, t.kind == nkSym or
|
||||
a.k in {locNone, locTemp, locLocalVar, locExpr}
|
||||
|
||||
@@ -72,6 +72,37 @@ proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string =
|
||||
else:
|
||||
m.g.mangledPrcs.incl(result)
|
||||
|
||||
proc sharedInstanceCName(m: BModule; s: PSym): string =
|
||||
## The module-free canonical C name for a content-keyed generic instance,
|
||||
## or "" when the symbol must keep its module-suffixed name. With a shared
|
||||
## name, every TU that instantiated the same generic with the same type
|
||||
## arguments calls one extern definition (first claimant's TU embeds it,
|
||||
## see `genProcLvl3`) instead of compiling its own static copy.
|
||||
##
|
||||
## The name is program-unique only if the 30-bit content hash does not
|
||||
## collide for same-named instances of *different* instantiations across
|
||||
## modules — the per-module probe in `setInstanceDisamb` cannot see that.
|
||||
## Claimants therefore must present the same signature; on mismatch the
|
||||
## later one keeps its module-suffixed name (no merge, still correct).
|
||||
## Residual risk: same name and signature, different generic args, AND a
|
||||
## 30-bit collision — vanishingly unlikely; a full-typeKey verification
|
||||
## channel can close it later.
|
||||
result = ""
|
||||
if m.config.cmd == cmdNifC and s.kind in routineKinds and
|
||||
(s.disamb and InstanceDisambBit) != 0'i32 and
|
||||
s.typ != nil and s.typ.callConv != ccInline and not m.hcrOn and
|
||||
{sfImportc, sfExportc, sfCodegenDecl} * s.flags == {}:
|
||||
# The content-derived `disamb` is unique per process (collision-probed in
|
||||
# `setInstanceDisamb`), so the mint-site-independent `_i<disamb>` name is
|
||||
# safe to use directly; identical instances across modules collide on it
|
||||
# exactly and the merge stage keeps one.
|
||||
result = s.name.s.mangle & "_i" & $s.disamb
|
||||
|
||||
proc isSharedInstanceCName(m: BModule; s: PSym): bool =
|
||||
m.config.cmd == cmdNifC and s.kind in routineKinds and
|
||||
(s.disamb and InstanceDisambBit) != 0'i32 and
|
||||
stripCnifMarks(s.loc.snippet) == s.name.s.mangle & "_i" & $s.disamb
|
||||
|
||||
proc fillBackendName(m: BModule; s: PSym) =
|
||||
if s.loc.snippet == "":
|
||||
var result: Rope
|
||||
@@ -79,13 +110,22 @@ proc fillBackendName(m: BModule; s: PSym) =
|
||||
m.g.config.symbolFiles == disabledSf:
|
||||
result = mangleProc(m, s, false).rope
|
||||
else:
|
||||
result = s.name.s.mangle.rope
|
||||
result.add mangleProcNameExt(m.g.graph, s)
|
||||
let shared = sharedInstanceCName(m, s)
|
||||
if shared.len > 0:
|
||||
result = shared.rope
|
||||
else:
|
||||
result = s.name.s.mangle.rope
|
||||
result.add mangleProcNameExt(m.g.graph, s)
|
||||
if m.hcrOn:
|
||||
result.add '_'
|
||||
result.add(idOrSig(s, m.module.name.s.mangle, m.sigConflicts, m.config))
|
||||
backendEnsureMutable s
|
||||
s.locImpl.snippet = result
|
||||
if m.config.cmd == cmdNifC:
|
||||
# mark the name so the cnif artifact writer can turn every occurrence
|
||||
# into a Symbol token; stripped from the actual C output in genModule
|
||||
s.locImpl.snippet = markCName(result)
|
||||
else:
|
||||
s.locImpl.snippet = result
|
||||
|
||||
proc fillParamName(m: BModule; s: PSym) =
|
||||
if s.loc.snippet == "":
|
||||
@@ -373,6 +413,12 @@ proc getSimpleTypeDesc(m: BModule; typ: PType): Rope =
|
||||
m.typeCache[sig] = result
|
||||
|
||||
proc pushType(m: BModule; typ: PType) =
|
||||
when defined(icDbgRefc):
|
||||
if typ.kind == tySequence and
|
||||
typ.elementType.skipTypes({tyGenericInst, tyAlias, tySink}).kind == tyGenericParam:
|
||||
echo "[icRefc] pushType seq-of-genericparam t=", typeToString(typ),
|
||||
" itemId=", typ.itemId.module, ".", typ.itemId.item, " mod=", m.module.name.s
|
||||
echo getStackTrace()
|
||||
for i in 0..high(m.typeStack):
|
||||
# pointer equality is good enough here:
|
||||
if m.typeStack[i] == typ: return
|
||||
@@ -618,6 +664,18 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
|
||||
for i in 1..<t.n.len:
|
||||
if t.n[i].kind != nkSym: internalError(m.config, t.n.info, "genProcParams")
|
||||
var param = t.n[i].sym
|
||||
# The hidden closure environment param (`:envP`) is not a real C parameter:
|
||||
# the environment is passed via the trailing `ClE_0` (added below) and
|
||||
# `closureSetup` materialises `:envP` as a local cast of it. In a from-source
|
||||
# build `:envP` only lives in the routine's AST params, never in the proc
|
||||
# *type's* `n`, so it never reaches here. Under IC `closureParams` re-shares
|
||||
# the AST param node with `typ.n`, so the lifted `:envP` leaks into `t.n`;
|
||||
# emitting it would produce a bogus extra parameter that collides with the
|
||||
# `closureSetup` local (the "redeclared as different kind of symbol" / env
|
||||
# pointer-type mismatch). We still must fill its name/loc (later passes such
|
||||
# as `assignParam` and `closureSetup` reference it), but it is omitted from
|
||||
# the C signature to match the from-source ABI.
|
||||
let isClosureEnv = t.callConv == ccClosure and param.name.s == ":envP"
|
||||
var descKind = dkParam
|
||||
if m.config.backend == backendCpp and optByRef in param.options:
|
||||
if param.typ.kind == tyGenericInst:
|
||||
@@ -629,6 +687,7 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
|
||||
fillParamName(m, param)
|
||||
fillLoc(param.locImpl, locParam, t.n[i],
|
||||
param.paramStorageLoc)
|
||||
if isClosureEnv: continue # name/loc filled, but not part of the C signature
|
||||
var typ: Rope
|
||||
if ccgIntroducedPtr(m.config, param, t.returnType) and descKind == dkParam:
|
||||
typ = ptrType(getTypeDescWeak(m, param.typ, check, descKind))
|
||||
@@ -1108,6 +1167,11 @@ proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDes
|
||||
tyUserTypeClass, tyUserTypeClassInst, tyInferred:
|
||||
result = getTypeDescAux(m, skipModifier(t), check, kind)
|
||||
else:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icRefc] getTypeDescAux ", t.kind, " t=", typeToString(t),
|
||||
" origTyp=", typeToString(origTyp), " t.itemId=", t.itemId.module, ".", t.itemId.item,
|
||||
" sym=", (if t.sym != nil: t.sym.name.s else: "nil"),
|
||||
" owner=", (if t.owner != nil: t.owner.name.s else: "nil")
|
||||
internalError(m.config, "getTypeDescAux(" & $t.kind & ')')
|
||||
result = ""
|
||||
# fixes bug #145:
|
||||
@@ -1146,6 +1210,10 @@ proc finishTypeDescriptions(m: BModule) =
|
||||
var check = initIntSet()
|
||||
while i < m.typeStack.len:
|
||||
let t = m.typeStack[i]
|
||||
when defined(icDbgRefc):
|
||||
echo "[icRefc] finishTypeDescriptions[", i, "] mod=", m.module.name.s,
|
||||
" t=", typeToString(t), " kind=", t.kind,
|
||||
" itemId=", t.itemId.module, ".", t.itemId.item
|
||||
if optSeqDestructors in m.config.globalOptions and t.skipTypes(abstractInst).kind == tySequence:
|
||||
seqV2ContentType(m, t, check)
|
||||
else:
|
||||
@@ -1260,7 +1328,9 @@ proc genProcHeader(m: BModule; prc: PSym; result: var Builder; visibility: var D
|
||||
elif prc.typ.callConv == ccInline or isNonReloadable(m, prc):
|
||||
visibility = StaticProc
|
||||
elif sfImportc notin prc.flags:
|
||||
visibility = Private
|
||||
if not isSharedInstanceCName(m, prc):
|
||||
visibility = Private
|
||||
# else: plain extern — the definition is shared across TUs
|
||||
if asPtr:
|
||||
result.addProcVar(m, prc, name, params, rettype, isStatic = isStaticVar, ignoreAttributes = true)
|
||||
else:
|
||||
@@ -1340,6 +1410,8 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType;
|
||||
else:
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
|
||||
if m.config.cmd == cmdNifC:
|
||||
m.icDataDefs.add (name, icNifName(m, origType))
|
||||
|
||||
proc genTypeInfoAux(m: BModule; typ, origType: PType, name: Rope;
|
||||
info: TLineInfo) =
|
||||
@@ -1627,8 +1699,13 @@ proc declareNimType(m: BModule; name: string; str: Rope, module: int) =
|
||||
m.s[cfsTypeInit1].addArgument(hcrGlobal):
|
||||
m.s[cfsTypeInit1].add("\"" & str & "\"")
|
||||
else:
|
||||
# cnif-mark the name: this extern declaration is the reference the
|
||||
# def-retention check consults when the defining TU regenerates and
|
||||
# the typeinfo cannot be re-demanded (type vanished) — the referencing
|
||||
# TU must lose its reuse then instead of producing a link error
|
||||
let declName = if m.config.cmd == cmdNifC: markCName(str) else: str
|
||||
m.s[cfsStrData].addDeclWithVisibility(Extern):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = str, typ = nr)
|
||||
m.s[cfsStrData].addVar(kind = Local, name = declName, typ = nr)
|
||||
|
||||
proc genTypeInfo2Name(m: BModule; t: PType): Rope =
|
||||
var it = t
|
||||
@@ -1767,6 +1844,8 @@ proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLin
|
||||
cgsym(m, "TNimTypeV2")
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
|
||||
if m.config.cmd == cmdNifC:
|
||||
m.icDataDefs.add (name, icNifName(m, origType))
|
||||
|
||||
var flags = 0
|
||||
if not canFormAcycle(m.g.graph, t): flags = flags or 1
|
||||
@@ -1829,8 +1908,15 @@ proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLin
|
||||
|
||||
proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineInfo) =
|
||||
cgsym(m, "TNimTypeV2")
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
# Under `nim nifc` every `cg` process that demands this type's RTTI emits its
|
||||
# definition (emit-everywhere). The forward declaration must therefore be a
|
||||
# real `extern` (not a tentative definition) so a TU whose copy the merge
|
||||
# stage drops still only *declares* it; the definition itself is wrapped as a
|
||||
# droppable `'d'` unit below and assigned to a single owner.
|
||||
m.s[cfsStrData].addDeclWithVisibility(if m.config.cmd == cmdNifC: Extern else: Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
|
||||
if m.config.cmd == cmdNifC:
|
||||
m.icDataDefs.add (name, icNifName(m, origType))
|
||||
|
||||
var flags = 0
|
||||
if not canFormAcycle(m.g.graph, t): flags = flags or 1
|
||||
@@ -1891,7 +1977,12 @@ proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineIn
|
||||
else:
|
||||
typeEntry.addField(typeInit, name = "flags"):
|
||||
typeEntry.addIntValue(flags)
|
||||
m.s[cfsVars].add extract(typeEntry)
|
||||
if m.config.cmd == cmdNifC:
|
||||
m.s[cfsVars].add(cnifDefDirective(name, "d", icNifName(m, origType)))
|
||||
m.s[cfsVars].add extract(typeEntry)
|
||||
m.s[cfsVars].add(cnifEndDefs())
|
||||
else:
|
||||
m.s[cfsVars].add extract(typeEntry)
|
||||
|
||||
if t.kind == tyObject and t.baseClass != nil and optEnableDeepCopy in m.config.globalOptions:
|
||||
discard genTypeInfoV1(m, t, info)
|
||||
@@ -1930,7 +2021,13 @@ proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
m.typeInfoMarkerV2[sig] = result
|
||||
|
||||
let owner = t.skipTypes(typedescPtrs).itemId.module
|
||||
if owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
|
||||
# In the per-module backend (`cg`) RTTI is emit-everywhere like procs and
|
||||
# consts: every demanding module emits the `'d'` definition (deduped to one
|
||||
# owner by the merge stage). The owner-routing below would instead push the
|
||||
# definition into the owner module's *unwritten* backend module (discarded in
|
||||
# this process) and emit only an extern here, leaving the symbol undefined.
|
||||
let perModuleCg = m.config.cmd == cmdNifC and m.config.icBackendStage == "cg"
|
||||
if not perModuleCg and owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
|
||||
# make sure the type info is created in the owner module
|
||||
discard genTypeInfoV2(m.g.mods[owner], origType, info)
|
||||
# reference the type info as extern here
|
||||
@@ -1997,6 +2094,10 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
|
||||
let marker = m.g.typeInfoMarker.getOrDefault(sig)
|
||||
if marker.str != "":
|
||||
when defined(icDbgRefc):
|
||||
if "catchableerror" in marker.str:
|
||||
echo "[icNti] ", marker.str, " in mod=", m.module.name.s,
|
||||
" -> extern:globalMarker owner=", marker.owner
|
||||
cgsym(m, "TNimType")
|
||||
cgsym(m, "TNimNode")
|
||||
declareNimType(m, "TNimType", marker.str, marker.owner)
|
||||
@@ -2007,8 +2108,16 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
result = "NTI$1$2_" % [rope(typeToC(t)), rope($sig)]
|
||||
m.typeInfoMarker[sig] = result
|
||||
|
||||
when defined(icDbgRefc):
|
||||
template dbgNti(branch: string) =
|
||||
if "catchableerror" in result:
|
||||
echo "[icNti] ", result, " in mod=", m.module.name.s, " -> ", branch
|
||||
else:
|
||||
template dbgNti(branch: string) = discard
|
||||
|
||||
let old = m.g.graph.emittedTypeInfo.getOrDefault($result)
|
||||
if old != FileIndex(0):
|
||||
dbgNti "extern:emittedTypeInfo"
|
||||
cgsym(m, "TNimType")
|
||||
cgsym(m, "TNimNode")
|
||||
declareNimType(m, "TNimType", result, old.int)
|
||||
@@ -2016,6 +2125,7 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
|
||||
var owner = t.skipTypes(typedescPtrs).itemId.module
|
||||
if owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
|
||||
dbgNti "extern:ownerRouted"
|
||||
# make sure the type info is created in the owner module
|
||||
discard genTypeInfoV1(m.g.mods[owner], origType, info)
|
||||
# reference the type info as extern here
|
||||
@@ -2026,6 +2136,7 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
else:
|
||||
owner = m.module.position.int32
|
||||
|
||||
dbgNti "DEFINED-HERE"
|
||||
m.g.typeInfoMarker[sig] = (str: result, owner: owner)
|
||||
#rememberEmittedTypeInfo(m.g.graph, FileIndex(owner), $result)
|
||||
|
||||
|
||||
@@ -112,10 +112,13 @@ proc encodeName*(name: string): string =
|
||||
|
||||
proc makeUnique(m: BModule; s: PSym, name: string = ""): string =
|
||||
result = if name == "": s.name.s else: name
|
||||
# keep backend-minted ids out of the `_u` namespace; their item counter
|
||||
# restarts at 0 and would collide with loaded symbols' ids
|
||||
result.add(if s.itemId.isBackendMinted: "_c" else: "_u")
|
||||
result.add $s.itemId.item
|
||||
# module suffix LAST (a strippable trailing token; see `mangleProcNameExt`)
|
||||
result.add "__"
|
||||
result.add m.g.graph.ifaces[s.itemId.module].uniqueName
|
||||
result.add "_u"
|
||||
result.add $s.itemId.item
|
||||
|
||||
proc encodeSym*(m: BModule; s: PSym; makeUnique: bool = false; extra: string = ""): string =
|
||||
#Module::Type
|
||||
|
||||
@@ -19,6 +19,10 @@ import
|
||||
mangleutils, cbuilderbase, modulegraphs
|
||||
|
||||
from expanddefaults import caseObjDefaultBranch
|
||||
from ast2nif import globalName, toNifFilename, icNifTypeName
|
||||
from typekeys import modname
|
||||
from std/algorithm import sort
|
||||
import cnif
|
||||
|
||||
import pipelineutils
|
||||
|
||||
@@ -51,7 +55,7 @@ when not declared(dynlib.libCandidates):
|
||||
else:
|
||||
dest.add(s)
|
||||
|
||||
when options.hasTinyCBackend:
|
||||
when defined(tinyc): # == hasTinyCBackend; spelled out for the IC dep scanner
|
||||
import tccgen
|
||||
|
||||
proc hcrOn(m: BModule): bool = m.config.hcrOn
|
||||
@@ -61,9 +65,18 @@ proc addForwardedProc(m: BModule, prc: PSym) =
|
||||
m.g.forwardedProcs.add(prc)
|
||||
|
||||
proc newModule*(g: BModuleList; module: PSym; conf: ConfigRef; idgen: IdGenerator): BModule
|
||||
proc getCFile*(m: BModule): AbsoluteFile
|
||||
|
||||
proc findPendingModule(m: BModule, s: PSym): BModule =
|
||||
# TODO fixme
|
||||
if m.config.cmd == cmdNifC and m.config.icBackendStage == "cg":
|
||||
# Per-module backend codegen: only module M (`m`) is emitted in this
|
||||
# process, so every demanded definition — whether a normal proc owned by
|
||||
# another (here unwritten) module or a minted instance/hook — is emitted
|
||||
# into M's TU. Definitions owned elsewhere are emitted again by their own
|
||||
# module's cg process; the merge stage keeps one per C name and turns the
|
||||
# rest into prototypes (which already live in the unmarked protos section).
|
||||
return m
|
||||
if m.config.symbolFiles == v2Sf or optCompress in m.config.globalOptions:
|
||||
let ms = s.itemId.module #getModule(s)
|
||||
result = m.g.mods[ms]
|
||||
@@ -71,15 +84,52 @@ proc findPendingModule(m: BModule, s: PSym): BModule =
|
||||
var ms = getModule(s)
|
||||
registerModule m.g.graph, ms
|
||||
if ms.position >= m.g.mods.len:
|
||||
result = newModule(m.g, ms, m.config, idGeneratorFromModule(ms))
|
||||
result = newModule(m.g, ms, m.config, idGeneratorForBackend(ms))
|
||||
else:
|
||||
result = m.g.mods[ms.position]
|
||||
if result == nil:
|
||||
result = newModule(m.g, ms, m.config, idGeneratorFromModule(ms))
|
||||
result = newModule(m.g, ms, m.config, idGeneratorForBackend(ms))
|
||||
else:
|
||||
var ms = getModule(s)
|
||||
result = m.g.mods[ms.position]
|
||||
|
||||
proc icNifName(m: BModule; s: PSym): string =
|
||||
## The serialized NIF name of `s`, recorded next to its C name in the cnif
|
||||
## artifact so a later run can re-demand the definition when a reused TU
|
||||
## still references it (the def-retention check). Backend-minted symbols
|
||||
## have no NIF name.
|
||||
if m.config.cmd == cmdNifC and s != nil and not isBackendMinted(s.itemId):
|
||||
result = globalName(s, m.config)
|
||||
else:
|
||||
result = ""
|
||||
|
||||
proc icNifName(m: BModule; t: PType): string =
|
||||
## The type flavor: recorded next to RTTI data definitions so the
|
||||
## def-retention check can re-demand the typeinfo of a regenerating TU's
|
||||
## previous artifact (`genTypeInfo` is type-driven, not symbol-driven).
|
||||
if m.config.cmd == cmdNifC:
|
||||
result = icNifTypeName(t, m.config)
|
||||
else:
|
||||
result = ""
|
||||
|
||||
|
||||
proc emitsBodyInThisModule(m: BModule, prc: PSym): bool =
|
||||
## Per-module backend codegen is concerned with ONE module: it emits the
|
||||
## bodies of the routines that module OWNS (its own top-level defs) and only
|
||||
## *prototypes* a routine owned by another module — that routine's body is
|
||||
## emitted by its own module's `cg` process, and the merge stage's DCE prunes
|
||||
## whatever ends up globally dead. The funnel where the main module re-emitted
|
||||
## its entire transitive closure (≈1.8 GB, a 56 MB `.c.nif`) is exactly this
|
||||
## rule being absent.
|
||||
##
|
||||
## Generic instances and synthesized hooks (`=destroy`, `$`, …) have no single
|
||||
## owning-module top-level — they are minted on demand — so each demander emits
|
||||
## them and the merge stage deduplicates by their content-addressed C name.
|
||||
if not (m.config.cmd == cmdNifC and m.config.icBackendStage == "cg"):
|
||||
return true
|
||||
result = prc.itemId.module == m.module.position or
|
||||
(prc.disamb and (InstanceDisambBit or HookDisambBit)) != 0'i32
|
||||
|
||||
proc initLoc(k: TLocKind, lode: PNode, s: TStorageLoc, flags: TLocFlags = {}): TLoc =
|
||||
result = TLoc(k: k, storage: s, lode: lode,
|
||||
snippet: "", flags: flags)
|
||||
@@ -124,8 +174,6 @@ proc useHeader(m: BModule, sym: PSym) =
|
||||
proc cgsym(m: BModule, name: string)
|
||||
proc cgsymValue(m: BModule, name: string): Rope
|
||||
|
||||
proc getCFile(m: BModule): AbsoluteFile
|
||||
|
||||
proc getModuleDllPath(m: BModule): Rope =
|
||||
let (dir, name, ext) = splitFile(getCFile(m))
|
||||
let filename = strutils.`%`(platform.OS[m.g.config.target.targetOS].dllFrmt, [name & ext])
|
||||
@@ -756,6 +804,9 @@ proc assignGlobalVar(p: BProc, n: PNode; value: Rope) =
|
||||
useHeader(p.module, s)
|
||||
if lfNoDecl in s.loc.flags: return
|
||||
if not containsOrIncl(p.module.declaredThings, s.id):
|
||||
if p.config.cmd == cmdNifC and sfImportc notin s.flags:
|
||||
p.module.icDataDefs.add (stripCnifMarks(s.loc.snippet),
|
||||
icNifName(p.module, s))
|
||||
if sfThread in s.flags:
|
||||
declareThreadVar(p.module, s, sfImportc in s.flags)
|
||||
if value != "":
|
||||
@@ -1316,6 +1367,34 @@ proc genProcBody(p: BProc; procBody: PNode) =
|
||||
p.blocks[0].sections[cpsInit].addCall(cgsymValue(p.module, "nimErrorFlag"))
|
||||
|
||||
proc genProcLvl3*(m: BModule, prc: PSym) =
|
||||
if m.config.cmd == cmdNifC:
|
||||
fillBackendName(m, prc)
|
||||
if (prc.disamb and (InstanceDisambBit or HookDisambBit)) != 0'i32 and
|
||||
containsOrIncl(m.emittedContentDefs, stripCnifMarks(prc.loc.snippet)):
|
||||
# A different symbol already emitted a body under this content-addressed
|
||||
# C name in this TU (same generic instance / hook minted in two source
|
||||
# modules, both loaded here). Emitting a second body is a C redefinition;
|
||||
# a prototype was already produced for it, so just stop.
|
||||
return
|
||||
if sfDispatcher in prc.flags and sfMainModule notin m.module.flags:
|
||||
# A method dispatcher enumerates the whole program's method set: its
|
||||
# body is synthesized by `generateIfMethodDispatchers` only after all
|
||||
# modules have been generated, and its single definition is emitted
|
||||
# into the main TU by `finishModule` (main is finished last and never
|
||||
# reused, so the definition can never go stale inside a cached TU).
|
||||
# Any demand before that point yields a prototype.
|
||||
genProcPrototype(m, prc)
|
||||
return
|
||||
if prc.itemId.module != m.module.position and
|
||||
not isBackendMinted(prc.itemId) and
|
||||
(prc.typ == nil or prc.typ.callConv != ccInline) and
|
||||
sfDispatcher notin prc.flags:
|
||||
# this TU embeds a definition whose body lives in another module's
|
||||
# NIF: record the impl dependency (the artifact's cdeps head) so the
|
||||
# reuse gate re-checks that module's impl cookie. Inline bodies are
|
||||
# already part of the iface cookie; dispatcher bodies are synthesized
|
||||
# from the whole program and live in main, which never reuses.
|
||||
m.icImplMods.incl prc.itemId.module
|
||||
var p = newProc(prc, m)
|
||||
var header = newBuilder("")
|
||||
let isCppMember = m.config.backend == backendCpp and sfCppMember * prc.flags != {}
|
||||
@@ -1436,7 +1515,37 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
|
||||
generatedProc.add(extract(p.s(cpsStmts)))
|
||||
if optStackTrace in prc.options: generatedProc.add(deinitFrame(p))
|
||||
generatedProc.add(returnStmt)
|
||||
m.s[cfsProcs].add(extract(generatedProc))
|
||||
if m.config.cmd == cmdNifC:
|
||||
# definition directive for the cnif artifact: groups the proc's text
|
||||
# under its name and carries the root-relevant flags. The end directive
|
||||
# right after the text makes the definition self-delimiting, so raw
|
||||
# cfsProcs emitters (NimMain block, trav markers, ...) never end up
|
||||
# inside a definition's span.
|
||||
var defFlags = ""
|
||||
if sfExportc in prc.flags or sfConstructor in prc.flags: defFlags.add 'x'
|
||||
if sfCompilerProc in prc.flags: defFlags.add 'c'
|
||||
if prc.kind == skMethod or sfDispatcher in prc.flags: defFlags.add 'm'
|
||||
if (prc.typ == nil or prc.typ.callConv != ccInline) and
|
||||
sfDispatcher notin prc.flags:
|
||||
# A unique program-wide definition: external linkage, so exactly one
|
||||
# translation unit may embed its body and everyone else declares it.
|
||||
# Each module's `cg` process emits the body (emit-everywhere); this flag
|
||||
# tells the merge stage which definitions to assign a single owner and
|
||||
# prototype in the rest. The complement — inline procs and method
|
||||
# dispatchers — is emitted into every using TU (`static`/main-only) and
|
||||
# must never be deduplicated.
|
||||
defFlags.add 'u'
|
||||
if not hasCnifMarks(prc.loc.snippet):
|
||||
# The C name was not minted through `fillBackendName` (e.g. set by an
|
||||
# `extern`/`rtl` pragma at sem time), so its uses are invisible to the
|
||||
# artifact's liveness walk — conservatively keep the definition.
|
||||
defFlags.add 'x'
|
||||
m.s[cfsProcs].add(cnifDefDirective(stripCnifMarks(prc.loc.snippet), defFlags,
|
||||
icNifName(m, prc)))
|
||||
m.s[cfsProcs].add(extract(generatedProc))
|
||||
m.s[cfsProcs].add(cnifEndDefs())
|
||||
else:
|
||||
m.s[cfsProcs].add(extract(generatedProc))
|
||||
if isReloadable(m, prc):
|
||||
m.s[cfsDynLibInit].add('\t')
|
||||
m.s[cfsDynLibInit].addAssignmentWithValue(prc.loc.snippet):
|
||||
@@ -1482,10 +1591,15 @@ proc genProcPrototype(m: BModule, sym: PSym) =
|
||||
var header = newBuilder("")
|
||||
var visibility: DeclVisibility = None
|
||||
genProcHeader(m, sym, header, visibility, asPtr = asPtr, addAttributes = true)
|
||||
# A prototype is not a *use*: strip the cnif name marks so the artifact's
|
||||
# liveness walk does not see every forward-declared proc as referenced.
|
||||
var headerText = extract(header)
|
||||
if m.config.cmd == cmdNifC:
|
||||
headerText = stripCnifMarks(headerText)
|
||||
if asPtr:
|
||||
m.s[cfsProcHeaders].addDeclWithVisibility(visibility):
|
||||
# genProcHeader would give variable declaration, add it directly
|
||||
m.s[cfsProcHeaders].add(extract(header))
|
||||
m.s[cfsProcHeaders].add(headerText)
|
||||
else:
|
||||
let extraVis =
|
||||
if sym.typ.callConv != ccInline and requiresExternC(m, sym):
|
||||
@@ -1494,7 +1608,7 @@ proc genProcPrototype(m: BModule, sym: PSym) =
|
||||
None
|
||||
m.s[cfsProcHeaders].addDeclWithVisibility(extraVis):
|
||||
m.s[cfsProcHeaders].addDeclWithVisibility(visibility):
|
||||
m.s[cfsProcHeaders].add(extract(header))
|
||||
m.s[cfsProcHeaders].add(headerText)
|
||||
m.s[cfsProcHeaders].finishProcHeaderAsProto()
|
||||
|
||||
include inliner
|
||||
@@ -1572,7 +1686,8 @@ proc genProcLvl2(m: BModule, prc: PSym) =
|
||||
# which will actually become a function pointer
|
||||
if isReloadable(m, prc):
|
||||
genProcPrototype(q, prc)
|
||||
genProcLvl3(q, prc)
|
||||
if emitsBodyInThisModule(m, prc):
|
||||
genProcLvl3(q, prc)
|
||||
else:
|
||||
fillProcLoc(m, prc.ast[namePos])
|
||||
useHeader(m, prc)
|
||||
@@ -1582,7 +1697,7 @@ proc requestConstImpl(p: BProc, sym: PSym) =
|
||||
if genConstSetup(p, sym):
|
||||
let m = p.module
|
||||
# declare implementation:
|
||||
var q = findPendingModule(m, sym)
|
||||
let q = findPendingModule(m, sym)
|
||||
if q != nil and not containsOrIncl(q.declaredThings, sym.id):
|
||||
assert q.initProc.module == q
|
||||
genConstDefinition(q, p, sym)
|
||||
@@ -1606,6 +1721,12 @@ proc genProc(m: BModule, prc: PSym) =
|
||||
if not containsOrIncl(m.g.generatedHeader.declaredThings, prc.id):
|
||||
genProcLvl3(m.g.generatedHeader, prc)
|
||||
|
||||
proc requestProcDef*(m: BModule, prc: PSym) =
|
||||
## Public demand entry: request `prc`'s definition; it is routed to the
|
||||
## module that owns it and generated once, exactly as if some generated
|
||||
## code had referenced it.
|
||||
genProc(m, prc)
|
||||
|
||||
proc genVarPrototype(m: BModule, n: PNode) =
|
||||
#assert(sfGlobal in sym.flags)
|
||||
let sym = n.sym
|
||||
@@ -1675,7 +1796,7 @@ proc getSomeNameForModule(conf: ConfigRef, filename: AbsoluteFile): Rope =
|
||||
## Returns a mangled module name.
|
||||
result = mangleModuleName(conf, filename).mangle
|
||||
|
||||
proc getSomeNameForModule(m: BModule): Rope =
|
||||
proc getSomeNameForModule*(m: BModule): Rope =
|
||||
## Returns a mangled module name.
|
||||
assert m.module.kind == skModule
|
||||
assert m.module.owner.kind == skPackage
|
||||
@@ -2062,6 +2183,40 @@ proc registerModuleToMain(g: BModuleList; m: BModule) =
|
||||
else:
|
||||
g.otherModsInit.addCallStmt(init)
|
||||
|
||||
proc registerReusedModuleToMain*(g: BModuleList; m: BModule;
|
||||
initRequired, datInitRequired: bool) =
|
||||
## `registerModuleToMain` for a module whose cached translation unit is
|
||||
## reused: the init/datInit presence comes from the artifact's meta head
|
||||
## instead of the (never generated) sections. Mirrors the non-hcr path of
|
||||
## `registerModuleToMain` — reuse is disabled when hcr is on.
|
||||
let
|
||||
init = m.getInitName
|
||||
datInit = m.getDatInitName
|
||||
|
||||
if datInitRequired:
|
||||
g.mainModProcs.addDeclWithVisibility(Private):
|
||||
g.mainModProcs.addProcHeader(ccNimCall, datInit, CVoid, cProcParams())
|
||||
g.mainModProcs.finishProcHeaderAsProto()
|
||||
g.mainDatInit.addCallStmt(datInit)
|
||||
|
||||
if sfSystemModule in m.module.flags:
|
||||
if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
|
||||
g.mainDatInit.addCallStmt(cgsymValue(m, "initThreadVarsEmulation"))
|
||||
if m.config.target.targetOS != osStandalone and m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
g.mainDatInit.addCallStmt(cgsymValue(m, "initStackBottomWith"),
|
||||
cCast(CPointer, cAddr("inner")))
|
||||
|
||||
if initRequired:
|
||||
g.mainModProcs.addDeclWithVisibility(Private):
|
||||
g.mainModProcs.addProcHeader(ccNimCall, init, CVoid, cProcParams())
|
||||
g.mainModProcs.finishProcHeaderAsProto()
|
||||
if sfMainModule in m.module.flags:
|
||||
g.mainModInit.addCallStmt(init)
|
||||
elif sfSystemModule in m.module.flags:
|
||||
g.mainDatInit.addCallStmt(init) # systemInit right after systemDatInit
|
||||
else:
|
||||
g.otherModsInit.addCallStmt(init)
|
||||
|
||||
proc genDatInitCode(m: BModule) =
|
||||
## this function is called in cgenWriteModules after all modules are closed,
|
||||
## it means raising dependency on the symbols is too late as it will not propagate
|
||||
@@ -2309,6 +2464,16 @@ proc genModule(m: BModule, cfile: Cfile): Rope =
|
||||
moduleIsEmpty = false
|
||||
res.add(extract(m.s[i]))
|
||||
|
||||
# what `registerModuleToMain` will announce for this module; recorded in
|
||||
# the artifact's meta head so a later run can reuse the TU
|
||||
let initRequired = m.s[cfsInitProc].buf.len > 0
|
||||
let datInitRequired = m.s[cfsDatInitProc].buf.len > 0
|
||||
|
||||
if m.config.cmd == cmdNifC:
|
||||
# close the definitions section: the init procs that follow belong to
|
||||
# the artifact's top level (always-run code, hence liveness roots)
|
||||
res.add(cnifEndDefs())
|
||||
|
||||
if m.s[cfsInitProc].buf.len > 0:
|
||||
moduleIsEmpty = false
|
||||
res.add(extract(m.s[cfsInitProc]))
|
||||
@@ -2331,6 +2496,22 @@ proc genModule(m: BModule, cfile: Cfile): Rope =
|
||||
|
||||
postprocessCode(m.config, result)
|
||||
|
||||
if m.config.cmd == cmdNifC and result.len > 0:
|
||||
let artifact = cfile.cname.string & ".nif"
|
||||
var implDeps: seq[string] = @[]
|
||||
for pos in m.icImplMods.items:
|
||||
if pos != m.module.position:
|
||||
implDeps.add modname(pos, m.config)
|
||||
sort implDeps
|
||||
writeCnifArtifact(result, artifact, initRequired, datInitRequired,
|
||||
m.icDataDefs,
|
||||
semmedNif = toNifFilename(m.config, FileIndex m.module.position),
|
||||
moduleBase = getSomeNameForModule(m),
|
||||
implDeps = implDeps)
|
||||
m.g.graph.icCnifFiles.add artifact
|
||||
# NB: under cmdNifC the returned text still carries the cnif marks; the
|
||||
# caller renders it (dropping dead definitions) or strips it.
|
||||
|
||||
proc initProcOptions(m: BModule): TOptions =
|
||||
let opts = m.config.options
|
||||
if sfSystemModule in m.module.flags: opts-{optStackTrace} else: opts
|
||||
@@ -2342,6 +2523,8 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule
|
||||
result.headerFiles = @[]
|
||||
result.declaredThings = initIntSet()
|
||||
result.declaredProtos = initIntSet()
|
||||
result.emittedContentDefs = initHashSet[string]()
|
||||
result.icImplMods = initIntSet()
|
||||
result.cfilename = filename
|
||||
result.filename = filename
|
||||
result.typeCache = initTable[SigHash, Rope]()
|
||||
@@ -2413,10 +2596,13 @@ proc writeHeader(m: BModule) =
|
||||
result.finishProcHeaderAsProto()
|
||||
if m.config.cppCustomNamespace.len > 0: closeNamespaceNim(result)
|
||||
result.addf("#endif /* $1 */$n", [guard])
|
||||
if not writeRope(extract(result), m.filename):
|
||||
var headerText = extract(result)
|
||||
if m.config.cmd == cmdNifC:
|
||||
headerText = stripCnifMarks(headerText)
|
||||
if not writeRope(headerText, m.filename):
|
||||
rawMessage(m.config, errCannotOpenFile, m.filename.string)
|
||||
|
||||
proc getCFile(m: BModule): AbsoluteFile =
|
||||
proc getCFile*(m: BModule): AbsoluteFile =
|
||||
let ext =
|
||||
if m.compileToCpp: ".nim.cpp"
|
||||
elif m.config.backend == backendObjc or sfCompileToObjc in m.module.flags: ".nim.m"
|
||||
@@ -2510,8 +2696,9 @@ proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
|
||||
rawMessage(m.config, errCannotOpenFile, cfile.cname.string)
|
||||
result = true
|
||||
|
||||
proc writeModule(m: BModule) =
|
||||
let cfile = getCFile(m)
|
||||
proc genModuleCode(m: BModule; cf: var Cfile): string =
|
||||
## First half of `writeModule`: finalizes the module and produces its code
|
||||
## text. Under cmdNifC the text still carries the cnif marks.
|
||||
if moduleHasChanged(m.g.graph, m.module):
|
||||
genInitCode(m)
|
||||
|
||||
@@ -2526,9 +2713,11 @@ proc writeModule(m: BModule) =
|
||||
m.s[cfsProcHeaders].add(extract(m.g.mainModProcs))
|
||||
generateThreadVarsSize(m)
|
||||
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {})
|
||||
var code = genModule(m, cf)
|
||||
result = genModule(m, cf)
|
||||
|
||||
proc registerModuleCode(m: BModule; cf: var Cfile; code: string) =
|
||||
## Second half of `writeModule`: writes the .c file if it changed and
|
||||
## registers it for compilation.
|
||||
if code != "" or m.config.symbolFiles != disabledSf:
|
||||
when hasTinyCBackend:
|
||||
if m.config.cmd == cmdTcc:
|
||||
@@ -2538,6 +2727,15 @@ proc writeModule(m: BModule) =
|
||||
if not shouldRecompile(m, code, cf): cf.flags = {CfileFlag.Cached}
|
||||
addFileToCompile(m.config, cf)
|
||||
|
||||
proc writeModule(m: BModule) =
|
||||
let cfile = getCFile(m)
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {})
|
||||
var code = genModuleCode(m, cf)
|
||||
if m.config.cmd == cmdNifC:
|
||||
code = stripCnifMarks(code)
|
||||
registerModuleCode(m, cf, code)
|
||||
|
||||
proc updateCachedModule(m: BModule) =
|
||||
let cfile = getCFile(m)
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
@@ -2623,7 +2821,12 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
|
||||
|
||||
if m.g.forwardedProcs.len == 0:
|
||||
incl m.flags, objHasKidsValid
|
||||
if optMultiMethods in m.g.config.globalOptions or
|
||||
if m.config.cmd == cmdNifC:
|
||||
# nifbackend synthesizes the dispatchers between the module loop
|
||||
# and the finish loop (emitMethodDispatchers): TUs demand-created
|
||||
# by the dispatcher bodies must still reach `modulesClosed`
|
||||
discard
|
||||
elif optMultiMethods in m.g.config.globalOptions or
|
||||
m.g.config.selectedGC notin {gcArc, gcOrc, gcAtomicArc, gcYrc} or
|
||||
vtables notin m.g.config.features:
|
||||
generateIfMethodDispatchers(graph, m.idgen)
|
||||
@@ -2637,9 +2840,8 @@ proc genForwardedProcs(g: BModuleList) =
|
||||
# a second pass here
|
||||
# Note: ``genProcLvl2`` may add to ``forwardedProcs``
|
||||
while g.forwardedProcs.len > 0:
|
||||
let
|
||||
prc = g.forwardedProcs.pop()
|
||||
m = g.mods[prc.itemId.module]
|
||||
let prc = g.forwardedProcs.pop()
|
||||
let m = g.mods[prc.itemId.module]
|
||||
if sfForward in prc.flags:
|
||||
internalError(m.config, prc.info, "still forwarded: " & prc.name.s)
|
||||
|
||||
@@ -2654,7 +2856,32 @@ proc cgenWriteModules*(backend: RootRef, config: ConfigRef) =
|
||||
# order anyway)
|
||||
genForwardedProcs(g)
|
||||
|
||||
for m in cgenModules(g):
|
||||
m.writeModule()
|
||||
if config.cmd == cmdNifC and not isDefined(config, "icNoCDce"):
|
||||
# Two-phase write: produce every module's marked text and artifact
|
||||
# first, then compute global liveness over the artifacts and render
|
||||
# the .c files with dead definitions dropped. Demand-driven codegen
|
||||
# over-approximates (it cannot retract a definition once some path
|
||||
# requested it); this is where the surplus is removed.
|
||||
var mods: seq[BModule] = @[]
|
||||
var cfs: seq[Cfile] = @[]
|
||||
var codes: seq[string] = @[]
|
||||
for m in cgenModules(g):
|
||||
let cfile = getCFile(m)
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {})
|
||||
let code = genModuleCode(m, cf)
|
||||
mods.add m
|
||||
cfs.add cf
|
||||
codes.add code
|
||||
let cl = computeLiveFromCArtifacts(g.graph.icCnifFiles)
|
||||
var dropped = 0
|
||||
for i in 0..<mods.len:
|
||||
let rendered =
|
||||
if cl.broken: stripCnifMarks(codes[i])
|
||||
else: renderMarkedC(codes[i], cl.live, dropped)
|
||||
registerModuleCode(mods[i], cfs[i], rendered)
|
||||
else:
|
||||
for m in cgenModules(g):
|
||||
m.writeModule()
|
||||
writeMapping(config, g.mapping)
|
||||
if g.generatedHeader != nil: writeHeader(g.generatedHeader)
|
||||
|
||||
@@ -158,6 +158,12 @@ type
|
||||
forwTypeCache*: TypeCache # cache for forward declarations of types
|
||||
declaredThings*: IntSet # things we have declared in this .c file
|
||||
declaredProtos*: IntSet # prototypes we have declared in this .c file
|
||||
emittedContentDefs*: HashSet[string]
|
||||
# cmdNifC per-module backend: content-addressed C names (generic
|
||||
# instances and synthesized hooks) whose body this TU already emitted.
|
||||
# Distinct symbols (minted in different source modules) can share one
|
||||
# `_i<disamb>` name; `declaredThings` keys on symbol id and lets the
|
||||
# second one through, so we dedup the body by name here instead.
|
||||
queue*: seq[PSym] # queue of procs to generate
|
||||
alive*: IntSet # symbol IDs of alive data as computed by `dce.nim`
|
||||
headerFiles*: seq[string] # needed headers to include
|
||||
@@ -176,6 +182,17 @@ type
|
||||
extensionLoaders*: array['0'..'9', Builder] # special procs for the
|
||||
# OpenGL wrapper
|
||||
sigConflicts*: CountTable[SigHash]
|
||||
icImplMods*: IntSet # module ids whose routine BODIES this TU
|
||||
# embeds (redirected defs, shared instances,
|
||||
# hooks); recorded as the artifact's cdeps so
|
||||
# the reuse gate can check their impl cookies
|
||||
icDataDefs*: seq[tuple[cname, nifname: string]]
|
||||
# C names of data definitions (consts, globals,
|
||||
# RTTI) this TU embeds plus their NIF symbol
|
||||
# names (empty for RTTI, which has no symbol);
|
||||
# recorded in the cnif artifact so a later run
|
||||
# can reuse the TU and re-demand definitions
|
||||
# that cached TUs still reference
|
||||
g*: BModuleList
|
||||
|
||||
template config*(m: BModule): ConfigRef = m.g.config
|
||||
|
||||
@@ -180,6 +180,7 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
g.methods[i].methods[0] != s:
|
||||
# already exists due to forwarding definition?
|
||||
localError(g.config, s.info, "method is not a base")
|
||||
logMethodDef(g, s)
|
||||
return
|
||||
of No: discard
|
||||
of Invalid:
|
||||
@@ -191,6 +192,7 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
else:
|
||||
g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).itemId)
|
||||
g.methods.add((methods: @[s], dispatcher: createDispatcher(s, g, idgen)))
|
||||
logMethodDef(g, s)
|
||||
#echo "adding ", s.info
|
||||
if witness != nil:
|
||||
localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s &
|
||||
|
||||
@@ -167,6 +167,8 @@ type
|
||||
curExcSym: PSym # Current exception
|
||||
externExcSym: PSym # Extern exception: what would getCurrentException() return outside of closure iter
|
||||
|
||||
enclosingPragmas: seq[PNode] # stack of pragma blocks wrapping stmtlist
|
||||
|
||||
states: seq[State] # The resulting states. Label is int literal.
|
||||
finallyPathStack: seq[FinallyTarget] # Stack of split blocks, whiles and finallies
|
||||
stateLoopLabel: PSym # Label to break on, when jumping between states.
|
||||
@@ -252,7 +254,8 @@ proc newCurExcAccess(ctx: var Ctx): PNode =
|
||||
ctx.newEnvVarAccess(ctx.curExcSym)
|
||||
|
||||
proc newStateLabel(ctx: Ctx): PNode =
|
||||
ctx.g.newIntLit(TLineInfo(), 0)
|
||||
result = nkIntLit.newIntNode(0)
|
||||
result.typ = getSysType(ctx.g, TLineInfo(), tyInt16)
|
||||
|
||||
proc newState(ctx: var Ctx, n: PNode, inlinable: bool, label: PNode): PNode =
|
||||
# Creates a new state, adds it to the context
|
||||
@@ -592,10 +595,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
let branch = n[i]
|
||||
case branch.kind
|
||||
of nkExceptBranch:
|
||||
if branch[0].kind == nkType:
|
||||
branch[1] = ctx.convertExprBodyToAsgn(branch[1], tmp)
|
||||
else:
|
||||
branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp)
|
||||
branch[^1] = ctx.convertExprBodyToAsgn(branch[^1], tmp)
|
||||
of nkFinally:
|
||||
discard
|
||||
else:
|
||||
@@ -985,9 +985,14 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
for j in i + 1..<n.len:
|
||||
s.add(n[j])
|
||||
|
||||
var body = s
|
||||
for pragma in ctx.enclosingPragmas:
|
||||
body = newTreeI(nkPragmaBlock, n[i + 1].info,
|
||||
pragma[0].copyTree, body)
|
||||
|
||||
n.sons.setLen(i + 1)
|
||||
discard ctx.newState(s, true, label)
|
||||
if ctx.transformClosureIteratorBody(s, gotoOut) != s:
|
||||
discard ctx.newState(body, true, label)
|
||||
if ctx.transformClosureIteratorBody(body, gotoOut) != body:
|
||||
internalError(ctx.g.config, "transformClosureIteratorBody != s")
|
||||
break
|
||||
else:
|
||||
@@ -1125,6 +1130,14 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit)
|
||||
dec ctx.curFinallyLevel
|
||||
|
||||
of nkPragmaBlock:
|
||||
# Propagate the pragma blocks so that blocks like {.cast(uncheckedAssign).}
|
||||
# remain effective
|
||||
ctx.enclosingPragmas.add(n)
|
||||
n[1] = ctx.transformClosureIteratorBody(n[1], gotoOut)
|
||||
discard ctx.enclosingPragmas.pop()
|
||||
result = n
|
||||
|
||||
of nkGotoState, nkForStmt:
|
||||
internalError(ctx.g.config, "closure iter " & $n.kind)
|
||||
|
||||
|
||||
726
compiler/cnif.nim
Normal file
726
compiler/cnif.nim
Normal file
@@ -0,0 +1,726 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2026 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## The "cnif" artifact: the C code generator's output as a NIF file.
|
||||
##
|
||||
## This is deliberately *not* NIFC: the C text is kept verbatim (Nim's
|
||||
## C-level machinery — exception handling in particular — is more refined
|
||||
## than what NIFC models today; the gap can be closed incrementally later).
|
||||
## The only structure the artifact adds is the part dead code elimination
|
||||
## and generic-instance merging need:
|
||||
##
|
||||
## - raw C text as string literals
|
||||
## - every *global* entity's C name as a `Symbol` token
|
||||
## - every emitted proc definition as a `(cdef SymbolDef flags ...)` group
|
||||
##
|
||||
## The C generator marks names with control characters at the single place
|
||||
## a global's C name is minted (`fillBackendName`) and emits a definition
|
||||
## directive at the single place finished procs are appended; the marks then
|
||||
## ride through all of the snippet composition untouched. This module turns
|
||||
## the final marked module text into the `.c.nif` artifact and strips the
|
||||
## marks for the actual `.c` output. Rendering C from the artifact is a
|
||||
## plain token walk: string literals verbatim, symbols by name — which is
|
||||
## also where a later merge step redirects losing generic instances.
|
||||
##
|
||||
## Marker scheme (cannot collide: C string literals escape control chars,
|
||||
## and `\1`/`\31`/`\23` of cgen's postprocess directives are distinct):
|
||||
## \2 name \3 a global's C name
|
||||
## \4 name \31 flags \31 nif \5 start of the definition of `name`;
|
||||
## `nif` is the defining symbol's NIF name
|
||||
## (empty for backend-minted symbols) so a
|
||||
## later run can re-demand the definition
|
||||
## \4 \5 end of the definitions section
|
||||
|
||||
import std / [tables, sets, os, assertions, syncio, algorithm]
|
||||
import "../dist/nimony/src/lib" / [nifbuilder, nifcoreparse]
|
||||
|
||||
const
|
||||
CnifSymStart* = '\2'
|
||||
CnifSymEnd* = '\3'
|
||||
CnifDefStart* = '\4'
|
||||
CnifDefSep* = '\31' # same separator char as cgen's postprocess directives
|
||||
CnifDefEnd* = '\5'
|
||||
|
||||
proc markCName*(name: string): string {.inline.} =
|
||||
CnifSymStart & name & CnifSymEnd
|
||||
|
||||
proc hasCnifMarks*(s: string): bool =
|
||||
for c in s:
|
||||
if c in {CnifSymStart, CnifSymEnd, CnifDefStart}: return true
|
||||
false
|
||||
|
||||
proc stripCnifMarks*(s: string): string =
|
||||
## Removes the symbol marks (keeping the names) and the definition
|
||||
## directives (entirely) so the result is plain C.
|
||||
if not hasCnifMarks(s): return s
|
||||
result = newStringOfCap(s.len)
|
||||
var i = 0
|
||||
while i < s.len:
|
||||
case s[i]
|
||||
of CnifSymStart, CnifSymEnd:
|
||||
inc i
|
||||
of CnifDefStart:
|
||||
while i < s.len and s[i] != CnifDefEnd: inc i
|
||||
inc i # skip CnifDefEnd
|
||||
else:
|
||||
result.add s[i]
|
||||
inc i
|
||||
|
||||
const
|
||||
CnifVersion* = "4"
|
||||
## Artifact format version, stored in the meta head. Artifacts written
|
||||
## by an older compiler lack the NIF names and the cref group the
|
||||
## def-retention check needs (v2), the cdeps group the fine-grained
|
||||
## reuse gate needs (v3), or the type NIF names and cnif-marked extern
|
||||
## RTTI references the typeinfo flavor of the def-retention check
|
||||
## needs (v4); `readCnifHeads` reports them as invalid so their TUs
|
||||
## simply regenerate once.
|
||||
|
||||
proc cnifDefDirective*(name, flags, nifName: string): string =
|
||||
CnifDefStart & name & CnifDefSep & flags & CnifDefSep & nifName & CnifDefEnd
|
||||
|
||||
proc cnifEndDefs*(): string =
|
||||
CnifDefStart & CnifDefEnd
|
||||
|
||||
proc writeCnifArtifact*(code: string; outfile: string;
|
||||
initRequired = false; datInitRequired = false;
|
||||
dataDefs: openArray[tuple[cname, nifname: string]] = [];
|
||||
semmedNif = ""; moduleBase = "";
|
||||
implDeps: openArray[string] = []) =
|
||||
## Splits the marked module text into the `.c.nif` artifact.
|
||||
## The artifact starts with a `(meta <flags> "semmedNif" "moduleBase"
|
||||
## "version")` head — whether the module has an init/datInit proc
|
||||
## ('i'/'d'), which semmed NIF it was generated from and the module's
|
||||
## mangled base name (what `registerModuleToMain` and the reuse decision
|
||||
## need when the TU is reused in a later run, possibly without the module
|
||||
## ever being loaded again) — a `(cdata (SymbolDef StrLit)*)` group naming
|
||||
## the data definitions (consts, globals, RTTI) the TU embeds together
|
||||
## with their NIF names, a `(cref Ident*)` group naming every C name
|
||||
## the TU references but does not define itself (what the def-retention
|
||||
## check consults when some *other* TU regenerates), and a
|
||||
## `(cdeps Ident*)` group naming the modules whose routine *bodies* this
|
||||
## TU embeds (redirected defs, shared instances, hooks): the fine-grained
|
||||
## reuse gate checks their `.impl.nif` cookies on top of the direct
|
||||
## imports' `.iface.nif` cookies.
|
||||
# pre-pass: every marked name is a use, every definition directive (and
|
||||
# every data def) is a definition; external references = uses - defs
|
||||
var uses = initHashSet[string]()
|
||||
var defs = initHashSet[string]()
|
||||
block prePass:
|
||||
var i = 0
|
||||
while i < code.len:
|
||||
case code[i]
|
||||
of CnifSymStart:
|
||||
inc i
|
||||
var name = ""
|
||||
while i < code.len and code[i] != CnifSymEnd:
|
||||
name.add code[i]
|
||||
inc i
|
||||
inc i
|
||||
uses.incl name
|
||||
of CnifDefStart:
|
||||
inc i
|
||||
var payload = ""
|
||||
while i < code.len and code[i] != CnifDefEnd:
|
||||
payload.add code[i]
|
||||
inc i
|
||||
inc i
|
||||
let sep = find(payload, CnifDefSep)
|
||||
if sep > 0: defs.incl payload[0..<sep]
|
||||
elif payload.len > 0: defs.incl payload
|
||||
else:
|
||||
inc i
|
||||
for d in dataDefs: defs.incl d.cname
|
||||
var crefs: seq[string] = @[]
|
||||
for u in uses:
|
||||
if u notin defs: crefs.add u
|
||||
sort crefs
|
||||
|
||||
var b = nifbuilder.open(outfile)
|
||||
b.withTree "stmts":
|
||||
b.withTree "meta":
|
||||
var metaFlags = ""
|
||||
if initRequired: metaFlags.add 'i'
|
||||
if datInitRequired: metaFlags.add 'd'
|
||||
if metaFlags.len > 0: b.addIdent metaFlags
|
||||
else: b.addEmpty
|
||||
b.addStrLit semmedNif
|
||||
b.addStrLit moduleBase
|
||||
b.addStrLit CnifVersion
|
||||
b.withTree "cdata":
|
||||
for d in dataDefs:
|
||||
b.addSymbolDef d.cname
|
||||
b.addStrLit d.nifname
|
||||
b.withTree "cref":
|
||||
for r in crefs:
|
||||
b.addIdent r
|
||||
b.withTree "cdeps":
|
||||
for s in implDeps:
|
||||
b.addIdent s
|
||||
var raw = ""
|
||||
var inDef = false
|
||||
template flushRaw() =
|
||||
if raw.len > 0:
|
||||
b.addStrLit raw
|
||||
raw.setLen 0
|
||||
var i = 0
|
||||
while i < code.len:
|
||||
case code[i]
|
||||
of CnifSymStart:
|
||||
flushRaw()
|
||||
inc i
|
||||
var name = ""
|
||||
while i < code.len and code[i] != CnifSymEnd:
|
||||
name.add code[i]
|
||||
inc i
|
||||
inc i # skip CnifSymEnd
|
||||
b.addSymbol name, ""
|
||||
of CnifDefStart:
|
||||
flushRaw()
|
||||
inc i
|
||||
var payload = ""
|
||||
while i < code.len and code[i] != CnifDefEnd:
|
||||
payload.add code[i]
|
||||
inc i
|
||||
inc i # skip CnifDefEnd
|
||||
if inDef:
|
||||
b.endTree()
|
||||
inDef = false
|
||||
if payload.len > 0:
|
||||
let sep = find(payload, CnifDefSep)
|
||||
let name = if sep >= 0: payload[0..<sep] else: payload
|
||||
var flags = if sep >= 0: payload[sep+1..^1] else: ""
|
||||
var nifName = ""
|
||||
let sep2 = find(flags, CnifDefSep)
|
||||
if sep2 >= 0:
|
||||
nifName = flags[sep2+1..^1]
|
||||
flags = flags[0..<sep2]
|
||||
b.addTree "cdef"
|
||||
b.addSymbolDef name
|
||||
if flags.len > 0: b.addIdent flags
|
||||
else: b.addEmpty
|
||||
b.addStrLit nifName
|
||||
inDef = true
|
||||
else:
|
||||
raw.add code[i]
|
||||
inc i
|
||||
flushRaw()
|
||||
if inDef:
|
||||
b.endTree()
|
||||
b.close()
|
||||
|
||||
proc renderMarkedC*(code: string; live: HashSet[string]; dropped: var int): string =
|
||||
## Renders the final C text from the marked module text: symbol marks are
|
||||
## removed (keeping the names — a later merge step substitutes them here),
|
||||
## and definitions whose name is not in `live` are dropped entirely. Each
|
||||
## definition is self-delimiting (genProcAux emits an end directive right
|
||||
## after the proc's text), so text written by other emitters is never part
|
||||
## of a definition's span and survives unconditionally.
|
||||
result = newStringOfCap(code.len)
|
||||
var i = 0
|
||||
while i < code.len:
|
||||
case code[i]
|
||||
of CnifSymStart, CnifSymEnd:
|
||||
inc i
|
||||
of CnifDefStart:
|
||||
var payload = ""
|
||||
inc i
|
||||
while i < code.len and code[i] != CnifDefEnd:
|
||||
payload.add code[i]
|
||||
inc i
|
||||
inc i # skip CnifDefEnd
|
||||
if payload.len > 0:
|
||||
let sep = find(payload, CnifDefSep)
|
||||
let name = if sep >= 0: payload[0..<sep] else: payload
|
||||
if name notin live:
|
||||
inc dropped
|
||||
# drop the definition's text: everything up to its end directive
|
||||
while i < code.len and code[i] != CnifDefStart: inc i
|
||||
else:
|
||||
result.add code[i]
|
||||
inc i
|
||||
|
||||
# ---- Liveness over the artifact -------------------------------------------
|
||||
|
||||
proc symOrIdentName(c: Cursor): string {.inline.} =
|
||||
if c.kind == Ident: strVal(c) else: symName(c)
|
||||
|
||||
type
|
||||
CnifHeads* = object
|
||||
## The cheap-to-parse part of an artifact that a later run needs in
|
||||
## order to reuse the TU without regenerating it.
|
||||
valid*: bool ## file parsed, carries the meta head and has
|
||||
## the current format version
|
||||
initRequired*: bool
|
||||
datInitRequired*: bool
|
||||
semmedNif*: string ## the semmed NIF this TU was generated from
|
||||
moduleBase*: string ## the module's mangled base name
|
||||
cdefs*: seq[tuple[cname, nifname: string]] ## the proc definitions
|
||||
cdata*: seq[tuple[cname, nifname: string]] ## the data definitions
|
||||
crefs*: seq[string] ## C names referenced but not defined here
|
||||
cdeps*: seq[string] ## module suffixes whose routine bodies this
|
||||
## TU embeds (impl-cookie gated on reuse)
|
||||
|
||||
proc readCnifHeads*(f: string): CnifHeads =
|
||||
## Reads `(meta ...)`, `(cdata ...)`, `(cref ...)` and the `(cdef ...)`
|
||||
## head names from an artifact. Artifacts written by an older compiler
|
||||
## (no meta head or a different format version) report `valid=false`.
|
||||
result = CnifHeads()
|
||||
if not fileExists(f): return
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let stmtsTag = tags.registerTag("stmts")
|
||||
let cdefTag = tags.registerTag("cdef")
|
||||
let cdataTag = tags.registerTag("cdata")
|
||||
let crefTag = tags.registerTag("cref")
|
||||
let cdepsTag = tags.registerTag("cdeps")
|
||||
let metaTag = tags.registerTag("meta")
|
||||
var buf = parseFromFile(f, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
endRead(c)
|
||||
return
|
||||
var version = ""
|
||||
var sawMeta = false
|
||||
c.loopInto:
|
||||
if c.kind == TagLit:
|
||||
if c.cursorTagId == metaTag:
|
||||
sawMeta = true
|
||||
var strIdx = 0
|
||||
c.loopInto:
|
||||
if c.kind == Ident:
|
||||
for ch in strVal(c):
|
||||
if ch == 'i': result.initRequired = true
|
||||
elif ch == 'd': result.datInitRequired = true
|
||||
inc c
|
||||
elif c.kind == StrLit:
|
||||
if strIdx == 0: result.semmedNif = strVal(c)
|
||||
elif strIdx == 1: result.moduleBase = strVal(c)
|
||||
elif strIdx == 2: version = strVal(c)
|
||||
inc strIdx
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == cdataTag:
|
||||
c.loopInto:
|
||||
if c.kind == SymbolDef:
|
||||
result.cdata.add (symName(c), "")
|
||||
inc c
|
||||
elif c.kind == StrLit:
|
||||
if result.cdata.len > 0:
|
||||
result.cdata[^1].nifname = strVal(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == crefTag:
|
||||
c.loopInto:
|
||||
if c.kind in {Ident, Symbol, SymbolDef}:
|
||||
result.crefs.add symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == cdepsTag:
|
||||
c.loopInto:
|
||||
if c.kind in {Ident, Symbol, SymbolDef}:
|
||||
result.cdeps.add symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == cdefTag:
|
||||
# fixed head: SymbolDef, flags (Ident or empty), NIF name StrLit;
|
||||
# everything after that is the definition's body text
|
||||
var state = 0
|
||||
c.loopInto:
|
||||
if c.kind == SymbolDef:
|
||||
result.cdefs.add (symName(c), "")
|
||||
state = 1
|
||||
inc c
|
||||
elif state == 1: # the flags field
|
||||
state = 2
|
||||
skip c
|
||||
elif state == 2: # the NIF name
|
||||
if c.kind == StrLit and result.cdefs.len > 0:
|
||||
result.cdefs[^1].nifname = strVal(c)
|
||||
state = 3
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
result.valid = sawMeta and version == CnifVersion
|
||||
|
||||
type
|
||||
CnifLiveness* = object
|
||||
defs*: int ## proc definitions emitted across all modules
|
||||
liveDefs*: int ## of those, reachable from the roots
|
||||
live*: HashSet[string] ## live C names
|
||||
broken*: bool
|
||||
|
||||
proc computeLiveFromCArtifacts*(files: openArray[string]): CnifLiveness =
|
||||
## dce1-style mark&sweep over the C-shaped artifacts: a `(cdef ...)`
|
||||
## group is a definition (flags 'x'/'c'/'m' — exportc, compilerproc,
|
||||
## method/dispatcher — make it a root), names at the top level (data,
|
||||
## globals, init code) are roots, names inside a group are its uses.
|
||||
## Because the artifact is *fully lowered* output, no conservative
|
||||
## modelling is needed: every call the C code contains is a token here.
|
||||
##
|
||||
## NB: mangled C names contain no dots, so NIF's text reader classifies
|
||||
## them as `Ident` rather than `Symbol`; the dialect therefore treats
|
||||
## Ident tokens as name uses. Inside a `(cdef ...)` the flags ident is
|
||||
## the one immediately following the SymbolDef; everything after is a use.
|
||||
result = CnifLiveness(live: initHashSet[string]())
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let stmtsTag = tags.registerTag("stmts")
|
||||
let cdefTag = tags.registerTag("cdef")
|
||||
let cdataTag = tags.registerTag("cdata")
|
||||
let crefTag = tags.registerTag("cref")
|
||||
let cdepsTag = tags.registerTag("cdeps")
|
||||
let metaTag = tags.registerTag("meta")
|
||||
var uses = initTable[string, HashSet[string]]()
|
||||
var roots = initHashSet[string]()
|
||||
var defs = initHashSet[string]()
|
||||
for f in files:
|
||||
if not fileExists(f):
|
||||
result.broken = true
|
||||
return
|
||||
var buf = parseFromFile(f, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
result.broken = true
|
||||
endRead(c)
|
||||
return
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of Symbol, Ident:
|
||||
roots.incl symOrIdentName(c)
|
||||
inc c
|
||||
of TagLit:
|
||||
if c.cursorTagId == metaTag or c.cursorTagId == cdataTag or
|
||||
c.cursorTagId == crefTag or c.cursorTagId == cdepsTag:
|
||||
# bookkeeping for TU reuse, irrelevant for liveness
|
||||
skip c
|
||||
elif c.cursorTagId == cdefTag:
|
||||
var owner = ""
|
||||
var flagsSeen = false
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of SymbolDef:
|
||||
owner = symName(c)
|
||||
defs.incl owner
|
||||
flagsSeen = false
|
||||
inc c
|
||||
of Symbol, Ident:
|
||||
let name = symOrIdentName(c)
|
||||
if not flagsSeen:
|
||||
# the flags field right after the SymbolDef
|
||||
flagsSeen = true
|
||||
for ch in name:
|
||||
# 'd' marks a data definition (const/RTTI): never DCE'd, so it
|
||||
# is a root whose body keeps its referenced procs live
|
||||
if ch in {'x', 'c', 'm', 'd'}:
|
||||
roots.incl owner
|
||||
break
|
||||
else:
|
||||
uses.mgetOrPut(owner, initHashSet[string]()).incl name
|
||||
inc c
|
||||
of DotToken:
|
||||
flagsSeen = true # empty flags field
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
c.loopInto:
|
||||
if c.kind in {Symbol, Ident}:
|
||||
roots.incl symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
# mark & sweep
|
||||
var work = newSeqOfCap[string](roots.len)
|
||||
for r in roots: work.add r
|
||||
while work.len > 0:
|
||||
let s = work.pop()
|
||||
if not result.live.containsOrIncl(s):
|
||||
if uses.hasKey(s):
|
||||
for dep in uses[s]:
|
||||
if dep notin result.live:
|
||||
work.add dep
|
||||
result.defs = defs.len
|
||||
for d in defs:
|
||||
if d in result.live: inc result.liveDefs
|
||||
|
||||
# ---- The merge stage: liveness + owner assignment -------------------------
|
||||
|
||||
type
|
||||
MergeDecision* = object
|
||||
## What the per-module backend's `merge` stage computes from every
|
||||
## module's `.c.nif` and what its `emit` stage consumes to render the
|
||||
## final `.c` of one module.
|
||||
live*: HashSet[string] ## globally reachable C names (dead cdefs
|
||||
## are dropped from every module)
|
||||
owners*: Table[string, string] ## for each `'u'`-flagged (unique,
|
||||
## externally-linked) definition, the single
|
||||
## artifact base name allowed to embed its
|
||||
## body; every other module prototypes it
|
||||
broken*: bool ## an artifact was missing or unparsable —
|
||||
## the caller should fall back / regenerate
|
||||
defs*, liveDefs*: int
|
||||
|
||||
proc computeMergeDecision*(files: openArray[string]): MergeDecision =
|
||||
## One pass over every `.c.nif`: the same mark&sweep as
|
||||
## `computeLiveFromCArtifacts` plus, per definition, owner assignment.
|
||||
##
|
||||
## Each `cg` process emits the body of every definition it demands
|
||||
## (emit-everywhere), so the same externally-linked definition appears in
|
||||
## several artifacts. A `'u'` flag on the `(cdef ...)` marks those that need
|
||||
## exactly one owner, assigned here across processes: the owner is the
|
||||
## lexicographically smallest artifact that emits it — a pure function of the
|
||||
## claimant set, hence stable across rebuilds. Definitions without `'u'`
|
||||
## (inline procs, dispatchers) are `static`/main-only and emitted into every
|
||||
## using TU, so they get no owner entry and are never deduplicated.
|
||||
result = MergeDecision(live: initHashSet[string](),
|
||||
owners: initTable[string, string]())
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let stmtsTag = tags.registerTag("stmts")
|
||||
let cdefTag = tags.registerTag("cdef")
|
||||
let cdataTag = tags.registerTag("cdata")
|
||||
let crefTag = tags.registerTag("cref")
|
||||
let cdepsTag = tags.registerTag("cdeps")
|
||||
let metaTag = tags.registerTag("meta")
|
||||
var uses = initTable[string, HashSet[string]]()
|
||||
var roots = initHashSet[string]()
|
||||
var defs = initHashSet[string]()
|
||||
for f in files:
|
||||
if not fileExists(f):
|
||||
result.broken = true
|
||||
return
|
||||
let owner = extractFilename(f)
|
||||
var buf = parseFromFile(f, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
result.broken = true
|
||||
endRead(c)
|
||||
return
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of Symbol, Ident:
|
||||
roots.incl symOrIdentName(c)
|
||||
inc c
|
||||
of TagLit:
|
||||
if c.cursorTagId == metaTag or c.cursorTagId == cdataTag or
|
||||
c.cursorTagId == crefTag or c.cursorTagId == cdepsTag:
|
||||
skip c
|
||||
elif c.cursorTagId == cdefTag:
|
||||
var ownerName = ""
|
||||
var flagsSeen = false
|
||||
var needsOwner = false
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of SymbolDef:
|
||||
ownerName = symName(c)
|
||||
defs.incl ownerName
|
||||
flagsSeen = false
|
||||
inc c
|
||||
of Symbol, Ident:
|
||||
let name = symOrIdentName(c)
|
||||
if not flagsSeen:
|
||||
flagsSeen = true
|
||||
for ch in name:
|
||||
if ch in {'x', 'c', 'm'}: roots.incl ownerName
|
||||
# 'u' = unique proc (DCE'd), 'd' = data (never DCE'd, hence a
|
||||
# root); both need a single owner across the emit-everywhere
|
||||
# processes
|
||||
elif ch == 'u': needsOwner = true
|
||||
elif ch == 'd':
|
||||
needsOwner = true
|
||||
roots.incl ownerName
|
||||
else:
|
||||
uses.mgetOrPut(ownerName, initHashSet[string]()).incl name
|
||||
inc c
|
||||
of DotToken:
|
||||
flagsSeen = true # empty flags field
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
if needsOwner and ownerName.len > 0:
|
||||
# smallest claimant wins; ties impossible (one entry per name)
|
||||
let prev = result.owners.getOrDefault(ownerName, "")
|
||||
if prev.len == 0 or owner < prev:
|
||||
result.owners[ownerName] = owner
|
||||
else:
|
||||
c.loopInto:
|
||||
if c.kind in {Symbol, Ident}:
|
||||
roots.incl symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
var work = newSeqOfCap[string](roots.len)
|
||||
for r in roots: work.add r
|
||||
while work.len > 0:
|
||||
let s = work.pop()
|
||||
if not result.live.containsOrIncl(s):
|
||||
if uses.hasKey(s):
|
||||
for dep in uses[s]:
|
||||
if dep notin result.live:
|
||||
work.add dep
|
||||
result.defs = defs.len
|
||||
for d in defs:
|
||||
if d in result.live: inc result.liveDefs
|
||||
|
||||
const MergeDecisionFile* = "ic.backend.merge.nif"
|
||||
## Fixed name of the merge stage's output in the nimcache, read by `emit`.
|
||||
|
||||
proc writeMergeDecision*(outfile: string; d: MergeDecision) =
|
||||
## Serializes the merge decision: `(merge (live Symbol*) (owners (own
|
||||
## Symbol StrLit)*))`. C names are mangled (no dots) so they serialize as
|
||||
## symbols; owner artifact base names go in string literals.
|
||||
var live: seq[string] = @[]
|
||||
for n in d.live: live.add n
|
||||
sort live
|
||||
var keys: seq[string] = @[]
|
||||
for k in d.owners.keys: keys.add k
|
||||
sort keys
|
||||
var b = nifbuilder.open(outfile)
|
||||
b.withTree "merge":
|
||||
b.withTree "live":
|
||||
for n in live: b.addSymbol n, ""
|
||||
b.withTree "owners":
|
||||
for k in keys:
|
||||
b.withTree "own":
|
||||
b.addSymbol k, ""
|
||||
b.addStrLit d.owners[k]
|
||||
b.close()
|
||||
|
||||
proc readMergeDecision*(f: string): MergeDecision =
|
||||
## Reads back a `writeMergeDecision` file; `broken=true` if absent/unparsable.
|
||||
result = MergeDecision(live: initHashSet[string](),
|
||||
owners: initTable[string, string]())
|
||||
if not fileExists(f):
|
||||
result.broken = true
|
||||
return
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let mergeTag = tags.registerTag("merge")
|
||||
let liveTag = tags.registerTag("live")
|
||||
let ownersTag = tags.registerTag("owners")
|
||||
let ownTag = tags.registerTag("own")
|
||||
var buf = parseFromFile(f, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != mergeTag:
|
||||
result.broken = true
|
||||
endRead(c)
|
||||
return
|
||||
c.loopInto:
|
||||
if c.kind == TagLit and c.cursorTagId == liveTag:
|
||||
c.loopInto:
|
||||
if c.kind in {Symbol, Ident}:
|
||||
result.live.incl symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.kind == TagLit and c.cursorTagId == ownersTag:
|
||||
c.loopInto:
|
||||
if c.kind == TagLit and c.cursorTagId == ownTag:
|
||||
var key = ""
|
||||
c.loopInto:
|
||||
if c.kind in {Symbol, Ident}:
|
||||
key = symOrIdentName(c)
|
||||
inc c
|
||||
elif c.kind == StrLit:
|
||||
if key.len > 0: result.owners[key] = strVal(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
|
||||
proc renderCFromArtifact*(artifact: string; d: MergeDecision; ownerId: string;
|
||||
dropped: var int): string =
|
||||
## The per-module backend's `emit` stage: render one module's final `.c` from
|
||||
## its `.c.nif` and the merge decision. String literals are emitted verbatim,
|
||||
## symbols by name; a `(cdef ...)` body is dropped when the name is dead, or
|
||||
## when it is a `'u'` unique definition this module does not own. The body's
|
||||
## prototype lives in the surrounding raw text (cgen emits a forward
|
||||
## declaration for every *used* proc, independent of where the body lands), so
|
||||
## a dropped body still leaves a valid declaration — no synthesis needed. The
|
||||
## head groups (meta/cdata/cref/cdeps) carry no C text.
|
||||
result = ""
|
||||
if not fileExists(artifact): return
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let stmtsTag = tags.registerTag("stmts")
|
||||
let cdefTag = tags.registerTag("cdef")
|
||||
var buf = parseFromFile(artifact, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
endRead(c)
|
||||
return
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of StrLit:
|
||||
result.add strVal(c)
|
||||
inc c
|
||||
of Symbol, Ident:
|
||||
result.add symOrIdentName(c)
|
||||
inc c
|
||||
of TagLit:
|
||||
if c.cursorTagId == cdefTag:
|
||||
# fixed head: SymbolDef, flags (Ident or empty), nifname StrLit; the
|
||||
# rest is the definition's body text. `state` counts past the head.
|
||||
var name = ""
|
||||
var isUnique = false
|
||||
var isData = false
|
||||
var keep = true
|
||||
var state = 0
|
||||
c.loopInto:
|
||||
if state == 0 and c.kind == SymbolDef:
|
||||
name = symName(c)
|
||||
state = 1
|
||||
inc c
|
||||
elif state == 1: # the flags field (one token: Ident/Symbol or empty)
|
||||
if c.kind in {Ident, Symbol}:
|
||||
for ch in symOrIdentName(c):
|
||||
if ch == 'u': isUnique = true
|
||||
elif ch == 'd': isData = true
|
||||
state = 2
|
||||
inc c
|
||||
elif state == 2: # the NIF name (one StrLit) — decide keep here
|
||||
let owned = d.owners.getOrDefault(name, ownerId) == ownerId
|
||||
keep =
|
||||
if isData: owned # data: kept by its owner only
|
||||
elif isUnique: (name in d.live) and owned
|
||||
else: name in d.live # inline/dispatcher: per-TU
|
||||
if not keep: inc dropped
|
||||
state = 3
|
||||
inc c
|
||||
else: # body tokens
|
||||
if keep:
|
||||
if c.kind == StrLit: result.add strVal(c)
|
||||
elif c.kind in {Symbol, Ident}: result.add symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
# head groups (meta/cdata/cref/cdeps) carry no C text
|
||||
skip c
|
||||
else:
|
||||
inc c
|
||||
endRead(c)
|
||||
@@ -24,7 +24,7 @@ bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep")
|
||||
bootSwitch(usedGoGC, defined(gogc), "--gc:go")
|
||||
bootSwitch(usedNoGC, defined(nogc), "--gc:none")
|
||||
|
||||
import std/[setutils, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
|
||||
import std/[setutils, sets, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
|
||||
import
|
||||
msgs, options, nversion, condsyms, extccomp, platform,
|
||||
wordrecg, nimblecmd, lineinfos, pathutils
|
||||
@@ -508,6 +508,7 @@ proc parseCommand*(command: string): Command =
|
||||
of "jsonscript": cmdJsonscript
|
||||
of "nifc": cmdNifC # generate C from NIF files
|
||||
of "ic": cmdIc # generate .build.nif for nifmake
|
||||
of "icconfig": cmdIcConfig # produce the precompiled config artifact
|
||||
else: cmdUnknown
|
||||
|
||||
proc setCmd*(conf: ConfigRef, cmd: Command) =
|
||||
@@ -653,6 +654,18 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf: ConfigRef) =
|
||||
var key = ""
|
||||
var val = ""
|
||||
# Record config-file switches so the `nim ic` driver can serialise them into a
|
||||
# precompiled-config artifact and have its per-module child processes replay
|
||||
# them instead of re-parsing the `nim.cfg` chain (and re-running `config.nims`
|
||||
# in the VM) on every invocation. Only `passPP` (config-file) switches are
|
||||
# captured; command-line switches are forwarded by the build graph as usual.
|
||||
# Path-search switches are skipped: their net effect already lives in the
|
||||
# resolved `searchPaths` the driver forwards as `--path`, and replaying their
|
||||
# raw (often relative-to-config-dir) arguments here would misresolve.
|
||||
if pass == passPP and switch.normalize notin
|
||||
["path", "p", "nimblepath", "lazypath", "excludepath",
|
||||
"nonimblepath", "clearnimblepath", "nimcache"]:
|
||||
conf.icConfigSwitches.add (switch, arg)
|
||||
case switch.normalize
|
||||
of "eval":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
@@ -923,6 +936,49 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
else: localError(conf, info, errOnOrOffExpectedButXFound % arg)
|
||||
of "noimportdoc":
|
||||
processOnOffSwitchG(conf, {optNoImportdoc}, arg, pass, info)
|
||||
of "ismainmodule":
|
||||
# `nim m` (IC) only: marks the single module being checked as the program's
|
||||
# real entry point so that `isMainModule` and `when isMainModule:` resolve
|
||||
# correctly even though every module is compiled with `sfMainModule` set.
|
||||
conf.isMainModule = switchOn(arg)
|
||||
of "icgroup":
|
||||
# `nim m` only: register a module that belongs to the current strongly-
|
||||
# connected import group, so it is compiled from source (not loaded from a
|
||||
# precompiled NIF) and gets its own NIF written. `deps.nim` emits one
|
||||
# `--icGroup:<path>` per member of a dependency cycle. The argument is an
|
||||
# absolute .nim path produced by the dependency scanner.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icGroup.incl(canonicalizePath(conf, AbsoluteFile arg).string)
|
||||
of "icproject":
|
||||
# `nim m`/`nim nifc` only: the ORIGINAL project file (see options.icProject)
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icProject = canonicalizePath(conf, AbsoluteFile arg).string
|
||||
of "icpreparsedconfig":
|
||||
# `nim m`/`nim nifc` only: path of the precompiled-config artifact (see
|
||||
# options.icPreparsedConfig). Read in `passCmd1`, before `loadConfigs`, so
|
||||
# config loading can replay it instead of re-parsing the `nim.cfg` chain.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.icPreparsedConfig = arg
|
||||
of "icconfigout":
|
||||
# `nim icconfig` only: where to write the precompiled config artifact (see
|
||||
# options.icConfigOut). The `nim ic` driver spawns the producer with this.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.icConfigOut = arg
|
||||
of "icbackendstage":
|
||||
# `nim nifc` only: per-module backend stage, one of cg|merge|emit (see
|
||||
# options.icBackendStage). Empty (switch unused) keeps the whole-program
|
||||
# backend. Emitted by `deps.nim`'s backend build file.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icBackendStage = arg
|
||||
of "icbackendmodule":
|
||||
# `nim nifc` only: the NIF module suffix the cg/emit stage operates on (see
|
||||
# options.icBackendModule).
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icBackendModule = arg
|
||||
of "import":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
|
||||
1119
compiler/deps.nim
1119
compiler/deps.nim
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,7 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
|
||||
n[resultPos] = newSymNode(res)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}
|
||||
setHookDisamb(g, result, "$enumtostr", t)
|
||||
|
||||
proc searchObjCaseImpl(obj: PNode; field: PSym): PNode =
|
||||
case obj.kind
|
||||
|
||||
@@ -46,7 +46,7 @@ proc isLocation(n: PNode): bool = not n.isValue
|
||||
|
||||
proc isLet(n: PNode): bool =
|
||||
if n.kind == nkSym:
|
||||
if n.sym.kind in {skLet, skTemp, skForVar}:
|
||||
if n.sym.kind in {skLet, skConst, skTemp, skForVar}: # guard immutable variables
|
||||
result = true
|
||||
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
|
||||
abstractInst).kind notin {tyVar}:
|
||||
|
||||
@@ -19,8 +19,11 @@ import std/tables
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
proc replayStateChanges*(module: PSym; g: ModuleGraph) =
|
||||
let list = module.ast
|
||||
proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
|
||||
## `list` is an `nkStmtList` of `nkReplayAction` nodes (macro-cache puts/incs/
|
||||
## adds/incls and a few pragmas) recorded for `module`. Under the NIF backend a
|
||||
## loaded module's `ast` is never reconstructed, so the caller passes the replay
|
||||
## actions it parsed out of the module's NIF directly.
|
||||
assert list != nil
|
||||
assert list.kind == nkStmtList
|
||||
for n in list:
|
||||
@@ -64,8 +67,9 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph) =
|
||||
g.cacheTables[destKey] = initBTree[string, PNode]()
|
||||
if not contains(g.cacheTables[destKey], key):
|
||||
g.cacheTables[destKey].add(key, val)
|
||||
else:
|
||||
internalError(g.config, n.info, "key already exists: " & key)
|
||||
# else: the same key was already replayed. Under IC the import closure is
|
||||
# replayed (direct module + transitive deps), so the same registration can
|
||||
# legitimately be reached twice; re-applying it is a no-op, not an error.
|
||||
of "incl":
|
||||
let destKey = n[1].strVal
|
||||
let val = n[2]
|
||||
@@ -86,3 +90,37 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph) =
|
||||
g.cacheSeqs[destKey].add val
|
||||
else:
|
||||
internalAssert g.config, false
|
||||
|
||||
proc replayBackendActions*(g: ModuleGraph; module: PSym; list: PNode) =
|
||||
## Applies the backend-relevant replay actions (C compile/link directives)
|
||||
## found in a NIF-loaded module's top-level statement list. The `nifc`
|
||||
## backend loads modules without going through sem's `replayStateChanges`,
|
||||
## so e.g. math's `{.passL: "-lm".}` was lost and the final link failed
|
||||
## with undefined references. VM cache actions are deliberately NOT
|
||||
## replayed here — codegen does not run macros.
|
||||
if list == nil: return
|
||||
for n in list:
|
||||
if n.kind == nkReplayAction and n.len >= 2 and
|
||||
n[0].kind == nkStrLit and n[1].kind == nkStrLit:
|
||||
case n[0].strVal
|
||||
of "compile":
|
||||
if n.len == 4 and n[2].kind == nkStrLit:
|
||||
let cname = AbsoluteFile n[1].strVal
|
||||
var cf = Cfile(nimname: splitFile(cname).name, cname: cname,
|
||||
obj: AbsoluteFile n[2].strVal,
|
||||
flags: {CfileFlag.External},
|
||||
customArgs: n[3].strVal)
|
||||
extccomp.addExternalFileToCompile(g.config, cf)
|
||||
of "link":
|
||||
extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal)
|
||||
of "passl":
|
||||
extccomp.addLinkOption(g.config, n[1].strVal)
|
||||
of "passc":
|
||||
extccomp.addCompileOption(g.config, n[1].strVal)
|
||||
of "localpassc":
|
||||
extccomp.addLocalCompileOption(g.config, n[1].strVal,
|
||||
toFullPathConsiderDirty(g.config, module.info.fileIndex))
|
||||
of "cppdefine":
|
||||
options.cppDefine(g.config, n[1].strVal)
|
||||
else:
|
||||
discard
|
||||
|
||||
283
compiler/icconfig.nim
Normal file
283
compiler/icconfig.nim
Normal file
@@ -0,0 +1,283 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2026 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Precompiled config for the incremental compiler (`nim ic`).
|
||||
##
|
||||
## `nim ic` builds the program by spawning one `nim m` child per module (or
|
||||
## strongly-connected import group) plus a final `nim nifc`. Each child is a
|
||||
## full Nim process, so each would normally re-read the whole `nim.cfg` chain
|
||||
## *and* re-run `config.nims` through the VM — work that is identical for every
|
||||
## child and, because of the VM run, far from free. With ~85 modules in the
|
||||
## compiler itself that config work is paid ~85 times during `koch bootic`.
|
||||
##
|
||||
## The fix mirrors Nimony's `.cfg.nif`: the driver parses config once, records
|
||||
## the net effect, and the children replay it. Every config-file switch funnels
|
||||
## through `processSwitch(..., passPP, ...)` (`nimconf.parseAssignment` and the
|
||||
## `switch()` callback in `scriptconfig`), so the recorded sequence of those
|
||||
## switches, replayed in order, reproduces an identical `ConfigRef` without any
|
||||
## file read or VM run. The one config side effect that does not go through
|
||||
## `processSwitch` is `cppDefine` (it mutates `conf.cppDefines` directly), so the
|
||||
## resolved set is serialised alongside.
|
||||
##
|
||||
## Path-search switches are deliberately excluded from the recording (see
|
||||
## `commands.processSwitch`): their resolved result already lives in
|
||||
## `conf.searchPaths`, which the driver forwards to every child as absolute
|
||||
## `--path` arguments; replaying their raw, config-dir-relative arguments here
|
||||
## would misresolve.
|
||||
|
||||
import options, commands, lineinfos, pathutils, msgs
|
||||
import std/[algorithm, os, sets, osproc, times, streams, syncio]
|
||||
import "../dist/nimony/src/lib" / [nifbuilder, nifcoreparse]
|
||||
|
||||
const
|
||||
IcConfigVersion* = "2"
|
||||
## Artifact format version. Bump on any layout change here so a child built
|
||||
## by an older compiler rejects a stale artifact and falls back to normal
|
||||
## config loading instead of replaying a format it cannot parse.
|
||||
|
||||
proc writeIcConfig*(conf: ConfigRef; outfile: string) =
|
||||
## Serialise the resolved config (the config-file switches recorded during
|
||||
## `loadConfigs`, the resolved `cppDefines`/`searchPaths`, the nimcache dir, and
|
||||
## the list of config *source* files for staleness detection) into `outfile`.
|
||||
## `OnlyIfChanged`: when the content is byte-identical to what is already on
|
||||
## disk the file is left untouched so its mtime does not advance — otherwise
|
||||
## every `nim ic` run would re-fire the whole nifmake graph (see `nifler`'s
|
||||
## `produceConfig`, whose model this mirrors).
|
||||
var b = nifbuilder.open(outfile, writeMode = OnlyIfChanged)
|
||||
b.withTree "stmts":
|
||||
b.withTree "meta":
|
||||
b.addStrLit IcConfigVersion
|
||||
b.withTree "sources":
|
||||
# Every config file read while loading (nim.cfg chain + config.nims), so a
|
||||
# later run can decide via mtimes whether this artifact is still current
|
||||
# (see `sourcesChanged`).
|
||||
for f in conf.configFiles:
|
||||
b.addStrLit f.string
|
||||
b.withTree "nimcache":
|
||||
# Resolved build nimcache. Recorded (unlike the path-search switches) so the
|
||||
# driver, which replays this artifact instead of parsing `nim.cfg`, still
|
||||
# learns a `--nimcache:` set inside `nim.cfg` and builds in the right place.
|
||||
b.addStrLit conf.nimcacheDir.string
|
||||
b.withTree "cppdefines":
|
||||
# HashSet iteration order is unspecified; sort so the artifact is
|
||||
# byte-stable across runs (nifmake keys rebuilds off content changes).
|
||||
var defs: seq[string] = @[]
|
||||
for d in conf.cppDefines: defs.add d
|
||||
sort defs
|
||||
for d in defs: b.addStrLit d
|
||||
b.withTree "searchpaths":
|
||||
# The resolved (absolute) search paths. Path-search *switches* are skipped
|
||||
# below because their raw arguments are config-dir-relative; the net effect
|
||||
# lives here instead, so a replayer with no `--path` command-line arguments
|
||||
# (the `nim ic` driver itself) still resolves imports. `nim m`/`nim nifc`
|
||||
# children also receive these as forwarded `--path` args; the dedup on
|
||||
# replay makes the overlap harmless.
|
||||
for p in conf.searchPaths:
|
||||
b.addStrLit p.string
|
||||
b.withTree "switches":
|
||||
for sw in conf.icConfigSwitches:
|
||||
b.addTree "sw"
|
||||
b.addStrLit sw.switch
|
||||
b.addStrLit sw.arg
|
||||
b.endTree()
|
||||
b.close()
|
||||
|
||||
proc applyIcConfig*(conf: ConfigRef; infile: string): bool =
|
||||
## Replay the precompiled config into `conf`. Returns false (and applies
|
||||
## nothing meaningful) when the artifact is missing or written by a compiler
|
||||
## with an incompatible format version, so the caller can fall back to reading
|
||||
## the config files normally.
|
||||
if not fileExists(infile): return false
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let
|
||||
stmtsTag = tags.registerTag("stmts")
|
||||
metaTag = tags.registerTag("meta")
|
||||
sourcesTag = tags.registerTag("sources")
|
||||
nimcacheTag = tags.registerTag("nimcache")
|
||||
cppTag = tags.registerTag("cppdefines")
|
||||
pathsTag = tags.registerTag("searchpaths")
|
||||
switchesTag = tags.registerTag("switches")
|
||||
swTag = tags.registerTag("sw")
|
||||
var buf = parseFromFile(infile, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
endRead(c)
|
||||
return false
|
||||
var version = ""
|
||||
var sawMeta = false
|
||||
let info = unknownLineInfo
|
||||
c.loopInto:
|
||||
if c.kind == TagLit:
|
||||
if c.cursorTagId == metaTag:
|
||||
sawMeta = true
|
||||
c.loopInto:
|
||||
if c.kind == StrLit:
|
||||
version = strVal(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == nimcacheTag:
|
||||
c.loopInto:
|
||||
if c.kind == StrLit:
|
||||
let nc = strVal(c)
|
||||
# Only when nimcache was not already pinned on the command line: a
|
||||
# `--nimcache:` argument the driver/child was launched with must win
|
||||
# over whatever `nim.cfg` recorded into the artifact.
|
||||
if nc.len > 0 and conf.nimcacheDir.isEmpty:
|
||||
conf.nimcacheDir = AbsoluteDir(nc)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == sourcesTag:
|
||||
# Replay does not need the source list; it exists only for
|
||||
# `sourcesChanged`. Skip the whole section.
|
||||
skip c
|
||||
elif c.cursorTagId == cppTag:
|
||||
c.loopInto:
|
||||
if c.kind == StrLit:
|
||||
cppDefine(conf, strVal(c))
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == pathsTag:
|
||||
c.loopInto:
|
||||
if c.kind == StrLit:
|
||||
# Append preserving the serialised order (which already reflects the
|
||||
# driver's addPath insert-at-front sequence), deduping against any
|
||||
# path a child already received via a forwarded `--path` argument.
|
||||
let d = AbsoluteDir(strVal(c))
|
||||
if not conf.searchPaths.contains(d): conf.searchPaths.add d
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == switchesTag:
|
||||
c.loopInto:
|
||||
if c.kind == TagLit and c.cursorTagId == swTag:
|
||||
var sw = ""
|
||||
var arg = ""
|
||||
var idx = 0
|
||||
c.loopInto:
|
||||
if c.kind == StrLit:
|
||||
if idx == 0: sw = strVal(c)
|
||||
else: arg = strVal(c)
|
||||
inc idx
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
processSwitch(sw, arg, passPP, info, conf)
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
result = sawMeta and version == IcConfigVersion
|
||||
|
||||
proc sourcesChanged*(configFile: string): bool =
|
||||
## True when the precompiled config at `configFile` is missing, malformed,
|
||||
## written by an incompatible version, or any recorded config *source* file is
|
||||
## newer than it (or has vanished) — i.e. the artifact must be regenerated.
|
||||
## Mirrors nifler's `sourcesChanged`: the source list lives inside the artifact
|
||||
## so this needs no out-of-band knowledge of which `nim.cfg`s were read.
|
||||
if not fileExists(configFile): return true
|
||||
let modtime = getLastModificationTime(configFile)
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let
|
||||
stmtsTag = tags.registerTag("stmts")
|
||||
metaTag = tags.registerTag("meta")
|
||||
sourcesTag = tags.registerTag("sources")
|
||||
var buf = parseFromFile(configFile, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
endRead(c)
|
||||
return true
|
||||
var version = ""
|
||||
var depsChanged = false
|
||||
c.loopInto:
|
||||
if c.kind == TagLit and c.cursorTagId == metaTag:
|
||||
c.loopInto:
|
||||
if c.kind == StrLit:
|
||||
version = strVal(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.kind == TagLit and c.cursorTagId == sourcesTag:
|
||||
c.loopInto:
|
||||
if c.kind == StrLit:
|
||||
let dep = strVal(c)
|
||||
if not fileExists(dep) or getLastModificationTime(dep) >= modtime:
|
||||
depsChanged = true
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
result = depsChanged or version != IcConfigVersion
|
||||
|
||||
proc produceIcConfig*(conf: ConfigRef) =
|
||||
## The `cmdIcConfig` command. By the time it runs, the normal pipeline has
|
||||
## already fully parsed the `nim.cfg` chain and run `config.nims`, so the
|
||||
## resolved config is sitting in `conf`; just serialise it to `--o`.
|
||||
let outPath = conf.icConfigOut
|
||||
if outPath.len == 0:
|
||||
rawMessage(conf, errGenerated, "icconfig: missing output path (--icConfigOut)")
|
||||
return
|
||||
createDir(parentDir(outPath))
|
||||
writeIcConfig(conf, outPath)
|
||||
|
||||
proc ensureIcConfig*(conf: ConfigRef) =
|
||||
## Driver-side (`cmdIc`). Make sure an up-to-date precompiled config exists,
|
||||
## (re)producing it in a *separate* process when missing or stale, then point
|
||||
## `conf.icPreparsedConfig` at it so the driver replays the very same config its
|
||||
## `nim m`/`nim nifc` children will — perfect speed (config parsed at most once,
|
||||
## skipped entirely when nothing changed) and consistency (one producer, every
|
||||
## process replays its output). The artifact lives in the nimcache derived from
|
||||
## the command line (pre-config-parse), which is the one the children are told;
|
||||
## a `--nimcache:` set inside `nim.cfg` is recovered from the artifact itself.
|
||||
let cacheDir = getNimcacheDir(conf).string
|
||||
# Start from a clean cache when the on-disk NIF format stamp is absent or stale
|
||||
# (see `icFormatVersion`). This must happen HERE, before the config artifact is
|
||||
# produced — `commandIc` performs the same check later, but by then the artifact
|
||||
# would already live in the cache and the wipe would delete it.
|
||||
createDir(cacheDir)
|
||||
let versionFile = cacheDir / "ic.version"
|
||||
let stamp = if fileExists(versionFile): readFile(versionFile) else: ""
|
||||
if stamp != icFormatVersion:
|
||||
removeDir(cacheDir)
|
||||
createDir(cacheDir)
|
||||
writeFile(versionFile, icFormatVersion)
|
||||
let outPath = cacheDir / "ic_config.cfg.nif"
|
||||
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`) 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 droppedCmd = false
|
||||
for a in commandLineParams():
|
||||
if not droppedCmd and a.len > 0 and a[0] != '-':
|
||||
droppedCmd = true # drop the original command token (`ic`)
|
||||
else:
|
||||
pargs.add a
|
||||
let p = startProcess(getAppFilename(), args = pargs,
|
||||
options = {poStdErrToStdOut})
|
||||
let outp = p.outputStream.readAll()
|
||||
let code = p.waitForExit()
|
||||
p.close()
|
||||
if code != 0 or not fileExists(outPath):
|
||||
rawMessage(conf, errGenerated,
|
||||
"failed to produce precompiled config (exit code " & $code & "):\n" & outp)
|
||||
return
|
||||
conf.icPreparsedConfig = outPath
|
||||
@@ -803,6 +803,23 @@ proc hasCustomDestructor(c: Con, t: PType): bool =
|
||||
obj = skipTypes(obj.baseClass, abstractPtrs)
|
||||
result = result or isCustomDestructor(c, obj)
|
||||
|
||||
const
|
||||
exprBranchKinds = {nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt,
|
||||
nkTryStmt, nkPragmaBlock}
|
||||
|
||||
proc distributeAsgn(asgnKind: TNodeKind; dest, ri: PNode; c: var Con; s: var Scope): PNode =
|
||||
## Distributes an assignment ``dest = ri`` into the leaf expressions of
|
||||
## ``ri`` when ``ri`` is an expression-based control flow construct. This
|
||||
## avoids creating pointless intermediate temporaries (bug #25850). The
|
||||
## descent is recursive so that nestings like ``block: ...; if c: a else: b``
|
||||
## assign directly to ``dest`` instead of going through a temp per branch.
|
||||
if ri.kind in exprBranchKinds:
|
||||
template process(child, s): untyped =
|
||||
distributeAsgn(asgnKind, dest, child, c, s)
|
||||
handleNestedTempl(ri, process, willProduceStmt = true)
|
||||
else:
|
||||
result = newTree(asgnKind, dest, p(ri, c, s, consumed))
|
||||
|
||||
proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSingleUsedTemp}; inReturn = false): PNode =
|
||||
if n.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt,
|
||||
nkIfExpr, nkCaseStmt, nkWhen, nkWhileStmt, nkParForStmt, nkTryStmt, nkPragmaBlock}:
|
||||
@@ -1004,13 +1021,11 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
|
||||
result = moveOrCopy(p(n[0], c, s, mode), n[1], c, s, flags)
|
||||
elif isDiscriminantField(n[0]):
|
||||
result = c.genDiscriminantAsgn(s, n)
|
||||
elif n[1].kind in {nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt, nkPragmaBlock}:
|
||||
elif n[1].kind in exprBranchKinds:
|
||||
# Distribute the assignment into each branch to avoid
|
||||
# creating pointless temporaries for expression-based control flow.
|
||||
let dest = p(n[0], c, s, mode)
|
||||
template process(child, s): untyped =
|
||||
newTree(n.kind, dest, p(child, c, s, consumed))
|
||||
handleNestedTempl(n[1], process, willProduceStmt = true)
|
||||
result = distributeAsgn(n.kind, dest, n[1], c, s)
|
||||
else:
|
||||
result = copyNode(n)
|
||||
result.add p(n[0], c, s, mode)
|
||||
|
||||
98
compiler/itemids.nim
Normal file
98
compiler/itemids.nim
Normal file
@@ -0,0 +1,98 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2026 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## `ItemId` is the identity of a symbol or type: a `(module, item)` pair.
|
||||
##
|
||||
## The fields are private on purpose: the module half reserves bit 30 as the
|
||||
## "backend minted" marker, so all construction and inspection has to go
|
||||
## through this module's API and the marker bit can never leak into module
|
||||
## indexing or arithmetic.
|
||||
##
|
||||
## Three id spaces coexist per module:
|
||||
## - Semantic-phase and NIF-loader ids: `itemId(module, item)` with `item > 0`.
|
||||
## - Backend-minted ids (IC codegen, `nim nifc`: transf labels and temps,
|
||||
## lifted hooks): `backendItemId` sets `BackendModuleBit`, so these can
|
||||
## never compare equal to a loader id even though both counters mint the
|
||||
## same small `item` range in one process. They never cross a process
|
||||
## boundary and must never be written to a NIF file.
|
||||
## - Derived env/tuple-field ids (`lowerings.addField`): the source local's
|
||||
## id with `item` negated. `derivedFieldId` preserves the backend marker,
|
||||
## keeping the derivation collision-free for both id spaces above.
|
||||
|
||||
import std/hashes
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
const
|
||||
BackendModuleBit = 0x4000_0000'i32
|
||||
# Bit 30 of the module field. Bit 31 stays clear so marked module values
|
||||
# remain non-negative and cannot be mistaken for the special negative
|
||||
# module ids like `PackageModuleId`.
|
||||
PackageModuleId* = -3'i32
|
||||
|
||||
type
|
||||
ItemId* = object
|
||||
moduleBits: int32
|
||||
itemBits: int32
|
||||
|
||||
proc itemId*(module, item: int32): ItemId {.inline.} =
|
||||
assert module < 0 or (module and BackendModuleBit) == 0
|
||||
ItemId(moduleBits: module, itemBits: item)
|
||||
|
||||
proc backendItemId*(module, item: int32): ItemId {.inline.} =
|
||||
## An id minted during IC codegen; distinct from every `itemId` of the
|
||||
## same module so that the loader's stub counter and the backend's counter
|
||||
## cannot collide in id-keyed tables.
|
||||
assert module >= 0 and (module and BackendModuleBit) == 0
|
||||
ItemId(moduleBits: module or BackendModuleBit, itemBits: item)
|
||||
|
||||
proc module*(x: ItemId): int32 {.inline.} =
|
||||
if x.moduleBits >= 0: x.moduleBits and not BackendModuleBit
|
||||
else: x.moduleBits
|
||||
|
||||
proc item*(x: ItemId): int32 {.inline.} = x.itemBits
|
||||
|
||||
proc isBackendMinted*(x: ItemId): bool {.inline.} =
|
||||
x.moduleBits >= 0 and (x.moduleBits and BackendModuleBit) != 0
|
||||
|
||||
proc derivedFieldId*(source: ItemId): ItemId {.inline.} =
|
||||
## The id of the env/tuple field that `lowerings.addField` derives for a
|
||||
## captured local: `item` negated, module bits (including the backend
|
||||
## marker) preserved.
|
||||
ItemId(moduleBits: source.moduleBits, itemBits: -abs(source.itemBits))
|
||||
|
||||
proc matchesDerivedFieldId*(field, source: ItemId): bool {.inline.} =
|
||||
## Does `field` carry the id `derivedFieldId` would derive for `source`?
|
||||
## `source` may itself already be the derived field id.
|
||||
field.moduleBits == source.moduleBits and
|
||||
field.itemBits == -abs(source.itemBits)
|
||||
|
||||
proc `==`*(a, b: ItemId): bool {.inline.} =
|
||||
# raw bit comparison: a backend-minted id never equals a loader id
|
||||
a.itemBits == b.itemBits and a.moduleBits == b.moduleBits
|
||||
|
||||
proc hash*(x: ItemId): Hash =
|
||||
var h: Hash = hash(x.moduleBits)
|
||||
h = h !& hash(x.itemBits)
|
||||
result = !$h
|
||||
|
||||
proc `$`*(x: ItemId): string =
|
||||
result = "(module: " & $x.module & ", item: " & $x.itemBits
|
||||
if x.isBackendMinted: result.add ", backend"
|
||||
result.add ")"
|
||||
|
||||
const
|
||||
moduleShift = when defined(cpu32): 20 else: 24
|
||||
|
||||
proc toId*(a: ItemId): int {.inline.} =
|
||||
## Packs an ItemId into a single int. Uses the raw module bits so the
|
||||
## backend marker keeps the two id spaces disjoint (bit 30 shifts to
|
||||
## bit 54; like the module/item split itself this needs a 64-bit int).
|
||||
(a.moduleBits.int shl moduleShift) + a.itemBits.int
|
||||
@@ -164,9 +164,21 @@ proc getClosureIterResult*(g: ModuleGraph; iter: PSym; idgen: IdGenerator): PSym
|
||||
incl(result.flagsImpl, sfUsed)
|
||||
iter.ast.add newSymNode(result)
|
||||
|
||||
proc closureParams(routine: PSym): PNode =
|
||||
## The formal parameters node lambda lifting reads and extends. In a
|
||||
## from-source compilation `routine.ast[paramsPos]` and `routine.typ.n` are the
|
||||
## very same node (see the `typ.n.len` based position math below). Under IC the
|
||||
## loaded proc AST omits the parameters (they are kept only in `typ.n`), so
|
||||
## restore the shared node here.
|
||||
result = routine.ast[paramsPos]
|
||||
if (result == nil or result.kind == nkEmpty) and routine.typ != nil and
|
||||
routine.typ.n != nil and routine.ast.len > paramsPos:
|
||||
result = routine.typ.n
|
||||
routine.ast[paramsPos] = result
|
||||
|
||||
proc addHiddenParam(routine: PSym, param: PSym) =
|
||||
assert param.kind == skParam
|
||||
var params = routine.ast[paramsPos]
|
||||
var params = closureParams(routine)
|
||||
# -1 is correct here as param.position is 0 based but we have at position 0
|
||||
# some nkEffect node:
|
||||
param.position = routine.typ.n.len-1
|
||||
@@ -177,7 +189,8 @@ proc addHiddenParam(routine: PSym, param: PSym) =
|
||||
|
||||
proc getEnvParam*(routine: PSym): PSym =
|
||||
if routine.ast.isNil: return nil
|
||||
let params = routine.ast[paramsPos]
|
||||
let params = closureParams(routine)
|
||||
if params == nil or params.len == 0: return nil
|
||||
let hidden = lastSon(params)
|
||||
if hidden.kind == nkSym and hidden.sym.kind == skParam and hidden.sym.name.s == paramName:
|
||||
result = hidden.sym
|
||||
@@ -294,6 +307,7 @@ proc markAsClosure(g: ModuleGraph; owner: PSym; n: PNode) =
|
||||
elif not (owner.typ.isClosure or owner.isNimcall and not owner.isExplicitCallConv or isEnv):
|
||||
localError(g.config, n.info, "illegal capture '$1' because '$2' has the calling convention: <$3>" %
|
||||
[s.name.s, owner.name.s, $owner.typ.callConv])
|
||||
unsealForTransform(owner.typ)
|
||||
incl(owner.typ, tfCapturesEnv)
|
||||
if not isEnv:
|
||||
owner.typ.callConv = ccClosure
|
||||
|
||||
@@ -711,6 +711,11 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
doAssert t.asink != nil
|
||||
body.add newHookCall(c, t.asink, x, y)
|
||||
of attachedDestructor:
|
||||
when defined(icDbg):
|
||||
if t.destructor == nil:
|
||||
echo "MISSING destructor: ", typeToString(t), " kind=", t.kind,
|
||||
" itemId=", t.itemId, " uniqueId=", t.uniqueId, " state=", t.state,
|
||||
" owner=", (if t.owner != nil: t.owner.name.s else: "nil")
|
||||
doAssert t.destructor != nil
|
||||
body.add destructorCall(c, t.destructor, x)
|
||||
of attachedTrace:
|
||||
@@ -1080,8 +1085,17 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
case t.kind
|
||||
of tyNone, tyEmpty, tyVoid: discard
|
||||
of tyUncheckedArray:
|
||||
# An UncheckedArray has no known length, so it cannot be copied, moved or
|
||||
# destroyed as a value: it only ever lives behind a pointer and its bytes
|
||||
# are managed manually (element ops for seqs/strings go through the
|
||||
# seq/string hooks, which know the length). Emitting `x = y` for it (as the
|
||||
# pointer-like group below does) produces an assignment of an unsized array,
|
||||
# which the C backend cannot lower (genAssignment: tyUncheckedArray). So all
|
||||
# value hooks for it are no-ops.
|
||||
discard
|
||||
of tyPointer, tySet, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCstring,
|
||||
tyPtr, tyUncheckedArray, tyVar, tyLent:
|
||||
tyPtr, tyVar, tyLent:
|
||||
defaultOp(c, t, body, x, y)
|
||||
of tyRef:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
@@ -1221,6 +1235,7 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
|
||||
n[resultPos] = newSymNode(res)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, {sfFromGeneric, sfGeneratedOp}
|
||||
setHookDisamb(g, result, AttachedOpToStr[kind], typ)
|
||||
|
||||
proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp;
|
||||
info: TLineInfo; idgen: IdGenerator; isDiscriminant = false): PSym =
|
||||
@@ -1267,6 +1282,10 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
|
||||
if kind == attachedWasMoved:
|
||||
incl result.flagsImpl, sfNoSideEffect
|
||||
incl result.typ, tfNoSideEffect
|
||||
if not isDiscriminant:
|
||||
# discriminant destructors derive their body from the enclosing object
|
||||
# AND the selected field; their key is set at the call site
|
||||
setHookDisamb(g, result, AttachedOpToStr[kind], typ)
|
||||
|
||||
proc genTypeFieldCopy(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
let xx = genBuiltin(c, mAccessTypeField, "accessTypeField", x)
|
||||
@@ -1359,6 +1378,7 @@ proc produceDestructorForDiscriminator*(g: ModuleGraph; typ: PType; field: PSym,
|
||||
assert(typ.skipTypes({tyAlias, tyGenericInst}).kind == tyObject)
|
||||
# discrimantor assignments needs pointers to destroy fields; alas, we cannot use non-var destructor here
|
||||
result = symPrototype(g, field.typ, typ.owner, attachedDestructor, info, idgen, isDiscriminant = true)
|
||||
setHookDisamb(g, result, "=destroy¦" & field.name.s & "¦" & $field.position, typ)
|
||||
var a = TLiftCtx(info: info, g: g, kind: attachedDestructor, asgnForType: typ, idgen: idgen,
|
||||
fn: result)
|
||||
a.asgnForType = typ
|
||||
|
||||
@@ -100,6 +100,7 @@ type
|
||||
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
|
||||
warnImplicitRangeConversion = "ImplicitRangeConversion",
|
||||
warnSystemRangeConversion = "SystemRangeConversion",
|
||||
warnInvalidCmpOp = "InvalidCmpOp",
|
||||
# hints
|
||||
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
||||
hintCC = "CC",
|
||||
@@ -210,6 +211,7 @@ const
|
||||
warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable",
|
||||
warnImplicitRangeConversion: "implicit range conversion $1",
|
||||
warnSystemRangeConversion: "implicit range conversion $1",
|
||||
warnInvalidCmpOp: "$1",
|
||||
hintSuccess: "operation successful: $#",
|
||||
# keep in sync with `testament.isSuccess`
|
||||
hintSuccessX: "$build\n$loc lines; ${sec}s; $mem; proj: $project; out: $output",
|
||||
|
||||
@@ -378,6 +378,9 @@ proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string;
|
||||
conflictsWith: TLineInfo, note = errGenerated) =
|
||||
## Emit a redefinition error if in non-interactive mode
|
||||
if c.config.cmd != cmdInteractive:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icRedef] ", s
|
||||
echo getStackTrace()
|
||||
localError(c.config, info, note,
|
||||
"redefinition of '$1'; previous declaration here: $2" %
|
||||
[s, c.config $ conflictsWith])
|
||||
@@ -459,6 +462,15 @@ proc openShadowScope*(c: PContext) =
|
||||
symbols: initStrTable(),
|
||||
depthLevel: c.scopeDepth)
|
||||
|
||||
proc rememberShadowDefs*(c: PContext) =
|
||||
## bug #25693: a template/macro operand's local definitions are sem-checked in
|
||||
## a shadow scope that is then discarded. Record those definitions so that a
|
||||
## later re-emission (e.g. a captured `typed` fragment expanded more than once)
|
||||
## can be detected as a redefinition rather than silently miscompiled.
|
||||
for s in c.currentScope.symbols:
|
||||
if s.kind in {skVar, skLet, skForVar} and {sfGenSym, sfWasGenSym} * s.flags == {}:
|
||||
c.shadowDiscardedDefs.incl s.id
|
||||
|
||||
proc closeShadowScope*(c: PContext) =
|
||||
## closes the shadow scope, but doesn't merge any of the symbols
|
||||
## Does not check for unused symbols or missing forward decls since a macro
|
||||
|
||||
@@ -207,7 +207,7 @@ proc lookupInRecord(n: PNode, id: ItemId): PSym =
|
||||
if result != nil: return
|
||||
else: discard
|
||||
of nkSym:
|
||||
if n.sym.itemId.module == id.module and n.sym.itemId.item == -abs(id.item): result = n.sym
|
||||
if matchesDerivedFieldId(n.sym.itemId, id): result = n.sym
|
||||
else: discard
|
||||
|
||||
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
|
||||
@@ -215,7 +215,7 @@ proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym
|
||||
# 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)
|
||||
field.itemId = ItemId(module: s.itemId.module, item: -s.itemId.item)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
let t = skipIntLit(s.typ, idgen)
|
||||
field.typ = t
|
||||
if s.kind in {skLet, skVar, skField, skForVar}:
|
||||
@@ -235,7 +235,7 @@ proc addUniqueField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator)
|
||||
if result == nil:
|
||||
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), idgen,
|
||||
s.owner, s.info, s.options)
|
||||
field.itemId = ItemId(module: s.itemId.module, item: -s.itemId.item)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
let t = skipIntLit(s.typ, idgen)
|
||||
field.typ = t
|
||||
assert t.kind != tyTyped
|
||||
|
||||
@@ -29,6 +29,7 @@ when defined(nimPreviewSlimSystem):
|
||||
import ../dist/checksums/src/checksums/sha1
|
||||
|
||||
import pipelines
|
||||
from icconfig import produceIcConfig
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
import nifbackend
|
||||
@@ -419,9 +420,14 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
# cmdM uses NIF files, not ROD files
|
||||
graph.config.symbolFiles = disabledSf
|
||||
setUseIc(true)
|
||||
# vtable dispatch needs a whole-program vtable layout, which the
|
||||
# per-module compilation model cannot provide (yet); methods dispatch
|
||||
# through the classic if-chain dispatchers instead
|
||||
excl conf.features, Feature.vtables
|
||||
commandCheck(graph)
|
||||
of cmdNifC:
|
||||
setUseIc(true)
|
||||
excl conf.features, Feature.vtables
|
||||
# Generate C code from NIF files
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
@@ -434,6 +440,11 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
commandIc(conf)
|
||||
else:
|
||||
rawMessage(conf, errGenerated, "nim deps not available in bootstrap build")
|
||||
of cmdIcConfig:
|
||||
# Produce the precompiled config artifact for `nim ic` (config already
|
||||
# parsed by the normal pipeline); a separate process spawned by the driver.
|
||||
wantMainModule(conf)
|
||||
produceIcConfig(conf)
|
||||
of cmdParse:
|
||||
wantMainModule(conf)
|
||||
discard parseFile(conf.projectMainIdx, cache, conf)
|
||||
@@ -450,7 +461,14 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
of cmdUnknown, cmdNone, cmdIdeTools:
|
||||
rawMessage(conf, errGenerated, "invalid command: " & conf.command)
|
||||
|
||||
if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop}:
|
||||
if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop, cmdM} and
|
||||
not (conf.cmd == cmdNifC and conf.icBackendStage.len > 0):
|
||||
# The IC build runs hundreds of internal per-module child processes — the
|
||||
# frontend `nim m` (cmdM) and the per-module backend stages (cg/emit/merge/
|
||||
# link). Each would print a `[SuccessX]` summary that is pure noise (and
|
||||
# misleading: `out: unknownOutput`, or `out: <the whole compiler>` for a
|
||||
# step that only wrote one `.c.nif`/`.c`). The driving `nim ic` (and koch)
|
||||
# reports the real result.
|
||||
if optProfileVM in conf.globalOptions:
|
||||
echo conf.dump(conf.vmProfileData)
|
||||
genSuccessX(conf)
|
||||
|
||||
@@ -53,7 +53,29 @@ proc mangleParamExt*(s: PSym): string =
|
||||
result.addInt s.position
|
||||
|
||||
proc mangleProcNameExt*(graph: ModuleGraph, s: PSym): string =
|
||||
result = "__"
|
||||
# The disambiguator comes first and the module suffix LAST, so the suffix is
|
||||
# a strippable trailing token: content-addressed cross-module merging chops
|
||||
# everything from the final `__` to recover a mint-site-independent name.
|
||||
if s.itemId.isBackendMinted:
|
||||
# A symbol minted during IC codegen (`idGeneratorForBackend`): its idgen
|
||||
# starts with an EMPTY per-name disamb table, so its `disamb` restarts at 0
|
||||
# and collides with same-named sem-time symbols loaded from NIFs (two
|
||||
# `=destroy` hooks both mangling to `_u2` → "conflicting types for ..." in
|
||||
# the generated C). These symbols never cross a process boundary (nifc
|
||||
# lifts, emits and compiles them in one run), so the per-module-unique
|
||||
# item id is a safe and deterministic discriminator; the `_c` marker keeps
|
||||
# the namespace disjoint from `_u<disamb>`.
|
||||
result = "_c"
|
||||
result.addInt s.itemId.item
|
||||
else:
|
||||
result = "_u"
|
||||
# Use `disamb` rather than `itemId.item`: under incremental compilation a
|
||||
# symbol loaded from a NIF file gets a fresh, load-order-dependent `itemId.item`
|
||||
# (from the per-module symbol counter), which is neither stable across the
|
||||
# processes that compile vs. use a module nor guaranteed distinct from another
|
||||
# loaded symbol's. `disamb` is assigned deterministically per (module, name)
|
||||
# and, together with the already-prepended mangled name, yields a unique and
|
||||
# stable C identifier.
|
||||
result.addInt s.disamb
|
||||
result.add "__"
|
||||
result.add graph.ifaces[s.itemId.module].uniqueName
|
||||
result.add "_u"
|
||||
result.addInt s.itemId.item # s.disamb #
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
## represents a complete Nim project. Single modules can either be kept in RAM
|
||||
## or stored in a rod-file.
|
||||
|
||||
import std/[intsets, tables, hashes, strtabs, os, strutils, parseutils]
|
||||
import std/[intsets, tables, hashes, strtabs, os, strutils, parseutils, sets]
|
||||
import ../dist/checksums/src/checksums/md5
|
||||
import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages, suggestsymdb
|
||||
|
||||
@@ -68,6 +68,42 @@ type
|
||||
enumToStringProcs*: Table[ItemId, PSym]
|
||||
loadedEnumToStringProcs: Table[string, PSym]
|
||||
emittedTypeInfo*: Table[string, FileIndex]
|
||||
instDisambs: Table[(int, int32), ItemId] # (name id, content disamb) ->
|
||||
# instance, for collision probing in
|
||||
# `setInstanceDisamb`
|
||||
icCnifFiles*: seq[string] # `.c.nif` artifacts written by this run
|
||||
pendingMethodReplays*: seq[PSym] # method registrations loaded under
|
||||
# `nim nifc`, bucketed only after every
|
||||
# module is loaded (`flushMethodReplays`)
|
||||
icImplDeps*: IntSet # NeedsImpl edge tracking under `nim m`:
|
||||
# module ids (FileIndex) whose routine BODIES
|
||||
# this compilation consumed at compile time.
|
||||
# Written to the `.edges` sidecar; deps.nim
|
||||
# then gates the dependent on those modules'
|
||||
# IMPL cookie instead of the iface cookie, so
|
||||
# e.g. `const x = dep.foo()` re-sems when foo's
|
||||
# body changes. Uniform across body-access
|
||||
# kinds — the iface cookie hashes signatures
|
||||
# ONLY (see ast2nif.cookieSd), so every body
|
||||
# consumer records an edge here: VM-compiled /
|
||||
# getImpl'ed bodies (recordIcImplDep from vm/
|
||||
# vmgen), expanded templates (semTemplateExpr)
|
||||
# and instantiated generics (generateInstance).
|
||||
# Inline iterators / `inline` procs are NOT
|
||||
# tracked: they are inlined at codegen, where
|
||||
# the nifc backend's NIF-mtime invalidation
|
||||
# already re-codegens their users.
|
||||
icQualIfaces*: IntSet # module positions whose interface tables were
|
||||
# populated ONLY for qualified access through a
|
||||
# module re-export (`import x; export x`); the
|
||||
# Iface.module stays nil so a later direct
|
||||
# import still takes the full load path
|
||||
inVMTransform*: int # >0 while the VM compiles a routine body
|
||||
# (vmgen.genProc's transformBody): hooks lifted
|
||||
# there (e.g. for closure-env types of LOADED
|
||||
# routines) are process-local VM artifacts —
|
||||
# serializing them would embed references to
|
||||
# derived env-field syms that no module defines
|
||||
|
||||
packageSyms*: TStrTable
|
||||
deps*: IntSet # the dependency graph or potentially its transitive closure.
|
||||
@@ -110,6 +146,11 @@ type
|
||||
cacheSeqs*: Table[string, PNode] # state that is shared to support the 'macrocache' API; IC: implemented
|
||||
cacheCounters*: Table[string, BiggestInt] # IC: implemented
|
||||
cacheTables*: Table[string, BTree[string, PNode]] # IC: implemented
|
||||
transitiveReplayActions*: seq[PNode] # macro-cache replay actions collected from
|
||||
# the transitive import closure of a NIF-loaded module (loadTransitiveHooks);
|
||||
# the caller (pipelines) replays them so a dependency's macrocache state — e.g.
|
||||
# nim-serialization's flavor registration — reaches a module that imports it
|
||||
# only indirectly. Drained per moduleFromNifFile call.
|
||||
passes*: seq[TPass]
|
||||
pipelinePass*: PipelinePass
|
||||
onDefinition*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
|
||||
@@ -126,6 +167,7 @@ type
|
||||
procGlobals*: seq[PNode]
|
||||
nifReplayActions*: Table[int32, seq[PNode]] # module position -> replay actions for NIF
|
||||
cachedMods: IntSet
|
||||
hookClosure: IntSet # modules whose serialized hooks were already registered
|
||||
|
||||
TPassContext* = object of RootObj # the pass's context
|
||||
idgen*: IdGenerator
|
||||
@@ -235,6 +277,18 @@ iterator allSyms*(g: ModuleGraph; m: PSym): PSym =
|
||||
if s != nil:
|
||||
yield s
|
||||
|
||||
proc reexportedModuleSyms*(g: ModuleGraph; m: PSym): seq[(string, string)] =
|
||||
## (name, NIF module suffix) of MODULE syms in `m`'s interface — these are
|
||||
## re-exports (`import x; export x`, added by `reexportSym`) acting as
|
||||
## qualifiers (`m.x.sym`). Consumed by the NIF writer; semExport does not
|
||||
## put them into the nkExportStmt children, so the AST walk cannot see them.
|
||||
result = @[]
|
||||
var seen = initIntSet()
|
||||
for s in g.ifaces[m.position].interf.data:
|
||||
if s != nil and s.kind == skModule and s.position != m.position and
|
||||
not seen.containsOrIncl(s.position):
|
||||
result.add (s.name.s, cachedModuleSuffix(g.config, FileIndex s.position))
|
||||
|
||||
proc someSym*(g: ModuleGraph; m: PSym; name: PIdent): PSym =
|
||||
let importHidden = optImportHidden in m.options
|
||||
result = strTableGet(g.ifaces[m.position].interfSelect(importHidden), name)
|
||||
@@ -280,21 +334,67 @@ proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
result = g.loadedOps[op].getOrDefault(key)
|
||||
#echo "fallback ", key, " ", op, " ", result
|
||||
when defined(icDbgHash):
|
||||
if result == nil and op == attachedDestructor:
|
||||
echo "HOOK MISS key=", key, " table.len=", g.loadedOps[op].len,
|
||||
" kind=", t.kind, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL")
|
||||
if key.len > 10:
|
||||
let probe = key[3 ..< min(key.len, 18)]
|
||||
for k in g.loadedOps[op].keys:
|
||||
if probe in k: echo " candidate: ", k
|
||||
else:
|
||||
result = nil
|
||||
|
||||
proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
|
||||
## we also need to record this to the packed module.
|
||||
if not g.attachedOps[op].contains(t.itemId):
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
# Use key-based deduplication for opsLog because different type objects
|
||||
# (e.g. canon vs orig) can have different itemIds but same structural key
|
||||
if key notin g.loadedOps[op]:
|
||||
# Hooks should be written to the module where the type is defined,
|
||||
# not the module that triggered the registration
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: ownerModule, key: key, sym: value)
|
||||
# Key-based deduplication for opsLog: different type objects (e.g. canon vs
|
||||
# orig) can have different itemIds but the same structural key.
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
if g.inVMTransform > 0 and g.config.cmd == cmdM:
|
||||
# hook lifted while the VM compiles a routine body (closure-env types of
|
||||
# loaded routines): register it for in-process lookup but keep it out of
|
||||
# the serialized log — it is a process-local artifact whose type graph
|
||||
# references derived env-field syms that no module's NIF defines
|
||||
if g.loadedOps[op].getOrDefault(key) == nil:
|
||||
g.loadedOps[op][key] = value
|
||||
g.attachedOps[op][t.itemId] = value
|
||||
return
|
||||
let existing = g.loadedOps[op].getOrDefault(key)
|
||||
if existing == nil:
|
||||
# Stamp the entry with the module whose compilation produced the hook
|
||||
# (`module`), NOT the type's def module: each `nim m` is a separate
|
||||
# process, so a hook lifted while compiling a *downstream* module simply
|
||||
# does not exist in the def module's process — stamping it with the def
|
||||
# module produced a `LogEntry` that no module ever writes (the def
|
||||
# module's writer ran in another process that never lifted it; this
|
||||
# module's writer skips it because `op.module != thisModule`) and codegen
|
||||
# failed with "'=destroy' operator not found" (e.g. astdef's `TStrTable`,
|
||||
# whose destroy is first needed by modulegraphs). This holds for nominal
|
||||
# types as much as for generic/structural instances. Duplicate
|
||||
# registrations across lifting modules are reconciled deterministically
|
||||
# at load time (see the HookEntry replay in `replayStateChanges`).
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: module, key: key, sym: value)
|
||||
g.loadedOps[op][key] = value
|
||||
elif existing != value:
|
||||
# Re-registration replacing an earlier sym for the same key. This happens
|
||||
# legitimately: `createTypeBoundOps` first registers empty `symPrototype`
|
||||
# placeholders, then `produceSym` replaces them — in particular
|
||||
# `produceSymDistinctType` replaces a distinct type's placeholder with the
|
||||
# BASE type's hook (a `distinct string` uses string's `=sink`). The log
|
||||
# must follow the replacement, otherwise the NIF ships the dead,
|
||||
# empty-bodied prototype and codegen in another process calls a no-op
|
||||
# `=sink`/`=copy`, silently losing the value (e.g. `conf.projectPath`
|
||||
# ended up empty: "cannot open '/'").
|
||||
g.loadedOps[op][key] = value
|
||||
var updated = false
|
||||
for e in mitems(g.opsLog):
|
||||
if e.kind == HookEntry and e.op == op and e.key == key:
|
||||
e.sym = value
|
||||
e.module = module
|
||||
updated = true
|
||||
break
|
||||
if not updated:
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: module, key: key, sym: value)
|
||||
g.attachedOps[op][t.itemId] = value
|
||||
|
||||
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
|
||||
@@ -343,8 +443,10 @@ proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
|
||||
proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
|
||||
g.enumToStringProcs[t.itemId] = value
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: value.itemId.module.int
|
||||
g.opsLog.add LogEntry(kind: EnumToStrEntry, module: ownerModule, key: key, sym: value)
|
||||
# Stamp with the module that owns the generated proc, not the enum's def
|
||||
# module: the def module's process may never have generated it (same
|
||||
# "written by nobody" failure as hook entries, see setAttachedOp).
|
||||
g.opsLog.add LogEntry(kind: EnumToStrEntry, module: value.itemId.module.int, key: key, sym: value)
|
||||
|
||||
iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
|
||||
if g.methodsPerGenericType.contains(t.itemId):
|
||||
@@ -357,6 +459,49 @@ proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSy
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, module: ownerModule, key: key, sym: m)
|
||||
|
||||
proc logMethodDef*(g: ModuleGraph; s: PSym) =
|
||||
## Log a method registration (`cgmeth.methodDef`) so that importers and
|
||||
## the backend can rebuild the dispatch buckets (`g.methods`) from the
|
||||
## NIF replay log — the serialized method ast carries its dispatcher sym
|
||||
## at `dispatcherPos`, so replay reuses the original dispatcher that all
|
||||
## call sites reference by name (see `registerLoadedMethod`).
|
||||
if g.config.cmd in {cmdNifC, cmdM}:
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, module: s.itemId.module.int,
|
||||
key: "", sym: s)
|
||||
|
||||
proc registerLoadedMethod*(g: ModuleGraph; m: PSym) =
|
||||
## Rebuild the dispatch buckets from a serialized method registration.
|
||||
## Buckets group the methods sharing a dispatcher; the dispatcher's BODY
|
||||
## does not exist in serialized form — `generateIfMethodDispatchers`
|
||||
## synthesizes it in the backend from the complete bucket.
|
||||
template dbg(msg: string) =
|
||||
when defined(icDbgMeth):
|
||||
echo "[icMeth] replay ", (if m != nil: m.name.s else: "nil"), ": ", msg
|
||||
if m == nil or sfDispatcher in m.flags: dbg "skip self/nil"; return
|
||||
if m.ast == nil or dispatcherPos >= m.ast.len:
|
||||
dbg "no dispatcherPos (len " & $(if m.ast != nil: m.ast.len else: -1) & ")"
|
||||
return
|
||||
let dn = m.ast[dispatcherPos]
|
||||
if dn == nil or dn.kind != nkSym or dn.sym == nil: dbg "empty dispatcher slot"; return
|
||||
let disp = dn.sym
|
||||
if sfDispatcher notin disp.flags: dbg "slot sym not a dispatcher"; return
|
||||
dbg "ok -> bucket of " & disp.name.s & "." & $disp.disamb
|
||||
for i in 0..<g.methods.len:
|
||||
if g.methods[i].dispatcher.itemId == disp.itemId:
|
||||
for existing in g.methods[i].methods:
|
||||
if existing.itemId == m.itemId: return
|
||||
g.methods[i].methods.add m
|
||||
return
|
||||
g.methods.add (methods: @[m], dispatcher: disp)
|
||||
|
||||
proc flushMethodReplays*(g: ModuleGraph) =
|
||||
## Builds the dispatch buckets from the method registrations collected
|
||||
## during module loading; called once every module of the program is
|
||||
## loaded (`nifbackend.generateCode`).
|
||||
for s in g.pendingMethodReplays:
|
||||
registerLoadedMethod(g, s)
|
||||
g.pendingMethodReplays.setLen 0
|
||||
|
||||
proc logGenericInstance*(g: ModuleGraph; inst: PSym) =
|
||||
## Log a generic instance so it gets written to the NIF file.
|
||||
## This is needed when generic instances are created during compile-time
|
||||
@@ -365,6 +510,86 @@ proc logGenericInstance*(g: ModuleGraph; inst: PSym) =
|
||||
let ownerModule = inst.itemId.module.int
|
||||
g.opsLog.add LogEntry(kind: GenericInstEntry, module: ownerModule, sym: inst)
|
||||
|
||||
const
|
||||
InstanceDisambBit* = 0x4000_0000'i32
|
||||
## Set in the `disamb` of routine instances whose value is content-derived
|
||||
## (see `setInstanceDisamb`); keeps them disjoint from the small counter
|
||||
## range ordinary symbols draw from, so the NIF name `name.disamb.module`
|
||||
## stays collision-free within a module.
|
||||
|
||||
proc setInstanceDisamb*(g: ModuleGraph; inst, generic: PSym;
|
||||
concreteTypes: openArray[PType]) =
|
||||
## Under IC, replace a fresh routine instance's counter-based `disamb` with
|
||||
## a content-derived one: a hash of the generic's identity plus the
|
||||
## `typeKey` of every concrete type argument — exactly the identity the
|
||||
## instantiation cache compares. The instance's NIF name
|
||||
## `name.disamb.modsuffix` then differs only in the module suffix when the
|
||||
## same instantiation is made by different modules, which is the
|
||||
## prerequisite for cross-module generic-instance merging (and gives the
|
||||
## dce analysis its `offers` keys). The hash is computed once, here; it is
|
||||
## never recomputed — the value travels in the serialized `disamb` field.
|
||||
if g.config.cmd notin {cmdNifC, cmdM}: return
|
||||
if isDefined(g.config, "icNoInstKey"): return
|
||||
var key = generic.name.s
|
||||
key.add '.'
|
||||
key.addInt generic.disamb
|
||||
key.add '.'
|
||||
key.add modname(generic.itemId.module, g.config)
|
||||
for t in concreteTypes:
|
||||
key.add '|'
|
||||
key.add typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
let d = toMD5(key)
|
||||
var h = (int32(d[0]) or (int32(d[1]) shl 8) or (int32(d[2]) shl 16) or
|
||||
(int32(d[3] and 0x3F'u8) shl 24)) or InstanceDisambBit
|
||||
# Same-name hash collisions inside this process get probed to the next
|
||||
# free value; the loser stays correct (its name keeps the module suffix),
|
||||
# it merely won't merge cross-module.
|
||||
while true:
|
||||
let probe = (inst.name.id, h)
|
||||
if g.instDisambs.hasKey(probe):
|
||||
if g.instDisambs[probe] == inst.itemId: break
|
||||
h = if h == high(int32): InstanceDisambBit else: h + 1
|
||||
else:
|
||||
g.instDisambs[probe] = inst.itemId
|
||||
break
|
||||
inst.disamb = h
|
||||
|
||||
const
|
||||
HookDisambBit* = 0x2000_0000'i32
|
||||
## Set in the `disamb` of synthesized type-bound operators and `$enum`
|
||||
## procs whose value is content-derived (see `setHookDisamb`); disjoint
|
||||
## from both the small counter range and the `InstanceDisambBit` range.
|
||||
|
||||
proc setHookDisamb*(g: ModuleGraph; hook: PSym; opName: string; typ: PType) =
|
||||
## Under IC, replace a synthesized hook's counter-based `disamb` with a
|
||||
## content-derived one: a hash of the operation name plus the `typeKey` of
|
||||
## the type it is bound to. Counter disambs renumber whenever an *earlier*
|
||||
## hook appears in a re-semmed module, so cached translation units keep
|
||||
## calling the old `_u<disamb>` C name while the regenerated producer
|
||||
## defines a new one — the hook flavor of the backend def-migration hole.
|
||||
## With a content-derived value the hook's NIF name (and hence its C name)
|
||||
## is stable as long as the type itself is unchanged.
|
||||
if g.config.cmd notin {cmdNifC, cmdM}: return
|
||||
if isDefined(g.config, "icNoHookKey"): return
|
||||
var key = opName
|
||||
key.add '|'
|
||||
key.add typeKey(typ, g.config, loadTypeCallback, loadSymCallback)
|
||||
let d = toMD5(key)
|
||||
var h = (int32(d[0]) or (int32(d[1]) shl 8) or (int32(d[2]) shl 16) or
|
||||
(int32(d[3] and 0x1F'u8) shl 24)) or HookDisambBit
|
||||
# Same-name hash collisions inside this process get probed to the next
|
||||
# free value (staying below InstanceDisambBit); the loser merely loses
|
||||
# cross-run name stability.
|
||||
while true:
|
||||
let probe = (hook.name.id, h)
|
||||
if g.instDisambs.hasKey(probe):
|
||||
if g.instDisambs[probe] == hook.itemId: break
|
||||
h = if h == InstanceDisambBit - 1'i32: HookDisambBit else: h + 1
|
||||
else:
|
||||
g.instDisambs[probe] = hook.itemId
|
||||
break
|
||||
hook.disamb = h
|
||||
|
||||
proc hasDisabledAsgn*(g: ModuleGraph; t: PType): bool =
|
||||
let op = getAttachedOp(g, t, attachedAsgn)
|
||||
result = op != nil and sfError in op.flags
|
||||
@@ -543,6 +768,7 @@ proc initModuleGraphFields(result: ModuleGraph) =
|
||||
result.emittedTypeInfo = initTable[string, FileIndex]()
|
||||
result.cachedFiles = newStringTable()
|
||||
result.cachedMods = initIntSet()
|
||||
result.hookClosure = initIntSet()
|
||||
|
||||
proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
|
||||
result = ModuleGraph()
|
||||
@@ -573,6 +799,15 @@ proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
proc moduleOpenForCodegen*(g: ModuleGraph; m: FileIndex): bool {.inline.} =
|
||||
result = true
|
||||
|
||||
proc recordIcImplDep*(g: ModuleGraph; s: PSym) =
|
||||
## NeedsImpl edge tracking, see `icImplDeps`. Called from the compile-time
|
||||
## body consumption sites (vmgen's proc compilation, the getImpl opcodes).
|
||||
## Own-module and group-member entries are filtered out when the `.edges`
|
||||
## sidecar is written.
|
||||
if g.config.cmd == cmdM and s != nil and s.kind in routineKinds and
|
||||
s.itemId.module >= 0 and not isBackendMinted(s.itemId):
|
||||
g.icImplDeps.incl module(s.itemId).int
|
||||
|
||||
proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b
|
||||
|
||||
proc addDep*(g: ModuleGraph; m: PSym, dep: FileIndex) =
|
||||
@@ -658,6 +893,105 @@ proc getBody*(g: ModuleGraph; s: PSym): PNode {.inline.} =
|
||||
assert result != nil
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
proc registerLoadedHooks(g: ModuleGraph; logOps: seq[LogEntry]) =
|
||||
let mainSuffix = getMainModuleSuffix(ast.program)
|
||||
for x in logOps:
|
||||
# A dependency's NIF may carry hooks whose syms belong to the module we
|
||||
# are compiling fresh (e.g. a stale NIF of that very module written by an
|
||||
# earlier in-process compilation). Loading those would collide with the
|
||||
# freshly semchecked hook declarations.
|
||||
if mainSuffix.len > 0 and
|
||||
cachedModuleSuffix(g.config, x.sym.itemId.module.FileIndex) == mainSuffix:
|
||||
continue
|
||||
case x.kind
|
||||
of HookEntry:
|
||||
# The same structural hook may be serialized by several instantiating
|
||||
# modules (a generic/structural instance has no single def site, so each
|
||||
# using module owns its copy). Pick one deterministic program-wide winner
|
||||
# by the smaller owning-module name, so every lookup resolves to the same
|
||||
# sym regardless of module load order.
|
||||
let existing = g.loadedOps[x.op].getOrDefault(x.key)
|
||||
if existing == nil or
|
||||
cachedModuleSuffix(g.config, x.sym.itemId.module.FileIndex) <
|
||||
cachedModuleSuffix(g.config, existing.itemId.module.FileIndex):
|
||||
g.loadedOps[x.op][x.key] = x.sym
|
||||
of EnumToStrEntry:
|
||||
g.loadedEnumToStringProcs[x.key] = x.sym
|
||||
of MethodEntry:
|
||||
# only `methodDef` registrations (empty key) rebuild dispatch
|
||||
# buckets; the `addMethodToGeneric` flavor (typeKey key) announces
|
||||
# the uninstantiated generic method, which must never enter a
|
||||
# bucket (methodsPerGenericType replay is still a todo).
|
||||
# Under `nim nifc` the replay is deferred: building a bucket forces
|
||||
# the method's body, and a body loaded mid `loadModuleDependencies`
|
||||
# registers modules it references in a different path context than
|
||||
# the lazy loads during codegen do (`flushMethodReplays`).
|
||||
if x.key.len == 0:
|
||||
if g.config.cmd == cmdNifC:
|
||||
g.pendingMethodReplays.add x.sym
|
||||
else:
|
||||
registerLoadedMethod(g, x.sym)
|
||||
else:
|
||||
discard
|
||||
|
||||
proc loadTransitiveHooks(g: ModuleGraph; deps: seq[ModuleSuffix]) =
|
||||
## Registers the serialized hooks (and enum-to-string procs) of every module
|
||||
## in the import closure of `deps`. Deliberately does NOT use
|
||||
## `moduleFromNifFile`: that would register the dep as a fully loaded module
|
||||
## and a later direct import of it would then skip `replayStateChanges`.
|
||||
var stack = deps
|
||||
var interf = initStrTable()
|
||||
var interfHidden = initStrTable()
|
||||
while stack.len > 0:
|
||||
let suffix = stack.pop()
|
||||
var isKnownFile = false
|
||||
let fileIdx = g.config.registerNifSuffix(string suffix, isKnownFile)
|
||||
if not g.hookClosure.containsOrIncl(fileIdx.int):
|
||||
let precomp = loadNifModule(ast.program, suffix, interf, interfHidden, {})
|
||||
registerLoadedHooks(g, precomp.logOps)
|
||||
# Collect the dependency's macro-cache replay actions (put/inc/add/incl)
|
||||
# so the importer being compiled also sees macrocache state registered
|
||||
# by a transitively-imported module. Pragma replay actions are a backend
|
||||
# concern and are intentionally not collected here.
|
||||
for n in precomp.topLevel:
|
||||
if n.kind == nkReplayAction and n.len >= 1 and n[0].kind == nkStrLit and
|
||||
n[0].strVal in ["put", "inc", "add", "incl"]:
|
||||
g.transitiveReplayActions.add n
|
||||
for d in precomp.deps: stack.add d
|
||||
|
||||
proc materializeReexportedModule(g: ModuleGraph; mname, msuffix: string): PSym =
|
||||
## A re-exported MODULE (`import x; export x`) acts as a qualifier in the
|
||||
## re-exporting module's interface (`asmm.x86.nd`). Reconstruct a module
|
||||
## symbol for it and make its interface tables available for qualified
|
||||
## lookup (`someSym` reads `g.ifaces[position]`) — WITHOUT registering
|
||||
## the module: `Iface.module` stays nil so a later direct import still
|
||||
## takes the full load path (replayStateChanges etc.).
|
||||
var isKnown = false
|
||||
let fIdx = g.config.registerNifSuffix(msuffix, isKnown)
|
||||
if fIdx.int >= g.ifaces.len: setLen(g.ifaces, fIdx.int + 1)
|
||||
if g.ifaces[fIdx.int].module != nil and
|
||||
g.ifaces[fIdx.int].module.name.s == mname:
|
||||
# properly registered already (directly imported earlier): reuse it
|
||||
return g.ifaces[fIdx.int].module
|
||||
result = PSym(kindImpl: skModule, itemId: itemId(int32(fIdx), 0'i32),
|
||||
name: getIdent(g.cache, mname),
|
||||
infoImpl: newLineInfo(fIdx, 1, 1),
|
||||
positionImpl: int(fIdx))
|
||||
setOwner(result, getPackage(g.config, g.cache, fIdx))
|
||||
if g.ifaces[fIdx.int].module == nil and
|
||||
not g.icQualIfaces.containsOrIncl(fIdx.int):
|
||||
var interf = initStrTable()
|
||||
var interfHidden = initStrTable()
|
||||
let precomp = loadNifModule(ast.program, ModuleSuffix(msuffix),
|
||||
interf, interfHidden, {})
|
||||
# chains: the re-exported module may itself re-export modules
|
||||
for (n2, s2) in precomp.reexportedModules:
|
||||
let inner = materializeReexportedModule(g, n2, s2)
|
||||
if inner != nil:
|
||||
strTableAdd(interf, inner)
|
||||
g.ifaces[fIdx.int].interf = interf
|
||||
g.ifaces[fIdx.int].interfHidden = interfHidden
|
||||
|
||||
proc moduleFromNifFile*(g: ModuleGraph; fileIdx: FileIndex;
|
||||
flags: set[LoadFlag] = {}): PrecompiledModule =
|
||||
## Returns 'nil' if the module needs to be recompiled.
|
||||
@@ -671,7 +1005,7 @@ when not defined(nimKochBootstrap):
|
||||
|
||||
let m = PSym(
|
||||
kindImpl: skModule,
|
||||
itemId: ItemId(module: int32(fileIdx), item: 0'i32),
|
||||
itemId: itemId(int32(fileIdx), 0'i32),
|
||||
name: getIdent(g.cache, splitFile(filename).name),
|
||||
infoImpl: newLineInfo(fileIdx, 1, 1),
|
||||
positionImpl: int(fileIdx))
|
||||
@@ -683,25 +1017,51 @@ when not defined(nimKochBootstrap):
|
||||
g.ifaces[fileIdx.int].interf,
|
||||
g.ifaces[fileIdx.int].interfHidden, flags)
|
||||
result.module = m
|
||||
for (mname, msuffix) in result.reexportedModules:
|
||||
let ms = materializeReexportedModule(g, mname, msuffix)
|
||||
if ms != nil:
|
||||
strTableAdd(g.ifaces[fileIdx.int].interf, ms)
|
||||
|
||||
# Rebuild `procInstCache` from this module's generic-instance OFFERS so a
|
||||
# consumer's `genericCacheGet` finds the instance and SKIPS re-running
|
||||
# `instantiateBody` in its own module scope (which lacks symbols visible only
|
||||
# at the generic's definition site — see ast2nif's `(offer …)`).
|
||||
for off in result.genericOffers:
|
||||
g.procInstCache.mgetOrPut(off.generic.itemId, @[]).add PInstantiation(
|
||||
sym: off.inst, concreteTypes: off.concreteTypes,
|
||||
genericParamsCount: off.genericParamsCount, compilesId: 0)
|
||||
|
||||
# Mark module as cached
|
||||
g.cachedMods.incl fileIdx.int
|
||||
g.hookClosure.incl fileIdx.int
|
||||
|
||||
# Register hooks from NIF index with the module graph
|
||||
registerLoadedHooks(g, result.logOps)
|
||||
for x in result.logOps:
|
||||
case x.kind
|
||||
of HookEntry:
|
||||
g.loadedOps[x.op][x.key] = x.sym
|
||||
of ConverterEntry:
|
||||
g.ifaces[fileIdx.int].converters.add x.sym
|
||||
of PureEnumEntry:
|
||||
# rebuild the pure-enum list (source path: `addPureEnum`) so importers can
|
||||
# offer this loaded `{.pure.}` enum's fields as the restricted pure-enum
|
||||
# fallback (`importPureEnumFields`).
|
||||
g.ifaces[fileIdx.int].pureEnums.add x.sym
|
||||
of MethodEntry:
|
||||
discard "todo"
|
||||
of EnumToStrEntry:
|
||||
g.loadedEnumToStringProcs[x.key] = x.sym
|
||||
discard "dispatch buckets already rebuilt by registerLoadedHooks"
|
||||
of GenericInstEntry:
|
||||
raiseAssert "GenericInstEntry should not be in the NIF index"
|
||||
of HookEntry, EnumToStrEntry:
|
||||
discard "already done by registerLoadedHooks"
|
||||
# Register methods per type from NIF index
|
||||
discard "todo"
|
||||
# `nim m` loads only its *direct* imports through this proc, but a hook for
|
||||
# a structural type (e.g. `=destroy` for `seq[PNode]`) lives in the NIF of
|
||||
# whichever module first lifted it — possibly a dependency of a dependency
|
||||
# that the current module never imports directly. Walk the whole import
|
||||
# closure so every serialized hook is visible. (Codegen, `nim nifc`, already
|
||||
# walks the closure in nifbackend.loadModuleDependencies.)
|
||||
if g.config.cmd == cmdM:
|
||||
loadTransitiveHooks(g, result.deps)
|
||||
|
||||
proc configComplete*(g: ModuleGraph) =
|
||||
#rememberStartupConfig(g.startupPackedConfig, g.config)
|
||||
@@ -730,7 +1090,16 @@ proc getPackage*(graph: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
|
||||
proc belongsToStdlib*(graph: ModuleGraph, sym: PSym): bool =
|
||||
## Check if symbol belongs to the 'stdlib' package.
|
||||
sym.getPackageSymbol.getPackageId == graph.systemModule.getPackageId
|
||||
# Compare the package *name* (an interned ident), not the package symbol's
|
||||
# `.id`. Under per-module IC (`nim m`) the system module is loaded from a NIF
|
||||
# in a process that does not compile it from source, so its package symbol is
|
||||
# reconstructed with a fresh `.id` that no longer matches the freshly-interned
|
||||
# package of a stdlib module compiled standalone here — making the old id
|
||||
# comparison wrongly report `false` and inject `--import`ed modules into the
|
||||
# stdlib. Both are canonically named `stdlib` (lib/stdlib.nimble); in a normal
|
||||
# `nim c` build (system compiled from source) the ids match too, so this is a
|
||||
# no-op there.
|
||||
sym.getPackageSymbol.name.id == graph.systemModule.getPackageSymbol.name.id
|
||||
|
||||
proc fileSymbols*(graph: ModuleGraph, fileIdx: FileIndex): SuggestFileSymbolDatabase =
|
||||
result = graph.suggestSymbols.getOrDefault(fileIdx, newSuggestFileSymbolDatabase(fileIdx, optIdeExceptionInlayHints in graph.config.globalOptions))
|
||||
|
||||
@@ -32,7 +32,7 @@ proc newModule*(graph: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
let filename = AbsoluteFile toFullPath(graph.config, fileIdx)
|
||||
# We cannot call ``newSym`` here, because we have to circumvent the ID
|
||||
# mechanism, which we do in order to assign each module a persistent ID.
|
||||
result = PSym(kindImpl: skModule, itemId: ItemId(module: int32(fileIdx), item: 0'i32),
|
||||
result = PSym(kindImpl: skModule, itemId: itemId(int32(fileIdx), 0'i32),
|
||||
name: getModuleIdent(graph, filename),
|
||||
infoImpl: newLineInfo(fileIdx, 1, 1))
|
||||
if not isNimIdentifier(result.name.s):
|
||||
|
||||
@@ -125,12 +125,25 @@ proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
|
||||
var dummy: bool = false
|
||||
result = fileInfoIdx(conf, filename, dummy)
|
||||
|
||||
proc expandOrPseudo(filename: string): AbsoluteFile =
|
||||
# `expandFilename` raises OSError when the path does not exist on disk. That is
|
||||
# fine for a real source path, but a macro can legitimately set a node's
|
||||
# line-info file to a name that has no file behind it — e.g. the `???` sentinel
|
||||
# produced by `toFilename` for a NIF-loaded node whose `fileIndex` is unknown
|
||||
# (FileIndex(-1)). Falling back to the raw name lets the `AbsoluteFile` overload
|
||||
# register it as a pseudo-path (like `command line`/`stdin`) instead of crashing
|
||||
# the whole `nim m` child with an unhandled OSError.
|
||||
try:
|
||||
result = AbsoluteFile expandFilename(filename)
|
||||
except OSError:
|
||||
result = AbsoluteFile filename
|
||||
|
||||
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile; isKnownFile: var bool): FileIndex =
|
||||
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), isKnownFile)
|
||||
fileInfoIdx(conf, expandOrPseudo(filename.string), isKnownFile)
|
||||
|
||||
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile): FileIndex =
|
||||
var dummy: bool = false
|
||||
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), dummy)
|
||||
fileInfoIdx(conf, expandOrPseudo(filename.string), dummy)
|
||||
|
||||
proc registerNifSuffix*(conf: ConfigRef; suffix: string; isKnownFile: var bool): FileIndex =
|
||||
result = conf.m.filenameToIndexTbl.getOrDefault(suffix, InvalidFileIdx)
|
||||
@@ -511,6 +524,9 @@ proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
|
||||
## 1-based index (matches editor line numbers); 1st line is for i.line = 1
|
||||
## last valid line is `numLines` inclusive
|
||||
if i.fileIndex.int32 < 0: return ""
|
||||
# line 0 means "unknown": nodes synthesized from an IC-loaded template or
|
||||
# macro body carry no source position.
|
||||
if i.line.int < 1: return ""
|
||||
let num = numLines(conf, i.fileIndex)
|
||||
# can happen if the error points to EOF:
|
||||
if i.line.int > num: return ""
|
||||
|
||||
@@ -17,18 +17,39 @@
|
||||
## 1. Compile modules to NIF: nim m mymodule.nim
|
||||
## 2. Generate C from NIF: nim nifc myproject.nim
|
||||
|
||||
import std/[intsets, tables, sets, os]
|
||||
import std/[intsets, tables, sets, os, algorithm, syncio, times, strutils]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ast, options, lineinfos, modulegraphs, cgendata, cgen,
|
||||
pathutils, extccomp, msgs, modulepaths, idents, types, ast2nif
|
||||
pathutils, extccomp, msgs, modulepaths, idents, types, ast2nif, typekeys,
|
||||
cnif
|
||||
from cgmeth import generateIfMethodDispatchers
|
||||
import ic / replayer
|
||||
|
||||
proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex): seq[PrecompiledModule] =
|
||||
proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex;
|
||||
nifFiles: var seq[string];
|
||||
depFlags: set[LoadFlag] = {LoadFullAst}): seq[PrecompiledModule] =
|
||||
## Traverse the module dependency graph using a stack.
|
||||
## Returns all modules that need code generation, in dependency order.
|
||||
##
|
||||
## The main module is always loaded with its full AST (it is the codegen
|
||||
## target). `depFlags` governs the rest: the whole-program backend needs every
|
||||
## module's full AST (it generates code for all of them), but a per-module
|
||||
## stage codegens only one target, so it loads the others interface-only
|
||||
## (`depFlags = {}`) — the interface, hooks, methods and the `(replay ...)`
|
||||
## directives are loaded regardless of `LoadFullAst`, and demanded bodies are
|
||||
## fetched lazily from the kept-open stream, so the per-module proc-body ASTs
|
||||
## (the bulk of the memory) are never materialized for non-targets.
|
||||
# The main module is loaded by its SOURCE FileIndex, but its serialized
|
||||
# symbols carry the module's NIF suffix. Pre-alias the suffix to the source
|
||||
# index so that `registerNifSuffix` does not allocate a second FileIndex for
|
||||
# the same module, which would split its codegen across two C translation
|
||||
# units (top-level globals in one, procs in the other → undeclared symbols).
|
||||
g.config.m.filenameToIndexTbl[cachedModuleSuffix(g.config, mainFileIdx)] = mainFileIdx
|
||||
let mainModule = moduleFromNifFile(g, mainFileIdx, {LoadFullAst})
|
||||
nifFiles.add toNifFilename(g.config, mainFileIdx)
|
||||
|
||||
var stack: seq[ModuleSuffix] = @[]
|
||||
result = @[]
|
||||
@@ -46,9 +67,10 @@ proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex): seq[Precomp
|
||||
if not visited.containsOrIncl(suffix.string):
|
||||
var isKnownFile = false
|
||||
let fileIdx = g.config.registerNifSuffix(suffix.string, isKnownFile)
|
||||
let precomp = moduleFromNifFile(g, fileIdx, {LoadFullAst})
|
||||
let precomp = moduleFromNifFile(g, fileIdx, depFlags)
|
||||
if precomp.module != nil:
|
||||
result.add precomp
|
||||
nifFiles.add toNifFilename(g.config, fileIdx)
|
||||
for dep in precomp.deps:
|
||||
if not visited.contains(dep.string):
|
||||
stack.add dep
|
||||
@@ -62,7 +84,13 @@ proc setupNifBackendModule(g: ModuleGraph; module: PSym): BModule =
|
||||
## Set up a BModule for code generation from a NIF module.
|
||||
if g.backend == nil:
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
result = cgen.newModule(BModuleList(g.backend), module, g.config, idGeneratorFromModule(module))
|
||||
result = cgen.newModule(BModuleList(g.backend), module, g.config, idGeneratorForBackend(module))
|
||||
|
||||
proc isMetaIter(t: PType, closure: RootRef): bool =
|
||||
# openArray/varargs hooks are sem bookkeeping: no real flow ever demands
|
||||
# them, and generating one pollutes the TU's type cache with a struct
|
||||
# descriptor for what must remain a (ptr, len) parameter expansion
|
||||
t.kind in tyMetaTypes + {tyTyped, tyUntyped, tyNone, tyVarargs, tyOpenArray}
|
||||
|
||||
proc finishModule(g: ModuleGraph; bmod: BModule) =
|
||||
# Finalize the module (this adds it to modulesClosed)
|
||||
@@ -70,9 +98,52 @@ proc finishModule(g: ModuleGraph; bmod: BModule) =
|
||||
let initStmt = newNode(nkStmtList)
|
||||
finalCodegenActions(g, bmod, initStmt)
|
||||
|
||||
# Generate dispatcher methods
|
||||
# NB: the method dispatchers are emitted in `emitMethodDispatchers`,
|
||||
# between the module loop and this finish loop: their bodies demand the
|
||||
# method definitions, which can in turn demand definitions from modules
|
||||
# the backend never loaded — and a TU demand-created during the LAST
|
||||
# finishModule call would miss `modulesClosed` and never be written.
|
||||
|
||||
proc emitMethodDispatchers(g: ModuleGraph) =
|
||||
## Synthesizes the method dispatcher bodies from the replayed dispatch
|
||||
## buckets (`registerLoadedMethod`) and emits their definitions into the
|
||||
## main TU. Main is regenerated on every run, so a dispatcher — whose
|
||||
## body enumerates the whole program's method set — can never go stale
|
||||
## inside a cached TU; cross-TU callers prototype it (see genProcLvl3).
|
||||
let bl = BModuleList(g.backend)
|
||||
var mainMod: BModule = nil
|
||||
for m in bl.mods:
|
||||
if m != nil and m.module != nil and sfMainModule in m.module.flags:
|
||||
mainMod = m
|
||||
break
|
||||
if mainMod == nil: return
|
||||
generateIfMethodDispatchers(g, mainMod.idgen)
|
||||
for disp in getDispatchers(g):
|
||||
genProcLvl3(bmod, disp)
|
||||
if not containsOrIncl(mainMod.declaredThings, disp.id):
|
||||
genProcLvl3(mainMod, disp)
|
||||
|
||||
proc signatureHasMetaType(t: PType; depth: int = 0): bool =
|
||||
## Whether a routine signature mentions a compile-time/meta element type
|
||||
## (`typed`/`untyped` — e.g. `echo`'s `varargs[typed]` — typedesc, static,
|
||||
## generic param). Such routines are expanded at their call sites and never
|
||||
## emitted standalone, so the per-module owned-routine seeding must skip them
|
||||
## (`getTypeDescAux(tyTyped)` otherwise). `tfHasMeta` alone misses the varargs
|
||||
## element case, hence the explicit scan.
|
||||
result = false
|
||||
if t == nil or depth > 8: return false
|
||||
if t.kind == tyGenericBody:
|
||||
# The uninstantiated template carried as a `tyGenericInst`'s first child
|
||||
# always mentions its `tyGenericParam` placeholders, but the instance
|
||||
# itself is fully concrete (e.g. `var CountTable[SigHash]`). Descending
|
||||
# here would wrongly flag every routine with a generic-instance parameter
|
||||
# as meta and drop it from the owned-routine seeding -> undefined symbols
|
||||
# at link (its only definer never emits it).
|
||||
return false
|
||||
if t.kind in {tyTyped, tyUntyped, tyTypeDesc, tyStatic, tyGenericParam,
|
||||
tyAnything, tyFromExpr, tyError}:
|
||||
return true
|
||||
for k in t.kids:
|
||||
if signatureHasMetaType(k, depth + 1): return true
|
||||
|
||||
proc generateCodeForModule(g: ModuleGraph; precomp: PrecompiledModule) =
|
||||
## Generate C code for a single module.
|
||||
@@ -81,76 +152,366 @@ proc generateCodeForModule(g: ModuleGraph; precomp: PrecompiledModule) =
|
||||
if bmod == nil:
|
||||
bmod = setupNifBackendModule(g, precomp.module)
|
||||
|
||||
# Apply the module's recorded C compile/link directives (passl/passc/...)
|
||||
# before generating code: the link step needs them (e.g. math's -lm).
|
||||
replayBackendActions(g, precomp.module, precomp.topLevel)
|
||||
|
||||
# Generate code for the module's top-level statements
|
||||
if precomp.topLevel != nil:
|
||||
cgen.genTopLevelStmt(bmod, precomp.topLevel)
|
||||
|
||||
proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Main entry point for NIF-based C code generation.
|
||||
## Traverses the module dependency graph and generates C code.
|
||||
# Per-module backend: emit the bodies of the routines this module OWNS, not
|
||||
# only the ones its top-level happens to demand. Procs are serialized as lazy
|
||||
# `(sd ...)` defs (never as `nkProcDef` statements), so `genTopLevelStmt` never
|
||||
# reaches them; a routine called only from *other* modules would otherwise be
|
||||
# emitted by nobody, because every module now merely prototypes its foreign
|
||||
# callees instead of funnelling their bodies (see `cgen.emitsBodyInThisModule`).
|
||||
# The merge stage's DCE drops whatever turns out globally dead.
|
||||
if g.config.cmd == cmdNifC and g.config.icBackendStage == "cg":
|
||||
let modPos = precomp.module.position
|
||||
for s in moduleSymbolStubs(ast.program, FileIndex modPos):
|
||||
if s.itemId.module == modPos and
|
||||
s.kind in {skProc, skFunc, skConverter, skMethod} and
|
||||
# Only MODULE-level routines: a nested/closure proc (its owner is a
|
||||
# proc) captures its enclosing scope and cannot be emitted standalone —
|
||||
# the captured params have no loc → `expr: param not init`. Nested procs
|
||||
# are emitted via their enclosing routine's lambda-lifting, so seeding
|
||||
# the enclosing (module-level) routine already covers them.
|
||||
s.skipGenericOwner != nil and s.skipGenericOwner.kind == skModule and
|
||||
s.magic == mNone and
|
||||
# Skip generic instances: they have no single owning-module top-level
|
||||
# and are emitted by demand (emit-everywhere, deduped by the merge
|
||||
# stage). An instance has an empty `genericParamsPos` just like a plain
|
||||
# concrete proc, so only `sfFromGeneric` tells them apart; seeding one
|
||||
# would force standalone codegen of an instance body whose `when T is X`
|
||||
# branches were never folded for this path → `genMagicExpr: mIs`.
|
||||
sfFromGeneric notin s.flags and
|
||||
# Every other routine the module owns must be emitted here, exported or
|
||||
# not: a non-exported helper is still reached from another module when a
|
||||
# `template`/inline routine expands at a call site there (e.g. msgs'
|
||||
# `internalErrorImpl` behind the `internalError` template), and that
|
||||
# caller now only prototypes it. `{.error.}`/`compileTime` sentinels and
|
||||
# bodyless forward decls are not real codegen targets.
|
||||
{sfForward, sfImportc, sfCompileTime, sfError} * s.flags == {} and
|
||||
s.typ != nil and not signatureHasMetaType(s.typ) and
|
||||
s.ast != nil and s.ast.safeLen > bodyPos and
|
||||
s.ast[genericParamsPos].kind == nkEmpty and
|
||||
s.ast[bodyPos].kind != nkEmpty:
|
||||
# a concrete, non-generic, runtime routine with a real body, owned here
|
||||
requestProcDef(bmod, s)
|
||||
|
||||
# Reset backend state
|
||||
proc loadBackendModules(g: ModuleGraph; mainFileIdx: FileIndex):
|
||||
tuple[modules: seq[PrecompiledModule], precompSys: PrecompiledModule,
|
||||
nifFiles: seq[string]] =
|
||||
## Shared by the per-module `cg` and `emit` stages: load system + the main
|
||||
## module's whole import closure and set up a `BModule` for each, so every
|
||||
## type/symbol resolves and `getCFile` yields the same path both stages use.
|
||||
## The main module is loaded by its source index (its NIF suffix is aliased to
|
||||
## it in `loadModuleDependencies`), so it gets exactly one `BModule`.
|
||||
##
|
||||
## Only the main module — the codegen target of the stages that use this — is
|
||||
## loaded with its full AST; every other module is loaded interface-only so
|
||||
## the whole program's proc bodies are not materialized into this process (that
|
||||
## was ~1.8 GB for the compiler's main `cg`). The `link` stage codegens nothing
|
||||
## and only needs each module's `(replay ...)` directives, which load anyway.
|
||||
resetForBackend(g)
|
||||
|
||||
var isKnownFile = false
|
||||
let systemFileIdx = registerNifSuffix(g.config, "sysma2dyk", isKnownFile)
|
||||
g.config.m.systemFileIdx = systemFileIdx
|
||||
#msgs.fileInfoIdx(g.config,
|
||||
# g.config.libpath / RelativeFile"system.nim")
|
||||
var precompSys = moduleFromNifFile(g, systemFileIdx, {AlwaysLoadInterface})
|
||||
g.systemModule = precompSys.module
|
||||
var nifFiles: seq[string] = @[toNifFilename(g.config, systemFileIdx)]
|
||||
var modules = loadModuleDependencies(g, mainFileIdx, nifFiles, depFlags = {})
|
||||
# loadModuleDependencies traverses the project's import closure and stops at
|
||||
# system. The whole-program backend then demand-loads system's own closure
|
||||
# (locks, allocators, threads, …) during codegen; the per-module backend
|
||||
# instead makes every one of those a first-class cg/emit target, so load that
|
||||
# closure here too — otherwise `findTargetModule` cannot resolve their suffix.
|
||||
block:
|
||||
var visited = initHashSet[string]()
|
||||
visited.incl "sysma2dyk"
|
||||
for m in modules:
|
||||
visited.incl cachedModuleSuffix(g.config, FileIndex m.module.position)
|
||||
var stack: seq[ModuleSuffix] = @[]
|
||||
if precompSys.module != nil:
|
||||
for dep in precompSys.deps: stack.add dep
|
||||
while stack.len > 0:
|
||||
let suffix = stack.pop()
|
||||
if not visited.containsOrIncl(suffix.string):
|
||||
var isKnown = false
|
||||
let fileIdx = registerNifSuffix(g.config, suffix.string, isKnown)
|
||||
let precomp = moduleFromNifFile(g, fileIdx, {})
|
||||
if precomp.module != nil:
|
||||
modules.add precomp
|
||||
nifFiles.add toNifFilename(g.config, fileIdx)
|
||||
for dep in precomp.deps: stack.add dep
|
||||
flushMethodReplays(g)
|
||||
for m in modules:
|
||||
discard setupNifBackendModule(g, m.module)
|
||||
if precompSys.module != nil:
|
||||
discard setupNifBackendModule(g, precompSys.module)
|
||||
result = (modules, precompSys, nifFiles)
|
||||
|
||||
# Load system module first - it's always needed and contains essential hooks
|
||||
var precompSys = PrecompiledModule(module: nil)
|
||||
precompSys = moduleFromNifFile(g, systemFileIdx, {LoadFullAst, AlwaysLoadInterface})
|
||||
proc loadDepClosure(g: ModuleGraph; targetSuffix: string):
|
||||
tuple[modules: seq[PrecompiledModule], precompSys: PrecompiledModule,
|
||||
target: PrecompiledModule] =
|
||||
## Per-module `cg`/`emit` for a NON-main target: load system + the target
|
||||
## module + the target's transitive import closure ONLY — not the whole
|
||||
## program. This is the "process the one file it is passed" model (à la
|
||||
## Nimony's `hexer c file.nif`): the foreign symbols the target's codegen
|
||||
## demands are loaded lazily by `ast2nif.moduleId`, which opens any referenced
|
||||
## module's NIF index on first touch, so a body in a not-loaded module still
|
||||
## resolves. The closure is loaded as full `BModule`s only so that the
|
||||
## incidental `g.mods[pos]` accesses during codegen resolve; system's own
|
||||
## internal closure (allocators, locks, …) is included because a target's
|
||||
## emit-everywhere codegen can demand those without importing them directly.
|
||||
##
|
||||
## The whole program is no longer loaded in this process, which is what bounds
|
||||
## per-process memory under nifmake's parallel fan-out (the main module's `cg`,
|
||||
## which still loads everything for NimMain's init list and the method
|
||||
## dispatchers, runs essentially alone since every other `.c.nif` precedes it).
|
||||
resetForBackend(g)
|
||||
var isKnownFile = false
|
||||
let systemFileIdx = registerNifSuffix(g.config, "sysma2dyk", isKnownFile)
|
||||
g.config.m.systemFileIdx = systemFileIdx
|
||||
let precompSys = moduleFromNifFile(g, systemFileIdx, {AlwaysLoadInterface})
|
||||
g.systemModule = precompSys.module
|
||||
|
||||
# Load all modules in dependency order using stack traversal
|
||||
# This must happen BEFORE any code generation so that hooks are loaded into loadedOps
|
||||
let modules = loadModuleDependencies(g, mainFileIdx)
|
||||
var modules: seq[PrecompiledModule] = @[]
|
||||
var visited = initHashSet[string]()
|
||||
visited.incl "sysma2dyk"
|
||||
|
||||
# Only the target is codegen'd, so only it needs its full AST; the closure is
|
||||
# loaded interface-only (demanded bodies come lazily from the kept-open
|
||||
# streams), which is what keeps a per-module process light under parallel fan-out.
|
||||
var isKnown = false
|
||||
let targetIdx = registerNifSuffix(g.config, targetSuffix, isKnown)
|
||||
let target = moduleFromNifFile(g, targetIdx, {LoadFullAst})
|
||||
visited.incl targetSuffix
|
||||
|
||||
var stack: seq[ModuleSuffix] = @[]
|
||||
if target.module != nil:
|
||||
modules.add target
|
||||
for dep in target.deps: stack.add dep
|
||||
if precompSys.module != nil:
|
||||
for dep in precompSys.deps: stack.add dep
|
||||
while stack.len > 0:
|
||||
let suffix = stack.pop()
|
||||
if not visited.containsOrIncl(suffix.string):
|
||||
var isKnown2 = false
|
||||
let fileIdx = registerNifSuffix(g.config, suffix.string, isKnown2)
|
||||
let precomp = moduleFromNifFile(g, fileIdx, {})
|
||||
if precomp.module != nil:
|
||||
modules.add precomp
|
||||
for dep in precomp.deps: stack.add dep
|
||||
flushMethodReplays(g)
|
||||
for m in modules:
|
||||
discard setupNifBackendModule(g, m.module)
|
||||
if precompSys.module != nil:
|
||||
discard setupNifBackendModule(g, precompSys.module)
|
||||
result = (modules, precompSys, target)
|
||||
|
||||
proc findTargetModule(g: ModuleGraph; modules: seq[PrecompiledModule];
|
||||
precompSys: PrecompiledModule; suffix: string): PrecompiledModule =
|
||||
## The loaded module whose NIF suffix is `suffix` (the `--icBackendModule`
|
||||
## value), or a nil module if none matches.
|
||||
result = PrecompiledModule(module: nil)
|
||||
for m in modules:
|
||||
if cachedModuleSuffix(g.config, FileIndex m.module.position) == suffix:
|
||||
return m
|
||||
if precompSys.module != nil and
|
||||
cachedModuleSuffix(g.config, FileIndex precompSys.module.position) == suffix:
|
||||
return precompSys
|
||||
|
||||
proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Per-module backend codegen (`--icBackendStage:cg --icBackendModule:<suffix>`):
|
||||
## generate C for the single module named by `icBackendModule` and write only
|
||||
## its `.c.nif` artifact (no merge, no `.c` render, no cc/link — those are
|
||||
## separate nifmake rules).
|
||||
##
|
||||
## `findPendingModule` routes every demand into the target (emit-everywhere).
|
||||
##
|
||||
## A NON-main target loads only its own import closure (`loadDepClosure`); the
|
||||
## whole program is no longer pulled into every parallel `cg` process. The main
|
||||
## module still loads everything (`loadBackendModules`) because NimMain's init
|
||||
## list and the method dispatchers are whole-program; its `cg` runs essentially
|
||||
## alone (every other `.c.nif` precedes it), so it does not contend for memory.
|
||||
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
|
||||
let targetIsMain = g.config.icBackendModule.len == 0 or
|
||||
g.config.icBackendModule == mainSuffix
|
||||
var modules: seq[PrecompiledModule]
|
||||
var precompSys: PrecompiledModule
|
||||
var target: PrecompiledModule
|
||||
if targetIsMain:
|
||||
var nifFiles: seq[string]
|
||||
(modules, precompSys, nifFiles) = loadBackendModules(g, mainFileIdx)
|
||||
if modules.len == 0:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
|
||||
return
|
||||
# No whole-program DCE here: each module emits the routines it owns and the
|
||||
# MERGE stage recomputes the one program-wide live set across all `.c.nif`s.
|
||||
# Running a whole-program liveness pass over all ~260 NIFs in the main `cg`
|
||||
# would cost ~900 MB for a result the merge stage throws away.
|
||||
target = findTargetModule(g, modules, precompSys, g.config.icBackendModule)
|
||||
else:
|
||||
# No whole-program load, hence no whole-program DCE: the target emits its
|
||||
# full demanded closure and the merge stage drops what is globally dead.
|
||||
(modules, precompSys, target) = loadDepClosure(g, g.config.icBackendModule)
|
||||
if target.module == nil:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module codegen: module not found for suffix: " & g.config.icBackendModule)
|
||||
return
|
||||
|
||||
generateCodeForModule(g, target)
|
||||
let bl = BModuleList(g.backend)
|
||||
# The main module also owns the whole-program method dispatchers + NimMain.
|
||||
if sfMainModule in target.module.flags:
|
||||
emitMethodDispatchers(g)
|
||||
# NimMain (generated when the main module is finished) must call every other
|
||||
# module's init/datInit. Those translation units are produced by their own
|
||||
# `cg` processes, so the calls are registered here from each `.c.nif` meta
|
||||
# head — which is why the main module's `cg` runs last, after every other
|
||||
# `.c.nif` exists. Modules without init code (no `.c.nif`) register nothing.
|
||||
for m in bl.mods:
|
||||
if m != nil and sfMainModule notin m.module.flags:
|
||||
let heads = readCnifHeads(getCFile(m).string & ".nif")
|
||||
registerReusedModuleToMain(bl, m, heads.initRequired, heads.datInitRequired)
|
||||
let tb = bl.mods[target.module.position]
|
||||
if tb != nil:
|
||||
finishModule(g, tb)
|
||||
|
||||
# Writes only the target's `.c.nif` (every other loaded module's TU is empty,
|
||||
# so `cgenWriteModules` emits no artifact for it). cc/link are NOT run here.
|
||||
cgenWriteModules(g.backend, g.config)
|
||||
|
||||
# Always leave a `.c.nif` for the target, even when the module has no code
|
||||
# (a leaf library whose procs all emit into their users): the per-module
|
||||
# nifmake graph declares one `.c.nif` output per `cg` rule, so a missing one
|
||||
# would re-fire the rule forever. An empty artifact renders to an empty `.c`.
|
||||
if tb != nil:
|
||||
let artifact = getCFile(tb).string & ".nif"
|
||||
if not fileExists(artifact):
|
||||
writeCnifArtifact("", artifact,
|
||||
semmedNif = toNifFilename(g.config, FileIndex target.module.position),
|
||||
moduleBase = $getSomeNameForModule(tb))
|
||||
|
||||
proc generateMergeStage(g: ModuleGraph) =
|
||||
## Per-module backend merge (`--icBackendStage:merge`): a pure artifact
|
||||
## operation, no module graph loaded. Reads every `.c.nif` the `cg` stages
|
||||
## wrote, computes the global live set and — for each `'u'`-flagged unique
|
||||
## definition that several `cg` processes emitted (emit-everywhere) — the one
|
||||
## artifact allowed to embed its body, and writes the decision the `emit`
|
||||
## stages consume — the cross-process replacement for what used to be
|
||||
## in-process first-claimant/DCE coordination.
|
||||
let nimcache = getNimcacheDir(g.config).string
|
||||
var files: seq[string] = @[]
|
||||
for artifact in walkFiles(nimcache / "*.c.nif"):
|
||||
files.add artifact
|
||||
sort files
|
||||
let decision = computeMergeDecision(files)
|
||||
if decision.broken:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module backend merge: a .c.nif artifact is missing or unparsable")
|
||||
return
|
||||
writeMergeDecision(nimcache / MergeDecisionFile, decision)
|
||||
if isDefined(g.config, "icDceCheck"):
|
||||
stderr.writeLine "[icMerge] artifacts: " & $files.len &
|
||||
" live: " & $decision.live.len & " defs: " & $decision.defs &
|
||||
" liveDefs: " & $decision.liveDefs & " owned: " & $decision.owners.len
|
||||
|
||||
proc generateEmitStage(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Per-module backend emit (`--icBackendStage:emit --icBackendModule:<suffix>`):
|
||||
## render the target module's final `.c` from its `.c.nif` and the merge
|
||||
## decision. Loads the target the same way `cg` does so `getCFile` returns the
|
||||
## identical path `cg` wrote to (the main module's source-vs-suffix aliasing in
|
||||
## particular); no codegen runs. A non-main target loads only its own closure
|
||||
## (`loadDepClosure`) so emit, like `cg`, stays bounded under parallel fan-out.
|
||||
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
|
||||
let targetIsMain = g.config.icBackendModule.len == 0 or
|
||||
g.config.icBackendModule == mainSuffix
|
||||
var modules: seq[PrecompiledModule]
|
||||
var precompSys: PrecompiledModule
|
||||
var target: PrecompiledModule
|
||||
if targetIsMain:
|
||||
var nifFiles: seq[string]
|
||||
(modules, precompSys, nifFiles) = loadBackendModules(g, mainFileIdx)
|
||||
if modules.len == 0:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
|
||||
return
|
||||
target = findTargetModule(g, modules, precompSys, g.config.icBackendModule)
|
||||
else:
|
||||
(modules, precompSys, target) = loadDepClosure(g, g.config.icBackendModule)
|
||||
if target.module == nil:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module emit: module not found for suffix: " & g.config.icBackendModule)
|
||||
return
|
||||
let decision = readMergeDecision(getNimcacheDir(g.config).string / MergeDecisionFile)
|
||||
if decision.broken:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module emit: missing or unparsable merge decision " & MergeDecisionFile)
|
||||
return
|
||||
let bmod = BModuleList(g.backend).mods[target.module.position]
|
||||
let cfile = getCFile(bmod).string
|
||||
let artifact = cfile & ".nif"
|
||||
var dropped = 0
|
||||
let code = renderCFromArtifact(artifact, decision, extractFilename(artifact), dropped)
|
||||
writeFile(cfile, code)
|
||||
if isDefined(g.config, "icDceCheck"):
|
||||
stderr.writeLine "[icEmit] " & extractFilename(cfile) & " dropped " &
|
||||
$dropped & " bodies (" & $code.len & " bytes)"
|
||||
|
||||
proc generateLinkStage(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Per-module backend link (`--icBackendStage:link`): the `emit` stages have
|
||||
## written every module's `.c`; register them and run the C compiler + linker
|
||||
## once via `extccomp.callCCompiler` (which parallelizes the per-file cc and
|
||||
## skips up-to-date objects itself). No codegen runs — the graph is loaded only
|
||||
## so `getCFile` yields each module's emitted `.c` path.
|
||||
let (modules, precompSys, _) = loadBackendModules(g, mainFileIdx)
|
||||
if modules.len == 0:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
|
||||
return
|
||||
|
||||
# Set up backend modules for all modules that need code generation
|
||||
# The per-module `cg` processes each collect their module's C compile/link
|
||||
# directives (`{.passL: "-lm".}` etc.) via `replayBackendActions`, but those
|
||||
# live in the cg process and never reach this separate link process. Re-collect
|
||||
# every loaded module's directives here so the final `callCCompiler` sees them
|
||||
# (without this, math's `-lm` is lost → undefined `floor`/`pow`/… at link).
|
||||
for m in modules:
|
||||
discard setupNifBackendModule(g, m.module)
|
||||
|
||||
# Also ensure system module is set up and generated first if it exists
|
||||
replayBackendActions(g, m.module, m.topLevel)
|
||||
if precompSys.module != nil:
|
||||
discard setupNifBackendModule(g, precompSys.module)
|
||||
generateCodeForModule(g, precompSys)
|
||||
|
||||
# Track which modules have been processed to avoid duplicates
|
||||
var processed = initIntSet()
|
||||
if precompSys.module != nil:
|
||||
processed.incl precompSys.module.position
|
||||
|
||||
# Generate code for all modules (skip system since it's already processed)
|
||||
for m in modules:
|
||||
if not processed.containsOrIncl(m.module.position):
|
||||
generateCodeForModule(g, m)
|
||||
|
||||
# during code generation of `main.nim` we can trigger the code generation
|
||||
# of symbols in different modules so we need to finish these modules
|
||||
# here later, after the above loop!
|
||||
# Important: The main module must be finished LAST so that all other modules
|
||||
# have registered their init procs before genMainProc uses them.
|
||||
var mainModule: BModule = nil
|
||||
for m in BModuleList(g.backend).mods:
|
||||
replayBackendActions(g, precompSys.module, precompSys.topLevel)
|
||||
let bl = BModuleList(g.backend)
|
||||
for m in bl.mods:
|
||||
if m != nil:
|
||||
assert m.module != nil
|
||||
if sfMainModule in m.module.flags:
|
||||
mainModule = m
|
||||
else:
|
||||
finishModule g, m
|
||||
if mainModule != nil:
|
||||
finishModule g, mainModule
|
||||
|
||||
# Write C files
|
||||
cgenWriteModules(g.backend, g.config)
|
||||
|
||||
# Run C compiler
|
||||
let cfile = getCFile(m)
|
||||
# Only modules that are their own cg/emit target produced a `.c`; the rest
|
||||
# (extra members of system's closure that no build rule targets) had their
|
||||
# code emit-everywhere'd into the targets, so they have no file to compile.
|
||||
if not fileExists(cfile.string): continue
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
obj: completeCfilePath(g.config, toObjFile(g.config, cfile)),
|
||||
flags: {})
|
||||
addFileToCompile(g.config, cf)
|
||||
if g.config.cmd != cmdTcc:
|
||||
extccomp.callCCompiler(g.config)
|
||||
if not g.config.hcrOn:
|
||||
extccomp.writeJsonBuildInstructions(g.config, g.cachedFiles)
|
||||
|
||||
proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Main entry point for NIF-based C code generation.
|
||||
## Traverses the module dependency graph and generates C code.
|
||||
if g.config.icBackendStage == "cg":
|
||||
generateCgStage(g, mainFileIdx)
|
||||
return
|
||||
elif g.config.icBackendStage == "merge":
|
||||
generateMergeStage(g)
|
||||
return
|
||||
elif g.config.icBackendStage == "emit":
|
||||
generateEmitStage(g, mainFileIdx)
|
||||
return
|
||||
elif g.config.icBackendStage == "link":
|
||||
generateLinkStage(g, mainFileIdx)
|
||||
return
|
||||
else:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"the per-module NIF backend requires --icBackendStage:cg|merge|emit|link")
|
||||
|
||||
@@ -183,12 +183,6 @@ func `<`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
func `<=`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
a.int16 <= b.int16
|
||||
|
||||
func `>`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
a.int16 > b.int16
|
||||
|
||||
func `>=`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
a.int16 >= b.int16
|
||||
|
||||
func `==`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
a.int16 == b.int16
|
||||
|
||||
|
||||
@@ -28,10 +28,13 @@ import
|
||||
commands, options, msgs, extccomp, main, idents, lineinfos, cmdlinehelper,
|
||||
pathutils, modulegraphs
|
||||
|
||||
from ast2nif import registerNifAstTags
|
||||
from icconfig import ensureIcConfig
|
||||
|
||||
from std/browsers import openDefaultBrowser
|
||||
from nodejs import findNodeJs
|
||||
|
||||
when hasTinyCBackend:
|
||||
when defined(tinyc): # == hasTinyCBackend; spelled out for the IC dep scanner
|
||||
import tccgen
|
||||
|
||||
when defined(profiler) or defined(memProfiler):
|
||||
@@ -96,6 +99,11 @@ proc getNimRunExe(conf: ConfigRef): string =
|
||||
result = ""
|
||||
|
||||
proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
# NIF tag registration must not depend on module init order — the IC-built
|
||||
# compiler orders module init calls differently and the top-level
|
||||
# `registerTag` initializers then ran against a not-yet-initialized pool,
|
||||
# corrupting every written NIF (see registerNifAstTags).
|
||||
registerNifAstTags()
|
||||
let self = NimProg(
|
||||
supportsStdinFile: true,
|
||||
processCmdLine: processCmdLine
|
||||
@@ -107,6 +115,14 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
|
||||
self.processCmdLineAndProjectPath(conf)
|
||||
|
||||
# `nim ic` driver: ensure the precompiled config exists (produced by a separate
|
||||
# `nim icconfig` process, skipped when nothing changed) BEFORE config loading,
|
||||
# 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 == cmdIc:
|
||||
ensureIcConfig(conf)
|
||||
|
||||
var graph = newModuleGraph(cache, conf)
|
||||
if not self.loadConfigsAndProcessCmdLine(cache, conf, graph):
|
||||
return
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import
|
||||
llstream, commands, msgs, lexer, ast,
|
||||
options, idents, wordrecg, lineinfos, pathutils, scriptconfig
|
||||
options, idents, wordrecg, lineinfos, pathutils, scriptconfig, icconfig
|
||||
|
||||
import std/[os, strutils, strtabs]
|
||||
|
||||
@@ -246,6 +246,16 @@ proc getSystemConfigPath*(conf: ConfigRef; filename: RelativeFile): AbsoluteFile
|
||||
|
||||
proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef; idgen: IdGenerator) =
|
||||
setDefaultLibpath(conf)
|
||||
# The `nim ic` driver and its `nim m`/`nim nifc` children replay the precompiled
|
||||
# config (produced once by a separate `nim icconfig` process — see
|
||||
# `icconfig.ensureIcConfig`, which sets `icPreparsedConfig` for the driver
|
||||
# before this runs; the children get it as a forwarded `--icPreparsedConfig`
|
||||
# argument) instead of re-reading the `nim.cfg` chain and re-running
|
||||
# `config.nims` in the VM. A missing/format-incompatible artifact returns false:
|
||||
# fall through to a normal parse (this is also the path the `nim icconfig`
|
||||
# producer itself takes, since it runs with no `icPreparsedConfig`).
|
||||
if conf.icPreparsedConfig.len > 0 and applyIcConfig(conf, conf.icPreparsedConfig):
|
||||
return
|
||||
template readConfigFile(path) =
|
||||
let configPath = path
|
||||
conf.currentConfigDir = configPath.splitFile.dir.string
|
||||
|
||||
@@ -29,6 +29,32 @@ const
|
||||
|
||||
nimEnableCovariance* = defined(nimEnableCovariance)
|
||||
|
||||
icFormatVersion* = "6"
|
||||
## 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`
|
||||
## stamp differs, instead of letting a newer reader mis-parse records
|
||||
## written by an older compiler (nifmake's rebuild check is mtime-only
|
||||
## and knows nothing about format changes).
|
||||
## v2: iface cookie hashes routine SIGNATURES only (no inline-semantics
|
||||
## body folding); body access now records a NeedsImpl edge instead. A v1
|
||||
## cache mixes body-sensitive and body-insensitive cookies, so it must be
|
||||
## wiped rather than warm-rebuilt.
|
||||
## v3: added the `.s.deps` sidecar (real post-sem imports) and switched the
|
||||
## macro-generated-import discovery from `icmissing.txt` to it.
|
||||
## v4: backend C-name scheme change — the module suffix is now the trailing
|
||||
## token (`name_u<disamb>__<suffix>`, was `name__<suffix>_u<disamb>`), so
|
||||
## cached `.c.nif` artifacts hold incompatible names and must be wiped.
|
||||
## v5: data definitions (consts, RTTI) are now wrapped in droppable `'d'`
|
||||
## cdef directives with an always-present extern declaration, so the
|
||||
## per-module merge stage can assign them a single owner; old `.c.nif`
|
||||
## artifacts lack the wrappers.
|
||||
## v6: `signatureHash`/`hashType` of a builtin type class (`object`, `tuple`,
|
||||
## `proc`, ...) no longer mixes in the placeholder son's process-local type
|
||||
## id, so its hash is stable across the NIF boundary (was breaking
|
||||
## nim-serialization's auto-serialization lookup under IC). The sem-NIF
|
||||
## macrocache entries and baked generic-instance bodies hold the old hashes.
|
||||
|
||||
type # please make sure we have under 32 options
|
||||
# (improves code efficiency a lot!)
|
||||
TOption* = enum # **keep binary compatible**
|
||||
@@ -179,6 +205,7 @@ type
|
||||
cmdCompileToNif
|
||||
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)
|
||||
|
||||
const
|
||||
cmdBackends* = {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
|
||||
@@ -262,6 +289,11 @@ type
|
||||
procParamTypeBackendAliases
|
||||
## Keep the old proc type compatibility rules that ignore backend
|
||||
## c type aliases.
|
||||
injectedSymbolRedefinition
|
||||
## Allow a template to inject a symbol *definition* that is then emitted
|
||||
## more than once (e.g. a `typed` argument captured by a `{.dirty.}`
|
||||
## template and re-emitted). This is a redefinition and rejected by
|
||||
## default; enabling this restores the old, unsound behavior. See #25693.
|
||||
|
||||
SymbolFilesOption* = enum
|
||||
disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest
|
||||
@@ -380,6 +412,48 @@ type
|
||||
lastCmdTime*: float # when caas is enabled, we measure each command
|
||||
symbolFiles*: SymbolFilesOption
|
||||
ic*: bool # whether ic is enabled
|
||||
icGroup*: HashSet[string] # under `nim m`: absolute paths of the modules in
|
||||
# this strongly-connected import group. They are all
|
||||
# compiled from source in one process (so mutual
|
||||
# recursion resolves in-memory) and each gets its NIF
|
||||
# written, instead of being loaded from a precompiled
|
||||
# NIF. See `compiler/deps.nim` (SCC grouping).
|
||||
icProject*: string # under `nim m`/`nim nifc`: absolute path of the
|
||||
# ORIGINAL project file. The child's own project file
|
||||
# is the module being compiled, which would make that
|
||||
# module's package the "main package" and unfilter
|
||||
# foreign-package diagnostics; the real project
|
||||
# restores whole-program filtering semantics.
|
||||
icPreparsedConfig*: string # under the `nim ic` driver and its `nim m`/`nim nifc`
|
||||
# children: path of the precompiled config artifact.
|
||||
# When set, `loadConfigs` replays the recorded
|
||||
# config-file switches from it instead of re-reading
|
||||
# the `nim.cfg` chain and re-running `config.nims`
|
||||
# (which the VM makes expensive) per process. The
|
||||
# artifact itself is produced by a separate
|
||||
# `nim icconfig` process (see `cmdIcConfig`).
|
||||
icConfigOut*: string # under `nim icconfig`: the path to write the
|
||||
# precompiled config artifact to (set via `--o`).
|
||||
icConfigSwitches*: seq[tuple[switch, arg: string]]
|
||||
# the config-file (`passPP`) switches applied while
|
||||
# loading config, in order. Recorded by every nim
|
||||
# process; only the `ic` driver serialises them.
|
||||
# Path-search switches are excluded — the driver
|
||||
# forwards the resolved `searchPaths` as `--path`.
|
||||
icBackendStage*: string # under `nim nifc`: which stage of the per-module
|
||||
# backend this invocation runs — "cg" (codegen one
|
||||
# module to its `.c.nif`), "merge" (global liveness
|
||||
# + owner assignment across all `.c.nif`), "emit"
|
||||
# (render one module's `.c` from its `.c.nif` + the
|
||||
# merge decision), "link" (cc + link every emitted
|
||||
# `.c`). Empty = whole-program backend (load all,
|
||||
# codegen+DCE+cc+link in one process). The stages
|
||||
# are wired as nifmake rules by `deps.nim`'s backend
|
||||
# build file. See `compiler/nifbackend.nim`.
|
||||
icBackendModule*: string # under `nim nifc` with icBackendStage in {cg,emit}:
|
||||
# the NIF module suffix this invocation codegens or
|
||||
# emits. The other modules are loaded only so types
|
||||
# resolve; their definitions are referenced extern.
|
||||
spellSuggestMax*: int # max number of spelling suggestions for typos
|
||||
|
||||
cppDefines*: HashSet[string] # (*)
|
||||
@@ -426,6 +500,12 @@ type
|
||||
lastMsgWasDot*: set[StdOrrKind] # the last compiler message was a single '.'
|
||||
projectMainIdx*: FileIndex # the canonical path id of the main module
|
||||
projectMainIdx2*: FileIndex # consider merging with projectMainIdx
|
||||
isMainModule*: bool # `nim m`/IC only: whether the single module being
|
||||
# semantically checked is the program's real entry point.
|
||||
# Under IC every module is compiled via `nim m` (which sets
|
||||
# `sfMainModule` so the module writes its own NIF), so
|
||||
# `sfMainModule` can no longer answer `isMainModule`. The IC
|
||||
# build file passes `--isMainModule:on` for the root module.
|
||||
command*: string # the main command (e.g. cc, check, scan, etc)
|
||||
commandArgs*: seq[string] # any arguments after the main command
|
||||
commandLine*: string
|
||||
@@ -582,6 +662,7 @@ proc newConfigRef*(): ConfigRef =
|
||||
arcToExpand: newStringTable(modeStyleInsensitive),
|
||||
m: initMsgConfig(),
|
||||
cppDefines: initHashSet[string](),
|
||||
icGroup: initHashSet[string](),
|
||||
headerFile: "", features: {}, legacyFeatures: {},
|
||||
configVars: newStringTable(modeStyleInsensitive),
|
||||
symbols: newStringTable(modeStyleInsensitive),
|
||||
@@ -657,6 +738,7 @@ proc isDefined*(conf: ConfigRef; symbol: string): bool =
|
||||
of "x86": result = conf.target.targetCPU == cpuI386
|
||||
of "itanium": result = conf.target.targetCPU == cpuIa64
|
||||
of "x8664": result = conf.target.targetCPU == cpuAmd64
|
||||
of "wasm": result = conf.target.targetCPU in {cpuWasm32, cpuWasm64}
|
||||
of "posix", "unix":
|
||||
result = conf.target.targetOS in {osLinux, osMorphos, osSkyos, osIrix, osPalmos,
|
||||
osQnx, osAtari, osAix,
|
||||
|
||||
@@ -54,7 +54,10 @@ import
|
||||
|
||||
when not defined(nimCustomAst):
|
||||
import ast
|
||||
else:
|
||||
when defined(nimCustomAst):
|
||||
# NOTE: explicit negated `when` rather than `else:` — nifler's dep scanner
|
||||
# guards `when`/`elif` imports with their condition but emits `else:` imports
|
||||
# unconditionally, which would wrongly schedule this module under `nim ic`.
|
||||
import plugins / customast
|
||||
|
||||
import std/strutils
|
||||
@@ -2241,14 +2244,17 @@ proc parseTypeClassParam(p: var Parser): PNode =
|
||||
|
||||
proc parseTypeClass(p: var Parser): PNode =
|
||||
#| conceptParam = ('var' | 'out' | 'ptr' | 'ref' | 'static' | 'type')? symbol
|
||||
#| conceptDecl = 'concept' conceptParam ^* ',' (pragma)? ('of' typeDesc ^* ',')?
|
||||
#| conceptDecl = 'concept' (conceptParam ^* ',' (pragma)?)? ('of' typeDesc ^* ',')?
|
||||
#| &IND{>} stmt
|
||||
result = newNodeP(nkTypeClassTy, p)
|
||||
getTok(p)
|
||||
if p.tok.tokType == tkComment:
|
||||
skipComment(p, result)
|
||||
|
||||
if p.tok.indent < 0:
|
||||
if p.tok.tokType == tkOf and p.tok.indent < 0:
|
||||
# new-styled `concept of A, B` on the same line as `concept`
|
||||
result.add(p.emptyNode)
|
||||
elif p.tok.indent < 0:
|
||||
var args = newNodeP(nkArgList, p)
|
||||
result.add(args)
|
||||
args.add(p.parseTypeClassParam)
|
||||
@@ -2274,9 +2280,10 @@ proc parseTypeClass(p: var Parser): PNode =
|
||||
result.add(p.emptyNode)
|
||||
if p.tok.tokType == tkComment:
|
||||
skipComment(p, result)
|
||||
# an initial IND{>} HAS to follow:
|
||||
# an initial IND{>} HAS to follow, unless this concept inherits requirements:
|
||||
if not realInd(p):
|
||||
if result.isNewStyleConcept:
|
||||
let hasParents = result[2].kind != nkEmpty
|
||||
if result.isNewStyleConcept and not hasParents:
|
||||
parMessage(p, "routine expected, but found '$1' (empty new-styled concepts are not allowed)", p.tok)
|
||||
result.add(p.emptyNode)
|
||||
else:
|
||||
|
||||
@@ -15,7 +15,7 @@ import ../dist/checksums/src/checksums/sha1
|
||||
when not defined(leanCompiler):
|
||||
import jsgen, docgen2
|
||||
|
||||
import std/[syncio, objectdollar, assertions, tables, strutils, strtabs]
|
||||
import std/[syncio, objectdollar, assertions, tables, strutils, strtabs, sets, intsets]
|
||||
import renderer
|
||||
import ic/replayer
|
||||
|
||||
@@ -243,9 +243,14 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
# For cmdM: only write NIF for the main module, not for imported modules
|
||||
# (imported modules should be loaded from existing NIF files)
|
||||
# (imported modules should be loaded from existing NIF files). Members of the
|
||||
# current strongly-connected import group (`--icGroup`) are the exception:
|
||||
# they are compiled from source here, so each must write its own NIF.
|
||||
let shouldWriteNif = (optCompress in graph.config.globalOptions) or
|
||||
(graph.config.cmd == cmdM and sfMainModule in module.flags)
|
||||
(graph.config.cmd == cmdM and
|
||||
(sfMainModule in module.flags or
|
||||
(graph.config.icGroup.len > 0 and
|
||||
toFullPath(graph.config, module.position.FileIndex) in graph.config.icGroup)))
|
||||
if shouldWriteNif and not graph.config.isDefined("nimscript"):
|
||||
topLevelStmts.add finalNode
|
||||
# Collect replay actions from both pragma computations and VM state diff
|
||||
@@ -259,16 +264,99 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
if m == module:
|
||||
replayActions.add n
|
||||
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog, replayActions)
|
||||
# NeedsImpl edge recording: which modules' bodies this process consumed
|
||||
# at compile time (VM/getImpl). For an --icGroup cycle every member gets
|
||||
# the union; intra-group entries are filtered by the writer.
|
||||
var implDeps: seq[int] = @[]
|
||||
for id in graph.icImplDeps: implDeps.add id
|
||||
# Generic-instance OFFERS: every instance THIS module created, so a
|
||||
# consumer reuses it rather than re-instantiating in its own scope (which
|
||||
# cannot see symbols visible only at the generic's definition site — e.g.
|
||||
# a distinct type's `==`). See ast2nif.writeNifModule / moduleFromNifFile.
|
||||
var genericOffers: seq[tuple[generic, inst: PSym;
|
||||
concreteTypes: seq[PType]; genericParamsCount: int]] = @[]
|
||||
for genItemId, instList in graph.procInstCache:
|
||||
for inst in instList:
|
||||
if inst.sym != nil and inst.sym.itemId.module == module.position and
|
||||
inst.sym.instantiatedFrom != nil and inst.compilesId == 0:
|
||||
# `concreteTypes` is pre-sized to `paramsLen+gp.len`; a tail slot can
|
||||
# stay nil (e.g. fewer materialized params than `paramsLen`). Such an
|
||||
# offer can't be serialized — skip it (the consumer re-instantiates,
|
||||
# the prior behaviour) rather than emit a nil type reference.
|
||||
var hasNil = false
|
||||
for ct in inst.concreteTypes:
|
||||
if ct == nil: hasNil = true; break
|
||||
if not hasNil:
|
||||
genericOffers.add (inst.sym.instantiatedFrom, inst.sym,
|
||||
inst.concreteTypes, inst.genericParamsCount)
|
||||
# The module's REAL resolved direct imports (incl. macro/template-generated
|
||||
# ones with no surviving syntactic node). Passed to writeNifModule so the
|
||||
# NIF `deps` section is complete (the backend closure walk needs it), and
|
||||
# reused below for the `.s.deps` sidecar (frontend graph re-derivation).
|
||||
let resolvedImportDeps = graph.importDeps.getOrDefault(module.position.FileIndex, @[])
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog,
|
||||
replayActions, implDeps, reexportedModuleSyms(graph, module),
|
||||
genericOffers, resolvedImportDeps)
|
||||
# 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] = @[]
|
||||
for f in resolvedImportDeps:
|
||||
semDepPaths.add toFullPath(graph.config, f)
|
||||
writeSemDeps(graph.config, module.position.int32, semDepPaths)
|
||||
|
||||
result = true
|
||||
|
||||
proc loadedDefSym(defs: PNode): PSym =
|
||||
## The defined symbol of a let/var entry as it loads back from a NIF: the
|
||||
## section child is a bare `nkSym` (the `(sd …)` reference), but be defensive
|
||||
## about the from-source shapes too (`nkIdentDefs`, a pragma-wrapped name).
|
||||
case defs.kind
|
||||
of nkSym: result = defs.sym
|
||||
of nkPragmaExpr:
|
||||
result = if defs.len > 0: loadedDefSym(defs[0]) else: nil
|
||||
of nkIdentDefs, nkConstDef:
|
||||
result = if defs.len > 0: loadedDefSym(defs[0]) else: nil
|
||||
else: result = nil
|
||||
|
||||
proc initLoadedCompileTimeGlobals(graph: ModuleGraph; module: PSym; topLevel: PNode) =
|
||||
## Eagerly initialize the compile-time globals (`let/var {.compileTime.}`) of a
|
||||
## module restored from a NIF. In a normal sem these VM slots are filled by
|
||||
## `setupCompileTimeVar` (semstmts) as the section is semchecked; a NIF-loaded
|
||||
## module is never semchecked, so without this a macro or compile-time proc that
|
||||
## reads such a global finds a nil slot. The lazy `vmgen.genGlobalInit` fallback
|
||||
## is order-fragile across proc boundaries (it emits the init at the first
|
||||
## VM-gen'd reference, which need not be the first one executed), so the init has
|
||||
## to happen here, once, before any of the module's code can run. The symbol's
|
||||
## own `ast` is the `nkIdentDefs` (initializer included); re-wrap it in a section
|
||||
## exactly as semstmts does and hand it to the same evaluator.
|
||||
if topLevel == nil: return
|
||||
let idgen = idGeneratorFromModule(module)
|
||||
for stmt in topLevel:
|
||||
if stmt.kind notin {nkLetSection, nkVarSection}: continue
|
||||
for defs in stmt:
|
||||
let s = loadedDefSym(defs)
|
||||
if s != nil and s.kind in {skLet, skVar} and
|
||||
{sfCompileTime, sfGlobal} <= s.flags and
|
||||
s.ast != nil and s.ast.kind == nkIdentDefs:
|
||||
var sect = newNodeI(stmt.kind, s.info)
|
||||
sect.add s.ast
|
||||
setupCompileTimeVar(module, idgen, graph, sect)
|
||||
|
||||
proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags; fromModule: PSym = nil): PSym =
|
||||
var flags = flags
|
||||
if fileIdx == graph.config.projectMainIdx2: flags.incl sfMainModule
|
||||
result = graph.getModule(fileIdx)
|
||||
|
||||
template processModuleAux(moduleStatus) =
|
||||
when defined(icDbg):
|
||||
block:
|
||||
let dbgf = open("/tmp/defdbg.txt", fmAppend)
|
||||
dbgf.writeLine toFullPath(graph.config, fileIdx) &
|
||||
" nimStackTraceOverride=" & $isDefined(graph.config, "nimStackTraceOverride") &
|
||||
" nimscript=" & $isDefined(graph.config, "nimscript") &
|
||||
" optCompress=" & $(optCompress in graph.config.globalOptions) &
|
||||
" cmd=" & $graph.config.cmd
|
||||
dbgf.close()
|
||||
onProcessing(graph, fileIdx, moduleStatus, fromModule = fromModule)
|
||||
var s: PLLStream = nil
|
||||
if sfMainModule in flags:
|
||||
@@ -278,14 +366,34 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
if result == nil:
|
||||
when not defined(nimKochBootstrap):
|
||||
# For cmdM: load imports from NIF files (but compile the main module from source)
|
||||
# Skip when withinSystem is true (compiling system.nim itself)
|
||||
# Skip when withinSystem is true (compiling system.nim itself).
|
||||
# Also skip for members of the current strongly-connected import group
|
||||
# (`--icGroup`): those are mutually recursive with the main module and have
|
||||
# no precompiled NIF yet, so they must be compiled from source in this same
|
||||
# process (falling through below) — that resolves the cycle in-memory, the
|
||||
# same way the non-incremental compiler handles recursive module imports.
|
||||
if graph.config.cmd == cmdM and
|
||||
sfMainModule notin flags and
|
||||
not graph.withinSystem and
|
||||
not graph.config.isDefined("nimscript"):
|
||||
not graph.config.isDefined("nimscript") and
|
||||
(graph.config.icGroup.len == 0 or
|
||||
toFullPath(graph.config, fileIdx) notin graph.config.icGroup):
|
||||
let precomp = moduleFromNifFile(graph, fileIdx)
|
||||
if precomp.module == nil:
|
||||
let nifPath = toNifFilename(graph.config, fileIdx)
|
||||
# Macro-generated imports (e.g. chronicles' parseStmt("import
|
||||
# chronicles/textlines") driven by the chronicles_sinks define) are
|
||||
# invisible to the static scanner, so this module's NIF was never
|
||||
# built. The importer already recorded this import via
|
||||
# addImportFileDep, so flush every module's `.s.deps`: `nim ic` reads
|
||||
# it, re-derives the graph with the missing node + edge, and reruns
|
||||
# the frontend. We still error — this process cannot finish sem
|
||||
# without the import — but the discovery is structured data now, not
|
||||
# a side-channel file.
|
||||
for importer, deps in graph.importDeps.pairs:
|
||||
var paths: seq[string] = @[]
|
||||
for f in deps: paths.add toFullPath(graph.config, f)
|
||||
writeSemDeps(graph.config, importer.int32, paths)
|
||||
globalError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for import: " & toFullPath(graph.config, fileIdx) &
|
||||
" (expected: " & nifPath & ")")
|
||||
@@ -296,9 +404,33 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
if sfSystemModule in flags:
|
||||
graph.systemModule = result
|
||||
partialInitModule(result, graph, fileIdx, AbsoluteFile(toFullPath(graph.config, fileIdx)))
|
||||
# Replay state changes from the loaded NIF module
|
||||
if result.ast != nil:
|
||||
replayStateChanges(result, graph)
|
||||
# Replay the module's recorded state changes: macro-cache operations
|
||||
# (std/macrocache puts/incs/adds/incls) plus a few pragmas. The loader
|
||||
# parsed them into `precomp.topLevel` (mixed with other top-level nodes),
|
||||
# so filter to the replay actions. A loaded module's `ast` is never
|
||||
# rebuilt, so this used to be skipped (`result.ast == nil`) and a
|
||||
# NIF-loaded module's macro cache was lost — e.g. nim-serialization's
|
||||
# flavor registration became invisible to dependents (`DefaultFlavor:
|
||||
# automatic serialization is not enabled`).
|
||||
var replayList = newNodeI(nkStmtList, result.info)
|
||||
for n in precomp.topLevel:
|
||||
# Only macro-cache ops (put/inc/add/incl). The pragma replay actions
|
||||
# (compile/link/passc/hint/...) are a backend/link concern handled by
|
||||
# the nifc closure, and re-emitting a loaded module's hints/warnings on
|
||||
# every import would be wrong — so they are deliberately skipped here.
|
||||
if n.kind == nkReplayAction and n.len >= 1 and n[0].kind == nkStrLit and
|
||||
n[0].strVal in ["put", "inc", "add", "incl"]:
|
||||
replayList.add n
|
||||
# Plus the macro-cache actions of the module's transitive import closure
|
||||
# (collected by the moduleFromNifFile call above via loadTransitiveHooks),
|
||||
# so a flavor/type registered in an indirectly-imported module is visible.
|
||||
for n in graph.transitiveReplayActions: replayList.add n
|
||||
graph.transitiveReplayActions.setLen 0
|
||||
if replayList.len > 0:
|
||||
replayStateChanges(result, graph, replayList)
|
||||
# Fill the VM slots of the module's `{.compileTime.}` globals now (sem
|
||||
# would have, but a NIF-loaded module is never semchecked).
|
||||
initLoadedCompileTimeGlobals(graph, result, precomp.topLevel)
|
||||
return result # Return early, don't process from source
|
||||
let path = toFullPath(graph.config, fileIdx)
|
||||
let filename = AbsoluteFile path
|
||||
@@ -364,7 +496,14 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
|
||||
let projectFile = if projectFileIdx == InvalidFileIdx: conf.projectMainIdx else: projectFileIdx
|
||||
conf.projectMainIdx2 = projectFile
|
||||
|
||||
let packSym = getPackage(graph, projectFile)
|
||||
var packSym = getPackage(graph, projectFile)
|
||||
if graph.config.cmd in {cmdM, cmdNifC} and graph.config.icProject.len > 0:
|
||||
# per-module IC children: the process' project file is the MODULE being
|
||||
# compiled, which would make its package the "main package" and unfilter
|
||||
# foreign-package diagnostics (a vendored package's hintAsError promotion
|
||||
# then aborts builds the whole-program compilation accepts). Use the
|
||||
# original project, forwarded by deps.nim via --icproject.
|
||||
packSym = getPackage(graph, fileInfoIdx(graph.config, AbsoluteFile graph.config.icProject))
|
||||
graph.config.mainPackageId = packSym.getPackageId
|
||||
graph.importStack.add projectFile
|
||||
|
||||
@@ -375,6 +514,9 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
|
||||
elif graph.config.cmd == cmdM:
|
||||
# For cmdM: load system.nim from NIF first, then compile the main module
|
||||
connectPipelineCallbacks(graph)
|
||||
# Record the main module so the IC loader won't materialise duplicate stubs
|
||||
# for its own symbols when a dependency (e.g. system) re-exports them.
|
||||
setIcMainModule(projectFile)
|
||||
graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
|
||||
graph.config.libpath / RelativeFile"system.nim")
|
||||
when not defined(nimKochBootstrap):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import std/intsets
|
||||
import ast, options, lineinfos, pathutils, msgs, modulegraphs, packages
|
||||
|
||||
proc skipCodegen*(config: ConfigRef; n: PNode): bool {.inline.} =
|
||||
@@ -23,4 +24,3 @@ proc prepareConfigNotes*(graph: ModuleGraph; module: PSym) =
|
||||
|
||||
proc moduleHasChanged*(graph: ModuleGraph; module: PSym): bool {.inline.} =
|
||||
result = true
|
||||
#module.id >= 0 or isDefined(graph.config, "nimBackendAssumesChange")
|
||||
|
||||
@@ -211,7 +211,7 @@ type
|
||||
cpuPowerpc64el, cpuSparc, cpuVm, cpuHppa, cpuIa64, cpuAmd64, cpuMips,
|
||||
cpuMipsel, cpuArm, cpuArm64, cpuJS, cpuNimVM, cpuAVR, cpuMSP430,
|
||||
cpuSparc64, cpuS390x, cpuMips64, cpuMips64el, cpuRiscV32, cpuRiscV64,
|
||||
cpuEsp, cpuWasm32, cpuE2k, cpuLoongArch64
|
||||
cpuEsp, cpuWasm32, cpuE2k, cpuLoongArch64, cpuWasm64
|
||||
|
||||
type
|
||||
TInfoCPU* = tuple[name: string, intSize: int, endian: Endianness,
|
||||
@@ -249,7 +249,8 @@ const
|
||||
(name: "esp", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32),
|
||||
(name: "wasm32", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32),
|
||||
(name: "e2k", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64),
|
||||
(name: "loongarch64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)]
|
||||
(name: "loongarch64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64),
|
||||
(name: "wasm64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)]
|
||||
|
||||
type
|
||||
Target* = object
|
||||
|
||||
@@ -105,9 +105,12 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
|
||||
result.typ = formal
|
||||
elif arg.kind in nkSymChoices and formal.skipTypes(abstractInst).kind == tyEnum:
|
||||
# Pick the right 'sym' from the sym choice by looking at 'formal' type:
|
||||
# The choice candidates may be wrapped in `var`/`lent` when they come from
|
||||
# a loop-local view, but for enum disambiguation only the underlying enum
|
||||
# type matters.
|
||||
result = nil
|
||||
for ch in arg:
|
||||
if sameType(ch.typ, formal):
|
||||
if sameType(ch.typ.skipTypes({tyVar, tyLent}), formal):
|
||||
return ch
|
||||
typeMismatch(c.config, info, formal, arg.typ, arg)
|
||||
else:
|
||||
@@ -247,6 +250,26 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
|
||||
if result.kind notin {kind, skTemp}:
|
||||
localError(c.config, n.info, "cannot use symbol of kind '$1' as a '$2'" %
|
||||
[result.kind.toHumanStr, kind.toHumanStr])
|
||||
# bug #25693: a local declared inside a template/macro operand (recorded in
|
||||
# `shadowDiscardedDefs`) can be captured by a `{.dirty.}` template and
|
||||
# re-emitted as a definition more than once. The first emission keeps the
|
||||
# original symbol (so a leaked dirty-template name still resolves); every
|
||||
# later emission gets a fresh copy, so distinct emissions don't share one
|
||||
# symbol - which the destructor/liveness analysis would otherwise miscompile.
|
||||
# Unlike a plain redefinition check this is control-flow agnostic, so the
|
||||
# common "emit a `typed` body in several mutually-exclusive branches" pattern
|
||||
# keeps working. gensym'ed locals (and ones derived from a gensym name) are
|
||||
# excluded: the gensym machinery already keeps their names unique, and a
|
||||
# fresh copy would reuse the unique name and clash in the same scope.
|
||||
if kind in {skVar, skLet, skForVar} and
|
||||
{sfGenSym, sfWasGenSym} * result.flags == {} and
|
||||
result.id in c.shadowDiscardedDefs:
|
||||
if containsOrIncl(c.realizedDefs, result.id):
|
||||
let fresh = copySym(result, c.idgen)
|
||||
fresh.ast = result.ast
|
||||
put(c.p, result, fresh)
|
||||
c.hasSymRedefs = true
|
||||
result = fresh
|
||||
when false:
|
||||
if sfGenSym in result.flags and result.kind notin {skTemplate, skMacro, skParam}:
|
||||
# declarative context, so produce a fresh gensym:
|
||||
|
||||
@@ -90,8 +90,14 @@ proc addTypeBoundSymbols(graph: ModuleGraph, arg: PType, name: PIdent,
|
||||
# argument must be typed first, meaning arguments always
|
||||
# matching `untyped` are ignored
|
||||
let t = nominalRoot(arg)
|
||||
if t != nil and t.owner.kind == skModule:
|
||||
# search module for routines attachable to `t`
|
||||
if t != nil and t.owner.kind == skModule and
|
||||
t.owner.position >= 0 and t.owner.position < graph.ifaces.len:
|
||||
# search module for routines attachable to `t`.
|
||||
# Under IC the nominal type may have been loaded from a NIF file, in which
|
||||
# case its owner module is a stub whose `position` (a NIF-suffix file index)
|
||||
# has no `ifaces` slot; such type-bound ops are reachable through normal
|
||||
# imports instead, so skip the direct module scan to avoid an out-of-range
|
||||
# access.
|
||||
let module = t.owner
|
||||
var iter = default(ModuleIter)
|
||||
var s = initModuleIter(iter, graph, module, name)
|
||||
@@ -726,6 +732,15 @@ proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode =
|
||||
result = paramTypesMatch(m, f, a, arg, nil)
|
||||
if m.genericConverter and result != nil:
|
||||
instGenericConvertersArg(c, result, m)
|
||||
when defined(icDbg):
|
||||
if result == nil and f != nil and a != nil and f.kind == tyEnum:
|
||||
echo "INDEXMISMATCH f=", typeToString(f), " itemId=", f.itemId,
|
||||
" uniqueId=", f.uniqueId, " mod=", toFullPath(c.config, f.itemId.module.FileIndex),
|
||||
" sym=", (if f.sym != nil: $f.sym.itemId else: "nil"), " state=", f.state
|
||||
let a2 = a.skipTypes({tyRange})
|
||||
echo " a=", typeToString(a), " itemId=", a2.itemId, " uniqueId=", a2.uniqueId,
|
||||
" mod=", toFullPath(c.config, a2.itemId.module.FileIndex),
|
||||
" sym=", (if a2.sym != nil: $a2.sym.itemId else: "nil"), " state=", a2.state
|
||||
|
||||
proc inferWithMetatype(c: PContext, formal: PType,
|
||||
arg: PNode, coerceDistincts = false): PNode =
|
||||
@@ -968,7 +983,12 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym, errors: var CandidateErr
|
||||
diagnostics: m.diagnostics))
|
||||
return nil
|
||||
var newInst = generateInstance(c, s, m.bindings, n.info)
|
||||
newInst.typ.excl tfUnresolved
|
||||
# `generateInstance` may return an instance REUSED from another module's NIF
|
||||
# `(offer …)` — its type is Sealed (immutable). Such an instance is already
|
||||
# fully resolved (`tfUnresolved` cleared at its original instantiation), so the
|
||||
# `excl` is a no-op; skip it rather than assert on a Sealed-type mutation.
|
||||
if newInst.typ.state != Sealed:
|
||||
newInst.typ.excl tfUnresolved
|
||||
let info = getCallLineInfo(n)
|
||||
markUsed(c, info, s, isGenericInstance = false)
|
||||
onUse(info, s, isGenericInstance = false)
|
||||
|
||||
@@ -189,6 +189,18 @@ type
|
||||
inTypeofContext*: int
|
||||
|
||||
semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.}
|
||||
shadowDiscardedDefs*: IntSet
|
||||
# ids of local symbols that were declared inside a template/macro operand's
|
||||
# shadow scope and then discarded; re-emitting such a symbol as a
|
||||
# definition gives a fresh copy so distinct emissions don't share a symbol.
|
||||
# See bug #25693 and `rememberShadowDefs`.
|
||||
realizedDefs*: IntSet
|
||||
# ids from `shadowDiscardedDefs` already realized once; the first emission
|
||||
# keeps the original symbol (so leaked dirty-template names still resolve),
|
||||
# later emissions get a fresh copy.
|
||||
hasSymRedefs*: bool
|
||||
# set once a redefinition mapping has been installed; makes `getGenSym`
|
||||
# consult the proc-con mapping for non-gensym symbols too.
|
||||
|
||||
TBorrowState* = enum
|
||||
bsNone, bsReturnNotMatch, bsNoDistinct, bsGeneric, bsNotSupported, bsMatch
|
||||
@@ -281,7 +293,10 @@ proc get*(p: PProcCon; key: PSym): PSym =
|
||||
result = p.mapping.getOrDefault(key.itemId)
|
||||
|
||||
proc getGenSym*(c: PContext; s: PSym): PSym =
|
||||
if sfGenSym notin s.flags: return s
|
||||
# `c.hasSymRedefs` additionally routes ordinary (non-gensym) symbols through
|
||||
# the mapping so a re-emitted definition can redirect them to its fresh copy,
|
||||
# see bug #25693 and `newSymG`.
|
||||
if sfGenSym notin s.flags and not c.hasSymRedefs: return s
|
||||
var it = c.p
|
||||
while it != nil:
|
||||
result = get(it, s)
|
||||
@@ -343,6 +358,8 @@ proc newContext*(graph: ModuleGraph; module: PSym): PContext =
|
||||
userPragmas: initStrTable(),
|
||||
generics: @[],
|
||||
unknownIdents: initIntSet(),
|
||||
shadowDiscardedDefs: initIntSet(),
|
||||
realizedDefs: initIntSet(),
|
||||
cache: graph.cache,
|
||||
graph: graph,
|
||||
signatures: initStrTable(),
|
||||
@@ -353,7 +370,16 @@ proc addIncludeFileDep*(c: PContext; f: FileIndex) =
|
||||
discard
|
||||
|
||||
proc addImportFileDep*(c: PContext; f: FileIndex) =
|
||||
discard
|
||||
# Under `nim m` (the IC frontend) record the REAL direct imports of the
|
||||
# current module as sem resolves them — including imports a macro generated
|
||||
# (e.g. chronicles' `parseStmt("import chronicles/textlines")`), which the
|
||||
# static dependency scanner never sees. `nim ic` writes this set as the
|
||||
# module's `.s.deps` sidecar and re-derives the build graph from it, so the
|
||||
# discovery is structured data instead of a build-failure side channel.
|
||||
if c.config.cmd == cmdM:
|
||||
let importer = c.module.position.FileIndex
|
||||
var deps = addr c.graph.importDeps.mgetOrPut(importer, @[])
|
||||
if f notin deps[]: deps[].add f
|
||||
|
||||
proc addPragmaComputation*(c: PContext; n: PNode) =
|
||||
# Also store for NIF-based IC (cmdM mode or optCompress)
|
||||
@@ -370,6 +396,18 @@ proc addConverter*(c: PContext, conv: PSym) =
|
||||
assert conv != nil
|
||||
if inclSym(c.converters, conv):
|
||||
add(c.graph.ifaces[c.module.position].converters, conv)
|
||||
# Record for IC: the loader rebuilds Iface.converters from the NIF's
|
||||
# (repconverter ...) entries (moduleFromNifFile). This must capture not only
|
||||
# converters DEFINED in this module (addConverterDef) but also ones IMPORTED
|
||||
# from another module here (importer.addUnnamedIt re-adds a re-exported
|
||||
# module's converters via this proc). Otherwise a loaded module's
|
||||
# re-exported converters were invisible to importers and implicit
|
||||
# conversions silently stopped matching at a consumer that reaches the
|
||||
# converter only through this module's re-export chain (e.g. faststreams'
|
||||
# `InputStreamHandle -> InputStream` via ssz_serialization, breaking
|
||||
# `SSZ.decode`/`encode`). `inclSym` guards against duplicate log entries.
|
||||
c.graph.opsLog.add LogEntry(kind: ConverterEntry, module: c.module.position,
|
||||
key: "", sym: conv)
|
||||
|
||||
proc addConverterDef*(c: PContext, conv: PSym) =
|
||||
addConverter(c, conv)
|
||||
@@ -377,6 +415,13 @@ proc addConverterDef*(c: PContext, conv: PSym) =
|
||||
proc addPureEnum*(c: PContext, e: PSym) =
|
||||
assert e != nil
|
||||
add(c.graph.ifaces[c.module.position].pureEnums, e)
|
||||
# record for IC: a NIF-loaded module rebuilds `Iface.pureEnums` from these log
|
||||
# entries (moduleFromNifFile); without it a loaded module's pure enums were
|
||||
# invisible to importers, so `importPureEnumFields` never offered their fields
|
||||
# and unqualified pure-enum values stopped resolving. (Same pattern as
|
||||
# `addConverterDef`.)
|
||||
c.graph.opsLog.add LogEntry(kind: PureEnumEntry, module: c.module.position,
|
||||
key: "", sym: e)
|
||||
|
||||
proc addPattern*(c: PContext, p: PSym) =
|
||||
assert p != nil
|
||||
|
||||
@@ -27,6 +27,11 @@ const
|
||||
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
|
||||
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
|
||||
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)
|
||||
@@ -57,6 +62,16 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||
elif {efWantStmt, efAllowStmt} * flags != {}:
|
||||
result.typ = newTypeS(tyVoid, c)
|
||||
else:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icNoType] semOperand: ", renderTree(result, {renderNoComments}),
|
||||
" kind=", result.kind,
|
||||
(if result.kind in {nkCall, nkCommand} and result[0].kind == nkSym:
|
||||
" calleeTyp=" & (if result[0].sym.typ == nil: "NIL" else:
|
||||
$result[0].sym.typ.kind & " ret=" &
|
||||
(if result[0].sym.typ.returnType == nil: "NIL"
|
||||
else: $result[0].sym.typ.returnType.kind))
|
||||
else: "")
|
||||
echo getStackTrace()
|
||||
localError(c.config, n.info, errExprXHasNoType %
|
||||
renderTree(result, {renderNoComments}))
|
||||
result.typ = errorType(c)
|
||||
@@ -83,6 +98,17 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType
|
||||
if result.typ == nil and efInTypeof in flags:
|
||||
result.typ = c.voidType
|
||||
elif result.typ == nil or result.typ == c.enforceVoidContext:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icNoType] semExprWithType: ", renderTree(result, {renderNoComments}),
|
||||
" kind=", result.kind,
|
||||
(if result.kind in {nkCall, nkCommand} and result[0].kind == nkSym:
|
||||
" callee=" & result[0].sym.name.s &
|
||||
" calleeTyp=" & (if result[0].sym.typ == nil: "NIL" else:
|
||||
$result[0].sym.typ.kind & " ret=" &
|
||||
(if result[0].sym.typ.returnType == nil: "NIL"
|
||||
else: $result[0].sym.typ.returnType.kind))
|
||||
else: "")
|
||||
echo getStackTrace()
|
||||
localError(c.config, n.info, errExprXHasNoType %
|
||||
renderTree(result, {renderNoComments}))
|
||||
result.typ = errorType(c)
|
||||
@@ -106,7 +132,9 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType
|
||||
|
||||
proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||
result = semExprCheck(c, n, flags)
|
||||
if result.typ == nil:
|
||||
if result.typ == nil and efInTypeof in flags:
|
||||
result.typ = c.voidType
|
||||
elif result.typ == nil:
|
||||
localError(c.config, n.info, errExprXHasNoType %
|
||||
renderTree(result, {renderNoComments}))
|
||||
result.typ = errorType(c)
|
||||
@@ -197,6 +225,29 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
|
||||
# set symchoice node type back to None
|
||||
n.typ = newTypeS(tyNone, c)
|
||||
|
||||
proc resolveOpenSymDotRhs(c: PContext, n: PNode): PNode =
|
||||
## Resolves an `nkOpenSym` in the field position of a dot expression.
|
||||
## The dot handling (`builtinFieldAccess`, `dotTransformation`) matches on
|
||||
## the node kind of the RHS directly, so the wrapper cannot be left for
|
||||
## `semExpr` to unwrap; without this the captured symbol degrades to a
|
||||
## plain identifier that is then only looked up in the instantiation
|
||||
## context. Mirrors `semOpenSym`: a symbol injected during instantiation
|
||||
## under the current proc replaces the captured symbol, otherwise the
|
||||
## captured node is used.
|
||||
let inner = n[0]
|
||||
result = inner
|
||||
if inner.kind != nkSym: return
|
||||
let id = newIdentNode(inner.sym.name, n.info)
|
||||
c.isAmbiguous = false
|
||||
let s2 = qualifiedLookUp(c, id, {})
|
||||
if s2 != nil and not c.isAmbiguous and s2 != inner.sym:
|
||||
# only consider symbols defined under the current proc:
|
||||
var o = s2.owner
|
||||
while o != nil:
|
||||
if o == c.p.owner:
|
||||
return id
|
||||
o = o.owner
|
||||
|
||||
proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
|
||||
if n.kind == nkOpenSymChoice:
|
||||
result = semOpenSym(c, n, flags, expectedType,
|
||||
@@ -1524,6 +1575,9 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
|
||||
suggestExpr(c, n)
|
||||
if exactEquals(c.config.m.trackPos, n[1].info): suggestExprNoCheck(c, n)
|
||||
|
||||
if n[1].kind == nkOpenSym:
|
||||
n[1] = resolveOpenSymDotRhs(c, n[1])
|
||||
|
||||
var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule})
|
||||
if s != nil:
|
||||
if s.kind in OverloadableSyms:
|
||||
@@ -1851,6 +1905,22 @@ proc takeImplicitAddr(c: PContext, n: PNode; isLent: bool): PNode =
|
||||
n.typ = n.typ.elementType
|
||||
result.add(n)
|
||||
|
||||
proc markResultVarIsPtr(c: PContext, x: PNode) {.inline.} =
|
||||
## Set `tfVarIsPtr` on the (result) sym node's type. Under IC that type can be a
|
||||
## NIF-loaded (Sealed) and interned instance which must not be mutated in place
|
||||
## (it could corrupt other users of the shared type, and the assert forbids it):
|
||||
## give this result its own copy carrying the flag, exactly like a from-source
|
||||
## compile has a fresh result type here.
|
||||
if tfVarIsPtr in x.typ.flags: return
|
||||
if x.typ.state == Sealed:
|
||||
let fresh = copyType(x.typ, c.idgen, x.typ.owner)
|
||||
fresh.incl tfVarIsPtr
|
||||
x.typ = fresh
|
||||
if x.kind == nkSym and x.sym.state != Sealed:
|
||||
x.sym.typ = fresh
|
||||
else:
|
||||
x.typ.incl tfVarIsPtr
|
||||
|
||||
proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} =
|
||||
if le.kind == nkHiddenDeref:
|
||||
var x = le[0]
|
||||
@@ -1858,10 +1928,10 @@ proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} =
|
||||
if x.sym.kind == skResult and (x.typ.kind in {tyVar, tyLent} or classifyViewType(x.typ) != noView):
|
||||
n[0] = x # 'result[]' --> 'result'
|
||||
n[1] = takeImplicitAddr(c, ri, x.typ.kind == tyLent)
|
||||
x.typ.incl tfVarIsPtr
|
||||
markResultVarIsPtr(c, x)
|
||||
#echo x.info, " setting it for this type ", typeToString(x.typ), " ", n.info
|
||||
elif sfGlobal in x.sym.flags:
|
||||
x.typ.incl tfVarIsPtr
|
||||
markResultVarIsPtr(c, x)
|
||||
|
||||
proc borrowCheck(c: PContext, n, le, ri: PNode) =
|
||||
const
|
||||
@@ -2118,6 +2188,12 @@ proc semProcBody(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
||||
|
||||
if c.p.owner.kind notin {skMacro, skTemplate} and
|
||||
c.p.resultSym != nil and c.p.resultSym.typ.isMetaType:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icMetaRet] meta result type for ", c.p.owner.name.s, ": ",
|
||||
typeToString(c.p.resultSym.typ), " kind=", c.p.resultSym.typ.kind,
|
||||
" 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
|
||||
if isEmptyType(result.typ):
|
||||
# we inferred a 'void' return type:
|
||||
c.p.resultSym.typ = errorType(c)
|
||||
|
||||
@@ -610,10 +610,21 @@ proc getConstExpr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode
|
||||
var s = n.sym
|
||||
case s.kind
|
||||
of skEnumField:
|
||||
when defined(icDbg):
|
||||
if n.typ == nil:
|
||||
echo "ENUMFIELD niltyp sym=", s.name.s, " symtyp=",
|
||||
(if s.typ == nil: "nil" else: $s.typ.kind), " lazy=", nfLazyType in n.flags,
|
||||
" symstate=", s.state, " symid=", s.itemId
|
||||
result = newIntNodeT(toInt128(s.position), n, idgen, g)
|
||||
of skConst:
|
||||
case s.magic
|
||||
of mIsMainModule: result = newIntNodeT(toInt128(ord(sfMainModule in m.flags)), n, idgen, g)
|
||||
of mIsMainModule:
|
||||
# Under `nim m` (IC) `sfMainModule` is set on every module that is being
|
||||
# compiled (so it writes its own NIF), so it cannot answer `isMainModule`;
|
||||
# the IC build file marks the real entry point with `--isMainModule:on`.
|
||||
let isMain = if g.config.cmd == cmdM: g.config.isMainModule
|
||||
else: sfMainModule in m.flags
|
||||
result = newIntNodeT(toInt128(ord(isMain)), n, idgen, g)
|
||||
of mCompileDate: result = newStrNodeT(getDateStr(), n, g)
|
||||
of mCompileTime: result = newStrNodeT(getClockStr(), n, g)
|
||||
of mCpuEndian: result = newIntNodeT(toInt128(ord(CPU[g.config.target.targetCPU].endian)), n, idgen, g)
|
||||
|
||||
@@ -129,7 +129,14 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
|
||||
result.typ = nil
|
||||
onUse(n.info, s)
|
||||
of skParam:
|
||||
result = n
|
||||
if s.owner == c.p.owner:
|
||||
# Parameters of the routine currently being semchecked stay as local
|
||||
# identifiers
|
||||
result = n
|
||||
else:
|
||||
# Preserve captured outer parameters so nested generic procs can still
|
||||
# see them after the generic pre-pass.
|
||||
result = newSymNode(s, n.info)
|
||||
onUse(n.info, s)
|
||||
of skType:
|
||||
if (s.typ != nil) and
|
||||
|
||||
@@ -119,11 +119,44 @@ proc freshGenSyms(c: PContext; n: PNode, owner, orig: PSym, symMap: var SymMappi
|
||||
|
||||
proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind)
|
||||
|
||||
proc aliasLoadedTypedescParams(c: PContext, instantiated, orig: PSym): bool =
|
||||
## When the generic being instantiated had its body LOADED from a NIF (only
|
||||
## `nim m`/`nim nifc`, only for a generic owned by another module), that body
|
||||
## re-sems from plain identifiers — ast2nif serialises locals/params as idents,
|
||||
## not `nkSym`. A `T: typedesc[...]` param referenced as a type must then
|
||||
## resolve `T` to the bound type, but the instantiated skParam carries the
|
||||
## concrete type `instantiateProcType` typedesc-skipped it to, which an ident
|
||||
## lookup cannot use as a type name. Shadow each such param with an `skType`
|
||||
## alias of the same name in a fresh scope layer (the alias is exactly how Nim
|
||||
## models "this name denotes a type"). In-process bodies reach the param as
|
||||
## `nkSym` and never take this path, hence the command gate.
|
||||
##
|
||||
## Returns true iff a scope layer was opened; the caller must `closeScope`.
|
||||
if c.config.cmd notin {cmdM, cmdNifC} or orig == nil or
|
||||
orig.itemId.module == c.module.position or
|
||||
orig.typ == nil or orig.typ.n == nil:
|
||||
return false
|
||||
result = false
|
||||
let procParams = instantiated.typ.n
|
||||
for i in 1..<min(procParams.len, orig.typ.n.len):
|
||||
if orig.typ.n[i].kind != nkSym: continue
|
||||
let origParamTyp = orig.typ.n[i].sym.typ
|
||||
if origParamTyp != nil and origParamTyp.kind == tyTypeDesc and
|
||||
tfUnresolved in origParamTyp.flags:
|
||||
if not result:
|
||||
openScope(c)
|
||||
result = true
|
||||
let p = procParams[i].sym
|
||||
let alias = newSym(skType, p.name, c.idgen, instantiated, p.info)
|
||||
alias.typ = p.typ
|
||||
addDecl(c, alias)
|
||||
|
||||
proc instantiateBody(c: PContext, n, params: PNode, result, orig: PSym) =
|
||||
if n[bodyPos].kind != nkEmpty:
|
||||
let procParams = result.typ.n
|
||||
for i in 1..<procParams.len:
|
||||
addDecl(c, procParams[i].sym)
|
||||
let aliasLayer = aliasLoadedTypedescParams(c, result, orig)
|
||||
maybeAddResult(c, result, result.ast)
|
||||
|
||||
inc c.inGenericInst
|
||||
@@ -152,6 +185,7 @@ proc instantiateBody(c: PContext, n, params: PNode, result, orig: PSym) =
|
||||
excl(result, sfForward)
|
||||
trackProc(c, result, result.ast[bodyPos])
|
||||
dec c.inGenericInst
|
||||
if aliasLayer: closeScope(c)
|
||||
|
||||
proc fixupInstantiatedSymbols(c: PContext, s: PSym) =
|
||||
for i in 0..<c.generics.len:
|
||||
@@ -245,7 +279,7 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
let originalParams = result.n
|
||||
result.n = originalParams.shallowCopy
|
||||
for i in 1 ..< originalParams.len:
|
||||
let resulti = originalParams[i].sym.typ
|
||||
var resulti = originalParams[i].sym.typ
|
||||
# twrong_field_caching requires these 'resetIdTable' calls:
|
||||
if i > FirstParamAt:
|
||||
resetIdTable(cl.symMap)
|
||||
@@ -258,6 +292,11 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
let needsStaticSkipping = resulti.kind == tyFromExpr
|
||||
let needsTypeDescSkipping = resulti.kind == tyTypeDesc and tfUnresolved in resulti.flags
|
||||
if resulti.kind == tyFromExpr:
|
||||
if resulti.state == Sealed:
|
||||
# The generic was loaded from a NIF; do not brand the shared original.
|
||||
# A tyFromExpr is a placeholder that `replaceTypeVarsT` resolves away,
|
||||
# so a copy carries no identity that later comparisons could miss.
|
||||
resulti = copyType(resulti, c.idgen, resulti.owner)
|
||||
resulti.incl tfNonConstExpr
|
||||
var paramType = replaceTypeVarsT(cl, resulti)
|
||||
if needsStaticSkipping:
|
||||
@@ -276,6 +315,12 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
let param = copySym(oldParam, c.idgen)
|
||||
setOwner(param, prc)
|
||||
param.typ = paramType
|
||||
when defined(icDbgRefc):
|
||||
echo "[icInst] ", prc.name.s, " param ", oldParam.name.s,
|
||||
": ", typeToString(resulti), " (kind=", resulti.kind,
|
||||
" uid=", resulti.uniqueId.module, ".", resulti.uniqueId.item,
|
||||
" flags=", resulti.flags, ") -> ", typeToString(paramType),
|
||||
" (kind=", paramType.kind, ")"
|
||||
|
||||
# The default value is instantiated and fitted against the final
|
||||
# concrete param type. We avoid calling `replaceTypeVarsN` on the
|
||||
@@ -283,6 +328,9 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
if oldParam.ast != nil:
|
||||
var def = oldParam.ast.copyTree
|
||||
if def.typ.kind == tyFromExpr:
|
||||
if def.typ.state == Sealed:
|
||||
# `copyTree` shares types; see the `resulti` comment above.
|
||||
def.typ = copyType(def.typ, c.idgen, def.typ.owner)
|
||||
def.typ.incl tfNonConstExpr
|
||||
if not isIntLit(def.typ):
|
||||
def = prepareNode(cl, def)
|
||||
@@ -374,6 +422,11 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
## parameters to their concrete types within the generic instance.
|
||||
# no need to instantiate generic templates/macros:
|
||||
internalAssert c.config, fn.kind notin {skMacro, skTemplate}
|
||||
# IC: instantiating `fn` consumes its generic body in the current module's
|
||||
# sem — record a NeedsImpl (strong) edge to `fn`'s module. The iface cookie
|
||||
# hashes only signatures now, so a generic body edit moves only the impl
|
||||
# cookie, and just the modules that instantiated it re-sem.
|
||||
recordIcImplDep(c.graph, fn)
|
||||
# generates an instantiated proc
|
||||
if c.instCounter > 50:
|
||||
globalError(c.config, info, "generic instantiation too nested")
|
||||
@@ -455,6 +508,10 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
# This is needed for cyclic module dependencies where generic instances
|
||||
# may be created in one module but referenced from another.
|
||||
logGenericInstance(c.graph, result)
|
||||
# Under IC the instance's NIF name must be canonical across modules:
|
||||
# derive its `disamb` from the instantiation identity (generic +
|
||||
# concrete types) instead of the per-module counter.
|
||||
setInstanceDisamb(c.graph, result, fn, entry.concreteTypes)
|
||||
# bug #12985 bug #22913
|
||||
# TODO: use the context of the declaration of generic functions instead
|
||||
# TODO: consider fixing options as well
|
||||
|
||||
@@ -43,17 +43,8 @@ proc semAddr(c: PContext; n: PNode): PNode =
|
||||
result.typ = makePtrType(c, x.typ.skipTypes({tySink}))
|
||||
|
||||
proc semTypeOf(c: PContext; n: PNode): PNode =
|
||||
var m = BiggestInt 1 # typeOfIter
|
||||
if n.len == 3:
|
||||
let mode = semConstExpr(c, n[2])
|
||||
if mode.kind != nkIntLit:
|
||||
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
|
||||
else:
|
||||
m = mode.intVal
|
||||
let typExpr = semTypeOfImpl(c, n)
|
||||
result = newNodeI(nkTypeOfExpr, n.info)
|
||||
inc c.inTypeofContext
|
||||
defer: dec c.inTypeofContext # compiles can raise an exception
|
||||
let typExpr = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {})
|
||||
result.add typExpr
|
||||
if typExpr.typ.kind == tyFromExpr:
|
||||
typExpr.typ.incl tfNonConstExpr
|
||||
|
||||
@@ -497,6 +497,33 @@ proc addRaiseEffect(a: PEffects, e, comesFrom: PNode) =
|
||||
if not isDefectException(e.typ):
|
||||
throws(a.exc, e, comesFrom)
|
||||
|
||||
proc skipHiddenConv(n: PNode): PNode =
|
||||
result = n
|
||||
while true:
|
||||
case result.kind
|
||||
of nkHiddenStdConv, nkHiddenSubConv:
|
||||
result = result[1]
|
||||
else: break
|
||||
|
||||
proc addRaiseEffectsFromExpr(a: PEffects, e, comesFrom: PNode) =
|
||||
if e.isNil:
|
||||
return
|
||||
case e.kind
|
||||
of nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr:
|
||||
if e.len > 0:
|
||||
addRaiseEffectsFromExpr(a, e.lastSon.skipHiddenConv, comesFrom)
|
||||
of nkIfExpr, nkIfStmt:
|
||||
for branch in items(e):
|
||||
if branch.len > 0:
|
||||
addRaiseEffectsFromExpr(a, branch.lastSon.skipHiddenConv, comesFrom)
|
||||
of nkCaseStmt:
|
||||
for i in 1..<e.len:
|
||||
let branch = e[i]
|
||||
if branch.len > 0:
|
||||
addRaiseEffectsFromExpr(a, branch.lastSon.skipHiddenConv, comesFrom)
|
||||
else:
|
||||
addRaiseEffect(a, e, comesFrom)
|
||||
|
||||
proc addTag(a: PEffects, e, comesFrom: PNode) =
|
||||
var aa = a.tags
|
||||
for i in 0..<aa.len:
|
||||
@@ -1208,6 +1235,7 @@ type
|
||||
enforcedGcSafety, enforceNoSideEffects: bool
|
||||
oldExc, oldTags, oldForbids: int
|
||||
exc, tags, forbids: PNode
|
||||
excSource, tagsSource, forbidsSource: PNode
|
||||
|
||||
proc createBlockContext(tracked: PEffects): PragmaBlockContext =
|
||||
var oldForbidsLen = 0
|
||||
@@ -1230,17 +1258,18 @@ proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
|
||||
# anything about 'raises' in the 'cast' at all. Same applies for 'tags'.
|
||||
setLen(tracked.exc.sons, bc.oldExc)
|
||||
for e in bc.exc:
|
||||
addRaiseEffect(tracked, e, e)
|
||||
addRaiseEffect(tracked, e, if bc.excSource != nil: bc.excSource else: e)
|
||||
if bc.tags != nil:
|
||||
setLen(tracked.tags.sons, bc.oldTags)
|
||||
for t in bc.tags:
|
||||
addTag(tracked, t, t)
|
||||
addTag(tracked, t, if bc.tagsSource != nil: bc.tagsSource else: t)
|
||||
if bc.forbids != nil:
|
||||
setLen(tracked.forbids.sons, bc.oldForbids)
|
||||
for t in bc.forbids:
|
||||
addNotTag(tracked, t, t)
|
||||
addNotTag(tracked, t, if bc.forbidsSource != nil: bc.forbidsSource else: t)
|
||||
|
||||
proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
|
||||
proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext) =
|
||||
let pragma = castPragma[1]
|
||||
case whichPragma(pragma)
|
||||
of wGcSafe:
|
||||
bc.enforcedGcSafety = true
|
||||
@@ -1253,6 +1282,7 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
|
||||
else:
|
||||
bc.tags = newNodeI(nkArgList, pragma.info)
|
||||
bc.tags.add n
|
||||
bc.tagsSource = castPragma
|
||||
of wForbids:
|
||||
let n = pragma[1]
|
||||
if n.kind in {nkCurly, nkBracket}:
|
||||
@@ -1260,6 +1290,7 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
|
||||
else:
|
||||
bc.forbids = newNodeI(nkArgList, pragma.info)
|
||||
bc.forbids.add n
|
||||
bc.forbidsSource = castPragma
|
||||
of wRaises:
|
||||
let n = pragma[1]
|
||||
if n.kind in {nkCurly, nkBracket}:
|
||||
@@ -1267,6 +1298,7 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
|
||||
else:
|
||||
bc.exc = newNodeI(nkArgList, pragma.info)
|
||||
bc.exc.add n
|
||||
bc.excSource = castPragma
|
||||
of wUncheckedAssign:
|
||||
discard "handled in sempass1"
|
||||
else:
|
||||
@@ -1303,6 +1335,8 @@ proc allowCStringConv(n: PNode): bool =
|
||||
|
||||
proc track(tracked: PEffects, n: PNode) =
|
||||
case n.kind
|
||||
of nkTypeOfExpr:
|
||||
discard "typeof() never evaluates its operand; not a definite-assignment use"
|
||||
of nkSym:
|
||||
useVar(tracked, n)
|
||||
if n.sym.typ != nil and tfHasAsgn in n.sym.typ.flags:
|
||||
@@ -1319,7 +1353,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
if n[0].kind != nkEmpty:
|
||||
n[0].info = n.info
|
||||
#throws(tracked.exc, n[0])
|
||||
addRaiseEffect(tracked, n[0], n)
|
||||
addRaiseEffectsFromExpr(tracked, n[0], n)
|
||||
for i in 0..<n.safeLen:
|
||||
track(tracked, n[i])
|
||||
createTypeBoundOps(tracked, n[0].typ, n.info)
|
||||
@@ -1520,7 +1554,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
of wNoSideEffect:
|
||||
bc.enforceNoSideEffects = true
|
||||
of wCast:
|
||||
castBlock(tracked, pragmaList[i][1], bc)
|
||||
castBlock(tracked, pragmaList[i], bc)
|
||||
else:
|
||||
discard
|
||||
applyBlockContext(tracked, bc)
|
||||
|
||||
@@ -2625,6 +2625,14 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
|
||||
n[genericParamsPos] = proto.ast[genericParamsPos]
|
||||
n[paramsPos] = proto.ast[paramsPos]
|
||||
n[pragmasPos] = proto.ast[pragmasPos]
|
||||
# miscPos holds this definition's *original* generic-param node (kept for
|
||||
# error messages, see setGenericParamsMisc / issue #1713). For an impl that
|
||||
# resolves to a forward decl, that node was analysed under the now-discarded
|
||||
# impl symbol and its generic-param constraint types are owned by it. Adopt
|
||||
# the prototype's miscPos so the discarded impl sym is fully unreachable —
|
||||
# otherwise it leaks (via `proto.ast = n` below) as a type owner and gets
|
||||
# serialized as a phantom duplicate overload under IC.
|
||||
n[miscPos] = proto.ast[miscPos]
|
||||
if n[namePos].kind != nkSym: internalError(c.config, n.info, "semProcAux")
|
||||
n[namePos].sym = proto
|
||||
if importantComments(c.config) and proto.ast.comment.len > 0:
|
||||
@@ -2642,6 +2650,11 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
|
||||
elif s.name.s == "()" and callOperator notin c.features:
|
||||
localError(c.config, n.info, "the overloaded " & s.name.s &
|
||||
" operator has to be enabled with {.experimental: \"callOperator\".}")
|
||||
elif sfImportc notin s.flags and (s.name.s == ">" or s.name.s == ">=" or s.name.s == "!="):
|
||||
# ignore imported procs as these operators in backend language might have different semantics
|
||||
let op1 = if s.name.s == "!=": "==" elif s.name.s == ">": "<" else: "<="
|
||||
message(c.config, n.info, warnInvalidCmpOp, "define `" & op1 & "` instead of `" & s.name.s & "` to implement user defined comparison operator. " &
|
||||
"it allows you to use `" & s.name.s & "` automatically.")
|
||||
|
||||
if sfBorrow in s.flags and c.config.cmd notin cmdDocLike:
|
||||
result[bodyPos] = c.graph.emptyNode
|
||||
|
||||
@@ -512,7 +512,13 @@ proc semArrayIndex(c: PContext, n: PNode): PType =
|
||||
if c.inGenericContext > 0: result.incl tfUnresolved
|
||||
else:
|
||||
result = e.typ.skipTypes({tyTypeDesc})
|
||||
result.incl tfImplicitStatic
|
||||
if result.state != Sealed:
|
||||
# For a type loaded from the IC cache we skip the flag instead of
|
||||
# mutating (or copying) the type: tfImplicitStatic has no readers in
|
||||
# the compiler, and a copy would get a fresh itemId, breaking enum
|
||||
# identity (`sameEnumTypes` compares ids) — `arr[enumVal]` on an
|
||||
# `array[LoadedEnum, T]` would no longer typecheck.
|
||||
result.incl tfImplicitStatic
|
||||
elif e.kind in (nkCallKinds + {nkBracketExpr}) and hasUnresolvedArgs(c, e):
|
||||
if not isOrdinalType(e.typ.skipTypes({tyStatic, tyAlias, tyGenericInst, tySink})):
|
||||
localError(c.config, n[1].info, errOrdinalTypeExpected % typeToString(e.typ, preferDesc))
|
||||
@@ -1355,7 +1361,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
|
||||
for i in 0..<paramType.len - 1:
|
||||
if paramType[i].kind == tyStatic:
|
||||
var staticCopy = paramType[i].exactReplica
|
||||
var staticCopy = paramType[i].exactReplica(c.idgen)
|
||||
staticCopy.incl tfInferrableStatic
|
||||
result.rawAddSon staticCopy
|
||||
else:
|
||||
@@ -1891,6 +1897,12 @@ proc semTypeExpr(c: PContext, n: PNode; prev: PType): PType =
|
||||
# by macros. Only macros can summon unnamed types
|
||||
# and cast spell upon AST. Here we need to give
|
||||
# it a name taken from left hand side's node
|
||||
if result.state == Sealed:
|
||||
# The unnamed type was loaded from a dependency's NIF and must not
|
||||
# be mutated in place; attach the name to a fresh copy instead.
|
||||
let orig = result
|
||||
result = copyType(orig, c.idgen, getCurrOwner(c))
|
||||
copyTypeProps(c.graph, c.idgen.module, result, orig)
|
||||
result.sym = prev.sym
|
||||
result.sym.typ = result
|
||||
else:
|
||||
@@ -2063,6 +2075,57 @@ proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType =
|
||||
result.rawAddSon(base)
|
||||
result.incl tfHasStatic
|
||||
|
||||
proc semTypeOfImpl(c: PContext; n: PNode): PNode =
|
||||
var m = BiggestInt 1 # typeOfIter
|
||||
var modifierMode = BiggestInt 0 # CompatibleTypeModifiers
|
||||
type
|
||||
TypeOfParams = enum
|
||||
topMode
|
||||
topModifier
|
||||
if n.len in 3 .. 4:
|
||||
for i in 2 ..< n.len:
|
||||
var argKind = topMode
|
||||
var arg: PNode = nil
|
||||
if n[i].kind == nkExprEqExpr and n[i][0].kind == nkIdent:
|
||||
# named param
|
||||
case n[i][0].ident.s
|
||||
of "mode": argKind = topMode
|
||||
of "modifierMode": argKind = topModifier
|
||||
else:
|
||||
localError(c.config, n.info, "typeof: got unknown parameter name")
|
||||
arg = n[i][1]
|
||||
else:
|
||||
if i == 2:
|
||||
argKind = topMode
|
||||
else:
|
||||
argKind = topModifier
|
||||
arg = n[i]
|
||||
case argKind
|
||||
of topMode:
|
||||
let mode = semConstExpr(c, arg)
|
||||
if mode.kind != nkIntLit:
|
||||
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
|
||||
else:
|
||||
m = mode.intVal
|
||||
of topModifier:
|
||||
let modMode = semConstExpr(c, arg)
|
||||
if modMode.kind != nkIntLit:
|
||||
localError(c.config, n.info, "typeof: cannot evaluate 'modifierMode' parameter at compile-time")
|
||||
else:
|
||||
modifierMode = modMode.intVal
|
||||
|
||||
inc c.inTypeofContext
|
||||
defer: dec c.inTypeofContext # compiles can raise an exception
|
||||
var typExpr = semExprNoDeref(c, n[1], if m == 1: {efInTypeof} else: {})
|
||||
if modifierMode == 0:
|
||||
# CompatibleTypeModifiers
|
||||
typExpr.typ = typExpr.typ.skipTypes({tyVar, tyLent})
|
||||
elif modifierMode == 1:
|
||||
# RemoveTypeModifiers
|
||||
typExpr.typ = typExpr.typ.skipTypes({tyVar, tyLent, tySink})
|
||||
|
||||
result = typExpr
|
||||
|
||||
proc semTypeOf(c: PContext; n: PNode; prev: PType): PType =
|
||||
openScope(c)
|
||||
inc c.inTypeofContext
|
||||
@@ -2083,16 +2146,7 @@ proc semTypeOf(c: PContext; n: PNode; prev: PType): PType =
|
||||
|
||||
proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType =
|
||||
openScope(c)
|
||||
var m = BiggestInt 1 # typeOfIter
|
||||
if n.len == 3:
|
||||
let mode = semConstExpr(c, n[2])
|
||||
if mode.kind != nkIntLit:
|
||||
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
|
||||
else:
|
||||
m = mode.intVal
|
||||
inc c.inTypeofContext
|
||||
defer: dec c.inTypeofContext # compiles can raise an exception
|
||||
let ex = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {})
|
||||
let ex = semTypeOfImpl(c, n)
|
||||
closeScope(c)
|
||||
result = ex.typ
|
||||
if result.kind == tyFromExpr:
|
||||
@@ -2136,7 +2190,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
|
||||
localError(c.config, n.info, errTypeExpected)
|
||||
return errorSym(c, n)
|
||||
result = result.typ.sym.copySym(c.idgen)
|
||||
result.typ = exactReplica(result.typ)
|
||||
result.typ = exactReplica(result.typ, c.idgen)
|
||||
result.typ.incl tfUnresolved
|
||||
|
||||
if result.kind == skGenericParam:
|
||||
|
||||
@@ -272,10 +272,17 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PT
|
||||
if n == nil: return
|
||||
result = copyNode(n)
|
||||
if n.typ != nil:
|
||||
if n.typ.kind == tyFromExpr:
|
||||
var nodeTyp = n.typ
|
||||
if nodeTyp.kind == tyFromExpr:
|
||||
# type of node should not be evaluated as a static value
|
||||
n.typ.incl tfNonConstExpr
|
||||
result.typ = replaceTypeVarsT(cl, n.typ)
|
||||
if nodeTyp.state == Sealed:
|
||||
# IC: do not brand the loaded shared original — a tyFromExpr is a
|
||||
# placeholder that `replaceTypeVarsT` resolves away, so the copy
|
||||
# carries no identity later comparisons could miss (mirrors
|
||||
# `instantiateProcType`)
|
||||
nodeTyp = copyType(nodeTyp, cl.c.idgen, nodeTyp.owner)
|
||||
nodeTyp.incl tfNonConstExpr
|
||||
result.typ = replaceTypeVarsT(cl, nodeTyp)
|
||||
checkMetaInvariants(cl, result.typ)
|
||||
case n.kind
|
||||
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||
@@ -387,6 +394,13 @@ proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType =
|
||||
# don't bind `auto` return type to a previous binding of `auto`
|
||||
return nil
|
||||
result = cl.typeMap.lookup(t)
|
||||
when defined(icDbgRefc):
|
||||
if t.kind in {tyGenericParam, tyTypeDesc}:
|
||||
echo "[icBind] lookup ", t.kind, " ", typeToString(t), " uid=", t.uniqueId.module, ".",
|
||||
t.uniqueId.item, " itemId=", t.itemId.module, ".", t.itemId.item,
|
||||
" state=", t.state, " flags=", t.flags, " -> ",
|
||||
(if result != nil: typeToString(result) else: "MISS"),
|
||||
" allowMeta=", cl.allowMetaTypes
|
||||
if result == nil:
|
||||
if cl.allowMetaTypes or tfRetType in t.flags: return
|
||||
localError(cl.c.config, t.sym.info, "cannot instantiate: '" & typeToString(t) & "'")
|
||||
@@ -401,7 +415,7 @@ proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType =
|
||||
proc instCopyType*(cl: var TReplTypeVars, t: PType): PType =
|
||||
# XXX: relying on allowMetaTypes is a kludge
|
||||
if cl.allowMetaTypes:
|
||||
result = t.exactReplica
|
||||
result = t.exactReplica(cl.c.idgen)
|
||||
else:
|
||||
result = copyType(t, cl.c.idgen, t.owner)
|
||||
copyTypeProps(cl.c.graph, cl.c.idgen.module, result, t)
|
||||
@@ -446,6 +460,13 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
header[i] = x
|
||||
propagateToOwner(header, x)
|
||||
else:
|
||||
# Under IC `t` may be a loaded dep type (Sealed/immutable); mutating it
|
||||
# would assert, so propagate into a copy. For non-Sealed types keep
|
||||
# devel's in-place propagation: unconditionally copying here changes
|
||||
# `header != t` and with it the cached-instance lookup below, which
|
||||
# regressed non-IC generic instantiations (arraymancer: a cached
|
||||
# NimSeqV2 instance with stale flags was returned for a cast target).
|
||||
if header == t and t.state == Sealed: header = instCopyType(cl, t)
|
||||
propagateToOwner(header, x)
|
||||
|
||||
if header != t:
|
||||
@@ -497,8 +518,14 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
let bbody = last body
|
||||
var newbody = replaceTypeVarsT(cl, bbody, isInstValue = true)
|
||||
cl.skipTypedesc = oldSkipTypedesc
|
||||
newbody.flags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
|
||||
result.flags = result.flags + newbody.flags - tfInstClearedFlags
|
||||
let newbodyFlags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
|
||||
if newbody.state != Sealed:
|
||||
newbody.flags = newbodyFlags
|
||||
# else: `newbody` is a type loaded from a dep module (it can even be a
|
||||
# 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
|
||||
|
||||
setToPreviousLayer(cl.typeMap)
|
||||
|
||||
@@ -518,8 +545,11 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
# generics *when the type is constructed*:
|
||||
cl.c.graph.setAttachedOp(cl.c.module.position, newbody, attachedDeepCopy,
|
||||
cl.c.instTypeBoundOp(cl.c, dc, result, cl.info, attachedDeepCopy, 1))
|
||||
if newbody.typeInst == nil:
|
||||
if newbody.typeInst == nil and newbody.state != Sealed:
|
||||
# doAssert newbody.typeInst == nil
|
||||
# An IC-loaded (Sealed) `newbody` keeps whatever `typeInst` its defining
|
||||
# module serialized; recording this process's first instantiation on the
|
||||
# shared type is not possible (and was always first-wins anyway).
|
||||
newbody.typeInst = result
|
||||
if tfRefsAnonObj in newbody.flags and newbody.kind != tyGenericInst:
|
||||
# can come here for tyGenericInst too, see tests/metatype/ttypeor.nim
|
||||
@@ -808,7 +838,11 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
if t.kind == tyRef and t.hasElementType and t.elementType.kind == tyObject and t.elementType.n != nil:
|
||||
discard replaceObjBranches(cl, t.elementType.n)
|
||||
|
||||
elif result.n != nil and t.kind == tyObject:
|
||||
elif result.n != nil and t.kind == tyObject and result.state != Sealed:
|
||||
# A type loaded from the IC cache already had its object branches
|
||||
# resolved when it was originally compiled, and must not be mutated in
|
||||
# place (nor copied, which would break object-inheritance identity), so
|
||||
# only non-Sealed types are processed here.
|
||||
# Invalidate the type size as we may alter its structure
|
||||
result.size = -1
|
||||
result.n = replaceObjBranches(cl, result.n)
|
||||
@@ -860,7 +894,10 @@ proc recomputeFieldPositions*(t: PType; obj: PNode; currPosition: var int) =
|
||||
for i in 1..<obj.len:
|
||||
recomputeFieldPositions(nil, lastSon(obj[i]), currPosition)
|
||||
of nkSym:
|
||||
obj.sym.position = currPosition
|
||||
# A field loaded from the IC cache is already at its final position and must
|
||||
# not be mutated; only freshly instantiated fields need (re)positioning.
|
||||
if obj.sym.state != Sealed:
|
||||
obj.sym.position = currPosition
|
||||
inc currPosition
|
||||
else: discard "cannot happen"
|
||||
|
||||
|
||||
@@ -52,7 +52,17 @@ proc hashSym(c: var MD5Context, s: PSym) =
|
||||
c &= ":anon"
|
||||
else:
|
||||
var it = s
|
||||
when defined(icDbgHash):
|
||||
var ownerSteps = 0
|
||||
while it != nil:
|
||||
when defined(icDbgHash):
|
||||
inc ownerSteps
|
||||
if ownerSteps >= 1000 and ownerSteps <= 1030:
|
||||
echo "OWNERLOOP(hashSym) n=", ownerSteps, " sym=", it.name.s, " kind=", it.kind,
|
||||
" id=", it.itemId, " flags=", it.flags, " state=", it.state,
|
||||
" start=", s.name.s, " startId=", s.itemId
|
||||
elif ownerSteps == 1031:
|
||||
raiseAssert "owner-chain cycle detected, see OWNERLOOP dump above"
|
||||
c &= it.name.s
|
||||
c &= "."
|
||||
it = it.owner
|
||||
@@ -65,7 +75,17 @@ proc hashTypeSym(c: var MD5Context, s: PSym; conf: ConfigRef) =
|
||||
else:
|
||||
var it = s
|
||||
c &= customPath(conf.toFullPath(s.info))
|
||||
when defined(icDbgHash):
|
||||
var ownerSteps = 0
|
||||
while it != nil:
|
||||
when defined(icDbgHash):
|
||||
inc ownerSteps
|
||||
if ownerSteps >= 1000 and ownerSteps <= 1030:
|
||||
echo "OWNERLOOP n=", ownerSteps, " sym=", it.name.s, " kind=", it.kind,
|
||||
" id=", it.itemId, " flags=", it.flags, " state=", it.state,
|
||||
" start=", s.name.s, " startId=", s.itemId
|
||||
elif ownerSteps == 1031:
|
||||
raiseAssert "owner-chain cycle detected, see OWNERLOOP dump above"
|
||||
if sfFromGeneric in it.flags and it.kind in routineKinds and
|
||||
it.typ != nil:
|
||||
hashType c, it.typ, {CoProc}, conf
|
||||
@@ -102,15 +122,44 @@ proc hashTree(c: var MD5Context, n: PNode; flags: set[ConsiderFlag]; conf: Confi
|
||||
else:
|
||||
for i in 0..<n.len: hashTree(c, n[i], flags, conf)
|
||||
|
||||
when defined(icDbgHash):
|
||||
var hashDepth = 0
|
||||
var hashCalls = 0
|
||||
var hashMaxDepth = 0
|
||||
|
||||
proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) =
|
||||
if t == nil:
|
||||
c &= "\254"
|
||||
return
|
||||
when defined(icDbgHash):
|
||||
inc hashDepth
|
||||
inc hashCalls
|
||||
if hashDepth > hashMaxDepth: hashMaxDepth = hashDepth
|
||||
if hashCalls >= 500_000_000 and hashCalls <= 500_000_300:
|
||||
echo "HASHLOOP n=", hashCalls, " d=", hashDepth, " kind=", t.kind, " id=", t.itemId,
|
||||
" uniq=", t.uniqueId, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL"),
|
||||
" state=", t.state, " owner=", (if t.owner != nil: t.owner.name.s else: "NIL")
|
||||
elif hashCalls == 500_000_301:
|
||||
echo "HASHLOOP maxDepth=", hashMaxDepth
|
||||
raiseAssert "hashType runaway detected, see HASHLOOP dump above"
|
||||
defer:
|
||||
dec hashDepth
|
||||
|
||||
# Ensure type is fully loaded before hashing to avoid hash changing
|
||||
# as properties are accessed and trigger lazy loading.
|
||||
backendEnsureMutable(t)
|
||||
|
||||
# Bare type-class keywords used as a typedesc without arguments (e.g. `array`,
|
||||
# `range`, `distinct` passed to `signatureHash`) have no children, so the
|
||||
# structural branches below would index a non-existent `elementType`. Hash them
|
||||
# by kind (+ sym for an extra, stable distinction) — enough for a stable,
|
||||
# distinct identity. (`seq`/`openArray`/`tuple` already fall through the empty
|
||||
# `else` loop unharmed; this covers the branches that index `elementType`.)
|
||||
if t.kind in {tyArray, tyRange, tyDistinct} and not t.hasElementType:
|
||||
c &= char(t.kind)
|
||||
if t.sym != nil: c.hashSym(t.sym)
|
||||
return
|
||||
|
||||
case t.kind
|
||||
of tyGenericInvocation:
|
||||
for a in t.kids:
|
||||
@@ -248,6 +297,29 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
c.hashType(param.typ, flags, conf)
|
||||
c &= ','
|
||||
c.hashType(t.returnType, flags, conf)
|
||||
elif t.n != nil and t.n.kind == nkFormalParams:
|
||||
# Under IC a loaded proc type stores its parameters only in `n`; `sons`
|
||||
# holds just the return type. Hashing `t.signature` would silently drop
|
||||
# every parameter, collapsing distinct proc types onto one hash, so the
|
||||
# same logical type got different C struct names in different TUs
|
||||
# ("incompatible type for argument" on closure args). Hash the return
|
||||
# type first and then the parameter types from `n` — for from-source
|
||||
# types `n`'s param types equal `sons[1..]`, so non-IC hashes are
|
||||
# unchanged. (Same fix as typekeys' tyProc branch.)
|
||||
c.hashType(t.returnType, flags, conf)
|
||||
for i in 1..<t.n.len:
|
||||
let p = t.n[i]
|
||||
if p.kind == nkSym:
|
||||
backendEnsureMutable(p.sym)
|
||||
# The hidden closure env param: under IC, lambda lifting shares the
|
||||
# routine's AST params with `typ.n`, so the lifted `:envP` leaks into
|
||||
# the TYPE's params (from-source types never carry it). It is not part
|
||||
# of the type's identity — `genProcParams` skips it the same way.
|
||||
if t.callConv == ccClosure and p.sym.name.s == ":envP":
|
||||
continue
|
||||
c.hashType(p.sym.typ, flags, conf)
|
||||
else:
|
||||
c.hashType(p.typ, flags, conf)
|
||||
else:
|
||||
for a in t.signature: c.hashType(a, flags, conf)
|
||||
c &= char(t.callConv)
|
||||
@@ -263,6 +335,21 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
c &= char(t.kind)
|
||||
c.hashType(t.indexType, flags-{CoIgnoreRange}+{CoIgnoreRangeInArray}, conf)
|
||||
c.hashType(t.elementType, flags-{CoIgnoreRange}, conf)
|
||||
of tyBuiltInTypeClass:
|
||||
# A builtin type class (`object`, `tuple`, `proc`, `ref`, `seq`, ...) is
|
||||
# identified solely by the *kind* of its single placeholder son plus a few
|
||||
# flags/callConv (see `sameType`). That son is a fresh, field-less, sym-less
|
||||
# type, so the generic `else` below would recurse into it and hash its
|
||||
# process-local `t.id` — unstable across the NIF boundary. nim-serialization
|
||||
# keys auto-serialization on `signatureHash(object)`/`tuple`/... and missed
|
||||
# under IC because the registering and consuming modules minted different
|
||||
# placeholder ids. Hash the class identity that `sameType` actually compares.
|
||||
c &= char(t.kind)
|
||||
let elem = t.elementType
|
||||
c &= char(elem.kind)
|
||||
for f in eqTypeFlags * elem.flags: c &= char(ord(f))
|
||||
if elem.kind == tyProc and tfExplicitCallConv in elem.flags:
|
||||
c &= char(elem.callConv)
|
||||
else:
|
||||
c &= char(t.kind)
|
||||
for a in t.kids: c.hashType(a, flags, conf)
|
||||
|
||||
@@ -135,6 +135,11 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} =
|
||||
writeStackTrace()
|
||||
if c.c.module.name.s == "temp3":
|
||||
echo "binding ", key, " -> ", val
|
||||
when defined(icDbgRefc):
|
||||
if key.kind in {tyGenericParam, tyTypeDesc}:
|
||||
echo "[icBind] put ", key.kind, " ", typeToString(key), " uid=", key.uniqueId.module, ".",
|
||||
key.uniqueId.item, " itemId=", key.itemId.module, ".", key.itemId.item,
|
||||
" state=", key.state, " -> ", typeToString(val)
|
||||
put(c.bindings, key, val.skipIntLit(c.c.idgen))
|
||||
|
||||
proc typeRel*(c: var TCandidate, f, aOrig: PType,
|
||||
@@ -791,8 +796,10 @@ proc procParamTypeRel(c: var TCandidate; f, a: PType): TTypeRelation =
|
||||
# different C types (size_t vs unsigned long long).
|
||||
let fCheck = concreteType(c, f)
|
||||
let aCheck = concreteType(c, a)
|
||||
# Note that `result` is equal; now check whether they have the same
|
||||
# backend type.
|
||||
if fCheck != nil and aCheck != nil and
|
||||
not sameBackendTypePickyAliases(fCheck, aCheck):
|
||||
not sameBackendTypePickyAliases(fCheck, aCheck, {IgnoreFlags}):
|
||||
result = isNone
|
||||
|
||||
if result <= isSubrange or inconsistentVarTypes(f, a):
|
||||
@@ -909,7 +916,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
|
||||
case typ.kind
|
||||
of tyStatic:
|
||||
param = paramSym skConst
|
||||
param.typ = typ.exactReplica
|
||||
param.typ = typ.exactReplica(m.c.idgen)
|
||||
#copyType(typ, c.idgen, typ.owner)
|
||||
if typ.n == nil:
|
||||
param.typ.incl tfInferrableStatic
|
||||
@@ -917,7 +924,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
|
||||
param.ast = typ.n
|
||||
of tyFromExpr:
|
||||
param = paramSym skVar
|
||||
param.typ = typ.exactReplica
|
||||
param.typ = typ.exactReplica(m.c.idgen)
|
||||
#copyType(typ, c.idgen, typ.owner)
|
||||
else:
|
||||
param = paramSym skType
|
||||
@@ -970,7 +977,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
|
||||
if ff.kind == tyUserTypeClassInst:
|
||||
result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff)
|
||||
else:
|
||||
result = ff.exactReplica
|
||||
result = ff.exactReplica(m.c.idgen)
|
||||
#copyType(ff, c.idgen, ff.owner)
|
||||
|
||||
result.n = checkedBody
|
||||
@@ -1166,6 +1173,10 @@ proc enterConceptMatch(c: var TCandidate; f,a: PType, flags: TTypeRelFlags): TTy
|
||||
if concpt.kind != tyConcept:
|
||||
container = concpt
|
||||
concpt = container.reduceToBase
|
||||
# considerPreviousT-like behavior
|
||||
let prev = lookup(c.bindings, concpt)
|
||||
if prev != nil:
|
||||
return typeRel(c, prev, a, flags)
|
||||
if trDontBind in flags:
|
||||
conceptFlags.incl mfDontBind
|
||||
if trCheckGeneric in flags:
|
||||
@@ -1757,6 +1768,21 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
let ff = last(f)
|
||||
if ff != nil:
|
||||
result = typeRel(c, ff, a, flags)
|
||||
if result == isNone and a.kind == tyGenericInst and trBindGenericParam in flags:
|
||||
var depth = -1
|
||||
# Generic-parameter constraints like `F: Future` can miss in `last(f)`
|
||||
# when the actual type inherits from a concrete generic instantiation.
|
||||
# Keep this fallback scoped to generic-parameter matching so typedesc
|
||||
# overloads such as `type Future[T]` still prefer more specific
|
||||
# descendants like `InternalRaisesFuture[T, E]`.
|
||||
if isGenericSubtype(c, a, f, depth, f) and depth > 0:
|
||||
var askip = skippedNone
|
||||
let aobj = a.skipToObject(askip)
|
||||
if aobj != nil and tfFinal notin aobj.flags:
|
||||
# Keep overload ranking consistent with other inheritance-based
|
||||
# matches: deeper descendants are slightly worse candidates.
|
||||
inc c.inheritancePenalty, depth + int(c.inheritancePenalty < 0)
|
||||
result = isGeneric
|
||||
of tyGenericInvocation:
|
||||
var x = a.skipGenericAlias
|
||||
if x.kind == tyGenericParam and x.len > 0:
|
||||
@@ -2471,6 +2497,10 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
|
||||
return arg
|
||||
elif f.kind == tyStatic and arg.typ.n != nil:
|
||||
return arg.typ.n
|
||||
elif f.kind == tyUntyped:
|
||||
# bug #25693: a different overload candidate may have sem-checked the
|
||||
# operand and left symbols behind; templates expect the pristine AST.
|
||||
return argOrig
|
||||
else:
|
||||
return argSemantized # argOrig
|
||||
|
||||
@@ -2645,7 +2675,7 @@ proc staticAwareTypeRel(m: var TCandidate, f: PType, arg: var PNode): TTypeRelat
|
||||
# The ast of the type does not point to the symbol.
|
||||
# Without this we will never resolve a `static proc` with overloads
|
||||
let copiedNode = copyNode(arg)
|
||||
copiedNode.typ = exactReplica(copiedNode.typ)
|
||||
copiedNode.typ = exactReplica(copiedNode.typ, m.c.idgen)
|
||||
copiedNode.typ.n = arg
|
||||
arg = copiedNode
|
||||
typeRel(m, f, arg.typ)
|
||||
@@ -2849,6 +2879,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
|
||||
if m.calleeSym != nil and m.calleeSym.kind notin {skTemplate, skMacro}:
|
||||
c.mergeShadowScope
|
||||
else:
|
||||
c.rememberShadowDefs
|
||||
c.closeShadowScope
|
||||
m.state = csNoMatch
|
||||
m.firstMismatch.arg = a
|
||||
@@ -2905,7 +2936,10 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
|
||||
setSon(m.call, formal.position + 1, container)
|
||||
else:
|
||||
incrIndexType(container.typ)
|
||||
container.add n[a]
|
||||
# bug #25693: like the scalar `tyUntyped` case in `paramTypesMatchAux`,
|
||||
# a previous overload candidate may have sem-checked the operand in
|
||||
# place; templates/macros expect the pristine AST, so use `nOrig`.
|
||||
container.add nOrig[a]
|
||||
elif n[a].kind == nkExprEqExpr:
|
||||
# named param
|
||||
m.firstMismatch.kind = kUnknownNamedParam
|
||||
@@ -3004,7 +3038,8 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
|
||||
setSon(m.call, formal.position + 1, container)
|
||||
else:
|
||||
incrIndexType(container.typ)
|
||||
container.add n[a]
|
||||
# bug #25693: see the leading isVarargsUntyped branch above.
|
||||
container.add nOrig[a]
|
||||
else:
|
||||
m.baseTypeMatch = false
|
||||
m.typedescMatched = false
|
||||
@@ -3056,6 +3091,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
|
||||
if m.state == csMatch and not (m.calleeSym != nil and m.calleeSym.kind in {skTemplate, skMacro}):
|
||||
c.mergeShadowScope
|
||||
else:
|
||||
c.rememberShadowDefs
|
||||
c.closeShadowScope
|
||||
|
||||
inc a
|
||||
|
||||
@@ -394,9 +394,10 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
|
||||
accum.offset = 1
|
||||
computeObjectOffsetsFoldFunction(conf, typ.n, false, accum)
|
||||
let paddingAtEnd = int16(accum.finish())
|
||||
if typ.sym != nil and
|
||||
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc} and
|
||||
tfCompleteStruct notin typ.flags:
|
||||
if (typ.sym != nil and
|
||||
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc} and
|
||||
tfCompleteStruct notin typ.flags) or
|
||||
tfIncompleteStruct in typ.flags:
|
||||
typ.size = szUnknownSize
|
||||
typ.align = szUnknownSize
|
||||
typ.paddingAtEnd = szUnknownSize
|
||||
|
||||
@@ -22,7 +22,7 @@ import std / tables
|
||||
|
||||
import
|
||||
options, ast, astalgo, trees, msgs,
|
||||
idents, renderer, types, semfold, magicsys, cgmeth,
|
||||
idents, renderer, types, semfold, magicsys, cgmeth, parampatterns,
|
||||
lowerings, liftlocals,
|
||||
modulegraphs, lineinfos
|
||||
|
||||
@@ -90,11 +90,21 @@ proc getCurrOwner(c: PTransf): PSym =
|
||||
if c.transCon != nil: result = c.transCon.owner
|
||||
else: result = c.module
|
||||
|
||||
proc freshOwnedSym(c: PTransf; s, owner: PSym): PNode =
|
||||
# We need to copy the symbol here because we might need to change its owner and
|
||||
# we don't want to mess with the original symbol which might be used in other places.
|
||||
# This can happen for example for iterators which are transformed multiple times when
|
||||
# they are used in different contexts.
|
||||
var fresh = copySym(s, c.idgen)
|
||||
if fresh.kind notin routineKinds:
|
||||
incl(fresh.flagsImpl, sfFromGeneric)
|
||||
setOwner(fresh, owner)
|
||||
result = newSymNode(fresh)
|
||||
|
||||
proc newTemp(c: PTransf, typ: PType, info: TLineInfo): PNode =
|
||||
let r = newSym(skTemp, getIdent(c.graph.cache, genPrefix), c.idgen, getCurrOwner(c), info)
|
||||
r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias, tySink})
|
||||
incl(r.flagsImpl, sfFromGeneric)
|
||||
let owner = getCurrOwner(c)
|
||||
result = newSymNode(r)
|
||||
|
||||
proc transform(c: PTransf, n: PNode, noConstFold = false): PNode
|
||||
@@ -185,11 +195,39 @@ proc transformSym(c: PTransf, n: PNode): PNode =
|
||||
result = transformSymAux(c, n)
|
||||
|
||||
proc freshVar(c: PTransf; v: PSym): PNode =
|
||||
let owner = getCurrOwner(c)
|
||||
var newVar = copySym(v, c.idgen)
|
||||
incl(newVar.flagsImpl, sfFromGeneric)
|
||||
setOwner(newVar, owner)
|
||||
result = newSymNode(newVar)
|
||||
result = freshOwnedSym(c, v, getCurrOwner(c))
|
||||
|
||||
proc introduceNewRoutineHeaderSyms(c: PTransf; n: PNode; oldOwner, newOwner: PSym) =
|
||||
# We need to introduce new symbols for the parameters and result of a routine when
|
||||
# we copy it for inlining or closure generation.
|
||||
# Otherwise, we would have multiple nodes referring to the same parameter symbols which
|
||||
# can lead to problems when we need to change the owner of these symbols.
|
||||
case n.kind
|
||||
of nkSym:
|
||||
if n.sym.owner == oldOwner:
|
||||
c.transCon.mapping[n.sym.itemId] = freshOwnedSym(c, n.sym, newOwner)
|
||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||
discard
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
introduceNewRoutineHeaderSyms(c, n[i], oldOwner, newOwner)
|
||||
|
||||
proc copyRoutineTypeHeader(c: PTransf; oldProc, newProc: PSym) =
|
||||
# We need to copy the routine type header to ensure that
|
||||
# modifications to the newProc do not affect the oldProc.
|
||||
if oldProc.typ != nil and oldProc.typ.kind == tyProc and oldProc.typ.n != nil:
|
||||
newProc.typ = copyType(oldProc.typ, c.idgen, newProc)
|
||||
newProc.typ.n = newNodeI(oldProc.typ.n.kind, oldProc.typ.n.info)
|
||||
if oldProc.typ.n.len > 0:
|
||||
newProc.typ.n.add copyTree(oldProc.typ.n[0])
|
||||
for i in 1..<oldProc.typ.n.len:
|
||||
let oldParam = oldProc.typ.n[i].sym
|
||||
var newParam = getOrDefault(c.transCon.mapping, oldParam.itemId)
|
||||
if newParam == nil:
|
||||
newParam = freshOwnedSym(c, oldParam, newProc)
|
||||
c.transCon.mapping[oldParam.itemId] = newParam
|
||||
doAssert newParam.kind == nkSym
|
||||
newProc.typ.addParam newParam.sym
|
||||
|
||||
proc transformVarSection(c: PTransf, v: PNode): PNode =
|
||||
result = newTransNode(v)
|
||||
@@ -338,11 +376,18 @@ proc introduceNewLocalVars(c: PTransf, n: PNode): PNode =
|
||||
return n
|
||||
of nkLambdaKinds, nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: # todo optimize nosideeffects?
|
||||
result = newTransNode(n)
|
||||
let x = newSymNode(copySym(n[namePos].sym, c.idgen))
|
||||
c.transCon.mapping[n[namePos].sym.itemId] = x
|
||||
let oldProc = n[namePos].sym
|
||||
let x = freshOwnedSym(c, oldProc, oldProc.owner)
|
||||
c.transCon.mapping[oldProc.itemId] = x
|
||||
introduceNewRoutineHeaderSyms(c, n[paramsPos], oldProc, x.sym)
|
||||
if resultPos < n.len and n[resultPos] != nil:
|
||||
introduceNewRoutineHeaderSyms(c, n[resultPos], oldProc, x.sym)
|
||||
copyRoutineTypeHeader(c, oldProc, x.sym)
|
||||
result[namePos] = x # we have to copy proc definitions for iters
|
||||
for i in 1..<n.len:
|
||||
result[i] = introduceNewLocalVars(c, n[i])
|
||||
if x.sym.typ != nil and x.sym.typ.kind == tyProc:
|
||||
result[paramsPos] = x.sym.typ.n
|
||||
result[namePos].sym.ast = result
|
||||
else:
|
||||
result = newTransNode(n)
|
||||
@@ -675,7 +720,7 @@ type
|
||||
paDirectMapping, paFastAsgn, paFastAsgnTakeTypeFromArg
|
||||
paVarAsgn, paComplexOpenarray, paViaIndirection
|
||||
|
||||
proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
|
||||
proc putArgInto(arg: PNode, formal: PType; borrowedFirstArg = false): TPutArgInto =
|
||||
# This analyses how to treat the mapping "formal <-> arg" in an
|
||||
# inline context.
|
||||
if formal.kind == tyTypeDesc: return paDirectMapping
|
||||
@@ -726,6 +771,13 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
|
||||
if skipTypes(formal, abstractInst).kind in {tyVar, tyLent}: result = paVarAsgn
|
||||
else: result = paFastAsgn
|
||||
|
||||
if borrowedFirstArg and result == paDirectMapping and parampatterns.exprRoot(arg) == nil and
|
||||
parampatterns.isAssignable(nil, arg) == arNone:
|
||||
# Inline iterators like `items(array)` borrow from the first argument.
|
||||
# If that argument is just a transient expression, materialize it so the
|
||||
# lifted closure keeps the backing storage alive across yields.
|
||||
result = paFastAsgnTakeTypeFromArg
|
||||
|
||||
proc findWrongOwners(c: PTransf, n: PNode) =
|
||||
if n.kind == nkVarSection:
|
||||
let x = n[0][0]
|
||||
@@ -824,13 +876,16 @@ proc transformFor(c: PTransf, n: PNode): PNode =
|
||||
if iter.kind != skIterator: return result
|
||||
# generate access statements for the parameters (unless they are constant)
|
||||
pushTransCon(c, newC)
|
||||
let borrowedIterResult =
|
||||
iter.typ != nil and iter.typ.returnType != nil and
|
||||
skipTypes(iter.typ.returnType, abstractInst).kind in {tyLent, tyVar}
|
||||
for i in 1..<call.len:
|
||||
var arg = transform(c, call[i])
|
||||
let ff = skipTypes(iter.typ, abstractInst)
|
||||
# can happen for 'nim check':
|
||||
if i >= ff.n.len: return result
|
||||
var formal = ff.n[i].sym
|
||||
let pa = putArgInto(arg, formal.typ)
|
||||
let pa = putArgInto(arg, formal.typ, borrowedIterResult and i == 1)
|
||||
case pa
|
||||
of paDirectMapping:
|
||||
newC.mapping[formal.itemId] = arg
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
## Based on sighashes.nim but works on astdef directly as we need it in ast2nif.nim.
|
||||
## Also produces more readable names thanks to treemangler.
|
||||
|
||||
import std/assertions
|
||||
import std/[assertions, sets]
|
||||
|
||||
import "../dist/nimony/src/lib" / [treemangler]
|
||||
import "../dist/nimony/src/gear2" / modnames
|
||||
@@ -54,6 +54,9 @@ type
|
||||
m: Mangler
|
||||
tl: TypeLoader
|
||||
sl: SymLoader
|
||||
visited: HashSet[ItemId] # anonymous object types whose fields are currently
|
||||
# being hashed — a non-mutating guard against endless
|
||||
# recursion when a field references the type itself.
|
||||
|
||||
proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef)
|
||||
proc symKey(c: var Context; s: PSym; conf: ConfigRef) =
|
||||
@@ -67,14 +70,26 @@ proc symKey(c: var Context; s: PSym; conf: ConfigRef) =
|
||||
name.add '.'
|
||||
name.addInt s.disamb
|
||||
|
||||
# The owner may still be an unloaded stub (kind `skStub`): force it in
|
||||
# before inspecting its kind, otherwise the module suffix is silently
|
||||
# dropped from the key and def-vs-use keys diverge — e.g. `Lexer`'s base
|
||||
# class keyed as `TBaseLexer.0.` at nifc vs `TBaseLexer.0.nimqydn3y` at
|
||||
# sem time, making `getAttachedOp` miss ("'=destroy' operator not found").
|
||||
template forceLoaded(x: PSym): PSym =
|
||||
let tmp = x
|
||||
if tmp != nil and tmp.state == Partial and c.sl != nil: c.sl(tmp)
|
||||
tmp
|
||||
|
||||
let owner = forceLoaded(s.ownerFieldImpl)
|
||||
let it =
|
||||
if s.kindImpl == skModule:
|
||||
s
|
||||
elif s.kindImpl in skProcKinds and sfFromGeneric in s.flagsImpl and s.ownerFieldImpl.kindImpl != skModule:
|
||||
s.ownerFieldImpl.ownerFieldImpl
|
||||
elif s.kindImpl in skProcKinds and sfFromGeneric in s.flagsImpl and
|
||||
owner != nil and owner.kindImpl != skModule:
|
||||
forceLoaded(owner.ownerFieldImpl)
|
||||
else:
|
||||
s.ownerFieldImpl
|
||||
if it.kindImpl == skModule:
|
||||
owner
|
||||
if it != nil and it.kindImpl == skModule:
|
||||
name.add '.'
|
||||
name.add modname(it, conf)
|
||||
c.m.addSymbol(name)
|
||||
@@ -138,7 +153,12 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
for a in t.sonsImpl:
|
||||
c.typeKey a, flags, conf
|
||||
of tyDistinct:
|
||||
if CoDistinct in flags:
|
||||
if t.sonsImpl.len == 0:
|
||||
# a bare `distinct` typeclass (e.g. `foo(distinct, ...)` matched
|
||||
# against a `T: type` param) has no base type to key — it IS its kind
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
c.m.addEmpty()
|
||||
elif CoDistinct in flags:
|
||||
if t.symImpl != nil: symKey(c, t.symImpl, conf)
|
||||
if t.symImpl == nil or tfFromGeneric in t.flagsImpl:
|
||||
c.typeKey t.sonsImpl[^1], flags, conf
|
||||
@@ -147,7 +167,14 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
else:
|
||||
symKey(c, t.symImpl, conf)
|
||||
of tyGenericInst:
|
||||
if sfInfixCall in t.sonsImpl[0].symImpl.flagsImpl:
|
||||
# The generic head (son[0]) may be a lazily-loaded stub under IC; ensure it
|
||||
# is materialised before peeking at its symbol. A nil sym means this is not
|
||||
# an imported C++ generic, so fall through to the normal `skipModifierB`.
|
||||
var base = t.sonsImpl[0]
|
||||
if base.state == Partial:
|
||||
assert c.tl != nil
|
||||
c.tl(base)
|
||||
if base.symImpl != nil and sfInfixCall in base.symImpl.flagsImpl:
|
||||
# This is an imported C++ generic type.
|
||||
# We cannot trust the `lastSon` to hold a properly populated and unique
|
||||
# value for each instantiation, so we hash the generic parameters here:
|
||||
@@ -216,14 +243,48 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
if t.typeInstImpl != nil:
|
||||
# prevent against infinite recursions here, see bug #8883:
|
||||
let inst = t.typeInstImpl
|
||||
if inst.state == Partial:
|
||||
# a lazily-loaded typeInst stub has no sons until forced in
|
||||
assert c.tl != nil
|
||||
c.tl(inst)
|
||||
t.typeInstImpl = nil # IC: spurious writes are ok since we set it back immediately
|
||||
assert inst.kind == tyGenericInst
|
||||
c.typeKey inst.sonsImpl[0], flags, conf
|
||||
if inst.sonsImpl.len > 0:
|
||||
c.typeKey inst.sonsImpl[0], flags, conf
|
||||
for i in 1..<inst.sonsImpl.len-1:
|
||||
c.typeKey inst.sonsImpl[i], flags, conf
|
||||
# Match sighashes: generic-instantiation arguments are keyed with
|
||||
# `CoDistinct` so distinct args are not collapsed to their base.
|
||||
c.typeKey inst.sonsImpl[i], flags+{CoDistinct}, conf
|
||||
t.typeInstImpl = inst
|
||||
elif t.symImpl != nil:
|
||||
c.symKey(t.symImpl, conf)
|
||||
# Anonymous / gensym'd object types (e.g. closure environments and
|
||||
# `ref object` ObjectTypes) share the placeholder name `´anon`, so `symKey`
|
||||
# alone collapses every one of them onto the same key — which made distinct
|
||||
# closure-env `=destroy`/`=sink` hooks collide. Mirror sighashes: when the
|
||||
# type symbol is anonymous/gensym'd, disambiguate further by keying the
|
||||
# field types and names (or `.empty` when there are none).
|
||||
template hasFlag(sym: PSym): bool =
|
||||
{sfAnon, sfGenSym} * sym.flagsImpl != {}
|
||||
if hasFlag(t.symImpl) or
|
||||
(t.kind == tyObject and t.ownerFieldImpl != nil and t.ownerFieldImpl.kindImpl == skType and
|
||||
t.ownerFieldImpl.typImpl != nil and t.ownerFieldImpl.typImpl.kind == tyRef and hasFlag(t.ownerFieldImpl)):
|
||||
if t.nImpl != nil and t.nImpl.len > 0:
|
||||
# Guard against endless recursion when a field references this type
|
||||
# itself. Unlike sighashes (which temporarily clears `sfAnon`/`sfGenSym`
|
||||
# on the symbol), do NOT mutate: `typeKey` runs during sem — it is
|
||||
# called unconditionally from `modulegraphs.setAttachedOp` — so a
|
||||
# mutation that an assertion deeper in `treeKey` left unrestored would
|
||||
# corrupt the type. `symKey` above already emitted the type's identity,
|
||||
# so on a back-reference we simply stop.
|
||||
if not containsOrIncl(c.visited, t.itemId):
|
||||
c.treeKey(t.nImpl, flags + {CoHashTypeInsideNode}, conf)
|
||||
c.visited.excl t.itemId
|
||||
else:
|
||||
c.m.addIdent "´empty"
|
||||
# Object inheritance is part of identity: key the base class too.
|
||||
if t.kind == tyObject and t.sonsImpl.len > 0 and t.sonsImpl[0] != nil:
|
||||
c.typeKey t.sonsImpl[0], flags, conf
|
||||
else:
|
||||
c.m.addIdent "`bug"
|
||||
of tyFromExpr:
|
||||
@@ -238,10 +299,19 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
c.symKey(t.nImpl[i].sym, conf)
|
||||
c.typeKey(t.nImpl[i].sym.typImpl, flags+{CoIgnoreRange}, conf)
|
||||
else:
|
||||
for i in 1..<t.sonsImpl.len:
|
||||
# ALL sons are tuple fields (son 0 included — unlike tyProc, where
|
||||
# son 0 is the return type). Starting at 1 dropped the first field,
|
||||
# collapsing e.g. `(PSym, NifIndexEntry)` and `(PType, NifIndexEntry)`
|
||||
# onto one key, so hook lookup called the wrong `=destroy`/`=sink`
|
||||
# (incompatible-argument C errors). Mirrors sighashes' `for a in t.kids`.
|
||||
for i in 0..<t.sonsImpl.len:
|
||||
c.typeKey t.sonsImpl[i], flags+{CoIgnoreRange}, conf
|
||||
of tyRange:
|
||||
if CoIgnoreRange notin flags:
|
||||
if t.sonsImpl.len == 0:
|
||||
# bare `range` typeclass: no base type, key the kind alone
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
c.m.addEmpty()
|
||||
elif CoIgnoreRange notin flags:
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
c.treeKey(t.nImpl, {}, conf)
|
||||
c.typeKey(t.sonsImpl[^1], flags, conf)
|
||||
@@ -254,12 +324,28 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
c.typeKey(t.skipModifierB, flags, conf)
|
||||
of tyProc:
|
||||
withTree c.m, (if tfIterator in t.flagsImpl: "itertype" else: "proctype"):
|
||||
if CoProc in flags and t.nImpl != nil:
|
||||
# Proc parameter *types* are part of the type's identity. Under IC the
|
||||
# parameters live in `nImpl` (`sonsImpl` holds only the return type), so a
|
||||
# loaded proc type has an empty `sonsImpl[1..]`; reading params from there
|
||||
# would silently drop them and collide every same-return/same-callconv
|
||||
# closure onto one key (e.g. `proc(cb: proc())` onto bare `proc()`),
|
||||
# which made hook lookup resolve to the wrong `=copy`. Prefer `nImpl`
|
||||
# (consistent in-memory and after load); hash param types only, not their
|
||||
# symbols — parameter names do not affect type identity.
|
||||
if t.nImpl != nil and t.nImpl.kind == nkFormalParams:
|
||||
let params = t.nImpl
|
||||
for i in 1..<params.len:
|
||||
let param = params[i].sym
|
||||
c.symKey(param, conf)
|
||||
c.typeKey(param.typImpl, flags, conf)
|
||||
if params[i].kind == nkSym:
|
||||
# The param sym may be a lazily-loaded stub: force it in (as `symKey`
|
||||
# does) so its type is available, then hash the param *type* only —
|
||||
# parameter names are not part of the type's identity. Without the
|
||||
# load the type reads back nil at codegen and the key silently loses
|
||||
# its parameters (collapsing distinct closure types onto one key).
|
||||
let ps = params[i].sym
|
||||
if ps.state == Partial and c.sl != nil: c.sl(ps)
|
||||
c.typeKey(ps.typImpl, flags, conf)
|
||||
else:
|
||||
c.typeKey(params[i].typField, flags, conf)
|
||||
else:
|
||||
for i in 1..<t.sonsImpl.len:
|
||||
c.typeKey(t.sonsImpl[i], flags, conf)
|
||||
@@ -270,8 +356,12 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
if tfVarargs in t.flagsImpl: c.m.addIdent "´varargs"
|
||||
of tyArray:
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
c.typeKey(t.sonsImpl[^1], flags-{CoIgnoreRange}, conf)
|
||||
c.typeKey(t.sonsImpl[0], flags-{CoIgnoreRange}, conf)
|
||||
if t.sonsImpl.len == 0:
|
||||
# bare `array` typeclass: no element/index types
|
||||
c.m.addEmpty()
|
||||
else:
|
||||
c.typeKey(t.sonsImpl[^1], flags-{CoIgnoreRange}, conf)
|
||||
c.typeKey(t.sonsImpl[0], flags-{CoIgnoreRange}, conf)
|
||||
else:
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
for i in 0..<t.sonsImpl.len:
|
||||
@@ -280,6 +370,16 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
c.m.addIdent "´notnil"
|
||||
|
||||
proc typeKey*(t: PType; conf: ConfigRef; tl: TypeLoader; sl: SymLoader): string =
|
||||
var c: Context = Context(m: createMangler(30, -1), tl: tl, sl: sl)
|
||||
typeKey(c, t, {}, conf)
|
||||
var c: Context = Context(m: createMangler(30, -1), tl: tl, sl: sl,
|
||||
visited: initHashSet[ItemId]())
|
||||
# Mirror the flags liftdestructors uses for its `canonTypes` hash
|
||||
# (`hashType(skipped, {CoType, CoConsiderOwned, CoDistinct})`): hook keys must
|
||||
# distinguish what hook *lifting* distinguishes. With empty flags a generic
|
||||
# `distinct` instance (e.g. nilcheck's `SeqOfDistinct[T, U]`) took the bare
|
||||
# `symKey` branch — the sym is the generic's and thus SHARED by all
|
||||
# instances, so `SeqOfDistinct[I, PNode]` and `SeqOfDistinct[I, Nilability]`
|
||||
# collided onto one key and hook lookup returned the wrong `=sink`
|
||||
# ("incompatible type for argument" in the generated C). Under `CoDistinct` a
|
||||
# `tfFromGeneric` distinct keys as sym + base type, keeping instances apart.
|
||||
typeKey(c, t, {CoType, CoConsiderOwned, CoDistinct}, conf)
|
||||
result = c.m.extract()
|
||||
|
||||
@@ -1069,9 +1069,10 @@ proc sameBackendTypeIgnoreRange*(x, y: PType): bool =
|
||||
c.cmp = dcEqIgnoreDistinct
|
||||
result = sameTypeAux(x, y, c)
|
||||
|
||||
proc sameBackendTypePickyAliases*(x, y: PType): bool =
|
||||
proc sameBackendTypePickyAliases*(x, y: PType, flags: TTypeCmpFlags = {}): bool =
|
||||
var c = initSameTypeClosure()
|
||||
c.flags.incl {IgnoreTupleFields, IgnoreRangeShallow, PickyCAliases, PickyBackendAliases}
|
||||
c.flags.incl flags
|
||||
c.cmp = dcEqIgnoreDistinct
|
||||
result = sameTypeAux(x, y, c)
|
||||
|
||||
|
||||
@@ -185,6 +185,9 @@ proc root(v: var Partitions; start: int): int =
|
||||
proc potentialMutation(v: var Partitions; s: PSym; level: int; info: TLineInfo) =
|
||||
let id = variableId(v, s)
|
||||
if id >= 0:
|
||||
# mutated here => alive here: keep aliveEnd in sync so dangerousMutation catches
|
||||
# mutations recorded after the var's last use (e.g. via a call arg). See #25595.
|
||||
v.s[id].aliveEnd = max(v.s[id].aliveEnd, v.abstractTime)
|
||||
let r = root(v, id)
|
||||
let flags = if s.kind == skParam:
|
||||
if isConstParam(s):
|
||||
|
||||
@@ -28,7 +28,7 @@ from magicsys import getSysType
|
||||
const
|
||||
traceCode = defined(nimVMDebug)
|
||||
|
||||
when hasFFI:
|
||||
when defined(nimHasLibFFI): # == hasFFI; spelled out for the IC dep scanner
|
||||
import evalffi
|
||||
|
||||
|
||||
@@ -1310,6 +1310,9 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
var a = regs[rb].node
|
||||
if a.kind == nkVarTy: a = a[0]
|
||||
if a.kind == nkSym:
|
||||
# a macro observed this symbol's implementation: NeedsImpl edge to
|
||||
# its home module under IC.
|
||||
recordIcImplDep(c.graph, a.sym)
|
||||
regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit)
|
||||
else: copyTree(a.sym.ast)
|
||||
regs[ra].node.flags.incl nfIsRef
|
||||
@@ -1319,6 +1322,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
decodeB(rkNode)
|
||||
let a = regs[rb].node
|
||||
if a.kind == nkSym:
|
||||
recordIcImplDep(c.graph, a.sym)
|
||||
regs[ra].node =
|
||||
if a.sym.ast.isNil:
|
||||
newNode(nkNilLit)
|
||||
@@ -1951,7 +1955,21 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
if regs[rb].node.kind != nkSym:
|
||||
stackTrace(c, tos, pc, "node is not a symbol")
|
||||
else:
|
||||
regs[ra].node.strVal = $sigHash(regs[rb].node.sym, c.config)
|
||||
let shSym = regs[rb].node.sym
|
||||
# When `signatureHash` is applied to a type (e.g. a `T: typedesc`/generic
|
||||
# param), hash the *type* it denotes, not the parameter symbol. Hashing the
|
||||
# symbol routes through `hashNonProc`, which mixes in `s.disamb` — a
|
||||
# per-module instantiation counter. Under incremental compilation the
|
||||
# registering module and a consuming module instantiate the surrounding
|
||||
# generic separately, get different `disamb`s, and produce different
|
||||
# hashes for the same type (nim-serialization's auto-serialization lookup
|
||||
# missed because of this). Hashing the underlying type via `hashType` is
|
||||
# type-identity based and stable across the NIF boundary.
|
||||
let shTyp = shSym.typ
|
||||
if shTyp != nil and shTyp.kind == tyTypeDesc and shTyp.hasElementType:
|
||||
regs[ra].node.strVal = $hashType(shTyp.elementType, c.config)
|
||||
else:
|
||||
regs[ra].node.strVal = $sigHash(shSym, c.config)
|
||||
of opcSlurp:
|
||||
decodeB(rkNode)
|
||||
createStr regs[ra]
|
||||
|
||||
@@ -308,7 +308,7 @@ proc newCtx*(module: PSym; cache: IdentCache; g: ModuleGraph; idgen: IdGenerator
|
||||
callDepth: g.config.maxCallDepthVM,
|
||||
comesFromHeuristic: unknownLineInfo, callbacks: @[], callbackIndex: initTable[string, int](), errorFlag: "",
|
||||
cache: cache, config: g.config, graph: g, idgen: idgen,
|
||||
contstantTab: initNodeTable(true))
|
||||
contstantTab: initNodeTable(true), templInstCounter: new int)
|
||||
|
||||
proc refresh*(c: PCtx, module: PSym; idgen: IdGenerator) =
|
||||
c.module = module
|
||||
|
||||
@@ -36,7 +36,7 @@ import
|
||||
magicsys, options, lowerings, lineinfos, transf, astmsgs,
|
||||
treetab
|
||||
|
||||
from modulegraphs import getBody
|
||||
from modulegraphs import getBody, recordIcImplDep
|
||||
|
||||
when defined(nimCompilerStacktraceHints):
|
||||
import std/stackframes
|
||||
@@ -46,7 +46,7 @@ const
|
||||
|
||||
when debugEchoCode:
|
||||
import std/private/asciitables
|
||||
when hasFFI:
|
||||
when defined(nimHasLibFFI): # == hasFFI; spelled out for the IC dep scanner
|
||||
import evalffi
|
||||
|
||||
type
|
||||
@@ -786,8 +786,12 @@ proc genBinaryABCD(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) =
|
||||
c.freeTemp(tmp2)
|
||||
c.freeTemp(tmp3)
|
||||
|
||||
template sizeOfLikeMsg(name): string =
|
||||
"'$1' requires '.importc' types to be '.completeStruct'" % [name]
|
||||
template sizeOfLikeMsg(name, incompleteStruct): string =
|
||||
block:
|
||||
if incompleteStruct:
|
||||
"'$1' cannot be used with '.incompleteStruct' types" % [name]
|
||||
else:
|
||||
"'$1' requires '.importc' types to be '.completeStruct'" % [name]
|
||||
|
||||
proc genNarrow(c: PCtx; n: PNode; dest: TDest) =
|
||||
let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
|
||||
@@ -1476,11 +1480,14 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}, m: TMag
|
||||
else:
|
||||
globalError(c.config, n.info, "expandToAst requires a call expression")
|
||||
of mSizeOf:
|
||||
globalError(c.config, n.info, sizeOfLikeMsg("sizeof"))
|
||||
let arg = n[1].typ.skipTypes({tyTypeDesc})
|
||||
globalError(c.config, n.info, sizeOfLikeMsg("sizeof", tfIncompleteStruct in arg.flags))
|
||||
of mAlignOf:
|
||||
globalError(c.config, n.info, sizeOfLikeMsg("alignof"))
|
||||
let arg = n[1].typ.skipTypes({tyTypeDesc})
|
||||
globalError(c.config, n.info, sizeOfLikeMsg("alignof", tfIncompleteStruct in arg.flags))
|
||||
of mOffsetOf:
|
||||
globalError(c.config, n.info, sizeOfLikeMsg("offsetof"))
|
||||
let arg = n[1].typ.skipTypes({tyTypeDesc})
|
||||
globalError(c.config, n.info, sizeOfLikeMsg("offsetof", tfIncompleteStruct in arg.flags))
|
||||
of mRunnableExamples:
|
||||
discard "just ignore any call to runnableExamples"
|
||||
of mDestroy, mTrace: discard "ignore calls to the default destructor"
|
||||
@@ -1775,8 +1782,15 @@ proc genGlobalInit(c: PCtx; n: PNode; s: PSym) =
|
||||
# This is rather hard to support, due to the laziness of the VM code
|
||||
# generator. See tests/compile/tmacro2 for why this is necessary:
|
||||
# var decls{.compileTime.}: seq[NimNode] = @[]
|
||||
# Load the slot's ADDRESS (not its value): the lazy initializer must REPLACE
|
||||
# the null slot, which `opcWrDeref` only does for an `rkNodeAddr` target
|
||||
# (`nAddr[] = n` for refs). With `opcLdGlobal` the slot value is loaded and for
|
||||
# a ref-typed global that value is an `nkNilLit` ("nil ref"); writing through it
|
||||
# hits the VM's nil-deref guard ("attempt to access a nil address"). This path
|
||||
# is reached for compile-time globals whose defining module is restored from a
|
||||
# NIF under `nim ic` (so `setupCompileTimeVar` never ran to eagerly init them).
|
||||
let dest = c.getTemp(s.typ)
|
||||
c.gABx(n, opcLdGlobal, dest, s.position)
|
||||
c.gABx(n, opcLdGlobalAddr, dest, s.position)
|
||||
if s.astdef != nil:
|
||||
let tmp = c.genx(s.astdef)
|
||||
c.genAdditionalCopy(n, opcWrDeref, dest, 0, tmp)
|
||||
@@ -1842,6 +1856,8 @@ proc genArrAccessOpcode(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
|
||||
if dest < 0: dest = c.getTemp(n.typ)
|
||||
if opc in {opcLdArrAddr, opcLdStrIdxAddr} and gfNodeAddr in flags:
|
||||
c.gABC(n, opc, dest, a, b)
|
||||
if c.prc.regInfo[a].kind >= slotTempUnknown:
|
||||
c.prc.regInfo[a].kind = slotTempPerm
|
||||
elif needsRegLoad():
|
||||
var cc = c.getTemp(n.typ)
|
||||
c.gABC(n, opc, cc, a, b)
|
||||
@@ -1858,6 +1874,8 @@ proc genObjAccessAux(c: PCtx; n: PNode; a, b: int, dest: var TDest; flags: TGenF
|
||||
if dest < 0: dest = c.getTemp(n.typ)
|
||||
if {gfNodeAddr} * flags != {}:
|
||||
c.gABC(n, opcLdObjAddr, dest, a, b)
|
||||
if a < c.prc.regInfo.len and c.prc.regInfo[a].kind >= slotTempUnknown:
|
||||
c.prc.regInfo[a].kind = slotTempPerm
|
||||
elif needsRegLoad():
|
||||
var cc = c.getTemp(n.typ)
|
||||
c.gABC(n, opcLdObj, cc, a, b)
|
||||
@@ -2456,6 +2474,10 @@ proc optimizeJumps(c: PCtx; start: int) =
|
||||
proc genProc(c: PCtx; s: PSym): VmProcInfo =
|
||||
result = c.procToCodePos.getOrDefault(s.id, NoVmProcInfo)
|
||||
if result.usedRegisters < 0:
|
||||
# compile-time execution consumes this routine's BODY: under IC that is a
|
||||
# NeedsImpl dependency on the routine's home module (iface-cookie gating
|
||||
# alone would miss body-only edits, e.g. `const x = dep.foo()`).
|
||||
recordIcImplDep(c.graph, s)
|
||||
#if s.name.s == "outterMacro" or s.name.s == "innerProc":
|
||||
# echo "GENERATING CODE FOR ", s.name.s
|
||||
let last = c.code.len-1
|
||||
@@ -2469,7 +2491,9 @@ proc genProc(c: PCtx; s: PSym): VmProcInfo =
|
||||
c.procToCodePos[s.id] = result
|
||||
# thanks to the jmp we can add top level statements easily and also nest
|
||||
# procs easily:
|
||||
inc c.graph.inVMTransform
|
||||
let body = transformBody(c.graph, c.idgen, s, if isCompileTimeProc(s): {} else: {useCache})
|
||||
dec c.graph.inVMTransform
|
||||
let procStart = c.xjmp(body, opcJmp, 0)
|
||||
var p = PProc(blocks: @[], sym: s)
|
||||
let oldPrc = c.prc
|
||||
|
||||
@@ -36,7 +36,9 @@ from std/osproc import nil
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/syncio
|
||||
else:
|
||||
when not defined(nimPreviewSlimSystem):
|
||||
# explicit negated `when` rather than `else:` so nifler's dep scanner guards
|
||||
# this import with its condition (it emits `else:` imports unconditionally).
|
||||
from std/formatfloat import addFloatRoundtrip, addFloatSprintf
|
||||
|
||||
|
||||
|
||||
@@ -152,6 +152,8 @@ proc sortVTableDispatchers*(g: ModuleGraph) =
|
||||
rootItemIdCount.inc(baseType.itemId)
|
||||
for idx in 0..<g.methods[bucket].methods.len:
|
||||
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
|
||||
if obj.itemId notin itemTable:
|
||||
itemTable[obj.itemId] = newSeq[PSym](methodIndexLen)
|
||||
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
|
||||
|
||||
for baseType in rootTypeSeq:
|
||||
|
||||
@@ -168,6 +168,19 @@ nimblepath="$home/.nimble/pkgs/"
|
||||
switch_gcc.cpp.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -D__SWITCH__ -fno-rtti -fno-exceptions -std=gnu++11"
|
||||
@end
|
||||
|
||||
# Emscripten toolchain for WebAssembly (wasm32, or wasm64/Memory64).
|
||||
@if emscripten:
|
||||
cc = clang
|
||||
clang.exe = "emcc"
|
||||
clang.linkerexe = "emcc"
|
||||
clang.cpp.exe = "emcc"
|
||||
clang.cpp.linkerexe = "emcc"
|
||||
@if wasm64:
|
||||
passC = "-sMEMORY64=1"
|
||||
passL = "-sMEMORY64=1"
|
||||
@end
|
||||
@end
|
||||
|
||||
# Configuration for the Intel C/C++ compiler:
|
||||
@if windows:
|
||||
icl.options.speed = "/Ox /arch:SSE2"
|
||||
|
||||
@@ -188,7 +188,7 @@ objectPart = IND{>} objectPart^+IND{=} DED
|
||||
/ objectWhen / objectCase / 'nil' / 'discard' / declColonEquals
|
||||
objectDecl = 'object' ('of' typeDesc)? COMMENT? objectPart
|
||||
conceptParam = ('var' | 'out' | 'ptr' | 'ref' | 'static' | 'type')? symbol
|
||||
conceptDecl = 'concept' conceptParam ^* ',' (pragma)? ('of' typeDesc ^* ',')?
|
||||
conceptDecl = 'concept' (conceptParam ^* ',' (pragma)?)? ('of' typeDesc ^* ',')?
|
||||
&IND{>} stmt
|
||||
typeDef = identVisDot genericParamList? pragma '=' optInd typeDefValue
|
||||
indAndComment?
|
||||
|
||||
412
doc/ic.md
412
doc/ic.md
@@ -2,165 +2,325 @@
|
||||
Incremental Compilation (IC)
|
||||
======================================
|
||||
|
||||
The ``nim ic`` command provides incremental compilation support for Nim projects,
|
||||
allowing faster rebuilds by reusing previously compiled intermediate representations
|
||||
of modules that haven't changed.
|
||||
The ``nim ic`` command provides incremental compilation for Nim projects. It
|
||||
decomposes compilation into per-module steps whose results are cached as NIF
|
||||
files, and uses the external ``nifmake`` build tool to re-run only the steps
|
||||
whose inputs changed.
|
||||
|
||||
This document describes **how `nim ic` works today**, including the edge cases
|
||||
that shaped the current design. The per-module backend rewrite that earlier
|
||||
editions of this document listed as a *Plan* has **landed**: the whole-program,
|
||||
reuse/redirect/def-retention backend is gone and codegen is now a set of
|
||||
`nifmake`-driven per-module rules (see *The backend*).
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
Incremental compilation works by decomposing the compilation process into several stages:
|
||||
The pipeline has two halves driven by one process (`nim ic`, `commandIc` in
|
||||
``compiler/deps.nim``) that constructs a dependency graph, writes a build file,
|
||||
and hands it to ``nifmake``:
|
||||
|
||||
1. **Parsing** - Source files are parsed into an abstract syntax tree (AST)
|
||||
2. **Semantic Analysis** - Symbols are resolved and type checking is performed
|
||||
3. **Code Generation** - Platform-specific code is generated from the analyzed AST
|
||||
4. **Linking** - The generated code is linked into an executable
|
||||
1. **Frontend** — per module:
|
||||
- ``nifler parse --deps`` turns ``.nim`` source into a parsed NIF
|
||||
(``.p.nif``) plus a static dependency list (``.deps.nif``).
|
||||
- ``nim m`` (the *semantic* step, `cmdM`) reads the parsed NIF + the
|
||||
precompiled NIFs of the module's imports, type-checks, and writes the
|
||||
**semmed NIF** (``.nif``) plus invalidation sidecars (see *Cookies*).
|
||||
2. **Backend** — ``nim nifc`` (`cmdNifC`, ``compiler/nifbackend.nim``) reads the
|
||||
semmed NIFs, generates C, compiles and links.
|
||||
|
||||
The IC mechanism caches the results of earlier stages in NIF files
|
||||
(Nim intermediate format): ``.p.nif`` (parsed), ``.deps.nif`` (dependencies),
|
||||
and ``.nif`` (semantically analyzed). When recompiling, only modules that have
|
||||
changed need to be reprocessed through the semantic analysis and code generation
|
||||
stages, significantly reducing compilation time for large projects.
|
||||
``nifmake`` orders the steps by their input/output files: every `nim m` runs
|
||||
before the `nim nifc` step that consumes its NIF, and a step re-fires only when
|
||||
one of its inputs is newer than its outputs. The driver invokes ``nifmake run
|
||||
--parallel`` by default, so independent steps at the same DAG depth fan out
|
||||
across cores; pass ``-d:icNoParallel`` to serialize (readable child output when
|
||||
debugging a build).
|
||||
|
||||
NIF File Format
|
||||
===============
|
||||
Artifacts (the NIF zoo)
|
||||
=======================
|
||||
|
||||
NIF (Nim Intermediate Format) files are text-based files that use a Lisp-like
|
||||
syntax. They employ a hybrid format where byte offsets into the text are used for
|
||||
efficient access, making them simultaneously human-readable and machine-efficient.
|
||||
The text representation is particularly valuable for debugging and introspection.
|
||||
Per module ``<suffix>`` (a content hash of the path; see *NIF symbols* below),
|
||||
under the nimcache directory:
|
||||
|
||||
Each ``.nim`` module produces its own ``.nif`` file during compilation.
|
||||
The NIF format contains:
|
||||
| File | Producer | Purpose |
|
||||
| ---- | -------- | ------- |
|
||||
| ``<s>.p.nif`` | nifler | parsed AST (syntactic) |
|
||||
| ``<s>.deps.nif`` | nifler | **static** import list (syntactic `import`s) |
|
||||
| ``<s>.s.deps.nif`` | `nim m` | **real** post-sem imports (incl. macro-generated); see *Discovery* |
|
||||
| ``<s>.nif`` | `nim m` | semmed module (symbols resolved, typed) |
|
||||
| ``<s>.iface.nif`` | `nim m` | **iface cookie**: hash of the importer-visible surface |
|
||||
| ``<s>.impl.nif`` | `nim m` | **impl cookie**: hash of the entire content (bodies included) |
|
||||
| ``<s>.edges.nif`` | `nim m` | **NeedsImpl edges**: modules whose bodies this sem consumed |
|
||||
| ``<s>.c.nif`` | `nim nifc` | the C text as a NIF, with def/ref markers for DCE & dedup |
|
||||
| ``ic_config.cfg.nif`` | driver | precompiled config replayed by every child (`icconfig.nim`) |
|
||||
| ``ic.version`` | driver | format stamp; a mismatch wipes the cache (`icFormatVersion`) |
|
||||
|
||||
- **Header** - Version information (e.g., `(.nif27)`)
|
||||
- **Dependencies** - List of source files and dependencies
|
||||
- **Interface** - Exported symbols and their indices
|
||||
- **Body** - The intermediate representation of the module's code in Lisp-like syntax
|
||||
|
||||
The NIF format is designed specifically for Nim and allows efficient serialization
|
||||
and deserialization of the compiler's intermediate representation while remaining
|
||||
readable and debuggable by tools and developers.
|
||||
|
||||
The ``nim ic`` Switch
|
||||
=====================
|
||||
|
||||
The ``nim ic`` command initiates incremental compilation for a project.
|
||||
It automatically manages the build process by:
|
||||
|
||||
1. Parsing all source files into ``.nif`` format (using the ``nifler`` tool)
|
||||
2. Performing semantic analysis on modified modules
|
||||
3. Generating code only for modules with changes or dependencies on changed modules
|
||||
4. Generating a build file (in NIFMake format) that orchestrates the compilation
|
||||
5. Executing the build file through ``nifmake``
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
- **nifler** - Tool for parsing Nim source files into NIF format. The ``nim ic`` command uses ``nifler parse --deps`` to generate both parsed files (``.p.nif``) and dependency files (``.deps.nif``).
|
||||
- **nifmake** - Build orchestration tool that follows dependencies and executes the build rules defined in ``.build.nif`` files.
|
||||
|
||||
If these tools are not available, ``nim ic`` will display instructions on how to
|
||||
obtain them.
|
||||
|
||||
Key Modules for IC Logic
|
||||
NIF symbols and ownership
|
||||
=========================
|
||||
|
||||
The primary modules in the compiler that handle incremental compilation logic are:
|
||||
(See ``../nifspec/doc/nif-spec.md``.) A global symbol is
|
||||
``<ident>.<disamb>.<moduleSuffix>``. For a **generic instantiation** the
|
||||
`<disamb>` is not a counter but a *content hash* — `setInstanceDisamb`
|
||||
(``modulegraphs.nim``) MD5s the generic's identity plus the `typeKey` of every
|
||||
concrete type argument, masks it to 30 bits and tags it with `InstanceDisambBit`.
|
||||
So the only part of the name that varies between two modules making the **same**
|
||||
instantiation (`seq[Foo]`) is the `<moduleSuffix>`. Two consequences drive the
|
||||
backend:
|
||||
|
||||
- **deps.nim** - Dependency analysis and build file generation. Contains the
|
||||
``commandIc`` procedure which is the main entry point for the ``nim ic`` command.
|
||||
This module orchestrates the incremental compilation process, handling dependency
|
||||
traversal (via ``nifler deps``), build rule generation, and build file creation.
|
||||
The build file is written to ``nifcache/`` directory. This module also explicitly
|
||||
models ``system.nim`` as a dependency of all modules.
|
||||
- **Instance names are content-addressed**: the same instantiation produced in
|
||||
different modules yields the *same* `<ident>.<disamb>`, so a deterministic dedup
|
||||
is possible by the *module-suffix-stripped* name. The cross-TU C name
|
||||
(`ccgtypes.sharedInstanceCName`) and the **merge** stage's live-set/owner
|
||||
decision (`nifbackend.computeMergeDecision`) both key on this stripped form.
|
||||
- **The suffix names a mint-site owner.** The `<moduleSuffix>` is the module
|
||||
*that minted the instance* (the instantiation site), so the same instance has a
|
||||
different full name in each module that makes it. Because every `cg` process
|
||||
emits the instances it demands (*emit-everywhere*), the same definition can be
|
||||
produced by several translation units; the **merge** stage then deterministically
|
||||
picks the single artifact allowed to embed each body (smallest claimant), which
|
||||
is the cross-process replacement for the old in-process single-writer machinery.
|
||||
|
||||
- **ast2nif.nim** - Core mapping between AST and NIF.
|
||||
The driver: graph construction (`commandIc`)
|
||||
============================================
|
||||
|
||||
1. Stamp/wipe the cache by ``icFormatVersion``.
|
||||
2. Seed the graph with the root module and **`system.nim`**. `system`'s entire
|
||||
import closure is folded into one node (one `nim m` invocation) — see
|
||||
*single-writer* below.
|
||||
3. ``traverseDeps`` runs ``nifler`` per module and reads ``.deps.nif`` to add
|
||||
import edges.
|
||||
4. **SCC grouping**: strongly-connected import cycles are collapsed (Tarjan).
|
||||
A singleton compiles as ``nim m <mod>``; a cycle compiles as one
|
||||
``nim m <rep> --icGroup:<member>…`` that builds every member *from source* in
|
||||
one process (resolving the recursion in memory) and writes each member's NIF.
|
||||
Only edges *leaving* the component become build-graph inputs.
|
||||
5. **Discovery fixpoint**: write the build file, run ``nifmake``; if it fails,
|
||||
re-derive the graph from every module's ``.s.deps.nif`` (adding nodes/edges
|
||||
for imports the static scanner missed), and retry. See *Discovery*.
|
||||
6. The backend step (`nim nifc`) depends on every module's semmed NIF, so
|
||||
``nifmake`` runs it last.
|
||||
|
||||
**Code, Logic & Debugging**
|
||||
===========================
|
||||
Invalidation: the cookie system
|
||||
================================
|
||||
|
||||
This section focuses on the compiler-side code paths, the logic you will
|
||||
inspect while debugging IC, and a pragmatic manual workflow for bug hunting
|
||||
using local invocations such as ``nim m --nimcache:nifcache``.
|
||||
A dependent must re-sem only when a dependency's relevant surface changed. Two
|
||||
hashes per module (``ast2nif.nim``):
|
||||
|
||||
Core places to inspect
|
||||
- **`compiler/deps.nim`**: generates the NIF-based build file and implements
|
||||
``commandIc`` (entry point for ``nim ic``). Look for how build rules are
|
||||
emitted (calls to the NIF builder) and how inputs/outputs are wired.
|
||||
- **`compiler/modulegraphs.nim`** and **`compiler/pipelines.nim`**:
|
||||
dependency graph and compilation pipeline integration — useful when a module
|
||||
is rebuilt unexpectedly.
|
||||
- **iface cookie** (``.iface.nif``): hashes only the *importer-visible* surface —
|
||||
exported declarations' **signatures** (for *all* routine kinds: plain procs,
|
||||
templates, macros, generics, `inline` procs alike), full content for
|
||||
consts/types, plus import/export/replay/hook records. Routine **bodies are
|
||||
excluded.** It also chains in the iface cookies of its own dependencies, so a
|
||||
surface change anywhere in the import closure propagates. A `nim m` rule for a
|
||||
module depends on its dependencies' iface cookies, so a body-only edit moves no
|
||||
iface cookie and stops the re-sem cascade.
|
||||
- **impl cookie** (``.impl.nif``): hashes the *entire* serialized content (private
|
||||
defs and bodies included), with the module's own iface mixed in.
|
||||
|
||||
Understanding the NIF text
|
||||
- NIF files are human-readable; open the per-module ``.nif`` files in
|
||||
``nifcache/`` to inspect parsed ASTs, dependency lists and interface tables.
|
||||
- Because NIF uses textual nodes and byte offsets, tools can quickly seek to
|
||||
positions in the file — but for debugging you usually only need to read the
|
||||
file top-to-bottom.
|
||||
**NeedsImpl edges** (``.edges.nif``): if a module *consumed another module's body*
|
||||
during sem — a macro expansion, a generic instantiation, a `getImpl`, or a
|
||||
compile-time call run in the VM — it records a strong edge. The dependent is then
|
||||
gated on that dependency's **impl** cookie instead of its iface cookie, so e.g.
|
||||
`const x = dep.foo()` re-sems when `foo`'s body changes. Recording sites:
|
||||
`semExprs.semTemplateExpr` (templates), `seminst.generateInstance` (generics),
|
||||
`vmgen.genProc` (VM/macros/CT procs), `vm.opcGetImpl` (`getImpl`). Inline
|
||||
iterators and `inline` procs are *not* tracked — they are inlined at codegen,
|
||||
where the backend's NIF-mtime invalidation re-codegens their users.
|
||||
|
||||
Manual bug-hunting workflow
|
||||
- Prepare a clean nimcache directory (relative to your project):
|
||||
Discovery of macro-generated imports
|
||||
====================================
|
||||
|
||||
```bash
|
||||
mkdir -p nifcache
|
||||
```
|
||||
The static scanner only sees syntactic `import`s. A macro can synthesize one
|
||||
(chronicles does `parseStmt("import chronicles/textlines")` driven by the
|
||||
`chronicles_sinks` define). Such an import is invisible until sem runs the macro.
|
||||
Each `nim m` records the imports it *actually* resolved (via the
|
||||
``semdata.addImportFileDep`` hook → ``graph.importDeps`` → ``ast2nif.writeSemDeps``)
|
||||
into ``<s>.s.deps.nif``; a child that fails on a not-yet-built import flushes it
|
||||
before erroring. The driver re-derives the graph from those sidecars — adding the
|
||||
missing node + the importer→import edge — and reruns to a fixpoint. (This replaced
|
||||
an earlier `icmissing.txt` side channel.)
|
||||
|
||||
- Parse/semantic-check a single module and write NIF/sem artifacts:
|
||||
The backend: per-module `nifc` stages
|
||||
=====================================
|
||||
|
||||
```bash
|
||||
nim m --nimcache:nifcache path/to/module.nim
|
||||
```
|
||||
Codegen is no longer one whole-program process. ``nim nifc`` (`cmdNifC`,
|
||||
``compiler/nifbackend.nim``) is invoked once per **stage** via
|
||||
``--icBackendStage:<stage>``; `commandIc` emits these as ordinary `nifmake` rules
|
||||
so "which TUs rebuild" is just "which rules `nifmake` re-fires from input mtimes"
|
||||
— exactly as the frontend already works. There are four stages:
|
||||
|
||||
- ``nim m`` runs the compiler up to the semantic checking stage for the
|
||||
specified module and emits intermediate cache files into ``nifcache/``.
|
||||
- Use this to reproduce and isolate failures in the semantic stage.
|
||||
1. **`cg`** (``--icBackendStage:cg --icBackendModule:<suffix>``) — generate C for
|
||||
the *single* named module and write only its ``<s>.c.nif`` artifact. A non-main
|
||||
target loads only its own import closure (`loadDepClosure`), so the whole
|
||||
program is **not** pulled into every parallel `cg` process. Codegen is still
|
||||
demand-driven and **emit-everywhere**: a `cg` process emits every entity it
|
||||
demands (generic instances, hooks, RTTI), referencing nothing `extern`-only.
|
||||
There is no whole-program DCE here — a liveness pass over all ~260 NIFs would
|
||||
cost ~900 MB for a result the merge stage recomputes anyway. The **main**
|
||||
module's `cg` is special: it loads everything (`loadBackendModules`), emits the
|
||||
whole-program method dispatchers and `NimMain`, and registers every other
|
||||
module's init/datInit from the `.c.nif` meta heads — so it runs *last*, after
|
||||
every other ``.c.nif`` exists. Every `cg` rule always leaves a ``.c.nif`` (empty
|
||||
if the module owns no code) so its nifmake output exists and the rule settles.
|
||||
2. **`merge`** (``--icBackendStage:merge``) — a pure artifact pass, *no module
|
||||
graph loaded*. Reads every ``.c.nif``, computes the one program-wide live set
|
||||
and, for each unique definition that several `cg` processes emitted, the single
|
||||
artifact allowed to embed its body; writes that to a merge-decision file
|
||||
(`computeMergeDecision` / `writeMergeDecision`). This is the cross-process
|
||||
replacement for the old in-process first-claimant + DCE coordination.
|
||||
3. **`emit`** (``--icBackendStage:emit --icBackendModule:<suffix>``) — render the
|
||||
target module's final ``.c`` from its ``.c.nif`` and the merge decision
|
||||
(`renderCFromArtifact`, dropping globally-dead and non-owned bodies). No codegen
|
||||
runs; the target is loaded only so `getCFile` yields the path `cg` wrote.
|
||||
4. **`link`** (``--icBackendStage:link``) — register every module's emitted ``.c``
|
||||
and run `extccomp.callCCompiler` once (it parallelizes per-file cc and skips
|
||||
up-to-date objects). Per-module C compile/link directives (`{.passL.}` etc.) are
|
||||
re-collected here via `replayBackendActions`, since the `cg` processes that
|
||||
originally saw them are separate processes (without this, e.g. `math`'s `-lm`
|
||||
would be lost → undefined `floor`/`pow` at link).
|
||||
|
||||
- Inspect the generated files for that module under ``nifcache/`` (look for
|
||||
``.nif``, sem/parsed artifacts). Because NIF is text-based you can open and
|
||||
grep it directly:
|
||||
Because each stage is a `nifmake` rule keyed on file mtimes, a body-only edit to
|
||||
one module re-fires that module's `cg`+`emit` (and the `merge`/`link`), not the
|
||||
whole program — and an unchanged module's `cg` does not run at all.
|
||||
|
||||
```bash
|
||||
sed -n '1,200p' nifcache/ModuleName.nif
|
||||
grep -n "someSymbol" -n nifcache/ModuleName.nif
|
||||
```
|
||||
Edge cases (and why the machinery exists)
|
||||
=========================================
|
||||
|
||||
- To reproduce a full incremental compilation of the project, generate the
|
||||
build file and run it (``nim ic`` automates this). The build file is generated
|
||||
in ``nifcache/`` directory. To debug an individual build step, run the command
|
||||
that the build file would execute manually:
|
||||
- Parsing step: ``nifler parse --deps input.nim`` (produces ``.p.nif`` and ``.deps.nif``)
|
||||
- Semantic step: ``nim m --nimcache:nifcache input.nim`` (produces ``.nif``)
|
||||
- Code generation: ``nim nifc --nimcache:nifcache input.nim`` (produces executable)
|
||||
- **Single-writer.** Instance type-ids are minted in process-local order, so if
|
||||
two `nim m` processes both write a module's NIF (e.g. a stdlib module pulled
|
||||
into `system`'s from-source closure *and* given its own rule), the second
|
||||
overwrites with different ids and every module checked against the first carries
|
||||
dangling refs ("symbol has no offset"). Fixed by folding `system`'s closure into
|
||||
one SCC and by **forwarding the project's defines** to every child so their
|
||||
`when` bodies (hence import sets and NIF contents) match the scanner's.
|
||||
- **`when … else: import`.** nifler emits `else`-branch imports unguarded, so a
|
||||
dead `else: import` would be scheduled. The compiler's own sources were rewritten
|
||||
to explicit negated `when`s; the vendored nifler later learned to negate prior
|
||||
conditions for the `else`.
|
||||
- **`nil` sons of loaded ASTs.** NIF dot-tokens load as `nil` where from-source
|
||||
ASTs have `nkEmpty`; several passes gained `nil` guards.
|
||||
- **Sealed loaded types.** Loaded types are `Sealed`; sem/transform mutate via
|
||||
`unsealForTransform`/`exactReplica(idgen)` (the latter mints a fresh `uniqueId`
|
||||
so serialized replicas don't collapse).
|
||||
- **Methods/RTTI ownership.** RTTI and type-bound hooks are emit-everywhere at
|
||||
`cg` and deduplicated by the `merge` stage, like generic instances; the main
|
||||
module's `cg` owns the whole-program method dispatchers.
|
||||
- **Config cost.** Each child re-parsing `nim.cfg` + re-running `config.nims` in
|
||||
the VM was ~80 ms; replaced by a precompiled `ic_config.cfg.nif` replayed in
|
||||
`loadConfigs` (`compiler/icconfig.nim`).
|
||||
- **`koch bootic`** bootstraps the compiler through `nim ic` (a 3-iteration
|
||||
fixed-point check). It writes its binary to ``bin/nim_ic`` and never clobbers
|
||||
``bin/nim``.
|
||||
|
||||
- Force a cache invalidation for a single module by removing its NIF/sem
|
||||
artifact and re-running the semantic step:
|
||||
Resolved by the rewrite
|
||||
-----------------------
|
||||
|
||||
```bash
|
||||
rm nifcache/ModuleName.nif
|
||||
nim m --nimcache:nifcache path/to/ModuleName.nim
|
||||
```
|
||||
The whole-program backend's hand-rolled mini-`nifmake` — `computeModuleReuse`,
|
||||
`enforceDefRetention`, `redirectToLiveModule`, the cached-defs/claim bookkeeping
|
||||
and the standalone `dce.nim` — **is gone**. Reuse is now just per-rule `nifmake`
|
||||
mtime checks, and the single-writer decision is the `merge` stage. The old
|
||||
**cross-mm / `--force` `var not init`** hazard dissolved with it: every codegen
|
||||
rule's config (including `--mm`) is a declared `nifmake` input, so a stale-config
|
||||
TU is simply rebuilt rather than mixed in. `koch bootic` is green under both `orc`
|
||||
and `--mm:refc`.
|
||||
|
||||
- When investigating incorrect replayed state (pragmas, `{.compile: ...}`):
|
||||
inspect the replay actions in ``compiler/ic/replayer.nim`` and open the
|
||||
module's NIF to find the ``toReplay``/action entries that will be executed
|
||||
during reload.
|
||||
Known residual hack
|
||||
-------------------
|
||||
|
||||
Tips for efficient debugging
|
||||
- Use ``--path:...`` flags when invoking ``nim m`` to emulate the exact
|
||||
search paths used in your project, e.g. ``--path:lib --path:vendor``.
|
||||
- Compare two successive ``.nif`` files with ``diff`` to see what changed and
|
||||
why a module was rebuilt.
|
||||
- `deps.runNifler` still uses `setLastModificationTime` to mark its scan
|
||||
up-to-date and deletes a stale parsed file to coordinate with the nifmake nifler
|
||||
rule — the driver duplicating nifmake's freshness logic. It is explicitly
|
||||
flagged in the source and folds away with a full frontend/nifler split.
|
||||
|
||||
Where to change behavior
|
||||
- Cache invalidation decisions and build-rule emission are implemented in
|
||||
``compiler/deps.nim``. When investigating surprising
|
||||
rebuilds, instrument those modules to log the footprint/hash/comparison
|
||||
outcome.
|
||||
Status and performance
|
||||
======================
|
||||
|
||||
`nim ic` self-builds the compiler (`koch bootic`'s byte-identical fixed-point
|
||||
check) under both `orc` and `--mm:refc`, and passes the external-package CI set.
|
||||
|
||||
Cold full bootstrap on a 32-core box (`-d:release`, **no edits** — IC's worst
|
||||
case, since incremental reuse is not exercised):
|
||||
|
||||
| | wall | notes |
|
||||
| - | ---- | ----- |
|
||||
| `koch boot` (classic) | ~1m00s | reference |
|
||||
| `koch bootic` (`nim ic`) | ~1m39s | **~1.66×** |
|
||||
|
||||
This is down from ~7.5× in the whole-program-backend era. IC does modestly more
|
||||
aggregate work (more processes, NIF re-parsing of imports per process), but on a
|
||||
many-core box that overhead is absorbed by the parallel `nim m`/`nifc` fan-out,
|
||||
and the C compile+link floor is shared with the classic backend. On few-core
|
||||
machines the cold gap is correspondingly wider — IC trades single-build latency
|
||||
for incremental latency.
|
||||
|
||||
The cold number is the *least* favourable comparison: it pays IC's full per-process
|
||||
overhead while using none of its incremental machinery. **Warm rebuilds — the
|
||||
actual point of IC — recompile only the modules whose inputs changed** (a body-only
|
||||
edit re-fires one module's `cg`+`emit`, not the program), so an edit-driven rebuild
|
||||
is a small fraction of either full build.
|
||||
|
||||
The strategic direction (decided 2026-06-13) is to make this NIF backend
|
||||
(`cmdNifC`) the **default** code generator. The per-module pipeline above is the
|
||||
realization of that direction; remaining work is *promotion + deletion* of the
|
||||
classic path, not new machinery.
|
||||
|
||||
Design notes and open decisions
|
||||
===============================
|
||||
|
||||
The per-module backend (above) mirrors Nimony's ``src/nimony/deps.nim``: the
|
||||
backend stopped re-implementing `nifmake`; each stage is a build rule, so reuse is
|
||||
just mtime checks and the merge stage is the only cross-module coordination.
|
||||
|
||||
Settled vs. open:
|
||||
|
||||
- **Ownership.** Emittable entities (generic instances, type-bound hooks, RTTI,
|
||||
lifted procs) are emit-everywhere at `cg` time and deduplicated at `merge` time
|
||||
(smallest claimant owns each unique body). The earlier idea of a *static*
|
||||
per-suffix owner computed before codegen was not needed — content-addressed names
|
||||
make the merge decision deterministic. The precise owner *rule* (minting module
|
||||
vs. root-type's module) can still be tuned where it would force a downstream
|
||||
package to own stdlib code.
|
||||
- **Remaining cleanup.** The `runNifler` `setLastModificationTime` coordination
|
||||
(above) folds away with a full frontend/nifler split; dead `when` imports could
|
||||
also be pruned during the `.s.deps` re-derivation.
|
||||
|
||||
Validation bar (held on every change): `koch bootic` must reach its byte-identical
|
||||
fixed point, and binary size must not regress (DCE parity), across the
|
||||
external-package CI set.
|
||||
|
||||
Code, logic & debugging
|
||||
========================
|
||||
|
||||
Core modules:
|
||||
- **`compiler/deps.nim`** — graph construction, SCC grouping, discovery fixpoint,
|
||||
build-file generation; `commandIc`.
|
||||
- **`compiler/ast2nif.nim`** — AST↔NIF, the cookie hashes (`cookieSd`,
|
||||
`writeIfaceCookie`, `writeImplCookie`, `writeEdgesFile`, `writeSemDeps`).
|
||||
- **`compiler/nifbackend.nim`** — the per-module backend stages (`generateCgStage`,
|
||||
`generateMergeStage`, `generateEmitStage`, `generateLinkStage`).
|
||||
- **`compiler/cnif.nim`** — `.c.nif` artifact read/write, `computeMergeDecision`,
|
||||
`renderCFromArtifact`.
|
||||
- **`compiler/icconfig.nim`** — precompiled config.
|
||||
- **`compiler/pipelines.nim`** / **`modulegraphs.nim`** — pipeline integration and
|
||||
the graph state (`importDeps`, `icImplDeps`, `icCnifFiles`, `instDisambs`, …).
|
||||
|
||||
Manual workflow:
|
||||
- Frontend a module: ``nim m --nimcache:nifcache path/to/mod.nim`` (writes
|
||||
``.nif`` + cookies + ``.s.deps``).
|
||||
- Backend is stage-based (a bare ``nim nifc main.nim`` errors — there is no
|
||||
whole-program fallback). The exact per-stage commands `nifmake` runs are in the
|
||||
``*.backend.build.nif`` build file; rerun one directly against an existing cache,
|
||||
e.g. ``nim nifc --nimcache:nifcache --icBackendStage:cg --icBackendModule:<suffix> main.nim``
|
||||
to regenerate one module's ``.c.nif``, then ``--icBackendStage:merge`` /
|
||||
``:emit`` / ``:link``.
|
||||
- NIF and ``.c.nif`` files are text — open/grep them directly; ``diff`` two
|
||||
successive ``.nif`` to see why a module rebuilt.
|
||||
- Force a re-sem: delete the module's ``.nif`` and rerun `nim m`.
|
||||
- A stale-cache crash after editing the serialization layout means bumping
|
||||
``icFormatVersion`` (`compiler/options.nim`).
|
||||
|
||||
See also
|
||||
========
|
||||
|
||||
- `nif-spec` - NIF format specification (text format and node grammar):
|
||||
[nifspec/doc/nif-spec.md](../nifspec/doc/nif-spec.md)
|
||||
- NIF format spec: [nifspec/doc/nif-spec.md](../nifspec/doc/nif-spec.md)
|
||||
- NIFC (C-like target) spec: dist/nimony/doc/nifc-spec.md
|
||||
|
||||
@@ -596,12 +596,12 @@ Regular expressions
|
||||
|
||||
* [re](re.html)
|
||||
Procedures and operators for handling regular
|
||||
expressions. The current implementation uses PCRE2.
|
||||
expressions. The current implementation uses PCRE.
|
||||
|
||||
* [nre](nre.html)
|
||||
|
||||
Many help functions for handling regular expressions.
|
||||
The current implementation uses PCRE2.
|
||||
The current implementation uses PCRE.
|
||||
|
||||
Database support
|
||||
----------------
|
||||
@@ -661,9 +661,6 @@ Regular expressions
|
||||
* [pcre](pcre.html)
|
||||
Wrapper for the PCRE library.
|
||||
|
||||
* [pcre2](pcre2.html)
|
||||
Wrapper for the PCRE2 library.
|
||||
|
||||
|
||||
Database support
|
||||
----------------
|
||||
|
||||
@@ -6123,40 +6123,48 @@ instantiations cross multiple different modules:
|
||||
|
||||
```nim
|
||||
# module A
|
||||
type O* = object
|
||||
|
||||
proc genericA*[T](x: T) =
|
||||
mixin init
|
||||
init(x)
|
||||
```
|
||||
|
||||
```nim
|
||||
# module C
|
||||
import A
|
||||
|
||||
proc init*(x: O) = discard
|
||||
```
|
||||
|
||||
```nim
|
||||
import C
|
||||
|
||||
# module B
|
||||
import A, C
|
||||
|
||||
proc genericB*[T](x: T) =
|
||||
# Without the `bind init` statement C's init proc is
|
||||
# not available when `genericB` is instantiated:
|
||||
# Without the `bind init` statement, C's `init` proc is not
|
||||
# available when `genericA` is instantiated through `genericB`
|
||||
# from `module main`, which does not import C:
|
||||
bind init
|
||||
genericA(x)
|
||||
```
|
||||
|
||||
```nim
|
||||
# module C
|
||||
type O = object
|
||||
proc init*(x: var O) = discard
|
||||
```
|
||||
|
||||
```nim
|
||||
# module main
|
||||
import B, C
|
||||
import A, B
|
||||
|
||||
genericB O()
|
||||
genericB(O())
|
||||
```
|
||||
|
||||
In module B has an `init` proc from module C in its scope that is not
|
||||
taken into account when `genericB` is instantiated which leads to the
|
||||
instantiation of `genericA`. The solution is to `forward`:idx: these
|
||||
symbols by a `bind` statement inside `genericB`.
|
||||
Because `genericA` uses `mixin init`, `init` is an open symbol that is
|
||||
resolved when `genericA` is instantiated. Here `genericA` is instantiated
|
||||
through `genericB`, whose final instantiation happens in `module main`.
|
||||
Since `module main` does not import `module C`, `init` is not in scope at
|
||||
that point, and the instantiation fails with ``undeclared identifier: 'init'``.
|
||||
The `bind init` statement inside `genericB` forwards the `init` symbol that
|
||||
is visible in `module B` into the instantiation of `genericA`, which makes
|
||||
the example compile. This `bind`, which re-exposes a symbol to a nested
|
||||
generic instantiation, is a `delegating bind`:idx:.
|
||||
|
||||
|
||||
Templates
|
||||
@@ -7996,6 +8004,9 @@ underlying C `struct`:c: in a `sizeof` expression:
|
||||
pure, incompleteStruct.} = object
|
||||
```
|
||||
|
||||
Attempting to use `sizeof` on an `incompleteStruct` type at compile-time
|
||||
will error with "'sizeof' cannot be used with '.incompleteStruct' types".
|
||||
|
||||
|
||||
CompleteStruct pragma
|
||||
---------------------
|
||||
|
||||
@@ -86,7 +86,7 @@ That means you can always use only 1 such an option with logical OR, e.g.
|
||||
Meaning of `^`:literal: and `$`:literal:
|
||||
========================================
|
||||
|
||||
`nimgrep`:cmd: PCRE2 engine is run in a single-line mode so
|
||||
`nimgrep`:cmd: PCRE engine is run in a single-line mode so
|
||||
`^`:literal: matches the beginning of whole input *file* and
|
||||
`$`:literal: matches the end of *file* (or whole input *string* for
|
||||
options like `--filename`).
|
||||
@@ -97,7 +97,7 @@ Add the `(?m)`:literal: modifier to the beginning of your pattern for
|
||||
Examples
|
||||
========
|
||||
|
||||
All examples below use default PCRE2 Regex patterns:
|
||||
All examples below use default PCRE Regex patterns:
|
||||
|
||||
+ To search recursively in Nim files using style-insensitive identifiers:
|
||||
|
||||
|
||||
85
koch.nim
85
koch.nim
@@ -16,11 +16,11 @@ const
|
||||
ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1
|
||||
SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39"
|
||||
|
||||
NimonyStableCommit = "750aa47f2139fe5ad69f04b44428b752011fe873" # unversioned \
|
||||
NimonyStableCommit = "5fa72628a6867f8ca09f8955a493749cf65f006a" # 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-05-05
|
||||
# Commit from 2026-06-14
|
||||
|
||||
# examples of possible values for fusion: #head, #ea82b54, 1.2.3
|
||||
FusionStableHash = "#562467452b32cb7a97410ea177f083e6d8405734"
|
||||
@@ -76,6 +76,7 @@ Options:
|
||||
--skipIntegrityCheck skips integrity check when booting the compiler
|
||||
Possible Commands:
|
||||
boot [options] bootstraps with given command line options
|
||||
bootic [options] bootstraps via the incremental compiler (`nim ic`)
|
||||
distrohelper [bindir] helper for distro packagers
|
||||
tools builds Nim related tools
|
||||
toolsNoExternal builds Nim related tools (except external tools,
|
||||
@@ -406,6 +407,55 @@ proc boot(args: string, skipIntegrityCheck: bool) =
|
||||
if not skipIntegrityCheck:
|
||||
echo "[Warning] executables are still not equal"
|
||||
|
||||
proc bootic(args: string, skipIntegrityCheck: bool) =
|
||||
## Like `boot`, but bootstraps the compiler through the NIF-based incremental
|
||||
## compiler (`nim ic`) instead of `nim c`. Differences from `boot`:
|
||||
## * It starts from an already-bootstrapped Nim (found via `findStartNim`): the
|
||||
## csources compiler is far too old to provide the `ic` command, and the
|
||||
## `-d:nimKochBootstrap` define used by `boot`'s first stage *disables*
|
||||
## `commandIc`, so neither can be used here.
|
||||
## * `nim ic` drives the per-module build and the final link itself (via
|
||||
## `nifmake`), so there is no `--compileOnly` + `jsonscript` split.
|
||||
## The 3-step fixed-point check is kept: a successful run proves the compiler
|
||||
## can compile itself under IC and reproduces a stable binary.
|
||||
var output = "compiler" / "nim".exe
|
||||
# Deliberately NOT `bin/nim`: `bootic` must not clobber the development
|
||||
# compiler (that would replace a fast release `bin/nim` with bootic's build
|
||||
# and slow every later `koch`/`nim` invocation). The IC-bootstrapped binary
|
||||
# lands at `bin/nim_ic` instead; `bin/nim` is only ever read (via findStartNim).
|
||||
var finalDest = "bin" / "nim_ic".exe
|
||||
let smartNimcache = (if "release" in args or "danger" in args: "nimcache/ric_" else: "nimcache/dic_") &
|
||||
hostOS & "_" & hostCPU
|
||||
|
||||
bundleChecksums(false)
|
||||
|
||||
let nimStart = findStartNim().quoteShell()
|
||||
let times = 2 - ord(skipIntegrityCheck)
|
||||
# `boot` shares the `compiler/nim` output path; remove it so a fully warm
|
||||
# cache still relinks and iteration 1 cannot adopt a stale foreign binary.
|
||||
removeFile output
|
||||
for i in 0..times:
|
||||
echo "iteration: ", i+1
|
||||
# Iteration 1 may build incrementally (that's the point of IC), but every
|
||||
# later iteration must start from a clean cache: with a warm cache a
|
||||
# no-change rerun correctly rebuilds nothing, so iteration i+1 would just
|
||||
# keep iteration i's binary and the fixed-point check would be vacuous.
|
||||
# The check is only meaningful if the freshly built compiler re-translates
|
||||
# everything.
|
||||
if i > 0: removeDir smartNimcache
|
||||
let nimi = if i == 0: nimStart else: i.thVersion
|
||||
exec "$# ic --nimcache:$# $# compiler" / "nim.nim" %
|
||||
[nimi, smartNimcache, args]
|
||||
if sameFileContent(output, i.thVersion):
|
||||
copyExe(output, finalDest)
|
||||
echo "executables are equal: SUCCESS! (IC-bootstrapped compiler: ", finalDest, ")"
|
||||
return
|
||||
copyExe(output, (i+1).thVersion)
|
||||
copyExe(output, finalDest)
|
||||
when not defined(windows):
|
||||
if not skipIntegrityCheck:
|
||||
echo "[Warning] executables are still not equal"
|
||||
|
||||
# -------------- clean --------------------------------------------------------
|
||||
|
||||
const
|
||||
@@ -550,19 +600,39 @@ proc xtemp(cmd: string) =
|
||||
finally:
|
||||
copyExe(d / "bin" / "nim_backup".exe, d / "bin" / "nim".exe)
|
||||
|
||||
proc icTest(args: string) =
|
||||
temp("")
|
||||
let inp = os.parseCmdLine(args)[0]
|
||||
proc runIcTestFile(inp: string) =
|
||||
## Compile a single `tests/ic` file with `nim ic`, once per `#!EDIT!#` fragment
|
||||
## (each fragment is the file's source after that incremental edit). Only checks
|
||||
## that `nim ic` exits 0 — the produced binary's output is not verified here.
|
||||
let content = readFile(inp)
|
||||
let nimExe = getAppDir() / "bin" / "nim_temp".exe
|
||||
var i = 0
|
||||
for fragment in content.split("#!EDIT!#"):
|
||||
let file = inp.replace(".nim", "_temp.nim")
|
||||
writeFile(file, fragment)
|
||||
var cmd = nimExe & " ic --hint:Conf:off --warnings:off "
|
||||
cmd.add quoteShell(file)
|
||||
exec(cmd)
|
||||
inc i
|
||||
|
||||
# The `tests/ic` files that `nim ic` must keep compiling. Multi-module tests rely
|
||||
# on a sibling helper (`timp` -> `myimp`, `tcompiletimeglobal` -> `mctglobal`),
|
||||
# which exercises the NIF import/load path the single-file tests do not.
|
||||
const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils",
|
||||
"tcompiletimeglobal", "tsighashstable", "tpureenum", "tgenericoffer",
|
||||
"tconverterreexport"]
|
||||
|
||||
proc icTest(args: string) =
|
||||
temp("")
|
||||
let parsed = os.parseCmdLine(args)
|
||||
if parsed.len > 0 and parsed[0].len > 0:
|
||||
# `koch ic <file>`: run just that file.
|
||||
runIcTestFile(parsed[0])
|
||||
else:
|
||||
# `koch ic`: the full regression set we want to keep working — the test
|
||||
# suite plus both self-host bootstraps (`bootic` and `bootic -d:release`).
|
||||
for t in icSuite:
|
||||
runIcTestFile("tests" / "ic" / (t & ".nim"))
|
||||
bootic("", skipIntegrityCheck = false)
|
||||
bootic("-d:release", skipIntegrityCheck = false)
|
||||
|
||||
proc buildDrNim(args: string) =
|
||||
if not dirExists("dist/nimz3"):
|
||||
@@ -744,6 +814,7 @@ when isMainModule:
|
||||
of cmdArgument:
|
||||
case normalize(op.key)
|
||||
of "boot": boot(op.cmdLineRest, skipIntegrityCheck)
|
||||
of "bootic": bootic(op.cmdLineRest, skipIntegrityCheck)
|
||||
of "clean": clean(op.cmdLineRest)
|
||||
of "doc", "docs": buildDocs(op.cmdLineRest & " --d:nimPreviewSlimSystem " & paCode, localDocsOnly, localDocsOut)
|
||||
of "doc0", "docs0":
|
||||
|
||||
@@ -7,21 +7,21 @@
|
||||
#
|
||||
|
||||
when defined(js):
|
||||
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE2; See jsre for JS backend.".}
|
||||
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".}
|
||||
|
||||
## .. warning:: NRE is deprecated.
|
||||
## Use [Regex](https://github.com/nitely/nim-regex) or
|
||||
## `NRE2 <nre2.html>`_ that wraps Regex so that you can easily replace NRE.
|
||||
## This compatibility module uses PCRE2.
|
||||
## PCRE library is now at end of life.
|
||||
##
|
||||
## What is NRE?
|
||||
## ============
|
||||
##
|
||||
## A regular expression library for Nim using PCRE2 to do the hard work.
|
||||
## A regular expression library for Nim using PCRE to do the hard work.
|
||||
##
|
||||
## For documentation on how to write patterns, there exists `the official PCRE2
|
||||
## For documentation on how to write patterns, there exists `the official PCRE
|
||||
## pattern documentation
|
||||
## <https://www.pcre.org/current/doc/html/pcre2pattern.html>`_. You can also
|
||||
## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_. You can also
|
||||
## search the internet for a wide variety of third-party documentation and
|
||||
## tools.
|
||||
##
|
||||
@@ -39,8 +39,10 @@ runnableExamples:
|
||||
## Licencing
|
||||
## ---------
|
||||
##
|
||||
## PCRE2 is distributed under a BSD-style licence.
|
||||
## PCRE has `some additional terms`_ that you must agree to in order to use
|
||||
## this module.
|
||||
##
|
||||
## .. _`some additional terms`: https://pcre.sourceforge.net/license.txt
|
||||
runnableExamples:
|
||||
import std/sugar
|
||||
let vowels = re"[aeoui]"
|
||||
@@ -64,7 +66,7 @@ runnableExamples:
|
||||
assert find("uxabc", re"(?<=x|y)ab", start = 1).get.captures[-1] == "ab"
|
||||
assert find("uxabc", re"ab", start = 3).isNone
|
||||
|
||||
import std/pcre2 as pcre
|
||||
from std/pcre import nil
|
||||
import nre/private/util
|
||||
import std/tables
|
||||
from std/strutils import `%`
|
||||
@@ -80,6 +82,7 @@ type
|
||||
RegexDesc* = object
|
||||
pattern*: string
|
||||
pcreObj: ptr pcre.Pcre ## not nil
|
||||
pcreExtra: ptr pcre.ExtraData ## nil
|
||||
|
||||
captureNameToId: Table[string, int]
|
||||
|
||||
@@ -90,9 +93,9 @@ type
|
||||
##
|
||||
## `pattern: string`
|
||||
## : the string that was used to create the pattern. For details on how
|
||||
## to write a pattern, please see `the official PCRE2 pattern
|
||||
## to write a pattern, please see `the official PCRE pattern
|
||||
## documentation.
|
||||
## <https://www.pcre.org/current/doc/html/pcre2pattern.html>`_
|
||||
## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_
|
||||
##
|
||||
## `captureCount: int`
|
||||
## : the number of captures that the pattern has.
|
||||
@@ -137,23 +140,23 @@ type
|
||||
## NEL (next line, U+0085), LS (line separator, U+2028), and PS
|
||||
## (paragraph separator, U+2029). For the 8-bit library, the last two
|
||||
## are recognized only in UTF-8 mode.
|
||||
## -- man pcre2pattern
|
||||
## — man pcre
|
||||
##
|
||||
## - `(*JAVASCRIPT_COMPAT)` - JavaScript compatibility
|
||||
## - `(*NO_STUDY)` - turn off studying; study is enabled by default
|
||||
##
|
||||
## For more details on the leading option groups, see the `Option
|
||||
## Setting <https://www.pcre.org/current/doc/html/pcre2syntax.html#SEC16>`_
|
||||
## Setting <https://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`_
|
||||
## and the `Newline
|
||||
## Convention <https://www.pcre.org/current/doc/html/pcre2syntax.html#SEC17>`_
|
||||
## sections of the `PCRE2 syntax
|
||||
## manual <https://www.pcre.org/current/doc/html/pcre2syntax.html>`_.
|
||||
## Convention <https://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`_
|
||||
## sections of the `PCRE syntax
|
||||
## manual <https://man7.org/linux/man-pages/man3/pcresyntax.3.html>`_.
|
||||
##
|
||||
## Some of these options are not part of a pattern and are converted by nre
|
||||
## into PCRE2 flags. These include `NEVER_UTF`, `ANCHORED`,
|
||||
## Some of these options are not part of PCRE and are converted by nre
|
||||
## into PCRE flags. These include `NEVER_UTF`, `ANCHORED`,
|
||||
## `DOLLAR_ENDONLY`, `FIRSTLINE`, `NO_AUTO_CAPTURE`,
|
||||
## `JAVASCRIPT_COMPAT`, `U`, `NO_STUDY`. In other PCRE2 wrappers, you
|
||||
## will need to pass these as separate flags to PCRE2.
|
||||
## `JAVASCRIPT_COMPAT`, `U`, `NO_STUDY`. In other PCRE wrappers, you
|
||||
## will need to pass these as separate flags to PCRE.
|
||||
|
||||
RegexMatch* = object
|
||||
## Usually seen as `Option[RegexMatch]`, it represents the result of an
|
||||
@@ -193,7 +196,7 @@ type
|
||||
pattern*: Regex ## The regex doing the matching.
|
||||
## Not nil.
|
||||
str*: string ## The string that was matched against.
|
||||
pcreMatchBounds: seq[HSlice[csize_t, csize_t]] ## First item is the bounds of the match
|
||||
pcreMatchBounds: seq[HSlice[cint, cint]] ## First item is the bounds of the match
|
||||
## Other items are the captures
|
||||
## `a` is inclusive start, `b` is exclusive end
|
||||
|
||||
@@ -224,32 +227,38 @@ when defined(gcDestructors):
|
||||
when defined(nimAllowNonVarDestructor) and defined(nimPreviewNonVarDestructor):
|
||||
proc `=destroy`(pattern: RegexDesc) =
|
||||
`=destroy`(pattern.pattern)
|
||||
pcre.code_free(pattern.pcreObj)
|
||||
pcre.free_substring(cast[cstring](pattern.pcreObj))
|
||||
if pattern.pcreExtra != nil:
|
||||
pcre.free_study(pattern.pcreExtra)
|
||||
`=destroy`(pattern.captureNameToId)
|
||||
else:
|
||||
proc `=destroy`(pattern: var RegexDesc) =
|
||||
`=destroy`(pattern.pattern)
|
||||
pcre.code_free(pattern.pcreObj)
|
||||
pcre.free_substring(cast[cstring](pattern.pcreObj))
|
||||
if pattern.pcreExtra != nil:
|
||||
pcre.free_study(pattern.pcreExtra)
|
||||
`=destroy`(pattern.captureNameToId)
|
||||
else:
|
||||
proc destroyRegex(pattern: Regex) =
|
||||
`=destroy`(pattern.pattern)
|
||||
pcre.code_free(pattern.pcreObj)
|
||||
pcre.free_substring(cast[cstring](pattern.pcreObj))
|
||||
if pattern.pcreExtra != nil:
|
||||
pcre.free_study(pattern.pcreExtra)
|
||||
`=destroy`(pattern.captureNameToId)
|
||||
|
||||
proc getinfo[T](pattern: Regex, opt: uint32): T =
|
||||
proc getinfo[T](pattern: Regex, opt: cint): T =
|
||||
result = default(T)
|
||||
let retcode = pcre.pattern_info(pattern.pcreObj, opt, addr result)
|
||||
let retcode = pcre.fullinfo(pattern.pcreObj, pattern.pcreExtra, opt, addr result)
|
||||
|
||||
if retcode < 0:
|
||||
# XXX Error message that doesn't expose implementation details
|
||||
raise newException(FieldDefect, "Invalid getinfo for $1, errno $2" % [$opt, $retcode])
|
||||
|
||||
proc getNameToNumberTable(pattern: Regex): Table[string, int] =
|
||||
let entryCount = getinfo[uint32](pattern, pcre.INFO_NAMECOUNT).int
|
||||
let entrySize = getinfo[uint32](pattern, pcre.INFO_NAMEENTRYSIZE).int
|
||||
let entryCount = getinfo[cint](pattern, pcre.INFO_NAMECOUNT)
|
||||
let entrySize = getinfo[cint](pattern, pcre.INFO_NAMEENTRYSIZE)
|
||||
let table = cast[ptr UncheckedArray[uint8]](
|
||||
getinfo[pointer](pattern, pcre.INFO_NAMETABLE))
|
||||
getinfo[int](pattern, pcre.INFO_NAMETABLE))
|
||||
|
||||
result = initTable[string, int]()
|
||||
|
||||
@@ -265,69 +274,61 @@ proc getNameToNumberTable(pattern: Regex): Table[string, int] =
|
||||
|
||||
result[name] = num
|
||||
|
||||
proc pcreErrorMessage(errorCode: cint): string =
|
||||
var buffer: array[256, uint8]
|
||||
let length = pcre.get_error_message(errorCode, addr buffer[0], buffer.len.csize_t)
|
||||
if length >= 0:
|
||||
result = newString(length)
|
||||
if length > 0:
|
||||
copyMem(addr result[0], addr buffer[0], length)
|
||||
else:
|
||||
result = $errorCode
|
||||
|
||||
proc jitCompile(pattern: ptr pcre.Pcre) =
|
||||
var hasJit: cint = 0
|
||||
if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0 and hasJit == 1:
|
||||
discard pcre.jit_compile(pattern, pcre.JIT_COMPLETE.uint32)
|
||||
|
||||
proc initRegex(pattern: string, flags: uint32, study = true): Regex =
|
||||
proc initRegex(pattern: string, flags: int, study = true): Regex =
|
||||
when defined(gcDestructors):
|
||||
result = Regex()
|
||||
else:
|
||||
new(result, destroyRegex)
|
||||
result.pattern = pattern
|
||||
|
||||
var
|
||||
errorCode: cint = 0
|
||||
errOffset: csize_t = 0
|
||||
var errorMsg: cstring = ""
|
||||
var errOffset: cint = 0
|
||||
|
||||
result.pcreObj = pcre.compile(cast[ptr uint8](cstring(pattern)),
|
||||
pattern.len.csize_t, flags, addr errorCode,
|
||||
result.pcreObj = pcre.compile(cstring(pattern),
|
||||
# better hope int is at least 4 bytes..
|
||||
cint(flags), addr errorMsg,
|
||||
addr errOffset, nil)
|
||||
if result.pcreObj == nil:
|
||||
# failed to compile
|
||||
raise SyntaxError(msg: pcreErrorMessage(errorCode), pos: errOffset.int,
|
||||
pattern: pattern)
|
||||
raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern)
|
||||
|
||||
if study:
|
||||
jitCompile(result.pcreObj)
|
||||
var options: cint = 0
|
||||
var hasJit: cint = cint(0)
|
||||
if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0:
|
||||
if hasJit == 1'i32:
|
||||
options = pcre.STUDY_JIT_COMPILE
|
||||
result.pcreExtra = pcre.study(result.pcreObj, options, addr errorMsg)
|
||||
if errorMsg != nil:
|
||||
raise StudyError(msg: $errorMsg)
|
||||
|
||||
result.captureNameToId = result.getNameToNumberTable()
|
||||
|
||||
proc captureCount*(pattern: Regex): int =
|
||||
return getinfo[uint32](pattern, pcre.INFO_CAPTURECOUNT).int
|
||||
return getinfo[cint](pattern, pcre.INFO_CAPTURECOUNT)
|
||||
|
||||
proc captureNameId*(pattern: Regex): Table[string, int] =
|
||||
return pattern.captureNameToId
|
||||
|
||||
proc matchesCrLf(pattern: Regex): bool =
|
||||
let newline = getinfo[uint32](pattern, pcre.INFO_NEWLINE)
|
||||
case newline
|
||||
of pcre.NEWLINE_CRLF, pcre.NEWLINE_ANY, pcre.NEWLINE_ANYCRLF:
|
||||
let flags = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS))
|
||||
let newlineFlags = flags and (pcre.NEWLINE_CRLF or
|
||||
pcre.NEWLINE_ANY or
|
||||
pcre.NEWLINE_ANYCRLF)
|
||||
if newlineFlags > 0u32:
|
||||
return true
|
||||
of pcre.NEWLINE_CR, pcre.NEWLINE_LF, pcre.NEWLINE_NUL:
|
||||
return false
|
||||
else:
|
||||
discard
|
||||
|
||||
# get flags from build config
|
||||
var confFlags: uint32 = 0
|
||||
var confFlags: cint = cint(0)
|
||||
if pcre.config(pcre.CONFIG_NEWLINE, addr confFlags) != 0:
|
||||
assert(false, "CONFIG_NEWLINE apparently got screwed up")
|
||||
|
||||
case confFlags
|
||||
of pcre.NEWLINE_CR, pcre.NEWLINE_LF, pcre.NEWLINE_NUL: return false
|
||||
of pcre.NEWLINE_CRLF, pcre.NEWLINE_ANY, pcre.NEWLINE_ANYCRLF: return true
|
||||
of 13: return false
|
||||
of 10: return false
|
||||
of (13 shl 8) or 10: return true
|
||||
of -2: return true
|
||||
of -1: return true
|
||||
else: return false
|
||||
|
||||
|
||||
@@ -337,9 +338,7 @@ func captures*(pattern: RegexMatch): Captures = return Captures(pattern)
|
||||
|
||||
func contains*(pattern: CaptureBounds, i: int): bool =
|
||||
let pattern = RegexMatch(pattern)
|
||||
let index = i + 1
|
||||
index >= 0 and index < pattern.pcreMatchBounds.len and
|
||||
pattern.pcreMatchBounds[index].a != pcre.UNSET
|
||||
pattern.pcreMatchBounds[i + 1].a != -1
|
||||
|
||||
func contains*(pattern: Captures, i: int): bool =
|
||||
i in CaptureBounds(pattern)
|
||||
@@ -350,7 +349,7 @@ func `[]`*(pattern: CaptureBounds, i: int): HSlice[int, int] =
|
||||
raise newException(IndexDefect, "Group '" & $i & "' was not captured")
|
||||
|
||||
let bounds = pattern.pcreMatchBounds[i + 1]
|
||||
int(bounds.a) .. (int(bounds.b) - 1)
|
||||
int(bounds.a)..int(bounds.b-1)
|
||||
|
||||
func `[]`*(pattern: Captures, i: int): string =
|
||||
let pattern = RegexMatch(pattern)
|
||||
@@ -438,7 +437,8 @@ proc `$`*(pattern: RegexMatch): string =
|
||||
proc `==`*(a, b: Regex): bool =
|
||||
if not a.isNil and not b.isNil:
|
||||
return a.pattern == b.pattern and
|
||||
a.pcreObj == b.pcreObj
|
||||
a.pcreObj == b.pcreObj and
|
||||
a.pcreExtra == b.pcreExtra
|
||||
else:
|
||||
return system.`==`(a, b)
|
||||
|
||||
@@ -453,7 +453,7 @@ const PcreOptions = {
|
||||
"FIRSTLINE": pcre.FIRSTLINE,
|
||||
"NO_AUTO_CAPTURE": pcre.NO_AUTO_CAPTURE,
|
||||
"JAVASCRIPT_COMPAT": pcre.JAVASCRIPT_COMPAT,
|
||||
"U": pcre.UTF or pcre.UCP
|
||||
"U": pcre.UTF8 or pcre.UCP
|
||||
}.toTable
|
||||
|
||||
# Options that are supported inside regular expressions themselves
|
||||
@@ -503,63 +503,46 @@ proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study:
|
||||
|
||||
proc re*(pattern: string): Regex =
|
||||
let (pattern, flags, study) = extractOptions(pattern)
|
||||
initRegex(pattern, cast[uint32](flags), study)
|
||||
initRegex(pattern, flags, study)
|
||||
|
||||
func isInvalidUnicodeError(errorCode: cint): bool =
|
||||
(errorCode <= pcre.ERROR_UTF8_ERR1 and errorCode >= pcre.ERROR_UTF8_ERR21) or
|
||||
errorCode == pcre.ERROR_BADUTFOFFSET or
|
||||
errorCode == pcre.ERROR_DFA_UINVALID_UTF
|
||||
|
||||
proc newMatchData(pattern: Regex): ptr pcre.MatchData =
|
||||
result = pcre.match_data_create_from_pattern(pattern.pcreObj, nil)
|
||||
if result == nil:
|
||||
raise RegexInternalError(msg: "could not allocate PCRE2 match data")
|
||||
|
||||
proc matchImpl(str: string, pattern: Regex, start, endpos: int, options: uint32): Option[RegexMatch] =
|
||||
proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Option[RegexMatch] =
|
||||
var myResult = RegexMatch(pattern: pattern, str: str)
|
||||
myResult.pcreMatchBounds = newSeq[HSlice[csize_t, csize_t]](pattern.captureCount() + 1)
|
||||
# See PCRE man pages.
|
||||
# 2x capture count to make room for start-end pairs
|
||||
# 1x capture count as slack space for PCRE
|
||||
let vecsize = (pattern.captureCount() + 1) * 3
|
||||
# div 2 because each element is 2 cints long
|
||||
# plus 1 because we need the ceiling, not the floor
|
||||
myResult.pcreMatchBounds = newSeq[HSlice[cint, cint]]((vecsize + 1) div 2)
|
||||
myResult.pcreMatchBounds.setLen(vecsize div 3)
|
||||
|
||||
let strlen = if endpos == int.high: str.len else: endpos+1
|
||||
doAssert(strlen <= str.len) # don't want buffer overflows
|
||||
if start < 0 or start > strlen:
|
||||
return none(RegexMatch)
|
||||
|
||||
let matchData = newMatchData(pattern)
|
||||
defer: pcre.match_data_free(matchData)
|
||||
let execRet = pcre.match(pattern.pcreObj,
|
||||
cast[ptr uint8](cstring(str)),
|
||||
strlen.csize_t,
|
||||
start.csize_t,
|
||||
options,
|
||||
matchData,
|
||||
nil)
|
||||
let rawMatches = cast[ptr UncheckedArray[csize_t]](pcre.get_ovector_pointer(matchData))
|
||||
let ovectorCount = min(myResult.pcreMatchBounds.len,
|
||||
pcre.get_ovector_count(matchData).int)
|
||||
for i in 0 ..< ovectorCount:
|
||||
myResult.pcreMatchBounds[i] = rawMatches[i * 2] .. rawMatches[i * 2 + 1]
|
||||
|
||||
let execRet = pcre.exec(pattern.pcreObj,
|
||||
pattern.pcreExtra,
|
||||
cstring(str),
|
||||
cint(strlen),
|
||||
cint(start),
|
||||
cint(flags),
|
||||
cast[ptr cint](addr myResult.pcreMatchBounds[0]),
|
||||
cint(vecsize))
|
||||
if execRet >= 0:
|
||||
return some(myResult)
|
||||
|
||||
if isInvalidUnicodeError(execRet):
|
||||
let errorPos = if myResult.pcreMatchBounds.len > 0 and
|
||||
myResult.pcreMatchBounds[0].a != pcre.UNSET:
|
||||
myResult.pcreMatchBounds[0].a.int
|
||||
case execRet:
|
||||
of pcre.ERROR_NOMATCH:
|
||||
return none(RegexMatch)
|
||||
of pcre.ERROR_NULL:
|
||||
raise newException(AccessViolationDefect, "Expected non-null parameters")
|
||||
of pcre.ERROR_BADOPTION:
|
||||
raise RegexInternalError(msg: "Unknown pattern flag. Either a bug or " &
|
||||
"outdated PCRE.")
|
||||
of pcre.ERROR_BADUTF8, pcre.ERROR_SHORTUTF8, pcre.ERROR_BADUTF8_OFFSET:
|
||||
raise InvalidUnicodeError(msg: "Invalid unicode byte sequence",
|
||||
pos: myResult.pcreMatchBounds[0].a)
|
||||
else:
|
||||
start
|
||||
raise InvalidUnicodeError(msg: "Invalid unicode byte sequence", pos: errorPos)
|
||||
|
||||
case execRet
|
||||
of pcre.ERROR_NOMATCH:
|
||||
return none(RegexMatch)
|
||||
of pcre.ERROR_NULL:
|
||||
raise newException(AccessViolationDefect, "Expected non-null parameters")
|
||||
of pcre.ERROR_BADOPTION:
|
||||
raise RegexInternalError(msg: "Unknown pattern flag. Either a bug or " &
|
||||
"outdated PCRE2.")
|
||||
else:
|
||||
raise RegexInternalError(msg: "Unknown internal error: " & $execRet)
|
||||
raise RegexInternalError(msg: "Unknown internal error: " & $execRet)
|
||||
|
||||
proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
|
||||
## Like `find(...)<#find,string,Regex,int>`_, but anchored to the start of the
|
||||
@@ -576,7 +559,7 @@ proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[R
|
||||
assert 0 in "abc".match(re"(\w)").get.captureBounds
|
||||
assert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
|
||||
assert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
|
||||
return str.matchImpl(pattern, start, endpos, cast[uint32](pcre.ANCHORED))
|
||||
return str.matchImpl(pattern, start, endpos, pcre.ANCHORED)
|
||||
|
||||
iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch =
|
||||
## Works the same as `find(...)<#find,string,Regex,int>`_, but finds every
|
||||
@@ -590,21 +573,21 @@ iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): R
|
||||
## Variants:
|
||||
##
|
||||
## - `proc findAll(...)` returns a `seq[string]`
|
||||
# see pcre2demo for explanation => https://www.pcre.org/current/doc/html/pcre2demo.html
|
||||
# see pcredemo for explanation => https://www.pcre.org/original/doc/html/pcredemo.html
|
||||
let matchesCrLf = pattern.matchesCrLf()
|
||||
let unicode = uint32(getinfo[uint32](pattern, pcre.INFO_ALLOPTIONS) and
|
||||
pcre.UTF.uint32) > 0u32
|
||||
let unicode = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS) and
|
||||
pcre.UTF8) > 0u32
|
||||
let strlen = if endpos == int.high: str.len else: endpos+1
|
||||
var offset = start
|
||||
var match: Option[RegexMatch] = default(Option[RegexMatch])
|
||||
var neverMatched = true
|
||||
|
||||
while true:
|
||||
var flags = 0'u32
|
||||
var flags = 0
|
||||
if match.isSome and
|
||||
match.get.matchBounds.a > match.get.matchBounds.b:
|
||||
# 0-len match
|
||||
flags = pcre.NOTEMPTY_ATSTART.uint32
|
||||
flags = pcre.NOTEMPTY_ATSTART
|
||||
match = str.matchImpl(pattern, offset, endpos, flags)
|
||||
|
||||
if match.isNone:
|
||||
@@ -640,7 +623,7 @@ proc find*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[Re
|
||||
## `endpos`
|
||||
## : The maximum index for a match; `int.high` means the end of the
|
||||
## string, otherwise it’s an inclusive upper bound.
|
||||
return str.matchImpl(pattern, start, endpos, 0'u32)
|
||||
return str.matchImpl(pattern, start, endpos, 0)
|
||||
|
||||
proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[string] =
|
||||
result = @[]
|
||||
|
||||
@@ -8,25 +8,27 @@
|
||||
#
|
||||
|
||||
when defined(js):
|
||||
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE2; See jsre for JS backend.".}
|
||||
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".}
|
||||
|
||||
## .. warning:: This module is deprecated.
|
||||
## Use [Regex](https://github.com/nitely/nim-regex).
|
||||
## This compatibility module uses PCRE2.
|
||||
## PCRE library is now at end of life.
|
||||
##
|
||||
## Regular expression support for Nim.
|
||||
##
|
||||
## This module is implemented by providing a wrapper around the
|
||||
## `PCRE2 (Perl-Compatible Regular Expressions) <https://www.pcre.org>`_
|
||||
## C library. This means that your application will depend on the PCRE2
|
||||
## `PCRE (Perl-Compatible Regular Expressions) <https://www.pcre.org>`_
|
||||
## C library. This means that your application will depend on the PCRE
|
||||
## library's licence when using this module, which should not be a problem
|
||||
## though.
|
||||
##
|
||||
## .. note:: There are also alternative nimble packages such as [tinyre](https://github.com/khchen/tinyre)
|
||||
## and [regex](https://github.com/nitely/nim-regex).
|
||||
##
|
||||
## PCRE2 is distributed under a BSD-style licence.
|
||||
|
||||
## PCRE's licence follows:
|
||||
##
|
||||
## .. include:: ../../doc/regexprs.txt
|
||||
##
|
||||
|
||||
runnableExamples:
|
||||
## Unless specified otherwise, `start` parameter in each proc indicates
|
||||
@@ -38,7 +40,7 @@ runnableExamples:
|
||||
# can't match start of string since we're starting at 1
|
||||
|
||||
import
|
||||
std/[pcre2, strutils]
|
||||
std/[pcre, strutils, rtarrays]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/syncio
|
||||
@@ -58,7 +60,8 @@ type
|
||||
## expression will be used only once)
|
||||
|
||||
RegexDesc = object
|
||||
h: ptr pcre2.Pcre
|
||||
h: ptr Pcre
|
||||
e: ptr ExtraData
|
||||
|
||||
Regex* = ref RegexDesc ## a compiled regular expression
|
||||
|
||||
@@ -68,10 +71,14 @@ type
|
||||
when defined(gcDestructors):
|
||||
when defined(nimAllowNonVarDestructor):
|
||||
proc `=destroy`(x: RegexDesc) =
|
||||
pcre2.code_free(x.h)
|
||||
pcre.free_substring(cast[cstring](x.h))
|
||||
if not isNil(x.e):
|
||||
pcre.free_study(x.e)
|
||||
else:
|
||||
proc `=destroy`(x: var RegexDesc) =
|
||||
pcre2.code_free(x.h)
|
||||
pcre.free_substring(cast[cstring](x.h))
|
||||
if not isNil(x.e):
|
||||
pcre.free_study(x.e)
|
||||
|
||||
proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} =
|
||||
var e: ref RegexError
|
||||
@@ -79,43 +86,21 @@ proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} =
|
||||
e.msg = msg
|
||||
raise e
|
||||
|
||||
proc pcre2ErrorMessage(errorCode: cint): string =
|
||||
var buffer: array[256, uint8]
|
||||
let length = pcre2.get_error_message(errorCode, addr buffer[0], buffer.len.csize_t)
|
||||
if length >= 0:
|
||||
result = newString(length)
|
||||
if length > 0:
|
||||
copyMem(addr result[0], addr buffer[0], length)
|
||||
else:
|
||||
result = $errorCode
|
||||
|
||||
proc rawCompile(pattern: string, options: uint32): ptr pcre2.Pcre =
|
||||
proc rawCompile(pattern: string, flags: cint): ptr Pcre =
|
||||
var
|
||||
errorCode: cint = 0
|
||||
offset: csize_t = 0
|
||||
result = pcre2.compile(cast[ptr uint8](pattern.cstring), pattern.len.csize_t,
|
||||
options, addr errorCode, addr offset, nil)
|
||||
msg: cstring = ""
|
||||
offset: cint = 0
|
||||
result = pcre.compile(pattern, flags, addr(msg), addr(offset), nil)
|
||||
if result == nil:
|
||||
raiseInvalidRegex(pcre2ErrorMessage(errorCode) & "\n" & pattern & "\n" &
|
||||
spaces(offset.int) & "^\n")
|
||||
raiseInvalidRegex($msg & "\n" & pattern & "\n" & spaces(offset) & "^\n")
|
||||
|
||||
proc finalizeRegEx(x: Regex) =
|
||||
pcre2.code_free(x.h)
|
||||
|
||||
func toPcre2Options(flags: set[RegexFlag]): uint32 =
|
||||
if reIgnoreCase in flags:
|
||||
result = result or pcre2.CASELESS.uint32
|
||||
if reMultiLine in flags:
|
||||
result = result or pcre2.MULTILINE.uint32
|
||||
if reDotAll in flags:
|
||||
result = result or pcre2.DOTALL.uint32
|
||||
if reExtended in flags:
|
||||
result = result or pcre2.EXTENDED.uint32
|
||||
|
||||
proc jitCompile(pattern: ptr pcre2.Pcre) =
|
||||
var hasJit: cint = 0
|
||||
if pcre2.config(pcre2.CONFIG_JIT, addr hasJit) == 0 and hasJit == 1:
|
||||
discard pcre2.jit_compile(pattern, pcre2.JIT_COMPLETE.uint32)
|
||||
# XXX This is a hack, but PCRE does not export its "free" function properly.
|
||||
# Sigh. The hack relies on PCRE's implementation (see `pcre_get.c`).
|
||||
# Fortunately the implementation is unlikely to change.
|
||||
pcre.free_substring(cast[cstring](x.h))
|
||||
if not isNil(x.e):
|
||||
pcre.free_study(x.e)
|
||||
|
||||
proc re*(s: string, flags = {reStudy}): Regex =
|
||||
## Constructor of regular expressions.
|
||||
@@ -131,9 +116,16 @@ proc re*(s: string, flags = {reStudy}): Regex =
|
||||
result = Regex()
|
||||
else:
|
||||
new(result, finalizeRegEx)
|
||||
result.h = rawCompile(s, toPcre2Options(flags))
|
||||
result.h = rawCompile(s, cast[cint](flags - {reStudy}))
|
||||
if reStudy in flags:
|
||||
jitCompile(result.h)
|
||||
var msg: cstring = ""
|
||||
var options: cint = 0
|
||||
var hasJit: cint = 0
|
||||
if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0:
|
||||
if hasJit == 1'i32:
|
||||
options = pcre.STUDY_JIT_COMPILE
|
||||
result.e = pcre.study(result.h, options, addr msg)
|
||||
if not isNil(msg): raiseInvalidRegex($msg)
|
||||
|
||||
proc rex*(s: string, flags = {reStudy, reExtended}): Regex =
|
||||
## Constructor for extended regular expressions.
|
||||
@@ -150,58 +142,25 @@ proc bufSubstr(b: cstring, sPos, ePos: int): string {.inline.} =
|
||||
copyMem(addr(result[0]), unsafeAddr(b[sPos]), sz)
|
||||
result.setLen(sz)
|
||||
|
||||
proc newMatchData(slots: int): ptr pcre2.MatchData =
|
||||
result = pcre2.match_data_create(max(slots, 1).uint32, nil)
|
||||
if result == nil:
|
||||
raiseInvalidRegex("could not allocate PCRE2 match data")
|
||||
|
||||
template ovector(matchData: ptr pcre2.MatchData): ptr UncheckedArray[csize_t] =
|
||||
cast[ptr UncheckedArray[csize_t]](pcre2.get_ovector_pointer(matchData))
|
||||
|
||||
proc rawMatch(buf: cstring, pattern: Regex, start, bufSize: int,
|
||||
options: uint32, matchData: ptr pcre2.MatchData): cint =
|
||||
if start < 0 or bufSize < 0:
|
||||
return pcre2.ERROR_BADOFFSET
|
||||
pcre2.match(pattern.h, cast[ptr uint8](buf), bufSize.csize_t,
|
||||
start.csize_t, options, matchData, nil)
|
||||
|
||||
proc copyStringMatches(buf: cstring, rawMatches: ptr UncheckedArray[csize_t],
|
||||
captureCount: int, matches: var openArray[string]) =
|
||||
let upper = min(captureCount - 1, matches.len)
|
||||
if upper > 0:
|
||||
for i in 1 .. upper:
|
||||
let matchStart = rawMatches[i * 2]
|
||||
let matchEnd = rawMatches[i * 2 + 1]
|
||||
if matchStart != pcre2.UNSET:
|
||||
matches[i-1] = bufSubstr(buf, int(matchStart), int(matchEnd))
|
||||
else:
|
||||
matches[i-1] = ""
|
||||
|
||||
proc copyBoundsMatches(rawMatches: ptr UncheckedArray[csize_t],
|
||||
captureCount: int,
|
||||
matches: var openArray[tuple[first, last: int]]) =
|
||||
let upper = min(captureCount - 1, matches.len)
|
||||
if upper > 0:
|
||||
for i in 1 .. upper:
|
||||
let matchStart = rawMatches[i * 2]
|
||||
let matchEnd = rawMatches[i * 2 + 1]
|
||||
if matchStart != pcre2.UNSET:
|
||||
matches[i-1] = (int(matchStart), int(matchEnd) - 1)
|
||||
else:
|
||||
matches[i-1] = (-1, 0)
|
||||
|
||||
proc matchOrFind(buf: cstring, pattern: Regex, matches: var openArray[string],
|
||||
start, bufSize: int, options: uint32): int =
|
||||
let matchData = newMatchData(matches.len + 1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
let res = rawMatch(buf, pattern, start, bufSize, options, matchData)
|
||||
let rawMatches = ovector(matchData)
|
||||
if res < 0: return int(res)
|
||||
copyStringMatches(buf, rawMatches, int(res), matches)
|
||||
return int(rawMatches[1]) - int(rawMatches[0])
|
||||
start, bufSize, flags: cint): cint =
|
||||
var
|
||||
rtarray = initRtArray[cint]((matches.len+1)*3)
|
||||
rawMatches = rtarray.getRawData
|
||||
res = pcre.exec(pattern.h, pattern.e, buf, bufSize, start, flags,
|
||||
cast[ptr cint](rawMatches), (matches.len+1).cint*3)
|
||||
if res < 0'i32: return res
|
||||
for i in 1..int(res)-1:
|
||||
var a = rawMatches[i * 2]
|
||||
var b = rawMatches[i * 2 + 1]
|
||||
if a >= 0'i32:
|
||||
matches[i-1] = bufSubstr(buf, int(a), int(b))
|
||||
else: matches[i-1] = ""
|
||||
return rawMatches[1] - rawMatches[0]
|
||||
|
||||
const MaxReBufSize* = high(int)
|
||||
## Maximum PCRE2 buffer start/size accepted by this Nim API.
|
||||
const MaxReBufSize* = high(cint)
|
||||
## Maximum PCRE (API 1) buffer start/size equal to `high(cint)`, which even
|
||||
## for 64-bit systems can be either 2`31`:sup:-1 or 2`63`:sup:-1.
|
||||
|
||||
proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string],
|
||||
start = 0, bufSize: int): tuple[first, last: int] =
|
||||
@@ -213,12 +172,17 @@ proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string],
|
||||
##
|
||||
## Note: The memory for `matches` needs to be allocated before this function is
|
||||
## called, otherwise it will just remain empty.
|
||||
let matchData = newMatchData(matches.len + 1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
|
||||
let rawMatches = ovector(matchData)
|
||||
if res < 0: return (-1, 0)
|
||||
copyStringMatches(buf, rawMatches, int(res), matches)
|
||||
var
|
||||
rtarray = initRtArray[cint]((matches.len+1)*3)
|
||||
rawMatches = rtarray.getRawData
|
||||
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
|
||||
cast[ptr cint](rawMatches), (matches.len+1).cint*3)
|
||||
if res < 0'i32: return (-1, 0)
|
||||
for i in 1..int(res)-1:
|
||||
var a = rawMatches[i * 2]
|
||||
var b = rawMatches[i * 2 + 1]
|
||||
if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b))
|
||||
else: matches[i-1] = ""
|
||||
return (rawMatches[0].int, rawMatches[1].int - 1)
|
||||
|
||||
proc findBounds*(s: string, pattern: Regex, matches: var openArray[string],
|
||||
@@ -248,12 +212,17 @@ proc findBounds*(buf: cstring, pattern: Regex,
|
||||
## `(-1,0)` is returned.
|
||||
##
|
||||
## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
|
||||
let matchData = newMatchData(matches.len + 1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
|
||||
let rawMatches = ovector(matchData)
|
||||
if res < 0: return (-1, 0)
|
||||
copyBoundsMatches(rawMatches, int(res), matches)
|
||||
var
|
||||
rtarray = initRtArray[cint]((matches.len+1)*3)
|
||||
rawMatches = rtarray.getRawData
|
||||
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
|
||||
cast[ptr cint](rawMatches), (matches.len+1).cint*3)
|
||||
if res < 0'i32: return (-1, 0)
|
||||
for i in 1..int(res)-1:
|
||||
var a = rawMatches[i * 2]
|
||||
var b = rawMatches[i * 2 + 1]
|
||||
if a >= 0'i32: matches[i-1] = (int(a), int(b)-1)
|
||||
else: matches[i-1] = (-1,0)
|
||||
return (rawMatches[0].int, rawMatches[1].int - 1)
|
||||
|
||||
proc findBounds*(s: string, pattern: Regex,
|
||||
@@ -275,28 +244,29 @@ proc findBounds*(s: string, pattern: Regex,
|
||||
min(start, MaxReBufSize), min(s.len, MaxReBufSize))
|
||||
|
||||
proc findBoundsImpl(buf: cstring, pattern: Regex,
|
||||
start = 0, bufSize = 0,
|
||||
options = 0'u32): tuple[first, last: int] =
|
||||
let matchData = newMatchData(1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
let res = rawMatch(buf, pattern, start, bufSize, options, matchData)
|
||||
let rawMatches = ovector(matchData)
|
||||
if res < 0:
|
||||
start = 0, bufSize = 0, flags = 0): tuple[first, last: int] =
|
||||
var rtarray = initRtArray[cint](3)
|
||||
let rawMatches = rtarray.getRawData
|
||||
let res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, flags.int32,
|
||||
cast[ptr cint](rawMatches), 3)
|
||||
|
||||
if res < 0'i32:
|
||||
result = (-1, 0)
|
||||
else:
|
||||
result = (int(rawMatches[0]), int(rawMatches[1]) - 1)
|
||||
result = (int(rawMatches[0]), int(rawMatches[1]-1))
|
||||
|
||||
proc findBounds*(buf: cstring, pattern: Regex,
|
||||
start = 0, bufSize: int): tuple[first, last: int] =
|
||||
## returns the `first` and `last` position of `pattern` in `buf`,
|
||||
## where `buf` has length `bufSize` (not necessarily `'\0'` terminated).
|
||||
## If it does not match, `(-1,0)` is returned.
|
||||
let matchData = newMatchData(1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
|
||||
let rawMatches = ovector(matchData)
|
||||
if res < 0: return (int(res), 0)
|
||||
return (int(rawMatches[0]), int(rawMatches[1]) - 1)
|
||||
var
|
||||
rtarray = initRtArray[cint](3)
|
||||
rawMatches = rtarray.getRawData
|
||||
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
|
||||
cast[ptr cint](rawMatches), 3)
|
||||
if res < 0'i32: return (int(res), 0)
|
||||
return (int(rawMatches[0]), int(rawMatches[1]-1))
|
||||
|
||||
proc findBounds*(s: string, pattern: Regex,
|
||||
start = 0): tuple[first, last: int] {.inline.} =
|
||||
@@ -309,16 +279,14 @@ proc findBounds*(s: string, pattern: Regex,
|
||||
result = findBounds(cstring(s), pattern,
|
||||
min(start, MaxReBufSize), min(s.len, MaxReBufSize))
|
||||
|
||||
proc matchOrFind(buf: cstring, pattern: Regex, start, bufSize: int,
|
||||
options: uint32): int =
|
||||
let matchData = newMatchData(1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
let res = rawMatch(buf, pattern, start, bufSize, options, matchData)
|
||||
if res >= 0:
|
||||
let rawMatches = ovector(matchData)
|
||||
result = int(rawMatches[1]) - int(rawMatches[0])
|
||||
else:
|
||||
result = int(res)
|
||||
proc matchOrFind(buf: cstring, pattern: Regex, start, bufSize: int, flags: cint): cint =
|
||||
var
|
||||
rtarray = initRtArray[cint](3)
|
||||
rawMatches = rtarray.getRawData
|
||||
result = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, flags,
|
||||
cast[ptr cint](rawMatches), 3)
|
||||
if result >= 0'i32:
|
||||
result = rawMatches[1] - rawMatches[0]
|
||||
|
||||
proc matchLen*(s: string, pattern: Regex, matches: var openArray[string],
|
||||
start = 0): int {.inline.} =
|
||||
@@ -327,7 +295,7 @@ proc matchLen*(s: string, pattern: Regex, matches: var openArray[string],
|
||||
## of zero can happen.
|
||||
##
|
||||
## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
|
||||
result = matchOrFind(cstring(s), pattern, matches, start, s.len, cast[uint32](pcre2.ANCHORED))
|
||||
result = matchOrFind(cstring(s), pattern, matches, start.cint, s.len.cint, pcre.ANCHORED)
|
||||
|
||||
proc matchLen*(buf: cstring, pattern: Regex, matches: var openArray[string],
|
||||
start = 0, bufSize: int): int {.inline.} =
|
||||
@@ -336,7 +304,7 @@ proc matchLen*(buf: cstring, pattern: Regex, matches: var openArray[string],
|
||||
## of zero can happen.
|
||||
##
|
||||
## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
|
||||
return matchOrFind(buf, pattern, matches, start, bufSize, cast[uint32](pcre2.ANCHORED))
|
||||
return matchOrFind(buf, pattern, matches, start.cint, bufSize.cint, pcre.ANCHORED)
|
||||
|
||||
proc matchLen*(s: string, pattern: Regex, start = 0): int {.inline.} =
|
||||
## the same as `match`, but it returns the length of the match,
|
||||
@@ -347,13 +315,13 @@ proc matchLen*(s: string, pattern: Regex, start = 0): int {.inline.} =
|
||||
doAssert matchLen("abcdefg", re"cde", 2) == 3
|
||||
doAssert matchLen("abcdefg", re"abcde") == 5
|
||||
doAssert matchLen("abcdefg", re"cde") == -1
|
||||
result = matchOrFind(cstring(s), pattern, start, s.len, cast[uint32](pcre2.ANCHORED))
|
||||
result = matchOrFind(cstring(s), pattern, start.cint, s.len.cint, pcre.ANCHORED)
|
||||
|
||||
proc matchLen*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int {.inline.} =
|
||||
## the same as `match`, but it returns the length of the match,
|
||||
## if there is no match, `-1` is returned. Note that a match length
|
||||
## of zero can happen.
|
||||
result = matchOrFind(buf, pattern, start, bufSize, cast[uint32](pcre2.ANCHORED))
|
||||
result = matchOrFind(buf, pattern, start.cint, bufSize, pcre.ANCHORED)
|
||||
|
||||
proc match*(s: string, pattern: Regex, start = 0): bool {.inline.} =
|
||||
## returns `true` if `s[start..]` matches the `pattern`.
|
||||
@@ -393,13 +361,18 @@ proc find*(buf: cstring, pattern: Regex, matches: var openArray[string],
|
||||
## `buf` has length `bufSize` (not necessarily `'\0'` terminated).
|
||||
##
|
||||
## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
|
||||
let matchData = newMatchData(matches.len + 1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
|
||||
let rawMatches = ovector(matchData)
|
||||
if res < 0: return int(res)
|
||||
copyStringMatches(buf, rawMatches, int(res), matches)
|
||||
return int(rawMatches[0])
|
||||
var
|
||||
rtarray = initRtArray[cint]((matches.len+1)*3)
|
||||
rawMatches = rtarray.getRawData
|
||||
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
|
||||
cast[ptr cint](rawMatches), (matches.len+1).cint*3)
|
||||
if res < 0'i32: return res
|
||||
for i in 1..int(res)-1:
|
||||
var a = rawMatches[i * 2]
|
||||
var b = rawMatches[i * 2 + 1]
|
||||
if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b))
|
||||
else: matches[i-1] = ""
|
||||
return rawMatches[0]
|
||||
|
||||
proc find*(s: string, pattern: Regex, matches: var openArray[string],
|
||||
start = 0): int {.inline.} =
|
||||
@@ -414,12 +387,13 @@ proc find*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int =
|
||||
## returns the starting position of `pattern` in `buf`,
|
||||
## where `buf` has length `bufSize` (not necessarily `'\0'` terminated).
|
||||
## If it does not match, `-1` is returned.
|
||||
let matchData = newMatchData(1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
|
||||
let rawMatches = ovector(matchData)
|
||||
if res < 0: return int(res)
|
||||
return int(rawMatches[0])
|
||||
var
|
||||
rtarray = initRtArray[cint](3)
|
||||
rawMatches = rtarray.getRawData
|
||||
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
|
||||
cast[ptr cint](rawMatches), 3)
|
||||
if res < 0'i32: return res
|
||||
return rawMatches[0]
|
||||
|
||||
proc find*(s: string, pattern: Regex, start = 0): int {.inline.} =
|
||||
## returns the starting position of `pattern` in `s`. If it does not
|
||||
@@ -439,38 +413,40 @@ iterator findAll*(s: string, pattern: Regex, start = 0): string =
|
||||
##
|
||||
## Note that since this is an iterator you should not modify the string you
|
||||
## are iterating over: bad things could happen.
|
||||
var i = start
|
||||
let matchData = newMatchData(1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
var
|
||||
i = int32(start)
|
||||
rtarray = initRtArray[cint](3)
|
||||
rawMatches = rtarray.getRawData
|
||||
while true:
|
||||
let res = rawMatch(s.cstring, pattern, i, len(s), 0'u32, matchData)
|
||||
if res < 0: break
|
||||
let rawMatches = ovector(matchData)
|
||||
let matchStart = rawMatches[0]
|
||||
let matchEnd = rawMatches[1]
|
||||
if matchStart == matchEnd and matchStart.int == i: break
|
||||
yield substr(s, int(matchStart), int(matchEnd) - 1)
|
||||
i = matchEnd.int
|
||||
let res = pcre.exec(pattern.h, pattern.e, s, len(s).cint, i, 0'i32,
|
||||
cast[ptr cint](rawMatches), 3)
|
||||
if res < 0'i32: break
|
||||
let a = rawMatches[0]
|
||||
let b = rawMatches[1]
|
||||
if a == b and a == i: break
|
||||
yield substr(s, int(a), int(b)-1)
|
||||
i = b
|
||||
|
||||
iterator findAll*(buf: cstring, pattern: Regex, start = 0, bufSize: int): string =
|
||||
## Yields all matching `substrings` of `s` that match `pattern`.
|
||||
##
|
||||
## Note that since this is an iterator you should not modify the string you
|
||||
## are iterating over: bad things could happen.
|
||||
var i = start
|
||||
let matchData = newMatchData(1)
|
||||
defer: pcre2.match_data_free(matchData)
|
||||
var
|
||||
i = int32(start)
|
||||
rtarray = initRtArray[cint](3)
|
||||
rawMatches = rtarray.getRawData
|
||||
while true:
|
||||
let res = rawMatch(buf, pattern, i, bufSize, 0'u32, matchData)
|
||||
if res < 0: break
|
||||
let rawMatches = ovector(matchData)
|
||||
let matchStart = rawMatches[0]
|
||||
let matchEnd = rawMatches[1]
|
||||
if matchStart == matchEnd and matchStart.int == i: break
|
||||
var str = newString(int(matchEnd - matchStart))
|
||||
copyMem(str[0].addr, unsafeAddr(buf[int(matchStart)]), int(matchEnd - matchStart))
|
||||
let res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, i, 0'i32,
|
||||
cast[ptr cint](rawMatches), 3)
|
||||
if res < 0'i32: break
|
||||
let a = rawMatches[0]
|
||||
let b = rawMatches[1]
|
||||
if a == b and a == i: break
|
||||
var str = newString(b-a)
|
||||
copyMem(str[0].addr, unsafeAddr(buf[a]), b-a)
|
||||
yield str
|
||||
i = matchEnd.int
|
||||
i = b
|
||||
|
||||
proc findAll*(s: string, pattern: Regex, start = 0): seq[string] {.inline.} =
|
||||
## returns all matching `substrings` of `s` that match `pattern`.
|
||||
@@ -527,7 +503,7 @@ proc replace*(s: string, sub: Regex, by = ""): string =
|
||||
doAssert "var1=key; var2=key2".replace(re"(\w+)=(\w+)", "?") == "?; ?"
|
||||
result = ""
|
||||
var prev = 0
|
||||
var flags = 0'u32
|
||||
var flags = int32(0)
|
||||
while prev < s.len:
|
||||
var match = findBoundsImpl(s.cstring, sub, prev, s.len, flags)
|
||||
flags = 0
|
||||
@@ -536,7 +512,7 @@ proc replace*(s: string, sub: Regex, by = ""): string =
|
||||
add(result, by)
|
||||
if match.first > match.last:
|
||||
# 0-len match
|
||||
flags = pcre2.NOTEMPTY_ATSTART.uint32
|
||||
flags = pcre.NOTEMPTY_ATSTART
|
||||
prev = match.last + 1
|
||||
add(result, substr(s, prev))
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ proc toHashSet*[A](keys: openArray[A]): HashSet[A] =
|
||||
result = initHashSet[A](keys.len)
|
||||
for key in items(keys): result.incl(key)
|
||||
|
||||
iterator items*[A](s: HashSet[A]): A =
|
||||
iterator items*[A](s: HashSet[A]): lent A =
|
||||
## Iterates over elements of the set `s`.
|
||||
##
|
||||
## If you need a sequence with the elements you can use `sequtils.toSeq
|
||||
@@ -891,7 +891,7 @@ proc `$`*[A](s: OrderedSet[A]): string =
|
||||
## ```
|
||||
dollarImpl()
|
||||
|
||||
iterator items*[A](s: OrderedSet[A]): A =
|
||||
iterator items*[A](s: OrderedSet[A]): lent A =
|
||||
## Iterates over keys in the ordered set `s` in insertion order.
|
||||
##
|
||||
## If you need a sequence with the elements you can use `sequtils.toSeq
|
||||
|
||||
@@ -15,12 +15,16 @@
|
||||
## It also provides some fast iterators over lines in text files (or
|
||||
## other "line-like", variable length, delimited records).
|
||||
|
||||
const
|
||||
nimUseFallBack = defined(nintendoswitch) or defined(nimMemfileFallback)
|
||||
|
||||
when defined(windows):
|
||||
import std/winlean
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/widestrs
|
||||
elif defined(posix):
|
||||
import std/posix
|
||||
when not nimUseFallBack:
|
||||
import std/posix
|
||||
else:
|
||||
{.error: "the memfiles module is not supported on your operating system!".}
|
||||
|
||||
@@ -29,45 +33,48 @@ import std/oserrors
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions]
|
||||
elif nimUseFallBack:
|
||||
import std/syncio
|
||||
|
||||
from system/ansi_c import c_memchr
|
||||
|
||||
proc newEIO(msg: string): ref IOError =
|
||||
result = (ref IOError)(msg: msg)
|
||||
|
||||
proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode =
|
||||
## Set the size of open file pointed to by `fh` to `newFileSize` if != -1,
|
||||
## allocating | freeing space from the file system. This routine returns the
|
||||
## last OSErrorCode found rather than raising to support old rollback/clean-up
|
||||
## code style. [ Should maybe move to std/osfiles. ]
|
||||
result = OSErrorCode(0)
|
||||
if newFileSize < 0 or newFileSize == oldSize:
|
||||
return result
|
||||
when defined(windows):
|
||||
var sizeHigh = int32(newFileSize shr 32)
|
||||
let sizeLow = int32(newFileSize and 0xffffffff)
|
||||
let status = setFilePointer(Handle fh, sizeLow, addr(sizeHigh), FILE_BEGIN)
|
||||
let lastErr = osLastError()
|
||||
if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
|
||||
setEndOfFile(Handle fh) == 0:
|
||||
result = lastErr
|
||||
else:
|
||||
if newFileSize > oldSize: # grow the file
|
||||
var e: cint = cint(0) # posix_fallocate truncates up when needed.
|
||||
when declared(posix_fallocate):
|
||||
while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR):
|
||||
discard
|
||||
if e == EINVAL or e == EOPNOTSUPP or e == ENOSYS:
|
||||
# fallback arguable; Most portable BUT allows SEGV
|
||||
if ftruncate(fh, newFileSize) == -1:
|
||||
when not nimUseFallBack:
|
||||
proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode =
|
||||
## Set the size of open file pointed to by `fh` to `newFileSize` if != -1,
|
||||
## allocating | freeing space from the file system. This routine returns the
|
||||
## last OSErrorCode found rather than raising to support old rollback/clean-up
|
||||
## code style. [ Should maybe move to std/osfiles. ]
|
||||
result = OSErrorCode(0)
|
||||
if newFileSize < 0 or newFileSize == oldSize:
|
||||
return result
|
||||
when defined(windows):
|
||||
var sizeHigh = int32(newFileSize shr 32)
|
||||
let sizeLow = int32(newFileSize and 0xffffffff)
|
||||
let status = setFilePointer(Handle fh, sizeLow, addr(sizeHigh), FILE_BEGIN)
|
||||
let lastErr = osLastError()
|
||||
if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
|
||||
setEndOfFile(Handle fh) == 0:
|
||||
result = lastErr
|
||||
else:
|
||||
if newFileSize > oldSize: # grow the file
|
||||
var e: cint = cint(0) # posix_fallocate truncates up when needed.
|
||||
when declared(posix_fallocate):
|
||||
while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR):
|
||||
discard
|
||||
if e == EINVAL or e == EOPNOTSUPP or e == ENOSYS:
|
||||
# fallback arguable; Most portable BUT allows SEGV
|
||||
if ftruncate(fh, newFileSize) == -1:
|
||||
result = osLastError()
|
||||
else:
|
||||
discard
|
||||
elif e != 0:
|
||||
result = osLastError()
|
||||
else: # shrink the file
|
||||
if ftruncate(fh.cint, newFileSize) == -1:
|
||||
result = osLastError()
|
||||
else:
|
||||
discard
|
||||
elif e != 0:
|
||||
result = osLastError()
|
||||
else: # shrink the file
|
||||
if ftruncate(fh.cint, newFileSize) == -1:
|
||||
result = osLastError()
|
||||
|
||||
type
|
||||
MemFile* = object ## represents a memory mapped file
|
||||
@@ -84,6 +91,89 @@ type
|
||||
else:
|
||||
handle*: cint ## **Caution**: Posix specific public field.
|
||||
flags: cint ## **Caution**: Platform specific private field.
|
||||
when nimUseFallBack:
|
||||
backing: string
|
||||
path: string
|
||||
readonly: bool
|
||||
allowRemap: bool
|
||||
|
||||
when nimUseFallBack:
|
||||
proc fallbackMappedSize(backingLen, mappedSize, offset: int): int =
|
||||
if mappedSize < -1:
|
||||
raise newEIO("mappedSize cannot be less than -1")
|
||||
if offset < 0 or offset > backingLen:
|
||||
raise newEIO("offset out of bounds")
|
||||
if mappedSize == -1:
|
||||
result = backingLen - offset
|
||||
else:
|
||||
result = min(mappedSize, backingLen - offset)
|
||||
|
||||
proc setFallbackView(m: var MemFile, mappedSize, offset: int) =
|
||||
m.size = fallbackMappedSize(m.backing.len, mappedSize, offset)
|
||||
if m.size > 0:
|
||||
m.mem = cast[pointer](addr m.backing[offset])
|
||||
else:
|
||||
m.mem = nil
|
||||
|
||||
proc openFallbackMemFile(filename: string, mode: FileMode, mappedSize,
|
||||
offset, newFileSize: int,
|
||||
allowRemap: bool): MemFile =
|
||||
result = MemFile(
|
||||
handle: -1,
|
||||
flags: 0,
|
||||
path: filename,
|
||||
readonly: mode == fmRead,
|
||||
allowRemap: allowRemap
|
||||
)
|
||||
if newFileSize != -1:
|
||||
result.backing = newString(newFileSize)
|
||||
else:
|
||||
result.backing = readFile(filename)
|
||||
setFallbackView(result, mappedSize, offset)
|
||||
|
||||
proc mapMemFallback(m: var MemFile, mode: FileMode,
|
||||
mappedSize, offset: int): pointer =
|
||||
if not m.allowRemap:
|
||||
raise newException(IOError,
|
||||
"Cannot remap MemFile opened with allowRemap=false")
|
||||
if mode != fmRead and m.readonly:
|
||||
raise newEIO("cannot write to read-only mapping")
|
||||
let size = fallbackMappedSize(m.backing.len, mappedSize, offset)
|
||||
if size > 0:
|
||||
result = cast[pointer](addr m.backing[offset])
|
||||
else:
|
||||
result = nil
|
||||
|
||||
proc flushFallback(m: var MemFile) =
|
||||
if m.readonly or m.path.len == 0:
|
||||
return
|
||||
writeFile(m.path, m.backing)
|
||||
|
||||
proc resizeFallback(m: var MemFile, newFileSize: int) =
|
||||
if m.readonly:
|
||||
raise newException(IOError, "Cannot resize read-only MemFile")
|
||||
if not m.allowRemap:
|
||||
raise newException(IOError,
|
||||
"Cannot resize MemFile opened with allowRemap=false")
|
||||
if m.size != m.backing.len:
|
||||
raise newException(IOError, "Cannot resize partial MemFile")
|
||||
let oldLen = m.backing.len
|
||||
m.backing.setLen(newFileSize)
|
||||
for i in oldLen ..< newFileSize:
|
||||
m.backing[i] = '\0'
|
||||
setFallbackView(m, newFileSize, 0)
|
||||
|
||||
proc closeFallback(m: var MemFile) =
|
||||
if not m.readonly:
|
||||
flushFallback(m)
|
||||
m.mem = nil
|
||||
m.size = 0
|
||||
m.handle = -1
|
||||
m.flags = 0
|
||||
m.backing = ""
|
||||
m.path = ""
|
||||
m.readonly = false
|
||||
m.allowRemap = false
|
||||
|
||||
proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
|
||||
mappedSize = -1, offset = 0, mapFlags = cint(-1)): pointer =
|
||||
@@ -94,7 +184,7 @@ proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
|
||||
if mode == fmAppend:
|
||||
raise newEIO("The append mode is not supported.")
|
||||
|
||||
var readonly = mode == fmRead
|
||||
let readonly = mode == fmRead
|
||||
when defined(windows):
|
||||
result = mapViewOfFileEx(
|
||||
m.mapHandle,
|
||||
@@ -105,6 +195,8 @@ proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
|
||||
nil)
|
||||
if result == nil:
|
||||
raiseOSError(osLastError())
|
||||
elif nimUseFallBack:
|
||||
result = mapMemFallback(m, mode, mappedSize, offset)
|
||||
else:
|
||||
assert mappedSize > 0
|
||||
|
||||
@@ -132,6 +224,8 @@ proc unmapMem*(f: var MemFile, p: pointer, size: int) =
|
||||
## via `mapMem`.
|
||||
when defined(windows):
|
||||
if unmapViewOfFile(p) == 0: raiseOSError(osLastError())
|
||||
elif nimUseFallBack:
|
||||
discard
|
||||
else:
|
||||
if munmap(p, size) != 0: raiseOSError(osLastError())
|
||||
|
||||
@@ -178,7 +272,7 @@ proc open*(filename: string, mode: FileMode = fmRead,
|
||||
raise newEIO("The append mode is not supported.")
|
||||
|
||||
assert newFileSize == -1 or mode != fmRead
|
||||
var readonly = mode == fmRead
|
||||
let readonly = mode == fmRead
|
||||
|
||||
template rollback =
|
||||
result.mem = nil
|
||||
@@ -252,7 +346,10 @@ proc open*(filename: string, mode: FileMode = fmRead,
|
||||
if closeHandle(result.fHandle) != 0:
|
||||
result.fHandle = INVALID_HANDLE_VALUE
|
||||
|
||||
else:
|
||||
elif nimUseFallBack:
|
||||
result = openFallbackMemFile(filename, mode, mappedSize, offset,
|
||||
newFileSize, allowRemap)
|
||||
elif defined(posix):
|
||||
template fail(errCode: OSErrorCode, msg: string) =
|
||||
rollback()
|
||||
if result.handle != -1: discard close(result.handle)
|
||||
@@ -309,6 +406,8 @@ proc flush*(f: var MemFile; attempts: Natural = 3) =
|
||||
lastErr = osLastError()
|
||||
if lastErr != ERROR_LOCK_VIOLATION.OSErrorCode:
|
||||
raiseOSError(lastErr)
|
||||
elif nimUseFallBack:
|
||||
flushFallback(f)
|
||||
else:
|
||||
for i in 1..attempts:
|
||||
res = msync(f.mem, f.size, MS_SYNC or MS_INVALIDATE) == 0
|
||||
@@ -318,59 +417,71 @@ proc flush*(f: var MemFile; attempts: Natural = 3) =
|
||||
if lastErr != EBUSY.OSErrorCode:
|
||||
raiseOSError(lastErr, "error flushing mapping")
|
||||
|
||||
proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} =
|
||||
## Resize & re-map the file underlying an `allowRemap MemFile`. If the OS/FS
|
||||
## supports it, file space is reserved to ensure room for new virtual pages.
|
||||
## Caller should wait often enough for `flush` to finish to limit use of
|
||||
## system RAM for write buffering, perhaps just prior to this call.
|
||||
## **Note**: this assumes the entire file is mapped read-write at offset 0.
|
||||
## Also, the value of `.mem` will probably change.
|
||||
if newFileSize < 1: # Q: include system/bitmasks & use PageSize ?
|
||||
raise newException(IOError, "Cannot resize MemFile to < 1 byte")
|
||||
when defined(windows):
|
||||
if not f.wasOpened:
|
||||
raise newException(IOError, "Cannot resize unopened MemFile")
|
||||
if f.fHandle == INVALID_HANDLE_VALUE:
|
||||
raise newException(IOError,
|
||||
"Cannot resize MemFile opened with allowRemap=false")
|
||||
if unmapViewOfFile(f.mem) == 0 or closeHandle(f.mapHandle) == 0: # Un-do map
|
||||
raiseOSError(osLastError())
|
||||
if newFileSize != f.size: # Seek to size & `setEndOfFile` => allocated.
|
||||
if (let e = setFileSize(f.fHandle.FileHandle, newFileSize);
|
||||
e != 0.OSErrorCode): raiseOSError(e)
|
||||
f.mapHandle = createFileMappingW(f.fHandle, nil, PAGE_READWRITE, 0,0,nil)
|
||||
if f.mapHandle == 0: # Re-do map
|
||||
raiseOSError(osLastError())
|
||||
let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE,
|
||||
0, 0, WinSizeT(newFileSize), nil)
|
||||
if m != nil:
|
||||
f.mem = m
|
||||
when nimUseFallBack:
|
||||
proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError].} =
|
||||
## Resize & re-map the file underlying an `allowRemap MemFile`. If the OS/FS
|
||||
## supports it, file space is reserved to ensure room for new virtual pages.
|
||||
## Caller should wait often enough for `flush` to finish to limit use of
|
||||
## system RAM for write buffering, perhaps just prior to this call.
|
||||
## **Note**: this assumes the entire file is mapped read-write at offset 0.
|
||||
## Also, the value of `.mem` will probably change.
|
||||
if newFileSize < 1: # Q: include system/bitmasks & use PageSize ?
|
||||
raise newException(IOError, "Cannot resize MemFile to < 1 byte")
|
||||
resizeFallback(f, newFileSize)
|
||||
else:
|
||||
proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} =
|
||||
## Resize & re-map the file underlying an `allowRemap MemFile`. If the OS/FS
|
||||
## supports it, file space is reserved to ensure room for new virtual pages.
|
||||
## Caller should wait often enough for `flush` to finish to limit use of
|
||||
## system RAM for write buffering, perhaps just prior to this call.
|
||||
## **Note**: this assumes the entire file is mapped read-write at offset 0.
|
||||
## Also, the value of `.mem` will probably change.
|
||||
if newFileSize < 1: # Q: include system/bitmasks & use PageSize ?
|
||||
raise newException(IOError, "Cannot resize MemFile to < 1 byte")
|
||||
when defined(windows):
|
||||
if not f.wasOpened:
|
||||
raise newException(IOError, "Cannot resize unopened MemFile")
|
||||
if f.fHandle == INVALID_HANDLE_VALUE:
|
||||
raise newException(IOError,
|
||||
"Cannot resize MemFile opened with allowRemap=false")
|
||||
if unmapViewOfFile(f.mem) == 0 or closeHandle(f.mapHandle) == 0: # Un-do map
|
||||
raiseOSError(osLastError())
|
||||
if newFileSize != f.size: # Seek to size & `setEndOfFile` => allocated.
|
||||
if (let e = setFileSize(f.fHandle.FileHandle, newFileSize);
|
||||
e != 0.OSErrorCode): raiseOSError(e)
|
||||
f.mapHandle = createFileMappingW(f.fHandle, nil, PAGE_READWRITE, 0,0,nil)
|
||||
if f.mapHandle == 0: # Re-do map
|
||||
raiseOSError(osLastError())
|
||||
let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE,
|
||||
0, 0, WinSizeT(newFileSize), nil)
|
||||
if m != nil:
|
||||
f.mem = m
|
||||
f.size = newFileSize
|
||||
else:
|
||||
raiseOSError(osLastError())
|
||||
elif defined(posix):
|
||||
if f.handle == -1:
|
||||
raise newException(IOError,
|
||||
"Cannot resize MemFile opened with allowRemap=false")
|
||||
if newFileSize != f.size:
|
||||
let e = setFileSize(f.handle.FileHandle, newFileSize, f.size)
|
||||
if e != 0.OSErrorCode: raiseOSError(e)
|
||||
when defined(linux): #Maybe NetBSD, too?
|
||||
# On Linux this can be over 100 times faster than a munmap,mmap cycle.
|
||||
proc mremap(old: pointer; oldSize, newSize: csize_t; flags: cint):
|
||||
pointer {.importc: "mremap", header: "<sys/mman.h>".}
|
||||
let newAddr = mremap(f.mem, csize_t(f.size), csize_t(newFileSize), 1.cint)
|
||||
if newAddr == cast[pointer](MAP_FAILED):
|
||||
raiseOSError(osLastError())
|
||||
else:
|
||||
if munmap(f.mem, f.size) != 0:
|
||||
raiseOSError(osLastError())
|
||||
let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE,
|
||||
f.flags, f.handle, 0)
|
||||
if newAddr == cast[pointer](MAP_FAILED):
|
||||
raiseOSError(osLastError())
|
||||
f.mem = newAddr
|
||||
f.size = newFileSize
|
||||
else:
|
||||
raiseOSError(osLastError())
|
||||
elif defined(posix):
|
||||
if f.handle == -1:
|
||||
raise newException(IOError,
|
||||
"Cannot resize MemFile opened with allowRemap=false")
|
||||
if newFileSize != f.size:
|
||||
let e = setFileSize(f.handle.FileHandle, newFileSize, f.size)
|
||||
if e != 0.OSErrorCode: raiseOSError(e)
|
||||
when defined(linux): #Maybe NetBSD, too?
|
||||
# On Linux this can be over 100 times faster than a munmap,mmap cycle.
|
||||
proc mremap(old: pointer; oldSize, newSize: csize_t; flags: cint):
|
||||
pointer {.importc: "mremap", header: "<sys/mman.h>".}
|
||||
let newAddr = mremap(f.mem, csize_t(f.size), csize_t(newFileSize), 1.cint)
|
||||
if newAddr == cast[pointer](MAP_FAILED):
|
||||
raiseOSError(osLastError())
|
||||
else:
|
||||
if munmap(f.mem, f.size) != 0:
|
||||
raiseOSError(osLastError())
|
||||
let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE,
|
||||
f.flags, f.handle, 0)
|
||||
if newAddr == cast[pointer](MAP_FAILED):
|
||||
raiseOSError(osLastError())
|
||||
f.mem = newAddr
|
||||
f.size = newFileSize
|
||||
|
||||
proc close*(f: var MemFile) =
|
||||
## closes the memory mapped file `f`. All changes are written back to the
|
||||
@@ -389,6 +500,8 @@ proc close*(f: var MemFile) =
|
||||
f.fHandle = INVALID_HANDLE_VALUE
|
||||
if error:
|
||||
lastErr = osLastError()
|
||||
elif nimUseFallBack:
|
||||
closeFallback(f)
|
||||
else:
|
||||
error = munmap(f.mem, f.size) != 0
|
||||
lastErr = osLastError()
|
||||
|
||||
@@ -175,23 +175,48 @@ proc parseEscapedUTF16*(buf: cstring, pos: var int): int =
|
||||
else:
|
||||
return -1
|
||||
|
||||
proc addSpan(dst: var string; src: string; startPos, endPos: int) {.inline.} =
|
||||
let n = endPos - startPos
|
||||
if n <= 0:
|
||||
return
|
||||
|
||||
let old = dst.len
|
||||
dst.setLen old + n
|
||||
|
||||
template impl =
|
||||
for i in 0..<n:
|
||||
dst[old + i] = src[startPos + i]
|
||||
|
||||
when nimvm:
|
||||
impl
|
||||
else:
|
||||
when defined(js) or defined(nimscript):
|
||||
impl
|
||||
else:
|
||||
{.noSideEffect.}:
|
||||
copyMem dst[old].addr, src[startPos].unsafeAddr, n
|
||||
|
||||
proc parseString(my: var JsonParser): TokKind =
|
||||
result = tkString
|
||||
var pos = my.bufpos + 1
|
||||
var spanStart = pos
|
||||
if my.rawStringLiterals:
|
||||
add(my.a, '"')
|
||||
while true:
|
||||
case my.buf[pos]
|
||||
of '\0':
|
||||
my.err = errQuoteExpected
|
||||
my.err = errInvalidToken
|
||||
addSpan(my.a, my.buf, spanStart, pos)
|
||||
result = tkError
|
||||
break
|
||||
of '"':
|
||||
addSpan(my.a, my.buf, spanStart, pos)
|
||||
if my.rawStringLiterals:
|
||||
add(my.a, '"')
|
||||
inc(pos)
|
||||
break
|
||||
of '\\':
|
||||
addSpan(my.a, my.buf, spanStart, pos)
|
||||
if my.rawStringLiterals:
|
||||
add(my.a, '\\')
|
||||
case my.buf[pos+1]
|
||||
@@ -251,14 +276,18 @@ proc parseString(my: var JsonParser): TokKind =
|
||||
# don't bother with the error
|
||||
add(my.a, my.buf[pos])
|
||||
inc(pos)
|
||||
spanStart = pos
|
||||
of '\c':
|
||||
addSpan(my.a, my.buf, spanStart, pos)
|
||||
pos = lexbase.handleCR(my, pos)
|
||||
add(my.a, '\c')
|
||||
spanStart = pos
|
||||
of '\L':
|
||||
addSpan(my.a, my.buf, spanStart, pos)
|
||||
pos = lexbase.handleLF(my, pos)
|
||||
add(my.a, '\L')
|
||||
spanStart = pos
|
||||
else:
|
||||
add(my.a, my.buf[pos])
|
||||
inc(pos)
|
||||
my.bufpos = pos # store back
|
||||
|
||||
|
||||
@@ -487,11 +487,18 @@ func `/`*(x: Uri, path: string): Uri =
|
||||
|
||||
func `?`*(u: Uri, query: openArray[(string, string)]): Uri =
|
||||
## Concatenates the query parameters to the specified URI object.
|
||||
## If the URI already has a query string, the new parameters are appended.
|
||||
runnableExamples:
|
||||
let foo = parseUri("https://example.com") / "foo" ? {"bar": "qux"}
|
||||
assert $foo == "https://example.com/foo?bar=qux"
|
||||
let bar = parseUri("https://example.com/foo?existing=1") ? {"bar": "qux"}
|
||||
assert $bar == "https://example.com/foo?existing=1&bar=qux"
|
||||
result = u
|
||||
result.query = encodeQuery(query)
|
||||
let newQuery = encodeQuery(query)
|
||||
if newQuery.len > 0:
|
||||
if result.query.len > 0:
|
||||
result.query.add('&')
|
||||
result.query.add(newQuery)
|
||||
|
||||
func `$`*(u: Uri): string =
|
||||
## Returns the string representation of the specified URI object.
|
||||
|
||||
@@ -54,7 +54,12 @@ type
|
||||
typeOfProc, ## Prefer the interpretation that means `x` is a proc call.
|
||||
typeOfIter ## Prefer the interpretation that means `x` is an iterator call.
|
||||
|
||||
proc typeof*(x: untyped; mode = typeOfIter): typedesc {.
|
||||
TypeOfModifiers* = enum ## Modes to handle type modifiers `var`, `sink` and `lent`.
|
||||
CompatibleTypeModifiers, ## Remove or keep type modifiers in the same way as old typeof. That means keep `sink` but remove `var` and `lent`.
|
||||
RemoveTypeModifiers, ## Remove type modifiers.
|
||||
KeepTypeModifiers, ## Keep type modifiers.
|
||||
|
||||
proc typeof*(x: untyped; mode = typeOfIter; modifierMode = CompatibleTypeModifiers): typedesc {.
|
||||
magic: "TypeOf", noSideEffect, compileTime.} =
|
||||
## Builtin `typeof` operation for accessing the type of an expression.
|
||||
## Since version 0.20.0.
|
||||
@@ -76,6 +81,11 @@ proc typeof*(x: untyped; mode = typeOfIter): typedesc {.
|
||||
# since `typeOfProc` expects a typed expression and `myFoo2()` can
|
||||
# only be used in a `for` context.
|
||||
|
||||
proc varParam(x: var int;
|
||||
y: typeof(x, modifierMode = RemoveTypeModifiers);
|
||||
z: typeof(x, modifierMode = KeepTypeModifiers)) = discard
|
||||
doAssert varParam is proc (x: var int; y: int; z: var int) {.nimcall.}
|
||||
|
||||
proc `or`*(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.}
|
||||
## Constructs an `or` meta class.
|
||||
|
||||
@@ -1713,11 +1723,18 @@ when not (notJSnotNims and defined(nimSeqsV2)):
|
||||
let ns = cast[NimString](s)
|
||||
if ns == nil: nil
|
||||
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
|
||||
template readRawDataStable*(s: var string; start = 0): ptr UncheckedArray[char] =
|
||||
## Same as `readRawData` here: the data lives in a heap `NimStringDesc` at a
|
||||
## stable address, so the pointer already survives moves of `s`. Takes `s` by
|
||||
## `var` to match the `--strings:sso` version, so code can prepare for that
|
||||
## upgrade without `when declared` guards.
|
||||
readRawData(s, start)
|
||||
else:
|
||||
# JS/nimscript: callers are guarded by whenNotVmJsNims/when not defined(js)
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
|
||||
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} = discard
|
||||
template readRawData*(s: string; start = 0): ptr UncheckedArray[char] = nil
|
||||
template readRawDataStable*(s: var string; start = 0): ptr UncheckedArray[char] = nil
|
||||
|
||||
when not defined(js):
|
||||
template newSeqImpl(T, len) =
|
||||
@@ -3121,10 +3138,7 @@ when notJSnotNims:
|
||||
not defined(nuttx) and
|
||||
hostOS != "any"
|
||||
|
||||
proc raiseEIO(msg: string) {.noinline, noreturn.} =
|
||||
raise newException(IOError, msg)
|
||||
|
||||
proc echoBinSafe(args: openArray[string]) {.compilerproc.} =
|
||||
proc echoBinSafe(args: openArray[string]) {.compilerproc, raises: [].} =
|
||||
when defined(androidNDK):
|
||||
# When running nim in android app, stdout goes nowhere, so echo gets ignored
|
||||
# To redirect echo to the android logcat, use -d:androidNDK
|
||||
@@ -3146,7 +3160,7 @@ when notJSnotNims:
|
||||
for s in args:
|
||||
when defined(windows):
|
||||
# equivalent to syncio.writeWindows
|
||||
proc writeWindows(f: CFilePtr; s: string; doRaise = false) =
|
||||
proc writeWindows(f: CFilePtr; s: string) =
|
||||
# Don't ask why but the 'printf' family of function is the only thing
|
||||
# that writes utf-8 strings reliably on Windows. At least on my Win 10
|
||||
# machine. We also enable `setConsoleOutputCP(65001)` now by default.
|
||||
@@ -3157,13 +3171,11 @@ when notJSnotNims:
|
||||
if s[i] == '\0':
|
||||
let w = c_fputc('\0', f)
|
||||
if w != 0:
|
||||
if doRaise: raiseEIO("cannot write string to file")
|
||||
break
|
||||
inc i
|
||||
else:
|
||||
let w = c_fprintf(f, "%s", unsafeAddr s[i])
|
||||
if w <= 0:
|
||||
if doRaise: raiseEIO("cannot write string to file")
|
||||
break
|
||||
inc i, w
|
||||
writeWindows(cstdout, s)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
when not defined(nimNoLentIterators):
|
||||
when (not defined(nimNoLentIterators)) and not defined(js) and not defined(nimscript):
|
||||
template lent2(T): untyped = lent T
|
||||
else:
|
||||
template lent2(T): untyped = T
|
||||
@@ -37,7 +37,7 @@ iterator mitems*[T](a: var openArray[T]): var T {.inline.} =
|
||||
yield a[i]
|
||||
unCheckedInc(i)
|
||||
|
||||
iterator items*[IX, T](a: array[IX, T]): T {.inline.} =
|
||||
iterator items*[IX, T](a: array[IX, T]): lent2 T {.inline.} =
|
||||
## Iterates over each item of `a`.
|
||||
when a.len > 0:
|
||||
var i = low(IX)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
when notJSnotNims:
|
||||
proc zeroMem*(p: pointer, size: Natural) {.inline, noSideEffect,
|
||||
tags: [], raises: [], enforceNoRaises.}
|
||||
proc zeroMem*(p: pointer, size: Natural) {.inline, gcsafe,
|
||||
tags: [], raises: [], enforceNoRaises, noSideEffect.}
|
||||
## Overwrites the contents of the memory at `p` with the value 0.
|
||||
##
|
||||
## Exactly `size` bytes will be overwritten. Like any procedure
|
||||
## dealing with raw memory this is **unsafe**.
|
||||
|
||||
proc copyMem*(dest, source: pointer, size: Natural) {.inline, gcsafe,
|
||||
tags: [], raises: [], enforceNoRaises.}
|
||||
tags: [], raises: [], enforceNoRaises, noSideEffect.}
|
||||
## Copies the contents from the memory at `source` to the memory
|
||||
## at `dest`.
|
||||
## Exactly `size` bytes will be copied. The memory
|
||||
@@ -15,7 +15,7 @@ when notJSnotNims:
|
||||
## memory this is **unsafe**.
|
||||
|
||||
proc moveMem*(dest, source: pointer, size: Natural) {.inline, gcsafe,
|
||||
tags: [], raises: [], enforceNoRaises.}
|
||||
tags: [], raises: [], enforceNoRaises, noSideEffect.}
|
||||
## Copies the contents from the memory at `source` to the memory
|
||||
## at `dest`.
|
||||
##
|
||||
@@ -24,8 +24,8 @@ when notJSnotNims:
|
||||
## and is thus somewhat more safe than `copyMem`. Like any procedure
|
||||
## dealing with raw memory this is still **unsafe**, though.
|
||||
|
||||
proc equalMem*(a, b: pointer, size: Natural): bool {.inline, noSideEffect,
|
||||
tags: [], raises: [], enforceNoRaises.}
|
||||
proc equalMem*(a, b: pointer, size: Natural): bool {.inline, gcsafe,
|
||||
tags: [], raises: [], enforceNoRaises, noSideEffect.}
|
||||
## Compares the memory blocks `a` and `b`. `size` bytes will
|
||||
## be compared.
|
||||
##
|
||||
@@ -33,8 +33,8 @@ when notJSnotNims:
|
||||
## otherwise. Like any procedure dealing with raw memory this is
|
||||
## **unsafe**.
|
||||
|
||||
proc cmpMem*(a, b: pointer, size: Natural): int {.inline, noSideEffect,
|
||||
tags: [], raises: [], enforceNoRaises.}
|
||||
proc cmpMem*(a, b: pointer, size: Natural): int {.inline, gcsafe,
|
||||
tags: [], raises: [], enforceNoRaises, noSideEffect.}
|
||||
## Compares the memory blocks `a` and `b`. `size` bytes will
|
||||
## be compared.
|
||||
##
|
||||
|
||||
@@ -91,7 +91,10 @@ else:
|
||||
elif defined(gcMarkAndSweep):
|
||||
# XXX use 'compileOption' here
|
||||
include "system/gc_ms"
|
||||
else:
|
||||
elif not (defined(nimV2) or usesDestructors):
|
||||
# equivalent to a plain `else` here, but spelled out so that the IC
|
||||
# dependency scanner (which sees `else` imports/includes unguarded)
|
||||
# doesn't schedule system/gc's transitive imports under --mm:orc
|
||||
include "system/gc"
|
||||
|
||||
when not declared(nimNewSeqOfCap) and not defined(nimSeqsV2):
|
||||
|
||||
@@ -40,7 +40,8 @@ type
|
||||
wasm32, ## WASM, 32-bit
|
||||
e2k, ## MCST Elbrus 2000
|
||||
loongarch64, ## LoongArch 64-bit processor
|
||||
s390x ## IBM Z
|
||||
s390x, ## IBM Z
|
||||
wasm64 ## WASM, 64-bit
|
||||
|
||||
OsPlatform* {.pure.} = enum ## the OS this program will run on.
|
||||
none, dos, windows, os2, linux, morphos, skyos, solaris,
|
||||
@@ -101,5 +102,6 @@ const
|
||||
elif defined(e2k): CpuPlatform.e2k
|
||||
elif defined(loongarch64): CpuPlatform.loongarch64
|
||||
elif defined(s390x): CpuPlatform.s390x
|
||||
elif defined(wasm64): CpuPlatform.wasm64
|
||||
else: CpuPlatform.none
|
||||
## the CPU this program will run on.
|
||||
|
||||
@@ -261,4 +261,14 @@ template readRawData*(s: string; start = 0): ptr UncheckedArray[char] =
|
||||
## Template ensures no copy of `s`; ptr is valid while `s` is alive.
|
||||
rawDataImpl(cast[ptr NimStringV2](unsafeAddr s), start)
|
||||
|
||||
template readRawDataStable*(s: var string; start = 0): ptr UncheckedArray[char] =
|
||||
## Like `readRawData`, but the returned pointer additionally survives moves and
|
||||
## copies of `s` (while `s` stays alive and is not reassigned). For this string
|
||||
## implementation the char data already lives in a heap payload at an address
|
||||
## independent of the `string` value itself, so no promotion is needed and this
|
||||
## is identical to `readRawData`. Takes `s` by `var` to match the `--strings:sso`
|
||||
## version (which promotes a small inline string to the heap), so code written
|
||||
## against `readRawDataStable` compiles unchanged under either implementation.
|
||||
rawDataImpl(cast[ptr NimStringV2](addr s), start)
|
||||
|
||||
{.pop.}
|
||||
|
||||
@@ -770,6 +770,33 @@ template readRawData*(s: string; start = 0): ptr UncheckedArray[char] =
|
||||
## Template ensures no copy of `s` is made; ptr is valid while `s` is alive.
|
||||
rawDataImpl(cast[ptr SmallString](unsafeAddr s), start)
|
||||
|
||||
proc readRawDataStable*(s: var string; start = 0): ptr UncheckedArray[char] {.inline.} =
|
||||
## Like `readRawData`, but the returned pointer stays valid across moves and
|
||||
## copies of `s` (as long as `s` stays alive and is not reassigned). A
|
||||
## short/medium string keeps its chars *inline* in the string object, so a
|
||||
## plain `readRawData` pointer dangles the moment the object is moved; this
|
||||
## promotes `s` to its heap (long) representation first, whose payload address
|
||||
## is independent of where the string object itself lives. Use this whenever an
|
||||
## interior pointer must outlive the current scope of the owning string (e.g.
|
||||
## a cursor cached alongside the buffer it points into).
|
||||
let ss = cast[ptr SmallString](addr s)
|
||||
let slen = ssLen(ss[])
|
||||
if slen > 0 and slen <= PayloadSize:
|
||||
# Promote inline/medium to a long heap block so the payload lives at a
|
||||
# stable address. Mirrors the short/medium -> long transition in `add`.
|
||||
let newCap = max(slen, resize(slen))
|
||||
let p = cast[ptr LongString](alloc(LongStringDataOffset + newCap + 1))
|
||||
p.rc = 1
|
||||
p.fullLen = slen
|
||||
p.capImpl = newCap
|
||||
copyMem(addr p.data[0], inlinePtr(ss[]), slen)
|
||||
p.data[slen] = '\0'
|
||||
ss[].more = p
|
||||
setSSLen(ss[], HeapSlen)
|
||||
# Hot-prefix cache (bytes 1..AlwaysAvail) already mirrors data[0..AlwaysAvail-1]
|
||||
# because setSSLen only rewrote byte 0; the inline chars are untouched.
|
||||
rawDataImpl(ss, start)
|
||||
|
||||
# These take `string` (tyString) so the codegen uses them directly, bypassing
|
||||
# strmantle.nim's versions which go through nimStrLen/nimStrAtMutV3 compilerproc calls.
|
||||
proc cmpStrings(a, b: string): int {.compilerproc, inline.} =
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2026 Nim Contributors
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Wrapper for the 8-bit PCRE2 API.
|
||||
|
||||
when sizeof(int) == 4:
|
||||
const ANCHORED* = low(int)
|
||||
else:
|
||||
const ANCHORED* = int(0x80000000)
|
||||
|
||||
const
|
||||
NO_UTF_CHECK* = int(0x40000000)
|
||||
ENDANCHORED* = int(0x20000000)
|
||||
|
||||
const
|
||||
ALLOW_EMPTY_CLASS* = 0x00000001
|
||||
ALT_BSUX* = 0x00000002
|
||||
AUTO_CALLOUT* = 0x00000004
|
||||
CASELESS* = 0x00000008
|
||||
DOLLAR_ENDONLY* = 0x00000010
|
||||
DOTALL* = 0x00000020
|
||||
DUPNAMES* = 0x00000040
|
||||
EXTENDED* = 0x00000080
|
||||
FIRSTLINE* = 0x00000100
|
||||
MATCH_UNSET_BACKREF* = 0x00000200
|
||||
MULTILINE* = 0x00000400
|
||||
NEVER_UCP* = 0x00000800
|
||||
NEVER_UTF* = 0x00001000
|
||||
NO_AUTO_CAPTURE* = 0x00002000
|
||||
NO_AUTO_POSSESS* = 0x00004000
|
||||
NO_DOTSTAR_ANCHOR* = 0x00008000
|
||||
NO_START_OPTIMIZE* = 0x00010000
|
||||
NO_START_OPTIMISE* = NO_START_OPTIMIZE
|
||||
UCP* = 0x00020000
|
||||
UNGREEDY* = 0x00040000
|
||||
UTF* = 0x00080000
|
||||
UTF8* = UTF
|
||||
NEVER_BACKSLASH_C* = 0x00100000
|
||||
ALT_CIRCUMFLEX* = 0x00200000
|
||||
ALT_VERBNAMES* = 0x00400000
|
||||
USE_OFFSET_LIMIT* = 0x00800000
|
||||
EXTENDED_MORE* = 0x01000000
|
||||
LITERAL* = 0x02000000
|
||||
MATCH_INVALID_UTF* = 0x04000000
|
||||
ALT_EXTENDED_CLASS* = 0x08000000
|
||||
|
||||
## PCRE2 no longer exposes PCRE's `JAVASCRIPT_COMPAT` option. `ALT_BSUX`
|
||||
## preserves the most important JavaScript-style escape handling.
|
||||
JAVASCRIPT_COMPAT* = ALT_BSUX
|
||||
|
||||
const
|
||||
JIT_COMPLETE* = 0x00000001
|
||||
JIT_PARTIAL_SOFT* = 0x00000002
|
||||
JIT_PARTIAL_HARD* = 0x00000004
|
||||
JIT_INVALID_UTF* = 0x00000100
|
||||
JIT_TEST_ALLOC* = 0x00000200
|
||||
|
||||
const
|
||||
NOTBOL* = 0x00000001
|
||||
NOTEOL* = 0x00000002
|
||||
NOTEMPTY* = 0x00000004
|
||||
NOTEMPTY_ATSTART* = 0x00000008
|
||||
PARTIAL_SOFT* = 0x00000010
|
||||
PARTIAL_HARD* = 0x00000020
|
||||
DFA_RESTART* = 0x00000040
|
||||
DFA_SHORTEST* = 0x00000080
|
||||
NO_JIT* = 0x00002000
|
||||
COPY_MATCHED_SUBJECT* = 0x00004000
|
||||
DISABLE_RECURSELOOP_CHECK* = 0x00040000
|
||||
|
||||
const
|
||||
NEWLINE_CR* = 1
|
||||
NEWLINE_LF* = 2
|
||||
NEWLINE_CRLF* = 3
|
||||
NEWLINE_ANY* = 4
|
||||
NEWLINE_ANYCRLF* = 5
|
||||
NEWLINE_NUL* = 6
|
||||
BSR_UNICODE* = 1
|
||||
BSR_ANYCRLF* = 2
|
||||
|
||||
const
|
||||
ERROR_NOMATCH* = -1
|
||||
ERROR_PARTIAL* = -2
|
||||
|
||||
ERROR_UTF8_ERR1* = -3
|
||||
ERROR_UTF8_ERR2* = -4
|
||||
ERROR_UTF8_ERR3* = -5
|
||||
ERROR_UTF8_ERR4* = -6
|
||||
ERROR_UTF8_ERR5* = -7
|
||||
ERROR_UTF8_ERR6* = -8
|
||||
ERROR_UTF8_ERR7* = -9
|
||||
ERROR_UTF8_ERR8* = -10
|
||||
ERROR_UTF8_ERR9* = -11
|
||||
ERROR_UTF8_ERR10* = -12
|
||||
ERROR_UTF8_ERR11* = -13
|
||||
ERROR_UTF8_ERR12* = -14
|
||||
ERROR_UTF8_ERR13* = -15
|
||||
ERROR_UTF8_ERR14* = -16
|
||||
ERROR_UTF8_ERR15* = -17
|
||||
ERROR_UTF8_ERR16* = -18
|
||||
ERROR_UTF8_ERR17* = -19
|
||||
ERROR_UTF8_ERR18* = -20
|
||||
ERROR_UTF8_ERR19* = -21
|
||||
ERROR_UTF8_ERR20* = -22
|
||||
ERROR_UTF8_ERR21* = -23
|
||||
|
||||
ERROR_BADDATA* = -29
|
||||
ERROR_MIXEDTABLES* = -30
|
||||
ERROR_BADMAGIC* = -31
|
||||
ERROR_BADMODE* = -32
|
||||
ERROR_BADOFFSET* = -33
|
||||
ERROR_BADOPTION* = -34
|
||||
ERROR_BADREPLACEMENT* = -35
|
||||
ERROR_BADUTFOFFSET* = -36
|
||||
ERROR_CALLOUT* = -37
|
||||
ERROR_INTERNAL* = -44
|
||||
ERROR_JIT_BADOPTION* = -45
|
||||
ERROR_JIT_STACKLIMIT* = -46
|
||||
ERROR_MATCHLIMIT* = -47
|
||||
ERROR_NOMEMORY* = -48
|
||||
ERROR_NOSUBSTRING* = -49
|
||||
ERROR_NULL* = -51
|
||||
ERROR_RECURSELOOP* = -52
|
||||
ERROR_DEPTHLIMIT* = -53
|
||||
ERROR_RECURSIONLIMIT* = ERROR_DEPTHLIMIT
|
||||
ERROR_UNAVAILABLE* = -54
|
||||
ERROR_UNSET* = -55
|
||||
ERROR_BADOFFSETLIMIT* = -56
|
||||
ERROR_HEAPLIMIT* = -63
|
||||
ERROR_DFA_UINVALID_UTF* = -66
|
||||
ERROR_INVALIDOFFSET* = -67
|
||||
ERROR_JIT_UNSUPPORTED* = -68
|
||||
|
||||
const
|
||||
INFO_ALLOPTIONS* = 0
|
||||
INFO_ARGOPTIONS* = 1
|
||||
INFO_BACKREFMAX* = 2
|
||||
INFO_BSR* = 3
|
||||
INFO_CAPTURECOUNT* = 4
|
||||
INFO_FIRSTCODEUNIT* = 5
|
||||
INFO_FIRSTCODETYPE* = 6
|
||||
INFO_FIRSTBITMAP* = 7
|
||||
INFO_HASCRORLF* = 8
|
||||
INFO_JCHANGED* = 9
|
||||
INFO_JITSIZE* = 10
|
||||
INFO_LASTCODEUNIT* = 11
|
||||
INFO_LASTCODETYPE* = 12
|
||||
INFO_MATCHEMPTY* = 13
|
||||
INFO_MATCHLIMIT* = 14
|
||||
INFO_MAXLOOKBEHIND* = 15
|
||||
INFO_MINLENGTH* = 16
|
||||
INFO_NAMECOUNT* = 17
|
||||
INFO_NAMEENTRYSIZE* = 18
|
||||
INFO_NAMETABLE* = 19
|
||||
INFO_NEWLINE* = 20
|
||||
INFO_DEPTHLIMIT* = 21
|
||||
INFO_RECURSIONLIMIT* = INFO_DEPTHLIMIT
|
||||
INFO_SIZE* = 22
|
||||
INFO_HASBACKSLASHC* = 23
|
||||
INFO_FRAMESIZE* = 24
|
||||
INFO_HEAPLIMIT* = 25
|
||||
INFO_EXTRAOPTIONS* = 26
|
||||
|
||||
const
|
||||
CONFIG_BSR* = 0
|
||||
CONFIG_JIT* = 1
|
||||
CONFIG_JITTARGET* = 2
|
||||
CONFIG_LINKSIZE* = 3
|
||||
CONFIG_MATCHLIMIT* = 4
|
||||
CONFIG_NEWLINE* = 5
|
||||
CONFIG_PARENSLIMIT* = 6
|
||||
CONFIG_DEPTHLIMIT* = 7
|
||||
CONFIG_RECURSIONLIMIT* = CONFIG_DEPTHLIMIT
|
||||
CONFIG_STACKRECURSE* = 8
|
||||
CONFIG_UNICODE* = 9
|
||||
CONFIG_UNICODE_VERSION* = 10
|
||||
CONFIG_VERSION* = 11
|
||||
CONFIG_HEAPLIMIT* = 12
|
||||
CONFIG_NEVER_BACKSLASH_C* = 13
|
||||
CONFIG_COMPILED_WIDTHS* = 14
|
||||
CONFIG_TABLES_LENGTH* = 15
|
||||
|
||||
const
|
||||
ZERO_TERMINATED* = not 0.csize_t
|
||||
UNSET* = not 0.csize_t
|
||||
|
||||
type
|
||||
Pcre* = object
|
||||
CompileContext* = object
|
||||
GeneralContext* = object
|
||||
MatchContext* = object
|
||||
MatchData* = object
|
||||
JitStack* = object
|
||||
|
||||
when not defined(usePcreHeader):
|
||||
when hostOS == "windows":
|
||||
const pcre2Dll = "pcre2-8.dll"
|
||||
elif hostOS == "macosx":
|
||||
const pcre2Dll = "libpcre2-8(.0|).dylib"
|
||||
else:
|
||||
const pcre2Dll = "libpcre2-8.so(.0|)"
|
||||
{.push dynlib: pcre2Dll.}
|
||||
else:
|
||||
{.passC: "-DPCRE2_CODE_UNIT_WIDTH=8".}
|
||||
{.push header: "<pcre2.h>".}
|
||||
|
||||
{.push cdecl, importc: "pcre2_$1_8".}
|
||||
|
||||
proc compile*(pattern: ptr uint8,
|
||||
length: csize_t,
|
||||
options: uint32,
|
||||
errorCode: ptr cint,
|
||||
errorOffset: ptr csize_t,
|
||||
context: ptr CompileContext): ptr Pcre
|
||||
|
||||
proc code_free*(code: ptr Pcre)
|
||||
|
||||
proc config*(what: uint32,
|
||||
where: pointer): cint
|
||||
|
||||
proc get_error_message*(errorCode: cint,
|
||||
buffer: ptr uint8,
|
||||
bufferLength: csize_t): cint
|
||||
|
||||
proc match*(code: ptr Pcre,
|
||||
subject: ptr uint8,
|
||||
length: csize_t,
|
||||
startOffset: csize_t,
|
||||
options: uint32,
|
||||
matchData: ptr MatchData,
|
||||
context: ptr MatchContext): cint
|
||||
|
||||
proc match_data_create*(oveccount: uint32,
|
||||
context: ptr GeneralContext): ptr MatchData
|
||||
|
||||
proc match_data_create_from_pattern*(code: ptr Pcre,
|
||||
context: ptr GeneralContext): ptr MatchData
|
||||
|
||||
proc match_data_free*(matchData: ptr MatchData)
|
||||
|
||||
proc get_ovector_pointer*(matchData: ptr MatchData): ptr csize_t
|
||||
|
||||
proc get_ovector_count*(matchData: ptr MatchData): uint32
|
||||
|
||||
proc pattern_info*(code: ptr Pcre,
|
||||
what: uint32,
|
||||
where: pointer): cint
|
||||
|
||||
proc jit_compile*(code: ptr Pcre,
|
||||
options: uint32): cint
|
||||
|
||||
proc jit_free_unused_memory*()
|
||||
|
||||
{.pop.}
|
||||
{.pop.}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
[](https://dev.azure.com/nim-lang/Nim/_build/latest?definitionId=1&branchName=devel)
|
||||
|
||||
|
||||
This repository contains the Nim compiler, Nim's stdlib, tools, and documentation.
|
||||
For more information about Nim, including downloads and documentation for
|
||||
the latest release, check out [Nim's website][nim-site] or [bleeding edge docs](https://nim-lang.github.io/Nim/).
|
||||
|
||||
@@ -158,6 +158,7 @@ pkg "sim"
|
||||
pkg "smtp", "nimble compileExample"
|
||||
pkg "snip", "nimble test", "https://github.com/genotrance/snip"
|
||||
pkg "ssostrings", "nim c -r tests/tssostrings.nim"
|
||||
pkg "ssz_serialization", "nim c -r tests/test_all.nim"
|
||||
pkg "stew"
|
||||
pkg "stint", "nimble test_internal"
|
||||
pkg "strslice"
|
||||
|
||||
19
tests/arc/t19312.nim
Normal file
19
tests/arc/t19312.nim
Normal file
@@ -0,0 +1,19 @@
|
||||
discard """
|
||||
matrix: "--mm:orc"
|
||||
output: '''(val: 1)
|
||||
(val: 1)'''
|
||||
"""
|
||||
# Issue #19312: copied ref object is converted to nil if not used in declaration module under ARC/ORC
|
||||
# https://github.com/nim-lang/Nim/issues/19312
|
||||
|
||||
type
|
||||
Wrapper* = object
|
||||
val: int
|
||||
RefWrapper* = ref Wrapper
|
||||
|
||||
let
|
||||
a* = RefWrapper(val: 1)
|
||||
b* = a
|
||||
|
||||
echo b[]
|
||||
echo a[]
|
||||
43
tests/arc/t25595.nim
Normal file
43
tests/arc/t25595.nim
Normal file
@@ -0,0 +1,43 @@
|
||||
discard """
|
||||
matrix: "--mm:orc; --mm:arc; --mm:refc"
|
||||
"""
|
||||
|
||||
# bug #25595: cursor inference must not borrow a case object whose source can be
|
||||
# mutated through the cursor's own ref across a call. `let c = h.w` was inferred as a
|
||||
# non-owning cursor; `clear(c.r)` overwrites `h.w` via the cursor's back-reference,
|
||||
# freeing the ref while the borrow still uses it -> use-after-free. Detected here
|
||||
# deterministically: the element's destructor must not run during the call.
|
||||
|
||||
var destroyed = false
|
||||
|
||||
type
|
||||
O = ref object
|
||||
value: int
|
||||
home: H
|
||||
W = object
|
||||
case k: bool
|
||||
of true: r: O
|
||||
of false: discard
|
||||
H = ref object
|
||||
w: W
|
||||
|
||||
proc `=destroy`(o: var typeof(O()[])) =
|
||||
destroyed = true
|
||||
|
||||
proc clear(o: O): int =
|
||||
o.home.w = W()
|
||||
doAssert not destroyed, "use-after-free: element destroyed during the call"
|
||||
result = o.value
|
||||
|
||||
proc go(h: H): int =
|
||||
let c = h.w
|
||||
result = clear(c.r)
|
||||
|
||||
proc main =
|
||||
let h = H()
|
||||
let o = O(value: 42)
|
||||
o.home = h
|
||||
h.w = W(k: true, r: o)
|
||||
doAssert go(h) == 42
|
||||
|
||||
main()
|
||||
47
tests/arc/t25850.nim
Normal file
47
tests/arc/t25850.nim
Normal file
@@ -0,0 +1,47 @@
|
||||
discard """
|
||||
cmd: '''nim c --mm:orc --expandArc:uIf --expandArc:uCase $file'''
|
||||
nimout: '''
|
||||
--expandArc: uIf
|
||||
|
||||
block :tmp:
|
||||
let s = w()
|
||||
if true:
|
||||
r[] = s
|
||||
else:
|
||||
r[] = s
|
||||
-- end of expandArc ------------------------
|
||||
--expandArc: uCase
|
||||
|
||||
block :tmp:
|
||||
let s = w()
|
||||
case n
|
||||
of 0:
|
||||
r[] = s
|
||||
else:
|
||||
r[] = w()
|
||||
-- end of expandArc ------------------------
|
||||
'''
|
||||
"""
|
||||
|
||||
# bug #25850
|
||||
# Assigning an expression-based control flow construct (an `if`/`case` nested in
|
||||
# a `block`) must distribute the assignment directly into the leaf branches
|
||||
# instead of creating redundant intermediate temporaries per branch.
|
||||
|
||||
proc w(): array[1000, byte] {.noinline.} = discard
|
||||
|
||||
proc uIf(r: ptr array[1000, byte]) =
|
||||
r[] = (block:
|
||||
let s = w()
|
||||
if true: s else: s)
|
||||
|
||||
proc uCase(r: ptr array[1000, byte], n: int) =
|
||||
r[] = (block:
|
||||
let s = w()
|
||||
case n
|
||||
of 0: s
|
||||
else: w())
|
||||
|
||||
var d: array[1000, byte]
|
||||
uIf(addr d)
|
||||
uCase(addr d, 0)
|
||||
17
tests/async/t16416.nim
Normal file
17
tests/async/t16416.nim
Normal file
@@ -0,0 +1,17 @@
|
||||
discard """
|
||||
output: '''done'''
|
||||
"""
|
||||
# Issue #16416: Can't call closure iterator from inside an async function
|
||||
# https://github.com/nim-lang/Nim/issues/16416
|
||||
|
||||
import asyncdispatch
|
||||
|
||||
iterator x(): int {.closure.} =
|
||||
yield 1
|
||||
|
||||
proc y() {.async.} =
|
||||
for z in x():
|
||||
discard
|
||||
|
||||
waitFor y()
|
||||
echo "done"
|
||||
36
tests/ccg/tclosure_err_panic_goto.nim
Normal file
36
tests/ccg/tclosure_err_panic_goto.nim
Normal file
@@ -0,0 +1,36 @@
|
||||
discard """
|
||||
matrix: "; --panics:on"
|
||||
"""
|
||||
# issue #25851: --panics:on must not drop the nimErr_ check after a closure
|
||||
# call whose result is consumed directly (e.g. `result.add elem(src)`).
|
||||
# Regression from #25295.
|
||||
|
||||
type
|
||||
Overrun = object of CatchableError
|
||||
Source = object
|
||||
data: seq[bool]
|
||||
cursor: int
|
||||
ElemFn = proc(src: var Source): bool {.closure.}
|
||||
|
||||
proc drawBool(src: var Source): bool =
|
||||
if src.cursor >= src.data.len: raise newException(Overrun, "exhausted")
|
||||
result = src.data[src.cursor]; inc src.cursor
|
||||
|
||||
proc listRun(elem: ElemFn, src: var Source): seq[bool] =
|
||||
result = @[]
|
||||
while true:
|
||||
if not src.drawBool(): break
|
||||
result.add elem(src) # closure call – the result flows straight
|
||||
# into `add`, which previously caused the
|
||||
# compiler to skip the nimErr_ check.
|
||||
|
||||
let elem: ElemFn = proc(src: var Source): bool = src.drawBool()
|
||||
|
||||
# Both --panics:on and --panics:off must propagate the Overrun.
|
||||
var caught = false
|
||||
try:
|
||||
var src = Source(data: @[true])
|
||||
discard listRun(elem, src)
|
||||
except Overrun:
|
||||
caught = true
|
||||
doAssert caught, "Overrun exception was swallowed"
|
||||
@@ -80,3 +80,20 @@ block tissue7104:
|
||||
sp do ():
|
||||
inc i
|
||||
echo "ok ", i
|
||||
|
||||
block: # bug #25903
|
||||
iterator g: int {.closure.} =
|
||||
discard try:
|
||||
yield 0
|
||||
0
|
||||
except IOError, OSError:
|
||||
0
|
||||
let _ = g
|
||||
|
||||
block: # bug #25904
|
||||
iterator w: int {.closure.} =
|
||||
discard try: 0
|
||||
except IOError, OSError:
|
||||
yield 0
|
||||
0
|
||||
let _ = w
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
discard """
|
||||
targets: "c"
|
||||
matrix: "--debugger:native --mangle:nim"
|
||||
ccodecheck: "'testFunc__titaniummangle95nim_u'"
|
||||
ccodecheck: "'testFunc_u' \\d+ '__titaniummangle95nim'"
|
||||
"""
|
||||
|
||||
#When debugging this notice that if one check fails, it can be due to any of the above.
|
||||
|
||||
28
tests/concepts/conceptv2negative/tconsistentparams.nim
Normal file
28
tests/concepts/conceptv2negative/tconsistentparams.nim
Normal file
@@ -0,0 +1,28 @@
|
||||
discard """
|
||||
action: "reject"
|
||||
errormsg: "type mismatch"
|
||||
"""
|
||||
|
||||
type
|
||||
Dollarable = concept
|
||||
proc `$`(x: Self): string
|
||||
|
||||
proc checkEqual(x, y: Dollarable) =
|
||||
if x != y:
|
||||
echo $x
|
||||
echo $y
|
||||
|
||||
type
|
||||
StateFlags = enum
|
||||
sfMatch
|
||||
sfSoft
|
||||
|
||||
MatchKind = enum
|
||||
NoFurtherMatch
|
||||
NoMatch
|
||||
Match
|
||||
AllFurtherMatch
|
||||
|
||||
proc `==`(a: set[StateFlags]; b: MatchKind): bool = true
|
||||
|
||||
checkEqual({sfMatch, sfSoft}, Match)
|
||||
23
tests/concepts/t14913.nim
Normal file
23
tests/concepts/t14913.nim
Normal file
@@ -0,0 +1,23 @@
|
||||
discard """
|
||||
output: '''done'''
|
||||
"""
|
||||
# Issue #14913: Compiler crash when using a default parameter value for a parameter whose type is a concept
|
||||
# https://github.com/nim-lang/Nim/issues/14913
|
||||
|
||||
type
|
||||
State = object
|
||||
MoreState = object
|
||||
StringRecord = concept x, type T
|
||||
for k, v in fieldPairs(x):
|
||||
k is string
|
||||
v is string
|
||||
StateStrings = object
|
||||
a, b: string
|
||||
|
||||
proc combine(a: State, b: MoreState): StateStrings = discard
|
||||
|
||||
proc whoops[T: StringRecord](a: State, b: MoreState, c: T = a.combine(b)) =
|
||||
discard
|
||||
|
||||
whoops(State(), MoreState())
|
||||
echo "done"
|
||||
@@ -14,6 +14,8 @@ b
|
||||
c
|
||||
1
|
||||
2
|
||||
5
|
||||
test
|
||||
'''
|
||||
"""
|
||||
import conceptsv2_helper
|
||||
@@ -600,3 +602,15 @@ block:
|
||||
|
||||
let test = MemMapFileStream()
|
||||
spring(test)
|
||||
|
||||
# explicit negative "bind once"
|
||||
|
||||
type
|
||||
Dollarable = concept
|
||||
proc `$`(x: Self): string
|
||||
|
||||
proc checkEqual2[T: Dollarable; S: Dollarable](x: T, y: S) =
|
||||
echo $x
|
||||
echo $y
|
||||
|
||||
checkEqual2(5, "test")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user