Compare commits

..

1 Commits

Author SHA1 Message Date
ringabout
c38fab3576 test 2026-03-05 16:11:11 +08:00
488 changed files with 5123 additions and 31398 deletions

View File

@@ -15,7 +15,7 @@ jobs:
name: ${{ matrix.platform }}-bisects
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install OpenSSL (Windows)
if: |

View File

@@ -53,7 +53,7 @@ jobs:
steps:
- name: 'Checkout'
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 2
@@ -109,7 +109,7 @@ jobs:
if: |
github.event_name == 'push' && github.ref == 'refs/heads/devel' &&
matrix.target == 'linux'
uses: crazy-max/ghaction-github-pages@v5
uses: crazy-max/ghaction-github-pages@v4
with:
build_dir: doc/html
env:

View File

@@ -18,12 +18,12 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
batch: ["0_3", "1_3", "2_3"] # list of `index_num`
os: [ubuntu-latest, macos-14]
batch: ["allowed_failures", "0_3", "1_3", "2_3"] # list of `index_num`
include:
- os: ubuntu-latest
cpu: amd64
- os: macos-latest
- os: macos-14
cpu: arm64
name: '${{ matrix.os }} (batch: ${{ matrix.batch }})'
runs-on: ${{ matrix.os }}
@@ -33,12 +33,12 @@ jobs:
NIM_TESTAMENT_BATCH: ${{ matrix.batch }}
steps:
- name: 'Checkout'
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 2
- name: 'Install node.js'
uses: actions/setup-node@v7
uses: actions/setup-node@v6
with:
node-version: 24

View File

@@ -17,12 +17,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: 'Checkout'
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 2
- name: 'Install node.js'
uses: actions/setup-node@v7
uses: actions/setup-node@v6
with:
node-version: 24
@@ -60,7 +60,7 @@ jobs:
run: nim c -r -d:release ci/action.nim
- name: 'Comment'
uses: actions/github-script@v9
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');

View File

@@ -9,7 +9,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v11
- uses: actions/stale@v10
with:
days-before-pr-stale: 365
days-before-pr-close: 30

1
.gitignore vendored
View File

@@ -87,7 +87,6 @@ tweeter_test.db
/tests/megatest.nim
/tests/ic/*_temp.nim
/tests/ic/*_mm/
/tests/navigator/*_temp.nim

View File

@@ -35,23 +35,10 @@ errors.
- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`.
- Procedure compatibility also checks the backend representation of the
parameter and result types, not just their source-level shape. Use
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
## Standard library additions and changes
[//]: # "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.
@@ -73,33 +60,17 @@ parameter and result types, not just their source-level shape. Use
- `copyDirWithPermissions` to recursively preserve attributes
- `system.setLenUninit` now supports refc, JS and VM backends.
- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth.
- `std/parseopt` now supports multiple parser modes via a `CliMode` enum.
Modes include `Nim` (default, fully compatible) and two new experimental modes:
`Lax` and `Gnu` for different option parsing behaviors.
- `std/symlinks.expandSymlink` now supports Windows symlinks and junctions with
POSIX-like single-hop `readlink` semantics.
- `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` 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).
- `std/jsonutils`: `fromJson` now throws an exception when converting to `array`/`seq` if the JSON isn't an array instead of silently failing
## Language changes
@@ -138,11 +109,6 @@ parameter and result types, not just their source-level shape. Use
See the [experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#typeminusbound-overloads)
for more information.
- Seven more Unicode characters are now parsed as operators, implementing the RFC
https://github.com/nim-lang/RFCs/issues/571: `⟑ ⟇ ⩓ ⩔ ■ □ ☆`. They all have the
same priority as `*` (multiplication). As with the other Unicode operators, Nim
only lexes them; their meaning is up to user code.
## Compiler changes
- Fixed a bug where `sizeof(T)` inside a `typedesc` template called from a generic type's
@@ -150,13 +116,6 @@ parameter and result types, not just their source-level shape. Use
The issue was that `hasValuelessStatics` in `semtypinst.nim` didn't recognize
`tyTypeDesc(tyGenericParam)` as an unresolved generic parameter.
- The JS backend now implements write-through for `var openArray` parameters that
receive a `toOpenArray` view (bug #15952): mutations reach the caller's storage
instead of silently writing to a copy. Fixed homogeneous numeric arrays
(`array[N, T]`, JS typed arrays) slice via `subarray`; `seq` and non-numeric
arrays slice via a `{base, off, len}` view. This also covers seq/non-numeric-array
write-through, pass-through, re-slicing and `@` (openArray-to-seq) of such views.
## Tool changes
- Added `--raw` flag when generating JSON docs to not render markup.

View File

@@ -8,7 +8,7 @@ const
nkBracketExpr, nkDerefExpr, nkHiddenDeref,
nkAddr, nkHiddenAddr,
nkObjDownConv, nkObjUpConv}
PathKinds1* = {nkHiddenStdConv, nkHiddenSubConv, nkCast}
PathKinds1* = {nkHiddenStdConv, nkHiddenSubConv}
proc skipConvDfa*(n: PNode): PNode =
result = n
@@ -125,3 +125,4 @@ proc aliases*(obj, field: PNode): AliasKind =
else:
result = maybe
else: assert false # unreachable

View File

@@ -21,49 +21,6 @@ type
TAnalysisResult* = enum
arNo, arMaybe, arYes
PartFlag* = enum
pfStructural ## use structural prefix-chain detection and tree-walk
pfBidirectional ## also check reverse direction per field in nkObjConstr
proc isCompileTimeOnlyNode(n: PNode): bool {.inline.} =
## `typeof` and typedesc/static values describe types at compile time; they
## do not read the runtime location that alias analysis is protecting.
n.kind == nkTypeOfExpr or (n.typ != nil and n.typ.isCompileTimeOnly)
func sameLocation(a, b: PNode): bool =
template sameConstIndex(a, b: PNode): bool =
a.kind in nkLiterals and b.kind in nkLiterals and a.intVal == b.intVal
var a = a
var b = b
while a.kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv}: a = a[1]
while b.kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv}: b = b[1]
if a.kind != b.kind: return false
case a.kind
of nkSym: result = a.sym.id == b.sym.id
of nkDotExpr, nkCheckedFieldExpr:
result = a[1].kind == nkSym and b[1].kind == nkSym and
sameLocation(a[0], b[0]) and a[1].sym.id == b[1].sym.id
of nkBracketExpr:
result = sameLocation(a[0], b[0]) and sameConstIndex(a[1], b[1])
of nkObjUpConv, nkObjDownConv, nkDerefExpr, nkHiddenDeref:
result = sameLocation(a[0], b[0])
else: result = false
proc isAccessorPrefixOf(a, b: PNode): bool =
var cur = b
while cur.kind in {nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, nkObjUpConv,
nkObjDownConv, nkHiddenDeref, nkDerefExpr,
nkHiddenStdConv, nkHiddenSubConv, nkConv}:
if sameLocation(cur, a): return true
case cur.kind
of nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv,
nkHiddenDeref, nkDerefExpr:
cur = cur[0]
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
cur = cur[1]
else: discard
result = sameLocation(cur, a)
proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult
proc isPartOfAux(n: PNode, b: PType, marker: var IntSet): TAnalysisResult =
@@ -113,28 +70,14 @@ proc isPartOf(a, b: PType): TAnalysisResult =
# watch out: parameters reversed because I'm too lazy to change the code...
result = isPartOfAux(b, a, marker)
proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
## Checks if location `a` can be part of location `b`: i.e. whether writing to
## `b` could affect what `a` reads. We treat seqs and strings as pointers
## because the code gen often just passes them as such.
proc isPartOf*(a, b: PNode): TAnalysisResult =
## checks if location `a` can be part of location `b`. We treat seqs and
## strings as pointers because the code gen often just passes them as such.
##
## Note: `a` can only be part of `b`, if `a`'s type can be part of `b`'s
## type. Since however type analysis is more expensive, we perform it only
## if necessary.
##
## When `pfStructural` is set additional aliasing is detected:
## * a structural prefix of an accessor chain is considered part of it
## (e.g. `x.f <| x.f.g`). Normally `x.f !<| x.f.g` because the
## same-kind `nkDotExpr` comparison treats the differing field names as
## siblings, but `pfStructural` walks the chain to recognise the
## relationship.
## * Unrecognised node kinds are traversed recursively.
##
## When `pfBidirectional` is set:
## * In `nkObjConstr` the reverse direction `isPartOf(value, a)` is also
## checked per field value so that reads hidden behind calls/closures
## are detected.
##
## cases:
##
## YES-cases:
@@ -143,14 +86,13 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
## x[] <| x
## x[i] <| x
## x.f <| x
## x.f <| x.f.g # when pfStructural (prefix chain)
## ```
##
## NO-cases:
## ```
## x !<| y # depending on type and symbol kind
## x[constA] !<| x[constB]
## x.f !<| x.g # sibling fields at same level
## x.f !<| x.g
## x.f !<| y.f iff x !<= y
## ```
##
@@ -162,13 +104,10 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
##
## x[] ?<| y depending on type
## ```
if a.isCompileTimeOnlyNode or b.isCompileTimeOnlyNode:
return arNo
if a.kind == b.kind:
case a.kind
of nkSym:
const varKinds = {skVar, skTemp, skResult, skProc, skFunc}
const varKinds = {skVar, skTemp, skProc, skFunc}
# same symbol: aliasing:
if a.sym.id == b.sym.id: result = arYes
elif a.sym.kind in varKinds or b.sym.kind in varKinds:
@@ -182,7 +121,7 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
else:
result = arNo
of nkBracketExpr:
result = isPartOf(a[0], b[0], flags)
result = isPartOf(a[0], b[0])
if a.len >= 2 and b.len >= 2:
# array accesses:
if result == arYes and isDeepConstExpr(a[1]) and isDeepConstExpr(b[1]):
@@ -192,11 +131,7 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
var y = if b[1].kind == nkHiddenStdConv: b[1][1] else: b[1]
if sameValue(x, y): result = arYes
elif pfStructural in flags and isAccessorPrefixOf(a, b):
result = arYes
else: result = arNo
elif pfStructural in flags and isAccessorPrefixOf(a, b):
result = arYes
# else: maybe and no are accurate
else:
# pointer derefs:
@@ -204,25 +139,22 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
if isPartOf(a.typ, b.typ) != arNo: result = arMaybe
of nkDotExpr:
result = isPartOf(a[0], b[0], flags)
result = isPartOf(a[0], b[0])
if result != arNo:
# if the fields are different, it's not the same location
if a[1].sym.id != b[1].sym.id:
if pfStructural in flags and isAccessorPrefixOf(a, b):
result = arYes
else:
result = arNo
result = arNo
of nkHiddenDeref, nkDerefExpr:
result = isPartOf(a[0], b[0], flags)
result = isPartOf(a[0], b[0])
# weaken because of indirection:
if result != arYes:
if isPartOf(a.typ, b.typ) != arNo: result = arMaybe
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
result = isPartOf(a[1], b[1], flags)
result = isPartOf(a[1], b[1])
of nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
result = isPartOf(a[0], b[0], flags)
result = isPartOf(a[0], b[0])
else: result = arNo
# Calls return a new location, so a default of ``arNo`` is fine.
else:
@@ -235,31 +167,31 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
case b.kind
of Ix0Kinds:
# a* !<| b.f iff a* !<| b
result = isPartOf(a, b[0], flags)
result = isPartOf(a, b[0])
of DerefKinds:
# a* !<| b[] iff
result = arNo
if isPartOf(a.typ, b.typ) != arNo:
result = isPartOf(a, b[0], flags)
result = isPartOf(a, b[0])
if result == arNo: result = arMaybe
of Ix1Kinds:
# a* !<| T(b) iff a* !<| b
result = isPartOf(a, b[1], flags)
result = isPartOf(a, b[1])
of nkSym:
# b is an atom, so we have to check a:
case a.kind
of Ix0Kinds:
# a.f !<| b* iff a.f !<| b*
result = isPartOf(a[0], b, flags)
result = isPartOf(a[0], b)
of Ix1Kinds:
result = isPartOf(a[1], b, flags)
result = isPartOf(a[1], b)
of DerefKinds:
if isPartOf(a.typ, b.typ) != arNo:
result = isPartOf(a[0], b, flags)
result = isPartOf(a[0], b)
if result == arNo: result = arMaybe
else:
result = arNo
@@ -267,34 +199,20 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
of nkObjConstr:
result = arNo
for i in 1..<b.len:
let res = isPartOf(a, b[i][1], flags)
let res = isPartOf(a, b[i][1])
if res != arNo:
result = res
if res == arYes: break
if pfBidirectional in flags:
let res2 = isPartOf(b[i][1], a, {pfStructural})
if res2 != arNo:
result = res2
if res2 == arYes: break
of nkCallKinds:
result = arNo
for i in 1..<b.len:
# A call such as `fill(typeof(result.f))` has a compile-time-only
# argument. It must not make the object constructor look aliased with
# `result.f`; runtime arguments remain subject to the normal analysis.
if b[i].isCompileTimeOnlyNode:
continue
let res = isPartOf(a, b[i], flags)
let res = isPartOf(a, b[i])
if res != arNo:
result = res
if res == arYes: break
of nkBracket:
if b.len > 0:
result = isPartOf(a, b[0], flags)
result = isPartOf(a, b[0])
else:
result = arNo
else:
if pfStructural in flags:
for i in 0..<b.safeLen:
if isPartOf(a, b[i], flags) != arNo: return arMaybe
result = arNo
else: result = arNo

View File

@@ -36,13 +36,6 @@ 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):
@@ -77,21 +70,11 @@ 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): lent PSym {.inline.} =
proc owner*(s: PSym): PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.ownerFieldImpl
proc owner*(s: PType): lent PSym {.inline.} =
proc owner*(s: PType): PSym {.inline.} =
if s.state == Partial: loadType(s)
result = s.ownerFieldImpl
@@ -114,7 +97,7 @@ proc `kind=`*(s: PSym, val: TSymKind) {.inline.} =
if s.state == Partial: loadSym(s)
s.kindImpl = val
proc gcUnsafetyReason*(s: PSym): lent PSym {.inline.} =
proc gcUnsafetyReason*(s: PSym): PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.gcUnsafetyReasonImpl
@@ -123,7 +106,7 @@ proc `gcUnsafetyReason=`*(s: PSym, val: PSym) {.inline.} =
if s.state == Partial: loadSym(s)
s.gcUnsafetyReasonImpl = val
proc transformedBody*(s: PSym): lent PNode {.inline.} =
proc transformedBody*(s: PSym): PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.transformedBodyImpl
@@ -133,7 +116,7 @@ proc `transformedBody=`*(s: PSym, val: PNode) {.inline.} =
if s.state == Partial: loadSym(s)
s.transformedBodyImpl = val
proc guard*(s: PSym): lent PSym {.inline.} =
proc guard*(s: PSym): PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.guardImpl
@@ -169,7 +152,7 @@ proc `magic=`*(s: PSym, val: TMagic) {.inline.} =
if s.state == Partial: loadSym(s)
s.magicImpl = val
proc typ*(s: PSym): lent PType {.inline.} =
proc typ*(s: PSym): PType {.inline.} =
if s.state == Partial: loadSym(s)
result = s.typImpl
@@ -215,7 +198,7 @@ proc `flags=`*(s: PSym, val: TSymFlags) {.inline.} =
if s.state == Partial: loadSym(s)
s.flagsImpl = val
proc ast*(s: PSym): lent PNode {.inline.} =
proc ast*(s: PSym): PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.astImpl
@@ -238,10 +221,7 @@ proc position*(s: PSym): int {.inline.} =
result = s.positionImpl
proc `position=`*(s: PSym, val: int) {.inline.} =
# 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.
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.positionImpl = val
@@ -263,7 +243,7 @@ proc `loc=`*(s: PSym, val: TLoc) {.inline.} =
if s.state == Partial: loadSym(s)
s.locImpl = val
proc annex*(s: PSym): lent PLib {.inline.} =
proc annex*(s: PSym): PLib {.inline.} =
if s.state == Partial: loadSym(s)
result = s.annexImpl
@@ -282,7 +262,7 @@ when hasFFI:
if s.state == Partial: loadSym(s)
s.cnameImpl = val
proc constraint*(s: PSym): lent PNode {.inline.} =
proc constraint*(s: PSym): PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.constraintImpl
@@ -291,7 +271,7 @@ proc `constraint=`*(s: PSym, val: PNode) {.inline.} =
if s.state == Partial: loadSym(s)
s.constraintImpl = val
proc instantiatedFrom*(s: PSym): lent PSym {.inline.} =
proc instantiatedFrom*(s: PSym): PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.instantiatedFromImpl
@@ -332,10 +312,7 @@ when defined(nimsuggest):
result = s.allUsagesImpl
proc `allUsages=`*(s: PSym, val: sink seq[TLineInfo]) {.inline.} =
# No `assert s.state != Sealed`: `allUsagesImpl` is nimsuggest-only usage
# tracking, NOT part of the NIF-serialized symbol. nimsuggest loads symbols
# as `Sealed` (ast2nif.loadedState under cmdM) yet `suggestSym` legitimately
# records usages on them; the getter likewise doesn't assert.
assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.allUsagesImpl = val
@@ -359,18 +336,6 @@ proc `flags=`*(t: PType, val: TTypeFlags) {.inline.} =
t.flagsImpl = val
proc sons*(t: PType): var TTypeSeq {.inline.} =
## The RAW child seq. Despite the name this is NOT the counterpart of the
## `sons` ITERATOR over a `PNode`, and it is not the way to walk a type's
## children — use `kids` / `ikids` / `paramTypes` / `signature`, or the named
## accessors (`returnType`, `baseClass`, `elementType`, `indexType`,
## `genericHead`, ...), which say WHICH child they mean.
##
## The difference is not cosmetic. A `tyProc` keeps its parameter types in
## `n`, not here — `setSons` asserts `sonsImpl.len <= 1` for one — so `[]`,
## `len` and every iterator built on them route parameters through
## `n[i].sym.typ`, while this seq holds only the return type. `for x in
## t.sons` therefore compiles, looks like the `PNode` idiom, and silently
## visits a different set of types.
if t.state == Partial: loadType(t)
result = t.sonsImpl
@@ -379,7 +344,7 @@ proc `sons=`*(t: PType, val: sink TTypeSeq) {.inline.} =
if t.state == Partial: loadType(t)
t.sonsImpl = val
proc n*(t: PType): lent PNode {.inline.} =
proc n*(t: PType): PNode {.inline.} =
if t.state == Partial: loadType(t)
result = t.nImpl
@@ -388,7 +353,7 @@ proc `n=`*(t: PType, val: PNode) {.inline.} =
if t.state == Partial: loadType(t)
t.nImpl = val
proc sym*(t: PType): lent PSym {.inline.} =
proc sym*(t: PType): PSym {.inline.} =
if t.state == Partial: loadType(t)
result = t.symImpl
@@ -430,7 +395,7 @@ proc `loc=`*(t: PType, val: TLoc) {.inline.} =
if t.state == Partial: loadType(t)
t.locImpl = val
proc typeInst*(t: PType): lent PType {.inline.} =
proc typeInst*(t: PType): PType {.inline.} =
if t.state == Partial: loadType(t)
result = t.typeInstImpl
@@ -459,7 +424,7 @@ proc excl*(t: PType; flags: set[TTypeFlag]) {.inline.} =
if t.state == Partial: loadType(t)
t.flagsImpl.excl(flags)
proc typ*(n: PNode): lent PType {.inline.} =
proc typ*(n: PNode): PType {.inline.} =
result = n.typField
if result == nil and nfLazyType in n.flags:
result = n.sym.typ
@@ -480,18 +445,12 @@ var gconfig {.threadvar.}: Gconfig
proc setUseIc*(useIc: bool) = gconfig.useIc = useIc
proc comment*(n: PNode): string =
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)
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]
else:
result = ""
nodeCommentReader = proc(n: PNode): string {.nimcall.} = comment(n)
proc `comment=`*(n: PNode, a: string) =
let id = n.nodeId
if a.len > 0:
@@ -507,8 +466,6 @@ proc `comment=`*(n: PNode, a: string) =
n.flags.excl nfHasComment
gconfig.comments.del(id)
nodeCommentWriter = proc(n: PNode; s: string) {.nimcall.} = n.comment = s
# BUGFIX: a module is overloadable so that a proc can have the
# same name as an imported module. This is necessary because of
# the poor naming choices in the standard library.
@@ -521,8 +478,14 @@ proc getPIdent*(a: PNode): PIdent {.inline.} =
of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: a.sons[0].sym.name
else: nil
template id*(a: PSym): int = toId(a.itemId)
template id*(a: PType): int = toId(a.bindingId)
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
IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here.
@@ -530,62 +493,28 @@ 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)
when not defined(nimKochBootstrap):
if x.backendMinted:
# Share the loader's per-module backend counter so a freshly-minted
# backend sym never collides with an `@bk` sym loaded from the module's
# `.t.bif` (see ast2nif.nextBackendSymItem).
let it = nextBackendSymItem(program, x.module)
if it >= 0'i32:
return backendItemId(x.module, it)
inc x.symId
result = if x.backendMinted: backendItemId(x.module, x.symId)
else: itemId(x.module, x.symId)
result = ItemId(module: x.module, item: x.symId)
proc nextTypeId*(x: IdGenerator): ItemId {.inline.} =
assert(not x.sealed)
when not defined(nimKochBootstrap):
if x.backendMinted:
# Share the loader's per-module backend TYPE counter (seeded from the
# module's `(unusedid)`) so a freshly-minted backend type sits ABOVE every
# loaded type — never colliding with a frontend type's `toId` (the bug that
# crashed cgen's `getTypeDescAux` cycle check on `AsyncBufferRef`). Mirrors
# `nextSymId` (see ast2nif.nextBackendTypeItem).
let it = nextBackendTypeItem(program, x.module)
if it >= 0'i32:
return backendItemId(x.module, it)
inc x.typeId
result = if x.backendMinted: backendItemId(x.module, x.typeId)
else: itemId(x.module, x.typeId)
result = ItemId(module: x.module, item: x.typeId)
when false:
proc nextId*(x: IdGenerator): ItemId {.inline.} =
@@ -777,28 +706,10 @@ when false:
echo k
echo v
when defined(icSymCount):
import std / [syncio, exitprocs, tables as symCountTables]
var symMints*: symCountTables.CountTable[string]
var symMintTotal*: int
var symCountHooked = false
proc newSym*(symKind: TSymKind, name: PIdent, idgen: IdGenerator; owner: PSym,
info: TLineInfo; options: TOptions = {}): PSym =
# generates a symbol and initializes the hash field too
assert not name.isNil
when defined(icSymCount):
# Counting symbol MINTS, not their names in the output: a gensym's number is
# its item id, so one extra symbol anywhere shifts every later name. A count
# is therefore far more sensitive than diffing generated C, and it localises
# the extra mint by kind instead of by whatever file happened to show it.
inc symMintTotal
symMints.inc $symKind
if not symCountHooked:
symCountHooked = true
addExitProc proc () =
stderr.writeLine "SYMMINT total=" & $symMintTotal
for k, v in symMints: stderr.writeLine "SYMMINT " & k & "=" & $v
let id = nextSymId idgen
result = PSym(name: name, kindImpl: symKind, flagsImpl: {}, infoImpl: info, itemId: id,
optionsImpl: options, ownerFieldImpl: owner, offsetImpl: defaultOffset,
@@ -880,10 +791,6 @@ proc newSymNode*(sym: PSym): PNode =
result = newNode(nkSym)
result.sym = sym
result.typField = sym.typ
if result.typField == nil and nifcBackendActive:
# See the two-arg overload in astdef: in the NIF backend cg stage a sym node
# built from a not-yet-typed stub must track the symbol's type lazily.
result.flags.incl nfLazyType
result.info = sym.info
proc newOpenSym*(n: PNode): PNode {.inline.} =
@@ -897,7 +804,7 @@ proc newIntNode*(kind: TNodeKind, intVal: Int128): PNode =
result = newNode(kind)
result.intVal = castToInt64(intVal)
proc lastSon*(n: PNode): lent PNode {.inline.} = n.sons[^1]
proc lastSon*(n: PNode): PNode {.inline.} = n.sons[^1]
template setLastSon*(n: PNode, s: PNode) = n.sons[^1] = s
template firstSon*(n: PNode): PNode = n.sons[0]
@@ -919,29 +826,29 @@ proc last*(n: PType): PType {.inline.} =
else:
n.sonsImpl[^1]
proc elementType*(n: PType): lent PType {.inline.} =
proc elementType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[^1]
n.sonsImpl[^1]
proc skipModifier*(n: PType): lent PType {.inline.} =
proc skipModifier*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[^1]
n.sonsImpl[^1]
proc indexType*(n: PType): lent PType {.inline.} =
proc indexType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[0]
n.sonsImpl[0]
proc baseClass*(n: PType): lent PType {.inline.} =
proc baseClass*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[0]
n.sonsImpl[0]
proc base*(t: PType): lent PType {.inline.} =
proc base*(t: PType): PType {.inline.} =
if t.state == Partial: loadType(t)
result = t.sonsImpl[0]
proc returnType*(n: PType): lent PType {.inline.} =
proc returnType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[0]
n.sonsImpl[0]
proc setReturnType*(n, r: PType) {.inline.} =
if n.state == Partial: loadType(n)
@@ -958,17 +865,17 @@ proc firstParamType*(n: PType): PType {.inline.} =
else:
n.sonsImpl[1]
proc firstGenericParam*(n: PType): lent PType {.inline.} =
proc firstGenericParam*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[1]
n.sonsImpl[1]
proc typeBodyImpl*(n: PType): lent PType {.inline.} =
proc typeBodyImpl*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[^1]
n.sonsImpl[^1]
proc genericHead*(n: PType): lent PType {.inline.} =
proc genericHead*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
result = n.sonsImpl[0]
n.sonsImpl[0]
proc skipTypes*(t: PType, kinds: TTypeKinds): PType =
## Used throughout the compiler code to test whether a type tree contains or
@@ -1128,7 +1035,7 @@ proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType
let id = nextTypeId idgen
result = PType(kind: kind, ownerFieldImpl: owner, sizeImpl: defaultSize,
alignImpl: defaultAlignment, itemId: id,
bindingId: id, sonsImpl: @[])
uniqueId: id, sonsImpl: @[])
if son != nil:
assert kind != tyProc
result.sonsImpl.add son
@@ -1136,11 +1043,6 @@ 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
@@ -1203,24 +1105,10 @@ 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; idgen: IdGenerator): PType =
## Copy that INHERITS `bindingId` — the generic-param binding tables
## (`LayeredIdTable`) key on it, so the copy must keep matching its original
## there — while getting its own `itemId`, like every other type. The two
## remaining callers are `semtypinst.instCopyType` (a partially instantiated
## meta type must still bind in the next instantiation round) and the
## `tfUnresolved` typedesc replica in `semtypes.semTypeIdent`; everything
## else that used to come through here is a plain `copyType`.
##
## Do not "simplify" this to share `itemId` as well: `itemId` is the
## serialization identity, and replicas sharing it serialized as duplicate
## defs under one NIF name, which the loader collapsed into a single type —
## losing their flag differences (use-site `tfUnresolved` typedescs) or
## their structure (meta instance bodies shadowing a generic's canonical
## body).
proc exactReplica*(t: PType): PType =
result = PType(kind: t.kind, ownerFieldImpl: t.owner, sizeImpl: defaultSize,
alignImpl: defaultAlignment, itemId: nextTypeId(idgen),
bindingId: t.bindingId)
alignImpl: defaultAlignment, itemId: t.itemId,
uniqueId: t.uniqueId)
assignType(result, t)
result.symImpl = t.sym # backend-info should not be copied
@@ -1308,12 +1196,7 @@ proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
let o2 = owner.skipTypes({tyGenericInst, tyAlias, tySink})
if o2.kind in {tyTuple, tyObject, tyArray,
tySequence, tyString, tySet, tyDistinct}:
if o2.state == Sealed:
# During the original compilation, propagateToOwner set tfHasAsgn/tfHasOwned on the type before it was sealed
# On IC reload, the sealed type already has those flags
assert mask <= o2.flags, "IC bug: sealed type missing propagated flags"
else:
o2.incl mask
o2.incl mask
owner.incl mask
if owner.kind notin {tyProc, tyGenericInst, tyGenericBody,
@@ -1383,15 +1266,11 @@ 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,
optionsImpl: obj.optionsImpl, positionImpl: obj.positionImpl, offsetImpl: obj.offsetImpl,
disamb: obj.disamb, locImpl: obj.locImpl, annexImpl: obj.annexImpl, constraintImpl: obj.constraintImpl,
instantiatedFromImpl: obj.instantiatedFromImpl)
locImpl: obj.locImpl, annexImpl: obj.annexImpl, constraintImpl: obj.constraintImpl)
when hasFFI:
s.cnameImpl = obj.cnameImpl
when defined(nimsuggest):
@@ -1664,7 +1543,7 @@ proc isImportedException*(t: PType; conf: ConfigRef): bool =
result = base.sym != nil and {sfCompileToCpp, sfImportc} * base.sym.flags != {}
proc isInfixAs*(n: PNode): bool =
return n.kind == nkInfix and n.firstSon.kind == nkIdent and n.firstSon.ident.id == ord(wAs)
return n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.id == ord(wAs)
proc skipColon*(n: PNode): PNode =
result = n
@@ -1744,83 +1623,28 @@ proc addParam*(procType: PType; param: PSym) =
const magicsThatCanRaise = {
mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst, mEcho}
# `canRaise` reaches the effect list through `effectsOf` / `raisesNothing`
# rather than by subscripting `fn.typ.n`, so the layout is written down in one
# place. Under `--ic:on` that list came back from a `.bif`, and whether it came
# back intact is checked separately: `-d:icCanRaiseLog` logs every verdict, and
# the same program built with and without `--ic:on` must produce the same ones.
when defined(icCanRaiseLog):
var canRaiseBranch* = 0
## Which branch decided the last answer: 1 = the symbol's magic/flags,
## 2 = `mEcho`, 3 = the EFFECT LIST reached through `effectsOf`, 4 = the
## conservative predicate, 5 = short-circuited in `canRaiseDisp` before
## either predicate ran, 0 = fell through. Only branch 3 reads anything
## that had to survive a `.bif` round trip, so a differential in which no
## callee reaches it would prove nothing about the writer — which is the
## whole point of running the differential. See `-d:icCanRaiseLog`.
template markCanRaiseBranch*(n: int) =
when defined(icCanRaiseLog): canRaiseBranch = n
proc canRaiseConservative*(fn: PNode): bool =
markCanRaiseBranch 4
result = not (fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise)
proc effectsOf*(t: PType): PNode {.inline.} =
## The `nkEffectList` a proc type carries as child 0 of its formal-params
## node, with the parameters following from index 1 (`newProcType` builds it
## that way; `cgen` reads the params back with `sonsFrom(prc.typ.n, 1)`).
##
## Named rather than subscripted so that the layout is written down in ONE
## place. `.n` here is a TYPE's node, never a routine body, so it is always
## fully materialised and `firstSon` is safe — the `nfLazyBody` hazard that
## makes raw child access dangerous elsewhere (see `astdef.sons`) cannot reach
## it. A proc type always has this child; `t.n` with no children is not a
## shape the writer or sem produces, and this deliberately does not paper over
## one appearing.
result = if t.n == nil: nil else: t.n.firstSon
proc raisesNothing*(effects: PNode): bool =
## Whether an effect list says DEFINITIVELY that nothing is raised: it is long
## enough to have a raises slot at all, the slot is present, and it is empty.
##
## Every other shape — a list too short to carry the slot, an absent slot, a
## non-empty one — means the effects are unspecified or non-empty, and a
## caller must assume a raise. Stating it as the NEGATIVE is the point: the
## safe default has to be "can raise", so the one narrow case that licenses
## dropping an exception check is the one spelled out here, and a shape nobody
## anticipated falls on the conservative side by construction rather than by
## luck.
result = effects != nil and effects.len >= effectListLen and
effects[exceptionEffects] != nil and
effects[exceptionEffects].safeLen == 0
if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise:
result = false
else:
result = true
proc canRaise*(fn: PNode): bool =
if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or
{sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or
sfGeneratedOp in fn.sym.flags):
markCanRaiseBranch 1
result = false
elif fn.kind == nkSym and fn.sym.magic == mEcho:
markCanRaiseBranch 2
result = true
elif fn.typ != nil and fn.typ.kind == tyProc and fn.typ.n != nil:
markCanRaiseBranch 3
let effects = effectsOf(fn.typ)
if effects.kind == nkSym:
# The historical shape: slot 0 used to be an `nkType` before the effects
# moved in (see `newProcType`). Nothing to read, so nothing licenses a
# raise.
# TODO check for n having sons? or just return false for now if not
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 = not raisesNothing(effects)
result = ((fn.typ.n[0].len < effectListLen) or
(fn.typ.n[0][exceptionEffects] != nil and
fn.typ.n[0][exceptionEffects].safeLen > 0))
else:
markCanRaiseBranch 0
result = false
proc toHumanStrImpl[T](kind: T, num: static int): string =
@@ -1837,7 +1661,7 @@ proc toHumanStr*(kind: TTypeKind): string =
result = toHumanStrImpl(kind, 2)
proc skipHiddenAddr*(n: PNode): PNode {.inline.} =
(if n.kind == nkHiddenAddr: n.firstSon else: n)
(if n.kind == nkHiddenAddr: n[0] else: n)
proc isNewStyleConcept*(n: PNode): bool {.inline.} =
assert n.kind == nkTypeClassTy

File diff suppressed because it is too large Load Diff

View File

@@ -529,11 +529,8 @@ proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool =
type
TIdentIter* = object # iterator over all syms with same identifier
h*: Hash # current hash
name* {.cursor.}: PIdent
name*: PIdent
# String tables are always initialized with non-empty, power-of-two storage,
# and every probe is masked by `high(tab.data)`.
{.push boundChecks: off.}
proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
# hot spots
var h = ti.h and high(tab.data)
@@ -551,7 +548,6 @@ proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
else:
result = nil
ti.h = nextTry(h, high(tab.data))
{.pop.}
proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym =
ti.h = s.h
@@ -639,14 +635,9 @@ proc getOrDefault*[T](t: TIdTable[T], key: ItemId): T =
if index >= 0: result = t.data[index].val
else: result = default(T)
template idTableGet*[T](t: TIdTable[T], key: PSym): T =
template idTableGet*[T](t: TIdTable[T], key: PType | PSym): T =
getOrDefault(t, key.itemId)
template idTableGet*[T](t: TIdTable[T], key: PType): T =
## Type-keyed tables are BINDING tables: an `exactReplica` must find what its
## original bound, hence `bindingId` and not the type's own identity.
getOrDefault(t, key.bindingId)
proc idTableRawInsert[T](data: var TIdPairSeq[T], key: ItemId, val: T) =
var h: Hash
let keyId = toId(key)
@@ -677,12 +668,9 @@ proc `[]=`*[T](t: var TIdTable[T], key: ItemId, val: T) =
idTableRawInsert(t.data, key, val)
inc(t.counter)
template idTablePut*[T](t: var TIdTable[T], key: PSym, val: T) =
template idTablePut*[T](t: var TIdTable[T], key: PType | PSym, val: T) =
t[key.itemId] = val
template idTablePut*[T](t: var TIdTable[T], key: PType, val: T) =
t[key.bindingId] = val
iterator idTablePairs*[T](t: TIdTable[T]): tuple[key: ItemId, val: T] =
for i in 0..high(t.data):
if not isNil(t.data[i].key):
@@ -741,6 +729,6 @@ proc listSymbolNames*(symbols: openArray[PSym]): string =
result.add sym.name.s
proc isDiscriminantField*(n: PNode): bool =
if n.kind == nkCheckedFieldExpr: sfDiscriminant in n.firstSon.secondSon.sym.flags
elif n.kind == nkDotExpr: sfDiscriminant in n.secondSon.sym.flags
if n.kind == nkCheckedFieldExpr: sfDiscriminant in n[0][1].sym.flags
elif n.kind == nkDotExpr: sfDiscriminant in n[1].sym.flags
else: false

View File

@@ -17,21 +17,9 @@ when defined(nimPreviewSlimSystem):
export int128
var nifcBackendActive* = false
## Set only while the per-module NIF backend codegen stage runs
## (`nifbackend.generateCgStage`, `cmd == cmdNifC`). It gates `newSymNode`'s
## lazy-type marking so it applies ONLY in the backend — where syms are loaded
## from NIF and a cg-stage transform can build a sym node from a not-yet-typed
## stub — and never during frontend sem, where the same marking would perturb
## effect/exception inference (it diverges from a non-IC build, e.g.
## `times.toDateTimeByWeek` gaining a spurious unlisted `Exception`).
import nodekinds
export nodekinds
import itemids
export itemids
type
TCallingConvention* = enum
ccNimCall = "nimcall" # nimcall, also the default
@@ -339,14 +327,6 @@ type
# because openSym experimental switch is disabled
# gives warning instead
nfLazyType # node has a lazy type
nfLazyBody # IC: this node is a placeholder for a routine body (bodyPos son)
# not yet materialized. Reading its children (via `len`/`safeLen`)
# triggers `forceLazyBodyHook`. Process-local, stripped on serialize.
nfBroadcast # this `nkBracket` is a *broadcast* default array: a single son
# standing for `lengthOrd` identical zero copies (see
# `broadcastArrayThreshold`). The flag disambiguates it from an
# ordinary 1-element collection (e.g. a seq value that happens to
# carry an array type), so it must survive copies + serialization.
TNodeFlags* = set[TNodeFlag]
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 47)
@@ -591,6 +571,23 @@ 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]
@@ -681,10 +678,6 @@ type
TInstantiation* = object
sym*: PSym
concreteTypes*: seq[PType]
bindings*: seq[tuple[key: ItemId, value: PType]]
## An optional exact snapshot of the matcher bindings. In-process
## instances use it for a fast cache probe; serialized instances fall
## back to comparing the fully instantiated signature.
genericParamsCount*: int # for terrible reasons `concreteTypes` contains all the types,
# so we need to know how many generic params there were
# this is not serialized for IC and that is fine.
@@ -708,7 +701,6 @@ type
PLib* = ref TLib
TSym* {.acyclic.} = object # Keep in sync with ast2nif.nim
# Check `transitionSymKindCommon` in ast.nim when add a new field.
itemId*: ItemId
# proc and type instantiations are cached in the generic symbol
state*: ItemState
@@ -784,16 +776,11 @@ type
# same id; there may be multiple copies of a type
# in memory!
# Keep in sync with PackedType
itemId*: ItemId # THE identity of this type: unique per instance, forever.
# Names the type in the NIF cache and decides which
# module owns its definition.
itemId*: ItemId
kind*: TTypeKind # kind of type
state*: ItemState
bindingId*: ItemId # the id of the type this one is a REPLICA of (its own
# `itemId` when it is not a replica). Only the generic
# binding tables (`LayeredIdTable` & friends) key on it:
# `exactReplica` produces a copy that must keep matching
# its original in those tables. Never an identity.
uniqueId*: ItemId # due to a design mistake, we need to keep the real ID here as it
# is required by the --incremental:on mode.
callConvImpl*: TCallingConvention # for procs
flagsImpl*: TTypeFlags # flags of the type
sonsImpl*: TTypeSeq # base types, etc.
@@ -883,8 +870,7 @@ const
nfFromTemplate, nfDefaultRefsParam,
nfExecuteOnReload, nfLastRead,
nfFirstWrite, nfSkipFieldChecking,
nfDisabledOpenSym, nfLazyType,
nfBroadcast}
nfDisabledOpenSym, nfLazyType}
namePos* = 0
patternPos* = 1 # empty except for term rewriting macros
genericParamsPos* = 2
@@ -921,24 +907,7 @@ const
defaultOffset* = -1
var forceLazyBodyHook*: proc (n: PNode) {.nimcall, raises: [], tags: [], gcsafe.}
## Set by the IC loader (ast2nif). When a node carries `nfLazyBody`, any access
## to its children through `len` materializes the deferred routine body in place.
## `safeLen` delegates to `len`, so it is covered transitively; a lazy body is
## never a leaf kind, so the `{nkNone..nkNilLit}` short-circuit never hides it.
##
## The type MUST be effect-free (`raises: []`/`tags: []`): `len` is a fundamental
## `PNode` accessor that the whole compiler — and every compiler-as-library
## consumer (nimble, nimsuggest, ...) — assumes cannot raise. An unannotated
## `proc` var defaults to `raises: [Exception]`, so the indirect call tainted
## `len`/`safeLen`/`items` with `Exception`, breaking any iterator/`{.raises.}`
## over a `PNode` (e.g. nimble's `extract {.raises: [CatchableError].}`).
## Materialization is a pure in-memory buffer transform; a corrupt buffer is a
## `Defect` (`raiseAssert`), which is outside exception tracking.
proc len*(n: PNode): int {.inline.} =
if nfLazyBody in n.flags and forceLazyBodyHook != nil:
forceLazyBodyHook(n)
result = n.sons.len
proc safeLen*(n: PNode): int {.inline.} =
@@ -955,57 +924,6 @@ template `[]=`*(n: PNode, i: BackwardsIndex; x: PNode) = n[n.len - i.int] = x
iterator items*(n: PNode): PNode =
for i in 0..<n.safeLen: yield n[i]
iterator sons*(n: PNode): PNode =
## Iterates over the children of `n`. Preferred over `for i in 0..<n.len: n[i]`
## as it does not rely on random indexed access, and over `for x in n.sons`,
## which reads the raw FIELD and so skips the `len` hook that materialises a
## deferred `nfLazyBody` body — over such a body that loop silently visits
## nothing.
for i in 0..<n.safeLen: yield n[i]
iterator isons*(n: PNode; start = 0): tuple[i: int, n: PNode] =
## Like `sons` but also yields the child index, and optionally skips the first
## `start` children. Replaces `for i in start..<n.len: ... n[i] ...` when `i`
## itself is still needed — for a parameter position, a `needTmp[i-1]` lookup,
## a parallel index into the routine's `PType`, and so on. `start` is almost
## always 1, to step over a call's callee or a case statement's selector.
##
## Use `sonsFrom` instead when the index is only ever used to subscript `n`.
for i in start..<n.safeLen: yield (i, n[i])
iterator sonsFrom*(n: PNode; start: int): PNode =
## `sons` skipping the first `start` children. Replaces
## `for i in start..<n.len: ... n[i] ...`, which is by far the commonest
## indexed shape in the code generator — `start` is almost always 1, to step
## over a case/try statement's selector or a call's callee.
for i in start..<n.safeLen: yield n[i]
iterator sonsButLast*(n: PNode; count = 1): PNode =
## `sons` without the last `count` children. Replaces `for i in 0..<n.len-1:
## ... n[i] ...`, which is what an `nkOfBranch`/`nkExceptBranch` walk looks
## like: the last child is the branch BODY, the ones before it are the labels
## it matches. `count = 2` is the `nkVarTuple`/`nkIdentDefs` shape, whose last
## two children are the type and the value. A `Cursor` can serve this with a
## single pass and `count` nodes of lookahead; the indexed form has to re-walk
## the children for every label.
##
## Use `isonsButLast` instead when the index is still needed.
for i in 0 ..< n.safeLen - count: yield n[i]
iterator isonsButLast*(n: PNode; count = 1): tuple[i: int, n: PNode] =
## Like `sonsButLast` but also yields the child index — for a tuple field
## position, a parallel index into the tuple's `PType`, and so on.
for i in 0 ..< n.safeLen - count: yield (i, n[i])
template son*(n: PNode; i: int): PNode =
## Named indexed access to child `i`, for the small constant positions that
## `firstSon`/`secondSon`/`lastSon` do not cover.
n[i]
template hasSons*(n: PNode): bool =
## Emptiness test; goes through `safeLen` so a deferred body is materialised.
n.safeLen > 0
when defined(useNodeIds):
const nodeIdToDebug* = -1 # 2322968
var gNodeId: int
@@ -1065,14 +983,6 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
result = newNode(nkSym)
result.sym = sym
result.typField = sym.typImpl
if result.typField == nil and nifcBackendActive:
# In the per-module NIF backend cg stage a transform (chronos async
# closure-iterator lowering) builds `result = …` sym nodes from a not-yet-typed
# NIF stub; snapshotting the nil here would leave the node permanently typeless
# and the backend later reads `t.flags` off it and SIGSEGVs (injectdestructors
# hasDestructor). Mark it lazy so `typ` re-reads `sym.typ` once resolved. Gated
# on `nifcBackendActive` so frontend sem is untouched (see the flag's doc).
result.flags.incl nfLazyType
result.info = info
proc newStrNode*(kind: TNodeKind, strVal: string): PNode =
@@ -1087,56 +997,9 @@ proc newStrNode*(strVal: string; info: TLineInfo): PNode =
# handling for IC, they end up in IC indexes etc. Thus we "log" them in the module graph
# and to pass them around to the NIF writer. This is not very elegant but it works.
const
InstanceDisambBit* = 0x4000_0000'i32
## Set in the `disamb` of routine instances whose value is content-derived
## (see `modulegraphs.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.
HookDisambBit* = 0x2000_0000'i32
## Set in the `disamb` of synthesized type-bound operators and `$enum`
## procs whose value is content-derived (see `modulegraphs.setHookDisamb`);
## disjoint from both the small counter range and `InstanceDisambBit`.
##
## Both live here rather than in `modulegraphs` because `ast2nif` — which
## cannot import that module — names symbols by them.
proc backendMintedDisamb*(s: PSym): int32 {.inline.} =
## The integer that identifies a BACKEND-MINTED symbol (`isBackendMinted`) in
## every name derived from it: its NIF name (`ast2nif.toNifSymName`) and its C
## name (`mangleutils.mangleProcNameExt`, `ccgutils.makeUnique`).
##
## Two cases, and the whole point of having ONE function is that all three
## sites take the same one:
##
## * A lifted HOOK's `disamb` is CONTENT-derived (`modulegraphs.setHookDisamb`),
## so it is identical in every process. Such a hook really does cross process
## boundaries — `lower` mints the env hooks of nested routines while `cg`
## mints those of the module's top level, and both land in the same
## translation unit — and its C name is also baked into emit-everywhere RTTI
## tables. `itemId.item` would differ per process, so two unrelated hooks
## collided on one `_c<item>` and the merge stage kept a single body for both
## (C accepted the mistyped call, C++ rejected it).
## * Otherwise `itemId.item` — the writer's dedup identity, unique per `@bk`
## sym. `disamb` cannot serve here: a module's `:env` syms are minted from TWO
## id spaces (the backend `lower` stage's idgen and sem's `vmTransfIdgen`)
## whose `disambTable`s each start `:env` at the same low count, so a
## macro-lowered and a backend-lowered `:env` collide on `:env.2.<mod>@bk`.
##
## The loader copies the name's numeric component back into `disamb`, so after a
## round trip `disamb` equals this value and `ast2nif.globalName` — which always
## reads `disamb` — agrees with the name the writer produced.
##
## This rule used to be written out at each of the three sites. They drifted:
## `toNifSymName` lacked the hook exception, so a content-derived value was
## overwritten by the loader and two backend hooks merged into one C function.
if (s.disamb and HookDisambBit) != 0'i32: s.disamb
else: s.itemId.item
type
LogEntryKind* = enum
HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry,
PureEnumEntry, CppMemberEntry
HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry
LogEntry* = object
kind*: LogEntryKind
op*: TTypeAttachedOp
@@ -1183,7 +1046,7 @@ proc forcePartial*(s: PSym) =
proc forcePartial*(t: PType) =
## Resets all impl-fields to their default values and sets state to Partial.
## This is useful for creating a stub type that can be lazily loaded later.
## The fields itemId, kind, bindingId are preserved.
## The fields itemId, kind, uniqueId are preserved.
t.state = Partial
t.callConvImpl = ccNimCall
t.flagsImpl = {}
@@ -1201,11 +1064,8 @@ const # for all kind of hash tables:
GrowthFactor* = 2 # must be power of 2, > 0
StartSize* = 8 # must be power of 2, > 0
{.push overflowChecks: off.}
proc nextTry*(h, maxHash: Hash): Hash {.inline.} =
# Overflow is intentional: only the low bits selected by maxHash are used.
result = ((5 * h) + 1) and maxHash
{.pop.}
# For any initial h in range(maxHash), repeating that maxHash times
# generates each int in range(maxHash) exactly once (see any text on
# random-number generation for proof).
@@ -1302,11 +1162,3 @@ proc strTableGet*(t: TStrTable, name: PIdent): PSym =
if result == nil: break
if result.name.id == name.id: break
h = nextTry(h, high(t.data))
# --- doc-comment bridge for the NIF serializer -------------------------------
# `ast2nif` (the NIF reader/writer) cannot import `ast` (where the comment
# accessor and its `gconfig.comments` side table live) because `ast` imports
# `ast2nif`. These hooks are assigned by `ast` and let the serializer carry a
# decl's `##` doc comment across a NIF round-trip.
var nodeCommentReader*: proc(n: PNode): string {.nimcall.}
var nodeCommentWriter*: proc(n: PNode; s: string) {.nimcall.}

View File

@@ -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; 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 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 symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; nl: bool, indent: int; maxRecDepth: int) =
proc symToYamlAux(res: var string; conf: ConfigRef; n: PSym; marker: var IntSet; indent: int; maxRecDepth: int) =
if n == nil:
res.add("null")
elif containsOrIncl(marker, n.id):
@@ -57,12 +57,10 @@ 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, true, indent + 1, maxRecDepth - 1)
res.typeToYamlAux(conf, n.typ, marker, 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)])
@@ -70,7 +68,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, true, indent + 1, maxRecDepth - 1)
res.treeToYamlAux(conf, n.ast, marker, 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)])
@@ -78,57 +76,53 @@ 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: ", [istr])
res.treeToYamlAux(conf, n.loc.lode, marker, true, indent + 1, maxRecDepth - 1)
res.addf("\n$1lode: $2", [istr])
res.treeToYamlAux(conf, n.loc.lode, marker, indent + 1, maxRecDepth - 1)
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; nl: bool, indent: int; maxRecDepth: int) =
proc typeToYamlAux(res: var string; conf: ConfigRef; n: PType; marker: var IntSet; 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: ", 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)
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)
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:", istr)
res.addf("\n$1sons:")
for a in n.kids:
res.addf("\n$1 - ", istr)
res.typeToYamlAux(conf, a, marker, false, indent + 1, maxRecDepth - 1)
res.addf("\n - ")
res.typeToYamlAux(conf, a, marker, indent + 1, maxRecDepth - 1)
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; nl: bool, indent: int;
proc treeToYamlAux(res: var string; conf: ConfigRef; n: PNode; marker: var IntSet; 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 .. nkUInt64Lit:
of nkCharLit .. nkInt64Lit:
res.addf("\n$1intVal: $2", [istr, $(n.intVal)])
of nkFloatLit .. nkFloat128Lit:
of nkFloatLit, nkFloat32Lit, nkFloat64Lit:
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, true, indent + 1, maxRecDepth)
res.symToYamlAux(conf, n.sym, marker, indent + 1, maxRecDepth)
of nkIdent:
if n.ident != nil:
res.addf("\n$1ident: $2", [istr, makeYamlString(n.ident.s)])
@@ -139,22 +133,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, false, indent + 1, maxRecDepth - 1)
res.treeToYamlAux(conf, n[i], marker, indent + 1, maxRecDepth - 1)
if n.typ != nil:
res.addf("\n$1typ: ", [istr])
res.typeToYamlAux(conf, n.typ, marker, true, indent + 1, maxRecDepth)
res.typeToYamlAux(conf, n.typ, marker, 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, false, indent, maxRecDepth)
result.treeToYamlAux(conf, n, marker, 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, false, indent, maxRecDepth)
result.typeToYamlAux(conf, n, marker, 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, false, indent, maxRecDepth)
result.symToYamlAux(conf, n, marker, indent, maxRecDepth)

View File

@@ -326,7 +326,7 @@ proc startStruct(obj: var Builder; m: BModule; t: PType; name: string; baseType:
# rest of the options add a field or don't need it due to inheritance,
# we need to add the dummy field for uncheckedarray ahead of time
# so that it remains trailing
if t.bindingId notin m.g.graph.memberProcsPerType and
if t.itemId notin m.g.graph.memberProcsPerType and
t.n != nil and t.n.len == 1 and t.n[0].kind == nkSym and
t.n[0].sym.typ.skipTypes(abstractInst).kind == tyUncheckedArray:
# only consists of flexible array field, add *initial* dummy field
@@ -341,7 +341,7 @@ proc startStruct(obj: var Builder; m: BModule; t: PType; name: string; baseType:
proc finishStruct(obj: var Builder; m: BModule; t: PType; info: StructBuilderInfo) =
if info.baseKind == bcNone and info.preFieldsLen == obj.buf.len and
t.bindingId notin m.g.graph.memberProcsPerType:
t.itemId notin m.g.graph.memberProcsPerType:
# no fields were added, add dummy field
obj.addField(name = "dummy", typ = CChar)
if info.named:

View File

@@ -11,17 +11,7 @@
proc canRaiseDisp(p: BProc; n: PNode): bool =
# we assume things like sysFatal cannot raise themselves
# 5 = "decided here, neither predicate ran". Without resetting, the marker
# keeps whatever the PREVIOUS call left in it and the early return below
# attributes this answer to a branch that did not execute — which is how the
# first run of this differential came to claim effect-list coverage it did
# not have. Both short-circuits below leave it at 5.
markCanRaiseBranch 5
if n.kind == nkSym and n.sym.kind == skMethod:
# A base method may be overridden by a branch with a wider exception set.
# Its inferred effects describe only the base body, not every vtable target.
result = true
elif n.kind == nkSym and {sfNeverRaises, sfImportc, sfCompilerProc} * n.sym.flags != {}:
if n.kind == nkSym and {sfNeverRaises, sfImportc, sfCompilerProc} * n.sym.flags != {}:
result = false
elif optPanics in p.config.globalOptions or
(n.kind == nkSym and sfSystemModule in getModule(n.sym).flags and
@@ -31,13 +21,6 @@ proc canRaiseDisp(p: BProc; n: PNode): bool =
else:
# we have to be *very* conservative:
result = canRaiseConservative(n)
when defined(icCanRaiseLog):
# `canRaise` reads the raises spec off `fn.typ.n`, and under `--ic:on` that
# node came back from a `.bif`. The only oracle for whether it came back
# INTACT is the same program built without IC. Log the verdict per callee;
# the two builds must produce the same one.
if n.kind == nkSym:
logCanRaise(n.sym, result)
proc preventNrvo(p: BProc; dest, le, ri: PNode): bool =
proc locationEscapes(p: BProc; le: PNode; inTryStmt: bool): bool =
@@ -57,29 +40,31 @@ proc preventNrvo(p: BProc; dest, le, ri: PNode): bool =
return false
of nkDotExpr, nkBracketExpr, nkObjUpConv, nkObjDownConv,
nkCheckedFieldExpr:
n = n.firstSon
n = n[0]
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
n = n.secondSon
n = n[1]
else:
# cannot analyse the location; assume the worst
return true
result = false
if le != nil:
for r in sonsFrom(ri, 1):
if isPartOf(le, r, {pfStructural}) != arNo: return true
for i in 1..<ri.len:
let r = ri[i]
if isPartOf(le, r) != arNo: return true
# we use the weaker 'canRaise' here in order to prevent too many
# annoying warnings, see #14514
if canRaise(ri.firstSon) and
if canRaise(ri[0]) and
locationEscapes(p, le, p.nestedTryStmts.len > 0):
message(p.config, le.info, warnObservableStores, $le)
# bug #19613 prevent dangerous aliasing too:
if dest != nil and dest != le:
for r in sonsFrom(ri, 1):
if isPartOf(dest, r, {pfStructural}) != arNo: return true
for i in 1..<ri.len:
let r = ri[i]
if isPartOf(dest, r) != arNo: return true
proc hasNoInit(call: PNode): bool {.inline.} =
result = call.firstSon.kind == nkSym and sfNoInit in call.firstSon.sym.flags
result = call[0].kind == nkSym and sfNoInit in call[0].sym.flags
proc isHarmlessStore(p: BProc; canRaise: bool; d: TLoc): bool =
if d.k in {locTemp, locNone} or not canRaise:
@@ -110,12 +95,12 @@ proc cleanupTemp(p: BProc; returnType: PType, tmp: TLoc): bool =
else:
result = false
proc fixupCall(p: BProc, le: PNode, ri: PNode, d: var TLoc,
proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc,
result: var Builder, call: var CallBuilder) =
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri.firstSon)
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri[0])
genLineDir(p, ri)
# getUniqueType() is too expensive here:
var typ = skipTypes(ri.firstSon.typ, abstractInst)
var typ = skipTypes(ri[0].typ, abstractInst)
if typ.returnType != nil:
var flags: TAssignmentFlags = {}
if typ.returnType.kind in {tyOpenArray, tyVarargs}:
@@ -199,9 +184,9 @@ proc reifiedOpenArray(n: PNode): bool {.inline.} =
while true:
case x.kind
of {nkAddr, nkHiddenAddr, nkHiddenDeref}:
x = x.firstSon
x = x[0]
of nkHiddenStdConv:
x = x.secondSon
x = x[1]
else:
break
if x.kind == nkSym and x.sym.kind == skParam:
@@ -210,9 +195,9 @@ proc reifiedOpenArray(n: PNode): bool {.inline.} =
result = true
proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareForMutation = false): (Rope, Rope) =
var a = initLocExpr(p, q.secondSon)
var b = initLocExpr(p, son(q, 2))
var c = initLocExpr(p, son(q, 3))
var a = initLocExpr(p, q[1])
var b = initLocExpr(p, q[2])
var c = initLocExpr(p, q[3])
# bug #23321: In the function mapType, ptrs (tyPtr, tyVar, tyLent, tyRef)
# are mapped into ctPtrToArray, the dereference of which is skipped
# in the `genDeref`. We need to skip these ptrs here
@@ -238,36 +223,27 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareF
let lit = cIntLiteral(first)
result = (cCast(ptrType(dest), cOp(Add, NimInt, ra, cOp(Sub, NimInt, rb, lit))), lengthExpr)
of tyOpenArray, tyVarargs:
let data = if reifiedOpenArray(q.secondSon): dotField(ra, "Field0") else: ra
let data = if reifiedOpenArray(q[1]): dotField(ra, "Field0") else: ra
result = (cCast(ptrType(dest), cOp(Add, NimInt, data, rb)), lengthExpr)
of tyUncheckedArray, tyCstring:
result = (cCast(ptrType(dest), cOp(Add, NimInt, ra, rb)), lengthExpr)
of tyString, tySequence:
let atyp = skipTypes(a.t, abstractInst)
if formalType.skipTypes(abstractInst).kind in {tyVar} and atyp.kind == tyString and
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
optSeqDestructors in p.config.globalOptions:
let bra = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
bra)
if p.config.usesSso() and
skipTypes(a.t, abstractVar + abstractInst).kind == tyString:
let strPtr = if atyp.kind in {tyVar} and not compileToCpp(p.module): ra
else: addrLoc(p.config, a)
result = (
cCast(ptrType(dest), cOp(Add, NimInt,
cCall(cgsymValue(p.module, "nimStrData"), strPtr), rb)),
lengthExpr)
var val: Snippet
if atyp.kind in {tyVar} and not compileToCpp(p.module):
val = cDeref(ra)
else:
var val: Snippet
if atyp.kind in {tyVar} and not compileToCpp(p.module):
val = cDeref(ra)
else:
val = ra
result = (
cIfExpr(dataFieldAccessor(p, val),
cCast(ptrType(dest), cOp(Add, NimInt, dataField(p, val), rb)),
NimNil),
lengthExpr)
val = ra
result = (
cIfExpr(dataFieldAccessor(p, val),
cCast(ptrType(dest), cOp(Add, NimInt, dataField(p, val), rb)),
NimNil),
lengthExpr)
else:
result = ("", "")
internalError(p.config, "openArrayLoc: " & typeToString(a.t))
@@ -275,23 +251,23 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareF
proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
var q = skipConv(n)
var skipped = false
while q.kind == nkStmtListExpr and q.hasSons:
while q.kind == nkStmtListExpr and q.len > 0:
skipped = true
q = q.lastSon
if getMagic(q) == mSlice:
# magic: pass slice to openArray:
if skipped:
q = skipConv(n)
while q.kind == nkStmtListExpr and q.hasSons:
for it in sonsButLast(q):
genStmts(p, it)
while q.kind == nkStmtListExpr and q.len > 0:
for i in 0..<q.len-1:
genStmts(p, q[i])
q = q.lastSon
let (x, y) = genOpenArraySlice(p, q, formalType, n.typ.elementType)
result.add(x)
result.addArgumentSeparator()
result.add(y)
else:
var a = initLocExpr(p, if n.kind == nkHiddenStdConv: n.secondSon else: n)
var a = initLocExpr(p, if n.kind == nkHiddenStdConv: n[1] else: n)
case skipTypes(a.t, abstractVar+{tyStatic}).kind
of tyOpenArray, tyVarargs:
let ra = rdLoc(a)
@@ -311,22 +287,11 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
of tyString, tySequence:
let ntyp = skipTypes(n.typ, abstractInst)
if formalType.skipTypes(abstractInst).kind in {tyVar} and ntyp.kind == tyString and
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
optSeqDestructors in p.config.globalOptions:
let bra = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
bra)
if p.config.usesSso() and
skipTypes(n.typ, abstractVar + abstractInst).kind == tyString:
if ntyp.kind in {tyVar} and not compileToCpp(p.module):
let ra = a.rdLoc
result.add(cCall(cgsymValue(p.module, "nimStrData"), ra))
result.addArgumentSeparator()
result.add(cCall(cgsymValue(p.module, "nimStrLen"), cDeref(ra)))
else:
result.add(cCall(cgsymValue(p.module, "nimStrData"), addrLoc(p.config, a)))
result.addArgumentSeparator()
result.add(lenExpr(p, a))
elif ntyp.kind in {tyVar} and not compileToCpp(p.module):
if ntyp.kind in {tyVar} and not compileToCpp(p.module):
let ra = a.rdLoc
var t = TLoc(snippet: cDeref(ra))
let lt = lenExpr(p, t)
@@ -350,14 +315,9 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
let ra = a.rdLoc
var t = TLoc(snippet: cDeref(ra))
let lt = lenExpr(p, t)
if p.config.usesSso():
result.add(cCall(cgsymValue(p.module, "nimStrData"), ra))
result.addArgumentSeparator()
result.add(cCall(cgsymValue(p.module, "nimStrLen"), t.snippet))
else:
result.add(cIfExpr(dataFieldAccessor(p, t.snippet), dataField(p, t.snippet), NimNil))
result.addArgumentSeparator()
result.add(lt)
result.add(cIfExpr(dataFieldAccessor(p, t.snippet), dataField(p, t.snippet), NimNil))
result.addArgumentSeparator()
result.add(lt)
of tyArray:
let ra = rdLoc(a)
result.add(ra)
@@ -383,9 +343,8 @@ proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc =
genAssignment(p, result, a, {})
proc genArgStringToCString(p: BProc, n: PNode; result: var Builder; needsTmp: bool) {.inline.} =
var a = initLocExpr(p, n.firstSon)
let tmp = withTmpIfNeeded(p, a, needsTmp)
let ra = if p.config.usesSso(): byRefLoc(p, tmp) else: tmp.rdLoc
var a = initLocExpr(p, n[0])
let ra = withTmpIfNeeded(p, a, needsTmp).rdLoc
result.addCall(cgsymValue(p.module, "nimToCStringConv"), ra)
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; needsTmp = false) =
@@ -393,9 +352,9 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n
if n.kind == nkStringToCString:
genArgStringToCString(p, n, result, needsTmp)
elif skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs}:
var n = if n.kind != nkHiddenAddr: n else: n.firstSon
var n = if n.kind != nkHiddenAddr: n else: n[0]
openArrayLoc(p, param.typ, n, result)
elif ccgIntroducedPtr(p.config, param, call.firstSon.typ.returnType) and
elif ccgIntroducedPtr(p.config, param, call[0].typ.returnType) and
(optByRef notin param.options or not p.module.compileToCpp):
a = initLocExpr(p, n)
if n.kind in {nkCharLit..nkNilLit}:
@@ -407,16 +366,16 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n
# bug #23748: we need to introduce a temporary here. The expression type
# will be a reference in C++ and we cannot create a temporary reference
# variable. Thus, we create a temporary pointer variable instead.
let needsIndirect = mapType(p.config, n.firstSon.typ, mapTypeChooser(n.firstSon) == skParam) != ctArray
let needsIndirect = mapType(p.config, n[0].typ, mapTypeChooser(n[0]) == skParam) != ctArray
if needsIndirect:
n.typ = copyType(n.typ, p.module.idgen, n.typ.owner)
n.typ = n.typ.exactReplica
n.typ.incl tfVarIsPtr
a = initLocExprSingleUse(p, n)
a = withTmpIfNeeded(p, a, needsTmp)
if needsIndirect: a.flags.incl lfIndirect
# if the proc is 'importc'ed but not 'importcpp'ed then 'var T' still
# means '*T'. See posix.nim for lots of examples that do that in the wild.
let callee = call.firstSon
let callee = call[0]
if callee.kind == nkSym and
{sfImportc, sfInfixCall, sfCompilerProc} * callee.sym.flags == {sfImportc} and
{lfHeader, lfNoDecl} * callee.sym.loc.flags != {} and
@@ -454,9 +413,9 @@ proc skipTrivialIndirections(n: PNode): PNode =
while true:
case result.kind
of nkDerefExpr, nkHiddenDeref, nkAddr, nkHiddenAddr, nkObjDownConv, nkObjUpConv:
result = result.firstSon
result = result[0]
of nkHiddenStdConv, nkHiddenSubConv:
result = result.secondSon
result = result[1]
else: break
proc getPotentialReads(n: PNode; result: var seq[PNode]) =
@@ -464,47 +423,44 @@ proc getPotentialReads(n: PNode; result: var seq[PNode]) =
of nkLiterals, nkIdent, nkFormalParams: discard
of nkSym: result.add n
else:
for s in sons(n):
for s in n:
getPotentialReads(s, result)
proc genParams(p: BProc, ri: PNode, typ: PType; result: var Builder, argBuilder: var CallBuilder) =
# We must generate temporaries in cases like #14396
# to keep the strict Left-To-Right evaluation
# The arguments are walked BACKWARDS below; collect them once and index that.
var args: seq[PNode] = @[]
for it in sonsFrom(ri, 1): args.add it
var needTmp = newSeq[bool](args.len)
var needTmp = newSeq[bool](ri.len - 1)
var potentialWrites: seq[PNode] = @[]
for i in countdown(args.high, 0):
if args[i].skipTrivialIndirections.kind == nkSym:
needTmp[i] = potentialAlias(args[i], potentialWrites)
for i in countdown(ri.len - 1, 1):
if ri[i].skipTrivialIndirections.kind == nkSym:
needTmp[i - 1] = potentialAlias(ri[i], potentialWrites)
else:
#if not args[i].typ.isCompileTimeOnly:
#if not ri[i].typ.isCompileTimeOnly:
var potentialReads: seq[PNode] = @[]
getPotentialReads(args[i], potentialReads)
getPotentialReads(ri[i], potentialReads)
for n in potentialReads:
if not needTmp[i]:
needTmp[i] = potentialAlias(n, potentialWrites)
getPotentialWrites(args[i], false, potentialWrites)
if not needTmp[i - 1]:
needTmp[i - 1] = potentialAlias(n, potentialWrites)
getPotentialWrites(ri[i], false, potentialWrites)
when false:
# this optimization is wrong, see bug #23748
if args[i].kind in {nkHiddenAddr, nkAddr}:
if ri[i].kind in {nkHiddenAddr, nkAddr}:
# Optimization: don't use a temp, if we would only take the address anyway
needTmp[i] = false
needTmp[i - 1] = false
for i, it in isons(ri, 1):
for i in 1..<ri.len:
if i < typ.n.len:
assert(son(typ.n, i).kind == nkSym)
let paramType = son(typ.n, i)
assert(typ.n[i].kind == nkSym)
let paramType = typ.n[i]
if not paramType.typ.isCompileTimeOnly:
var arg = newBuilder("")
genArg(p, it, paramType.sym, ri, arg, needTmp[i-1])
genArg(p, ri[i], paramType.sym, ri, arg, needTmp[i-1])
if arg.buf.len != 0:
result.addArgument(argBuilder):
result.add(extract(arg))
else:
var arg = newBuilder("")
genArgNoParam(p, it, arg, needTmp[i-1])
genArgNoParam(p, ri[i], arg, needTmp[i-1])
if arg.buf.len != 0:
result.addArgument(argBuilder):
result.add(extract(arg))
@@ -514,23 +470,23 @@ proc addActualSuffixForHCR(res: var Rope, module: PSym, sym: PSym) =
(sym.typ.callConv == ccInline or sym.owner.id == module.id):
res = res & "_actual".rope
proc genPrefixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) =
# this is a hotspot in the compiler
var op = initLocExpr(p, ri.firstSon)
var op = initLocExpr(p, ri[0])
# getUniqueType() is too expensive here:
var typ = skipTypes(ri.firstSon.typ, abstractInstOwned)
var typ = skipTypes(ri[0].typ, abstractInstOwned)
assert(typ.kind == tyProc)
var callee = rdLoc(op)
if p.hcrOn and ri.firstSon.kind == nkSym:
callee.addActualSuffixForHCR(p.module.module, ri.firstSon.sym)
if p.hcrOn and ri[0].kind == nkSym:
callee.addActualSuffixForHCR(p.module.module, ri[0].sym)
var res = newBuilder("")
var call = initCallBuilder(res, callee)
genParams(p, ri, typ, res, call)
fixupCall(p, le, ri, d, res, call)
proc genClosureCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
template callProc(rp, params, pTyp: Snippet): Snippet =
let e = dotField(rp, "ClE_0")
@@ -555,22 +511,16 @@ proc genClosureCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
else:
cCall(p, params, e)
var op = initLocExpr(p, ri.firstSon)
var op = initLocExpr(p, ri[0])
# getUniqueType() is too expensive here:
var typ = skipTypes(ri.firstSon.typ, abstractInstOwned)
var typ = skipTypes(ri[0].typ, abstractInstOwned)
assert(typ.kind == tyProc)
var params = newBuilder("")
var argBuilder = default(CallBuilder) # not initCallBuilder, we just want the params
genParams(p, ri, typ, params, argBuilder)
# `rawProc` is bound BEFORE the `{.dirty.}` template that uses it. Inside a
# generic proc a dirty template's identifiers resolve at instantiation, and a
# local declared after the template loses to the module-level `rawProc` proc
# — which type-checks as a completely different thing.
let rawProc = getClosureType(p.module, typ, clHalf)
template genCallPattern {.dirty.} =
let rp = rdLoc(op)
let pars = extract(params)
@@ -579,7 +529,9 @@ proc genClosureCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
p.s(cpsStmts).add(callIter(rp, pars))
else:
p.s(cpsStmts).add(callProc(rp, pars, rawProc))
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri.firstSon)
let rawProc = getClosureType(p.module, typ, clHalf)
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri[0])
if typ.returnType != nil:
if isInvalidReturnType(p.config, typ):
# beware of 'result = p(result)'. We may need to allocate a temporary:
@@ -635,22 +587,22 @@ proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder;
if i < typ.n.len:
# 'var T' is 'T&' in C++. This means we ignore the request of
# any nkHiddenAddr when it's a 'var T'.
let paramType = son(typ.n, i)
let paramType = typ.n[i]
assert(paramType.kind == nkSym)
if paramType.typ.isCompileTimeOnly:
discard
elif paramType.typ.kind in {tyVar} and son(ri, i).kind == nkHiddenAddr:
elif paramType.typ.kind in {tyVar} and ri[i].kind == nkHiddenAddr:
result.addArgument(argBuilder):
genArgNoParam(p, son(ri, i).firstSon, result)
genArgNoParam(p, ri[i][0], result)
else:
result.addArgument(argBuilder):
genArgNoParam(p, son(ri, i), result) #, son(typ.n, i).sym)
genArgNoParam(p, ri[i], result) #, typ.n[i].sym)
else:
if tfVarargs notin typ.flags:
localError(p.config, ri.info, "wrong argument count")
else:
result.addArgument(argBuilder):
genArgNoParam(p, son(ri, i), result)
genArgNoParam(p, ri[i], result)
discard """
Dot call syntax in C++
@@ -694,16 +646,16 @@ proc skipAddrDeref(node: PNode): PNode =
var isAddr = false
case n.kind
of nkAddr, nkHiddenAddr:
n = n.firstSon
n = n[0]
isAddr = true
of nkDerefExpr, nkHiddenDeref:
n = n.firstSon
n = n[0]
else: return n
if n.kind == nkObjDownConv: n = n.firstSon
if n.kind == nkObjDownConv: n = n[0]
if isAddr and n.kind in {nkDerefExpr, nkHiddenDeref}:
result = n.firstSon
result = n[0]
elif n.kind in {nkAddr, nkHiddenAddr}:
result = n.firstSon
result = n[0]
else:
result = node
@@ -712,34 +664,34 @@ proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder) =
# However manual wrappers may also use 'ptr T'. In any case we support both
# for convenience.
internalAssert p.config, i < typ.n.len
assert(son(typ.n, i).kind == nkSym)
assert(typ.n[i].kind == nkSym)
# if the parameter is lying (tyVar) and thus we required an additional deref,
# skip the deref:
var ri = son(ri, i)
while ri.kind == nkObjDownConv: ri = ri.firstSon
var ri = ri[i]
while ri.kind == nkObjDownConv: ri = ri[0]
let t = typ[i].skipTypes({tyGenericInst, tyAlias, tySink})
if t.kind in {tyVar}:
let x = if ri.kind == nkHiddenAddr: ri.firstSon else: ri
let x = if ri.kind == nkHiddenAddr: ri[0] else: ri
if x.typ.kind == tyPtr:
genArgNoParam(p, x, result)
result.add("->")
elif x.kind in {nkHiddenDeref, nkDerefExpr} and x.firstSon.typ.kind == tyPtr:
genArgNoParam(p, x.firstSon, result)
elif x.kind in {nkHiddenDeref, nkDerefExpr} and x[0].typ.kind == tyPtr:
genArgNoParam(p, x[0], result)
result.add("->")
else:
genArgNoParam(p, x, result)
result.add(".")
elif t.kind == tyPtr:
if ri.kind in {nkAddr, nkHiddenAddr}:
genArgNoParam(p, ri.firstSon, result)
genArgNoParam(p, ri[0], result)
result.add(".")
else:
genArgNoParam(p, ri, result)
result.add("->")
else:
ri = skipAddrDeref(ri)
if ri.kind in {nkAddr, nkHiddenAddr}: ri = ri.firstSon
genArgNoParam(p, ri, result) #, son(typ.n, i).sym)
if ri.kind in {nkAddr, nkHiddenAddr}: ri = ri[0]
genArgNoParam(p, ri, result) #, typ.n[i].sym)
result.add(".")
proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Builder) =
@@ -749,20 +701,20 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Bu
case pat[i]
of '@':
var callBuilder = default(CallBuilder) # not init call builder
for k, _ in isons(ri, j):
for k in j..<ri.len:
genOtherArg(p, ri, k, typ, result, callBuilder)
inc i
of '#':
if i+1 < pat.len and pat[i+1] in {'+', '@'}:
let ri = son(ri, j)
let ri = ri[j]
if ri.kind in nkCallKinds:
let typ = skipTypes(ri.firstSon.typ, abstractInst)
if pat[i+1] == '+': genArgNoParam(p, ri.firstSon, result)
let typ = skipTypes(ri[0].typ, abstractInst)
if pat[i+1] == '+': genArgNoParam(p, ri[0], result)
result.add("(")
if 1 < ri.len:
var callBuilder: CallBuilder = default(CallBuilder)
genOtherArg(p, ri, 1, typ, result, callBuilder)
for k, _ in isons(ri, j+1):
for k in j+1..<ri.len:
var callBuilder: CallBuilder = default(CallBuilder)
genOtherArg(p, ri, k, typ, result, callBuilder)
result.add(")")
@@ -773,8 +725,8 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Bu
genThisArg(p, ri, j, typ, result)
inc i
elif i+1 < pat.len and pat[i+1] == '[':
var arg = son(ri, j).skipAddrDeref
while arg.kind in {nkAddr, nkHiddenAddr, nkObjDownConv}: arg = arg.firstSon
var arg = ri[j].skipAddrDeref
while arg.kind in {nkAddr, nkHiddenAddr, nkObjDownConv}: arg = arg[0]
genArgNoParam(p, arg, result)
#result.add debugTree(arg, 0, 10)
else:
@@ -796,19 +748,19 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Bu
if i - 1 >= start:
result.add(substr(pat, start, i - 1))
proc genInfixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
var op = initLocExpr(p, ri.firstSon)
proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
var op = initLocExpr(p, ri[0])
# getUniqueType() is too expensive here:
var typ = skipTypes(ri.firstSon.typ, abstractInst)
var typ = skipTypes(ri[0].typ, abstractInst)
assert(typ.kind == tyProc)
# don't call '$' here for efficiency:
let pat = $ri.firstSon.sym.loc.snippet
let pat = $ri[0].sym.loc.snippet
internalAssert p.config, pat.len > 0
if pat.contains({'#', '(', '@', '\''}):
var pl = newBuilder("")
genPatternCall(p, ri, pat, typ, pl)
# simpler version of 'fixupCall' that works with the pl+params combination:
var typ = skipTypes(ri.firstSon.typ, abstractInst)
var typ = skipTypes(ri[0].typ, abstractInst)
if typ.returnType != nil:
if p.module.compileToCpp and lfSingleUse in d.flags:
# do not generate spurious temporaries for C++! For C we're better off
@@ -833,20 +785,20 @@ proc genInfixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
pl.add(op.snippet)
var res = newBuilder("")
var call = initCallBuilder(res, extract(pl))
for i, _ in isons(ri, 2):
for i in 2..<ri.len:
genOtherArg(p, ri, i, typ, res, call)
fixupCall(p, le, ri, d, res, call)
proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
# generates a crappy ObjC call
var op = initLocExpr(p, ri.firstSon)
var op = initLocExpr(p, ri[0])
var pl = newBuilder("[")
# getUniqueType() is too expensive here:
var typ = skipTypes(ri.firstSon.typ, abstractInst)
var typ = skipTypes(ri[0].typ, abstractInst)
assert(typ.kind == tyProc)
# don't call '$' here for efficiency:
let pat = $ri.firstSon.sym.loc.snippet
let pat = $ri[0].sym.loc.snippet
internalAssert p.config, pat.len > 0
var start = 3
if ' ' in pat:
@@ -854,25 +806,25 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
pl.add(op.snippet)
if ri.len > 1:
pl.add(": ")
genArg(p, ri.secondSon, typ.n.secondSon.sym, ri, pl)
genArg(p, ri[1], typ.n[1].sym, ri, pl)
start = 2
else:
if ri.len > 1:
genArg(p, ri.secondSon, typ.n.secondSon.sym, ri, pl)
genArg(p, ri[1], typ.n[1].sym, ri, pl)
pl.add(" ")
pl.add(op.snippet)
if ri.len > 2:
pl.add(": ")
genArg(p, son(ri, 2), son(typ.n, 2).sym, ri, pl)
for i, it in isons(ri, start):
genArg(p, ri[2], typ.n[2].sym, ri, pl)
for i in start..<ri.len:
if i >= typ.n.len:
internalError(p.config, ri.info, "varargs for objective C method?")
assert(son(typ.n, i).kind == nkSym)
var param = son(typ.n, i).sym
assert(typ.n[i].kind == nkSym)
var param = typ.n[i].sym
pl.add(" ")
pl.add(param.name.s)
pl.add(": ")
genArg(p, it, param, ri, pl)
genArg(p, ri[i], param, ri, pl)
if typ.returnType != nil:
if isInvalidReturnType(p.config, typ):
if ri.len > 1: pl.add(" ")
@@ -925,27 +877,17 @@ proc isInactiveDestructorCall(p: BProc, e: PNode): bool =
We want to return early but the 'finally' section is traversed before
the 'let args = ...' statement. We exploit this to generate better
code for 'return'. ]#
result = e.safeLen == 2 and e.firstSon.kind == nkSym and
e.firstSon.sym.name.s == "=destroy" and notYetAlive(e.secondSon.skipAddr)
result = e.len == 2 and e[0].kind == nkSym and
e[0].sym.name.s == "=destroy" and notYetAlive(e[1].skipAddr)
proc genAsgnCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
if p.withinBlockLeaveActions > 0 and isInactiveDestructorCall(p, ri):
return
when defined(icDbgHash):
if ri.firstSon.typ == nil:
echo "NILCALLEE kind=", ri.firstSon.kind,
" sym=", (if ri.firstSon.kind == nkSym: ri.firstSon.sym.name.s else: "-"),
" symKind=", (if ri.firstSon.kind == nkSym: $ri.firstSon.sym.kind else: "-"),
" flags=", (if ri.firstSon.kind == nkSym: $ri.firstSon.sym.flags else: "-"),
" lazy=", nfLazyType in ri.firstSon.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.firstSon.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
if ri[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
genClosureCall(p, le, ri, d)
elif ri.firstSon.kind == nkSym and sfInfixCall in ri.firstSon.sym.flags:
elif ri[0].kind == nkSym and sfInfixCall in ri[0].sym.flags:
genInfixCall(p, le, ri, d)
elif ri.firstSon.kind == nkSym and sfNamedParamCall in ri.firstSon.sym.flags:
elif ri[0].kind == nkSym and sfNamedParamCall in ri[0].sym.flags:
genNamedParamCall(p, ri, d)
else:
genPrefixCall(p, le, ri, d)

File diff suppressed because it is too large Load Diff

View File

@@ -22,11 +22,10 @@ template detectVersion(field, corename) =
result = 1
proc detectStrVersion(m: BModule): int =
if m.g.config.usesSso() and
m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
result = 3
else:
detectVersion(strVersion, "nimStrVersion")
detectVersion(strVersion, "nimStrVersion")
proc detectSeqVersion(m: BModule): int =
detectVersion(seqVersion, "nimSeqVersion")
# ----- Version 1: GC'ed strings and seqs --------------------------------
@@ -129,175 +128,19 @@ proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Bu
result.addField(strInit, name = "p"):
result.add(cCast(ptrType("NimStrPayload"), cAddr(pureLit)))
proc ssoBytesLit(m: BModule; s: string; slen: int): string =
## Compute the `bytes` field value for the new SmallString layout.
## byte 0 = slen, bytes 1-7 = inline chars 0-6 (zero-padded).
## On LE: slen in bits 0-7, char[i] in bits (i+1)*8..(i+1)*8+7.
## On BE: slen in bits 56-63, char[i] in bits (6-i)*8..(6-i)*8+7.
const AlwaysAvail = 7
var val: uint64
if CPU[m.g.config.target.targetCPU].endian == littleEndian:
val = uint64(slen)
for i in 0..<min(s.len, AlwaysAvail):
val = val or (uint64(s[i]) shl (uint(i + 1) * 8))
else:
val = uint64(slen) shl 56
for i in 0..<min(s.len, AlwaysAvail):
val = val or (uint64(s[i]) shl (uint(AlwaysAvail - 1 - i) * 8))
# Cast to NU (C name for Nim's uint, = NU64 on 64-bit). NU64 = uint64_t.
result = cCast("NU", $val & "ULL")
proc ssoMoreLit(m: BModule; s: string): string =
## For medium string literals (AlwaysAvail < len <= PayloadSize), encode
## chars[AlwaysAvail..ptrSize-1] in the 'more' pointer field bit-pattern.
## The last pointer byte is always '\0' (null terminator), guaranteed by
## PayloadSize = AlwaysAvail + ptrSize - 1. slen <= PayloadSize guards
## prevent any code from dereferencing this as an actual pointer.
const AlwaysAvail = 7
let ptrSize = m.g.config.target.ptrSize
var val: uint64 = 0
for i in 0..<ptrSize:
let ch: uint64 = if AlwaysAvail + i < s.len: uint64(s[AlwaysAvail + i]) else: 0
if CPU[m.g.config.target.targetCPU].endian == littleEndian:
val = val or (ch shl (uint(i) * 8))
else:
val = val or (ch shl (uint(ptrSize - 1 - i) * 8))
result = cCast(ptrType("LongString"), "(uintptr_t)" & $val)
proc genStringLiteralV3Const(m: BModule; n: PNode; isConst: bool; result: var Builder) =
# Inline SmallString struct initializer for use inside const aggregate types.
# Layout: {bytes: NimUint, more: ptr LongString}
# bytes = slen (low byte) | char[0]<<8 | char[1]<<16 | ... | char[6]<<56
const AlwaysAvail = 7
let s = n.strVal
cgsym(m, "SmallString")
cgsym(m, "LongString")
let payloadSize = AlwaysAvail + m.g.config.target.ptrSize - 1
var si: StructInitializer
result.addStructInitializer(si, kind = siOrderedStruct):
if s.len <= AlwaysAvail:
result.addField(si, name = "bytes"):
result.add(ssoBytesLit(m, s, s.len))
result.addField(si, name = "more"):
result.add(NimNil)
elif s.len <= payloadSize:
# Medium string: bytes holds slen + chars 0-6; more holds chars 7..PayloadSize-1.
result.addField(si, name = "bytes"):
result.add(ssoBytesLit(m, s, s.len))
result.addField(si, name = "more"):
result.add(ssoMoreLit(m, s))
else:
# Emit the LongString block into cfsStrData and reference it inline.
let dataName = getTempName(m)
var res = newBuilder("")
res.addVarWithTypeAndInitializer(
if isConst: AlwaysConst else: Global,
name = dataName):
res.addSimpleStruct(m, name = "", baseType = ""):
res.addField(name = "rc", typ = NimInt)
res.addField(name = "fullLen", typ = NimInt)
res.addField(name = "capImpl", typ = NimInt)
res.addArrayField(name = "data", elementType = NimChar, len = s.len + 1)
do:
var di: StructInitializer
res.addStructInitializer(di, kind = siOrderedStruct):
res.addField(di, name = "fullLen"):
res.addIntValue(s.len)
res.addField(di, name = "rc"):
res.addIntValue(1)
res.addField(di, name = "capImpl"):
res.addIntValue(0) # static, never freed
res.addField(di, name = "data"):
res.add(makeCString(s))
m.s[cfsStrData].add(extract(res))
# slen = StaticSlen (254): marks this as a static (never-freed) long string.
result.addField(si, name = "bytes"):
result.add(ssoBytesLit(m, s, 254))
result.addField(si, name = "more"):
result.add(cCast(ptrType("LongString"), cAddr(dataName)))
# ------ Version 3: SmallString (SSO) strings --------------------------------
proc genStringLiteralV3(m: BModule; n: PNode; isConst: bool; result: var Builder) =
# SmallString literal. Always generate a fresh SmallString variable (like v2
# always generates a fresh outer NimStringV2). For long strings, cache the
# LongString payload to avoid duplicates within a module.
const AlwaysAvail = 7 # must match strs_v3.nim
let s = n.strVal
let tmp = getTempName(m)
result.add tmp
cgsym(m, "SmallString")
cgsym(m, "LongString")
let payloadSize = AlwaysAvail + m.g.config.target.ptrSize - 1
var res = newBuilder("")
if s.len <= AlwaysAvail:
# Short: bytes holds slen + all chars (zero-padded), more = NULL.
res.addVarWithInitializer(
if isConst: AlwaysConst else: Global,
name = tmp, typ = "SmallString"):
var si: StructInitializer
res.addStructInitializer(si, kind = siOrderedStruct):
res.addField(si, name = "bytes"):
res.add(ssoBytesLit(m, s, s.len))
res.addField(si, name = "more"):
res.add(NimNil)
elif s.len <= payloadSize:
# Medium: bytes holds slen + chars 0-6; more holds chars 7..PayloadSize-1 as raw bits.
res.addVarWithInitializer(
if isConst: AlwaysConst else: Global,
name = tmp, typ = "SmallString"):
var si: StructInitializer
res.addStructInitializer(si, kind = siOrderedStruct):
res.addField(si, name = "bytes"):
res.add(ssoBytesLit(m, s, s.len))
res.addField(si, name = "more"):
res.add(ssoMoreLit(m, s))
else:
# Long: cache the LongString block to emit it only once per module per string.
# Always generate a fresh SmallString pointing at the (possibly cached) block.
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var dataName: string
if id == m.labels:
dataName = getTempName(m)
res.addVarWithTypeAndInitializer(
if isConst: AlwaysConst else: Global,
name = dataName):
res.addSimpleStruct(m, name = "", baseType = ""):
res.addField(name = "rc", typ = NimInt)
res.addField(name = "fullLen", typ = NimInt)
res.addField(name = "capImpl", typ = NimInt)
res.addArrayField(name = "data", elementType = NimChar, len = s.len + 1)
do:
var di: StructInitializer
res.addStructInitializer(di, kind = siOrderedStruct):
res.addField(di, name = "fullLen"):
res.addIntValue(s.len)
res.addField(di, name = "rc"):
res.addIntValue(1)
res.addField(di, name = "capImpl"):
res.addIntValue(0) # bit 0 = 0: static, never freed
res.addField(di, name = "data"):
res.add(makeCString(s))
else:
dataName = m.tmpBase & $id
# slen = StaticSlen (254): marks this as a static (never-freed) long string.
res.addVarWithInitializer(
if isConst: AlwaysConst else: Global,
name = tmp, typ = "SmallString"):
var si: StructInitializer
res.addStructInitializer(si, kind = siOrderedStruct):
res.addField(si, name = "bytes"):
res.add(ssoBytesLit(m, s, 254))
res.addField(si, name = "more"):
res.add(cCast(ptrType("LongString"), cAddr(dataName)))
m.s[cfsStrData].add(extract(res))
# ------ Version selector ---------------------------------------------------
proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo;
isConst: bool; result: var Rope) =
case detectStrVersion(m)
of 0, 1: genStringLiteralDataOnlyV1(m, s, result)
of 2:
let tmp = getTempName(m)
genStringLiteralDataOnlyV2(m, s, tmp, isConst)
result.add tmp
else:
localError(m.config, info, "cannot determine how to produce code for string literal")
proc genNilStringLiteral(m: BModule; info: TLineInfo; result: var Builder) =
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil))
@@ -305,6 +148,5 @@ proc genStringLiteral(m: BModule; n: PNode; result: var Builder) =
case detectStrVersion(m)
of 0, 1: genStringLiteralV1(m, n, result)
of 2: genStringLiteralV2(m, n, isConst = true, result)
of 3: genStringLiteralV3(m, n, isConst = true, result)
else:
localError(m.config, n.info, "cannot determine how to produce code for string literal")

View File

@@ -19,17 +19,18 @@ proc specializeResetN(p: BProc, accessor: Rope, n: PNode;
if n == nil: return
case n.kind
of nkRecList:
for it in sons(n):
specializeResetN(p, accessor, it, typ)
for i in 0..<n.len:
specializeResetN(p, accessor, n[i], typ)
of nkRecCase:
if (n.firstSon.kind != nkSym): internalError(p.config, n.info, "specializeResetN")
let disc = n.firstSon.sym
if (n[0].kind != nkSym): internalError(p.config, n.info, "specializeResetN")
let disc = n[0].sym
if disc.loc.snippet == "": fillObjectFields(p.module, typ)
if disc.loc.t == nil:
internalError(p.config, n.info, "specializeResetN()")
let discField = dotField(accessor, disc.loc.snippet)
p.s(cpsStmts).addSwitchStmt(discField):
for branch in sonsFrom(n, 1):
for i in 1..<n.len:
let branch = n[i]
assert branch.kind in {nkOfBranch, nkElse}
var caseBuilder: SwitchCaseBuilder
p.s(cpsStmts).addSwitchCase(caseBuilder):
@@ -74,23 +75,6 @@ proc specializeResetT(p: BProc, accessor: Rope, typ: PType) =
cSizeof(getTypeDesc(p.module, typ)))
else:
specializeResetN(p, accessor, typ.n, typ)
if isCaseObj(typ.n):
# The active branch was released above. Clear the complete object so
# stale bytes from overlapping branches cannot be traced by the GC.
# type
# Foo = object
# case kind: bool
# of true:
# a: ref Bar # 8 bytes (pointer)
# of false:
# b: int # 4 bytes
# specializeResetT for b emits accessor.b = 0 — writes 4 bytes
# But the union is 8 bytes wide (sized by the largest branch)
# The remaining 4 bytes where a used to live are untouched
# Those stale bytes could contain a heap pointer the GC traces → crash
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
cCast(CPointer, cAddr(accessor)),
cSizeof(getTypeDesc(p.module, typ)))
of tyTuple:
let typ = getUniqueType(typ)
for i, a in typ.ikids:

File diff suppressed because it is too large Load Diff

View File

@@ -39,30 +39,11 @@ proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) =
if isExtern: Extern
elif lfExportLib in s.loc.flags: ExportLibVar
else: Private
if m.config.cmd == cmdNifC and vis == Private and not isExtern:
# A `{.threadvar.}`/`{.global.}` thread-local declared inside a routine is
# emitted by every module that emit-everywhere's its enclosing routine
# (e.g. libp2p's `var keys {.global.}: HashSet`), so its content-addressed
# name collides at link. Same fix as a plain global (genGlobalVarDecl):
# `extern` declaration + a droppable `'d'` definition unit the merge stage
# assigns one owner. The thread-local storage class rides on both.
let cname = stripCnifMarks(s.loc.snippet)
let td = getTypeDesc(m, s.loc.t)
# `extern` declaration via the full `addVar` overload — it knows the
# thread-local storage class (`NIM_THREADVAR`); the simple `addVar`'s
# `addVarHeader` does not implement `Threadvar`.
m.s[cfsVars].addVar(m, s, name = s.loc.snippet, typ = td,
kind = Threadvar, visibility = Extern)
m.s[cfsVars].add(cnifDefDirective(cname, "d", icNifName(m, s)))
m.s[cfsVars].addVar(m, s,
name = s.loc.snippet, typ = td, kind = Threadvar, visibility = vis)
m.s[cfsVars].add(cnifEndDefs())
else:
m.s[cfsVars].addVar(m, s,
name = s.loc.snippet,
typ = getTypeDesc(m, s.loc.t),
kind = Threadvar,
visibility = vis)
m.s[cfsVars].addVar(m, s,
name = s.loc.snippet,
typ = getTypeDesc(m, s.loc.t),
kind = Threadvar,
visibility = vis)
proc generateThreadLocalStorage(m: BModule) =
if m.g.nimtv.buf.len != 0 and (usesThreadVars in m.flags or sfMainModule in m.module.flags):

View File

@@ -31,18 +31,19 @@ proc genTraverseProc(c: TTraversalClosure, accessor: Rope, n: PNode;
if n == nil: return
case n.kind
of nkRecList:
for it in sons(n):
genTraverseProc(c, accessor, it, typ)
for i in 0..<n.len:
genTraverseProc(c, accessor, n[i], typ)
of nkRecCase:
if (n.firstSon.kind != nkSym): internalError(c.p.config, n.info, "genTraverseProc")
if (n[0].kind != nkSym): internalError(c.p.config, n.info, "genTraverseProc")
var p = c.p
let disc = n.firstSon.sym
let disc = n[0].sym
if disc.loc.snippet == "": fillObjectFields(c.p.module, typ)
if disc.loc.t == nil:
internalError(c.p.config, n.info, "genTraverseProc()")
let discField = dotField(accessor, disc.loc.snippet)
p.s(cpsStmts).addSwitchStmt(discField):
for branch in sonsFrom(n, 1):
for i in 1..<n.len:
let branch = n[i]
assert branch.kind in {nkOfBranch, nkElse}
var caseBuilder: SwitchCaseBuilder
p.s(cpsStmts).addSwitchCase(caseBuilder):

View File

@@ -59,10 +59,10 @@ proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string =
result = "_Z" # Common prefix in Itanium ABI
var params = ""
var staticLists = ""
if s.typ.paramsLen > 0: # we dont care about the return param
for _, pt in paramTypes(s.typ):
if pt.isNil: continue
params.add encodeType(m, pt, staticLists)
if s.typ.len > 1: #we dont care about the return param
for i in 1..<s.typ.len:
if s.typ[i].isNil: continue
params.add encodeType(m, s.typ[i], staticLists)
result.add encodeSym(m, s, makeUnique, staticLists)
result.add params
@@ -72,67 +72,20 @@ 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
if s.kind in routineKinds and {optCDebug, optItaniumMangle} * m.g.config.globalOptions == {optCDebug, optItaniumMangle} and
m.g.config.symbolFiles == disabledSf:
# Under the per-module IC backend the bare-name uniqueness probe
# (`m.g.mangledPrcs`) only sees the routines of the CURRENT module, so the
# clean-vs-`makeUnique` decision is made independently per process: a
# method base mangles clean at its owner but loses the in-module race to
# its same-signature dispatcher elsewhere (clean `speak` defined twice ->
# "multiple definition"; demanders call `speak_u<n>` that nobody defines).
# Force the stable, disamb-based unique name so every process agrees.
result = mangleProc(m, s, makeUnique = m.config.cmd == cmdNifC).rope
result = mangleProc(m, s, false).rope
else:
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)
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
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
s.locImpl.snippet = result
proc fillParamName(m: BModule; s: PSym) =
if s.loc.snippet == "":
@@ -311,7 +264,7 @@ proc isInvalidReturnType(conf: ConfigRef; typ: PType, isProc = true): bool =
var rettype = typ
var isAllowedCall = true
if isProc:
rettype = rettype.returnType
rettype = rettype[0]
isAllowedCall = typ.callConv in {ccClosure, ccInline, ccNimCall}
if rettype == nil or (isAllowedCall and
getSize(conf, rettype) > conf.target.floatSize*3):
@@ -356,7 +309,7 @@ proc addAbiCheck(m: BModule; t: PType, name: Rope) =
proc fillResult(conf: ConfigRef; param: PNode, proctype: PType) =
backendEnsureMutable param.sym
ensureMutable param.sym
fillLoc(param.sym.locImpl, locParam, param, "Result",
OnStack)
let t = param.sym.typ
@@ -386,10 +339,6 @@ proc getSimpleTypeDesc(m: BModule; typ: PType): Rope =
cgsym(m, "NimStrPayload")
cgsym(m, "NimStringV2")
result = typeNameOrLiteral(m, typ, "NimStringV2")
of 3:
cgsym(m, "LongString")
cgsym(m, "SmallString")
result = typeNameOrLiteral(m, typ, "SmallString")
else:
cgsym(m, "NimStringDesc")
result = typeNameOrLiteral(m, typ, "NimStringDesc*")
@@ -420,12 +369,6 @@ 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
@@ -480,7 +423,7 @@ proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet; kind: TypeDescKind
of tySequence:
let sig = hashType(t, m.config)
if optSeqDestructors in m.config.globalOptions:
if skipTypes(etB.elementType, typedescInst).kind == tyEmpty:
if skipTypes(etB[0], typedescInst).kind == tyEmpty:
internalError(m.config, "cannot map the empty seq type to a C type")
result = cacheGetType(m.forwTypeCache, sig)
@@ -510,21 +453,13 @@ proc getSeqPayloadType(m: BModule; t: PType): Rope =
result = getTypeDescWeak(m, t, check, dkParam) & "_Content"
#result = getTypeForward(m, t, hashType(t)) & "_Content"
proc seqPayloadElem(m: BModule; t: PType): Snippet =
## Returns the C type name for a seq's element as stored in the payload,
## suitable for sizeof()/alignof(). Must use dkVar, not the dkParam default,
## because reified openArrays (experimental views) differ: dkParam gives a
## bare pointer (T*) while dkVar gives the two-word struct actually stored.
var check = initIntSet()
result = getTypeDescAux(m, t.elementType, check, dkVar)
proc seqV2ContentType(m: BModule; t: PType; check: var IntSet) =
let sig = hashType(t, m.config)
let result = cacheGetType(m.typeCache, sig)
if result == "":
discard getTypeDescAux(m, t, check, dkVar)
else:
let dataTyp = getTypeDescAux(m, t.skipTypes(abstractInst).elementType, check, dkVar)
let dataTyp = getTypeDescAux(m, t.skipTypes(abstractInst)[0], check, dkVar)
m.s[cfsTypes].addSimpleStruct(m, name = result & "_Content", baseType = ""):
m.s[cfsTypes].addField(name = "cap", typ = NimInt)
m.s[cfsTypes].addField(name = "data",
@@ -598,10 +533,10 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
rettype = runtimeFormat(rettype.replace("'0", "$1"), [getTypeDescAux(m, t.returnType, check, dkResult)])
var types, names, args: seq[string] = @[]
if not isCtor:
var this = t.n.secondSon.sym
backendEnsureMutable this
var this = t.n[1].sym
ensureMutable this
fillParamName(m, this)
fillLoc(this.locImpl, locParam, t.n.secondSon,
fillLoc(this.locImpl, locParam, t.n[1],
this.paramStorageLoc)
if this.typ.kind == tyPtr:
this.locImpl.snippet = "this"
@@ -611,9 +546,9 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
types.add getTypeDescWeak(m, this.typ, check, dkParam)
let firstParam = if isCtor: 1 else: 2
for it in sonsFrom(t.n, firstParam):
if it.kind != nkSym: internalError(m.config, t.n.info, "genMemberProcParams")
var param = it.sym
for i in firstParam..<t.n.len:
if t.n[i].kind != nkSym: internalError(m.config, t.n.info, "genMemberProcParams")
var param = t.n[i].sym
var descKind = dkParam
if optByRef in param.options:
if param.typ.kind == tyGenericInst:
@@ -621,9 +556,9 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
else:
descKind = dkRefParam
var typ, name: string
backendEnsureMutable param
ensureMutable param
fillParamName(m, param)
fillLoc(param.locImpl, locParam, it,
fillLoc(param.locImpl, locParam, t.n[i],
param.paramStorageLoc)
if ccgIntroducedPtr(m.config, param, t.returnType) and descKind == dkParam:
typ = getTypeDescWeak(m, param.typ, check, descKind) & "*"
@@ -668,21 +603,9 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
rettype = getTypeDescWeak(m, t.returnType, check, dkResult)
var paramBuilder: ProcParamBuilder
params.addProcParams(paramBuilder):
for child in sonsFrom(t.n, 1):
if child.kind != nkSym: internalError(m.config, t.n.info, "genProcParams")
var param = child.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"
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
var descKind = dkParam
if m.config.backend == backendCpp and optByRef in param.options:
if param.typ.kind == tyGenericInst:
@@ -692,9 +615,8 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
if isCompileTimeOnly(param.typ): continue
backendEnsureMutable param
fillParamName(m, param)
fillLoc(param.locImpl, locParam, child,
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))
@@ -715,7 +637,7 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
# need to pass hidden parameter:
params.addParam(paramBuilder, name = param.locImpl.snippet & "Len_" & $j, typ = NimInt)
inc(j)
arr = arr.elementType.skipTypes({tySink})
arr = arr[0].skipTypes({tySink})
if t.returnType != nil and isInvalidReturnType(m.config, t):
var arr = t.returnType
var typ: Snippet
@@ -742,8 +664,8 @@ proc mangleRecFieldName(m: BModule; field: PSym): Rope =
proc hasCppCtor(m: BModule; typ: PType): bool =
result = false
if m.compileToCpp and typ != nil and typ.bindingId in m.g.graph.memberProcsPerType:
for prc in m.g.graph.memberProcsPerType[typ.bindingId]:
if m.compileToCpp and typ != nil and typ.itemId in m.g.graph.memberProcsPerType:
for prc in m.g.graph.memberProcsPerType[typ.itemId]:
if sfConstructor in prc.flags:
return true
@@ -752,8 +674,8 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): string
proc genCppInitializer(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): string =
#To avoid creating a BProc per test when called inside a struct nil BProc is allowed
result = "{}"
if typ.bindingId in m.g.graph.initializersPerType:
let call = m.g.graph.initializersPerType[typ.bindingId]
if typ.itemId in m.g.graph.initializersPerType:
let call = m.g.graph.initializersPerType[typ.itemId]
if call != nil:
var p = prc
if p == nil:
@@ -767,20 +689,20 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
check: var IntSet; result: var Builder; unionPrefix = "") =
case n.kind
of nkRecList:
for ni in sons(n):
genRecordFieldsAux(m, ni, rectype, check, result, unionPrefix)
for i in 0..<n.len:
genRecordFieldsAux(m, n[i], rectype, check, result, unionPrefix)
of nkRecCase:
if n.firstSon.kind != nkSym: internalError(m.config, n.info, "genRecordFieldsAux")
genRecordFieldsAux(m, n.firstSon, rectype, check, result, unionPrefix)
if n[0].kind != nkSym: internalError(m.config, n.info, "genRecordFieldsAux")
genRecordFieldsAux(m, n[0], rectype, check, result, unionPrefix)
# prefix mangled name with "_U" to avoid clashes with other field names,
# since identifiers are not allowed to start with '_'
var unionBody = newBuilder("")
for i, it in isons(n, 1):
case it.kind
for i in 1..<n.len:
case n[i].kind
of nkOfBranch, nkElse:
let k = lastSon(it)
let k = lastSon(n[i])
if k.kind != nkSym:
let structName = "_" & mangleRecFieldName(m, n.firstSon.sym) & "_" & $i
let structName = "_" & mangleRecFieldName(m, n[0].sym) & "_" & $i
var a = newBuilder("")
genRecordFieldsAux(m, k, rectype, check, a, unionPrefix & $structName & ".")
if a.buf.len != 0:
@@ -819,11 +741,7 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
# don't use fieldType here because we need the
# tyGenericInst for C++ template support
let noInit = sfNoInit in field.flags or (field.typ.sym != nil and sfNoInit in field.typ.sym.flags)
# Under `nim ic`, object fields are local NIF syms restored without an
# `owner`; `rectype` is the owning record type, so fall back to it rather
# than deref a nil `field.owner`.
let ownerTyp = if field.owner != nil: field.owner.typ else: rectype
if not noInit and (fieldType.isOrHasImportedCppType() or hasCppCtor(m, ownerTyp)):
if not noInit and (fieldType.isOrHasImportedCppType() or hasCppCtor(m, field.owner.typ)):
var didGenTemp = false
initializer = genCppInitializer(m, nil, fieldType, didGenTemp)
result.addField(field, sname, typ, isFlexArray, initializer)
@@ -833,8 +751,8 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
proc addRecordFields(result: var Builder; m: BModule; typ: PType, check: var IntSet) =
genRecordFieldsAux(m, typ.n, typ, check, result)
if typ.bindingId in m.g.graph.memberProcsPerType:
let procs = m.g.graph.memberProcsPerType[typ.bindingId]
if typ.itemId in m.g.graph.memberProcsPerType:
let procs = m.g.graph.memberProcsPerType[typ.itemId]
var isDefaultCtorGen, isCtorGen: bool = false
for prc in procs:
if sfConstructor in prc.flags:
@@ -882,8 +800,6 @@ proc getTupleDesc(m: BModule; typ: PType, name: Rope,
var res = newBuilder("")
res.addStruct(m, typ, name, ""):
for i, a in typ.ikids:
# Do not produce code for void types
if isEmptyType(a): continue
res.addField(
name = "Field" & $i,
typ = getTypeDescAux(m, a, check, dkField))
@@ -915,7 +831,7 @@ proc resolveStarsInCppType(typ: PType, idx, stars: int): PType =
result = typ[idx]
for i in 1..stars:
if result != nil and result.kidsLen > 0:
result = if result.kind == tyGenericInst: result.firstGenericParam
result = if result.kind == tyGenericInst: result[FirstGenericParamAt]
else: result.elemType
proc getOpenArrayDesc(m: BModule; t: PType, check: var IntSet; kind: TypeDescKind): Rope =
@@ -1075,9 +991,9 @@ proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDes
let owner = hashOwner(t.sym)
if not gDebugInfo.hasEnum(t.sym.name.s, t.sym.info.line, owner):
var vals: seq[(string, int)] = @[]
for son in sons(t.n):
assert(son.kind == nkSym)
let field = son.sym
for i in 0..<t.n.len:
assert(t.n[i].kind == nkSym)
let field = t.n[i].sym
vals.add((field.name.s, field.position.int))
gDebugInfo.registerEnum(EnumDesc(size: size, owner: owner, id: t.sym.id,
name: t.sym.name.s, values: vals))
@@ -1178,11 +1094,6 @@ 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:
@@ -1221,10 +1132,6 @@ 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:
@@ -1266,8 +1173,8 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
let isCtor = sfConstructor in prc.flags
var check = initIntSet()
fillBackendName(m, prc)
backendEnsureMutable prc
fillLoc(prc.locImpl, locProc, son(prc.ast, namePos), OnUnknown)
ensureMutable prc
fillLoc(prc.locImpl, locProc, prc.ast[namePos], OnUnknown)
var memberOp = "#." #only virtual
var typ: PType
if isCtor:
@@ -1289,14 +1196,6 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
name = typDesc
if isFnConst:
fnConst = " const"
if not isCtor:
# The call-site form (`x->salute(@)`), not the mangled Nim name. Set it on
# BOTH paths: whole-program cgen always emitted the out-of-class definition
# (the `else` branch) before any caller, but the per-module backend emits a
# foreign member proc's body in ITS OWN module, so the caller's TU only ever
# reaches the in-class declaration below — and called the member by the
# mangled name (`loo->salute_u0__vireouyks1()`, "struct Loo has no member").
prc.locImpl.snippet = "$1$2(@)" % [memberOp, name]
if isFwdDecl:
if isStatic:
result.add "static "
@@ -1306,7 +1205,9 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
override = " override"
superCall = ""
else:
if isCtor and superCall != "":
if not isCtor:
prc.locImpl.snippet = "$1$2(@)" % [memberOp, name]
elif superCall != "":
superCall = " : " & superCall
name = "$1::$2" % [typDesc, name]
@@ -1321,7 +1222,7 @@ proc genProcHeader(m: BModule; prc: PSym; result: var Builder; visibility: var D
var check = initIntSet()
fillBackendName(m, prc)
backendEnsureMutable prc
fillLoc(prc.locImpl, locProc, son(prc.ast, namePos), OnUnknown)
fillLoc(prc.locImpl, locProc, prc.ast[namePos], OnUnknown)
var rettype: Snippet = ""
var desc = newBuilder("")
genProcParams(m, prc.typ, rettype, desc, check, true, false)
@@ -1345,9 +1246,7 @@ 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:
if not isSharedInstanceCName(m, prc):
visibility = Private
# else: plain extern — the definition is shared across TUs
visibility = Private
if asPtr:
result.addProcVar(m, prc, name, params, rettype, isStatic = isStaticVar, ignoreAttributes = true)
else:
@@ -1425,24 +1324,8 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType;
m.hcrCreateTypeInfosProc.addCast(typ = ptrType(CPointer)):
m.hcrCreateTypeInfosProc.add(cAddr(name))
else:
if m.config.cmd == cmdNifC:
# Emit-everywhere (see genTypeInfoV1's perModuleCg gate): every demanding
# `cg` process emits this type info's tentative definition. Declare it
# `extern` 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 so
# exactly one external-linkage tentative definition survives (preserving
# the RTTI pointer identity refc relies on).
m.s[cfsStrData].addDeclWithVisibility(Extern):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
m.s[cfsStrData].add(cnifDefDirective(name, "d", icNifName(m, origType)))
m.s[cfsStrData].addDeclWithVisibility(Private):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
m.s[cfsStrData].add(cnifEndDefs())
m.icDataDefs.add (name, icNifName(m, origType))
else:
m.s[cfsStrData].addDeclWithVisibility(Private):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
m.s[cfsStrData].addDeclWithVisibility(Private):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
proc genTypeInfoAux(m: BModule; typ, origType: PType, name: Rope;
info: TLineInfo) =
@@ -1462,11 +1345,13 @@ proc discriminatorTableName(m: BModule; objtype: PType, d: PSym): Rope =
# bugfix: we need to search the type that contains the discriminator:
var objtype = objtype.skipTypes(abstractPtrs)
while lookupInRecord(objtype.n, d.name) == nil:
objtype = objtype.baseClass.skipTypes(abstractPtrs)
objtype = objtype[0].skipTypes(abstractPtrs)
if objtype.sym == nil:
internalError(m.config, d.info, "anonymous obj with discriminator")
result = "NimDT_$1_$2" % [rope($hashType(objtype, m.config)), rope(d.name.s.mangle)]
proc rope(arg: Int128): Rope = rope($arg)
proc discriminatorTableDecl(m: BModule; objtype: PType, d: PSym, result: var Builder) =
cgsym(m, "TNimNode")
var tmp = discriminatorTableName(m, objtype, d)
@@ -1501,14 +1386,14 @@ proc genObjectFields(m: BModule; typ, origType: PType, n: PNode, expr: Rope;
case n.kind
of nkRecList:
if n.len == 1:
genObjectFields(m, typ, origType, n.firstSon, expr, info)
genObjectFields(m, typ, origType, n[0], expr, info)
elif n.len > 0:
var tmp = getTempName(m) & "_" & $n.len
genTNimNodeArray(m, tmp, n.len)
for i, ni in isons(n):
for i in 0..<n.len:
var tmp2 = getNimNode(m)
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(i), cAddr(tmp2))
genObjectFields(m, typ, origType, ni, tmp2, info)
genObjectFields(m, typ, origType, n[i], tmp2, info)
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", n.len)
m.s[cfsTypeInit3].addFieldAssignment(expr, "kind", 2)
m.s[cfsTypeInit3].addFieldAssignment(expr, "sons",
@@ -1517,8 +1402,8 @@ proc genObjectFields(m: BModule; typ, origType: PType, n: PNode, expr: Rope;
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", n.len)
m.s[cfsTypeInit3].addFieldAssignment(expr, "kind", 2)
of nkRecCase:
assert(n.firstSon.kind == nkSym)
var field = n.firstSon.sym
assert(n[0].kind == nkSym)
var field = n[0].sym
var tmp = discriminatorTableName(m, typ, field)
var L = lengthOrd(m.config, field.typ)
assert L > 0
@@ -1533,41 +1418,25 @@ proc genObjectFields(m: BModule; typ, origType: PType, n: PNode, expr: Rope;
m.s[cfsTypeInit3].addFieldAssignment(expr, "name", makeCString(field.name.s))
m.s[cfsTypeInit3].addFieldAssignment(expr, "sons", cAddr(subscript(tmp, cIntValue(0))))
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", L)
if m.config.cmd == cmdNifC:
# The discriminator table has a content-addressed name
# (`NimDT_<hashType>_<field>`) and is emitted by every module that demands
# this variant type's RTTI (emit-everywhere; RTTI has no single owner —
# emission is lazy and often skipped). Declare it `extern` + wrap the
# tentative definition as a droppable `'d'` unit so the merge stage keeps
# exactly one external-linkage definition (mirrors the `TNimType` var and
# consts); otherwise the identical name collides across modules at link.
m.s[cfsData].addDeclWithVisibility(Extern):
m.s[cfsData].addArrayVar(kind = Local, name = tmp,
elementType = ptrType("TNimNode"), len = toInt(L)+1)
m.s[cfsData].add(cnifDefDirective(tmp, "d", ""))
m.s[cfsData].addArrayVar(kind = Local, name = tmp,
elementType = ptrType("TNimNode"), len = toInt(L)+1)
m.s[cfsData].add(cnifEndDefs())
m.icDataDefs.add (tmp, "")
else:
m.s[cfsData].addArrayVar(kind = Local, name = tmp,
elementType = ptrType("TNimNode"), len = toInt(L)+1)
for b in sonsFrom(n, 1):
m.s[cfsData].addArrayVar(kind = Local, name = tmp,
elementType = ptrType("TNimNode"), len = toInt(L)+1)
for i in 1..<n.len:
var b = n[i] # branch
var tmp2 = getNimNode(m)
genObjectFields(m, typ, origType, lastSon(b), tmp2, info)
case b.kind
of nkOfBranch:
if b.len < 2:
internalError(m.config, b.info, "genObjectFields; nkOfBranch broken")
for label in sonsButLast(b):
if label.kind == nkRange:
var x = toInt(getOrdValue(label.firstSon))
var y = toInt(getOrdValue(label.secondSon))
for j in 0..<b.len - 1:
if b[j].kind == nkRange:
var x = toInt(getOrdValue(b[j][0]))
var y = toInt(getOrdValue(b[j][1]))
while x <= y:
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(x), cAddr(tmp2))
inc(x)
else:
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(getOrdValue(label)), cAddr(tmp2))
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(getOrdValue(b[j])), cAddr(tmp2))
of nkElse:
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(L), cAddr(tmp2))
else: internalError(m.config, n.info, "genObjectFields(nkRecCase)")
@@ -1603,38 +1472,27 @@ proc genObjectInfo(m: BModule; typ, origType: PType, name: Rope; info: TLineInfo
t.incl tfObjHasKids
t = t.baseClass
proc validTupleTypeFields(t: PType): int =
# we want to treat tuples with only void fields as empty, so we need to exclude void types here:
result = 0
for a in t.kids:
if not isEmptyType(a): inc result
proc genTupleInfo(m: BModule; typ, origType: PType, name: Rope; info: TLineInfo) =
genTypeInfoAuxBase(m, typ, typ, name, cIntValue(0), info)
var expr = getNimNode(m)
let nonVoidKids = validTupleTypeFields(typ)
if nonVoidKids > 0:
var tmp = getTempName(m) & "_" & $nonVoidKids
genTNimNodeArray(m, tmp, nonVoidKids)
var j = 0
if not typ.isEmptyTupleType:
var tmp = getTempName(m) & "_" & $typ.kidsLen
genTNimNodeArray(m, tmp, typ.kidsLen)
for i, a in typ.ikids:
# Do not produce code for void types
if isEmptyType(a): continue
var tmp2 = getNimNode(m)
let fieldTypInfo = genTypeInfoV1(m, a, info)
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(j), cAddr(tmp2))
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(i), cAddr(tmp2))
m.s[cfsTypeInit3].addFieldAssignment(tmp2, "kind", 1)
m.s[cfsTypeInit3].addFieldAssignmentWithValue(tmp2, "offset"):
m.s[cfsTypeInit3].addOffsetof(getTypeDesc(m, origType, dkVar), "Field" & $i)
m.s[cfsTypeInit3].addFieldAssignment(tmp2, "typ", fieldTypInfo)
m.s[cfsTypeInit3].addFieldAssignment(tmp2, "name", "\"Field" & $i & "\"")
inc j
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", nonVoidKids)
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", typ.kidsLen)
m.s[cfsTypeInit3].addFieldAssignment(expr, "kind", 2)
m.s[cfsTypeInit3].addFieldAssignment(expr, "sons",
cAddr(subscript(tmp, cIntValue(0))))
else:
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", cIntValue(0))
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", typ.kidsLen)
m.s[cfsTypeInit3].addFieldAssignment(expr, "kind", 2)
m.s[cfsTypeInit3].addFieldAssignment(tiNameForHcr(m, name), "node", cAddr(expr))
@@ -1652,9 +1510,9 @@ proc genEnumInfo(m: BModule; typ: PType, name: Rope; info: TLineInfo) =
var firstNimNode = m.typeNodes
var hasHoles = false
enumNames.addStructInitializer(enumNamesInit, kind = siArray):
for i, son in isons(typ.n):
assert(son.kind == nkSym)
var field = son.sym
for i in 0..<typ.n.len:
assert(typ.n[i].kind == nkSym)
var field = typ.n[i].sym
var elemNode = getNimNode(m)
enumNames.addField(enumNamesInit, name = ""):
if field.ast == nil:
@@ -1744,13 +1602,8 @@ 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 = declName, typ = nr)
m.s[cfsStrData].addVar(kind = Local, name = str, typ = nr)
proc genTypeInfo2Name(m: BModule; t: PType): Rope =
var it = t
@@ -1785,7 +1638,7 @@ proc generateRttiDestructor(g: ModuleGraph; typ: PType; owner: PSym; kind: TType
dest.typ = getSysType(g, info, tyPointer)
result.typ = newProcType(info, idgen, result)
result.typ = newProcType(info, idgen, owner)
result.typ.addParam dest
var n = newNodeI(nkProcDef, info, bodyPos+1)
@@ -1814,16 +1667,6 @@ proc generateRttiDestructor(g: ModuleGraph; typ: PType; owner: PSym; kind: TType
incl result.flagsImpl, sfFromGeneric
incl result.flagsImpl, sfGeneratedOp
# Under IC the `rttiDestroy` wrapper is generated independently in every cg
# process that emits `typ`'s RTTI (the type-info is emit-everywhere). A plain
# counter `disamb` renumbers per process, so the RTTI table baked in module A
# references `rttiDestroy_c<n>` while module B (the =destroy owner) defines a
# different number → undefined at link. Give it a content-derived `disamb`
# (stable across processes) + `HookDisambBit`, exactly like `symPrototype` does
# for the hook itself: same `typ` ⇒ same C name everywhere, and the bit makes
# `emitsBodyInThisModule` emit the body in every demander (merge dedups). The
# `"rttiDestroy"` op-name keeps its key disjoint from the real `=destroy` hook's.
setHookDisamb(g, result, "rttiDestroy", typ)
proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp; result: var Builder) =
let theProc = getAttachedOp(m.g.graph, t, op)
@@ -1861,7 +1704,7 @@ proc getObjDepth(t: PType): int16 =
result = -1
while x != nil:
x = skipTypes(x, skipPtrs)
x = x.baseClass
x = x[0]
inc(result)
proc genDisplayElem(d: MD5Digest): uint32 =
@@ -1877,7 +1720,7 @@ proc genDisplay(result: var Builder, m: BModule; t: PType, depth: int) =
while x != nil:
x = skipTypes(x, skipPtrs)
seqs[i] = cIntValue(genDisplayElem(MD5Digest(hashType(x, m.config))))
x = x.baseClass
x = x[0]
inc i
var arr: StructInitializer
@@ -1896,30 +1739,9 @@ proc genVTable(result: var Builder, seqs: seq[PSym]) =
result.add(cCast(CPointer, seqs[i].loc.snippet))
proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLineInfo) =
## The C++/HCR flavour: C++ has no designated initializers, so the RTTI record
## is a bare variable that the module's `DatInit` fills field by field.
cgsym(m, "TNimTypeV2")
if m.config.cmd == cmdNifC:
# Same emit-everywhere split as `genTypeInfoV2Impl`: every `cg` process that
# demands this type declares it `extern`, and the DEFINITION is a droppable
# `'d'` unit the merge stage gives a single owner. Without the split the bare
# `TNimTypeV2 x;` in each TU is a tentative definition — which C's linker
# merges but C++'s does not, so `nim cpp --ic:on` died at link with
# "multiple definition of NTIv2__…". The field ASSIGNMENTS stay in every
# TU's `DatInit`: they are top-level code, not a definition, and every module
# computes the same values.
m.s[cfsStrData].addDeclWithVisibility(Extern):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
m.s[cfsVars].add(cnifDefDirective(name, "d", icNifName(m, origType)))
var def = newBuilder("")
def.addDeclWithVisibility(Private):
def.addVar(kind = Local, name = name, typ = "TNimTypeV2")
m.s[cfsVars].add extract(def)
m.s[cfsVars].add(cnifEndDefs())
m.icDataDefs.add (name, icNifName(m, origType))
else:
m.s[cfsStrData].addDeclWithVisibility(Private):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
m.s[cfsStrData].addDeclWithVisibility(Private):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
var flags = 0
if not canFormAcycle(m.g.graph, t): flags = flags or 1
@@ -1982,15 +1804,8 @@ 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")
# 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].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
@@ -2051,12 +1866,7 @@ proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineIn
else:
typeEntry.addField(typeInit, name = "flags"):
typeEntry.addIntValue(flags)
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)
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)
@@ -2094,14 +1904,8 @@ proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope =
result = "NTIv2$1_" % [rope($sig)]
m.typeInfoMarkerV2[sig] = result
let owner = t.skipTypes(typedescPtrs).bindingId.module
# 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):
let owner = t.skipTypes(typedescPtrs).itemId.module
if 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
@@ -2168,10 +1972,6 @@ 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)
@@ -2182,32 +1982,15 @@ 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)
return prefixTI(result)
var owner = t.skipTypes(typedescPtrs).bindingId.module
# In the per-module backend (`cg`) V1 RTTI is emit-everywhere like procs,
# consts and V2 type info: 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 at link — the refc `NTI*` undefined-reference bug. (V2 got
# this gate in 8e0dd4bfb; V1, only reached under `--mm:refc`, was missed.)
let perModuleCg = m.config.cmd == cmdNifC and m.config.icBackendStage == "cg"
if not perModuleCg and owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
dbgNti "extern:ownerRouted"
var owner = t.skipTypes(typedescPtrs).itemId.module
if owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
# 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
@@ -2218,7 +2001,6 @@ 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)
@@ -2289,8 +2071,8 @@ proc genTypeInfo*(config: ConfigRef, m: BModule; t: PType; info: TLineInfo): Rop
proc retrieveSym(n: PNode): PSym =
case n.kind
of nkPostfix: result = retrieveSym(n.secondSon)
of nkPragmaExpr, nkTypeDef: result = retrieveSym(n.firstSon)
of nkPostfix: result = retrieveSym(n[1])
of nkPragmaExpr, nkTypeDef: result = retrieveSym(n[0])
of nkSym: result = n.sym
else: result = nil
@@ -2305,21 +2087,3 @@ proc genTypeSection(m: BModule, n: PNode) =
discard getTypeDescAux(m, s.typ, intSet, descKindFromSymKind(s.kind))
if m.g.generatedHeader != nil:
discard getTypeDescAux(m.g.generatedHeader, s.typ, intSet, descKindFromSymKind(s.kind))
# Unlike genCppInitializer which returns just the braced value list (e.g. "{a, b}"),
# genCppConstructorExpr returns a full type-prefixed expression (e.g. "Foo(a, b)").
# This is used when a standalone construction expression is needed — e.g. on the
# right-hand side of an assignment — whereas genCppInitializer is used in variable
# declarations where the type is already written separately before the initializer.
proc genCppConstructorExpr(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): Snippet =
var params = ""
if typ.bindingId in m.g.graph.initializersPerType:
let call = m.g.graph.initializersPerType[typ.bindingId]
if call != nil:
var p = prc
if p == nil:
p = BProc(module: m)
params = genCppParamsForCtor(p, call, didGenTemp)
if prc == nil:
assert p.blocks.len == 0, "BProc belongs to a struct doesnt have blocks"
result = getTypeDesc(m, typ, dkVar) & "(" & params & ")"

View File

@@ -11,7 +11,7 @@
import
ast, types, msgs, wordrecg,
platform, trees, options, cgendata, mangleutils, renderer, modulegraphs
platform, trees, options, cgendata, mangleutils, renderer
import std/[hashes, strutils, formatfloat]
@@ -22,13 +22,13 @@ proc getPragmaStmt*(n: PNode, w: TSpecialWord): PNode =
case n.kind
of nkStmtList:
result = nil
for it in sons(n):
result = getPragmaStmt(it, w)
for i in 0..<n.len:
result = getPragmaStmt(n[i], w)
if result != nil: break
of nkPragma:
result = nil
for it in sons(n):
if whichPragma(it) == w: return it
for i in 0..<n.len:
if whichPragma(n[i]) == w: return n[i]
else:
result = nil
@@ -92,7 +92,7 @@ proc ccgIntroducedPtr*(conf: ConfigRef; s: PSym, retType: PType): bool =
result = true
elif (optByRef in s.options) or (getSize(conf, pt) > conf.target.floatSize * 3):
result = true # requested anyway
elif (tfFinal in pt.flags) and (pt.baseClass == nil):
elif (tfFinal in pt.flags) and (pt[0] == nil):
result = false # no need, because no subtyping possible
else:
result = true # ordinary objects are always passed by reference,
@@ -112,26 +112,10 @@ 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. Which integer
# identifies such a symbol is decided ONCE, in `astdef.backendMintedDisamb`,
# shared with `mangleProcNameExt` and `ast2nif.toNifSymName`.
if s.itemId.isBackendMinted:
result.add "_c"
result.add $backendMintedDisamb(s)
else:
result.add "_u"
# Mirror `mangleProcNameExt`: use the per-(module,name) `disamb`, NOT
# `itemId.item`. Under the per-module IC backend the same symbol is loaded
# from a NIF in many processes and `itemId.item` is a fresh, load-order
# dependent counter — so a method base would mangle to `_u1` in one module,
# `_u3` in another and clean at its owner, none of which link. `disamb` is
# assigned deterministically per (module, name) and is serialized, so every
# process that touches the symbol derives the identical C name.
result.add $s.disamb
# 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
@@ -148,7 +132,7 @@ proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
of tyObject, tyEnum, tyDistinct, tyUserTypeClass, tyGenericParam:
result = encodeSym(m, t.sym)
of tyGenericInst, tyUserTypeClassInst, tyGenericBody:
result = encodeName(t.genericHead.sym.name.s)
result = encodeName(t[0].sym.name.s)
result.add "I"
for i in 1..<t.len - 1:
result.add encodeType(m, t[i], staticLists)
@@ -160,7 +144,8 @@ proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
of tySequence: encodeName("seq")
else: encodeName(kindName)
result.add "I"
for s in kids(t):
for i in 0..<t.len:
let s = t[i]
if s.isNil: continue
result.add encodeType(m, s, staticLists)
result.add "E"
@@ -171,12 +156,12 @@ proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
raiseAssert "unreachable"
of tyRange:
var val = "range_"
if t.n.firstSon.typ.kind in {tyFloat..tyFloat128}:
val.addFloat t.n.firstSon.floatVal
if t.n[0].typ.kind in {tyFloat..tyFloat128}:
val.addFloat t.n[0].floatVal
val.add "_"
val.addFloat t.n.secondSon.floatVal
val.addFloat t.n[1].floatVal
else:
val.add $t.n.firstSon.intVal & "_" & $t.n.secondSon.intVal
val.add $t.n[0].intVal & "_" & $t.n[1].intVal
result = encodeName(val)
of tyString..tyUInt64, tyPointer, tyBool, tyChar, tyVoid, tyAnything, tyNil, tyEmpty:
result = encodeName(kindName)

File diff suppressed because it is too large Load Diff

View File

@@ -75,13 +75,10 @@ type
flags*: set[TCProcFlag]
lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements
currLineInfo*: TLineInfo # AST codegen will make this superfluous
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]]
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]]
# in how many nested try statements we are
# (the vars must be volatile then)
# `inExcept` is true when we are in the except part of a try block.
# `isHidden` is true for compiler-injected `nkHiddenTryStmt` wrappers
# (e.g. ARC's destructor try/finally around `except T as e:` bodies);
# finallyActions walks past such wrappers to reach the user's try.
# bool is true when are in the except part of a try block
finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when
# using return in finally statements
labels*: Natural # for generating unique labels in the C proc
@@ -142,13 +139,6 @@ type
# not a list of IDs nor can it be made to be one.
mangledPrcs*: HashSet[string]
icEmitted*: IntSet
## Under `--icBackendStage:cg`: the positions of the modules THIS process
## writes a translation unit for. `cgen.findPendingModule` consults it to
## decide where a demanded definition goes — see the comment there. Empty
## outside that stage, which is why every other backend keeps the ordinary
## whole-program routing.
TCGen = object of PPassContext # represents a C source file
s*: TCFileSections # sections of the C file
flags*: set[CodegenFlag]
@@ -165,12 +155,6 @@ 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
@@ -189,21 +173,6 @@ 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
icGlobalDtorName*: string # per-module backend: the C name of this
# module's global-destructor proc, recorded in
# the artifact's meta head so the main module's
# `cg` — a different process — can call it
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
@@ -245,8 +214,7 @@ proc newProc*(prc: PSym, module: BModule): BProc =
proc newModuleList*(g: ModuleGraph): BModuleList =
BModuleList(typeInfoMarker: initTable[SigHash, tuple[str: Rope, owner: int32]](),
config: g.config, graph: g, nimtvDeclared: initIntSet(),
icEmitted: initIntSet())
config: g.config, graph: g, nimtvDeclared: initIntSet())
iterator cgenModules*(g: BModuleList): BModule =
for m in g.modulesClosed:

View File

@@ -160,17 +160,7 @@ proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
var witness: PSym = nil
if s.typ.firstParamType.owner.getModule != s.getModule and vtables in g.config.features and not
g.config.isDefined("nimInternalNonVtablesTesting") and sfFromGeneric notin s.flags:
# `sfFromGeneric` excepted: this is the same-module restriction for vtable
# slot placement, and it must be judged on the GENERIC method, not on an
# instance. The generic `method skip[T](x: Input[T])` never reaches here
# (`semMethodPrototype` registers generic methods via `addMethodToGeneric`,
# bypassing `methodDef`); only its instance `skip[string]` does, and that
# instance's first-param type `Input[string]` is owned by whichever module
# first instantiated it (`tparsecombnum`, which `import parsecomb`s and uses
# it), NOT by `Input[T]`'s defining module — so the comparison spuriously
# fails for a method that is perfectly legal at the generic level. (Concrete
# methods, `sfFromGeneric notin flags`, are still checked.)
g.config.isDefined("nimInternalNonVtablesTesting"):
localError(g.config, s.info, errGenerated, "method `" & s.name.s &
"` can be defined only in the same module with its type (" & s.typ.firstParamType.typeToString() & ")")
if sfImportc in s.flags:
@@ -190,19 +180,17 @@ 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:
if witness.isNil: witness = g.methods[i].methods[0]
# create a new dispatcher:
# stores the id and the position
if s.typ.firstParamType.skipTypes(skipPtrs).bindingId notin g.bucketTable:
g.bucketTable[s.typ.firstParamType.skipTypes(skipPtrs).bindingId] = 1
if s.typ.firstParamType.skipTypes(skipPtrs).itemId notin g.bucketTable:
g.bucketTable[s.typ.firstParamType.skipTypes(skipPtrs).itemId] = 1
else:
g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).bindingId)
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 &

View File

@@ -139,7 +139,7 @@
import
ast, msgs, idents,
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos, trees
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos
import std/tables
@@ -167,8 +167,6 @@ 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.
@@ -254,8 +252,7 @@ proc newCurExcAccess(ctx: var Ctx): PNode =
ctx.newEnvVarAccess(ctx.curExcSym)
proc newStateLabel(ctx: Ctx): PNode =
result = nkIntLit.newIntNode(0)
result.typ = getSysType(ctx.g, TLineInfo(), tyInt16)
ctx.g.newIntLit(TLineInfo(), 0)
proc newState(ctx: var Ctx, n: PNode, inlinable: bool, label: PNode): PNode =
# Creates a new state, adds it to the context
@@ -336,14 +333,9 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
var cond: PNode = nil
for i in 0..<c.len - 1:
assert(c[i].kind == nkType)
# Use the :curExc env field (set by the wrapper before entering the
# except landing state) instead of calling getCurrentException():
# injectdestructors does not process the args of this raw generic
# `of` magic call, so an owning getCurrentException() temp would
# never be destroyed and the caught exception would leak (#23615).
let nextCond = newTreeIT(nkCall, c.info, ctx.g.getSysType(c.info, tyBool),
newSymNode(g.getSysMagic(c.info, "of", mOf)),
ctx.newCurExcAccess(),
g.callCodegenProc("getCurrentException"),
c[i])
cond = if cond.isNil: nextCond
@@ -600,7 +592,10 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
let branch = n[i]
case branch.kind
of nkExceptBranch:
branch[^1] = ctx.convertExprBodyToAsgn(branch[^1], tmp)
if branch[0].kind == nkType:
branch[1] = ctx.convertExprBodyToAsgn(branch[1], tmp)
else:
branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp)
of nkFinally:
discard
else:
@@ -990,14 +985,9 @@ 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(body, true, label)
if ctx.transformClosureIteratorBody(body, gotoOut) != body:
discard ctx.newState(s, true, label)
if ctx.transformClosureIteratorBody(s, gotoOut) != s:
internalError(ctx.g.config, "transformClosureIteratorBody != s")
break
else:
@@ -1135,14 +1125,6 @@ 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)
@@ -1408,34 +1390,18 @@ proc optimizeStates(ctx: var Ctx) =
for i in 0 .. ctx.states.high:
ctx.states[i].label.intVal = i
proc detectCapturedSym(c: var Ctx, s: PSym, stateIdx: int) =
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym:
let vs = c.varStates.getOrDefault(s.itemId, localNotSeen)
if vs == localNotSeen: # First seing this variable
c.varStates[s.itemId] = stateIdx
elif vs == localRequiresLifting:
discard # Sym already marked
elif vs != stateIdx:
c.captureVar(s)
proc isClosureIterLocal(c: Ctx, s: PSym): bool =
s.kind in {skResult, skVar, skLet, skForVar, skTemp} and
sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym
proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) =
case n.kind
of nkSym:
let s = n.sym
detectCapturedSym(c, s, stateIdx)
of nkAddr, nkHiddenAddr:
let s = getRoot(n)
if s != nil and isClosureIterLocal(c, s):
detectCapturedSym(c, s, stateIdx)
# bug #25596; lifetime extension for `addr`-taken locals as
# we claim ARC/ORC do destruction based on scopes, not on last-usages.
c.captureVar(s)
for i in 0 ..< n.safeLen:
detectCapturedVars(c, n[i], stateIdx)
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym:
let vs = c.varStates.getOrDefault(s.itemId, localNotSeen)
if vs == localNotSeen: # First seing this variable
c.varStates[s.itemId] = stateIdx
elif vs == localRequiresLifting:
discard # Sym already marked
elif vs != stateIdx:
c.captureVar(s)
of nkReturnStmt:
if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}:
# we have a `result = result` expression produced by the closure

View File

@@ -53,8 +53,7 @@ proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) =
proc loadConfigsAndProcessCmdLine*(self: NimProg, cache: IdentCache; conf: ConfigRef;
graph: ModuleGraph): bool =
if self.suggestMode:
conf.setCmd cmdCheck
conf.ideActive = true
conf.setCmd cmdIdeTools
if conf.cmd == cmdNimscript:
incl(conf.globalOptions, optWasNimscript)
loadConfigs(DefaultConfig, cache, conf, graph.idgen) # load all config files

View File

@@ -1,740 +0,0 @@
#
#
# 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* = "5"
## 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), the type NIF names and cnif-marked extern
## RTTI references the typeinfo flavor of the def-retention check
## needs (v4), or the global-destructor name the main module's `cg`
## calls at teardown (v5); `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 = ""; globalDtor = "";
implDeps: openArray[string] = []) =
## Splits the marked module text into the `.c.nif` artifact.
## The artifact starts with a `(meta <flags> "semmedNif" "moduleBase"
## "version" "globalDtor")` head — whether the module has an init/datInit
## proc ('i'/'d'), which semmed NIF it was generated from, 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) and the C name of the module's global-destructor
## proc, if any (what the main module's `cg` calls at program teardown; see
## `cgen.genIcModuleDestroyGlobals`) — 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.addStrLit globalDtor
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
globalDtor*: string ## C name of the module's global-destructor proc
## ("" when the module has no global destructors)
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)
elif strIdx == 3: result.globalDtor = 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"
const LiveModulesFile* = "ic.backend.live.txt"
## One `.c.nif` path per line: exactly the artifacts of the modules the CURRENT
## build graph considers live. The `merge` stage reads this instead of globbing
## `*.c.nif` off the nimcache, so a leftover artifact from an unrelated build
## that happens to share the cache directory cannot be merged in (which is what
## made a shared prebuilt cache unusable: merge picked owners in modules the
## program does not import, and the link then wanted their objects).
## 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)

View File

@@ -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, sets, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
import std/[setutils, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
import
msgs, options, nversion, condsyms, extccomp, platform,
wordrecg, nimblecmd, lineinfos, pathutils
@@ -250,7 +250,6 @@ const
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
errInvalidFeatureButXFound = Feature.toSeq.map(proc(val:Feature): string = "'$1'" % $val).join(", ") & " expected, but '$1' found"
errDefaultOrSsoExpectedButXFound = "'default' or 'sso' expected, but '$1' found"
template warningOptionNoop(switch: string) =
warningDeprecated(conf, info, "'$#' is deprecated, now a noop" % switch)
@@ -307,13 +306,6 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
else:
result = false
localError(conf, info, errInvalidExceptionSystem % arg)
of "strings":
case arg.normalize
of "default": result = conf.selectedStrings == stringDefault
of "sso": result = conf.selectedStrings == stringSso
else:
result = false
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
of "experimental":
try:
result = conf.features.contains parseEnum[Feature](arg)
@@ -508,8 +500,6 @@ 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
of "track": cmdTrack # IDE goto-def / find-usages over `nim ic`'s NIF output
else: cmdUnknown
proc setCmd*(conf: ConfigRef, cmd: Command) =
@@ -627,11 +617,7 @@ proc processMemoryManagementOption(switch, arg: string, pass: TCmdLinePass,
conf.selectedGC = gcHooks
defineSymbol(conf.symbols, "gchooks")
incl conf.globalOptions, optSeqDestructors
# (The `arg` here is the mm MODE — "hooks" — so feeding it to an on/off
# switch made `--mm:hooks` fail outright with "'on' or 'off' expected, but
# 'hooks' found". The `incl` above is what that call was meant to do.
# Reachable only via the explicit switch: `--newruntime` sets
# `selectedGC` directly, which is why this stayed hidden.)
processOnOffSwitchG(conf, {optSeqDestructors}, arg, pass, info)
if pass in {passCmd2, passPP}:
defineSymbol(conf.symbols, "nimSeqsV2")
of "go":
@@ -659,18 +645,6 @@ 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)
@@ -723,14 +697,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.outDir = processPath(conf, arg, info, notRelativeToProj=true)
of "usenimcache":
processOnOffSwitchG(conf, {optUseNimcache}, arg, pass, info)
of "ideimports":
# nimsuggest: where the import closure comes from. IC is opt-in.
# nif|on load unchanged imports from precompiled NIF (cmdM)
# source|off (default) recompile the whole closure from source (cmdCheck)
case arg.normalize
of "nif", "on", "": conf.ideImportsFromNif = true
of "source", "off": conf.ideImportsFromNif = false
else: localError(conf, info, "'--ideImports' expects 'nif' or 'source', got: '$1'" % arg)
of "docseesrcurl":
expectArg(conf, switch, arg, pass, info)
conf.docSeeSrcUrl = arg
@@ -784,17 +750,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
processMemoryManagementOption(switch, arg, pass, info, conf)
of "mm":
processMemoryManagementOption(switch, arg, pass, info, conf)
of "strings":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
case arg.normalize
of "default":
conf.selectedStrings = stringDefault
of "sso":
conf.selectedStrings = stringSso
defineSymbol(conf.symbols, "nimsso")
else:
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
of "warnings", "w":
if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
@@ -830,8 +785,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
localError(conf, info, "expected nim|cpp but found " & arg)
of "compress":
conf.globalOptions.incl optCompress
of "genbif":
processOnOffSwitchG(conf, {optGenBif}, arg, pass, info)
of "g": # alias for --debugger:native
conf.globalOptions.incl optCDebug
conf.options.incl optLineDir
@@ -847,7 +800,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
of "hotcodereloading":
processOnOffSwitchG(conf, {optHotCodeReloading}, arg, pass, info)
if conf.hcrOn:
warningDeprecated(conf, info, "hotCodeReloading is deprecated, see https://github.com/nim-lang/RFCs/issues/573 for further information")
defineSymbol(conf.symbols, "hotcodereloading")
defineSymbol(conf.symbols, "useNimRtl")
# hardcoded linking with dynamic runtime for MSVC for smaller binaries
@@ -952,53 +904,6 @@ 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", "icbackendmodules":
# `nim nifc` only: the NIF module suffixes the lower/cg/emit stage operates
# on, comma-separated — the invocation's batch (see
# options.icBackendModules). The singular spelling is the same switch: a
# one-module batch is what the per-module fan-out passes.
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
conf.icBackendModules = @[]
for suffix in arg.split(','):
if suffix.len > 0: conf.icBackendModules.add suffix
of "import":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
@@ -1006,7 +911,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
if m.len == 0:
localError(conf, info, "Cannot resolve filename: " & arg)
else:
conf.implicitImports.add(if arg.startsWith(stdPrefix): arg else: m)
conf.implicitImports.add m
of "include":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
@@ -1046,7 +951,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
expectArg(conf, switch, arg, pass, info)
var value: int = 10_000_000
discard parseSaturatedNatural(arg, value)
if value <= 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero")
if not value > 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero")
conf.maxLoopIterationsVM = value
of "maxcalldepthvm":
expectArg(conf, switch, arg, pass, info)
@@ -1096,14 +1001,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
expectNoArg(conf, switch, arg, pass, info)
helpOnError(conf, pass)
of "symbolfiles", "incremental", "ic":
if pass in {passCmd2, passPP} and switch.normalize == "symbolfiles":
deprecatedAlias(switch, "incremental")
if switch.normalize == "symbolfiles": deprecatedAlias(switch, "incremental")
# xxx maybe also ic, since not in help?
# `--ic:on` is read in passCmd1 too: `nim.nim` decides BEFORE config loading
# whether this run is an IC driver (`ensureIcConfig` must produce the
# precompiled config the driver itself then replays), and passCmd1 is the
# only pass that has run by then.
if pass in {passCmd1, passCmd2, passPP}:
if pass in {passCmd2, passPP}:
case arg.normalize
of "on": conf.ic = true
of "legacy": conf.symbolFiles = v2Sf
@@ -1341,16 +1241,8 @@ proc processArgument*(pass: TCmdLinePass; p: OptParser;
# support UNIX style filenames everywhere for portable build scripts:
if config.projectName.len == 0:
config.projectName = unixToNativePath(p.key)
if config.cmd == cmdTrack:
# `nim track PROJ --def:...`: unlike a normal command (where everything
# after the project file is passed to the compiled program), `track`
# accepts its IDE-query switches AFTER the project — the natural,
# nimsuggest-like invocation form. So don't swallow the rest of the line
# into `arguments`; keep parsing the remaining tokens as switches.
result = false
else:
config.arguments = cmdLineRest(p)
result = true
config.arguments = cmdLineRest(p)
result = true
else:
result = false
inc argsCount

View File

@@ -11,8 +11,7 @@
## for details. Note this is a first implementation and only the "Concept matching"
## section has been implemented.
import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types,
layeredtable, semtypinst
import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
import std/sets
@@ -72,8 +71,7 @@ proc semConceptDeclaration*(c: PContext; n: PNode): PNode =
type
MatchFlags* = enum
mfDontBind # Do not export bindings from the concept match
mfBindGenericParam # Export inferred invocation parameters despite mfDontBind
mfDontBind # Do not bind generic parameters
mfCheckGeneric # formal <- formal comparison as opposed to formal <- operand
ConceptTypePair = tuple[conceptId, typeId: ItemId]
@@ -207,7 +205,7 @@ proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon):
# Cycle detection: track (concept, type) pairs to prevent infinite recursion.
# Returns true on cycle (coinductive semantics) to support co-dependent concepts.
let pair: ConceptTypePair = (concpt.bindingId, potentialImpl.bindingId)
let pair: ConceptTypePair = (concpt.itemId, potentialImpl.itemId)
if pair in m.marker:
return true
m.marker.incl pair
@@ -271,8 +269,10 @@ proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind =
let
fn = fc.conceptBody
an = ac.conceptBody
sameLen = fc.len == ac.len
var match = false
for fdef in fn:
var cmpResult = false
for ia, ndef in an:
match = cmpConceptDefs(c, fdef, ndef, m)
if match:
@@ -330,10 +330,13 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
result = matchType(c, f.skipModifier, a, m)
of tyTypeDesc:
if isSelf(f):
let ua = a.skipTypes(asymmetricConceptParamMods)
if m.magic in {mArrPut, mArrGet}:
if m.potentialImplementation.reduceToBase.kind in arrPutGetMagicApplies:
bindParam(c, m, a, last m.potentialImplementation)
result = true
#elif ua.isConcept:
# result = matchType(c, m.concpt, ua, m)
else:
result = matchType(c, a.skipTypes(ignorableForArgType), m.potentialImplementation, m)
else:
@@ -575,17 +578,7 @@ proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool =
# error was reported earlier.
result = false
proc resolvedBinding(c: PContext; t: PType; m: MatchCon): PType =
## An inferred concept parameter can refer to an implementation-local
## generic parameter, for example `Elem[Impl.T]`. Resolve it while the
## matcher's private bindings (`Impl.T -> int`) are still available.
if t.containsUnresolvedType:
prepareMetatypeForSigmatch(c, m.bindings, m.concpt.sym.info, t)
else:
t
proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
invocation: PType; m: var MatchCon) =
proc fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; m: var MatchCon) =
# invocation != nil means we have a non-atomic concept:
if invocation != nil and invocation.kind == tyGenericInvocation:
assert concpt.sym.typ.kind == tyGenericBody
@@ -597,9 +590,8 @@ proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
continue
let found = m.bindings.lookup(thisSym)
if found != nil:
let resolved = resolvedBinding(c, found, m)
when logBindings: echo "Invocation bind: ", thisSym, " ", resolved
bindings.put(thisSym, resolved)
when logBindings: echo "Invocation bind: ", thisSym, " ", found
bindings.put(thisSym, found)
# bind even more generic parameters
let genBody = invocation.base
@@ -615,20 +607,6 @@ proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
bindings.put(invocation[i], boundV)
bindings.put(concpt, m.potentialImplementation)
proc fixConstraintBindings(c: PContext; bindings: var LayeredIdTable;
invocation: PType; m: MatchCon) =
## Propagates only the dependent parameters of a concept constraint. The
## concept itself and its private matcher bindings must remain unbound so
## that independent constraints using the same concept don't get coupled.
if invocation != nil and invocation.kind == tyGenericInvocation:
let genBody = invocation.base
assert genBody.kind == tyGenericBody
for i in FirstGenericParamAt ..< invocation.kidsLen:
if lookup(bindings, invocation[i]) == nil:
let boundValue = m.bindings.lookup(genBody[i - 1])
if boundValue != nil:
bindings.put(invocation[i], resolvedBinding(c, boundValue, m))
proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool =
m.bindings = m.bindings.newTypeMapLayer()
if invocation != nil and invocation.kind == tyGenericInst:
@@ -638,11 +616,8 @@ proc processConcept(c: PContext; concpt, invocation: PType, bindings: var Layere
if invocation[i].kind != tyVoid:
bindParam(c, m, genericBody[i-1], invocation[i])
result = conceptMatchNode(c, concpt.conceptBody, m)
if result:
if mfDontBind notin m.flags:
fixBindings(c, bindings, concpt, invocation, m)
elif mfBindGenericParam in m.flags:
fixConstraintBindings(c, bindings, invocation, m)
if result and mfDontBind notin m.flags:
fixBindings(bindings, concpt, invocation, m)
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable; invocation: PType, flags: set[MatchFlags] = {}): bool =
## Entry point from sigmatch. 'concpt' is the concept we try to match (here still a PType but

File diff suppressed because it is too large Load Diff

View File

@@ -454,7 +454,7 @@ proc gen(c: var Con; n: PNode) =
of nkPragmaBlock: gen(c, n.lastSon)
of nkDiscardStmt, nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString:
gen(c, n[0])
of nkConv, nkExprColonExpr, nkExprEqExpr, PathKinds1:
of nkConv, nkExprColonExpr, nkExprEqExpr, nkCast, PathKinds1:
gen(c, n[1])
of nkVarSection, nkLetSection: genVarSection(c, n)
of nkDefer: raiseAssert "dfa construction pass requires the elimination of 'defer'"

View File

@@ -148,7 +148,7 @@ proc cmpDecimalsIgnoreCase(a, b: string): int =
limitB = iB
while limitA < aLen and isDigit(a[limitA]): inc limitA
while limitB < bLen and isDigit(b[limitB]): inc limitB
var pos = max(limitA-iA, limitB-iB)
var pos = max(limitA-iA, limitB-iA)
while pos > 0:
if limitA-pos < iA: # digit in `a` is 0 effectively
result = ord('0') - ord(b[limitB-pos])
@@ -425,6 +425,12 @@ template dispA(conf: ConfigRef; dest: var string, xml, tex: string,
if not conf.isLatexCmd: dest.addf(xml, args)
else: dest.addf(tex, args)
proc getVarIdx(varnames: openArray[string], id: string): int =
for i in 0..high(varnames):
if cmpIgnoreStyle(varnames[i], id) == 0:
return i
result = -1
proc genComment(d: PDoc, n: PNode): PRstNode =
if n.comment.len > 0:
if optDocRaw in d.conf.globalOptions:
@@ -534,11 +540,10 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var string;
elif s != nil and s.kind in {skType, skVar, skLet, skConst} and
sfExported in s.flags and s.owner != nil and
belongsToProjectPackage(d.conf, s.owner) and d.target == outHtml:
let href = (if d.module == s.owner: ""
else: externalDep(d, s.owner).changeFileExt("html")
) & "#" & literal
result.addf "<a href=\"$1\"><span class=\"Identifier\">$2</span></a>",
[href, escLit]
let external = externalDep(d, s.owner)
result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
[changeFileExt(external, "html"), literal,
escLit]
else:
dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
"\\spanIdentifier{$1}", [escLit])

View File

@@ -29,6 +29,7 @@ proc shouldProcess(g: PGen): bool =
template closeImpl(body: untyped) {.dirty.} =
var g = PGen(p)
let useWarning = sfMainModule notin g.module.flags
let groupedToc = true
if shouldProcess(g):
finishGenerateDoc(g.doc)
body
@@ -40,7 +41,7 @@ template closeImpl(body: untyped) {.dirty.} =
proc closeDoc*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
result = nil
closeImpl:
writeOutput(g.doc, useWarning, true)
writeOutput(g.doc, useWarning, groupedToc)
proc closeJson*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
result = nil

View File

@@ -14,7 +14,7 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
res.typ = getSysType(g, info, tyString)
result.typ = newType(tyProc, idgen, result)
result.typ = newType(tyProc, idgen, t.owner)
result.typ.n = newNodeI(nkFormalParams, info)
rawAddSon(result.typ, res.typ)
result.typ.n.add newNodeI(nkEffectList, info)
@@ -48,4 +48,65 @@ 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
of nkSym:
result = nil
of nkElse, nkOfBranch:
result = searchObjCaseImpl(obj.lastSon, field)
else:
if obj.kind == nkRecCase and obj[0].kind == nkSym and obj[0].sym == field:
result = obj
else:
result = nil
for x in obj:
result = searchObjCaseImpl(x, field)
if result != nil: break
proc searchObjCase(t: PType; field: PSym): PNode =
result = searchObjCaseImpl(t.n, field)
if result == nil and t.baseClass != nil:
result = searchObjCase(t.baseClass.skipTypes({tyAlias, tyGenericInst, tyRef, tyPtr}), field)
doAssert result != nil
proc genCaseObjDiscMapping*(t: PType; field: PSym; info: TLineInfo; g: ModuleGraph; idgen: IdGenerator): PSym =
result = newSym(skProc, getIdent(g.cache, "objDiscMapping"), idgen, t.owner, info)
let dest = newSym(skParam, getIdent(g.cache, "e"), idgen, result, info)
dest.typ = field.typ
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
res.typ = getSysType(g, info, tyUInt8)
result.typ = newType(tyProc, idgen, t.owner)
result.typ.n = newNodeI(nkFormalParams, info)
rawAddSon(result.typ, res.typ)
result.typ.n.add newNodeI(nkEffectList, info)
result.typ.addParam dest
var body = newNodeI(nkStmtList, info)
var caseStmt = newNodeI(nkCaseStmt, info)
caseStmt.add(newSymNode dest)
let subObj = searchObjCase(t, field)
for i in 1..<subObj.len:
let ofBranch = subObj[i]
var newBranch = newNodeI(ofBranch.kind, ofBranch.info)
for j in 0..<ofBranch.len-1:
newBranch.add ofBranch[j]
newBranch.add newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res), newIntNode(nkInt8Lit, i)))
caseStmt.add newBranch
body.add(caseStmt)
var n = newNodeI(nkProcDef, info, bodyPos+2)
for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)
n[namePos] = newSymNode(result)
n[paramsPos] = result.typ.n
n[bodyPos] = body
n[resultPos] = newSymNode(res)
result.ast = n
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}

View File

@@ -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, skConst, skTemp, skForVar}: # guard immutable variables
if n.sym.kind in {skLet, skTemp, skForVar}:
result = true
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
abstractInst).kind notin {tyVar}:

View File

@@ -1473,8 +1473,6 @@ proc genFlags*(s: set[TNodeFlag]; dest: var string) =
of nfSkipFieldChecking: dest.add "s0"
of nfDisabledOpenSym: dest.add "d3"
of nfLazyType: dest.add "l1"
of nfLazyBody: discard # process-local placeholder; never serialized
of nfBroadcast: dest.add "v"
proc parse*(t: typedesc[TNodeFlag]; s: string): set[TNodeFlag] =
@@ -1535,7 +1533,6 @@ proc parse*(t: typedesc[TNodeFlag]; s: string): set[TNodeFlag] =
inc i
else: result.incl nfSem
of 't': result.incl nfTransf
of 'v': result.incl nfBroadcast
of 'w': result.incl nfFirstWrite
else: discard
inc i

View File

@@ -14,82 +14,13 @@
import ".." / [ast, modulegraphs, trees, extccomp, btrees,
msgs, lineinfos, pathutils, options, cgmeth]
import std/[tables, os, strutils, syncio]
import std/tables
when defined(nimPreviewSlimSystem):
import std/assertions
const BackendActionsExt* = ".cflags"
## Sidecar written by a module's `cg` stage next to its `.c`, carrying the C
## compile/link directives that module's `{.passL.}`/`{.compile.}`/… pragmas
## recorded. See `writeBackendActions`.
proc writeBackendActions*(g: ModuleGraph; module: PSym; list: PNode;
outfile: string) =
## Serialize the backend-relevant replay actions of ONE module to `outfile`,
## one tab-separated action per line.
##
## The `link` stage used to recover these by loading the whole import closure
## as `PrecompiledModule`s and re-running `replayBackendActions` over each —
## a 3.7s whole-program graph load, per link, purely to recover a handful of
## strings and the modules' `.c` paths. The producing `cg` process already has
## them in hand, so it writes them down instead and `link` reads them back
## (`applyBackendActions`). Written unconditionally, even when empty: it is a
## declared nifmake output of the `cg` rule, and a missing output re-fires the
## rule for ever.
##
## `localpassc` needs the module's own source path, which only the writer can
## resolve, so it is baked in here as a third field.
var content = ""
if list != nil:
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 and n[3].kind == nkStrLit:
content.add "compile\t" & n[1].strVal & "\t" & n[2].strVal & "\t" &
n[3].strVal & "\n"
of "link", "passl", "passc", "cppdefine":
content.add n[0].strVal & "\t" & n[1].strVal & "\n"
of "localpassc":
content.add "localpassc\t" & n[1].strVal & "\t" &
toFullPathConsiderDirty(g.config, module.info.fileIndex).string & "\n"
else: discard
writeFile(outfile, content)
proc applyBackendActions*(g: ModuleGraph; infile: string) =
## Apply one module's recorded C directives (see `writeBackendActions`). The
## `link` stage's replacement for loading that module and replaying its AST.
if not fileExists(infile): return
for line in lines(infile):
if line.len == 0: continue
let f = line.split('\t')
case f[0]
of "compile":
if f.len == 4:
let cname = AbsoluteFile f[1]
var cf = Cfile(nimname: splitFile(cname).name, cname: cname,
obj: AbsoluteFile f[2],
flags: {CfileFlag.External}, customArgs: f[3])
extccomp.addExternalFileToCompile(g.config, cf)
of "link":
if f.len == 2: extccomp.addExternalFileToLink(g.config, AbsoluteFile f[1])
of "passl":
if f.len == 2: extccomp.addLinkOption(g.config, f[1])
of "passc":
if f.len == 2: extccomp.addCompileOption(g.config, f[1])
of "localpassc":
if f.len == 3: extccomp.addLocalCompileOption(g.config, f[1], AbsoluteFile f[2])
of "cppdefine":
if f.len == 2: options.cppDefine(g.config, f[1])
else: discard
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.
proc replayStateChanges*(module: PSym; g: ModuleGraph) =
let list = module.ast
assert list != nil
assert list.kind == nkStmtList
for n in list:
@@ -133,9 +64,8 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
g.cacheTables[destKey] = initBTree[string, PNode]()
if not contains(g.cacheTables[destKey], key):
g.cacheTables[destKey].add(key, val)
# 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.
else:
internalError(g.config, n.info, "key already exists: " & key)
of "incl":
let destKey = n[1].strVal
let val = n[2]
@@ -156,37 +86,3 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
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

View File

@@ -1,307 +0,0 @@
#
#
# 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, strutils]
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`/`track`) in favour of `icconfig`
# and the explicit output path. Every switch must land BEFORE the project
# file, because anything after the project is swallowed into
# `config.arguments` by `cmdLineRest` (and a non-empty `arguments` without
# `--run` is a hard error). Callers may legitimately put switches after the
# project — `nim track PROJ --def:...` — so we re-order rather than replay
# verbatim: all `-`-prefixed switches first (in encounter order), then the
# non-switch project token(s). The producer re-reads `nim.cfg` itself.
var pargs = @["icconfig", "--icConfigOut:" & outPath]
# The command token is dropped below, so `nim cpp --ic:on` would hand the
# producer a C-backend config: name the backend explicitly. (`nim ic
# --backend:cpp` already carries the switch; the duplicate is harmless.)
if conf.backend != backendInvalid:
pargs.add "--backend:" & $conf.backend
var rest: seq[string] = @[]
var droppedCmd = false
for a in commandLineParams():
if a.len == 0: continue
if a[0] == '-':
# `--run`/`-r` must not reach the producer: it only serialises the
# resolved config, has no output binary, and `nim.nim`'s run step asserts
# on the empty `outFile` (`nim cpp --ic:on -r foo.nim`).
var name = ""
var i = 1
if i < a.len and a[i] == '-': inc i
while i < a.len and a[i] notin {':', '='}:
name.add a[i]
inc i
if normalize(name) in ["r", "run"]: continue
pargs.add a
elif not droppedCmd:
droppedCmd = true # drop the original command token (`ic`/`track`)
else:
rest.add a # project file (and any further non-switch tokens) go last
for a in rest: 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

View File

@@ -1,55 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Nim's OWN module-suffix, replacing nimony's `gear2/modnames.moduleSuffix`.
##
## nimony's version hashes a path made RELATIVE to `getCurrentDir()` (or the
## shortest search-path-relative form), so the produced suffix depends on the
## current working directory AND the searchPath set. Under `nim ic` the
## DISCOVERY pass (`deps.nim`, in the driver process) and the COMPILE pass
## (`nifgen`/`typekeys`, in a child `nim m` process) can run with different CWDs
## or `--path` sets, so the SAME file hashes to two different suffixes: e.g.
## `std/staticos` became `sta5rk8sn1` at discovery but `sta4c0qxk` at compile, so
## every importer waited forever for a `.s.bif` that was actually written under
## the other name — a cold `nim ic` build (of anything pulling in `std/os`, whose
## `oscommon` does `from std/staticos import PathComponent`) never converged.
##
## Hashing the CANONICAL ABSOLUTE path makes the suffix a pure function of the
## file, identical across every process and call site. The base-name prefix +
## base-36 `uhash` layout is kept byte-for-byte compatible with the old scheme so
## nothing but the hashed string changes.
import std/os
import "../dist/nimony/src/lib" / tinyhashes
const
PrefixLen = 3 # keep it short: the suffix ends up in every mangled C name
Base36 = "0123456789abcdefghijklmnopqrstuvwxyz"
proc moduleSuffix*(path: string; searchPaths: openArray[string]): string =
## `searchPaths` is accepted for signature-compatibility with the replaced
## `modnames.moduleSuffix` but is deliberately IGNORED — the suffix must not
## depend on the search-path set or the CWD (see the module doc).
# Absolute inputs (the norm at every call site: `toFullPath`/`projectFull`)
# pass straight through `normalizedPath` with no `getCurrentDir` involvement;
# a stray relative path is made absolute against the CWD only as a fallback.
var f = path
if not isAbsolute(f):
try: f = absolutePath(f)
except CatchableError: discard
f = normalizedPath(f)
let m = splitFile(f).name
var id = uhash(f)
result = newStringOfCap(10)
for i in 0 ..< min(m.len, PrefixLen):
result.add m[i]
# base-36 of the hash, low digit first (order is irrelevant for identity).
while id > 0'u32:
result.add Base36[int(id mod 36'u32)]
id = id div 36'u32

View File

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

View File

@@ -1,111 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Opt-in instrumentation for the IC backend, enabled with `-d:icBNodeProf`.
## Off, every template below is `discard` and nothing is linked in.
##
## It lives in its own module with NO compiler imports so that any stage can
## use it without creating a cycle — `ast2nif` for the loader, `nifbackend` for
## the stage phases, `cgen` for what happens per routine.
##
## Each backend process appends ONE line to `$NIM_IC_BNODE_PROF` at exit (or to
## stderr when that is unset), because a `--ic:on` build fans out a process per
## module per stage and interleaved writes would tear. Use `-d:icNoParallel`
## when the numbers need to be attributable to a particular module.
##
## Counts are for volume, timings for cost, and the two answer different
## questions: a call count alone once pointed at the wrong accessor (700k calls
## worth 8ms) while the real cost was 259k `info` resolutions worth 1.36s.
when defined(icBNodeProf):
import std / [envvars, exitprocs, syncio, monotimes]
from std / times import inNanoseconds
type
ProfSlot* = enum
pTyp, pIfaceExported, pIfaceHidden, pIfaceModules,
pTopNodes, pExportSyms, pPeekKind, pPeekFallback, pPeekLoaded,
pTopToolingSkip
TimeSlot* = enum
tLoadClosure, tModuleId, tBifLoad, tPosIndex, tTopLevel, tInterfTables,
tTransform, tGenBody, tExportBranch, tResolveSym, tEnumFields,
# Coarse phases, added to find where a backend process spends the time
# that none of the slots above account for. `tStage` is the whole stage
# body, so `Process - tStage` is everything before it: exec, the Nim
# runtime, config replay, `registerNifSuffix`/graph setup.
tStage,
tLowerOwned, tLowerHooks, tLowerWrite,
tCgGen, tCgInit, tCgFinish, tCgWrite,
tMergeStage, tEmitRender, tLinkStage,
# `nim m` (the frontend): the sem pass as a whole, and writing the module's
# `.s.bif`. `Stage - WriteNif - <the loading slots>` is then sem proper.
tWriteNif,
# `processTopLevel`'s branches: which part of a module HEADER costs what.
tTopReplay, tTopLogOps, tTopOffers, tTopStmts
let procStart = getMonoTime()
## Set when this module initialises, i.e. essentially at process start, so
## the dump can report total process wall time and the startup share can be
## derived as `Process - Stage`.
var profStageName* = "frontend"
## Which invocation this is: the backend stage name, or "frontend" for a
## `nim m` process, which arms the profiler through ast2nif but never enters
## a backend stage. Without it the `Process - Stage` startup figure is
## meaningless — 204 frontend processes' whole runtime lands in it.
var profCounts: array[ProfSlot, int]
var profNanos: array[TimeSlot, int64]
var profStart: array[TimeSlot, MonoTime]
var profArmed = false
proc profDump() =
var line = "BNODEPROF stage=" & profStageName
for s in ProfSlot: line.add " " & ($s)[1..^1] & "=" & $profCounts[s]
for s in TimeSlot: line.add " " & ($s)[1..^1] & "ms=" & $(profNanos[s] div 1_000_000)
line.add " Processms=" & $((getMonoTime() - procStart).inNanoseconds div 1_000_000)
let f = getEnv("NIM_IC_BNODE_PROF")
if f.len > 0:
let h = open(f, fmAppend)
h.writeLine line
h.close()
else:
stderr.writeLine line
template armProf() =
if not profArmed:
profArmed = true
addExitProc profDump
template prof*(s: ProfSlot; n = 1) =
armProf()
inc profCounts[s], n
template icProfStart*(s: TimeSlot) =
armProf()
profStart[s] = getMonoTime()
template icProfStop*(s: TimeSlot) =
profNanos[s] += (getMonoTime() - profStart[s]).inNanoseconds
template timed*(s: TimeSlot; body: untyped) =
## Leaf timing. NOT re-entrant, and the phase slots are not disjoint —
## `tTransform` contains body materialization. Read them as nested, not
## additive.
##
## Arms the dump like `prof`/`icProfStart` do. It did not, and so a process
## whose ONLY instrumentation is a `timed` never reported at all: the
## `merge`, `emit` and `link` stages were silently absent from every profile.
armProf()
let t0 = getMonoTime()
body
profNanos[s] += (getMonoTime() - t0).inNanoseconds
else:
template prof*(s: untyped; n = 1) = discard
template icProfStart*(s: untyped) = discard
template icProfStop*(s: untyped) = discard
template timed*(s: untyped; body: untyped) = body

View File

@@ -1,279 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## NIF-based goto-definition / find-all-usages for `nim track`.
##
## This is the mainline-Nim port of nimony's `idetools.nim`. It answers a
## `--def:FILE,LINE,COL` / `--usages:FILE,LINE,COL` query by *scanning the
## `.s.bif` files* (binary NIF, see `dist/nimony/src/lib/bif.nim`) that the
## preceding `nim ic` frontend (`nim track`) emitted into the nimcache directory
## — NOT by re-running sem. NIF distinguishes a definition (`SymbolDef` token) from a use
## (`Symbol` token) syntactically, so goto-def / find-uses become plain token
## scans over type-checked NIF, which is more reliable than the classic PSym
## engine because generics and macros are type-checked in the NIF too.
##
## Two passes (mirroring nimony's `usages`):
## 1. Load the queried module's `.s.bif` and find the `Symbol`/`SymbolDef`
## token whose line info + identifier length contains `conf.m.trackPos`.
## That yields the mangled symbol NAME and whether it is global (>= 2 dots).
## 2. `--usages`: emit every `Symbol` (use) token; `--def`: every `SymbolDef`.
## A global symbol is scanned across every module `.s.bif`; a local one only
## within the queried module.
##
## IMPORTANT porting note: `bif.load` mints FRESH per-file pools, so a `SymId`
## from module A's buffer is meaningless in module B's. The cross-module match is
## therefore by the mangled NAME string, never by `SymId` (nimony can compare ids
## because it parses every text NIF into one shared global pool; we cannot).
import std / [os, strutils, sets]
import options, msgs, pathutils
import lineinfos as astli
import ast2nif # toNifFilename
from deps import includerSbifs # deps-guided include-file lookup
import "../dist/nimony/src/lib/nifcore"
from "../dist/nimony/src/lib" / bif import load, BifModule, containsSym
proc identLen(name: string): int =
## Length of the displayed identifier: the run before the first `.` of a
## mangled NIF name (`ident.disamb[.moduleSuffix]`). Bounds the column match.
let d = name.find('.')
result = if d < 0: name.len else: d
proc isGlobalName(name: string): bool =
## A global symbol carries `ident.disamb.moduleSuffix` (>= 2 dots); a local at
## most `ident.disamb` (<= 1 dot). `moduleSuffix` is a dot-free hash, so a raw
## dot count is equivalent to nifbuilder's suffix-compressed test for our use.
var dots = 0
for i in 1 ..< name.len:
if name[i] == '.': inc dots
result = dots >= 2
proc posMatch(c: Cursor; conf: ConfigRef; target: TLineInfo; tokenLen: int): bool =
## True when `target` (the queried position) falls within the identifier span
## of the Symbol/SymbolDef token at `c`. Mirrors nimony's `lineInfoMatch`; the
## filename is resolved through the loaded buffer's own pool (fresh per file),
## then mapped to a `FileIndex` exactly like `ast2nif.oldLineInfo`.
let li = rawLineInfo(c)
if not li.isValid: return false
if li.line.int != target.line.int: return false
let f = fileInfoIdx(conf, AbsoluteFile lineInfoFile(c))
if f != target.fileIndex: return false
if target.col.int < li.col.int: return false
if target.col.int > li.col.int + tokenLen: return false
result = true
const sep = '\t'
proc formatSuggest(s: Suggest): string =
## Reproduce `suggest.$Suggest` for the `ideDef`/`ideUse` sections without
## importing `suggest` (which would create an import cycle). Layout:
## `section⭾symkind⭾qualifiedPath⭾forth⭾filePath⭾line⭾column⭾⭾quality`.
## symkind is always `skUnknown` here — the raw NIF scan has no PSym to give a
## real kind (like nimony's `foundSymbol`, which leaves it empty).
result = $s.section
result.add sep
result.add "skUnknown"
result.add sep
if s.qualifiedPath.len != 0:
result.add s.qualifiedPath.join(".")
result.add sep
result.add s.forth
result.add sep
result.add s.filePath
result.add sep
result.add $s.line
result.add sep
result.add $s.column
result.add sep # empty doc field (docgen is off outside nimsuggest)
if s.version == 0 or s.version == 3:
result.add sep
result.add $s.quality
proc emit(conf: ConfigRef; c: Cursor; section: IdeCmd; name: string;
seen: var HashSet[string]) =
## Report one hit as a nimsuggest-compatible result (routed through the
## structured-output hook / `--stdout`). We only have the mangled name + line
## info from the raw NIF, so symkind/type are left empty — like nimony's
## `foundSymbol`. `seen` deduplicates: the same source location can back
## several NIF `Symbol` tokens (e.g. a call argument re-emitted in a lowered
## form), which must surface as one hit.
let li = rawLineInfo(c)
if not li.isValid: return
let key = $section.int & ":" & lineInfoFile(c) & ":" & $li.line.int & ":" & $li.col.int
if seen.containsOrIncl(key):
return # already reported this location for this section
let s = Suggest(section: section,
qualifiedPath: @[name[0 ..< identLen(name)]],
filePath: lineInfoFile(c),
line: li.line.int,
column: li.col.int,
tokenLen: identLen(name),
forth: "",
symkind: 0'u8,
quality: 100,
version: conf.suggestVersion)
if conf.suggestionResultHook != nil:
conf.suggestionResultHook(s)
else:
conf.suggestWriteln(formatSuggest(s))
proc tokenSymId(c: Cursor): SymId {.inline.} =
## SymId (in the cursor's own per-file pool) of a `Symbol`/`SymbolDef` token,
## or `SymId(0)` for an inline-encoded one — which is never our search target:
## a mangled name (`ident.disamb.suffix`) is always longer than
## `StrInlineMaxLen`, so every occurrence of the symbol we look for is stored by
## pool id, decoded here with a shift and no string materialization.
if isInlineLit(c): SymId(0) else: SymId(combinedPayload(c) shr 1)
template symMatches(c: Cursor): bool =
## True when the token at `c` is the searched symbol. The fast path is a pure
## integer compare against `targetSym` (the symbol's id in THIS module's pool,
## resolved once per file by the caller). `targetSym == 0` means the name is not
## representable as a pool id (a rare <=3-byte local): fall back to a string
## compare, correct for both inline and pooled encodings.
(if targetSym != SymId(0): tokenSymId(c) == targetSym else: symName(c) == targetName)
proc scanUses(conf: ConfigRef; m: var BifModule; targetSym: SymId; targetName: string;
seen: var HashSet[string]) =
## `--usages`: report every `Symbol` (use) occurrence with valid line info.
if m.buf.len == 0: return
var c = m.buf.beginRead()
while c.hasMore:
if c.kind == Symbol and symMatches(c) and rawLineInfo(c).isValid:
emit(conf, c, ideUse, targetName, seen)
inc c
c.endRead()
proc scanDef(conf: ConfigRef; m: var BifModule; targetSym: SymId; targetName: string;
seen: var HashSet[string]) =
## `--def`: report the declaration of the target symbol if this module owns it
## (has its `SymbolDef`). The `SymbolDef` token itself carries no line info; the
## declaration location lives on the *enclosing tag* (e.g. `(sd @file:line:col`,
## like `bif.buildIndex`'s `mostRecentTagPos`). When that tag has no line info
## either, fall back to the declaration-site `Symbol` occurrence — but only in
## the owning module, so a plain user of the symbol is never reported as a def.
if m.buf.len == 0: return
var c = m.buf.beginRead()
var mostRecentTagPos = 0
var sawDef = false
var emitted = false
var fallbackPos = -1
while c.hasMore:
case c.kind
of TagLit:
mostRecentTagPos = cursorToPosition(m.buf, c)
inc c
of SymbolDef:
if symMatches(c):
sawDef = true
var tc = cursorAt(m.buf, mostRecentTagPos)
if rawLineInfo(tc).isValid:
emit(conf, tc, ideDef, targetName, seen)
emitted = true
tc.endRead()
inc c
of Symbol:
if fallbackPos < 0 and symMatches(c) and rawLineInfo(c).isValid:
fallbackPos = cursorToPosition(m.buf, c)
inc c
else:
inc c
c.endRead()
if sawDef and not emitted and fallbackPos >= 0:
var fc = cursorAt(m.buf, fallbackPos)
emit(conf, fc, ideDef, targetName, seen)
fc.endRead()
proc scanBuf(conf: ConfigRef; m: var BifModule; section: IdeCmd;
targetSym: SymId; targetName: string; seen: var HashSet[string]) =
## Emit hits for the target symbol in `m` per the query kind. `ideDus`
## (`--defusages`) reports both the definition and every usage.
if section in {ideDef, ideDus}:
scanDef(conf, m, targetSym, targetName, seen)
if section in {ideUse, ideDus}:
scanUses(conf, m, targetSym, targetName, seen)
proc findPos(conf: ConfigRef; m: var BifModule; target: TLineInfo;
foundName: var string): bool =
## Scan `m` for the `Symbol`/`SymbolDef` token covering the queried position
## `target` and set `foundName` to its mangled name. Returns true on a hit.
if m.buf.len == 0: return false
var c = m.buf.beginRead()
result = false
while c.hasMore:
let k = c.kind
if k == Symbol or k == SymbolDef:
let nm = symName(c)
if posMatch(c, conf, target, identLen(nm)):
foundName = nm
result = true
break
inc c
c.endRead()
proc runIdeQuery*(conf: ConfigRef) =
## Entry point: called from `main.nim` after `commandCheck` when a
## `--def`/`--usages` query is active. Assumes the check just emitted the
## project's `.s.bif` files into `getNimcacheDir(conf)`.
let section = conf.ideCmd
if section notin {ideDef, ideUse, ideDus}: return
let target = conf.m.trackPos
if target.fileIndex.int32 < 0: return
# Pass 1: position -> symbol. Try the queried file's own module bif first (the
# fast path when the position is inside a real module). An include file has no
# module bif of its own — its tokens live in the *including* module's bif with
# include-file line info — so when the direct lookup misses, consult the
# `.deps.nif` preludes (`includerSbifs`) to load only the module(s) that
# include the queried file (directly or transitively), never every bif in the
# nimcache. `ownerFile` is the bif that owns the hit.
let modFile = toNifFilename(conf, target.fileIndex)
var foundName = ""
var ownerFile = ""
if fileExists(modFile):
var qm = load(modFile)
if findPos(conf, qm, target, foundName):
ownerFile = modFile
if foundName.len == 0:
for cand in includerSbifs(conf, toFullPath(conf, target.fileIndex).AbsoluteFile):
if cand == modFile: continue
var m = load(cand)
if findPos(conf, m, target, foundName):
ownerFile = cand
break
if foundName.len == 0: return
# Pass 2: emit definition / usages. `seen` spans every module so a location is
# reported once even when scanned across the whole nimcache.
#
# Cross-file matching is by SymId, not by decoding every token's name. Two
# filters keep it cheap:
# 1. `bif.containsSym` — a sym-table-only probe that reads just the small
# trailing pools, NOT the token block or any `BiTable`. A module that never
# references the symbol is rejected here without a full `load` (no pools
# built, no token block mapped) — so a query whose symbol lives in a few
# modules no longer pays to load the whole nimcache.
# 2. For a module that does contain it, `bif.load` mints a fresh per-file pool,
# so the name is resolved to THIS file's SymId once via `getKeyId`; the scan
# then compares integer ids per token instead of materializing a string for
# each (see `symMatches`).
var seen = initHashSet[string]()
if isGlobalName(foundName):
for f in walkFiles((getNimcacheDir(conf).string) / "*.s.bif"):
if not containsSym(f, foundName): continue
var m = load(f)
let tid = m.buf.pool.syms.getKeyId(foundName)
if tid != SymId(0):
scanBuf(conf, m, section, tid, foundName, seen)
else:
# Local symbol: its mangled name is not unique across modules, so restrict
# the scan to the module it lives in (the one that owns the queried position).
var qm = load(ownerFile)
let tid = qm.buf.pool.syms.getKeyId(foundName)
scanBuf(conf, qm, section, tid, foundName, seen)

View File

@@ -13,7 +13,7 @@ import
ast, msgs, options, idents, lookups,
semdata, modulepaths, sigmatch, lineinfos,
modulegraphs, wordrecg
from std/strutils import `%`, startsWith, replace
from std/strutils import `%`, startsWith
from std/sequtils import addUnique
import std/[sets, tables, intsets]
@@ -304,9 +304,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
var prefix = ""
if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; "
message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated")
let moduleNameNorm = getModuleName(c.config, n).replace("\\", "/")
if belongsToStdlib(c.graph, result) and not startsWith(moduleNameNorm, stdPrefix) and
not startsWith(moduleNameNorm, "system/") and not startsWith(moduleNameNorm, "packages/"):
let moduleName = getModuleName(c.config, n)
if belongsToStdlib(c.graph, result) and not startsWith(moduleName, stdPrefix) and
not startsWith(moduleName, "system/") and not startsWith(moduleName, "packages/"):
message(c.config, n.info, warnStdPrefix, realModule.name.s)
proc suggestMod(n: PNode; s: PSym) =

View File

@@ -24,7 +24,7 @@ import std/[strtabs, tables, strutils, intsets]
when defined(nimPreviewSlimSystem):
import std/assertions
from trees import exprStructuralEquivalent, getRoot, isCursor, whichPragma, getPotentialWrites
from trees import exprStructuralEquivalent, getRoot, whichPragma, getPotentialWrites
type
Con = object
@@ -72,11 +72,9 @@ proc hasDestructor(c: Con; t: PType): bool {.inline.} =
if not result and c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
assert(not containsGarbageCollectedRef(t))
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo; needsInit: bool): PNode =
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo): PNode =
let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.idgen, c.owner, info)
sym.typ = typ
if not needsInit:
sym.incl sfNoInit
s.vars.add(sym)
result = newSymNode(sym)
@@ -173,12 +171,24 @@ template hasDestructorOrAsgn(c: var Con, typ: PType): bool =
proc isLastRead(n: PNode; c: var Con; s: var Scope): bool =
if not hasDestructorOrAsgn(c, n.typ): return true
let m = skipConvDfa(n)
result = isLastReadImpl(n, c, s)
proc isFirstWrite(n: PNode; c: var Con): bool =
let m = skipConvDfa(n)
result = nfFirstWrite in m.flags
proc isCursor(n: PNode): bool =
case n.kind
of nkSym:
sfCursor in n.sym.flags
of nkDotExpr:
isCursor(n[1])
of nkCheckedFieldExpr:
isCursor(n[0])
else:
false
template isFullyUnpackedTuple(n: PNode): bool =
## we move out all elements of unpacked tuples,
## hence unpacked tuples themselves don't need to be destroyed
@@ -233,18 +243,6 @@ proc genOp(c: var Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode
let canon = c.graph.canonTypes.getOrDefault(h)
if canon != nil:
op = getAttachedOp(c.graph, canon, kind)
if op == nil or op.ast.isGenericRoutine:
# IC: injectDestructorCalls is demand-driven and runs HERE (cg), not in the
# `lower` stage, so a structural, env-agnostic op the lower stage never had
# reason to serialize — most often a closure PROC type's `=destroy`/`=sink`
# (which act on the `(ClP_0, ClE_0)` tuple, NOT the concrete env) — must be
# lifted on demand, exactly as the lazy path's cg does. This is safe now:
# closure-env identity resolves via `attachedOps[itemId]`/env-erased typeKey,
# env objects load complete, and atomicRefOp's type-erased path covers any
# still-incomplete env (so the lift never walks a nil field).
excl t.flagsImpl, tfCheckedForDestructor
createTypeBoundOps(c.graph, nil, t, dest.info, c.idgen)
op = getAttachedOp(c.graph, t, kind)
if op == nil:
#echo dest.typ.id
globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] &
@@ -304,7 +302,7 @@ proc genSink(c: var Con; s: var Scope; dest, ri: PNode; flags: set[MoveOrCopyFla
if deepAliases(dest, ri):
# consider: x = x + y, it is wrong to destroy the destination first!
# tmp to support self assignments
let tmp = c.getTemp(s, dest.typ, dest.info, needsInit = false)
let tmp = c.getTemp(s, dest.typ, dest.info)
result = newTree(nkStmtList, newTree(nkFastAsgn, tmp, dest), newTree(nkFastAsgn, dest, ri),
c.genDestroy(tmp))
else:
@@ -373,7 +371,7 @@ proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode =
# but fields within active case branch might need destruction
# tmp to support self assignments
let tmp = c.getTemp(s, n[1].typ, n.info, needsInit = false)
let tmp = c.getTemp(s, n[1].typ, n.info)
result = newTree(nkStmtList)
result.add newTree(nkFastAsgn, tmp, p(n[1], c, s, consumed))
@@ -423,20 +421,6 @@ proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
result.add(newSymNode(createMagic(c.graph, c.idgen, "default", mDefault)))
result.typ = t
proc stabilizeBracketIndex(n: PNode; c: var Con; body: var PNode): PNode =
## Evaluate a side-effecting index once and return the stable access.
doAssert n.kind == nkBracketExpr and not isAtom(n[1])
let temp = newSym(skLet, getIdent(c.graph.cache, "bracketTmp"), c.idgen,
c.owner, n[1].info)
temp.typ = n[1].typ
let tempAsNode = newSymNode(temp)
body.add newTree(nkLetSection, n[1].info,
newTree(nkIdentDefs, tempAsNode,
newNodeI(nkEmpty, tempAsNode.info), n[1]))
result = copyNode(n)
result.add n[0]
result.add tempAsNode
proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
# generate: (let tmp = v; reset(v); tmp)
if (not hasDestructor(c, n.typ)) and c.inEnsureMove == 0:
@@ -448,10 +432,6 @@ proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
else:
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
var n = n
if n.kind == nkBracketExpr and not isAtom(n[1]):
n = stabilizeBracketIndex(n, c, result)
var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.idgen, c.owner, n.info)
temp.typ = n.typ
var v = newNodeI(nkLetSection, n.info)
@@ -478,52 +458,51 @@ proc isCapturedVar(n: PNode): bool =
proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
let nTyp = n.typ.skipTypes(tyUserTypeClasses)
if not hasDestructorOrAsgn(c, nTyp):
# Non-managed (plain-old-data) type: no ownership transfer is needed.
# Return the expression directly — no temp required.
if hasDestructorOrAsgn(c, nTyp):
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
let tmp = c.getTemp(s, nTyp, n.info)
let typ = nTyp.skipTypes({tyGenericInst, tyAlias, tySink})
let op = getAttachedOp(c.graph, typ, attachedDup)
if op != nil and tfHasOwned notin typ.flags:
if sfError in op.flags:
c.checkForErrorPragma(nTyp, n, "=dup")
else:
let copyOp = getAttachedOp(c.graph, typ, attachedAsgn)
if copyOp != nil and sfError in copyOp.flags and
sfOverridden notin op.flags:
c.checkForErrorPragma(nTyp, n, "=dup", inferredFromCopy = true)
let src = p(n, c, s, normal)
var newCall = newTreeIT(nkCall, src.info, src.typ,
newSymNode(op),
src)
c.finishCopy(newCall, n, {}, isFromSink = true)
result.add newTreeI(nkFastAsgn,
src.info, tmp,
newCall
)
else:
result.add c.genWasMoved(tmp)
var m = c.genCopy(tmp, n, {})
m.add p(n, c, s, normal)
c.finishCopy(m, n, {}, isFromSink = true)
result.add m
if isLValue(n) and not isCapturedVar(n) and nTyp.skipTypes(abstractInst).kind != tyRef and c.inSpawn == 0:
message(c.graph.config, n.info, hintPerformance,
("passing '$1' to a sink parameter introduces an implicit copy; " &
"if possible, rearrange your program's control flow to prevent it") % $n)
if c.inEnsureMove > 0:
localError(c.graph.config, n.info, errFailedMove,
("cannot move '$1', passing '$1' to a sink parameter introduces an implicit copy") % $n)
# Since we know somebody will take over the produced copy, there is
# no need to destroy it.
result.add tmp
else:
if c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
assert(not containsManagedMemory(nTyp))
if nTyp.skipTypes(abstractInst).kind in {tyOpenArray, tyVarargs}:
localError(c.graph.config, n.info, "cannot create an implicit openArray copy to be passed to a sink parameter")
return p(n, c, s, normal)
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
let tmp = c.getTemp(s, nTyp, n.info, needsInit = false)
let typ = nTyp.skipTypes({tyGenericInst, tyAlias, tySink})
let op = getAttachedOp(c.graph, typ, attachedDup)
if op != nil and tfHasOwned notin typ.flags:
if sfError in op.flags:
c.checkForErrorPragma(nTyp, n, "=dup")
else:
let copyOp = getAttachedOp(c.graph, typ, attachedAsgn)
if copyOp != nil and sfError in copyOp.flags and
sfOverridden notin op.flags:
c.checkForErrorPragma(nTyp, n, "=dup", inferredFromCopy = true)
let src = p(n, c, s, normal)
var newCall = newTreeIT(nkCall, src.info, src.typ,
newSymNode(op),
src)
c.finishCopy(newCall, n, {}, isFromSink = true)
result.add newTreeI(nkFastAsgn,
src.info, tmp,
newCall
)
else:
result.add c.genWasMoved(tmp)
var m = c.genCopy(tmp, n, {})
m.add p(n, c, s, normal)
c.finishCopy(m, n, {}, isFromSink = true)
result.add m
if isLValue(n) and not isCapturedVar(n) and nTyp.skipTypes(abstractInst).kind != tyRef and c.inSpawn == 0:
message(c.graph.config, n.info, hintPerformance,
("passing '$1' to a sink parameter introduces an implicit copy; " &
"if possible, rearrange your program's control flow to prevent it") % $n)
if c.inEnsureMove > 0:
localError(c.graph.config, n.info, errFailedMove,
("cannot move '$1', passing '$1' to a sink parameter introduces an implicit copy") % $n)
# Since we know somebody will take over the produced copy, there is
# no need to destroy it.
result.add tmp
result = p(n, c, s, normal)
proc isDangerousSeq(t: PType): bool {.inline.} =
let t = t.skipTypes(abstractInst)
@@ -551,7 +530,7 @@ proc ensureDestruction(arg, orig: PNode; c: var Con; s: var Scope): PNode =
# produce temp creation for (fn, env). But we need to move 'env'?
# This was already done in the sink parameter handling logic.
result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
let tmp = c.getTemp(s, arg.typ, arg.info, true)
let tmp = c.getTemp(s, arg.typ, arg.info)
result.add c.genSink(s, tmp, arg, {IsDecl})
result.add tmp
s.final.add c.genDestroy(tmp)
@@ -630,7 +609,7 @@ template processScopeExpr(c: var Con; s: var Scope; ret: PNode, processCall: unt
# There is a possibility to do this check: s.wasMoved.len > 0 or s.final.len > 0
# later and use it to eliminate the temporary when theres no need for it, but its
# tricky because you would have to intercept moveOrCopy at a certain point
let tmp = c.getTemp(s.parent[], ret.typ, ret.info, needsInit = true)
let tmp = c.getTemp(s.parent[], ret.typ, ret.info)
tmp.sym.flags = tmpFlags
let cpy = if hasDestructor(c, ret.typ) and
ret.typ.kind notin {tyOpenArray, tyVarargs}:
@@ -791,7 +770,7 @@ proc pRaiseStmt(n: PNode, c: var Con; s: var Scope): PNode =
result = copyNode(n)
result.add call
else:
let tmp = c.getTemp(s, n[0].typ, n.info, needsInit = true)
let tmp = c.getTemp(s, n[0].typ, n.info)
var m = c.genCopyNoCheck(tmp, n[0], attachedAsgn)
m.add p(n[0], c, s, normal)
c.finishCopy(m, n[0], {}, isFromSink = false)
@@ -821,23 +800,6 @@ 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}:
@@ -1039,11 +1001,6 @@ 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 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)
result = distributeAsgn(n.kind, dest, n[1], c, s)
else:
result = copyNode(n)
result.add p(n[0], c, s, mode)
@@ -1137,11 +1094,6 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
result[i] = n[i]
of nkGotoState, nkState, nkAsmStmt:
result = n
of nkReplayAction:
# A `.rod`/NIF replay record. It only ever appears in a NIF-loaded
# module's TOP-LEVEL statements (the loader prepends the `(replay ...)`
# entries there); cgen discards it, so pass it through untouched.
result = n
else:
result = nil
internalError(c.graph.config, n.info, "cannot inject destructors to node kind: " & $n.kind)
@@ -1173,11 +1125,24 @@ proc sameLocation*(a, b: PNode): bool =
else: false
proc genFieldAccessSideEffects(c: var Con; s: var Scope; dest, ri: PNode; flags: set[MoveOrCopyFlag] = {}): PNode =
result = newNodeI(nkStmtList, ri.info)
let newAccess = stabilizeBracketIndex(ri, c, result)
let snk = c.genSink(s, dest, newAccess, flags)
result.add snk
result.add c.genWasMoved(newAccess)
# with side effects
var temp = newSym(skLet, getIdent(c.graph.cache, "bracketTmp"), c.idgen, c.owner, ri[1].info)
temp.typ = ri[1].typ
var v = newNodeI(nkLetSection, ri[1].info)
let tempAsNode = newSymNode(temp)
var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
vpart[0] = tempAsNode
vpart[1] = newNodeI(nkEmpty, tempAsNode.info)
vpart[2] = ri[1]
v.add(vpart)
var newAccess = copyNode(ri)
newAccess.add ri[0]
newAccess.add tempAsNode
var snk = c.genSink(s, dest, newAccess, flags)
result = newTree(nkStmtList, v, snk, c.genWasMoved(newAccess))
proc ownsData(c: var Con; s: var Scope; orig: PNode; flags: set[MoveOrCopyFlag]): PNode =
var n = orig
@@ -1189,7 +1154,7 @@ proc ownsData(c: var Con; s: var Scope; orig: PNode; flags: set[MoveOrCopyFlag])
break
if n.kind in nkCallKinds and n.typ != nil and hasDestructor(c, n.typ):
result = newNodeIT(nkStmtListExpr, orig.info, orig.typ)
let tmp = c.getTemp(s, n.typ, n.info, needsInit = true)
let tmp = c.getTemp(s, n.typ, n.info)
tmp.sym.flagsImpl.incl sfSingleUsedTemp
result.add newTree(nkFastAsgn, tmp, copyTree(n))
s.final.add c.genDestroy(tmp)

View File

@@ -340,6 +340,9 @@ proc `*`*(a: Int128, b: int32): Int128 =
if b < 0:
result = -result
proc `*=`(a: var Int128, b: int32) =
a = a * b
proc makeInt128(high, low: uint64): Int128 =
result = Zero
result.udata[0] = cast[uint32](low)
@@ -457,9 +460,7 @@ proc addInt128*(result: var string; value: Int128) =
var i = initialSize
var j = high(result)
while i < j:
let tmp = result[i]
result[i] = result[j]
result[j] = tmp
swap(result[i], result[j])
i += 1
j -= 1

View File

@@ -1,98 +0,0 @@
#
#
# 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

View File

@@ -34,7 +34,7 @@ import
ropes, wordrecg, renderer,
cgmeth, lowerings, sighashes, modulegraphs, lineinfos,
transf, injectdestructors, sourcemap, astmsgs, pushpoppragmas,
mangleutils, varpartitions
mangleutils
import pipelineutils
@@ -148,6 +148,11 @@ proc newGlobals(): PGlobals =
typeInfoGenerated: initIntSet()
)
proc initCompRes(): TCompRes =
result = TCompRes(address: "", res: "",
tmpLoc: "", typ: etyNone, kind: resNone
)
proc rdLoc(a: TCompRes): Rope {.inline.} =
if a.typ != etyBaseIndex:
result = a.res
@@ -589,6 +594,15 @@ proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string,
r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer]
r.kind = resExpr
template ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
var x, y, z: TCompRes
useMagic(p, magic)
gen(p, n[1], x)
gen(p, n[2], y)
gen(p, n[3], z)
r.res = frmt % [x.rdLoc, y.rdLoc, z.rdLoc]
r.kind = resExpr
template unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
# $1 binds to n[1], if $2 is present it will be substituted to a tmp of $1
useMagic(p, magic)
@@ -1168,6 +1182,7 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode; isAsmStmt = false) =
of nkStrLit..nkTripleStrLit:
p.body.add(it.strVal)
of nkSym:
let v = it.sym
# for backwards compatibility we don't deref syms here :-(
if false:
discard
@@ -1240,6 +1255,17 @@ proc generateHeader(p: PProc, prc: PSym): Rope =
result.add(name)
result.add("_Idx")
proc countJsParams(typ: PType): int =
result = 0
for i in 1..<typ.n.len:
assert(typ.n[i].kind == nkSym)
var param = typ.n[i].sym
if isCompileTimeOnly(param.typ): continue
if mapType(param.typ) == etyBaseIndex:
inc result, 2
else:
inc result
const
nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit,
nkFloatLit..nkFloat64Lit, nkPar, nkStringToCString,
@@ -1272,16 +1298,14 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
xtyp = etySeq
case xtyp
of etySeq:
if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or
(x.kind == nkSym and sfCursor in x.sym.flags):
if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
else:
useMagic(p, "nimCopy")
lineF(p, "$1 = nimCopy(null, $2, $3);$n",
[a.rdLoc, b.res, genTypeInfo(p, y.typ)])
of etyObject:
if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or
(x.kind == nkSym and sfCursor in x.sym.flags):
if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
else:
useMagic(p, "nimCopy")
@@ -1450,20 +1474,6 @@ proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) =
r.res = "$1.$2" % [tmp, field.loc.snippet]
r.kind = resExpr
proc isVarOpenArrayParam(n: PNode): bool =
## True if `n` resolves to a `var openArray` parameter. The JS backend
## represents such parameters as a `{base, off, len}` slice view so that
## writes through a `toOpenArray` view reach the caller's storage (bug #15952).
var it = n
while true:
case it.kind
of nkHiddenDeref, nkDerefExpr, nkHiddenAddr, nkAddr: it = it[0]
of nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv: it = it[1]
else: break
result = it.kind == nkSym and it.sym.kind == skParam and
it.sym.typ != nil and it.sym.typ.kind == tyVar and
it.sym.typ.len > 0 and it.sym.typ[0].kind == tyOpenArray
proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
var
a, b: TCompRes = default(TCompRes)
@@ -1472,19 +1482,6 @@ proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
let m = if n.kind == nkHiddenAddr: n[0] else: n
gen(p, m[0], a)
gen(p, m[1], b)
if isVarOpenArrayParam(m[0]):
# `var openArray` param is a `{base, off, len}` view; index the base with
# the offset applied. `m[0]` is a plain param name, safe to reference
# repeatedly (no side effects, so no temp needed).
let pn = a.rdLoc
r.address = "($1).base" % [pn]
if optBoundsCheck in p.options:
useMagic(p, "chckIndx")
r.res = "($1).off + chckIndx($2, 0, ($1).len - 1)" % [pn, b.rdLoc]
else:
r.res = "($1).off + ($2)" % [pn, b.rdLoc]
r.kind = resExpr
return
#internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex
let (x, tmp) = maybeMakeTemp(p, m[0], a)
r.address = x
@@ -1547,7 +1544,7 @@ proc genSymAddr(p: PProc, n: PNode, typ: PType, r: var TCompRes) =
r.res = s.loc.snippet
r.address = ""
r.typ = etyNone
of skVar, skLet, skResult, skTemp, skForVar:
of skVar, skLet, skResult:
r.kind = resExpr
let jsType = mapType(p):
if typ.isNil:
@@ -1753,49 +1750,8 @@ proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) =
else:
r.res.add(a.res)
proc genVarOpenArrayArg(p: PProc, n: PNode, r: var TCompRes) =
## Emit a `{base, off, len}` slice view for an argument to a `var openArray`
## parameter (bug #15952). The view always aliases the base storage, so writes
## through the callee's `openArray` reach the caller's array/seq/typed array.
var b, lo, hi, v: TCompRes = default(TCompRes)
# the argument reaches codegen as `addr(toOpenArray(x, lo, hi))` (possibly
# under conversions); unwrap to the actual `toOpenArray` call.
var sl = n
while true:
case sl.kind
of nkHiddenAddr, nkAddr, nkHiddenDeref, nkDerefExpr: sl = sl[0]
of nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv: sl = sl[1]
else: break
if sl.kind in nkCallKinds and getMagic(sl) == mSlice:
gen(p, sl[1], b)
gen(p, sl[2], lo)
gen(p, sl[3], hi)
if isVarOpenArrayParam(sl[1]):
# slicing a `var openArray` view: rebase onto the same underlying storage
r.res = "{base: ($1).base, off: ($1).off + $2, len: $3 - $2 + 1}" % [
b.rdLoc, lo.rdLoc, hi.rdLoc]
else:
r.res = "{base: $1, off: $2, len: $3 - $2 + 1}" % [
b.rdLoc, lo.rdLoc, hi.rdLoc]
elif isVarOpenArrayParam(sl):
# already a view from another `var openArray` param: forward it unchanged
gen(p, sl, b)
r.res = b.rdLoc
else:
# a whole array/seq/typed-array value: wrap with a zero offset
gen(p, n, v)
r.res = "{base: $1, off: 0, len: ($1).length}" % [v.rdLoc]
r.kind = resExpr
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes;
emitted: ptr int = nil; skipVarOpenArray = false) =
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = nil) =
var a: TCompRes = default(TCompRes)
if (not skipVarOpenArray) and param.typ != nil and param.typ.kind == tyVar and
param.typ[0].kind == tyOpenArray:
# `var openArray` params are passed as a `{base, off, len}` slice view.
genVarOpenArrayArg(p, n, a)
r.res.add(a.rdLoc)
return
gen(p, n, a)
if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and
a.typ == etyBaseIndex:
@@ -1805,13 +1761,6 @@ proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes;
r.res.add(", ")
r.res.add(a.res)
if emitted != nil: inc emitted[]
elif skipTypes(param.typ, abstractVar).kind == tyOpenArray and
isVarOpenArrayParam(n):
# a `var openArray` view passed to a read-only `openArray` param: materialize
# a snapshot so the callee sees a plain array.
var w: TCompRes = default(TCompRes)
gen(p, n, w)
r.res.add("(($1).base).slice(($1).off, ($1).off + ($1).len)" % [w.rdLoc])
elif n.typ.kind in {tyVar, tyPtr, tyRef, tyLent, tyOwned} and
n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex:
# this fixes bug #5608:
@@ -1846,11 +1795,16 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) =
inc emitted
hasArgs = true
r.res.add(")")
when false:
# XXX look into this:
let jsp = countJsParams(typ)
if emitted != jsp and tfVarargs notin typ.flags:
localError(p.config, n.info, "wrong number of parameters emitted; expected: " & $jsp &
" but got: " & $emitted)
r.kind = resExpr
proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
generated: var int; r: var TCompRes;
skipVarOpenArray = false) =
generated: var int; r: var TCompRes) =
if i >= n.len:
globalError(p.config, n.info, "wrong importcpp pattern; expected parameter at position " & $i &
" but got only: " & $(n.len-1))
@@ -1863,12 +1817,11 @@ proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
if paramType.isNil:
genArgNoParam(p, it, r)
else:
genArg(p, it, paramType.sym, r, skipVarOpenArray = skipVarOpenArray)
genArg(p, it, paramType.sym, r)
inc generated
proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
r: var TCompRes) =
let skipVarOpenArray = sfImportc in n[0].sym.flags
var i = 0
var j = 1
r.kind = resExpr
@@ -1878,11 +1831,11 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
var generated = 0
for k in j..<n.len:
if generated > 0: r.res.add(", ")
genOtherArg(p, n, k, typ, generated, r, skipVarOpenArray)
genOtherArg(p, n, k, typ, generated, r)
inc i
of '#':
var generated = 0
genOtherArg(p, n, j, typ, generated, r, skipVarOpenArray)
genOtherArg(p, n, j, typ, generated, r)
inc j
inc i
of '\31':
@@ -2065,12 +2018,8 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope =
if indirect: result = "[$1]" % [result]
of tyTuple:
result = rope("{")
var first = true
for i in 0..<t.len:
# Do not produce code for void types
if isEmptyType(t[i]): continue
if not first: result.add(", ")
first = false
if i > 0: result.add(", ")
result.addf("Field$1: $2", [i.rope,
createVar(p, t[i], false)])
result.add("}")
@@ -2139,8 +2088,7 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
gen(p, n, a)
case mapType(p, v.typ)
of etyObject, etySeq:
if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n) or
sfCursor in v.flags:
if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n):
s = a.res
else:
useMagic(p, "nimCopy")
@@ -2380,6 +2328,9 @@ proc genJSArrayConstr(p: PProc, n: PNode, r: var TCompRes) =
r.res.add("]")
proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
var
a: TCompRes
line, filen: Rope
var op = n[0].sym.magic
case op
of mOr: genOr(p, n[1], n[2], r)
@@ -2448,21 +2399,13 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
useMagic(p, "nimCopy")
r.res = "nimCopy(null, $1, $2)" % [x.rdLoc, genTypeInfo(p, n.typ)]
of mOpenArrayToSeq:
if isVarOpenArrayParam(n[1]):
var x: TCompRes = default(TCompRes)
gen(p, n[1], x)
r.res = "(($1).base).slice(($1).off, ($1).off + ($1).len)" % [x.rdLoc]
r.kind = resExpr
else:
genCall(p, n, r)
genCall(p, n, r)
of mDestroy, mTrace: discard "ignore calls to the default destructor"
of mOrd: genOrd(p, n, r)
of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray:
var x: TCompRes = default(TCompRes)
gen(p, n[1], x)
if isVarOpenArrayParam(n[1]):
r.res = "($1).len" % [x.rdLoc]
elif skipTypes(n[1].typ, abstractInst).kind == tyCstring:
if skipTypes(n[1].typ, abstractInst).kind == tyCstring:
let (a, tmp) = maybeMakeTemp(p, n[1], x)
r.res = "(($1) == null ? 0 : ($2).length)" % [a, tmp]
else:
@@ -2471,9 +2414,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
of mHigh:
var x: TCompRes = default(TCompRes)
gen(p, n[1], x)
if isVarOpenArrayParam(n[1]):
r.res = "($1).len - 1" % [x.rdLoc]
elif skipTypes(n[1].typ, abstractInst).kind == tyCstring:
if skipTypes(n[1].typ, abstractInst).kind == tyCstring:
let (a, tmp) = maybeMakeTemp(p, n[1], x)
r.res = "(($1) == null ? -1 : ($2).length - 1)" % [a, tmp]
else:
@@ -2556,24 +2497,11 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
genCall(p, n, r)
of mSlice:
# arr.slice([begin[, end]]): 'end' is exclusive
# Fixed homogeneous numeric arrays lower to JS typed arrays; `slice`
# copies, which silently breaks `var openArray` write-through (bug #15952).
# `subarray` returns a live shared-buffer view with the same
# exclusive-end signature, so use it there; keep `slice` for seqs/strings.
var x, y, z: TCompRes = default(TCompRes)
gen(p, n[1], x)
gen(p, n[2], y)
gen(p, n[3], z)
if isVarOpenArrayParam(n[1]):
# re-slicing a `var openArray` view: materialize from the view's base/offset
r.res = "(($1).base).slice(($1).off + $2, ($1).off + $3 + 1)" % [
x.rdLoc, y.rdLoc, z.rdLoc]
else:
let baseTy = skipTypes(n[1].typ, abstractVarRange + {tyLent})
if baseTy.kind == tyArray and arrayTypeForElemType(p.config, elemType(baseTy)).len > 0:
r.res = "($1.subarray($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
else:
r.res = "($1.slice($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
r.res = "($1.slice($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
r.kind = resExpr
of mMove:
genMove(p, n, r)
@@ -2659,6 +2587,7 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
r.kind = resExpr
var initList : Rope = ""
var fieldIDs = initIntSet()
let nTyp = n.typ.skipTypes(abstractInst)
for i in 1..<n.len:
if i > 1: initList.add(", ")
var it = n[i]
@@ -2865,11 +2794,6 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, {})
if sfInjectDestructors in prc.flags:
transformedBody = injectDestructorCalls(p.module.graph, p.module.idgen, prc, transformedBody)
else:
# JS has a GC, so the destructor pass is off; but the cursor (alias) analysis
# is independent of ownership and always memory-safe on a traced target.
# Running it lets last-use `var b = a` aliases skip the deep `nimCopy`.
computeCursors(prc, transformedBody, p.module.graph)
p.nested: genStmt(p, transformedBody)

View File

@@ -126,6 +126,11 @@ const
paramName* = ":envP"
envName* = ":env"
proc newCall(a: PSym, b: PNode): PNode =
result = newNodeI(nkCall, a.info)
result.add newSymNode(a)
result.add b
proc createClosureIterStateType*(g: ModuleGraph; iter: PSym; idgen: IdGenerator): PType =
var n = newNodeI(nkRange, iter.info)
n.add newIntNode(nkIntLit, -1)
@@ -159,21 +164,9 @@ 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) =
proc addHiddenParam(routine: PSym, param: PSym) =
assert param.kind == skParam
var params = closureParams(routine)
var params = routine.ast[paramsPos]
# -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
@@ -184,8 +177,7 @@ proc addHiddenParam*(routine: PSym, param: PSym) =
proc getEnvParam*(routine: PSym): PSym =
if routine.ast.isNil: return nil
let params = closureParams(routine)
if params == nil or params.len == 0: return nil
let params = routine.ast[paramsPos]
let hidden = lastSon(params)
if hidden.kind == nkSym and hidden.sym.kind == skParam and hidden.sym.name.s == paramName:
result = hidden.sym
@@ -224,10 +216,6 @@ proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode =
result[0] = le
result[1] = ri
proc markInjectDestructors(s: PSym) {.inline.} =
backendEnsureMutable s
s.flagsImpl.incl sfInjectDestructors
proc makeClosure*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; env: PNode; info: TLineInfo): PNode =
result = newNodeIT(nkClosure, info, prc.typ)
result.add(newSymNode(prc))
@@ -240,7 +228,7 @@ proc makeClosure*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; env: PNode; inf
#if isClosureIterator(result.typ):
createTypeBoundOps(g, nil, result.typ, info, idgen)
if tfHasAsgn in result.typ.flags or optSeqDestructors in g.config.globalOptions:
markInjectDestructors(prc)
prc.incl sfInjectDestructors
template liftingHarmful(conf: ConfigRef; owner: PSym): bool =
## lambda lifting can be harmful for JS-like code generators.
@@ -252,7 +240,7 @@ proc createTypeBoundOpsLL(g: ModuleGraph; refType: PType; info: TLineInfo; idgen
createTypeBoundOps(g, nil, refType.elementType, info, idgen)
createTypeBoundOps(g, nil, refType, info, idgen)
if tfHasAsgn in refType.flags or optSeqDestructors in g.config.globalOptions:
markInjectDestructors(owner)
owner.incl sfInjectDestructors
proc genCreateEnv(env: PNode): PNode =
var c = newNodeIT(nkObjConstr, env.info, env.typ)
@@ -283,6 +271,7 @@ proc liftIterSym*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PN
addVar(v, env)
result.add(v)
# add 'new' statement:
#result.add newCall(getSysSym(g, n.info, "internalNew"), env)
result.add genCreateEnv(env)
createTypeBoundOpsLL(g, env.typ, n.info, idgen, owner)
result.add makeClosure(g, idgen, iter, env, n.info)
@@ -301,27 +290,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)
# A closure proc type that captures an env owns a REF to it: copying the closure
# value must incref the env and destroying it must decref. That is exactly what
# `tfHasAsgn` signals to `injectDestructorCalls` (so a closure assignment becomes
# `=copy`, not a raw field store).
#
# Set it HERE (closure-type creation) so the flag is DETERMINISTIC and serializes
# with the type — but ONLY under `nim ic`. The per-module `lower` stage is a
# separate process that lowers routines in index order; if a consumer (e.g.
# `workNimAsyncContinue`) was lowered before the closure type's ops were lifted,
# its env store emitted a RAW assign with no incref → freed env → async
# "yielded `nil`". A normal single-process `nim c` build does NOT need this —
# `createTypeBoundOps` sets the flag lazily, in lift order, before it matters
# (the old `liftdestructors ~1498` "XXX Breaks IC!" side effect) — and setting it
# eagerly there REGRESSES codegen: a `=destroy` hook gets generated against the
# bare `void(*)(void)` proc representation but is then called with closure structs
# (`eqdestroy__u2__stdZtypedthreads` type mismatch — broke megatest). So gate on
# `cmdNifC`; normal builds keep the lazy (devel) behavior.
if g.config.cmd == cmdNifC:
incl(owner.typ, tfHasAsgn)
if not isEnv:
owner.typ.callConv = ccClosure
@@ -439,12 +408,6 @@ Consider:
proc isTypeOf(n: PNode): bool =
n.kind == nkSym and n.sym.magic in {mTypeOf, mType}
proc isEnvTypeForRoutine(envTyp: PType; routine: PSym): bool =
## True if `envTyp` is (maybe wrapped) env object type owned by `routine`, as
## created by `getEnvTypeForOwner` / `createEnvObj`.
let obj = envTyp.skipTypes({tyOwned, tyRef, tyPtr})
result = obj.kind == tyObject and obj.owner.id == routine.id
proc addClosureParam(c: var DetectionPass; fn: PSym; info: TLineInfo) =
var cp = getEnvParam(fn)
let owner = if fn.kind == skIterator: fn else: fn.skipGenericOwner
@@ -455,13 +418,7 @@ proc addClosureParam(c: var DetectionPass; fn: PSym; info: TLineInfo) =
cp.typ = t
addHiddenParam(fn, cp)
elif cp.typ != t and fn.kind != skIterator:
# Nested `liftLambdas` uses a fresh `DetectionPass`, so `getEnvTypeForOwner`
# can allocate another PType for the same logical env; the hidden param from
# the inner pass is authoritative (bug #21242).
if isEnvTypeForRoutine(cp.typ, owner) and isEnvTypeForRoutine(t, owner):
c.ownerToType[owner.id] = cp.typ
else:
localError(c.graph.config, fn.info, "internal error: inconsistent environment type")
localError(c.graph.config, fn.info, "internal error: inconsistent environment type")
#echo "adding closure to ", fn.name.s
proc iterEnvHasUpField(g: ModuleGraph, iter: PSym): bool =
@@ -667,7 +624,7 @@ proc rawClosureCreation(owner: PSym;
if owner.kind != skMacro:
createTypeBoundOps(d.graph, nil, fieldAccess.typ, env.info, d.idgen)
if tfHasAsgn in fieldAccess.typ.flags or optSeqDestructors in d.graph.config.globalOptions:
markInjectDestructors(owner)
owner.incl sfInjectDestructors
let upField = lookupInRecord(env.typ.skipTypes({tyOwned, tyRef, tyPtr}).n, getIdent(d.graph.cache, upName))
if upField != nil:
@@ -675,17 +632,6 @@ proc rawClosureCreation(owner: PSym;
if up != nil and upField.typ.skipTypes({tyOwned, tyRef, tyPtr}) == up.typ.skipTypes({tyOwned, tyRef, tyPtr}):
result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info),
up, env.info))
# That assignment stores a real `ref`, so `injectDestructorCalls` has to
# find the up-field type's ops — otherwise it stays a raw pointer store,
# the enclosing env's refcount is one too low, and at teardown the two
# envs' mutually recursive `=destroy`s each believe they hold the last
# reference and recurse until the stack is gone. Whole-program cgen never
# noticed: some LATER lifting pass creates this very ref type's ops, and it
# runs before any routine's destructor injection. The per-module backend
# injects a routine right after lifting it (the `lower` stage), long before
# the module's top level is transformed at all (that is `cg`).
if up.typ != nil and up.typ.kind == tyRef and up.typ.elementType != nil:
createTypeBoundOpsLL(d.graph, up.typ, env.info, d.idgen, owner)
#elif oldenv != nil and oldenv.typ == upField.typ:
# result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info),
# oldenv, env.info))
@@ -743,10 +689,6 @@ proc closureCreationForIter(owner: PSym, iter: PNode;
if u != nil and u.typ.skipTypes({tyOwned, tyRef, tyPtr}) == expectedUpTyp:
result.add(newAsgnStmt(rawIndirectAccess(vnode, upField, iter.info),
u, iter.info))
# See the identical call in `rawClosureCreation`: the up-field's ops must
# exist by the time this assignment is destructor-injected.
if u.typ != nil and u.typ.kind == tyRef and u.typ.elementType != nil:
createTypeBoundOpsLL(d.graph, u.typ, iter.info, d.idgen, owner)
else:
localError(d.graph.config, iter.info, "internal error: cannot create up reference for iter")
result.add makeClosure(d.graph, d.idgen, iter.sym, vnode, iter.info)

View File

@@ -46,11 +46,12 @@ proc setToPreviousLayer*(pt: var LayeredIdTable) {.inline.} =
when useRef:
pt = pt.nextLayer
else:
# Must read nextLayer into a temp before destroying pt:
# `pt = pt.nextLayer[]` would call eqcopy(&pt, &(*pt.nextLayer)) which
# decrements pt.nextLayer's rc (freeing it) before reading pt.nextLayer.nextLayer.
let tmp = pt.nextLayer[]
pt = tmp
when defined(gcDestructors):
pt = pt.nextLayer[]
else:
# workaround refc
let tmp = pt.nextLayer[]
pt = tmp
iterator pairs*(pt: LayeredIdTable): (ItemId, PType) =
var tm = pt
@@ -61,17 +62,6 @@ iterator pairs*(pt: LayeredIdTable): (ItemId, PType) =
break
tm.setToPreviousLayer
proc lookupById*(typeMap: LayeredIdTable, key: ItemId): PType =
## Looks up an ItemId directly, observing the same layer shadowing rules as
## `lookup`. This form is useful when a binding key was previously captured.
result = nil
var tm = typeMap
while true:
result = getOrDefault(tm.topLayer, key)
if result != nil or tm.nextLayer == nil:
return
tm.setToPreviousLayer
proc lookup(typeMap: ref LayeredIdTableObj, key: ItemId): PType =
result = nil
var tm = typeMap
@@ -82,7 +72,7 @@ proc lookup(typeMap: ref LayeredIdTableObj, key: ItemId): PType =
template lookup*(typeMap: ref LayeredIdTableObj, key: PType): PType =
## recursively looks up binding of `key` in all parent layers
lookup(typeMap, key.bindingId)
lookup(typeMap, key.itemId)
when not useRef:
proc lookup(typeMap: LayeredIdTableObj, key: ItemId): PType {.inline.} =
@@ -91,11 +81,11 @@ when not useRef:
result = lookup(typeMap.nextLayer, key)
template lookup*(typeMap: LayeredIdTableObj, key: PType): PType =
lookup(typeMap, key.bindingId)
lookup(typeMap, key.itemId)
proc put(typeMap: var LayeredIdTable, key: ItemId, value: PType) {.inline.} =
typeMap.topLayer[key] = value
template put*(typeMap: var LayeredIdTable, key, value: PType) =
## binds `key` to `value` only in current layer
put(typeMap, key.bindingId, value)
put(typeMap, key.itemId, value)

View File

@@ -451,13 +451,7 @@ proc emitTok*(em: var Emitter; L: Lexer; tok: Token) =
elif tok.indent >= 0:
var newlineKind = ltCrucialNewline
if em.keepIndents > 0:
# Apply the requested --indent width to "don't touch" regions (if/block
# expressions) too: keep the relative offset from the enclosing block
# baseline, but rebase it onto indWidth. Otherwise a non-default
# --indent would leave these lines at the original column and inject
# invalid indentation (see #20078).
em.indentLevel = em.indentStack.high * em.indWidth +
(tok.indent - em.indentStack[^1])
em.indentLevel = tok.indent
elif (em.lastTok in (splitters + oprSet) and
tok.tokType notin (closedPars - {tkBracketDotRi})):
if tok.tokType in openPars and tok.indent > em.indentStack[^1]:

View File

@@ -735,11 +735,17 @@ proc getEscapedChar(L: var Lexer, tok: var Token) =
else: lexMessage(L, errGenerated, "invalid character constant")
proc handleCRLF(L: var Lexer, pos: int): int =
result =
case L.buf[pos]
of CR: nimlexbase.handleCR(L, pos)
of LF: nimlexbase.handleLF(L, pos)
else: pos
template registerLine =
let col = L.getColNumber(pos)
case L.buf[pos]
of CR:
registerLine()
result = nimlexbase.handleCR(L, pos)
of LF:
registerLine()
result = nimlexbase.handleLF(L, pos)
else: result = pos
type
StringMode = enum
@@ -839,8 +845,8 @@ proc getCharacter(L: var Lexer; tok: var Token) =
const
UnicodeOperatorStartChars = {'\226', '\194', '\195'}
# the allowed unicode characters ("∙ ∘ × ⊗ ⊘ ⊙ ⊛ ⊠ ⊡ ∩ ∧ ⊓ ⟑ ⟇ ⩓ ⩔ ■ □
# ± ⊕ ⊖ ⊞ ⊟ ⊔") all start with one of these.
# the allowed unicode characters ("∙ ∘ × ★ ⊗ ⊘ ⊙ ⊛ ⊠ ⊡ ∩ ∧ ⊓ ± ⊕ ⊖ ⊞ ⊟ ⊔")
# all start with one of these.
type
UnicodeOprPred = enum
@@ -872,18 +878,7 @@ proc unicodeOprLen(buf: cstring; pos: int): (int8, UnicodeOprPred) =
elif buf[pos+2] == '\159': result = 3.a # ⊟
elif buf[pos+2] == '\160': result = 3.m # ⊠
elif buf[pos+2] == '\161': result = 3.m # ⊡
elif buf[pos+1] == '\150':
if buf[pos+2] == '\160': result = 3.m # ■
elif buf[pos+2] == '\161': result = 3.m # □
elif buf[pos+1] == '\152':
if buf[pos+2] == '\133': result = 3.m # ★
elif buf[pos+2] == '\134': result = 3.m # ☆
elif buf[pos+1] == '\159':
if buf[pos+2] == '\135': result = 3.m # ⟇
elif buf[pos+2] == '\145': result = 3.m # ⟑
elif buf[pos+1] == '\169':
if buf[pos+2] == '\147': result = 3.m # ⩓
elif buf[pos+2] == '\148': result = 3.m # ⩔
elif buf[pos+1] == '\152' and buf[pos+2] == '\133': result = 3.m # ★
of '\194':
if buf[pos+1] == '\177': result = 2.a # ±
of '\195':
@@ -1354,7 +1349,7 @@ proc rawGetTok*(L: var Lexer, tok: var Token) =
lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
of '-':
if L.buf[L.bufpos+1] in {'0'..'9'} and
(L.bufpos == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
(L.bufpos-1 == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
# x)-23 # binary minus
# ,-23 # unary minus
# \n-78 # unary minus? Yes.

View File

@@ -94,21 +94,11 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
proc genAddr(c: var TLiftCtx; x: PNode): PNode =
# These synthesized addresses are always passed to codegen procs that expect a
# genuine pointer (nimAsgnYrc, nimSinkYrc, destructors, ...). `addr(deref x)`
# collapses to `x` only when `x` is a real pointer; on the C++ backend a `var`
# parameter is a C++ reference, so we must keep the `nkHiddenAddr` to actually
# take its address (`&dest`) instead of passing the reference's value. Likewise
# `tfVarIsPtr` keeps the C++ backend from lowering the synthesized address back
# to a reference and dropping the `&` (e.g. a closure's `tyPointer` env). See
# #26026 CI (yrc + cpp).
if x.kind == nkHiddenDeref and c.g.config.backend != backendCpp:
if x.kind == nkHiddenDeref:
checkSonsLen(x, 1, c.g.config)
result = x[0]
else:
let addrTyp = makeVarType(x.typ.owner, x.typ, c.idgen)
addrTyp.incl tfVarIsPtr
result = newNodeIT(nkHiddenAddr, x.info, addrTyp)
result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ, c.idgen))
result.add x
proc genWhileLoop(c: var TLiftCtx; i, dest: PNode): PNode =
@@ -596,12 +586,16 @@ proc newSeqCall(c: var TLiftCtx; x, y: PNode): PNode =
lenCall.typ = getSysType(c.g, x.info, tyInt)
result.add lenCall
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode; noinit = false): PNode =
proc setLenStrCall(c: var TLiftCtx; x, y: PNode): PNode =
let lenCall = genBuiltin(c, mLengthStr, "len", y)
lenCall.typ = getSysType(c.g, x.info, tyInt)
result = genBuiltin(c, mSetLengthStr, "setLen", x) # genAddr(g, x))
result.add lenCall
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode): PNode =
let lenCall = genBuiltin(c, mLengthSeq, "len", y)
lenCall.typ = getSysType(c.g, x.info, tyInt)
let name = if noinit: "setLenUninit" else: "setLen"
let magic = if noinit: mSetLengthSeqUninit else: mSetLengthSeq
var op = getSysMagic(c.g, x.info, name, magic)
var op = getSysMagic(c.g, x.info, "setLen", mSetLengthSeq)
op = instantiateGeneric(c, op, t, t)
result = newTree(nkCall, newSymNode(op, x.info), x, lenCall)
@@ -626,35 +620,11 @@ proc checkSelfAssignment(c: var TLiftCtx; t: PType; body, x, y: PNode) =
cond.typ = getSysType(c.g, c.info, tyBool)
body.add genIf(c, cond, newTreeI(nkReturnStmt, c.info, newNodeI(nkEmpty, c.info)))
proc genBulkCopySeq(c: var TLiftCtx; t: PType; body, x, y: PNode) =
## Generates a call to nimCopySeqPayload for bulk memcpy of seq data.
let elemType = t.elementType
let sym = magicsys.getCompilerProc(c.g, "nimCopySeqPayload")
if sym == nil:
localError(c.g.config, c.info, "system module needs: nimCopySeqPayload")
return
var sizeOf = genBuiltin(c, mSizeOf, "sizeof", newNodeIT(nkType, c.info, elemType))
sizeOf.typ = getSysType(c.g, c.info, tyInt)
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
alignOf.typ = getSysType(c.g, c.info, tyInt)
let call = newNodeI(nkCall, c.info)
call.add newSymNode(sym)
call.add newTreeIT(nkAddr, c.info, makePtrType(c.fn, x.typ, c.idgen), x)
call.add newTreeIT(nkAddr, c.info, makePtrType(c.fn, y.typ, c.idgen), y)
call.add sizeOf
call.add alignOf
call.typ = sym.typ.returnType
body.add call
proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case c.kind
of attachedDup:
let bulkCopy = supportsCopyMem(t.elementType)
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
if bulkCopy:
genBulkCopySeq(c, t, body, x, y)
else:
forallElements(c, t, body, x, y)
body.add setLenSeqCall(c, t, x, y)
forallElements(c, t, body, x, y)
of attachedAsgn, attachedDeepCopy:
# we generate:
# if x.p == y.p:
@@ -663,14 +633,9 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# var i = 0
# while i < y.len: dest[i] = y[i]; inc(i)
# This is usually more efficient than a destroy/create pair.
# For trivially copyable types, use bulk copyMem instead of element loop.
checkSelfAssignment(c, t, body, x, y)
let bulkCopy = supportsCopyMem(t.elementType)
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
if bulkCopy:
genBulkCopySeq(c, t, body, x, y)
else:
forallElements(c, t, body, x, y)
body.add setLenSeqCall(c, t, x, y)
forallElements(c, t, body, x, y)
of attachedSink:
let moveCall = genBuiltin(c, mMove, "move", x)
moveCall.add y
@@ -715,11 +680,6 @@ 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, " bindingId=", t.bindingId, " 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:
@@ -741,18 +701,11 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedAsgn, attachedDeepCopy, attachedDup:
body.add callCodegenProc(c.g, "nimAsgnStrV2", c.info, genAddr(c, x), y)
of attachedSink:
if c.g.config.usesSso():
# SmallString: destroy old dst, then bit-copy src (no rc increment — this is a move).
# No .p aliasing check needed; rc-based destroy handles COW sharing correctly.
doAssert t.destructor != nil
body.add destructorCall(c, t.destructor, x)
body.add newAsgnStmt(x, y)
else:
let moveCall = genBuiltin(c, mMove, "move", x)
moveCall.add y
doAssert t.destructor != nil
moveCall.add destructorCall(c, t.destructor, x)
body.add moveCall
let moveCall = genBuiltin(c, mMove, "move", x)
moveCall.add y
doAssert t.destructor != nil
moveCall.add destructorCall(c, t.destructor, x)
body.add moveCall
of attachedDestructor:
body.add genBuiltin(c, mDestroy, "destroy", x)
of attachedTrace:
@@ -800,22 +753,8 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
createTypeBoundOps(c.g, c.c, elemType, c.info, c.idgen)
# YRC uses dedicated runtime procs for the entire write barrier -- but ONLY
# for refs that can actually form cycles. Routing an acyclic ref through
# `nimAsgnYrc` defeats the entire purpose of `.acyclic`: the barrier defers
# the dec into a stripe queue, `drainStripe` then hands the cell to
# `registerLocal`, and it enters the collector as a capture ROOT -- so a
# type annotated precisely to stay out of the cycle collector gets traced
# by it anyway. (The collector never reaches such a cell by TRAVERSAL: the
# attachedTrace hook below only emits `nimTraceRef` when `isCyclic`. The
# queued dec was the only way in.)
#
# Falling through instead gives acyclic refs the same prompt arc-style
# reclamation they get under --mm:arc/orc, which is also what lets a thread
# that avoids cycles at compile time avoid the collector entirely at run
# time. `canFormAcycle` is the same predicate ccgtypes.nim:1903 uses to set
# the descriptor's acyclic flag, so codegen and runtime cannot disagree.
if c.g.config.selectedGC == gcYrc and types.canFormAcycle(c.g, elemType):
# YRC uses dedicated runtime procs for the entire write barrier:
if c.g.config.selectedGC == gcYrc:
let desc =
if isFinal(elemType):
let ti = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
@@ -839,15 +778,13 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
tfAcyclic in skipTypes(elemType, abstractInst+{tyOwned}-{tyTypeDesc}).flags
# dynamic Acyclic refs need to use dyn decRef
let useStatic = isFinal(elemType)
let tmp =
if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}:
declareTempOf(c, body, x)
else:
x
if useStatic:
if isFinal(elemType):
addDestructorCall(c, elemType, actions, genDeref(tmp, nkDerefExpr))
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
alignOf.typ = getSysType(c.g, c.info, tyInt)
@@ -858,7 +795,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
var cond: PNode
if isCyclic:
if useStatic:
if isFinal(elemType):
let typInfo = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
typInfo.typ = getSysType(c.g, c.info, tyPointer)
cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicStatic", c.info, tmp, typInfo)
@@ -893,7 +830,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedDeepCopy: assert(false, "cannot happen")
of attachedTrace:
if isCyclic:
if useStatic:
if isFinal(elemType):
let typInfo = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
typInfo.typ = getSysType(c.g, c.info, tyPointer)
body.add callCodegenProc(c.g, "nimTraceRef", c.info, genAddrOf(x, c.idgen), typInfo, y)
@@ -1105,17 +1042,8 @@ 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, tyVar, tyLent:
tyPtr, tyUncheckedArray, tyVar, tyLent:
defaultOp(c, t, body, x, y)
of tyRef:
if c.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
@@ -1233,7 +1161,7 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
res.typ = typ
src.typ = typ
result.typ = newType(tyProc, idgen, result)
result.typ = newType(tyProc, idgen, owner)
result.typ.n = newNodeI(nkFormalParams, info)
rawAddSon(result.typ, res.typ)
result.typ.n.add newNodeI(nkEffectList, info)
@@ -1255,7 +1183,6 @@ 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 =
@@ -1279,8 +1206,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
else:
src.typ = typ
# the hook OWNS its signature, like any routine sem'd from source
result.typ = newProcType(info, idgen, result)
result.typ = newProcType(info, idgen, owner)
result.typ.addParam dest
if kind notin {attachedDestructor, attachedWasMoved}:
result.typ.addParam src
@@ -1303,10 +1229,6 @@ 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)
@@ -1399,7 +1321,6 @@ 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

View File

@@ -100,7 +100,6 @@ type
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
warnImplicitRangeConversion = "ImplicitRangeConversion",
warnSystemRangeConversion = "SystemRangeConversion",
warnInvalidCmpOp = "InvalidCmpOp",
# hints
hintSuccess = "Success", hintSuccessX = "SuccessX",
hintCC = "CC",
@@ -211,7 +210,6 @@ 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",
@@ -268,7 +266,7 @@ proc computeNotesVerbosity(): array[0..3, TNoteKinds] =
result = default(array[0..3, TNoteKinds])
result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix, warnSystemRangeConversion}
result[2] = result[3] - {hintStackTrace, hintExtendedContext, hintDeclaredLoc, hintProcessingStmt}
result[1] = result[2] - {warnImplicitRangeConversion, warnProveField, warnProveIndex,
result[1] = result[2] - {warnProveField, warnProveIndex,
warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd,
hintSource, hintGlobalVar, hintGCStats, hintMsgOrigin, hintPerformance}
result[0] = result[1] - {hintSuccessX, hintSuccess, hintConf,

View File

@@ -163,7 +163,7 @@ proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int =
inc(s.lineOffset)
result = min(bufLen, s.s.len - s.rd)
if result > 0:
copyMem(buf, readRawData(s.s, s.rd), result)
copyMem(buf, addr(s.s[s.rd]), result)
inc(s.rd, result)
proc llStreamRead*(s: PLLStream, buf: pointer, bufLen: int): int =
@@ -173,7 +173,7 @@ proc llStreamRead*(s: PLLStream, buf: pointer, bufLen: int): int =
of llsString:
result = min(bufLen, s.s.len - s.rd)
if result > 0:
copyMem(buf, readRawData(s.s, s.rd), result)
copyMem(buf, addr(s.s[0 + s.rd]), result)
inc(s.rd, result)
of llsFile:
result = readBuffer(s.f, buf, bufLen)

View File

@@ -378,9 +378,6 @@ 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])
@@ -462,15 +459,6 @@ 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

View File

@@ -207,70 +207,15 @@ proc lookupInRecord(n: PNode, id: ItemId): PSym =
if result != nil: return
else: discard
of nkSym:
if matchesDerivedFieldId(n.sym.itemId, id): result = n.sym
else: discard
proc lookupCapturedField(n: PNode, s: PSym): PSym =
## Find an env field that `addField` would have produced for the captured
## local `s`. Used as a fallback when the derived-itemId match fails because
## `s` is a macro-generated gensym whose process-local id diverges from the
## loaded env field's (see `addField`). `addField` always names a field
## `s.name & $field.position`, so that pair uniquely identifies the field for a
## local of this name without relying on the (unstable) item id.
result = nil
case n.kind
of nkRecList:
for i in 0..<n.len:
result = lookupCapturedField(n[i], s)
if result != nil: return
of nkRecCase:
if n[0].kind != nkSym: return
result = lookupCapturedField(n[0], s)
if result != nil: return
for i in 1..<n.len:
case n[i].kind
of nkOfBranch, nkElse:
result = lookupCapturedField(lastSon(n[i]), s)
if result != nil: return
else: discard
of nkSym:
if n.sym.kind == skField and n.sym.name.s == s.name.s & $n.sym.position:
result = n.sym
if n.sym.itemId.module == id.module and n.sym.itemId.item == -abs(id.item): result = n.sym
else: discard
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
# Idempotent w.r.t. the captured symbol (mirrors `addUniqueField`): re-lifting
# a LOADED routine re-derives its transformed body (never serialized under IC)
# and re-captures the same locals, but the env object loaded from the NIF
# already carries their fields. Re-adding would duplicate the field and, worse,
# mutate a Sealed loaded type via `propagateToOwner` (the `t.state != Sealed`
# crash). Return the existing field instead.
let existing = lookupInRecord(obj.n, s.itemId)
if existing != nil:
return existing
# Re-lifting a LOADED routine during a VM transform (its transformed body is
# re-derived per process, never serialized) re-captures the same locals, but
# for a macro-generated gensym (e.g. libp2p `p2pProtocolBackendImpl`'s
# `msgVar`) its process-local id diverges from the one baked into the loaded
# env field, so the id match above misses. Reuse the existing same-named field
# rather than appending a divergent duplicate, which keeps the re-derived
# closure consistent (else a stale `:env` access reaches `cannotEval`).
# Confined to a loaded (Sealed) env: in a freshly built env ids are consistent,
# and two distinct same-named captures legitimately get distinct fields there.
if obj.state == Sealed:
let byName = lookupCapturedField(obj.n, s)
if byName != nil:
return byName
# Genuinely new field. Under IC the env may be a loaded Sealed type whose
# transform-time mutation is process-local (the body is discarded after the
# macro runs), so downgrade it to mutable instead of crashing on
# `t.state != Sealed` (mirrors `markAsClosure`).
unsealForTransform(obj)
# because of 'gensym' support, we have to mangle the name with its ID.
# This is hacky but the clean solution is much more complex than it looks.
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len),
idgen, s.owner, s.info, s.options)
field.itemId = derivedFieldId(s.itemId)
field.itemId = ItemId(module: s.itemId.module, item: -s.itemId.item)
let t = skipIntLit(s.typ, idgen)
field.typ = t
if s.kind in {skLet, skVar, skField, skForVar}:
@@ -290,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 = derivedFieldId(s.itemId)
field.itemId = ItemId(module: s.itemId.module, item: -s.itemId.item)
let t = skipIntLit(s.typ, idgen)
field.typ = t
assert t.kind != tyTyped
@@ -361,16 +306,6 @@ proc getFieldFromObj*(t: PType; v: PSym): PSym =
assert t.kind == tyObject
result = lookupInRecord(t.n, v.itemId)
if result != nil: break
# A LOADED (Sealed) env object carries fields baked by the producer process;
# re-lifting a NIF-loaded routine in a consumer (e.g. a macro VM-evaluating an
# imported `p2pProtocolBackendImpl`) re-captures the same local under a
# divergent process-local id, so the derived-itemId match misses. Fall back to
# the name+position identity `addField` uses — SYMMETRIC with `addField`'s
# Sealed by-name reuse — so the access resolves the field `addField` produced
# instead of failing with `not part of closure object type`.
if t.state == Sealed:
result = lookupCapturedField(t.n, v)
if result != nil: break
t = t.baseClass
if t == nil: break
t = t.skipTypes(skipPtrs)

View File

@@ -11,7 +11,7 @@
import
ast, msgs, platform, idents,
modulegraphs, lineinfos, types
modulegraphs, lineinfos
export createMagic
@@ -134,7 +134,7 @@ proc getNimScriptSymbol*(g: ModuleGraph; name: string): PSym =
proc resetNimScriptSymbols*(g: ModuleGraph) = g.exposed = initStrTable()
proc getMagicEqSymForType*(g: ModuleGraph; t: PType; info: TLineInfo): PSym =
case t.skipTypes(abstractRange).kind
case t.kind
of tyInt, tyInt8, tyInt16, tyInt32, tyInt64,
tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64:
result = getSysMagic(g, info, "==", mEqI)

View File

@@ -29,13 +29,10 @@ when defined(nimPreviewSlimSystem):
import ../dist/checksums/src/checksums/sha1
import pipelines
import icprof
from icconfig import produceIcConfig, ensureIcConfig
when not defined(nimKochBootstrap):
import nifbackend
import deps
import idetools
when not defined(leanCompiler):
import docgen
@@ -210,6 +207,22 @@ proc commandInteractive(graph: ModuleGraph) =
let s = llStreamOpenStdIn(onPrompt = proc() = flushDot(graph.config))
discard processPipelineModule(graph, m, idgen, s)
proc commandScan(cache: IdentCache, config: ConfigRef) =
var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt)
var stream = llStreamOpen(f, fmRead)
if stream != nil:
var
L: Lexer = default(Lexer)
tok: Token = default(Token)
openLexer(L, f, stream, cache, config)
while true:
rawGetTok(L, tok)
printTok(config, tok)
if tok.tokType == tkEof: break
closeLexer(L)
else:
rawMessage(config, errGenerated, "cannot open file: " & f.string)
const
PrintRopeCacheStats = false
@@ -270,28 +283,6 @@ proc mainCommand*(graph: ModuleGraph) =
proc compileToBackend() =
customizeForBackend(conf.backend)
if isIcDriver(conf):
# `nim c --ic:on` / `nim cpp --ic:on`: same driver as `nim ic`, entered
# through the ordinary compile command so every backend switch the user
# already knows keeps working (`nim cpp`, `--exceptions:`, `-d:`, ...).
# `customizeForBackend` above has already defined the backend symbol and
# picked the exception model, which is exactly what the per-module
# children must inherit — `computeForwardedArgs` forwards both.
setUseIc(true)
wantMainModule(conf)
setOutFile(conf)
when not defined(nimKochBootstrap):
if conf.icPreparsedConfig.len == 0:
# `--ic:on` came from a `nim.cfg`/`config.nims` rather than the command
# line, so `nim.nim` could not see it before config loading and the
# precompiled config the children replay does not exist yet. Produce it
# now. (The driver then keeps the config IT parsed instead of replaying
# the artifact; both come from the same files.)
ensureIcConfig(conf)
commandIc(conf)
else:
rawMessage(conf, errGenerated, "--ic:on not available in bootstrap build")
return
setOutFile(conf)
case conf.backend
of backendC: commandCompileToC(graph)
@@ -424,34 +415,13 @@ proc mainCommand*(graph: ModuleGraph) =
for it in conf.searchPaths: msgWriteln(conf, it.string)
of cmdCheck:
commandCheck(graph)
of cmdTrack:
# `nim track --def:/--usages:/--track:` — IDE goto-definition / find-usages.
# Runs `nim ic`'s incremental frontend (nifler + per-module `nim m`, so only
# changed modules recompile and each writes a faithful, VM-executed `.s.bif`
# — covering stdlib too), then scans those NIF files (idetools.runIdeQuery).
# Shares the `nim ic` nimcache dir, so a prior `nim ic` build is reused.
setUseIc(true)
wantMainModule(conf)
setOutFile(conf)
when not defined(nimKochBootstrap):
commandIc(conf, frontendOnly = true)
runIdeQuery(conf)
else:
rawMessage(conf, errGenerated, "nim track not available in bootstrap build")
of cmdM:
# cmdM uses NIF files, not ROD files
graph.config.symbolFiles = disabledSf
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
# `tStage` for a `nim m` process, so `Process - Stage` is its real startup
# (exec, runtime init, config replay) rather than its whole runtime.
timed tStage: commandCheck(graph)
commandCheck(graph)
of cmdNifC:
setUseIc(true)
excl conf.features, Feature.vtables
# Generate C code from NIF files
wantMainModule(conf)
setOutFile(conf)
@@ -460,18 +430,10 @@ proc mainCommand*(graph: ModuleGraph) =
# Generate .build.nif for nifmake
setUseIc(true)
wantMainModule(conf)
# Resolve the output binary path (honoring `--out`) up front, like cmdNifC:
# the backend build file derives the link target from `conf.absOutFile`.
setOutFile(conf)
when not defined(nimKochBootstrap):
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)
@@ -485,17 +447,10 @@ proc mainCommand*(graph: ModuleGraph) =
of cmdJsonscript:
setOutFile(graph.config)
commandJsonScript(graph)
of cmdUnknown, cmdNone:
of cmdUnknown, cmdNone, cmdIdeTools:
rawMessage(conf, errGenerated, "invalid command: " & conf.command)
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 conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop}:
if optProfileVM in conf.globalOptions:
echo conf.dump(conf.vmProfileData)
genSuccessX(conf)

View File

@@ -53,29 +53,7 @@ proc mangleParamExt*(s: PSym): string =
result.addInt s.position
proc mangleProcNameExt*(graph: ModuleGraph, s: PSym): string =
# 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). The `_c` marker keeps the namespace disjoint from
# `_u<disamb>`; `backendMintedDisamb` (astdef) is the ONE definition of which
# integer identifies such a symbol, shared with `ccgutils.makeUnique` and
# `ast2nif.toNifSymName` so the C name and the NIF name cannot drift apart.
result = "_c"
result.addInt backendMintedDisamb(s)
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 = "__"
result.add graph.ifaces[s.itemId.module].uniqueName
result.add "_u"
result.addInt s.itemId.item # s.disamb #

View File

@@ -11,14 +11,13 @@
## 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, sets]
import std/[intsets, tables, hashes, strtabs, os, strutils, parseutils]
import ../dist/checksums/src/checksums/md5
import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages, suggestsymdb
when not defined(nimKochBootstrap):
import ast2nif
import nifstreams
import "../dist/nimony/src/lib" / bitabs
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
import typekeys
@@ -36,10 +35,6 @@ type
pureEnums*: seq[PSym]
interf: TStrTable
interfHidden: TStrTable
hiddenPending: bool ## `interfHidden` holds only the exported half so far;
## `ensureHiddenIface` materialises the hidden-only
## symbols on first use. See
## `ast2nif.buildHiddenInterface`.
uniqueName*: Rope
Operators* = object
@@ -71,44 +66,7 @@ type
memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual,member and ctor so far).
initializersPerType*: Table[ItemId, PNode] # Type ID, AST call to the default ctor (c++ only)
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.
@@ -141,10 +99,6 @@ type
systemModule*: PSym
sysTypes*: array[TTypeKind, PType]
compilerprocs*: TStrTable
missingCompilerProcs*: HashSet[string]
# `nim nifc` only: compilerproc names no
# loaded module defines, so the whole-program
# index scan in `loadCompilerProc` runs once
exposed*: TStrTable
packageTypes*: TStrTable
emptyNode*: PNode
@@ -155,49 +109,22 @@ 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
pendingNifInit*: seq[tuple[module: PSym; topLevel: PNode]]
# EVERY module loaded from a NIF — whether a direct import (moduleFromNifFile)
# or only a dep-of-a-dep (loadTransitiveHooks) — is recorded here with its
# serialized top-level AST. The sem driver drains it once
# (pipelines.finalizeLoadedModules) and applies the module's VM-level load
# effects UNIFORMLY: macro-cache replay (std/macrocache put/inc/add/incl) and
# eager `{.compileTime.}` global init. This is the single place "what a loaded
# module does to global state" lives, so a transitively-reached module — which
# never passes through compilePipelineModule — gets the SAME treatment as a
# direct import instead of silently skipping it (its macrocache state would be
# lost; its CT globals would stay nil and a macro splicing one, e.g.
# chronicles' `chroniclesBlockName`, emits `break nil` / `nil == 0`). To add a
# new per-load VM effect, extend the drain — never a parallel buffer.
passes*: seq[TPass]
pipelinePass*: PipelinePass
onDefinition*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
globalDestructors*: seq[PNode]
icModuleDtors*: seq[string] # per-module backend: the C names of the
# other modules' global-destructor procs
# (`genIcModuleDestroyGlobals`), already in
# call order; only the main module's `cg`
# fills this, from the `.c.nif` meta heads
strongSemCheck*: proc (graph: ModuleGraph; owner: PSym; body: PNode) {.nimcall.}
compatibleProps*: proc (graph: ModuleGraph; formal, actual: PType): bool {.nimcall.}
idgen*: IdGenerator
vmTransfIdgen*: IdGenerator # process-local backend idgen for closure envs
# minted while the VM compiles a routine body
# (inVMTransform); see lambdalifting / ast2nif @bk
operators*: Operators
cachedFiles*: StringTableRef
procGlobals*: seq[PNode]
nifReplayActions*: Table[int32, seq[PNode]] # module position -> replay actions for NIF
nifExpansions*: Table[int32, seq[(PSym, TLineInfo)]]
# module position -> (template/macro sym, call-site info) for every expansion
# in that module. Templates/macros leave no trace in the sem'checked AST, so
# this side-channel (written into the `.bif`, see ast2nif) is what lets
# `nim track --usages`/`--def` find them. Populated by `rememberExpansion`.
cachedMods: IntSet
hookClosure: IntSet # modules whose serialized hooks were already registered
TPassContext* = object of RootObj # the pass's context
idgen*: IdGenerator
@@ -220,7 +147,6 @@ proc resetForBackend*(g: ModuleGraph) =
a.clear()
g.methodsPerGenericType.clear()
g.enumToStringProcs.clear()
g.loadedEnumToStringProcs.clear()
g.dispatchers.setLen(0)
g.methodsPerType.clear()
for a in mitems(g.loadedOps):
@@ -261,25 +187,6 @@ proc toBase64a(s: cstring, len: int): string =
result.add cb64[a shr 2]
result.add cb64[(a and 3) shl 4]
proc ensureHiddenIface(g: ModuleGraph; pos: int) =
## Materialise a loaded module's hidden-only interface the first time anything
## asks for it. Every READ of `interfHidden` goes through `interfSelect`, so
## guarding those sites is complete.
if g.ifaces[pos].hiddenPending:
when not defined(nimKochBootstrap):
# By SUFFIX: `c.mods` and `g.ifaces` use different FileIndexes for the
# same module (see `buildHiddenInterface`). Into a LOCAL table, because
# loading symbols can grow `g.ifaces` and a `var` alias into it would then
# point at the freed buffer. Cleared only on success, so an import whose
# `.s.bif` does not exist yet is retried rather than written off.
var tab = g.ifaces[pos].interfHidden
if buildHiddenInterface(ast.program,
cachedModuleSuffix(g.config, FileIndex pos), tab):
g.ifaces[pos].interfHidden = tab
g.ifaces[pos].hiddenPending = false
else:
g.ifaces[pos].hiddenPending = false
template interfSelect(iface: Iface, importHidden: bool): TStrTable =
var ret = iface.interf.addr # without intermediate ptr, it creates a copy and compiler becomes 15x slower!
if importHidden: ret = iface.interfHidden.addr
@@ -315,7 +222,6 @@ proc initModuleIter*(mi: var ModuleIter; g: ModuleGraph; m: PSym; name: PIdent):
assert m.kind == skModule
mi.modIndex = m.position
mi.importHidden = optImportHidden in m.options
if mi.importHidden: ensureHiddenIface(g, mi.modIndex)
result = initIdentIter(mi.ti, g.ifaces[mi.modIndex].interfSelect(mi.importHidden), name)
proc nextModuleIter*(mi: var ModuleIter; g: ModuleGraph): PSym =
@@ -323,48 +229,16 @@ proc nextModuleIter*(mi: var ModuleIter; g: ModuleGraph): PSym =
iterator allSyms*(g: ModuleGraph; m: PSym): PSym =
let importHidden = optImportHidden in m.options
if importHidden: ensureHiddenIface(g, m.position)
for s in g.ifaces[m.position].interfSelect(importHidden).data:
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 reexportedLocalSyms*(g: ModuleGraph; m: PSym): seq[ItemId] =
## Symbols DEFINED in `m` that reached `m`'s interface through an explicit
## `export s` rather than through a `*` marker on their declaration.
##
## `semExport` re-exports by `reexportSym`, which adds to the interface table
## and does NOT set `sfExported` — so a symbol can be importable while its
## declaration says otherwise. The NIF writer decides importability from
## `sfExported` alone and therefore missed exactly these. `std/random` does it
## (`proc initRand(): Rand` private, then `since (1, 5, 1): export initRand`),
## which is why `--ic:on` could not compile anything that reached
## `std/tempfiles` — `initRand()` was undeclared in the importer.
result = @[]
for s in g.ifaces[m.position].interf.data:
if s != nil and s.kind != skModule and sfExported notin s.flags and
s.itemId.module == m.position:
result.add s.itemId
proc someSym*(g: ModuleGraph; m: PSym; name: PIdent): PSym =
let importHidden = optImportHidden in m.options
if importHidden: ensureHiddenIface(g, m.position)
result = strTableGet(g.ifaces[m.position].interfSelect(importHidden), name)
proc someSymAmb*(g: ModuleGraph; m: PSym; name: PIdent; amb: var bool): PSym =
let importHidden = optImportHidden in m.options
if importHidden: ensureHiddenIface(g, m.position)
var ti: TIdentIter = default(TIdentIter)
result = initIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden), name)
if result != nil and nextIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden)) != nil:
@@ -397,75 +271,29 @@ iterator procInstCacheItems*(g: ModuleGraph; s: PSym): PInstantiation =
proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
## returns the requested attached operation for type `t`. Can return nil
## if no such operation exists.
if g.attachedOps[op].contains(t.bindingId):
result = g.attachedOps[op][t.bindingId]
if g.attachedOps[op].contains(t.itemId):
result = g.attachedOps[op][t.itemId]
elif g.config.cmd in {cmdNifC, cmdM}:
# Fall back to key-based lookup for NIF-loaded hooks
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.
# 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:
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)
g.loadedOps[op][key] = value
g.attachedOps[op][t.bindingId] = 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.bindingId] = value
g.attachedOps[op][t.itemId] = value
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
## Overload that takes ItemId directly, useful for registering hooks from NIF index.
@@ -473,7 +301,7 @@ proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttach
proc setAttachedOpPartial*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
## we also need to record this to the packed module.
g.attachedOps[op][t.bindingId] = value
g.attachedOps[op][t.itemId] = value
proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) {.inline.} =
discard
@@ -486,6 +314,10 @@ proc addDispatchers*(g: ModuleGraph, value: PSym) =
# TODO: add it for packed modules
g.dispatchers.add value
iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[PSym]): PSym =
for it in list.mitems:
yield it
proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[PSym]) =
# TODO: add it for packed modules
g.methodsPerType[id] = methods
@@ -495,122 +327,31 @@ proc addNifReplayAction*(g: ModuleGraph; module: int32; n: PNode) =
g.nifReplayActions.mgetOrPut(module, @[]).add n
iterator getMethodsPerType*(g: ModuleGraph; t: PType): PSym =
if g.methodsPerType.contains(t.bindingId):
for it in mitems g.methodsPerType[t.bindingId]:
if g.methodsPerType.contains(t.itemId):
for it in mitems g.methodsPerType[t.itemId]:
yield it
proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
result = g.enumToStringProcs.getOrDefault(t.bindingId)
if result == nil and g.config.cmd in {cmdNifC, cmdM}:
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
result = g.loadedEnumToStringProcs.getOrDefault(key)
result = g.enumToStringProcs[t.itemId]
assert result != nil
proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
g.enumToStringProcs[t.bindingId] = value
g.enumToStringProcs[t.itemId] = value
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
# 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)
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)
iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
if g.methodsPerGenericType.contains(t.bindingId):
for it in mitems g.methodsPerGenericType[t.bindingId]:
if g.methodsPerGenericType.contains(t.itemId):
for it in mitems g.methodsPerGenericType[t.itemId]:
yield (it[0], it[1])
proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) =
g.methodsPerGenericType.mgetOrPut(t.bindingId, @[]).add (col, m)
g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, m)
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
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 logCppMember*(g: ModuleGraph; s: PSym) =
## Log a C++ `{.member.}`/`{.virtual.}`/`{.constructor.}` registration (and the
## `importcpp` default-initializer flavour) so the NIF backend can rebuild
## `memberProcsPerType`/`initializersPerType`, which live only in the sem
## process. Without them the per-module backend emitted the struct WITHOUT its
## in-class member declarations and the out-of-class definitions did not match
## ("no declaration matches 'void Doo::memberProc()'").
##
## No type key: `replayCppMember` re-derives the type from the routine's
## signature exactly as `semCppMember` does, so nothing has to survive the
## round trip except the routine itself.
if g.config.cmd in {cmdNifC, cmdM}:
g.opsLog.add LogEntry(kind: CppMemberEntry, module: s.itemId.module.int,
key: "", sym: s)
proc replayCppMember*(g: ModuleGraph; s: PSym) =
## Inverse of `logCppMember`, mirroring `semstmts.semCppMember`'s derivation.
if s == nil or s.typ == nil: return
if sfImportc notin s.flags:
var typ = if sfConstructor in s.flags: s.typ.returnType else: s.typ.firstParamType
if typ != nil and typ.kind == tyPtr and sfConstructor notin s.flags:
typ = typ.elementType
if typ != nil and typ.kind == tyObject:
let procs = addr g.memberProcsPerType.mgetOrPut(typ.bindingId, @[])
for prc in procs[]:
if prc == s: return
procs[].add s
else:
let typ = s.typ.returnType
if typ != nil and typ.kind == tyObject and
typ.bindingId notin g.initializersPerType and s.typ.n != nil:
# The default values sem read off the `nkIdentDefs` live on the param syms.
var call = newTree(nkCall, newSymNode(s))
var isInitializer = s.typ.n.len > 1
for i in 1 ..< s.typ.n.len:
let p = s.typ.n[i]
if p.kind != nkSym or p.sym.ast == nil or p.sym.ast.kind == nkEmpty:
isInitializer = false
break
call.add p.sym.ast
if isInitializer:
g.initializersPerType[typ.bindingId] = call
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
@@ -619,83 +360,9 @@ proc logGenericInstance*(g: ModuleGraph; inst: PSym) =
let ownerModule = inst.itemId.module.int
g.opsLog.add LogEntry(kind: GenericInstEntry, module: ownerModule, sym: inst)
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
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 hasDisabledOp(g: ModuleGraph; t: PType; kind: TTypeAttachedOp): bool =
let op = getAttachedOp(g, t, kind)
result = op != nil and sfError in op.flags
proc hasDisabledAsgn*(g: ModuleGraph; t: PType): bool =
result = hasDisabledOp(g, t, attachedAsgn)
proc hasDisabledDup*(g: ModuleGraph; t: PType): bool =
result = hasDisabledOp(g, t, attachedDup)
let op = getAttachedOp(g, t, attachedAsgn)
result = op != nil and sfError in op.flags
proc copyTypeProps*(g: ModuleGraph; module: int; dest, src: PType) =
for k in low(TTypeAttachedOp)..high(TTypeAttachedOp):
@@ -710,49 +377,21 @@ proc loadCompilerProc*(g: ModuleGraph; name: string): PSym =
when not defined(nimKochBootstrap):
# Try to resolve from NIF for both cmdNifC and cmdM (which uses NIF files)
if g.config.cmd in {cmdNifC, cmdM}:
# First try system module (most compilerprocs are there).
# Only consult the NIF if it actually exists: under nimsuggest's cold
# cache (ideActive) system is compiled from source and has no NIF yet,
# in which case the proc is already registered in-memory and the caller
# found/falls back to it — so degrade to nil instead of asserting.
# First try system module (most compilerprocs are there)
let systemFileIdx = g.config.m.systemFileIdx
if systemFileIdx != InvalidFileIdx and not g.withinSystem and
fileExists(toNifFilename(g.config, systemFileIdx)):
if systemFileIdx != InvalidFileIdx and not g.withinSystem:
# Only try to load from NIF if the file exists (it may not during initial ic build)
result = tryResolveCompilerProc(ast.program, name, systemFileIdx)
if result != nil:
strTableAdd(g.compilerprocs, result)
return result
# `nim nifc`: a module loaded from a NIF is named by its mangled suffix
# (`thrkxstl4`), not by its source name, and its file index resolves to
# that suffix too — so the `"threadpool"` match below can never fire and
# `spawn`, expanded at codegen time, died on `system module needs:
# nimArgsPassingDone`. The backend loads the WHOLE program before
# codegen starts, so just consult every loaded module's index; a miss is
# final for the rest of the process (nothing more gets loaded) and is
# remembered, because `getCompilerProc` is also used as a mere presence
# probe and would otherwise rescan every index on every call.
if g.config.cmd == cmdNifC:
if name in g.missingCompilerProcs: return nil
for moduleIdx in 0..<g.ifaces.len:
let module = g.ifaces[moduleIdx].module
if module == nil or module.position.FileIndex == systemFileIdx: continue
if not fileExists(toNifFilename(g.config, module.position.FileIndex)):
continue
result = tryResolveCompilerProc(ast.program, name, module.position.FileIndex)
if result != nil:
strTableAdd(g.compilerprocs, result)
return result
g.missingCompilerProcs.incl name
return nil
# Try threadpool module (some compilerprocs like FlowVar are there)
# Find threadpool module by searching loaded modules
for moduleIdx in 0..<g.ifaces.len:
let module = g.ifaces[moduleIdx].module
if module != nil and module.name.s == "threadpool":
let threadpoolFileIdx = module.position.FileIndex
if not fileExists(toNifFilename(g.config, threadpoolFileIdx)): break
result = tryResolveCompilerProc(ast.program, name, threadpoolFileIdx)
if result != nil:
strTableAdd(g.compilerprocs, result)
@@ -772,6 +411,10 @@ proc hash*(u: SigHash): Hash =
proc hash*(x: FileIndex): Hash {.borrow.}
template getPContext(): untyped =
when c is PContext: c
else: c.c
when defined(nimsuggest):
template onUse*(info: TLineInfo; s: PSym; isGenericInstance = false) = discard
template onDefResolveForward*(info: TLineInfo; s: PSym) = discard
@@ -895,7 +538,6 @@ 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()
@@ -926,15 +568,6 @@ 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) =
@@ -1017,140 +650,9 @@ proc needsCompilation*(g: ModuleGraph, fileIdx: FileIndex): bool =
proc getBody*(g: ModuleGraph; s: PSym): PNode {.inline.} =
result = s.ast[bodyPos]
if result != nil and nfLazyBody in result.flags and forceLazyBodyHook != nil:
# Sanctioned body-access gate (see astdef.bodyPos): materialize the deferred
# IC body so callers may safely touch `.sons` directly, not only via `len`.
forceLazyBodyHook(result)
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 CppMemberEntry:
replayCppMember(g, 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):
# `SkipInterfaceTables`: `interf`/`interfHidden` here are scratch tables
# shared by every iteration and never read — this module is a
# dep-of-a-dep, so none of its symbols are visible to the module being
# semchecked. Building them called `loadSymFromIndexEntry` for every
# index entry of every closure member.
let precomp = loadNifModule(ast.program, suffix, interf, interfHidden,
{SkipInterfaceTables})
registerLoadedHooks(g, precomp.logOps)
# Record this transitively-loaded module so the sem driver applies its
# VM-level load effects (macro-cache replay + `{.compileTime.}` global init)
# exactly as for a direct import — see `pendingNifInit`. A throwaway module
# symbol (same shape as moduleFromNifFile's) gives the drain an idgen/info
# context; it is not registered, so a later direct import still loads fully.
if g.config.cmd == cmdM:
let m = PSym(kindImpl: skModule, itemId: itemId(int32(fileIdx), 0'i32),
name: getIdent(g.cache, splitFile(toFullPath(g.config, fileIdx)).name),
infoImpl: newLineInfo(fileIdx, 1, 1), positionImpl: int(fileIdx))
setOwner(m, getPackage(g.config, g.cache, fileIdx))
g.pendingNifInit.add (m, precomp.topLevel)
# Rebuild generic TYPE- and PROC-instance offers across the WHOLE closure,
# not just direct imports (`moduleFromNifFile`). An instance is frozen at
# the FIRST module to create it (in a scope where its body's symbols
# resolve unambiguously); a consumer many imports away must REUSE it rather
# than re-instantiate in its own scope, which may resolve a body symbol
# differently — a divergent `compiles()`-dependent array bound (SSZ
# `HashArray[8192, Gwei]`, type offer), or an ambiguous unqualified ident
# leaked from an unrelated import (`fromRaw` -> `SkRawPublicKeySize` from
# both `secp` and `secp256k1`, proc offer). Direct-only rebuild left the
# deep offer invisible when the clean instance lives a transitive hop away.
for off in precomp.typeOffers:
g.typeInstCache.mgetOrPut(off.generic.itemId, @[]).add off.inst
for off in precomp.genericOffers:
g.procInstCache.mgetOrPut(off.generic.itemId, @[]).add PInstantiation(
sym: off.inst, concreteTypes: off.concreteTypes,
genericParamsCount: off.genericParamsCount, compilesId: 0)
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
g.ifaces[fIdx.int].hiddenPending = true
proc moduleFromNifFile*(g: ModuleGraph; fileIdx: FileIndex;
flags: set[LoadFlag] = {}): PrecompiledModule =
## Returns 'nil' if the module needs to be recompiled.
@@ -1159,141 +661,42 @@ when not defined(nimKochBootstrap):
if not fileExists(toNifFilename(g.config, fileIdx)):
return PrecompiledModule(module: nil)
# NOTE: direction-(c) experiment (refuse to NIF-serve include-bearing modules
# under ideActive, forcing a source compile) is disabled — it reproduces the
# known sibling-resolution corruption (system.string -> excpt.nim:746). The
# cold-include *discovery* scan (scanIncludeGraph) stays; the round-trip
# fidelity of included symbols is the separate, still-open loader problem.
when false:
if g.config.ideActive and not g.withinSystem and
fileIdx != g.config.m.systemFileIdx and
nifModuleHasIncludes(g.config, fileIdx):
return PrecompiledModule(module: nil)
# Create module symbol
let filename = AbsoluteFile toFullPath(g.config, fileIdx)
let m = PSym(
kindImpl: skModule,
itemId: itemId(int32(fileIdx), 0'i32),
itemId: ItemId(module: int32(fileIdx), item: 0'i32),
name: getIdent(g.cache, splitFile(filename).name),
infoImpl: newLineInfo(fileIdx, 1, 1),
positionImpl: int(fileIdx))
setOwner(m, getPackage(g.config, g.cache, fileIdx))
# Register module in graph
registerModule(g, m)
# ... and, in the BACKEND, bind its NIF name to THIS symbol before anything
# in the file is decoded, so the loader never mints a second `skModule` for
# it (see `registerModuleSelfSym`). Backend-only: under `nim m` a module is
# loaded for its INTERFACE, and re-pointing the owner slot of every loaded
# symbol at the freshly built module sym changes what sem sees for an
# imported routine — `times.toDateTimeByWeek` then lost its inferred
# `raises` and the importer failed with "can raise an unlisted exception".
if g.config.cmd == cmdNifC:
registerModuleSelfSym(ast.program, cachedModuleSuffix(g.config, fileIdx), m)
result = loadNifModule(ast.program, fileIdx,
g.ifaces[fileIdx.int].interf,
g.ifaces[fileIdx.int].interfHidden, flags)
# The hidden-only half was not built; `ensureHiddenIface` will, if asked.
g.ifaces[fileIdx.int].hiddenPending = true
result.module = m
# Restore the module symbol's persisted flags (see ast2nif `(modflags)`);
# `cgen.genTopLevelStmt` gates the destructor pass on `sfInjectDestructors`.
if (result.moduleFlags and ModFlagInjectDestructors) != 0:
m.incl sfInjectDestructors
for (mname, msuffix) in result.reexportedModules:
let ms = materializeReexportedModule(g, mname, msuffix)
if ms != nil:
strTableAdd(g.ifaces[fileIdx.int].interf, ms)
# Re-establish include->module mapping so nimsuggest's `parentModule` can map
# a query in an included file back to this (NIF-loaded) module and recompile
# it, exactly as it does for a from-source module. Without this the include
# relationship is invisible for NIF-served modules.
for incPath in result.includes:
g.addIncludeDep(fileIdx, fileInfoIdx(g.config, AbsoluteFile incPath))
# 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)
# Rebuild `typeInstCache` from this module's generic TYPE-instance OFFERS so a
# consumer's `searchInstTypes` reuses the baked instance (e.g. an SSZ
# `HashArray` whose array bound depends on import-scope-sensitive `compiles()`)
# rather than re-instantiating it with a divergent bound — see ast2nif's
# `(toffer …)`. Keyed by the generic body sym's itemId, as `searchInstTypes`.
for off in result.typeOffers:
g.typeInstCache.mgetOrPut(off.generic.itemId, @[]).add off.inst
# 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 "dispatch buckets already rebuilt by registerLoadedHooks"
discard "todo"
of EnumToStrEntry:
discard "todo"
of GenericInstEntry:
raiseAssert "GenericInstEntry should not be in the NIF index"
of HookEntry, EnumToStrEntry, CppMemberEntry:
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)
# Record the directly-loaded module for the same VM-level load effects as its
# transitive deps (`pendingNifInit`). AFTER loadTransitiveHooks so the drain
# applies deps before the dependent (macro-cache order).
g.pendingNifInit.add (m, result.topLevel)
proc isModuleFile(g: ModuleGraph; fileIdx: FileIndex): bool =
let i = fileIdx.int32
i >= 0 and i < g.ifaces.len and g.ifaces[i].module != nil
proc registerIncluderFromNif*(g: ModuleGraph; fileIdx: FileIndex): bool =
## Targeted cold-include discovery for nimsuggest: scan the nimcache NIFs
## (`scanIncludeGraph`) for a module whose include-set contains *this* file
## and register only that single include->module edge in `inclToMod`, so a
## query inside the include file resolves its includer via `parentModule`.
##
## Deliberately targeted: registering *every* include relationship (i.e. also
## `system`'s own `include`s) eagerly assigns FileIndexes and pollutes
## `inclToMod`, which perturbs the NIF line-info decode of unrelated modules
## (`system.string` then resolves into `excpt.nim`). Touch nothing but the
## one edge we need.
let target = toFullPath(g.config, fileIdx)
for (includer, includes) in scanIncludeGraph(g.config):
for incFile in includes:
if cmpPaths(incFile, target) == 0:
g.addIncludeDep(fileInfoIdx(g.config, AbsoluteFile includer), fileIdx)
return true
result = false
proc needsIncludeScan*(g: ModuleGraph; fileIdx: FileIndex): bool =
## True when `fileIdx` is neither a known module of its own nor an
## already-known include file — i.e. a cold-opened file whose includer we
## must still discover via `registerIncluderFromNif`.
not g.isModuleFile(fileIdx) and not g.inclToMod.hasKey(fileIdx)
proc configComplete*(g: ModuleGraph) =
#rememberStartupConfig(g.startupPackedConfig, g.config)
@@ -1322,16 +725,7 @@ proc getPackage*(graph: ModuleGraph; fileIdx: FileIndex): PSym =
proc belongsToStdlib*(graph: ModuleGraph, sym: PSym): bool =
## Check if symbol belongs to the 'stdlib' package.
# 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
sym.getPackageSymbol.getPackageId == graph.systemModule.getPackageId
proc fileSymbols*(graph: ModuleGraph, fileIdx: FileIndex): SuggestFileSymbolDatabase =
result = graph.suggestSymbols.getOrDefault(fileIdx, newSuggestFileSymbolDatabase(fileIdx, optIdeExceptionInlayHints in graph.config.globalOptions))

View File

@@ -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(int32(fileIdx), 0'i32),
result = PSym(kindImpl: skModule, itemId: ItemId(module: int32(fileIdx), item: 0'i32),
name: getModuleIdent(graph, filename),
infoImpl: newLineInfo(fileIdx, 1, 1))
if not isNimIdentifier(result.name.s):

View File

@@ -24,6 +24,10 @@ template instLoc*(): InstantiationInfo = instantiationInfo(-2, fullPaths = true)
template toStdOrrKind(stdOrr): untyped =
if stdOrr == stdout: stdOrrStdout else: stdOrrStderr
proc toLowerAscii(a: var string) {.inline.} =
for c in mitems(a):
if isUpperAscii(c): c = char(uint8(c) xor 0b0010_0000'u8)
proc flushDot*(conf: ConfigRef) =
## safe to call multiple times
let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr
@@ -79,8 +83,7 @@ proc canonicalCase(path: var string) {.inline.} =
## the idea is to only use this for checking whether a path is already in
## the table but otherwise keep the original case
when FileSystemCaseSensitive: discard
else:
for c in mitems(path): c = toLowerAscii(c)
else: toLowerAscii(path)
proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
var
@@ -122,25 +125,12 @@ 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, expandOrPseudo(filename.string), isKnownFile)
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), isKnownFile)
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile): FileIndex =
var dummy: bool = false
fileInfoIdx(conf, expandOrPseudo(filename.string), dummy)
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), dummy)
proc registerNifSuffix*(conf: ConfigRef; suffix: string; isKnownFile: var bool): FileIndex =
result = conf.m.filenameToIndexTbl.getOrDefault(suffix, InvalidFileIdx)
@@ -348,7 +338,7 @@ proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
## This is used for 'nim dump' etc. where we don't have nimsuggest
## support.
#if conf.ideActive and optCDebug notin gGlobalOptions: return
#if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
let sep = if msgNoUnitSep notin flags: conf.unitSep else: ""
if not isNil(conf.writelnHook) and msgSkipHook notin flags:
conf.writelnHook(s & sep)
@@ -454,8 +444,8 @@ To create a stacktrace, rerun compilation with './koch temp $1 <file>', see $2 f
proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string, ignoreMsg: bool) =
if msg in fatalMsgs:
if conf.ideActive: log(s)
if not conf.ideActive or msg != errFatal:
if conf.cmd == cmdIdeTools: log(s)
if conf.cmd != cmdIdeTools or msg != errFatal:
quit(conf, msg)
if msg >= errMin and msg <= errMax or
(msg in warnMin..hintMax and msg in conf.warningAsErrors and not ignoreMsg):
@@ -469,7 +459,7 @@ proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string,
raiseRecoverableError(s)
else:
quit(conf, msg)
elif eh == doAbort and not conf.ideActive:
elif eh == doAbort and conf.cmd != cmdIdeTools:
quit(conf, msg)
elif eh == doRaise:
raiseRecoverableError(s)
@@ -500,7 +490,7 @@ proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
info = context.info
proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
msg >= errGenerated and conf.ideActive and optIdeDebug notin conf.globalOptions
msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
conf.m.fileInfos[fileIdx.int32].lines.add line
@@ -521,9 +511,6 @@ 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 ""
@@ -658,7 +645,7 @@ proc warningDeprecated*(conf: ConfigRef, info: TLineInfo = gCmdLineInfo, msg = "
message(conf, info, warnDeprecated, msg)
proc internalErrorImpl(conf: ConfigRef; info: TLineInfo, errMsg: string, info2: InstantiationInfo) =
if (conf.ideActive or conf.cmd == cmdCheck) and conf.structuredErrorHook.isNil: return
if conf.cmd in {cmdIdeTools, cmdCheck} and conf.structuredErrorHook.isNil: return
writeContext(conf, info)
liMessage(conf, info, errInternal, errMsg, doAbort, info2)

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ import
import "../dist/nimony/src/lib" / nifbuilder
import "../dist/nimony/src/models" / nifler_tags
import icmodnames
import "../dist/nimony/src/gear2" / modnames
## This was copied from Nifler's bridge.nim. However, this code will evolve
## in a different direction as it needs to translate the semchecked AST which

View File

@@ -1,247 +0,0 @@
## nifstreams — the classic NIF streaming surface, used ONLY by this compiler's
## IC modules: ast2nif, deps, modulegraphs and pipelines import it and must keep
## compiling unchanged across nimony's own refactorings.
##
## It used to live in `dist/nimony/src/lib`, which is where the rest of the NIF
## stack still is. It does not belong there: nimony's own code imports nifpools
## (via nifprelude) and is under standing orders never to import this file, so
## nothing over there ever exercised it — which is exactly how it came to hand
## out `TagLit` where every caller here tests for `ParLe` (see `next`), silently
## emptying the IC build graph. A compatibility shim with exactly one consumer
## belongs in the consumer's repo, where its tests run and its contract is
## somebody's problem.
##
## Everything it adapts (`nifpools`, `nifreader`, `lineinfos`) still comes from
## `dist/nimony`; only the adapter moved.
##
## Everything here is an honest adapter, not a fake:
## * Floats get a REAL interning pool: `pool.floats.getOrIncl` returns a
## `FloatId` index, `floatToken` packs it into a genuine `FloatLit` NifToken
## (transit-only: it must never enter a TokenBuf, whose float encoding is
## inline multi-token), and `pool.floats[t.floatId]` decodes it — lossless.
## * `Stream`/`next` wrap the textual nifreader; the unified NifKind has real
## `ParLe`/`ParRi`/`EofToken` members, so structural scanners (deps.nim)
## see the exact classic kinds. Ident/StringLit/Symbol payloads are interned
## into the global `pool`, so `pool.strings[t.litId]` works as before.
## Number tokens keep their KIND only (a 4-byte token cannot always carry
## the value); classic scanners never read those payloads.
import std / tables
import "../dist/nimony/src/lib" / nifpools
# `except`: the frontend went all-NifLineInfo; the classic side keeps speaking
# PackedLineInfo, so nifpools' same-name/same-params variants must not leak
# through (`info(n: NifToken)` differs only in return type, `NoLineInfo` is a
# same-name const of a different type — either would be ambiguous or wrong for
# ast2nif). The classic replacements are defined below / come from lineinfos.
# `tagId` is excluded for a different reason: nifpools decodes the 9-bit field
# of a real `TagLit`, but this surface hands out `ParLe` tokens whose tag id
# fills the whole 28-bit payload (see `next`), so the decode below is the only
# correct one here.
export nifpools except info, NoLineInfo, tagId
import "../dist/nimony/src/lib" / lineinfos
export lineinfos
from "../dist/nimony/src/lib" / nifreader import Reader, ExpandedToken, decodeStr
# ── Classic names the Nim compiler side still uses ───────────────────────
type
PackedToken* = NifToken ## ast2nif still says PackedToken
# Raw payload decodes, sound ONLY on this surface. Every token here comes from
# `next` or the classic `symToken`/`strToken`/`identToken` constructors, which
# intern EVERY literal — including names of at most `StrInlineMaxLen` bytes,
# which the nifcore builders would instead store inside the token. On such an
# inline token the payload is packed bytes, not an id, so nifpools (nimony's own
# surface, where buffers come from the builders) deliberately has no equivalent:
# there it must go through a `Cursor`, which handles both encodings.
proc tagId*(n: NifToken): TagId {.inline.} = TagId(uoperand(n))
## Classic `ParLe` tokens (see `next`) keep the tag id in the full 28-bit
## payload rather than in `TagLit`'s 9-bit field: `globalTags` already holds
## 355 tags before the Nim compiler registers its own dialect, so a 512-tag
## ceiling is not a ceiling this surface can live under.
proc litId*(n: NifToken): StrId {.inline.} = StrId(uoperand(n) shr 1)
proc symId*(n: NifToken): SymId {.inline.} = SymId(uoperand(n) shr 1)
proc litId*(c: Cursor): StrId {.inline.} = strId(c)
proc firstSon*(n: Cursor): Cursor {.inline.} = childCursor(n)
var lineMan*: LineInfoManager
## The classic packed line-info side channel (`pool.man`). Frontend code no
## longer uses it — it lives here purely for ast2nif's writer, which packs
## `TLineInfo` into `PackedLineInfo` and unpacks on emit.
template files*(p: Pool): untyped = p.filenames
template tags*(p: Pool): untyped = globalTags.tags
template man*(p: Pool): untyped = lineMan
proc info*(n: NifToken): PackedLineInfo {.inline.} = lineinfos.NoLineInfo
## Classic tokens carried their line info inline; a bare 4-byte nifcore
## token cannot, so reading it back yields `NoLineInfo` (ast2nif's
## `emitInfo(t.info)` then emits nothing — matching the writer, which
## attaches real positions at the builder level instead).
proc info*(c: Cursor): PackedLineInfo {.inline.} =
## Classic packed view of a cursor's line info (ast2nif shadows this with
## its own NifLineInfo template; kept for any other classic reader).
let li = rawLineInfo(c)
if li.file.isValid: pack(lineMan, li.file, li.line, li.col)
else: lineinfos.NoLineInfo
type
IntId* = distinct int64 ## value carriers (nifcore stores inline)
UIntId* = distinct uint64
## Identity proxies: the id already carries the value, `[]` returns it.
IntegersProxy* = object
UIntegersProxy* = object
func `==`*(a, b: IntId): bool {.borrow.}
func `==`*(a, b: UIntId): bool {.borrow.}
template integers*(p: Pool): IntegersProxy = IntegersProxy()
template uintegers*(p: Pool): UIntegersProxy = UIntegersProxy()
template `[]`*(x: IntegersProxy; id: IntId): int64 = int64(id)
template `[]`*(x: UIntegersProxy; id: UIntId): uint64 = uint64(id)
# nifcore stores integers inline: the "id" is the value itself.
template getOrIncl*(x: IntegersProxy; v: int64): IntId = IntId(v)
template getOrIncl*(x: UIntegersProxy; v: uint64): UIntId = UIntId(v)
proc intId*(n: NifToken): IntId {.inline.} = IntId(n.soperand)
proc uintId*(n: NifToken): UIntId {.inline.} = UIntId(uoperand(n))
proc intId*(c: Cursor): IntId {.inline.} = IntId(intVal(c))
proc uintId*(c: Cursor): UIntId {.inline.} = UIntId(uintVal(c))
proc addIntLit*(dest: var TokenBuf; id: IntId; info: PackedLineInfo) =
addIntLit(dest, int64(id))
if info.isValid:
let u = unpack(lineMan, info)
appendLineInfo(dest, u.file, u.line, u.col)
# Classic single-token constructors with a (dropped) line-info argument.
proc strToken*(s: StrId; info: PackedLineInfo): NifToken {.inline.} = strLitToken(s)
proc symToken*(id: SymId; info: PackedLineInfo): NifToken {.inline.} = symToken(id)
proc identToken*(id: StrId; info: PackedLineInfo): NifToken {.inline.} = identToken(id)
proc dotToken*(info: PackedLineInfo): NifToken {.inline.} = dotToken()
proc charToken*(ch: char; info: PackedLineInfo): NifToken {.inline.} = charToken(ch)
# ── Classic interned float literals (ast2nif) ────────────────────────────
type
FloatId* = distinct uint32 ## 1-based index into the global float pool
FloatPool* = object
values: seq[float64]
lookup: Table[uint64, uint32] # bit pattern -> 1-based id
func `==`*(a, b: FloatId): bool {.borrow.}
var globalFloats*: FloatPool
template floats*(p: Pool): var FloatPool = globalFloats
proc getOrIncl*(fp: var FloatPool; v: float64): FloatId =
let bits = cast[uint64](v)
let existing = fp.lookup.getOrDefault(bits, 0'u32)
if existing != 0'u32:
result = FloatId(existing)
else:
fp.values.add v
let id = uint32(fp.values.len)
fp.lookup[bits] = id
result = FloatId(id)
proc `[]`*(fp: FloatPool; id: FloatId): float64 {.inline.} =
fp.values[int(uint32(id)) - 1]
proc floatToken*(id: FloatId; info: PackedLineInfo): NifToken {.inline.} =
## Transit-only token: carries the pool index so the receiver can decode it
## via `pool.floats[t.floatId]`. It must never be appended to a TokenBuf
## (nifcore stores floats inline as a multi-token encoding); the line info
## is dropped like in the other classic token constructors.
NifToken((uint32(id) shl KindBits) or uint32(FloatLit))
proc floatId*(n: NifToken): FloatId {.inline.} = FloatId(uoperand(n))
# ── Classic streaming text reader (deps.nim) ─────────────────────────────
type
Stream* = object
r*: Reader
proc parLeToken*(t: TagId): NifToken {.inline.} =
## The classic surface's opening-tag token: kind `ParLe`, tag id in the
## payload. Transit-only, like `floatToken` — a `ParLe` never appears in a
## binary token stream, so this must not be appended to a TokenBuf.
NifToken((uint32(t) shl KindBits) or uint32(ParLe))
proc open*(filename: string): Stream =
Stream(r: nifreader.open(filename))
proc close*(s: var Stream) =
nifreader.close(s.r)
proc next*(s: var Stream): NifToken =
## One classic packed token per call. Pool-referencing kinds are interned
## into the global `pool`/`globalTags`, so `.litId`/`.tagId` accessors and
## `pool.strings[...]`/`pool.tags[...]` lookups behave exactly as classic
## nifstreams did. Kinds without a pool payload come back kind-only.
var t = default(ExpandedToken)
nifreader.next(s.r, t)
case t.tk
of ParLe:
# NOT `tagLitToken`: that would set the kind to `TagLit`, and every classic
# structural scanner tests for `ParLe` (deps.nim walks the import graph that
# way). Emitting `TagLit` here made every one of those tests silently fail —
# the scanner saw an unknown token, skipped the subtree, and the Nim
# compiler's IC build graph came out missing most of its edges.
result = parLeToken(registerTag(globalTags, decodeStr(s.r, t)))
of Ident:
result = identToken(pool.strings.getOrIncl(decodeStr(s.r, t)))
of StrLit:
result = strLitToken(pool.strings.getOrIncl(decodeStr(s.r, t)))
of Symbol:
result = symToken(pool.syms.getOrIncl(decodeStr(s.r, t)))
of SymbolDef:
result = symdefToken(pool.syms.getOrIncl(decodeStr(s.r, t)))
else:
# ParRi/EofToken/DotToken/CharLit/numbers: correct kind, no payload.
result = NifToken(uint32(t.tk))
when isMainModule:
# `nim c -r compiler/nifstreams.nim`.
#
# The promise this checks: structural scanners see the CLASSIC kinds. Nim's deps.nim walks
# the import graph by testing `t.kind == ParLe` and then reading
# `pool.tags[t.tagId]`. Hand out nifcore's own `TagLit` instead and every one
# of those tests falls through silently — the scanner treats the opener as an
# unknown token, skips the subtree, and Nim's IC build graph comes out missing
# most of its edges while each individual file still "parses" fine.
import std / [os, syncio]
from "../dist/nimony/src/lib" / nifreader import processDirectives
from std / assertions import assert
let f = getTempDir() / "nifstreams_selftest.nif"
syncio.writeFile f, "(.nif27)\n(stmts (import (infix / std (bracket os osproc))) (x \"s\" y))\n"
var kinds: seq[NifKind] = @[]
var tagNames: seq[string] = @[]
var lits: seq[string] = @[]
var s = nifstreams.open(f)
discard processDirectives(s.r)
while true:
let t = next(s)
if t.kind == EofToken: break
kinds.add t.kind
case t.kind
of ParLe: tagNames.add pool.tags[t.tagId]
of Ident, StrLit: lits.add pool.strings[t.litId]
else: discard
nifstreams.close(s)
removeFile f
assert tagNames == @["stmts", "import", "infix", "bracket", "x"], $tagNames
assert lits == @["/", "std", "os", "osproc", "s", "y"], $lits
assert ParRi in kinds, "closers must stay classic too"
assert TagLit notin kinds, "an opener must arrive as ParLe, not TagLit"
echo "success"

View File

@@ -183,6 +183,12 @@ 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

View File

@@ -12,12 +12,7 @@ define:nimPreviewNonVarDestructor
define:nimPreviewCheckedClose
define:nimPreviewAsmSemSymbol
define:nimPreviewCStringComparisons
#define:nimPreviewDuplicateModuleError
# Incompatible with Nimony's compat2.nim for now
# NOTE: `-d:virtualParRi` (jump-encoded ParLe + elided ParRi) is NOT yet enabled:
# the IC writer assembles buffers by raw token splicing (`dest.add content[i]`),
# which does not seal scopes the way `addParRi` does, so sealed `(stmts)` get
# jump=0 and serialize empty. Enabling it needs writer buffer-sealing work first.
define:nimPreviewDuplicateModuleError
threads:off

View File

@@ -28,13 +28,10 @@ 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 defined(tinyc): # == hasTinyCBackend; spelled out for the IC dep scanner
when hasTinyCBackend:
import tccgen
when defined(profiler) or defined(memProfiler):
@@ -99,11 +96,6 @@ 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
@@ -115,14 +107,6 @@ 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 in {cmdIc, cmdTrack} or isIcDriver(conf):
ensureIcConfig(conf)
var graph = newModuleGraph(cache, conf)
if not self.loadConfigsAndProcessCmdLine(cache, conf, graph):
return
@@ -134,14 +118,9 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
if conf.selectedGC == gcUnselected:
if conf.backend in {backendC, backendCpp, backendObjc} or
(conf.cmd in cmdDocLike and conf.backend != backendJs) or
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM, cmdTrack}:
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM}:
initOrcDefines(conf)
if conf.selectedStrings == stringSso and
conf.selectedGC notin {gcArc, gcOrc, gcYrc, gcAtomicArc}:
rawMessage(conf, errGenerated,
"--strings:sso requires --mm:arc, --mm:orc, --mm:yrc, or --mm:atomicArc")
mainCommand(graph)
if conf.hasHint(hintGCStats): echo(GC_getStatistics())
#echo(GC_getStatistics())

View File

@@ -11,7 +11,7 @@
import
llstream, commands, msgs, lexer, ast,
options, idents, wordrecg, lineinfos, pathutils, scriptconfig, icconfig
options, idents, wordrecg, lineinfos, pathutils, scriptconfig
import std/[os, strutils, strtabs]
@@ -246,16 +246,6 @@ 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
@@ -316,7 +306,7 @@ proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef; idgen:
if conf.cmd == cmdNimscript:
showHintConf()
conf.configFiles.setLen 0
if not conf.ideActive and conf.cmd notin {cmdCheck, cmdDump}:
if conf.cmd notin {cmdIdeTools, cmdCheck, cmdDump}:
if conf.cmd == cmdNimscript:
runNimScriptIfExists(conf.projectFull, isMain = true)
else:

View File

@@ -29,42 +29,6 @@ const
nimEnableCovariance* = defined(nimEnableCovariance)
icFormatVersion* = "38"
## 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.
## v7 (=31): anonymous wrapper types (`var T`, `lent T`, `sink T`, tuples)
## are named by their CONTENT instead of `itemId.item`, the module-wide
## type-mint counter (see ast2nif.CanonTypeKinds). Old caches name the same
## type differently, so every `.s.bif` reference would dangle.
## v8 (=32): the same for `tyProc`, except that a proc type which is a
## routine's SIGNATURE is named after that routine rather than by content
## (see ast2nif.sigRoutineOf). Renames types, so old caches dangle again.
## v9 (=33): and for the per-module `int`/`float` LITERAL COPIES (see
## ast2nif.CanonLitCopyKinds), the last mover that broke a build outright
## (`symbol has no offset` out of a cached `.t.bif`). Renames types again.
type # please make sure we have under 32 options
# (improves code efficiency a lot!)
TOption* = enum # **keep binary compatible**
@@ -150,7 +114,6 @@ type # please make sure we have under 32 options
optDocRaw # for documentation: Don't render markdown for JSON output
optItaniumMangle # mangling follows the Itanium spec
optCompress # turn on AST compression by converting it to NIF
optGenBif # generate semantic BIF alongside ordinary code generation
optWithinConfigSystem # we still compile within the configuration system
TGlobalOptions* = set[TGlobalOption]
@@ -194,6 +157,7 @@ type
cmdCheck # semantic checking for whole project
cmdM # only compile a single
cmdParse # parse a single file (for debugging)
cmdIdeTools # ide tools (e.g. nimsuggest)
cmdNimscript # evaluate nimscript
cmdDoc0
cmdDoc # convert .nim doc comments to HTML
@@ -215,8 +179,6 @@ 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)
cmdTrack # `nim track --def/--usages`: IC frontend build + NIF scan for IDE queries
const
cmdBackends* = {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
@@ -297,14 +259,6 @@ type
## Old transformation for closures in JS backend
noPanicOnExcept
## don't panic on bare except
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
@@ -313,10 +267,6 @@ type
ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccBcc, ccVcc,
ccTcc, ccEnv, ccIcl, ccIcc, ccClangCl, ccHipcc, ccNvcc
StringsMode* = enum
stringDefault = "default"
stringSso = "sso"
ExceptionSystem* = enum
excNone, # no exception system selected yet
excSetjmp, # setjmp based exception handling
@@ -411,73 +361,17 @@ type
evalMacroCounter*: int
exitcode*: int8
cmd*: Command # raw command parsed as enum
ideActive*: bool # serving IDE tooling (nimsuggest): collect suggestions and
# keep going after errors. Decoupled from `cmd` so the IDE
# server can run under any compilation mode (cmdCheck, cmdM).
ideImportsFromNif*: bool # nimsuggest: load the unchanged import closure from
# precompiled NIF (run under cmdM) instead of recompiling it
# from source (cmdCheck). IC is opt-in: default off (cmdCheck);
# `--ideImports:nif` opts in.
cmdInput*: string # input command
projectIsCmd*: bool # whether we're compiling from a command input
implicitCmd*: bool # whether some flag triggered an implicit `command`
selectedGC*: TGCMode # the selected GC (+)
exc*: ExceptionSystem
selectedStrings*: StringsMode
hintProcessingDots*: bool # true for dots, false for filenames
verbosity*: int # how verbose the compiler is
numberOfProcessors*: int # number of processors
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`.
icBackendModules*: seq[string]
# under `nim nifc` with icBackendStage in
# {lower,cg,emit}: the NIF module suffixes this
# invocation processes — its BATCH. One entry is
# the per-module fan-out; several share one process
# and therefore ONE dependency-closure load between
# them, which is the whole point (see
# `nifbackend.loadDepClosure`). Every other module
# is loaded only so types resolve; its definitions
# are referenced extern. Empty = the main module.
spellSuggestMax*: int # max number of spelling suggestions for typos
cppDefines*: HashSet[string] # (*)
@@ -524,12 +418,6 @@ 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
@@ -686,7 +574,6 @@ proc newConfigRef*(): ConfigRef =
arcToExpand: newStringTable(modeStyleInsensitive),
m: initMsgConfig(),
cppDefines: initHashSet[string](),
icGroup: initHashSet[string](),
headerFile: "", features: {}, legacyFeatures: {},
configVars: newStringTable(modeStyleInsensitive),
symbols: newStringTable(modeStyleInsensitive),
@@ -709,7 +596,6 @@ proc newConfigRef*(): ConfigRef =
command: "", # the main command (e.g. cc, check, scan, etc)
commandArgs: @[], # any arguments after the main command
commandLine: "",
ideImportsFromNif: false, # IC opt-in; see `--ideImports`
implicitImports: @[], # modules that are to be implicitly imported
implicitIncludes: @[], # modules that are to be implicitly included
docSeeSrcUrl: "",
@@ -763,7 +649,6 @@ 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,
@@ -811,20 +696,8 @@ template quitOrRaise*(conf: ConfigRef, msg = "") =
else:
quit(msg) # quits with QuitFailure
proc icReuseSemLowering*(conf: ConfigRef): bool {.inline.} =
## When ON, the per-module `lower` backend stage REUSES the VM/CT lowering that
## sem cached in the `.s.nif` 2-way-body slot (the non-IC single-lowering
## semantics) instead of re-deriving the transform. Default OFF: the backend
## re-derives every body from the pristine semchecked body (simpler; allowed by
## the 2026-06-27 spec that VM-requested frontend transforms need not influence
## the backend). The switch exists so caching can be restored if a target (e.g.
## Nimbus) depends on the cached lowering being reused, not re-derived. See
## doc/ic_backend_simplify.md §6b.
isDefined(conf, "icReuseSemLowering")
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.ideActive or conf.cmd in cmdDocLike
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in cmdDocLike + {cmdIdeTools}
proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc
proc usesSso*(conf: ConfigRef): bool {.inline.} = conf.selectedStrings == stringSso
template compilationCachePresent*(conf: ConfigRef): untyped =
false
@@ -946,28 +819,9 @@ proc getOsCacheDir(): string =
else:
result = getHomeDir() / genSubDir.string
proc isIcDriver*(conf: ConfigRef): bool =
## True for `nim c --ic:on` / `nim cpp --ic:on`: this process is the `nim ic`
## DRIVER (it builds the nifmake graph and spawns the per-module children),
## not a compilation. `nim ic` itself keeps its own `cmdIc` branch.
conf.ic and conf.cmd in {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC}
proc icCFileExt*(conf: ConfigRef): string =
## The extension the per-module backend gives a module's translation unit.
## Mirrors `cgen.getCFile` at BACKEND granularity, which is all the `nim ic`
## driver can know: it DECLARES every module's `.c`/`.cpp` output to nifmake
## without loading a single module, so a per-module `{.compile: cpp.}`
## (`sfCompileToCpp`) is out of reach — and `nim cpp` selects the backend for
## the whole program anyway.
case conf.backend
of backendCpp: ".nim.cpp"
of backendObjc: ".nim.m"
else: ".nim.c"
proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir =
proc nimcacheSuffix(conf: ConfigRef): string =
if conf.ideActive: "_nimsuggest" # dedicated cache, never shared with `nim c`
elif conf.cmd == cmdCheck: "_check"
if conf.cmd == cmdCheck: "_check"
elif isDefined(conf, "release") or isDefined(conf, "danger"): "_r"
else: "_d"

View File

@@ -27,6 +27,7 @@ proc getPackage*(conf: ConfigRef; cache: IdentCache; fileIdx: FileIndex): PSym =
## * `modulegraphs.getPackage`
let
filename = AbsoluteFile toFullPath(conf, fileIdx)
name = getIdent(cache, splitFile(filename).name)
info = newLineInfo(fileIdx, 1, 1)
pkgName = getPackageName(conf, filename.string)
pkgIdent = getIdent(cache, pkgName)

View File

@@ -54,10 +54,7 @@ import
when not defined(nimCustomAst):
import ast
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`.
else:
import plugins / customast
import std/strutils
@@ -2244,17 +2241,14 @@ 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.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:
if p.tok.indent < 0:
var args = newNodeP(nkArgList, p)
result.add(args)
args.add(p.parseTypeClassParam)
@@ -2280,10 +2274,9 @@ proc parseTypeClass(p: var Parser): PNode =
result.add(p.emptyNode)
if p.tok.tokType == tkComment:
skipComment(p, result)
# an initial IND{>} HAS to follow, unless this concept inherits requirements:
# an initial IND{>} HAS to follow:
if not realInd(p):
let hasParents = result[2].kind != nkEmpty
if result.isNewStyleConcept and not hasParents:
if result.isNewStyleConcept:
parMessage(p, "routine expected, but found '$1' (empty new-styled concepts are not allowed)", p.tok)
result.add(p.emptyNode)
else:

View File

@@ -6,18 +6,16 @@ import sem, cgen, modulegraphs, ast, llstream, parser, msgs,
when not defined(nimKochBootstrap):
import vmdef
import ast2nif
import nifstreams
import "../dist/nimony/src/lib" / bitabs
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
import pipelineutils
import icprof
import ../dist/checksums/src/checksums/sha1
when not defined(leanCompiler):
import jsgen, docgen2
import std/[syncio, objectdollar, assertions, tables, strutils, strtabs, sets, intsets]
import std/[syncio, objectdollar, assertions, tables, strutils, strtabs]
import renderer
import ic/replayer
@@ -169,8 +167,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
s = stream
graph.interactive = stream.kind == llsStdIn
var topLevelStmts =
if {optCompress, optGenBif} * graph.config.globalOptions != {} or
graph.config.cmd == cmdM:
if optCompress in graph.config.globalOptions or graph.config.cmd == cmdM:
newNodeI(nkStmtList, module.info)
else:
nil
@@ -246,31 +243,9 @@ 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). 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 =
if graph.config.errorCounter > 0:
# Never persist an artifact built from erroneous AST. `nim m` does exit
# non-zero, but its outputs would still land on disk NEWER than their
# inputs, so nifmake sees the rule as satisfied on the next run: the
# build then "succeeds" from a poisoned NIF — a silently wrong binary,
# or an internal error once codegen meets an `nkError` body. Leaving the
# outputs missing keeps the rule dirty so it re-fires and re-reports.
false
elif graph.config.ideActive:
# nimsuggest (cmdM): persist NIF for cleanly-compiled, SAVED modules so
# later queries load them instead of recompiling. Never persist the
# actively edited buffer (it may hold unsaved/incomplete code) nor a
# module that failed to compile — that would poison the cache.
graph.config.cmd == cmdM and graph.config.errorCounter == 0 and
graph.config.m.fileInfos[module.position].dirtyFile.isEmpty
else:
({optCompress, optGenBif} * graph.config.globalOptions != {}) or
(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)))
# (imported modules should be loaded from existing NIF files)
let shouldWriteNif = (optCompress in graph.config.globalOptions) or
(graph.config.cmd == cmdM and sfMainModule in module.flags)
if shouldWriteNif and not graph.config.isDefined("nimscript"):
topLevelStmts.add finalNode
# Collect replay actions from both pragma computations and VM state diff
@@ -284,150 +259,16 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
if m == module:
replayActions.add n
# 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)
# Generic TYPE-instance OFFERS: every `tyGenericInst` THIS module created,
# so a consumer reuses its baked structure (array bounds etc.) rather than
# re-instantiating with a scope-divergent bound. See ast2nif.writeNifModule.
var typeOffers: seq[tuple[generic: PSym; inst: PType]] = @[]
for genItemId, instList in graph.typeInstCache:
for inst in instList:
if inst != nil and inst.itemId.module == module.position and
inst.kidsLen > 0 and inst[0] != nil and
inst[0].kind == tyGenericBody and inst[0].sym != nil:
typeOffers.add (inst[0].sym, inst)
# 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, @[])
# The frontend's highest used itemId (max of the sym and type counters):
# the backend seeds its id minting ABOVE this so closure envs / RTTI hooks
# never share a `toId` with a frontend sym/type. See ast2nif `(unusedid)`.
let firstUnusedId = max(idgen.symId, idgen.typeId)
var expansions: seq[(PSym, TLineInfo)] = @[]
discard graph.nifExpansions.take(module.position.int32, expansions)
# The module symbol's own backend-relevant flags. `sfInjectDestructors` is
# set by sempass2 when the module's TOP-LEVEL statements need the
# destructor pass; `moduleFromNifFile` builds a fresh module PSym, so
# without persisting it `cgen.genTopLevelStmt` skipped
# `injectDestructorCalls` and top-level locals were never destroyed.
let moduleFlags =
if sfInjectDestructors in module.flags: ModFlagInjectDestructors else: 0'i32
timed tWriteNif:
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog,
replayActions, implDeps, reexportedModuleSyms(graph, module),
genericOffers, typeOffers, resolvedImportDeps, firstUnusedId,
expansions, moduleFlags,
reexportedLocalSyms(graph, module))
# 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)
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog, replayActions)
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 finalizeLoadedModules(graph: ModuleGraph) =
## Apply the VM-level load effects of every module just loaded from a NIF —
## direct import OR dep-of-a-dep, both collected in `graph.pendingNifInit` by the
## loader (modulegraphs.moduleFromNifFile / loadTransitiveHooks). This is the ONE
## place that knows what loading a module does to global VM state, so a
## transitively-reached module (which never passes through this proc's caller)
## gets identical treatment. Modules are in dependency order (deps before
## dependents), which is the correct macro-cache replay order.
## 1. macro-cache replay: std/macrocache put/inc/add/incl recorded in the
## module's top level (pragma replay actions are a backend concern, skipped).
## 2. eager `{.compileTime.}` global init (see initLoadedCompileTimeGlobals).
## To add a new per-load effect, extend this proc — do not add a parallel buffer.
if graph.pendingNifInit.len == 0: return
for (m, topLevel) in graph.pendingNifInit:
if topLevel == nil: continue
var replayList = newNodeI(nkStmtList, m.info)
for n in topLevel:
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
if replayList.len > 0:
replayStateChanges(m, graph, replayList)
initLoadedCompileTimeGlobals(graph, m, topLevel)
graph.pendingNifInit.setLen 0
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:
@@ -437,57 +278,27 @@ 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).
# 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.
# Skip when withinSystem is true (compiling system.nim itself)
if graph.config.cmd == cmdM and
sfMainModule notin flags and
not graph.withinSystem and
not graph.config.isDefined("nimscript") and
(graph.config.icGroup.len == 0 or
toFullPath(graph.config, fileIdx) notin graph.config.icGroup):
not graph.config.isDefined("nimscript"):
let precomp = moduleFromNifFile(graph, fileIdx)
if precomp.module == nil:
if graph.config.ideActive:
# nimsuggest bootstrap: this import has no precompiled NIF yet (cold
# cache, or it was invalidated). Don't error — fall through to the
# source-compile path below; the pass-close emits a fresh NIF so the
# next query loads it instead of recompiling.
discard
else:
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 & ")")
return nil # Don't fall through to compile from source
let nifPath = toNifFilename(graph.config, fileIdx)
globalError(graph.config, unknownLineInfo,
"nim m requires precompiled NIF for import: " & toFullPath(graph.config, fileIdx) &
" (expected: " & nifPath & ")")
return nil # Don't fall through to compile from source
else:
# Module successfully loaded from NIF file - use it and skip processing
result = precomp.module
if sfSystemModule in flags:
graph.systemModule = result
partialInitModule(result, graph, fileIdx, AbsoluteFile(toFullPath(graph.config, fileIdx)))
# Apply the VM-level load effects of this module AND every dep it pulled in
# (moduleFromNifFile recorded them all in graph.pendingNifInit): macro-cache
# replay (else a NIF-loaded module's macro cache is lost — e.g.
# nim-serialization flavor registration) and eager `{.compileTime.}` global
# init. Uniform for direct and transitive deps — see finalizeLoadedModules.
finalizeLoadedModules(graph)
# Replay state changes from the loaded NIF module
if result.ast != nil:
replayStateChanges(result, graph)
return result # Return early, don't process from source
let path = toFullPath(graph.config, fileIdx)
let filename = AbsoluteFile path
@@ -553,14 +364,7 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
let projectFile = if projectFileIdx == InvalidFileIdx: conf.projectMainIdx else: projectFileIdx
conf.projectMainIdx2 = 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))
let packSym = getPackage(graph, projectFile)
graph.config.mainPackageId = packSym.getPackageId
graph.importStack.add projectFile
@@ -571,32 +375,16 @@ 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):
# Don't clobber an already-compiled system: nimsuggest's NimScript config
# evaluation compiles `system` into this same graph before we get here.
let precomp = moduleFromNifFile(graph, graph.config.m.systemFileIdx)
graph.systemModule = precomp.module
if graph.systemModule == nil:
let precomp = moduleFromNifFile(graph, graph.config.m.systemFileIdx)
graph.systemModule = precomp.module
if graph.systemModule == nil:
if graph.config.ideActive:
# nimsuggest bootstrap: no system NIF yet — compile it from source
# (the pass-close emits it), then continue with the main module.
graph.compilePipelineSystemModule()
else:
let nifPath = toNifFilename(graph.config, graph.config.m.systemFileIdx)
localError(graph.config, unknownLineInfo,
"nim m requires precompiled NIF for system module (expected: " & nifPath & ")")
return
# Apply system's (and its deps') load effects now: the main module is
# compiled from source and never re-enters the moduleFromNifFile drain for
# system, so without this its macro-cache / CT globals would wait until the
# first NIF import is processed. See finalizeLoadedModules.
finalizeLoadedModules(graph)
let nifPath = toNifFilename(graph.config, graph.config.m.systemFileIdx)
localError(graph.config, unknownLineInfo,
"nim m requires precompiled NIF for system module (expected: " & nifPath & ")")
return
discard graph.compilePipelineModule(projectFile, {sfMainModule})
else:
graph.compilePipelineSystemModule()

View File

@@ -20,3 +20,7 @@ proc prepareConfigNotes*(graph: ModuleGraph; module: PSym) =
else:
if graph.config.mainPackageNotes == {}: graph.config.mainPackageNotes = graph.config.notes
graph.config.notes = graph.config.foreignPackageNotes
proc moduleHasChanged*(graph: ModuleGraph; module: PSym): bool {.inline.} =
result = true
#module.id >= 0 or isDefined(graph.config, "nimBackendAssumesChange")

View File

@@ -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, cpuWasm64
cpuEsp, cpuWasm32, cpuE2k, cpuLoongArch64
type
TInfoCPU* = tuple[name: string, intSize: int, endian: Endianness,
@@ -249,8 +249,7 @@ 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: "wasm64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)]
(name: "loongarch64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)]
type
Target* = object

View File

@@ -11,10 +11,25 @@
# This is needed for proper handling of forward declarations.
import
ast, astalgo, msgs, semdata, types, lookups
ast, astalgo, msgs, semdata, types, trees, lookups
import std/strutils
proc equalGenericParams(procA, procB: PNode): bool =
if procA.len != procB.len: return false
for i in 0..<procA.len:
if procA[i].kind != nkSym:
return false
if procB[i].kind != nkSym:
return false
let a = procA[i].sym
let b = procB[i].sym
if a.name.id != b.name.id or
not sameTypeOrNil(a.typ, b.typ, {ExactTypeDescValues}): return
if a.ast != nil and b.ast != nil:
if not exprStructuralEquivalent(a.ast, b.ast): return
result = true
proc searchForProcAux(c: PContext, scope: PScope, fn: PSym): PSym =
const flags = {ExactGenericParams, ExactTypeDescValues,
ExactConstraints, IgnoreCC}
@@ -44,3 +59,30 @@ proc searchForProc*(c: PContext, scope: PScope, fn: PSym): tuple[proto: PSym, co
scope = scope.parent
result.proto = searchForProcAux(c, scope, fn)
result.comesFromShadowScope = true
when false:
proc paramsFitBorrow(child, parent: PNode): bool =
result = false
if child.len == parent.len:
for i in 1..<child.len:
var m = child[i].sym
var n = parent[i].sym
assert((m.kind == skParam) and (n.kind == skParam))
if not compareTypes(m.typ, n.typ, dcEqOrDistinctOf): return
if not compareTypes(child[0].typ, parent[0].typ,
dcEqOrDistinctOf): return
result = true
proc searchForBorrowProc*(c: PContext, startScope: PScope, fn: PSym): PSym =
# Searches for the fn in the symbol table. If the parameter lists are suitable
# for borrowing the sym in the symbol table is returned, else nil.
var it: TIdentIter = default(TIdentIter)
for scope in walkScopes(startScope):
result = initIdentIter(it, scope.symbols, fn.Name)
while result != nil:
# watchout! result must not be the same as fn!
if (result.Kind == fn.kind) and (result.id != fn.id):
if equalGenericParams(result.ast[genericParamsPos],
fn.ast[genericParamsPos]):
if paramsFitBorrow(fn.typ.n, result.typ.n): return
result = NextIdentIter(it, scope.symbols)

View File

@@ -537,6 +537,10 @@ proc putNL(g: var TSrcGen, indent: int) =
g.lineLen = indent
g.pendingWhitespace = -1
proc previousNL(g: TSrcGen): bool =
result = g.pendingNL >= 0 or (g.tokens.len > 0 and
g.tokens[^1].kind == tkSpaces)
proc putNL(g: var TSrcGen) =
putNL(g, g.indent)
@@ -578,7 +582,6 @@ proc put(g: var TSrcGen, kind: TokType, s: string; sym: PSym = nil) =
inc(g.lineLen, s.len)
proc putComment(g: var TSrcGen, s: string) =
const SpecialWhitespace = {' ', '\t', '\r', '\n', '\0'}
if s.len == 0: return
var i = 0
let hi = s.len - 1
@@ -608,12 +611,12 @@ proc putComment(g: var TSrcGen, s: string) =
# gets too long:
# compute length of the following word:
var j = i
while j <= hi and s[j] notin SpecialWhitespace: inc(j)
while j <= hi and s[j] > ' ': inc(j)
if not isCode and (g.col + (j - i) > MaxLineLen):
put(g, tkComment, com)
optNL(g, ind)
com = "## "
while i <= hi and s[i] notin SpecialWhitespace:
while i <= hi and s[i] > ' ':
com.add(s[i])
inc(i)
put(g, tkComment, com)
@@ -642,6 +645,28 @@ proc maxLineLength(s: string): int =
inc(lineLen)
inc(i)
proc putRawStr(g: var TSrcGen, kind: TokType, s: string) =
var i = 0
let hi = s.len - 1
var str = ""
while i <= hi:
case s[i]
of '\r':
put(g, kind, str)
str = ""
inc(i)
if i <= hi and s[i] == '\n': inc(i)
optNL(g, 0)
of '\n':
put(g, kind, str)
str = ""
inc(i)
optNL(g, 0)
else:
str.add(s[i])
inc(i)
put(g, kind, str)
proc containsNL(s: string): bool =
for i in 0..<s.len:
case s[i]
@@ -1758,6 +1783,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
gsub(g, n, 1)
of nkInfix:
if n.len < 3:
var i = 0
put(g, tkOpr, "Too few children for nkInfix")
return
let oldLineLen = g.lineLen # we cache this because lineLen gets updated below
@@ -2049,6 +2075,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
of nkPragma:
if g.inPragma <= 0:
inc g.inPragma
#if not previousNL(g):
put(g, tkSpaces, Space)
put(g, tkCurlyDotLe, "{.")
gcomma(g, n, emptyContext)

View File

@@ -34,6 +34,14 @@ when defined(windows) and defined(bcc):
#endif
""".}
proc c_snprintf(s: cstring; n: uint; frmt: cstring): cint {.importc: "snprintf", header: "<stdio.h>", nodecl, varargs.}
when not declared(signbit):
proc c_signbit(x: SomeFloat): cint {.importc: "signbit", header: "<math.h>".}
proc signbit*(x: SomeFloat): bool {.inline.} =
result = c_signbit(x) != 0
import std/formatfloat
proc toStrMaxPrecision*(f: BiggestFloat | float32): string =

View File

@@ -77,7 +77,7 @@ template semIdeForTemplateOrGeneric(c: PContext; n: PNode;
# templates perform some quick check whether the cursor is actually in
# the generic or template.
when defined(nimsuggest):
if c.config.ideActive and requiresCheck:
if c.config.cmd == cmdIdeTools and requiresCheck:
#if optIdeDebug in gGlobalOptions:
# echo "passing to safeSemExpr: ", renderTree(n)
discard safeSemExpr(c, n)
@@ -89,18 +89,6 @@ proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode =
changeType(c, x, formal, check=true)
result = arg
result = skipHiddenSubConv(result, c.graph, c.idgen)
# Walk through nested statement-list/block expressions to find the innermost
# value node. Empty containers (e.g. `@[]`) inside `nkStmtListExpr` wrappers
# need their type resolved to match the formal type, otherwise the C codegen
# cannot map `tyEmpty` to a concrete type (fixes #25945).
var tail = result
while tail.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkPragmaBlock} and tail.len > 0:
tail = tail.lastSon
if tail.typ != nil and tail.typ.isEmptyContainer and
formal.kind notin {tyUntyped, tyBuiltInTypeClass, tyAnything}:
changeType(c, tail, formal, check=true)
# mark inserted converter as used:
var a = result
if a.kind == nkHiddenDeref: a = a[0]
@@ -117,12 +105,9 @@ 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.skipTypes({tyVar, tyLent}), formal):
if sameType(ch.typ, formal):
return ch
typeMismatch(c.config, info, formal, arg.typ, arg)
else:
@@ -262,40 +247,12 @@ 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:
result = copySym(result)
result.ast = n.sym.ast
put(c.p, n.sym, result)
if result.state == Sealed:
# the symbol was loaded from another module's NIF cache (e.g. a param
# symbol spliced out of an imported proc type by a `typed` macro) and is
# therefore immutable; the caller re-owns it and assigns its type/flags,
# so hand back a fresh, mutable copy owned by the current module instead.
let fresh = copySym(result, c.idgen)
fresh.ast = result.ast
result = fresh
# when there is a nested proc inside a template, semtmpl
# will assign a wrong owner during the first pass over the
# template; we must fix it here: see #909
@@ -332,6 +289,7 @@ proc typeAllowedCheck(c: PContext; info: TLineInfo; typ: PType; kind: TSymKind;
proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} =
typeAllowedCheck(c, typ.n.info, typ, skProc)
proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym
proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode
proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
@@ -583,12 +541,10 @@ const
proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
let info = getCallLineInfo(n)
# the callee identifier's position is the usage site tooling expects (matches
# `markUsed` below), not the whole-call `nOrig.info`.
rememberExpansion(c, info, sym)
rememberExpansion(c, nOrig.info, sym)
pushInfoContext(c.config, nOrig.info, sym.detailedInfo)
let info = getCallLineInfo(n)
markUsed(c, info, sym)
onUse(info, sym)
if sym == c.p.owner:
@@ -895,7 +851,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
result = hloStmt(c, result)
if c.config.cmd == cmdInteractive and not isEmptyType(result.typ):
result = buildEchoStmt(c, result)
if c.config.ideActive:
if c.config.cmd == cmdIdeTools:
appendToModule(c.module, result)
trackStmt(c, c.module, result, isTopLevel = true)
if optMultiMethods notin c.config.globalOptions and
@@ -932,7 +888,7 @@ proc semWithPContext*(c: PContext, n: PNode): PNode =
result = nil
else:
result = newNodeI(nkEmpty, n.info)
#if c.config.ideActive: findSuggest(c, n)
#if c.config.cmd == cmdIdeTools: findSuggest(c, n)
proc reportUnusedModules(c: PContext) =
if c.config.cmd == cmdM: return
@@ -941,7 +897,7 @@ proc reportUnusedModules(c: PContext) =
message(c.config, info, warnUnusedImportX, s.name.s)
proc closePContext*(graph: ModuleGraph; c: PContext, n: PNode): PNode =
if c.config.ideActive and not c.suggestionsMade:
if c.config.cmd == cmdIdeTools and not c.suggestionsMade:
suggestSentinel(c)
closeScope(c) # close module's scope
rawCloseScope(c) # imported symbols; don't check for unused ones!

View File

@@ -77,7 +77,7 @@ proc isAttachableRoutineTo(prc: PSym, arg: PType): bool =
# has default value, parameter is not considered in type attachment
continue
let t = nominalRoot(prc.typ[i])
if t != nil and t.bindingId == arg.bindingId:
if t != nil and t.itemId == arg.itemId:
# parameter `i` is a nominal type in this module
# attachable if the nominal root `t` has the same id as `arg`
return true
@@ -90,14 +90,8 @@ 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 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.
if t != nil and t.owner.kind == skModule:
# search module for routines attachable to `t`
let module = t.owner
var iter = default(ModuleIter)
var s = initModuleIter(iter, graph, module, name)
@@ -137,7 +131,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
var sym = syms[0].s
let name = sym.name
var scope = syms[0].scope
c.openShadowScope
if allowTypeBoundOps:
for a in 1 ..< n.len:
# for every already typed argument, add type bound ops
@@ -166,13 +160,9 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
addTypeBoundSymbols(c.graph, arg.typ, name, filter, symMarker, syms)
if z.state == csMatch:
# Iterator preference is heuristic in iterator-admitting contexts.
# The dedicated iterable path uses `iteratorPreference`, other
# context use exact-match bump
# little hack so that iterators are preferred over everything else:
if sym.kind == skIterator:
if efPreferIteratorForIterable in flags:
inc(z.iteratorPreference)
elif not (efWantIterator notin flags and efWantIterable in flags):
if not (efWantIterator notin flags and efWantIterable in flags):
inc(z.exactMatches, 200)
else:
dec(z.exactMatches, 200)
@@ -224,10 +214,6 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
scope = syms[nextSymIndex].scope
inc(nextSymIndex)
if best.state == csMatch and best.calleeSym != nil and best.calleeSym.kind in {skTemplate, skMacro}:
c.closeShadowScope
else:
c.mergeShadowScope
proc effectProblem(f, a: PType; result: var string; c: PContext) =
if f.kind == tyProc and a.kind == tyProc:
@@ -685,11 +671,11 @@ proc bracketNotFoundError(c: PContext; n: PNode; flags: TExprFlags) =
# copied from semOverloadedCallAnalyzeEffects, might be overkill:
const baseFilter = {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate}
let filter =
if flags*{efInTypeof, efWantIterator, efWantIterable, efPreferIteratorForIterable} != {}:
if flags*{efInTypeof, efWantIterator, efWantIterable} != {}:
baseFilter + {skIterator}
else: baseFilter
# this will add the errors:
discard resolveOverloads(c, n, n, filter, flags, errors, true)
var r = resolveOverloads(c, n, n, filter, flags, errors, true)
if errors.len == 0:
localError(c.config, n.info, "could not resolve: " & $n)
else:
@@ -732,15 +718,6 @@ 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,
" bindingId=", f.bindingId, " 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, " bindingId=", a2.bindingId,
" 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 =
@@ -926,6 +903,15 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
result.typ = finalCallee.typ.returnType
updateDefaultParams(c, result)
proc canDeref(n: PNode): bool {.inline.} =
result = n.len >= 2 and (let t = n[1].typ;
t != nil and t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyPtr, tyRef})
proc tryDeref(n: PNode): PNode =
result = newNodeI(nkHiddenDeref, n.info)
result.typ = n.typ.skipTypes(abstractInst)[0]
result.add n
proc semOverloadedCall(c: PContext, n, nOrig: PNode,
filter: TSymKinds, flags: TExprFlags;
expectedType: PType = nil): PNode =
@@ -974,12 +960,7 @@ 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)
# `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
newInst.typ.excl tfUnresolved
let info = getCallLineInfo(n)
markUsed(c, info, s, isGenericInstance = false)
onUse(info, s, isGenericInstance = false)

View File

@@ -54,18 +54,7 @@ type
inst*: PInstantiation
TExprFlag* = enum
efLValue,
# The expression is used as an assignable location.
efWantIterator,
# Admit iterator candidates and prefer them during overload resolution.
efWantIterable,
# Admit iterator candidates for expressions that may feed iterable-style
# chaining.
efPreferIteratorForIterable,
# Prefer iterator candidates for `iterable[T]` matching and wrap a
# successful iterator call as `tyIterable`.
efInTypeof,
# The expression is being semchecked under `typeof`.
efLValue, efWantIterator, efWantIterable, efInTypeof,
efNeedStatic,
# Use this in contexts where a static value is mandatory
efPreferStatic,
@@ -180,33 +169,12 @@ type
sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
inUncheckedAssignSection*: int
importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id])
forwardTypeUpdates*: seq[(PSym, PType, PNode)]
# top-level owner, type, and type node for delayed retries inside a
# type section due to containing forward types
forwardFieldUpdates*: seq[(PType, PNode, PType)]
# object/tuple field definitions whose default values mention forward
# types and need delayed const checking
forwardFlagUpdates*: seq[(PType, PType)]
# (owner, son) pairs whose `propagateToOwner` ran on a not yet reified
# forward type and has to be redone in the final pass
staleTypeFlags*: IntSet
# ids of the owners in `forwardFlagUpdates`; their flags are provisional
# too, so reading them makes the reader provisional in turn
forwardTypeUpdates*: seq[(PType, PNode)]
# types that need to be updated in a type section
# due to containing forward types, and their corresponding nodes
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
@@ -299,10 +267,7 @@ proc get*(p: PProcCon; key: PSym): PSym =
result = p.mapping.getOrDefault(key.itemId)
proc getGenSym*(c: PContext; s: PSym): PSym =
# `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
if sfGenSym notin s.flags: return s
var it = c.p
while it != nil:
result = get(it, s)
@@ -312,7 +277,7 @@ proc getGenSym*(c: PContext; s: PSym): PSym =
it = it.next
result = s
proc considerGenSymsAux(c: PContext; n: PNode) =
proc considerGenSyms*(c: PContext; n: PNode) =
if n == nil:
discard "can happen for nkFormalParams/nkArgList"
elif n.kind == nkSym:
@@ -321,16 +286,7 @@ proc considerGenSymsAux(c: PContext; n: PNode) =
n.sym = s
else:
for i in 0..<n.safeLen:
considerGenSymsAux(c, n[i])
proc considerGenSyms*(c: PContext; n: PNode) =
var it = c.p
while it != nil:
if it.mappingExists:
# Save a tree traversal when no mapping exists
considerGenSymsAux(c, n)
return
it = it.next
considerGenSyms(c, n[i])
proc newOptionEntry*(conf: ConfigRef): POptionEntry =
result = POptionEntry(
@@ -373,9 +329,6 @@ proc newContext*(graph: ModuleGraph; module: PSym): PContext =
userPragmas: initStrTable(),
generics: @[],
unknownIdents: initIntSet(),
shadowDiscardedDefs: initIntSet(),
realizedDefs: initIntSet(),
staleTypeFlags: initIntSet(),
cache: graph.cache,
graph: graph,
signatures: initStrTable(),
@@ -386,21 +339,11 @@ proc addIncludeFileDep*(c: PContext; f: FileIndex) =
discard
proc addImportFileDep*(c: PContext; f: FileIndex) =
# 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
discard
proc addPragmaComputation*(c: PContext; n: PNode) =
# Also store whenever the semchecked module is serialized to NIF/BIF.
if {optCompress, optGenBif} * c.config.globalOptions != {} or
c.config.cmd == cmdM:
# Also store for NIF-based IC (cmdM mode or optCompress)
if optCompress in c.config.globalOptions or c.config.cmd == cmdM:
addNifReplayAction(c.graph, c.module.position.int32, n)
proc inclSym(sq: var seq[PSym], s: PSym): bool =
@@ -413,18 +356,6 @@ 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)
@@ -432,13 +363,6 @@ 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
@@ -685,15 +609,7 @@ proc rememberExpansion*(c: PContext; info: TLineInfo; expandedSym: PSym) =
## ("find all usages of this template" would not work). We need special
## logic to remember macro/template expansions. This is done here and
## delegated to the "NIF" file mechanism.
##
## We only bother when a NIF file is actually going to be written (IC / `nim m`,
## `--compress`, semantic BIF output, or a running suggestion engine); a plain
## `nim c` throws the record away, so recording it would be pure overhead.
if info.fileIndex == InvalidFileIdx: return
if c.config.cmd == cmdM or
{optCompress, optGenBif} * c.config.globalOptions != {} or
c.config.ideActive:
c.graph.nifExpansions.mgetOrPut(c.module.position.int32, @[]).add (expandedSym, info)
discard "XXX To implement"
const
errVarForOutParamNeededX = "for a 'var' type a variable needs to be passed; but '$1' is immutable"
@@ -707,11 +623,6 @@ proc renderNotLValue*(n: PNode): string =
elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2:
result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")"
proc isSsoStringIndex*(conf: ConfigRef; n: PNode): bool =
result = conf.usesSso() and n.kind == nkBracketExpr and n.len >= 1 and
n[0].typ != nil and
n[0].typ.skipTypes(abstractVar + abstractInst - {tyTypeDesc}).kind == tyString
proc isAssignable(c: PContext, n: PNode): TAssignableResult =
result = parampatterns.isAssignable(c.p.owner, n)
@@ -819,7 +730,7 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
case kind
of attachedDestructor:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, attachedDestructor)
if op != nil:
result[0] = newSymNode(op)
@@ -831,13 +742,13 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
result[1] = skipAddr(n[1])
of attachedTrace:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, attachedTrace)
if op != nil:
result[0] = newSymNode(op)
of attachedDup:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, attachedDup)
if op != nil:
result[0] = newSymNode(op)
@@ -847,26 +758,18 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
result.add boolLit
of attachedWasMoved:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, attachedWasMoved)
if op != nil:
result[0] = newSymNode(op)
analyseIfAddressTakenInCall(c, result, false)
of attachedSink:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let op = getAttachedOp(c.graph, t, kind)
if op != nil:
result[0] = newSymNode(op)
result = c.semAsgnOpr(c, n, nkSinkAsgn)
of attachedAsgn:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let op = getAttachedOp(c.graph, t, kind)
if op != nil:
result[0] = newSymNode(op)
result = c.semAsgnOpr(c, n, nkAsgn)
of attachedDeepCopy:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, kind)
if op != nil:
result[0] = newSymNode(op)

View File

@@ -22,18 +22,12 @@ const
errNamedExprExpected = "named expression expected"
errNamedExprNotAllowed = "named expression not allowed here"
errFieldInitTwice = "field initialized twice: '$1'"
errUndeclaredFieldX = "undeclared field: '$1'"
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
rememberExpansion(c, n.info, s)
let info = getCallLineInfo(n)
# `info` (the callee identifier's position, not the whole call node) is what
# tooling wants to see as the usage site — matches `markUsed` below.
rememberExpansion(c, info, s)
# 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)
markUsed(c, info, s)
onUse(info, s)
# Note: This is n.info on purpose. It prevents template from creating an info
@@ -63,16 +57,6 @@ 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)
@@ -99,17 +83,6 @@ 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)
@@ -133,9 +106,7 @@ 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 and efInTypeof in flags:
result.typ = c.voidType
elif result.typ == nil:
if result.typ == nil:
localError(c.config, n.info, errExprXHasNoType %
renderTree(result, {renderNoComments}))
result.typ = errorType(c)
@@ -226,29 +197,6 @@ 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,
@@ -704,9 +652,6 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode =
result = semExpr(c, result, flags = {efNoUndeclared})
proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
template isViewTarget(t: PType): bool =
t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyVar, tyLent}
case n.kind
of nkCurly:
for i in 0..<n.len:
@@ -735,15 +680,12 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
if f == nil:
globalError(c.config, m.info, "unknown identifier: " & m.sym.name.s)
return
if not isViewTarget(f.typ):
changeType(c, n[i][1], f.typ, check)
changeType(c, n[i][1], f.typ, check)
else:
if not isViewTarget(tup[i]):
changeType(c, n[i][1], tup[i], check)
changeType(c, n[i][1], tup[i], check)
else:
for i in 0..<n.len:
if not isViewTarget(tup[i]):
changeType(c, n[i], tup[i], check)
changeType(c, n[i], tup[i], check)
when false:
var m = n[i]
var a = newNodeIT(nkExprColonExpr, m.info, newType[i])
@@ -766,9 +708,19 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
localError(c.config, n.info, "cannot convert '" & n.sym.name.s &
"' to '" & typeNameAndDesc(newType) & "'")
else: discard
n.typ = newType
proc arrayConstrType(c: PContext, n: PNode): PType =
var typ = newTypeS(tyArray, c)
rawAddSon(typ, nil) # index type
if n.len == 0:
rawAddSon(typ, newTypeS(tyEmpty, c)) # needs an empty basetype!
else:
var t = skipTypes(n[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
addSonSkipIntLit(typ, t, c.idgen)
typ.setIndexType makeRangeType(c, 0, n.len - 1, n.info)
result = typ
proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode =
result = newNodeI(nkBracket, n.info)
# nkBracket nodes can also be produced by the VM as seq constant nodes
@@ -1011,15 +963,12 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
# echo "SUCCESS evaluated at compile time: ", call.renderTree
proc semStaticExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
let oldErrorCount = c.config.errorCounter
inc c.inStaticContext
openScope(c)
let a = semExprWithType(c, n, expectedType = expectedType)
closeScope(c)
dec c.inStaticContext
if a.findUnresolvedStatic != nil or
c.config.errorCounter != oldErrorCount:
return a
if a.findUnresolvedStatic != nil: return a
result = evalStaticExpr(c.module, c.idgen, c.graph, a, c.p.owner)
if result.isNil:
localError(c.config, n.info, errCannotInterpretNodeX % renderTree(n))
@@ -1030,7 +979,7 @@ proc semStaticExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
flags: TExprFlags; expectedType: PType = nil): PNode =
if flags*{efInTypeof, efWantIterator, efWantIterable, efPreferIteratorForIterable} != {}:
if flags*{efInTypeof, efWantIterator, efWantIterable} != {}:
# consider: 'for x in pReturningArray()' --> we don't want the restriction
# to 'skIterator' anymore; skIterator is preferred in sigmatch already
# for typeof support.
@@ -1057,8 +1006,7 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
# See bug #2051:
result[0] = newSymNode(errorSym(c, n))
elif callee.kind == skIterator:
if result.typ.kind != tyIterable and
flags * {efWantIterable, efPreferIteratorForIterable} != {}:
if efWantIterable in flags:
let typ = newTypeS(tyIterable, c)
rawAddSon(typ, result.typ)
result.typ = typ
@@ -1327,6 +1275,7 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent,
else: illFormedAst(n, c.config)
const
tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass}
tyDotOpTransparent = {tyVar, tyLent, tyPtr, tyRef, tyOwned, tyAlias, tySink}
proc readTypeParameter(c: PContext, typ: PType,
@@ -1448,11 +1397,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
# not sure the symbol really ends up being used:
# var len = 0 # but won't be called
# genericThatUsesLen(x) # marked as taking a closure?
# Lowered returns use resolved symbol nodes internally; warn only for
# source-level references to the implicit result variable.
if s.kind == skResult and
(n.kind != nkSym or nfFromTemplate in n.flags) and
hasWarn(c.config, warnResultUsed):
if hasWarn(c.config, warnResultUsed):
message(c.config, n.info, warnResultUsed)
of skGenericParam:
@@ -1564,13 +1509,10 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
# here at all!
#if isSymChoice(n[1]): return
when defined(nimsuggest):
if c.config.ideActive:
if c.config.cmd == cmdIdeTools:
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:
@@ -1583,7 +1525,7 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
return
# extra flags since LHS may become a call operand:
n[0] = semExprWithType(c, n[0], flags + {efDetermineType, efWantIterable, efAllowSymChoice})
n[0] = semExprWithType(c, n[0], flags+{efDetermineType, efWantIterable, efAllowSymChoice})
#restoreOldStyleType(n[0])
var i = considerQuotedIdent(c, n[1], n)
var ty = n[0].typ
@@ -1898,22 +1840,6 @@ 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]
@@ -1921,17 +1847,17 @@ 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)
markResultVarIsPtr(c, x)
x.typ.incl tfVarIsPtr
#echo x.info, " setting it for this type ", typeToString(x.typ), " ", n.info
elif sfGlobal in x.sym.flags:
markResultVarIsPtr(c, x)
x.typ.incl tfVarIsPtr
proc borrowCheck(c: PContext, n, le, ri: PNode) =
const
PathKinds0 = {nkDotExpr, nkCheckedFieldExpr,
nkBracketExpr, nkAddr, nkHiddenAddr,
nkObjDownConv, nkObjUpConv}
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv, nkCast}
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv}
proc getRoot(n: PNode; followDeref: bool): PNode =
result = n
@@ -2151,8 +2077,6 @@ proc semReturn(c: PContext, n: PNode): PNode =
# optimize away ``result = result``:
if result[0][1].kind == nkSym and result[0][1].sym == c.p.resultSym:
result[0] = c.graph.emptyNode
elif c.p.resultSym != nil and hasWarn(c.config, warnResultUsed):
message(c.config, n.info, warnResultUsed)
else:
localError(c.config, n.info, "'return' not allowed here")
@@ -2183,12 +2107,6 @@ 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,
" itemId=", c.p.resultSym.typ.itemId.module, ".", c.p.resultSym.typ.itemId.item,
" state=", c.p.resultSym.typ.state
if isEmptyType(result.typ):
# we inferred a 'void' return type:
c.p.resultSym.typ = errorType(c)
@@ -2313,6 +2231,24 @@ proc semDeclared(c: PContext, n: PNode, onlyCurrentScope: bool): PNode =
result.info = n.info
result.typ = getSysType(c.graph, n.info, tyBool)
proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym =
## The argument to the proc should be nkCall(...) or similar
## Returns the macro/template symbol
if isCallExpr(n):
var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared})
if expandedSym == nil:
errorUndeclaredIdentifier(c, n.info, n[0].renderTree)
return errorSym(c, n[0])
if expandedSym.kind notin {skMacro, skTemplate}:
localError(c.config, n.info, "'$1' is not a macro or template" % expandedSym.name.s)
return errorSym(c, n[0])
result = expandedSym
else:
localError(c.config, n.info, "'$1' is not a macro or template" % n.renderTree)
result = errorSym(c, n)
proc expectString(c: PContext, n: PNode): string =
var n = semConstExpr(c, n)
if n.kind in nkStrKinds:
@@ -2327,6 +2263,14 @@ proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym =
proc semExpandToAst(c: PContext, n: PNode): PNode =
let macroCall = n[1]
when false:
let expandedSym = expectMacroOrTemplateCall(c, macroCall)
if expandedSym.kind == skError: return n
macroCall[0] = newSymNode(expandedSym, macroCall.info)
markUsed(c, n.info, expandedSym)
onUse(n.info, expandedSym)
if isCallExpr(macroCall):
for i in 1..<macroCall.len:
#if macroCall[0].typ[i].kind != tyUntyped:
@@ -2498,6 +2442,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
let oldInStaticContext = c.inStaticContext
let oldProcCon = c.p
c.generics = @[]
var err: string
try:
result = semExpr(c, n, flags)
if result != nil and efNoSem2Check notin flags:
@@ -2708,22 +2653,6 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags; expectedType: P
else:
result = semDirectOp(c, n, flags, expectedType)
proc semNimvmBranch(c: PContext, n: PNode, flags: TExprFlags): PNode =
let
oldOptionStack = c.optionStack[0..^1]
oldOptions = c.config.options
oldNotes = c.config.notes
oldWarningAsErrors = c.config.warningAsErrors
oldFeatures = c.features
try:
result = semExpr(c, n, flags)
finally:
c.optionStack = oldOptionStack
c.config.options = oldOptions
c.config.notes = oldNotes
c.config.warningAsErrors = oldWarningAsErrors
c.features = oldFeatures
proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
# If semCheck is set to false, ``when`` will return the verbatim AST of
# the correct branch. Otherwise the AST will be passed through semStmt.
@@ -2760,7 +2689,7 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
checkSonsLen(it, 2, c.config)
if whenNimvm:
if semCheck:
it[1] = semNimvmBranch(c, it[1], flags)
it[1] = semExpr(c, it[1], flags)
typ = commonType(c, typ, it[1].typ)
result = n # when nimvm is not elimited until codegen
elif c.inGenericContext > 0:
@@ -2791,8 +2720,7 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
discard
elif result == nil or whenNimvm:
if semCheck:
it[0] = if whenNimvm: semNimvmBranch(c, it[0], flags)
else: semExpr(c, it[0], flags)
it[0] = semExpr(c, it[0], flags)
typ = commonType(c, typ, it[0].typ)
if typ != nil and typ.kind != tyUntyped:
it[0] = fitNode(c, typ, it[0], it[0].info)
@@ -3390,7 +3318,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
c.config.expandNodeResult = $n
suggestQuit()
if c.config.ideActive: suggestExpr(c, n)
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
if nfSem in n.flags: return
case n.kind
of nkIdent, nkAccQuoted:

View File

@@ -19,7 +19,7 @@ import std/[strutils, math, strtabs]
#from system/memory import nimCStrLen
when defined(nimPreviewSlimSystem):
import std/[assertions]
import std/[assertions, formatfloat]
proc errorType*(g: ModuleGraph): PType =
## creates a type representing an error state
@@ -121,6 +121,21 @@ proc ordinalValToString*(a: PNode; g: ModuleGraph): string =
else:
result = $x
proc isFloatRange(t: PType): bool {.inline.} =
result = t.kind == tyRange and t.elementType.kind in {tyFloat..tyFloat128}
proc isIntRange(t: PType): bool {.inline.} =
result = t.kind == tyRange and t.elementType.kind in {
tyInt..tyInt64, tyUInt8..tyUInt32}
proc pickIntRange(a, b: PType): PType =
if isIntRange(a): result = a
elif isIntRange(b): result = b
else: result = a
proc isIntRangeOrLit(t: PType): bool =
result = isIntRange(t) or isIntLit(t)
proc evalOp(m: TMagic, n, a, b, c: PNode; idgen: IdGenerator; g: ModuleGraph): PNode =
# b and c may be nil
result = nil
@@ -377,6 +392,11 @@ proc rangeCheck(n: PNode, value: Int128; g: ModuleGraph) =
localError(g.config, n.info, "cannot convert " & $value &
" to " & typeToString(n.typ))
proc floatRangeCheck(n: PNode, value: BiggestFloat; g: ModuleGraph) =
if value < firstFloat(n.typ) or value > lastFloat(n.typ):
localError(g.config, n.info, "cannot convert " & $value &
" to " & typeToString(n.typ))
proc foldConv(n, a: PNode; idgen: IdGenerator; g: ModuleGraph; check = false): PNode =
let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc})
let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc})
@@ -456,12 +476,7 @@ proc foldArrayAccess(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNo
#localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n)
of nkBracket:
idx -= toInt64(firstOrd(g.config, x.typ))
if isDefaultBroadcastArray(x, g.config):
# compact default array: any in-bounds index folds to the default element
if idx >= 0 and idx < toInt64(lengthOrd(g.config, x.typ.skipTypes(abstractInst))):
result = copyTree(x[0])
else: result = nil
elif idx >= 0 and idx < x.len: result = x[int(idx)]
if idx >= 0 and idx < x.len: result = x[int(idx)]
else:
result = nil
#localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n)
@@ -595,21 +610,10 @@ 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:
# 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 mIsMainModule: result = newIntNodeT(toInt128(ord(sfMainModule in m.flags)), 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)

View File

@@ -129,19 +129,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
result.typ = nil
onUse(n.info, s)
of skParam:
if s.typ != nil and s.typ.kind == tyStatic and s.typ.n != nil:
# The enclosing routine gives this static parameter a concrete value.
# Keep that value so the nested generic can fold it as a compile-time
# expression instead of generating a runtime parameter reference.
result = s.typ.n
elif 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)
result = n
onUse(n.info, s)
of skType:
if (s.typ != nil) and
@@ -238,7 +226,7 @@ proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags,
if s.kind == skType: # don't put types in sym choice
var ambig = false
if candidates.len > 1:
discard searchInScopes(c, ident, ambig)
let s2 = searchInScopes(c, ident, ambig)
result = newDot(result, semGenericStmtSymbol(c, n, s, ctx, flags,
isAmbiguous = ambig, fromDotExpr = true))
else:
@@ -278,7 +266,7 @@ proc semGenericStmt(c: PContext, n: PNode,
when defined(nimsuggest):
if withinTypeDesc in flags: inc c.inTypeContext
#if conf.ideActive: suggestStmt(c, n)
#if conf.cmd == cmdIdeTools: suggestStmt(c, n)
semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody)
case n.kind
@@ -686,3 +674,4 @@ proc semConceptBody(c: PContext, n: PNode): PNode =
)
result = semGenericStmt(c, n, {withinConcept}, ctx)
semIdeForTemplateOrGeneric(c, result, ctx.cursorInBody)

View File

@@ -93,37 +93,6 @@ proc genericCacheGet(g: ModuleGraph; genericSym: PSym, entry: TInstantiation;
if (inst.compilesId == 0 or inst.compilesId == id) and sameInstantiation(entry, inst[]):
return inst.sym
proc sameBindingSnapshot(pt: LayeredIdTable; inst: PInstantiation): bool =
if inst.bindings.len == 0:
return false
const flags = {ExactTypeDescValues, ExactGcSafety, PickyCAliases}
for binding in inst.bindings:
let value = lookupById(pt, binding.key)
if value == nil or
(value != binding.value and
not compareTypes(value, binding.value, flags = flags)):
return false
# Reject a binding that wasn't visible in the saved mapping. Duplicate keys
# in parent layers are harmless because lookupById resolves the top layer.
for key, _ in pt.pairs:
var found = false
for binding in inst.bindings:
if key == binding.key:
found = true
break
if not found: return false
result = true
proc genericCacheGetFromBindings(g: ModuleGraph; genericSym: PSym,
pt: LayeredIdTable; id: CompilesId;
module: PSym): PSym =
result = nil
for inst in procInstCacheItems(g, genericSym):
if inst.sym != nil and inst.sym.itemId.module == module.position and
(inst.compilesId == 0 or inst.compilesId == id) and
sameBindingSnapshot(pt, inst):
return inst.sym
when false:
proc `$`(x: PSym): string =
result = x.name.s & " " & " id " & $x.id
@@ -150,44 +119,11 @@ 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
@@ -216,7 +152,6 @@ 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:
@@ -310,7 +245,7 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
let originalParams = result.n
result.n = originalParams.shallowCopy
for i in 1 ..< originalParams.len:
var resulti = originalParams[i].sym.typ
let resulti = originalParams[i].sym.typ
# twrong_field_caching requires these 'resetIdTable' calls:
if i > FirstParamAt:
resetIdTable(cl.symMap)
@@ -323,11 +258,6 @@ 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:
@@ -346,12 +276,6 @@ 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,
" itemId=", resulti.itemId.module, ".", resulti.itemId.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
@@ -359,9 +283,6 @@ 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)
@@ -407,10 +328,6 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
eraseVoidParams(result)
skipIntLiteralParams(result, c.idgen)
# The signature belongs to the INSTANCE, not to the generic it was copied
# from: `instCopyType` above kept the generic's owner, and every parameter has
# already been re-owned with `setOwner(param, prc)`.
setOwner(result, prc)
prc.typ = result
popInfoContext(c.config)
@@ -457,18 +374,6 @@ 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)
let canUseBindingCache = c.inGenericContext == 0 and c.matchedConcept == nil
if canUseBindingCache:
result = genericCacheGetFromBindings(c.graph, fn, pt, c.compilesContextId,
c.module)
if result != nil:
if result.kind == skMethod: finishMethod(c, result)
return
# generates an instantiated proc
if c.instCounter > 50:
globalError(c.config, info, "generic instantiation too nested")
@@ -478,6 +383,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
defer:
dec c.instCounter
c.inTypeofContext = currentTypeofContext
# careful! we copy the whole AST including the possibly nil body!
var n = copyTree(fn.ast)
# NOTE: for access of private fields within generics from a different module
# we set the friend module:
let producer = getModule(fn)
@@ -496,6 +403,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
setOwner(result, c.module)
else:
setOwner(result, fn)
result.ast = n
pushOwner(c, result)
# mixin scope:
@@ -503,10 +411,11 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
fillMixinScope(c)
openScope(c)
let gp = fn.ast[genericParamsPos]
let gp = n[genericParamsPos]
if gp.kind != nkGenericParams:
# bug #22137
globalError(c.config, info, "generic instantiation too nested")
n[namePos] = newSymNode(result)
pushInfoContext(c.config, info, fn.detailedInfo)
var entry = TInstantiation.new
entry.sym = result
@@ -522,15 +431,6 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
entry.concreteTypes[i] = s.typ
inc i
entry.genericParamsCount = i
if canUseBindingCache:
for key, _ in pt.pairs:
var seen = false
for binding in entry.bindings:
if binding.key == key:
seen = true
break
if not seen:
entry.bindings.add (key, lookupById(pt, key))
c.matchedConcept = nil
pushProcCon(c, result)
instantiateProcType(c, pt, result, info)
@@ -540,14 +440,9 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
#echo "INSTAN ", fn.name.s, " ", typeToString(result.typ), " ", entry.concreteTypes.len
if tfTriggersCompileTime in result.typ.flags:
incl(result, sfCompileTime)
n[genericParamsPos] = c.graph.emptyNode
var oldPrc = genericCacheGet(c.graph, fn, entry[], c.compilesContextId)
if oldPrc == nil:
# The signature has to be instantiated before the cache can be queried,
# but cache hits don't need a private copy of the generic's full AST.
var n = copyTree(fn.ast)
result.ast = n
n[namePos] = newSymNode(result)
n[genericParamsPos] = c.graph.emptyNode
# we MUST not add potentially wrong instantiations to the caching mechanism.
# This means recursive instantiations behave differently when in
# a ``compiles`` context but this is the lesser evil. See
@@ -560,10 +455,6 @@ 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

View File

@@ -35,16 +35,23 @@ proc semAddr(c: PContext; n: PNode): PNode =
let x = semExprWithType(c, n)
if x.kind == nkSym:
x.sym.flagsImpl.incl(sfAddrTaken)
let aa = isAssignable(c, x)
if aa notin {arLValue, arLocalLValue, arAddressableConst, arLentValue} and
(aa != arDiscriminant or c.inUncheckedAssignSection <= 0):
if isAssignable(c, x) notin {arLValue, arLocalLValue, arAddressableConst, arLentValue}:
localError(c.config, n.info, errExprHasNoAddress)
result.add x
result.typ = makePtrType(c, x.typ.skipTypes({tySink}))
proc semTypeOf(c: PContext; n: PNode): PNode =
let typExpr = semTypeOfImpl(c, n)
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
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
@@ -225,7 +232,10 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
of "stripGenericParams":
result = uninstantiate(operand).toNode(traitCall.info)
of "supportsCopyMem":
result = newIntNodeT(toInt128(ord(supportsCopyMem(operand))), traitCall, c.idgen, c.graph)
let t = operand.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
let complexObj = containsGarbageCollectedRef(t) or
hasDestructor(t)
result = newIntNodeT(toInt128(ord(not complexObj)), traitCall, c.idgen, c.graph)
of "canFormCycles":
result = newIntNodeT(toInt128(ord(types.canFormAcycle(c.graph, operand))), traitCall, c.idgen, c.graph)
of "hasDefaultValue":
@@ -239,13 +249,10 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
assert operand.kind == tyTuple, $operand.kind
result = newIntNodeT(toInt128(operand.len), traitCall, c.idgen, c.graph)
of "distinctBase":
var arg = operand.skipTypes(skippedTypes)
var arg = operand.skipTypes({tyGenericInst})
let rec = semConstExpr(c, traitCall[2]).intVal != 0
while true:
let distinctArg = arg.skipTypes(skippedTypes + {tyGenericInst})
if distinctArg.kind != tyDistinct:
break
arg = distinctArg.base.skipTypes(skippedTypes)
while arg.kind == tyDistinct:
arg = arg.base.skipTypes(skippedTypes + {tyGenericInst})
if not rec: break
result = getTypeDescNode(c, arg, operand.owner, traitCall.info)
of "rangeBase":
@@ -478,15 +485,6 @@ proc turnFinalizerIntoDestructor(c: PContext; orig: PSym; info: TLineInfo): PSym
# proc signature:
result.typ = newProcType(result.info, c.idgen, result)
result.typ.addParam newParam
# `transform` only rewrites the PARAMETER, so the copied AST still names `orig`
# at `namePos`. Make the definition name itself, the invariant every other
# routine AST keeps: the NIF writer re-derives a routine's serialized AST from
# `ast[namePos].sym.ast` (ast2nif's `nkProcDef` branch), so a stale name node
# made this proc serialize `orig`'s body — whose parameter belongs to `orig`.
# Lambda lifting then saw the body's parameter as a variable captured from
# another proc and aborted with "internal error: environment misses: x".
if result.ast != nil and result.ast.safeLen > namePos:
result.ast[namePos] = newSymNode(result, result.info)
proc semQuantifier(c: PContext; n: PNode): PNode =
checkSonsLen(n, 2, c.config)
@@ -618,9 +616,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
of mAsgn:
case n[0].sym.name.s
of "=", "=copy":
result = replaceHookMagic(c, n, attachedAsgn)
result = semAsgnOpr(c, n, nkAsgn)
of "=sink":
result = replaceHookMagic(c, n, attachedSink)
result = semAsgnOpr(c, n, nkSinkAsgn)
else:
result = semShallowCopy(c, n, flags)
of mIsPartOf: result = semIsPartOf(c, n, flags)
@@ -702,10 +700,5 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
if n[1].kind in {nkStmtListExpr, nkBlockExpr,
nkIfExpr, nkCaseStmt, nkTryStmt}:
localError(c.config, n.info, "Nested expressions cannot be moved: '" & $n[1] & "'")
of mMove:
result = n
if isCursor(n[1]):
localError(c.config, n.info, errFailedMove,
"cannot move cursor '" & $n[1] & "'; a cursor does not own its value")
else:
result = n

View File

@@ -440,7 +440,7 @@ proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext =
proc computeRequiresInit(c: PContext, t: PType): bool =
assert t.kind == tyObject
var constrCtx = initConstrContext(t, newNode(nkObjConstr))
discard semConstructTypeAux(c, constrCtx, {efWantNoDefaults})
let initResult = semConstructTypeAux(c, constrCtx, {efWantNoDefaults})
constrCtx.missingFields.len > 0
proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
@@ -450,7 +450,7 @@ proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
assert objType != nil
if objType.kind == tyObject:
var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info))
discard semConstructTypeAux(c, constrCtx, {efIgnoreDefaults})
let initResult = semConstructTypeAux(c, constrCtx, {efIgnoreDefaults})
if constrCtx.missingFields.len > 0:
localError(c.config, info,
"The $1 type doesn't have a default value. The following fields must be initialized: $2." % [typeToString(t), listSymbolNames(constrCtx.missingFields)])
@@ -486,11 +486,6 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
# we have to watch out, there are also 'owned proc' types that can be used
# multiple times as long as they don't have closures.
result.typ.incl tfHasOwned
if t.kind == tyForward and efDetermineType in flags:
# a forward object type does not error during determine-type analysis;
# it now stays unresolved long enough for the existing delayed field-default pass to resolve it after the type section finishes.
result.typ = t
return result
if t.kind != tyObject:
return localErrorNode(c, result, if t.kind != tyGenericBody:
"object constructor needs an object type".dup(addTypeNodeDeclaredLoc(c.config, t))

View File

@@ -82,7 +82,6 @@ type
guards: TModel # nested guards
locked: seq[PNode] # locked locations
gcUnsafe, isRecursive, isTopLevel, hasSideEffect, inEnforcedGcSafe: bool
canRaiseDefect: bool # defects are deliberately omitted from `exc`
isInnerProc: bool
inEnforcedNoSideEffects: bool
isArrayIndexing: bool
@@ -94,7 +93,6 @@ type
graph: ModuleGraph
c: PContext
escapingParams: IntSet
inNimvmBranch: int
PEffects = var TEffects
const
@@ -109,7 +107,7 @@ proc getObjDepth(t: PType): (int, ItemId) =
x = skipTypes(x, skipPtrs)
if x.kind != tyObject:
return (-3, default(ItemId))
stack.add x.bindingId
stack.add x.itemId
x = x.baseClass
inc(result[0])
result[1] = stack[^2]
@@ -142,11 +140,6 @@ proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo; explicit
createTypeBoundOps(tracked.graph, tracked.c, realType.lastSon, info)
createTypeBoundOps(tracked.graph, tracked.c, typ, info, tracked.c.idgen)
for kind in TTypeAttachedOp:
let op = getAttachedOp(tracked.graph, typ, kind)
if op != nil and sfNeverRaises notin op.flags:
tracked.canRaiseDefect = true
break
if tracked.config.selectedGC == gcRefc or
optSeqDestructors in tracked.config.globalOptions or
tfHasAsgn in typ.flags:
@@ -199,23 +192,6 @@ proc shouldWarnRangeConversion(conf: ConfigRef; info: TLineInfo; formalType, arg
else:
result = false
proc conversionCanRaiseDefect(conf: ConfigRef; destType, sourceType: PType): bool =
## Keep this in sync with the range checks introduced by `transformConv`.
let
dest = destType.skipTypes(abstractVarRange)
source = sourceType.skipTypes(abstractVarRange)
case dest.kind
of tyInt..tyInt64, tyEnum, tyChar, tyUInt8..tyUInt32:
if not source.isOrdinalType:
result = dest.kind in tyInt..tyInt64
else:
result = firstOrd(conf, destType) > firstOrd(conf, sourceType) or
lastOrd(conf, sourceType) > lastOrd(conf, destType)
of tyFloat..tyFloat128:
result = destType.skipTypes(abstractVar).kind == tyRange
else:
result = false
proc lockLocations(a: PEffects; pragma: PNode) =
if pragma.kind != nkExprColonExpr:
localError(a.config, pragma.info, "locks pragma without argument")
@@ -518,38 +494,9 @@ proc addRaiseEffect(a: PEffects, e, comesFrom: PNode) =
if sameType(a.graph.excType(aa[i]), a.graph.excType(e)): return
if e.typ != nil:
if isDefectException(e.typ):
a.canRaiseDefect = true
else:
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:
@@ -862,10 +809,6 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; ar
markSideEffect(tracked, a, n.info)
let paramType = if formals != nil and argIndex < formals.signatureLen: formals[argIndex] else: nil
if paramType != nil and paramType.kind in {tyVar}:
let arg = n.skipAddr()
if isSsoStringIndex(tracked.config, arg):
localError(tracked.config, arg.info,
"expression '$1' is immutable, not 'var'" % renderNotLValue(arg))
invalidateFacts(tracked.guards, n)
if n.kind == nkSym and isLocalSym(tracked, n.sym):
makeVolatile(tracked, n.sym)
@@ -1006,6 +949,7 @@ proc trackIf(tracked: PEffects, n: PNode) =
proc trackBlock(tracked: PEffects, n: PNode; typ: PType) =
if n.kind in {nkStmtList, nkStmtListExpr}:
let myBlock = tracked.currentBlock
var oldState = -1
for i in 0..<n.len:
if hasSubnodeWith(n[i], nkBreakStmt):
@@ -1028,6 +972,11 @@ proc trackBlock(tracked: PEffects, n: PNode; typ: PType) =
else:
track(tracked, n)
proc cstringCheck(tracked: PEffects; n: PNode) =
if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]);
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
message(tracked.config, n.info, warnUnsafeCode, renderTree(n))
proc patchResult(c: PEffects; n: PNode) =
if n.kind == nkSym and n.sym.kind == skResult:
let fn = c.owner
@@ -1134,85 +1083,9 @@ proc trackCall(tracked: PEffects; n: PNode) =
markSideEffect(tracked, a, n.info)
# p's effects are ours too:
var a = n[0]
if a.kind == nkSym:
let s = a.sym
case s.magic
of mNone:
if {sfNeverRaises, sfImportc, sfCompilerProc} * s.flags == {} and
(sfSystemModule notin getModule(s).flags or
sfSystemRaisesDefect in s.flags):
tracked.canRaiseDefect = true
of mUnaryMinusI..mAbsI, mAddI..mPred:
if optOverflowCheck in tracked.currOptions:
tracked.canRaiseDefect = true
of mInc, mDec:
let typ = n[1].typ.skipTypes({tyGenericInst, tyAlias, tySink,
tyVar, tyLent, tyRange, tyDistinct})
if optOverflowCheck in tracked.currOptions and
typ.kind notin {tyUInt..tyUInt64}:
tracked.canRaiseDefect = true
of mDivU, mModU:
tracked.canRaiseDefect = true
of mAddF64..mDivF64:
if {optNaNCheck, optInfCheck} * tracked.currOptions != {}:
tracked.canRaiseDefect = true
else:
discard
else:
tracked.canRaiseDefect = true
#if canRaise(a):
# echo "this can raise ", tracked.config $ n.info
let op = a.typ
# A routine whose body reaches a compile-time-only magic (`macros.error`,
# `slurp`, `gorge`, `getAst`, …) can never be code-generated — the C/JS
# backends reject those magics (ccgexprs `errXMustBeCompileTime`). Such a
# routine is compile-time-only by construction; mark it `sfCompileTime` so it
# is treated uniformly as such. Non-IC pruned it by demand-driven codegen, but
# the per-module IC backend emits every owned routine (no DCE) and would
# otherwise feed the magic to codegen. Mirrors the `tfTriggersCompileTime ->
# sfCompileTime` path in `semProcAux`.
#
# GATE TO THE IC STAGES ONLY (`cmdM` sem + `cmdNifC` cg). The magic can reach a
# runtime proc's body via an INLINED TEMPLATE (not a macro/template *owner*, so
# the `insideMeta` walk below can't see it) — e.g. confutils' runtime
# `addConfigFile`/json-serialization's `inputFile` expand a serialization
# template that pastes a `getAst`/`quote` magic inline. Under plain `nim c` such
# a proc still code-generates fine (the magic folds / is demand-pruned), so
# marking it `sfCompileTime` there is a pure regression: "request to generate
# code for .compileTime proc". Only the emit-everything IC backend needs the
# mark, so restrict it to `{cmdM, cmdNifC}` (was `!= cmdNimscript`, which
# wrongly swept in `cmdCompileToC`/JS/`cmdCheck`).
if a.kind == nkSym and a.sym.magic in {mNLen..mNError, mSlurp..mQuoteAst} and
tracked.owner != nil and tracked.owner.kind in routineKinds and
tracked.config.cmd in {cmdM, cmdNifC} and tracked.inNimvmBranch == 0:
# ...but NOT under `nim e`: nimscript has no codegen backend to protect, and
# marking a routine `sfCompileTime` makes `semExpr` eagerly fold calls to it
# at sem time (emConst), where module-level globals it reads have no VM slot
# yet — distros' `detectOsWithAllCmd` reaches `gorge` and reads the plain
# global `unameRes` → "cannot evaluate at compile time: unameRes". In the
# normal nimscript run (emRepl) the module's var section runs first and the
# slot exists, so the marking is both unnecessary and harmful here.
#
# ...and NOT if the routine is — or is nested inside — a macro/template:
# those are VM-only (never code-generated), so the per-module IC backend has
# nothing to protect there, while `sfCompileTime` on a macro-internal nested
# closure breaks its captured-variable access in the VM ("cannot evaluate at
# compile time: n" — `tests/macros/tmacros1`'s `innerProc` reading the
# macro-local `n`). Walk the owner chain and bail on the first
# skMacro/skTemplate. NB mark `tracked.owner` (the routine that directly
# reaches the magic), NOT its outermost enclosing: a runtime proc may legally
# nest a compile-time helper — `tests/generics/tunique_type`'s `[]` proc
# contains a nested `buildResult` macro — and marking the proc would wrongly
# make IT compile-time ("request to generate code for .compileTime proc: []").
var encl = tracked.owner
var insideMeta = false
while encl != nil and encl.kind != skModule:
if encl.kind in {skMacro, skTemplate}:
insideMeta = true
break
encl = encl.skipGenericOwner
if not insideMeta:
incl(tracked.owner, sfCompileTime)
if n.typ != nil:
if tracked.owner.kind != skMacro and n.typ.skipTypes(abstractVar).kind != tyOpenArray:
createTypeBoundOps(tracked, n.typ, n.info)
@@ -1254,17 +1127,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
else:
if laxEffects notin tracked.c.config.legacyFeatures and a.kind == nkSym and
a.sym.kind in routineKinds:
# A hook reaching here has no effect list yet, i.e. it has not been
# effect-tracked. Propagating from its (still unset) type flags would
# spuriously mark the caller GC-unsafe/side-effecting: e.g. under
# `nim ic` a concrete `=destroy` reached through a generic
# instantiation is not analyzed before the instance body is tracked
# here. Skip all such hooks (generalizes #25940, which special-cased
# `=asgn`/`=sink`/`=dup`); once analyzed they carry an effect list and
# take the branch below.
let (isHook, _) = findHookKind(a.sym.name.s)
if not isHook:
propagateEffects(tracked, n, a.sym)
propagateEffects(tracked, n, a.sym)
else:
mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n)
@@ -1301,7 +1164,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
var (isHook, opKind) = findHookKind(a.sym.name.s)
if isHook:
# rebind type bounds operations after createTypeBoundOps call
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes({tyAlias, tyVar})
if a.sym != getAttachedOp(tracked.graph, t, opKind):
createTypeBoundOps(tracked, t, n.info, explicit = true)
# replace builtin hooks with lifted ones
@@ -1339,18 +1202,14 @@ type
PragmaBlockContext = object
oldLocked: int
enforcedGcSafety, enforceNoSideEffects: bool
oldInEnforcedGcSafe, oldInEnforcedNoSideEffects: bool
oldExc, oldTags, oldForbids: int
exc, tags, forbids: PNode
excSource, tagsSource, forbidsSource: PNode
proc createBlockContext(tracked: PEffects): PragmaBlockContext =
var oldForbidsLen = 0
if tracked.forbids != nil: oldForbidsLen = tracked.forbids.len
result = PragmaBlockContext(oldLocked: tracked.locked.len,
enforcedGcSafety: false, enforceNoSideEffects: false,
oldInEnforcedGcSafe: tracked.inEnforcedGcSafe,
oldInEnforcedNoSideEffects: tracked.inEnforcedNoSideEffects,
oldExc: tracked.exc.len, oldTags: tracked.tags.len,
oldForbids: oldForbidsLen)
@@ -1359,27 +1218,25 @@ proc applyBlockContext(tracked: PEffects, bc: PragmaBlockContext) =
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = bc.oldInEnforcedGcSafe
if bc.enforceNoSideEffects:
tracked.inEnforcedNoSideEffects = bc.oldInEnforcedNoSideEffects
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = false
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
setLen(tracked.locked, bc.oldLocked)
if bc.exc != nil:
# beware that 'raises: []' is very different from not saying
# 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, if bc.excSource != nil: bc.excSource else: e)
addRaiseEffect(tracked, e, e)
if bc.tags != nil:
setLen(tracked.tags.sons, bc.oldTags)
for t in bc.tags:
addTag(tracked, t, if bc.tagsSource != nil: bc.tagsSource else: t)
addTag(tracked, t, t)
if bc.forbids != nil:
setLen(tracked.forbids.sons, bc.oldForbids)
for t in bc.forbids:
addNotTag(tracked, t, if bc.forbidsSource != nil: bc.forbidsSource else: t)
addNotTag(tracked, t, t)
proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext) =
let pragma = castPragma[1]
proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
case whichPragma(pragma)
of wGcSafe:
bc.enforcedGcSafety = true
@@ -1392,7 +1249,6 @@ proc castBlock(tracked: PEffects, castPragma: 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}:
@@ -1400,7 +1256,6 @@ proc castBlock(tracked: PEffects, castPragma: 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}:
@@ -1408,7 +1263,6 @@ proc castBlock(tracked: PEffects, castPragma: 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:
@@ -1445,8 +1299,6 @@ 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:
@@ -1460,11 +1312,10 @@ proc track(tracked: PEffects, n: PNode) =
else:
track(tracked, n[0])
of nkRaiseStmt:
tracked.canRaiseDefect = true
if n[0].kind != nkEmpty:
n[0].info = n.info
#throws(tracked.exc, n[0])
addRaiseEffectsFromExpr(tracked, n[0], n)
addRaiseEffect(tracked, n[0], n)
for i in 0..<n.safeLen:
track(tracked, n[i])
createTypeBoundOps(tracked, n[0].typ, n.info)
@@ -1489,8 +1340,6 @@ proc track(tracked: PEffects, n: PNode) =
for i in 0..<n.len: track(tracked, n[i])
tracked.leftPartOfAsgn = oldLeftPartOfAsgn
of nkCheckedFieldExpr:
if optFieldCheck in tracked.currOptions:
tracked.canRaiseDefect = true
track(tracked, n[0])
if tracked.config.hasWarn(warnProveField) or strictCaseObjects in tracked.c.features:
checkFieldAccess(tracked.guards, n, tracked.config, strictCaseObjects in tracked.c.features)
@@ -1505,6 +1354,7 @@ proc track(tracked: PEffects, n: PNode) =
dec tracked.leftPartOfAsgn
addAsgnFact(tracked.guards, n[0], n[1])
notNilCheck(tracked, n[1], n[0].typ)
when false: cstringCheck(tracked, n)
if tracked.owner.kind != skMacro and n[0].typ.kind notin {tyOpenArray, tyVarargs}:
createTypeBoundOps(tracked, n[0].typ, n.info)
if n[0].kind != nkSym or not isLocalSym(tracked, n[0].sym):
@@ -1554,9 +1404,7 @@ proc track(tracked: PEffects, n: PNode) =
of nkCaseStmt: trackCase(tracked, n)
of nkWhen: # This should be a "when nimvm" node.
let oldState = tracked.init.len
inc tracked.inNimvmBranch
track(tracked, n[0][1])
dec tracked.inNimvmBranch
tracked.init.setLen(oldState)
track(tracked, n[1][0])
of nkIfStmt, nkIfExpr: trackIf(tracked, n)
@@ -1668,7 +1516,7 @@ proc track(tracked: PEffects, n: PNode) =
of wNoSideEffect:
bc.enforceNoSideEffects = true
of wCast:
castBlock(tracked, pragmaList[i], bc)
castBlock(tracked, pragmaList[i][1], bc)
else:
discard
applyBlockContext(tracked, bc)
@@ -1689,9 +1537,6 @@ proc track(tracked: PEffects, n: PNode) =
if tracked.owner.kind != skMacro:
createTypeBoundOps(tracked, n.typ, n.info)
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
if optRangeCheck in tracked.currOptions and
conversionCanRaiseDefect(tracked.config, n.typ, n[1].typ):
tracked.canRaiseDefect = true
if n.kind in {nkHiddenStdConv, nkHiddenSubConv} and
n.typ.skipTypes(abstractInst).kind == tyCstring and
not allowCStringConv(n[1]):
@@ -1703,11 +1548,10 @@ proc track(tracked: PEffects, n: PNode) =
message(tracked.config, n.info, warnPtrToCstringConv,
$n[1].typ)
# Check for implicit range conversions. Compile-time constants are already
# fully known here, so only non-constant values need the downsizing warning.
# Check for implicit range conversions
if n.kind == nkHiddenStdConv and (not tracked.isArrayIndexing) and
shouldWarnRangeConversion(tracked.config, n.info, n.typ, n[1].typ) and
getConstExpr(tracked.ownerModule, n[1], tracked.c.idgen, tracked.graph) == nil:
n[1].kind notin {nkCharLit..nkUInt64Lit, nkFloatLit..nkFloat128Lit} and
shouldWarnRangeConversion(tracked.config, n.info, n.typ, n[1].typ):
message(tracked.config, n.info, warnImplicitRangeConversion,
typeToString(n[1].typ) & " -> " & typeToString(n.typ))
@@ -1729,11 +1573,6 @@ proc track(tracked: PEffects, n: PNode) =
if optStaticBoundsCheck in tracked.currOptions:
checkRange(tracked, n[1], n.typ)
of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
if n.kind in {nkObjUpConv, nkObjDownConv}:
if optObjCheck in tracked.currOptions:
tracked.canRaiseDefect = true
elif optRangeCheck in tracked.currOptions:
tracked.canRaiseDefect = true
if n.len == 1:
track(tracked, n[0])
if tracked.owner.kind != skMacro:
@@ -1748,8 +1587,6 @@ proc track(tracked: PEffects, n: PNode) =
if tracked.owner.kind != skMacro:
createTypeBoundOps(tracked, n.typ, n.info)
of nkBracketExpr:
if optBoundsCheck in tracked.currOptions:
tracked.canRaiseDefect = true
if optStaticBoundsCheck in tracked.currOptions and n.len == 2:
if n[0].typ != nil and skipTypes(n[0].typ, abstractVar).kind != tyTuple:
checkBounds(tracked, n[0], n[1])
@@ -1845,18 +1682,13 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode; s: PSym = nil) =
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
let forbidsSpec = effectSpec(n, wForbids)
let tagsSpec = effectSpec(n, wTags)
if not isNil(tagsSpec):
effects[tagEffects] = tagsSpec
elif not isNil(forbidsSpec):
# `.forbids` without `.tags` still declares a known empty tag set.
# Leaving this as nil would mean "unknown tags", which later widens
# indirect calls to `RootEffect`.
effects[tagEffects] = newNodeI(nkArgList, effects.info)
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
effects[tagEffects] = newNodeI(nkArgList, effects.info)
let forbidsSpec = effectSpec(n, wForbids)
if not isNil(forbidsSpec):
effects[forbiddenEffects] = forbidsSpec
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
@@ -1931,9 +1763,6 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
track(t, body)
if t.exc.len == 0 and not t.canRaiseDefect:
s.incl sfNeverRaises
if s.kind != skMacro:
let params = s.typ.n
for i in 1..<params.len:
@@ -1994,6 +1823,7 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
patchResult(t, ensuresSpec)
effects[ensuresEffects] = ensuresSpec
var mutationInfo = MutationInfo()
if views in c.features:
var partitions = computeGraphPartitions(s, body, g, {borrowChecking})
checkBorrowedLocations(partitions, body, g.config)

View File

@@ -17,14 +17,18 @@ const
errInvalidControlFlowX = "invalid control flow: $1"
errSelectorMustBeOfCertainTypes = "selector must be of an ordinal type, float or string"
errExprCannotBeRaised = "only a 'ref object' can be raised"
errBreakOnlyInLoop = "'break' only allowed in loop construct"
errExceptionAlreadyHandled = "exception already handled"
errYieldNotAllowedHere = "'yield' only allowed in an iterator"
errYieldNotAllowedInTryStmt = "'yield' cannot be used within 'try' in a non-inlined iterator"
errInvalidNumberOfYieldExpr = "invalid number of 'yield' expressions"
errCannotReturnExpr = "current routine cannot return an expression"
errGenericLambdaNotAllowed = "A nested proc can have generic parameters only when " &
"it is used as an operand to another routine and the types " &
"of the generic paramers can be inferred from the expected signature."
errCannotInferTypeOfTheLiteral = "cannot infer the type of the $1"
errCannotInferReturnType = "cannot infer the return type of '$1'"
errCannotInferStaticParam = "cannot infer the value of the static param '$1'"
errProcHasNoConcreteType = "'$1' doesn't have a concrete type, due to unspecified generic parameters."
errLetNeedsInit = "'let' symbol requires an initialization"
errThreadvarCannotInit = "a thread var cannot be initialized explicitly; this would only run for the main thread"
@@ -527,7 +531,7 @@ proc semUsing(c: PContext; n: PNode): PNode =
if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "using")
for i in 0..<n.len:
var a = n[i]
if c.config.ideActive: suggestStmt(c, a)
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
if a.kind == nkCommentStmt: continue
if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
checkMinSonsLen(a, 3, c.config)
@@ -541,6 +545,7 @@ proc semUsing(c: PContext; n: PNode): PNode =
strTableIncl(c.signatures, v)
else:
localError(c.config, a.info, "'using' section must have a type")
var def: PNode
if a[^1].kind != nkEmpty:
localError(c.config, a.info, "'using' sections cannot contain assignments")
@@ -833,7 +838,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
for i in 0..<n.len:
var a = n[i]
if c.config.ideActive: suggestStmt(c, a)
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
if a.kind == nkCommentStmt: continue
if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config)
checkMinSonsLen(a, 3, c.config)
@@ -989,7 +994,7 @@ proc semConst(c: PContext, n: PNode): PNode =
var b: PNode
for i in 0..<n.len:
var a = n[i]
if c.config.ideActive: suggestStmt(c, a)
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
if a.kind == nkCommentStmt: continue
if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config)
checkMinSonsLen(a, 3, c.config)
@@ -1091,12 +1096,7 @@ proc symForVar(c: PContext, n: PNode): PSym =
proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
result = n
let iterBase = n[^2].typ
let iterType =
if iterBase.kind == tyIterable:
iterBase.skipModifier
else:
skipTypes(iterBase, {tyAlias, tySink, tyOwned})
var iter = skipTypes(iterType, {tyGenericInst})
var iter = skipTypes(iterBase, {tyGenericInst, tyAlias, tySink, tyOwned})
var iterAfterVarLent = iter.skipTypes({tyGenericInst, tyAlias, tyLent, tyVar})
# n.len == 3 means that there is one for loop variable
# and thus no tuple unpacking:
@@ -1129,9 +1129,10 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
else:
var v = symForVar(c, n[0])
if getCurrOwner(c).kind == skModule: incl(v, sfGlobal)
# Use `iterType` here: it removes outer `tyIterable` / alias-like wrappers
# from the loop source, but still preserves `tyGenericInst` for the loop var.
v.typ = iterType
# BUGFIX: don't use `iter` here as that would strip away
# the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
# for an example:
v.typ = iterBase
n[0] = newSymNode(v)
if sfGenSym notin v.flags and not isDiscardUnderscore(v): addDecl(c, v)
elif v.owner == nil: setOwner(v, getCurrOwner(c))
@@ -1195,14 +1196,14 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
c.p.breakInLoop = oldBreakInLoop
dec(c.p.nestedLoopCounter)
proc implicitIterator(c: PContext, it: string, arg: PNode, flags: TExprFlags): PNode =
proc implicitIterator(c: PContext, it: string, arg: PNode): PNode =
result = newNodeI(nkCall, arg.info)
result.add(newIdentNode(getIdent(c.cache, it), arg.info))
if arg.typ != nil and arg.typ.kind in {tyVar, tyLent}:
result.add newDeref(arg)
else:
result.add arg
result = semExprNoDeref(c, result, flags + {efWantIterator})
result = semExprNoDeref(c, result, {efWantIterator})
proc isTrivalStmtExpr(n: PNode): bool =
for i in 0..<n.len-1:
@@ -1288,8 +1289,7 @@ proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
if result != nil: return result
openScope(c)
result = n
let iteratorFlags = flags * {efPreferIteratorForIterable}
n[^2] = semExprNoDeref(c, n[^2], iteratorFlags + {efWantIterator})
n[^2] = semExprNoDeref(c, n[^2], {efWantIterator})
var call = n[^2]
if call.kind == nkStmtListExpr and (isTrivalStmtExpr(call) or (call.lastSon.kind in nkCallKinds and call.lastSon[0].sym.kind == skIterator)):
@@ -1309,16 +1309,14 @@ proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
elif not isCallExpr or call[0].kind != nkSym or
call[0].sym.kind != skIterator:
if n.len == 3:
n[^2] = implicitIterator(c, "items", n[^2], iteratorFlags)
n[^2] = implicitIterator(c, "items", n[^2])
elif n.len == 4:
n[^2] = implicitIterator(c, "pairs", n[^2], iteratorFlags)
n[^2] = implicitIterator(c, "pairs", n[^2])
else:
localError(c.config, n[^2].info, "iterator within for loop context expected")
result = semForVars(c, n, flags)
else:
result = semForVars(c, n, flags)
if n[^2].typ != nil and n[^2].typ.kind == tyIterable:
n[^2].typ = n[^2].typ.skipModifier
# propagate any enforced VoidContext:
if n[^1].typ == c.enforceVoidContext:
result.typ = c.enforceVoidContext
@@ -1530,7 +1528,7 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) =
while i < n.len: # n may grow due to type pragma macros
var a = n[i]
when defined(nimsuggest):
if c.config.ideActive:
if c.config.cmd == cmdIdeTools:
inc c.inTypeContext
suggestStmt(c, a)
dec c.inTypeContext
@@ -1803,53 +1801,15 @@ proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) =
internalAssert c.config, false
proc typeSectionFinalPass(c: PContext, n: PNode) =
# each top level type needs to be processed, each epoch should reify at least one
var remainingOwners = initIntSet()
for (owner, _, _) in c.forwardTypeUpdates:
remainingOwners.incl owner.id
while c.forwardTypeUpdates.len > 0:
let pending = move c.forwardTypeUpdates
var madeProgress = false
for (owner, typ, typeNode) in pending:
# types that need to be updated due to containing forward types
# and their corresponding type nodes
# for example generic invocations of forward types end up here
var reified = semTypeNode(c, typeNode, nil)
assert reified != nil
assignType(typ, reified)
typ.bindingId = reified.bindingId # same id
if containsForwardType(typ):
c.forwardTypeUpdates.add (owner, typ, typeNode)
elif not remainingOwners.missingOrExcl(owner.id):
madeProgress = true
if not madeProgress:
# can't error here unfortunately
break
for (owner, field, expectedType) in c.forwardFieldUpdates:
semDelayedFieldDefault(c, owner, expectedType, field)
c.forwardFieldUpdates = @[]
# a son that still was a `tyForward` could not propagate `tfHasAsgn` and
# friends to its owner back then, see `rememberFlagUpdate`. Now that every
# forward declaration has a body, redo those propagations. They are recorded
# in declaration order rather than dependency order and an owner can itself
# be the son of another pair, so repeat until nothing changes; this
# terminates because flags are only ever added.
if c.forwardFlagUpdates.len > 0:
let updates = move c.forwardFlagUpdates
c.staleTypeFlags = initIntSet()
var changed = true
while changed:
changed = false
for (owner, elem) in updates:
let before = owner.flags
propagateToOwner(owner, elem)
if owner.flags != before: changed = true
for (typ, typeNode) in c.forwardTypeUpdates:
# types that need to be updated due to containing forward types
# and their corresponding type nodes
# for example generic invocations of forward types end up here
var reified = semTypeNode(c, typeNode, nil)
assert reified != nil
assignType(typ, reified)
typ.itemId = reified.itemId # same id
c.forwardTypeUpdates = @[]
for i in 0..<n.len:
var a = n[i]
if a.kind == nkCommentStmt: continue
@@ -2160,54 +2120,47 @@ proc checkedForDestructor(t: PType): bool =
return true
result = false
proc normalizeTypeHook(t: PType; markAsgn = false): PType =
proc whereToBindTypeHook(c: PContext; t: PType): PType =
result = t
while true:
if markAsgn:
incl(result, tfHasAsgn)
if result.kind == tyCompositeTypeClass and result.base.kind == tyGenericBody:
result = result.base
elif result.kind in {tyGenericBody, tyGenericInst}:
result = result.skipModifier
elif result.kind == tyGenericInvocation:
result = result.genericHead
else:
break
proc whereToBindTypeHook(c: PContext; t: PType): PType =
result = normalizeTypeHook(t)
if result.kind in {tyGenericBody, tyGenericInst}: result = result.skipModifier
elif result.kind == tyGenericInvocation: result = result[0]
else: break
if result.kind in {tyObject, tyDistinct, tySequence, tyString}:
result = canonType(c, result)
proc bindHookToType(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp;
typeToBind: PType): bool =
var obj = typeToBind
if obj.kind notin {tyObject, tyDistinct, tySequence, tyString}:
return false
obj = canonType(c, obj)
let ao = getAttachedOp(c.graph, obj, op)
if ao == s:
discard "forward declared hook"
elif ao.isNil and not checkedForDestructor(obj):
setAttachedOp(c.graph, c.module.position, obj, op, s)
else:
prevDestructor(c, op, ao, obj, n.info)
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
result = true
proc bindDupHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
let t = s.typ
var noError = false
let cond = t.len == 2 and t.returnType != nil
if cond:
var obj = normalizeTypeHook(t.firstParamType, markAsgn = true)
let res = normalizeTypeHook(t.returnType)
var obj = t.firstParamType
while true:
incl(obj, tfHasAsgn)
if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
else: break
if sameType(obj, res):
noError = bindHookToType(c, s, n, op, obj)
var res = t.returnType
while true:
if res.kind in {tyGenericBody, tyGenericInst}: res = res.skipModifier
elif res.kind == tyGenericInvocation: res = res.genericHead
else: break
if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, res):
obj = canonType(c, obj)
let ao = getAttachedOp(c.graph, obj, op)
if ao == s:
discard "forward declared destructor"
elif ao.isNil and not checkedForDestructor(obj):
setAttachedOp(c.graph, c.module.position, obj, op, s)
else:
prevDestructor(c, op, ao, obj, n.info)
noError = true
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
if not noError and sfSystemModule notin s.owner.flags:
localError(c.config, n.info, errGenerated,
@@ -2237,8 +2190,25 @@ proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
t.len >= 2 and t.returnType == nil
if cond:
var obj = normalizeTypeHook(t.firstParamType.skipTypes({tyVar}), markAsgn = true)
noError = bindHookToType(c, s, n, op, obj)
var obj = t.firstParamType.skipTypes({tyVar})
while true:
incl(obj, tfHasAsgn)
if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
else: break
if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
obj = canonType(c, obj)
let ao = getAttachedOp(c.graph, obj, op)
if ao == s:
discard "forward declared destructor"
elif ao.isNil and not checkedForDestructor(obj):
setAttachedOp(c.graph, c.module.position, obj, op, s)
else:
prevDestructor(c, op, ao, obj, n.info)
noError = true
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
if not noError and sfSystemModule notin s.owner.flags:
case op
of attachedTrace:
@@ -2305,12 +2275,35 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
message(c.config, n.info, warnDeprecated, "Overriding `=` hook is deprecated; Override `=copy` hook instead")
let t = s.typ
if t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar:
var obj = normalizeTypeHook(t.firstParamType.elementType, markAsgn = true)
let objB = normalizeTypeHook(t[2])
if sameType(obj, objB):
var obj = t.firstParamType.elementType
while true:
incl(obj, tfHasAsgn)
if obj.kind == tyGenericBody: obj = obj.skipModifier
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
else: break
var objB = t[2]
while true:
if objB.kind == tyGenericBody: objB = objB.skipModifier
elif objB.kind in {tyGenericInvocation, tyGenericInst}:
objB = objB.genericHead
else: break
if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB):
# attach these ops to the canonical tySequence
obj = canonType(c, obj)
#echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj)
let k = if name == "=" or name == "=copy": attachedAsgn else: attachedSink
if bindHookToType(c, s, n, k, obj): return
let ao = getAttachedOp(c.graph, obj, k)
if ao == s:
discard "forward declared op"
elif ao.isNil and not checkedForDestructor(obj):
setAttachedOp(c.graph, c.module.position, obj, k, s)
else:
prevDestructor(c, k, ao, obj, n.info)
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & name & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
return
if sfSystemModule notin s.owner.flags:
localError(c.config, n.info, errGenerated,
"signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)")
@@ -2376,8 +2369,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
if typ.kind != tyObject:
localError(c.config, n.info, pragmaName & " must be either ptr to object or object type.")
if sameOwners(typ.owner, s.owner) and sameOwners(c.module, s.owner):
c.graph.memberProcsPerType.mgetOrPut(typ.bindingId, @[]).add s
logCppMember(c.graph, s)
c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s
else:
localError(c.config, n.info,
pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope")
@@ -2385,7 +2377,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
localError(c.config, n.info, pragmaName & " procs are only supported in C++")
else:
var typ = s.typ.returnType
if typ != nil and typ.kind == tyObject and typ.bindingId notin c.graph.initializersPerType:
if typ != nil and typ.kind == tyObject and typ.itemId notin c.graph.initializersPerType:
var initializerCall = newTree(nkCall, newSymNode(s))
var isInitializer = n[paramsPos].len > 1
for i in 1..<n[paramsPos].len:
@@ -2399,8 +2391,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
initializerCall.add val
inc j
if isInitializer:
c.graph.initializersPerType[typ.bindingId] = initializerCall
logCppMember(c.graph, s)
c.graph.initializersPerType[typ.itemId] = initializerCall
proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
if s.isGenericRoutine:
@@ -2561,9 +2552,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
if not hasProto:
implicitPragmas(c, s, n.info, validPragmas)
if {sfError, sfExportc} * s.flags == {sfError, sfExportc}:
localError(c.config, n.info, "{.error.} and {.exportc.} pragmas are incompatible")
if n[pragmasPos].kind != nkEmpty and sfBorrow notin s.flags:
setEffectsForProcType(c.graph, s.typ, n[pragmasPos], s)
s.typ.incl tfEffectSystemWorkaround
@@ -2603,47 +2591,15 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
addParams(c, proto.typ.n, proto.kind)
proto.info = s.info # more accurate line information
proto.options = s.options
# `s` (the impl symbol) is discarded in favour of `proto`. It still carries
# `s.ast == n` (set above) and stays reachable as the owner of body-local
# symbols, so under IC it would be serialized as a SECOND, body-bearing
# `proc` entry — a phantom duplicate of `proto`. The per-module backend then
# codegens that phantom, whose `result` is owned by `proto` (addResult below
# re-parents it), not by the phantom: lambdalifting's capture check
# (`result.skipGenericOwner != owner`) then wrongly classifies `result` as a
# captured outer variable → "'result' … cannot be captured". Drop the
# discarded impl's body so it can never be emitted as a routine (same leak
# class the `miscPos` adoption below guards against for generic params).
let discardedImpl = s
s = proto
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:
n.comment = proto.ast.comment
proto.ast = n # needed for code generation
if discardedImpl != proto:
discardedImpl.ast = nil
# The impl symbol is discarded in favour of `proto`, but it stays `Complete`
# in this module, so `ast2nif.shouldWriteSymDef` still serializes it. With
# `sfExported` it would be written importable (`x` marker) and an importer
# would load BOTH it and `proto` into the overload set: "ambiguous call;
# both foo and foo" (identical signatures). Normally a discarded impl is a
# gensym/transient that isn't reached this way, but a `{.async: (raises).}`
# forward-decl + impl reconciles HERE with both syms exported. Strip the
# export so the design's "forward declarations are never importable" holds —
# the def still serializes (other refs may resolve to it) but is invisible
# to importer overload resolution; `proto` carries the export.
excl(discardedImpl, sfExported)
popOwner(c)
pushOwner(c, s)
@@ -2656,11 +2612,6 @@ 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
@@ -2874,8 +2825,7 @@ proc incMod(c: PContext, n: PNode, it: PNode, includeStmtResult, resolvedIncStmt
proc evalInclude(c: PContext, n: PNode): PNode =
result = newNodeI(nkStmtList, n.info)
var resolvedIncStmt: PNode = nil
if {optCompress, optGenBif} * c.config.globalOptions != {} or
c.config.cmd == cmdM:
if optCompress in c.config.globalOptions:
# New resolve the include filenames to string literals that contain absolute paths,
# nicer for IC:
resolvedIncStmt = newNodeI(nkIncludeStmt, n.info)
@@ -2956,15 +2906,13 @@ proc semPragmaBlock(c: PContext, n: PNode; expectedType: PType = nil): PNode =
proc semStaticStmt(c: PContext, n: PNode): PNode =
#echo "semStaticStmt"
#writeStackTrace()
let oldErrorCount = c.config.errorCounter
inc c.inStaticContext
openScope(c)
let a = semStmt(c, n[0], {})
closeScope(c)
dec c.inStaticContext
n[0] = a
if c.config.errorCounter == oldErrorCount:
evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
when false:
# for incremental replays, keep the AST as required for replays:
result = n

View File

@@ -581,7 +581,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
result.add newIdentNode(getIdent(c.c.cache, "[]="), n.info)
for i in 0..<a.len: result.add(a[i])
result.add(b)
discard semTemplBody(c, a[0])
let a0 = semTemplBody(c, a[0])
result = semTemplBodySons(c, result)
of nkCurlyExpr:
if a.typ == nil:

View File

@@ -19,11 +19,13 @@ const
errOverflowInEnumX = "The enum '$1' exceeds its maximum value ($2)"
errOrdinalTypeExpected = "ordinal type expected; given: $1"
errSetTooBig = "set is too large; use `std/sets` for ordinal types with more than 2^16 elements"
errBaseTypeMustBeOrdinal = "base type of a set must be an ordinal"
errInheritanceOnlyWithNonFinalObjects = "inheritance only works with non-final objects"
errXExpectsOneTypeParam = "'$1' expects one type parameter"
errArrayExpectsTwoTypeParams = "array expects two type parameters"
errInvalidVisibilityX = "invalid visibility: '$1'"
errXCannotBeAssignedTo = "'$1' cannot be assigned to"
errIteratorNotAllowed = "iterators can only be defined at the module's top level"
errXNeedsReturnType = "$1 needs a return type"
errNoReturnTypeDeclared = "no return type declared"
errTIsNotAConcreteType = "'$1' is not a concrete type"
@@ -58,18 +60,6 @@ proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType =
else:
result = newTypeS(kind, c)
proc rememberFlagUpdate(c: PContext; owner, elem: PType) =
## `propagateToOwner` just derived `owner`'s `tfHasAsgn` & friends from
## `elem`, but inside a type section `elem` can still be an unreified
## `tyForward` which has nothing to derive from yet -- and a type that read
## such a type is provisional in turn. Remember the pair so
## `typeSectionFinalPass` can redo the propagation once every forward
## declaration has a body, the same way `forwardFieldUpdates` defers the
## field defaults.
if elem != nil and (elem.kind == tyForward or elem.id in c.staleTypeFlags):
c.forwardFlagUpdates.add (owner, elem)
c.staleTypeFlags.incl owner.id
proc newConstraint(c: PContext, k: TTypeKind): PType =
result = newTypeS(tyBuiltInTypeClass, c)
result.incl tfCheckedForDestructor
@@ -229,13 +219,11 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tySet, prev, c)
if n.len == 2 and n[1].kind != nkEmpty:
var base = semTypeNode(c, n[1], nil)
if base.kind == tyTypeDesc: base = base.base # unwrap from type traits like distinctBase
addSonSkipIntLit(result, base, c.idgen)
rememberFlagUpdate(c, result, base)
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
if base.kind notin {tyGenericParam, tyGenericInvocation, tyFromExpr}:
if base.kind notin {tyGenericParam, tyGenericInvocation}:
if base.kind == tyForward:
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
c.forwardTypeUpdates.add (base, n[1])
elif not isOrdinalType(base, allowEnumWithHoles = true):
localError(c.config, n.info, errOrdinalTypeExpected % typeToString(base, preferDesc))
elif lengthOrd(c.config, base) > MaxSetElements:
@@ -250,7 +238,6 @@ proc semContainerArg(c: PContext; n: PNode, kindStr: string; result: PType) =
if base.kind == tyVoid:
localError(c.config, n.info, errTIsNotAConcreteType % typeToString(base))
addSonSkipIntLit(result, base, c.idgen)
rememberFlagUpdate(c, result, base)
else:
localError(c.config, n.info, errXExpectsOneTypeParam % kindStr)
addSonSkipIntLit(result, errorType(c), c.idgen)
@@ -331,62 +318,6 @@ proc fitDefaultNode(c: PContext, n: var PNode, expectedType: PType) =
typeAllowedCheck(c, n.info, n.typ, skConst, {taProcContextIsNotMacro, taIsDefaultField})
dec c.inStaticContext
proc containsForwardTypeAux(t: PType; seen: var IntSet): bool
proc containsForwardTypeAux(n: PNode; seen: var IntSet): bool =
result = false
if n.isNil or n.kind in nkLiterals + {nkNilLit, nkEmpty, nkType}:
return
if containsForwardTypeAux(n.typ, seen) or
(n.kind == nkSym and n.sym.typ != n.typ and containsForwardTypeAux(n.sym.typ, seen)):
return true
for i in 0 ..< n.safeLen:
if containsForwardTypeAux(n[i], seen):
return true
proc containsForwardTypeAux(t: PType; seen: var IntSet): bool =
result = false
if t.isNil:
return
if t.kind == tyForward:
return true
if not containsOrIncl(seen, t.id):
if containsForwardTypeAux(t.n, seen):
return true
for i in 0 ..< t.len:
if containsForwardTypeAux(t[i], seen):
return true
proc containsForwardType(arg: PNode): bool =
var seen = initIntSet()
containsForwardTypeAux(arg, seen)
proc containsForwardType(t: PType): bool =
var seen = initIntSet()
containsForwardTypeAux(t, seen)
proc semFieldDefault(c: PContext; owner, expectedType: PType; field: PNode): PType =
result = expectedType
field[^1] = semExprWithType(c, field[^1], {efDetermineType, efAllowSymChoice}, result)
if result == nil:
result = field[^1].typ
if c.inGenericContext == 0:
if containsForwardType(field[^1]):
c.forwardFieldUpdates.add (owner, field, result)
else:
fitDefaultNode(c, field[^1], result)
result = field[^1].typ.skipIntLit(c.idgen)
propagateToOwner(owner, result)
proc semDelayedFieldDefault(c: PContext; owner, expectedType: PType; field: PNode) =
resetSemFlag(field[^1])
fitDefaultNode(c, field[^1], expectedType)
propagateToOwner(owner, field[^1].typ.skipIntLit(c.idgen))
proc isRecursiveType*(t: PType): bool =
# handle simple recusive types before typeFinalPass
var cycleDetector = initIntSet()
@@ -399,7 +330,6 @@ proc addSonSkipIntLitChecked(c: PContext; father, son: PType; it: PNode, id: IdG
localError(c.config, it.info, "illegal recursion in type '" & typeToString(s) & "'")
else:
propagateToOwner(father, s)
rememberFlagUpdate(c, father, s)
proc semDistinct(c: PContext, n: PNode, prev: PType): PType =
if n.len == 0: return newConstraint(c, tyDistinct)
@@ -526,13 +456,7 @@ proc semArrayIndex(c: PContext, n: PNode): PType =
if c.inGenericContext > 0: result.incl tfUnresolved
else:
result = e.typ.skipTypes({tyTypeDesc})
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
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))
@@ -573,7 +497,6 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
# index type:
result = newOrPrevType(tyArray, prev, c, indx)
addSonSkipIntLit(result, base, c.idgen)
rememberFlagUpdate(c, result, base)
else:
localError(c.config, n.info, errArrayExpectsTwoTypeParams)
result = newOrPrevType(tyError, prev, c)
@@ -627,7 +550,13 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
var hasDefaultField = a[^1].kind != nkEmpty
if hasDefaultField:
typ = if a[^2].kind != nkEmpty: semTypeNode(c, a[^2], nil) else: nil
typ = semFieldDefault(c, result, typ, a)
if c.inGenericContext > 0:
a[^1] = semExprWithType(c, a[^1], {efDetermineType, efAllowSymChoice}, typ)
if typ == nil:
typ = a[^1].typ
else:
fitDefaultNode(c, a[^1], typ)
typ = a[^1].typ.skipIntLit(c.idgen)
elif a[^2].kind != nkEmpty:
typ = semTypeNode(c, a[^2], nil)
if c.graph.config.isDefined("nimPreviewRangeDefault") and typ.skipTypes(abstractInst).kind == tyRange:
@@ -650,7 +579,6 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
fSym.sym.ast.flags.incl nfSkipFieldChecking
result.n.add fSym
addSonSkipIntLit(result, typ, c.idgen)
rememberFlagUpdate(c, result, typ)
styleCheckDef(c, a[j].info, field)
onDef(field.info, field)
if result.n.len == 0: result.n = nil
@@ -994,7 +922,14 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
var hasDefaultField = n[^1].kind != nkEmpty
if hasDefaultField:
typ = if n[^2].kind != nkEmpty: semTypeNode(c, n[^2], nil) else: nil
typ = semFieldDefault(c, rectype, typ, n)
if c.inGenericContext > 0:
n[^1] = semExprWithType(c, n[^1], {efDetermineType, efAllowSymChoice}, typ)
if typ == nil:
typ = n[^1].typ
else:
fitDefaultNode(c, n[^1], typ)
typ = n[^1].typ.skipIntLit(c.idgen)
propagateToOwner(rectype, typ)
elif n[^2].kind == nkEmpty:
localError(c.config, n.info, errTypeExpected)
typ = errorType(c)
@@ -1004,7 +939,6 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
n[^1] = firstRange(c.config, typ)
hasDefaultField = true
propagateToOwner(rectype, typ)
rememberFlagUpdate(c, rectype, typ)
var fieldOwner = if c.inGenericContext > 0: c.getCurrOwner
else: rectype.sym
for i in 0..<n.len-2:
@@ -1138,9 +1072,8 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
if needsForwardUpdate:
# if the inherited object is a forward type,
# the entire object needs to be checked again
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) # we retry in the final pass
c.forwardTypeUpdates.add (result, n) # we retry in the final pass
rawAddSon(result, realBase)
rememberFlagUpdate(c, result, realBase)
if realBase == nil and tfInheritable in flags:
result.incl tfInheritable
if tfAcyclic in flags: result.incl tfAcyclic
@@ -1379,7 +1312,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
for i in 0..<paramType.len - 1:
if paramType[i].kind == tyStatic:
var staticCopy = copyType(paramType[i], c.idgen, paramType[i].owner)
var staticCopy = paramType[i].exactReplica
staticCopy.incl tfInferrableStatic
result.rawAddSon staticCopy
else:
@@ -1787,7 +1720,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
for i in 1..<n.len:
var elem = semGenericParamInInvocation(c, n[i])
addToResult(elem, true)
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
c.forwardTypeUpdates.add (result, n)
return
elif t.kind != tyGenericBody:
# we likely got code of the form TypeA[TypeB] where TypeA is
@@ -1840,14 +1773,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
localError(c.config, n.info, errCannotInstantiateX % s.name.s)
result = newOrPrevType(tyError, prev, c)
elif containsGenericInvocationWithForward(n[0]) or hasForwardTypeParam:
# isConcrete == false means this generic type is not instanciated here because
# it invoked with generic parameters.
# Even if isConcrete == true, don't instanciate it now if there are
# unresolved `tyForward` type params.
# Such `tyForward` type params will be semchecked later and we can
# instanciate this next time.
# Some generic types like std/options.Option[T] need the kind of the
# given type argument before their fields can be resolved.
# isConcrete == false means this generic type is not instanciated here because it invoked with generic parameters.
# Even if isConcrete == true, don't instanciate it now if there are any `tyForward` type params.
# Such `tyForward` type params will be semchecked later and we can instanciate this next time.
# Some generic types like std/options.Option[T] needs a type kinds of the given type argument.
# return `tyForward` instead of `tyGenericInvocation` because:
# ```nim
@@ -1863,7 +1792,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
else:
assignType(result, newTypeS(tyForward, c))
result.sym = s
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) #fixes 1500
c.forwardTypeUpdates.add (result, n) #fixes 1500
return
else:
result = instGenericContainer(c, n.info, result,
@@ -1915,12 +1844,6 @@ 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:
@@ -1964,6 +1887,7 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
return result
let
pragmas = n[1]
inherited = n[2]
var owner = getCurrOwner(c)
@@ -2092,57 +2016,6 @@ 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
@@ -2163,7 +2036,16 @@ proc semTypeOf(c: PContext; n: PNode; prev: PType): PType =
proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType =
openScope(c)
let ex = semTypeOfImpl(c, n)
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: {})
closeScope(c)
result = ex.typ
if result.kind == tyFromExpr:
@@ -2207,7 +2089,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, c.idgen)
result.typ = exactReplica(result.typ)
result.typ.incl tfUnresolved
if result.kind == skGenericParam:
@@ -2250,7 +2132,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
result = nil
inc c.inTypeContext
if c.config.ideActive: suggestExpr(c, n)
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
case n.kind
of nkEmpty: result = n.typ
of nkTypeOfExpr:
@@ -2352,9 +2234,6 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
result = semAnyRef(c, n, tyPtr, prev)
elif op.id == ord(wRef):
result = semAnyRef(c, n, tyRef, prev)
elif op.id == ord(wStatic):
checkSonsLen(n, 2, c.config)
result = semStaticType(c, n[1], prev)
elif op.id == ord(wType):
checkSonsLen(n, 2, c.config)
result = semTypeOf(c, n[1], prev)
@@ -2452,7 +2331,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
else:
result = typeExpr.typ.base
if result.isMetaType and
result.kind notin tyTypeClasses:
result.kind != tyUserTypeClass:
# the dot expression may refer to a concept type in
# a different module. allow a normal alias then.
let preprocessed = semGenericStmt(c, n)
@@ -2481,7 +2360,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
# bugfix: keep the fresh id for aliases to integral types:
if s.typ.kind notin {tyBool, tyChar, tyInt..tyInt64, tyFloat..tyFloat128,
tyUInt..tyUInt64}:
prev.bindingId = s.typ.bindingId
prev.itemId = s.typ.itemId
result = prev
of nkSym:
let s = getGenSym(c, n.sym)

View File

@@ -180,6 +180,35 @@ proc prepareNode*(cl: var TReplTypeVars, n: PNode): PNode =
for i in 0..<n.safeLen:
result.add(prepareNode(cl, n[i]))
proc isTypeParam(n: PNode): bool =
# XXX: generic params should use skGenericParam instead of skType
return n.kind == nkSym and
(n.sym.kind == skGenericParam or
(n.sym.kind == skType and sfFromGeneric in n.sym.flags))
when false: # old workaround
proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
# This is needed for tuninstantiatedgenericcalls
# It's possible that a generic param will be used in a proc call to a
# typedesc accepting proc. After generic param substitution, such procs
# should be optionally instantiated with the correct type. In order to
# perform this instantiation, we need to re-run the generateInstance path
# in the compiler, but it's quite complicated to do so at the moment so we
# resort to a mild hack; the head symbol of the call is temporary reset and
# overload resolution is executed again (which may trigger generateInstance).
if n.kind in nkCallKinds and sfFromGeneric in n[0].sym.flags:
var needsFixing = false
for i in 1..<n.safeLen:
if isTypeParam(n[i]): needsFixing = true
if needsFixing:
n[0] = newSymNode(n[0].sym.owner)
return cl.c.semOverloadedCall(cl.c, n, n, {skProc, skFunc}, {})
for i in 0..<n.safeLen:
n[i] = reResolveCallsWithTypedescParams(cl, n[i])
return n
proc replaceObjBranches(cl: TReplTypeVars, n: PNode): PNode =
result = n
case n.kind
@@ -243,17 +272,10 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PT
if n == nil: return
result = copyNode(n)
if n.typ != nil:
var nodeTyp = n.typ
if nodeTyp.kind == tyFromExpr:
if n.typ.kind == tyFromExpr:
# type of node should not be evaluated as a static value
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)
n.typ.incl tfNonConstExpr
result.typ = replaceTypeVarsT(cl, n.typ)
checkMetaInvariants(cl, result.typ)
case n.kind
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
@@ -265,22 +287,13 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PT
replaceTypeVarsS(cl, n.sym, result.typ)
else:
replaceTypeVarsS(cl, n.sym, replaceTypeVarsT(cl, n.sym.typ))
if result.sym.kind == skField and
if result.sym.kind == skField and result.sym.ast != nil and
(cl.owner == nil or result.sym.owner == cl.owner):
if result.sym.ast != nil:
# instantiate default value of object/tuple field
var n = result.sym.ast
cl.c.fitDefaultNode(cl.c, n, result.sym.typ)
result.sym.ast = n
result.sym.typ = n.typ.skipIntLit(cl.c.idgen)
elif result.typ != nil:
# The field SYM can be SHARED across the branches of an `nkRecWhen` (the
# generic body reuses one `value` PSym, so it carries the LAST branch's
# type), while the resolved field NODE carries the correct branch type.
# Sync the sym to the node so the instantiated field's sym-type and
# node-type agree (else a generic-object instance serializes a field
# whose sym-type diverges from its node-type -> loader/computeSize crash).
result.sym.typ = result.typ
# instantiate default value of object/tuple field
var n = result.sym.ast
cl.c.fitDefaultNode(cl.c, n, result.sym.typ)
result.sym.ast = n
result.sym.typ = n.typ.skipIntLit(cl.c.idgen)
# sym type can be nil if was gensym created by macro, see #24048
if result.sym.typ != nil and result.sym.typ.kind == tyVoid:
# don't add the 'void' field
@@ -374,13 +387,6 @@ 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), " itemId=", t.itemId.module, ".",
t.itemId.item, " bindingId=", t.bindingId.module, ".", t.bindingId.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) & "'")
@@ -395,7 +401,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(cl.c.idgen)
result = t.exactReplica
else:
result = copyType(t, cl.c.idgen, t.owner)
copyTypeProps(cl.c.graph, cl.c.idgen.module, result, t)
@@ -423,7 +429,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
var header = t
# search for some instantiation here:
if cl.allowMetaTypes:
result = getOrDefault(cl.localCache, t.bindingId)
result = getOrDefault(cl.localCache, t.itemId)
else:
result = searchInstTypes(cl.c.graph, t)
@@ -440,13 +446,6 @@ 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:
@@ -460,11 +459,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
else:
header = instCopyType(cl, t)
# The instantiating module owns the instance (and announces it as an offer):
# the generic body's module (`t.genericHead.owner`) has no business owning a
# type that references instantiation-site types — that is the IC parent->child
# heap leak the write-barrier surfaces.
result = newType(tyGenericInst, cl.c.idgen, cl.c.module, son = header.genericHead)
result = newType(tyGenericInst, cl.c.idgen, t.genericHead.owner, son = header.genericHead)
result.flags = header.flags
# be careful not to propagate unnecessary flags here (don't use rawAddSon)
# ugh need another pass for deeply recursive generic types (e.g. PActor)
@@ -473,7 +468,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
if not cl.allowMetaTypes:
cacheTypeInst(cl.c, result)
else:
cl.localCache[t.bindingId] = result
cl.localCache[t.itemId] = result
let oldSkipTypedesc = cl.skipTypedesc
cl.skipTypedesc = true
@@ -502,14 +497,8 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
let bbody = last body
var newbody = replaceTypeVarsT(cl, bbody, isInstValue = true)
cl.skipTypedesc = oldSkipTypedesc
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
newbody.flags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
result.flags = result.flags + newbody.flags - tfInstClearedFlags
setToPreviousLayer(cl.typeMap)
@@ -529,11 +518,8 @@ 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 and newbody.state != Sealed:
if newbody.typeInst == nil:
# 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
@@ -647,7 +633,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
# type
# Vector[N: static[int]] = array[N, float64]
# TwoVectors[Na, Nb: static[int]] = (Vector[Na], Vector[Nb])
result = getOrDefault(cl.localCache, t.bindingId)
result = getOrDefault(cl.localCache, t.itemId)
if result != nil: return result
inc cl.recursionLimit
@@ -739,7 +725,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
return
bailout()
result = instCopyType(cl, t)
cl.localCache[t.bindingId] = result
cl.localCache[t.itemId] = result
for i in FirstGenericParamAt..<result.kidsLen:
var r = result[i]
if r != nil:
@@ -755,7 +741,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
of tyGenericInst, tyUserTypeClassInst:
bailout()
result = instCopyType(cl, t)
cl.localCache[t.bindingId] = result
cl.localCache[t.itemId] = result
for i in FirstGenericParamAt..<result.kidsLen:
result[i] = replaceTypeVarsT(cl, result[i])
propagateToOwner(result, result.last)
@@ -770,7 +756,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
result = instCopyType(cl, t)
result.size = -1 # needs to be recomputed
#if not cl.allowMetaTypes:
cl.localCache[t.bindingId] = result
cl.localCache[t.itemId] = result
let propagateInstValue = isInstValue and isRefPtrObject(t)
for i, resulti in result.ikids:
@@ -818,21 +804,11 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
# trough replaceObjBranches in order to resolve any pending nkRecWhen nodes
result = t
# Slow path, we have some work to do. CRUCIAL: only ever mutate a type that
# is LOCAL to the module we are instantiating in (`itemId.module ==
# idgen.module`). A type loaded from another module's NIF (foreign) already
# had its object branches resolved when it was originally compiled; mutating
# it in place here is an old→new heap write that re-homes the loaded type to
# the instantiation site (its sym then looks owned by the consumer module and
# loses its `info`, colliding C type names — the libp2p `Message` bug). The
# prior `state != Sealed` guard was insufficient: a freshly-LOADED type is
# `Complete`, not `Sealed` (`Sealed` only means "already re-written to a NIF").
if t.kind == tyRef and t.hasElementType and t.elementType.kind == tyObject and
t.elementType.n != nil and t.elementType.itemId.module == cl.c.idgen.module.int:
# Slow path, we have some work to do
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 and result.state != Sealed and
result.itemId.module == cl.c.idgen.module.int:
elif result.n != nil and t.kind == tyObject:
# Invalidate the type size as we may alter its structure
result.size = -1
result.n = replaceObjBranches(cl, result.n)
@@ -884,10 +860,7 @@ proc recomputeFieldPositions*(t: PType; obj: PNode; currPosition: var int) =
for i in 1..<obj.len:
recomputeFieldPositions(nil, lastSon(obj[i]), currPosition)
of nkSym:
# 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
obj.sym.position = currPosition
inc currPosition
else: discard "cannot happen"

View File

@@ -10,7 +10,6 @@
## Computes hash values for routine (proc, method etc) signatures.
import ast, ropes, modulegraphs, options, msgs, pathutils
from lineinfos import FileIndex
from std/hashes import Hash
import std/tables
import types
@@ -53,17 +52,7 @@ 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
@@ -75,30 +64,8 @@ proc hashTypeSym(c: var MD5Context, s: PSym; conf: ConfigRef) =
c &= ":anon"
else:
var it = s
# The source file path disambiguates same-named object types from different
# modules whose owner-chain names also coincide (e.g. libp2p kademlia/protobuf
# `Message` vs rendezvous/protobuf `Message`, both modules named `protobuf`).
# A type sym that reaches the backend as a `Complete` stub never individually
# loaded carries `unknownLineInfo` (fileIndex -1), which `toFullPath` collapses
# to the `???` placeholder — so the two would hash to ONE mangled C name and the
# wrong struct gets emitted. Fall back to the sym's HOME module file (its
# per-module NIF-suffix path, stable+unique) for the path. Only fires on a -1
# fileIndex; non-IC type syms always have a real `info`, so the fast path is
# taken and the hash is unchanged (koch boot byte-equal).
let infoFi = s.info.fileIndex
let pathFi = if infoFi.int32 >= 0'i32: infoFi else: s.itemId.module.int32.FileIndex
c &= customPath(conf.toFullPath(pathFi))
when defined(icDbgHash):
var ownerSteps = 0
c &= customPath(conf.toFullPath(s.info))
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
@@ -135,44 +102,15 @@ 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,
" bindingId=", t.bindingId, " 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:
@@ -203,17 +141,12 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
if CoConsiderOwned in flags:
c &= char(t.kind)
c.hashType t.skipModifier, flags, conf
of tyBool, tyChar, tyPointer, tyCstring, tyInt..tyUInt64:
# no canonicalization for builtin scalar-ish / pointer-like types, so
# that e.g. ``pid_t`` or an imported ``pointer`` alias keep their
# backend spelling instead of collapsing into the generic Nim builtin:
of tyBool, tyChar, tyInt..tyUInt64:
# no canonicalization for integral types, so that e.g. ``pid_t`` is
# produced instead of ``NI``:
c &= char(t.kind)
if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}:
# Aliases inherit the external name, but have a different symbol.
if t.sym.loc.snippet != "":
c &= t.sym.loc.snippet
else:
c.hashSym(t.sym)
c.hashSym(t.sym)
of tyObject, tyEnum:
if t.typeInstImpl != nil:
# prevent against infinite recursions here, see bug #8883:
@@ -278,7 +211,6 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
c.hashTree(t.n, {}, conf)
of tyTuple:
c &= char(t.kind)
c &= t.len
if t.n != nil and CoType notin flags:
for i in 0..<t.n.len:
assert(t.n[i].kind == nkSym)
@@ -316,29 +248,6 @@ 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)
@@ -354,21 +263,6 @@ 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)
@@ -552,3 +446,4 @@ proc idOrSig*(s: PSym, currentModule: string,
if counter != 0:
result.add "_" & rope(counter+1)
sigCollisions.inc(sig)

View File

@@ -46,8 +46,7 @@ type
TCandidate* = object
c*: PContext
exactMatches*: int
iteratorPreference*: int # prefer iterators in iterator-oriented contexts
exactMatches*: int # also misused to prefer iters over procs
genericMatches: int # also misused to prefer constraints
subtypeMatches: int
intConvMatches: int # conversions to int are not as expensive
@@ -111,8 +110,7 @@ proc markOwnerModuleAsUsed*(c: PContext; s: PSym)
proc initCandidateAux(ctx: PContext,
callee: PType): TCandidate {.inline.} =
result = TCandidate(c: ctx, exactMatches: 0, subtypeMatches: 0,
iteratorPreference: 0, convMatches: 0, intConvMatches: 0,
genericMatches: 0,
convMatches: 0, intConvMatches: 0, genericMatches: 0,
state: csEmpty, firstMismatch: MismatchInfo(),
callee: callee, call: nil, baseTypeMatch: false,
genericConverter: false, inheritancePenalty: -1
@@ -135,11 +133,6 @@ 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), " itemId=", key.itemId.module, ".",
key.itemId.item, " bindingId=", key.bindingId.module, ".", key.bindingId.item,
" state=", key.state, " -> ", typeToString(val)
put(c.bindings, key, val.skipIntLit(c.c.idgen))
proc typeRel*(c: var TCandidate, f, aOrig: PType,
@@ -182,6 +175,7 @@ proc matchGenericParams*(m: var TCandidate, binding: PNode, callee: PSym) =
## state is set to `csMatch` if all generic params match, `csEmpty` if
## implicit generic parameters are missing (matches but cannot instantiate),
## `csNoMatch` if a constraint fails or param count doesn't match
let c = m.c
let typeParams = callee.ast[genericParamsPos]
let paramCount = typeParams.len
let bindingCount = binding.len-1
@@ -399,7 +393,6 @@ proc complexDisambiguation(a, b: PType): int =
proc writeMatches*(c: TCandidate) =
echo "Candidate '", c.calleeSym.name.s, "' at ", c.c.config $ c.calleeSym.info
echo " exact matches: ", c.exactMatches
echo " iterator preference: ", c.iteratorPreference
echo " generic matches: ", c.genericMatches
echo " subtype matches: ", c.subtypeMatches
echo " intconv matches: ", c.intConvMatches
@@ -418,8 +411,6 @@ proc cmpInheritancePenalty(a, b: int): int =
proc cmpCandidates*(a, b: TCandidate, isFormal=true): int =
result = a.exactMatches - b.exactMatches
if result != 0: return
result = a.iteratorPreference - b.iteratorPreference
if result != 0: return
result = a.genericMatches - b.genericMatches
if result != 0: return
result = a.subtypeMatches - b.subtypeMatches
@@ -648,7 +639,7 @@ type
SkippedPtr = enum skippedNone, skippedRef, skippedPtr
proc skipToObject(t: PType; skipped: var SkippedPtr): PType =
var r {.cursor.} = t
var r = t
# we're allowed to skip one level of ptr/ref:
var ptrs = 0
while r != nil:
@@ -706,6 +697,8 @@ proc recordRel(c: var TCandidate, f, a: PType, flags: TTypeRelFlags): TTypeRelat
result = isEqual
elif sameTupleLengths(a, f):
result = isEqual
let firstField = if f.kind == tyTuple: 0
else: 1
for _, ff, aa in tupleTypePairs(f, a):
var m = typeRel(c, ff, aa, flags)
if m < isSubtype: return isNone
@@ -786,19 +779,6 @@ proc procParamTypeRel(c: var TCandidate; f, a: PType): TTypeRelation =
# if f is metatype.
result = typeRel(c, f, a)
if result == isEqual and
procParamTypeBackendAliases notin c.c.config.legacyFeatures:
# Ensure types that are semantically equal also match at the backend level.
# E.g. reject assigning proc(csize_t) to proc(uint) since these map to
# 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, {IgnoreFlags}):
result = isNone
if result <= isSubrange or inconsistentVarTypes(f, a):
result = isNone
@@ -913,14 +893,16 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
case typ.kind
of tyStatic:
param = paramSym skConst
param.typ = copyType(typ, m.c.idgen, typ.owner)
param.typ = typ.exactReplica
#copyType(typ, c.idgen, typ.owner)
if typ.n == nil:
param.typ.incl tfInferrableStatic
else:
param.ast = typ.n
of tyFromExpr:
param = paramSym skVar
param.typ = copyType(typ, m.c.idgen, typ.owner)
param.typ = typ.exactReplica
#copyType(typ, c.idgen, typ.owner)
else:
param = paramSym skType
param.typ = if typ.isMetaType:
@@ -972,7 +954,8 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
if ff.kind == tyUserTypeClassInst:
result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff)
else:
result = copyType(ff, m.c.idgen, ff.owner)
result = ff.exactReplica
#copyType(ff, c.idgen, ff.owner)
result.n = checkedBody
@@ -987,6 +970,13 @@ proc shouldSkipDistinct(m: TCandidate; rules: PNode, callIdent: PIdent): bool =
if considerQuotedIdent(m.c, r) == callIdent: return false
return true
proc maybeSkipDistinct(m: TCandidate; t: PType, callee: PSym): PType =
if t != nil and t.kind == tyDistinct and t.n != nil and
shouldSkipDistinct(m, t.n, callee.name):
result = t.base
else:
result = t
proc tryResolvingStaticExpr(c: var TCandidate, n: PNode,
allowUnresolved = false,
allowCalls = false,
@@ -1160,14 +1150,8 @@ 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 trBindGenericParam in flags:
conceptFlags.incl mfBindGenericParam
if trCheckGeneric in flags:
conceptFlags.incl mfCheckGeneric
let mres = concepts.conceptMatch(c.c, concpt, a, c.bindings, container, flags = conceptFlags)
@@ -1231,28 +1215,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
assert(aOrig != nil)
let useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
not c.isNoCall and
f.kind != tyTypeDesc and
tfExplicit notin aOrig.flags and
tfConceptMatchedTypeSym notin aOrig.flags
var
useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
not c.isNoCall and
f.kind != tyTypeDesc and
tfExplicit notin aOrig.flags and
tfConceptMatchedTypeSym notin aOrig.flags
template skipTypeCursor(it, kinds: untyped) =
# `ast.last`, not a hand-inlined copy of it. What this replaces was `last`'s
# body verbatim MINUS its `if state == Partial: loadType` line -- and that
# line is the whole point: a NIF-loaded stub answers `kind` off its NIF name
# while `sonsImpl` is still EMPTY, so `sonsImpl[^1]` raised IndexDefect.
# nimbus-eth2 died on it in the very first `nim ic` pass, inside the `x is T`
# under a chronos `{.async.}` iterator's `when`. The second call site below
# is unguarded and runs on EVERY `typeRel`, so this is not a concept-only
# corner: a probe counts 195 Partial `tyVar`/`tyLent` arrivals across one
# nimbus frontend, each of which was an IndexDefect waiting for its turn.
while it.kind in kinds:
it = it.last
var aOrig {.cursor.} = aOrig
if useTypeLoweringRuleInTypeClass:
skipTypeCursor(aOrig, {tyTypeDesc})
aOrig = if useTypeLoweringRuleInTypeClass:
aOrig.skipTypes({tyTypeDesc})
else:
aOrig
if aOrig.kind == tyInferred:
let prev = aOrig.previouslyInferred
@@ -1289,14 +1262,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
template doBind: bool = trDontBind notin flags
# var, sink and static arguments match regular modifier-free types
var a {.cursor.} = aOrig
skipTypeCursor(a, {tyStatic, tyVar, tyLent, tySink})
# Keep this expanded: an expression template materializes a PType temporary
# here, adding an otherwise avoidable reference-counting pair.
if a.kind == tyDistinct and a.n != nil and
shouldSkipDistinct(c, a.n, c.calleeSym.name):
a = a.base
# XXX: Theoretically, distinct types could be skipped before we even
var a = maybeSkipDistinct(c, aOrig.skipTypes({tyStatic, tyVar, tyLent, tySink}), c.calleeSym)
# XXX: Theoretically, maybeSkipDistinct could be called before we even
# start the param matching process. This could be done in `prepareOperand`
# for example, but unfortunately `prepareOperand` is not called in certain
# situation when nkDotExpr are rotated to nkDotCalls
@@ -1774,21 +1741,6 @@ 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:
@@ -2093,18 +2045,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
result = typeRel(c, f.base, a, flags)
else:
result = isGeneric
if result != isNone:
if f.base.kind notin {tyNone, tyGenericParam} and
aOrig.kind == tyStatic and aOrig.n != nil and aOrig.n.typ != nil and
aOrig.n.typ.isEmptyContainer:
# we need to infer the inner type for empty containers
let literal = aOrig.n.copyTree
literal.typ = f.base
let staticArg = newTypeS(tyStatic, c.c, f.base)
staticArg.n = literal
put(c, f, staticArg)
else:
put(c, f, aOrig)
if result != isNone: put(c, f, aOrig)
elif aOrig.n != nil and aOrig.n.typ != nil:
result = if f.base.kind != tyNone:
typeRel(c, f.last, aOrig.n.typ, flags)
@@ -2454,13 +2395,11 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
argSemantized, argOrig: PNode): PNode =
result = nil
var
fMaybeStatic = f.skipTypes({tyDistinct})
arg = argSemantized
a = a
c = m.c
let hasStatic = tfHasStatic in f.flags or
(f.kind == tyDistinct and tfHasStatic in f.skipTypes({tyDistinct}).flags)
if hasStatic:
let fMaybeStatic = if f.kind == tyDistinct: f.skipTypes({tyDistinct}) else: f
if tfHasStatic in fMaybeStatic.flags:
# XXX: When implicit statics are the default
# this will be done earlier - we just have to
# make sure that static types enter here
@@ -2516,10 +2455,6 @@ 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
@@ -2694,7 +2629,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 = copyType(copiedNode.typ, m.c.idgen, copiedNode.typ.owner)
copiedNode.typ = exactReplica(copiedNode.typ)
copiedNode.typ.n = arg
arg = copiedNode
typeRel(m, f, arg.typ)
@@ -2813,8 +2748,7 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode, newlyTyped: var bool):
result = a
elif a.typ.isNil:
if formal.kind == tyIterable:
let flags = {efDetermineType, efAllowStmt, efWantIterator, efWantIterable,
efPreferIteratorForIterable}
let flags = {efDetermineType, efAllowStmt, efWantIterator, efWantIterable}
result = c.semOperand(c, a, flags)
else:
# XXX This is unsound! 'formal' can differ from overloaded routine to
@@ -2831,20 +2765,6 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode, newlyTyped: var bool):
considerGenSyms(c, result)
if result.kind != nkHiddenDeref and result.typ.kind in {tyVar, tyLent} and c.matchedConcept == nil:
result = newDeref(result)
# Recovery for calls resolved too early as non-iterators.
# TODO: retry only skIterator overloads instead of re-semming,
# or preserve iterator-candidates info from the earlier semcheck.
if formal.kind == tyIterable and result.typ.kind != tyIterable and
a.kind in nkCallKinds and a[0].kind in {nkIdent, nkAccQuoted, nkSym, nkOpenSym}:
let recheck = copyTree(a)
recheck.typ = nil
if recheck[0].kind == nkSym and recheck[0].sym != nil:
recheck[0] = newIdentNode(recheck[0].sym.name, recheck[0].info)
let flags = {efDetermineType, efAllowStmt, efNoUndeclared,
efWantIterator, efWantIterable, efPreferIteratorForIterable}
let fresh = c.semOperand(c, recheck, flags)
if fresh.typ != nil and fresh.typ.kind == tyIterable:
return fresh
proc prepareOperand(c: PContext; a: PNode, newlyTyped: var bool): PNode =
if a.typ.isNil:
@@ -2894,12 +2814,9 @@ proc findFirstArgBlock(m: var TCandidate, n: PNode): int =
else: break
proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var IntSet) =
template noMatch() =
if m.calleeSym != nil and m.calleeSym.kind notin {skTemplate, skMacro}:
c.mergeShadowScope
else:
c.rememberShadowDefs
c.closeShadowScope
c.mergeShadowScope #merge so that we don't have to resem for later overloads
m.state = csNoMatch
m.firstMismatch.arg = a
m.firstMismatch.formal = formal
@@ -2955,10 +2872,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
setSon(m.call, formal.position + 1, container)
else:
incrIndexType(container.typ)
# 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]
container.add n[a]
elif n[a].kind == nkExprEqExpr:
# named param
m.firstMismatch.kind = kUnknownNamedParam
@@ -3057,8 +2971,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
setSon(m.call, formal.position + 1, container)
else:
incrIndexType(container.typ)
# bug #25693: see the leading isVarargsUntyped branch above.
container.add nOrig[a]
container.add n[a]
else:
m.baseTypeMatch = false
m.typedescMatched = false
@@ -3110,7 +3023,6 @@ 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

View File

@@ -394,10 +394,9 @@ 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) or
tfIncompleteStruct in typ.flags:
if typ.sym != nil and
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc} and
tfCompleteStruct notin typ.flags:
typ.size = szUnknownSize
typ.align = szUnknownSize
typ.paddingAtEnd = szUnknownSize

View File

@@ -97,7 +97,9 @@ iterator tokenize*(line: string): (int, string) =
## normal JS code. This allows us to map mangled names back to Nim names.
## Yields (column, name). Doesn't yield anything but identifiers.
## See mangleName in compiler/jsgen.nim for how name mangling is done
var col = 0
var
col = 0
token = ""
while col < line.len:
var
token: string = ""
@@ -126,6 +128,7 @@ func parse*(source: string): SourceInfo =
## So it can convert those into a series of mappings
result = default(SourceInfo)
var
skipFirstLine = true
currColumn = 0
currLine = 0
currFile = ""

View File

@@ -10,7 +10,8 @@
## This module implements threadpool's ``spawn``.
import ast, types, idents, magicsys, msgs, options, modulegraphs,
lowerings, liftdestructors, renderer, trees
lowerings, liftdestructors, renderer
from trees import getMagic, getRoot
proc callProc(a: PNode): PNode =
result = newNodeI(nkCall, a.info)
@@ -52,24 +53,6 @@ proc typeNeedsNoDeepCopy(t: PType): bool =
if t.kind in {tyVar, tyLent, tySequence}: t = t.elementType
result = not containsGarbageCollectedRef(t)
proc newSpawnMoveStmt(g: ModuleGraph; idgen: IdGenerator; le, ri: PNode): PNode =
let op = getAttachedOp(g, ri.typ.skipTypes({tyGenericInst, tyAlias, tyVar, tySink}), attachedWasMoved)
if op != nil and sfOverridden in op.flags:
result = newNodeI(nkStmtList, le.info)
result.add newFastAsgnStmt(le, ri)
let wasMovedCall = newNodeI(nkCall, ri.info)
wasMovedCall.add newSymNode(op)
if op.typ != nil and op.typ.signatureLen > 1 and op.typ.firstParamType.kind != tyVar:
wasMovedCall.add ri.skipAddr
else:
wasMovedCall.add makeAddr(ri.skipAddr, idgen)
result.add wasMovedCall
else:
result = newFastMoveStmt(g, le, ri)
proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator; owner: PSym; typ: PType;
v: PNode; useShallowCopy=false): PSym =
result = newSym(skTemp, getIdent(g.cache, genPrefix), idgen, owner, varSection.info,
@@ -85,10 +68,10 @@ proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator;
if varInit != nil:
if g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}:
# inject destructors pass will do its own analysis
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
varInit.add newFastMoveStmt(g, newSymNode(result), v)
else:
if useShallowCopy and typeNeedsNoDeepCopy(typ) or optTinyRtti in g.config.globalOptions:
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
varInit.add newFastMoveStmt(g, newSymNode(result), v)
else:
let deepCopyCall = newNodeI(nkCall, varInit.info, 3)
deepCopyCall[0] = newSymNode(getSysMagic(g, varSection.info, "deepCopy", mDeepCopy))

Some files were not shown because too many files have changed in this diff Show More