Compare commits

..

9 Commits

Author SHA1 Message Date
ringabout
a659dbff1b Merge branch 'devel' into pr_nodecl_type 2025-10-10 21:00:12 +08:00
ringabout
276840610b adds a changelog 2025-01-09 21:31:32 +08:00
ringabout
449511995f uses nodecl 2025-01-08 22:32:29 +08:00
ringabout
33263ef806 fixes tests 2025-01-08 21:45:26 +08:00
ringabout
ce20f1b31a use nodecl 2025-01-08 19:53:43 +08:00
ringabout
c1f99aa362 fixes macos 2025-01-08 19:50:25 +08:00
ringabout
67d80c4971 oops 2025-01-08 19:32:40 +08:00
ringabout
237669a1b2 fixes types with a header 2025-01-08 19:27:25 +08:00
ringabout
d39c76b8bd fixes #24604; importc fails to generate stub type 2025-01-08 18:44:28 +08:00
678 changed files with 11394 additions and 49168 deletions

View File

@@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -15,16 +15,9 @@ jobs:
name: ${{ matrix.platform }}-bisects
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Install OpenSSL (Windows)
if: |
runner.os == 'Windows'
run: choco install openssl.light --version=1.1.1.0 # OpenSSL 3.x removed SSL_library_init
shell: 'powershell'
# v2 wont work here, because uses "hardcoded" nim versions, action "dynamically" finds version with bug.
- uses: jiro4989/setup-nim-action@v1
- uses: jiro4989/setup-nim-action@v1
with:
nim-version: 'devel'

View File

@@ -45,7 +45,7 @@ jobs:
- target: windows
os: windows-latest
- target: osx
os: macos-15
os: macos-13
name: ${{ matrix.target }}
runs-on: ${{ matrix.os }}
@@ -53,7 +53,7 @@ jobs:
steps:
- name: 'Checkout'
uses: actions/checkout@v7
uses: actions/checkout@v4
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,14 +33,14 @@ jobs:
NIM_TESTAMENT_BATCH: ${{ matrix.batch }}
steps:
- name: 'Checkout'
uses: actions/checkout@v7
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: 'Install node.js'
uses: actions/setup-node@v7
- name: 'Install node.js 20.x'
uses: actions/setup-node@v4
with:
node-version: 24
node-version: '20.x'
- name: 'Install dependencies (Linux amd64)'
if: runner.os == 'Linux' && matrix.cpu == 'amd64'

View File

@@ -17,14 +17,14 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: 'Checkout'
uses: actions/checkout@v7
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: 'Install node.js'
uses: actions/setup-node@v7
uses: actions/setup-node@v4
with:
node-version: 24
node-version: ''
- name: 'Install dependencies (Linux amd64)'
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
@@ -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@v7
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@v9
with:
days-before-pr-stale: 365
days-before-pr-close: 30

2
.gitignore vendored
View File

@@ -68,7 +68,6 @@ testament.db
/csources
/csources_v1
/csources_v2
/csources_v3
/dist/
# /lib/fusion # fusion is now unbundled; `git status` should reveal if it's there so users can act on it
@@ -87,7 +86,6 @@ tweeter_test.db
/tests/megatest.nim
/tests/ic/*_temp.nim
/tests/ic/*_mm/
/tests/navigator/*_temp.nim

View File

@@ -28,12 +28,12 @@ jobs:
# # g++-multilib : Depends: gcc-multilib (>= 4:5.3.1-1ubuntu1) but it is not going to be installed
# vmImage: 'ubuntu-18.04'
# CPU: i386
OSX_arm64:
vmImage: 'macos-15'
CPU: arm64
OSX_arm64_cpp:
vmImage: 'macos-15'
CPU: arm64
OSX_amd64:
vmImage: 'macOS-13'
CPU: amd64
OSX_amd64_cpp:
vmImage: 'macOS-13'
CPU: amd64
NIM_COMPILE_TO_CPP: true
Windows_amd64_batch0_3:
vmImage: 'windows-2025'

View File

@@ -19,6 +19,8 @@ errors.
- With `-d:nimPreviewAsmSemSymbol`, backticked symbols are type checked in the `asm/emit` statements.
- `importc` no longer implies `nodecl` for imported types. Use `header` or `nodecl` pragmas so that it doesn't generate a declaration for the type symbol.
- The bare `except:` now panics on `Defect`. Use `except Exception:` or `except Defect:` to catch `Defect`. `--legacy:noPanicOnExcept` is provided for a transition period.
- With `-d:nimPreviewCStringComparisons`, comparsions (`<`, `>`, `<=`, `>=`) between cstrings switch from reference semantics to value semantics like `==` and `!=`.
@@ -27,31 +29,11 @@ errors.
- With `-d:nimPreviewDuplicateModuleError`, importing two modules that share the same name becomes a compile-time error. This includes importing the same module more than once. Use `import foo as foo1` (or other aliases) to avoid collisions.
- Adds the switch `--mangle:nim|cpp`, which selects `nim` or `cpp` style name mangling when used with `debuginfo` on, defaults to `cpp`.
- The second parameter of `succ`, `pred`, `inc`, and `dec` in `system` now accepts `SomeInteger` (previously `Ordinal`).
- Bitshift operators (`shl`, `shr`, `ashr`) now apply bitmasking to the right operand in the C/C++/VM/JS backends.
- 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,17 +55,6 @@ 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:"
@@ -91,15 +62,6 @@ parameter and result types, not just their source-level shape. Use
- `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,31 +100,9 @@ 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
`when` clause would error with "'sizeof' requires '.importc' types to be '.completeStruct'".
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.
- Added `--stdinfile` flag to name of the file used when running program from stdin (defaults to `stdinfile.nim`)
- Added `--styleCheck:warning` flag to treat style check violations as warnings.
## Documentation changes
- Added documentation for the `completeStruct` pragma in the manual.

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@
import
ast, astyaml, options, lineinfos, idents, rodutils,
msgs, bnode
msgs
import std/[hashes, intsets]
import std/strutils except addf
@@ -68,6 +68,8 @@ template mdbg*: bool {.deprecated.} =
# ---------------------------------------------------------------------------
proc lookupInRecord*(n: PNode, field: PIdent): PSym
proc mustRehash*(length, counter: int): bool
proc nextTry*(h, maxHash: Hash): Hash {.inline.}
# ------------- table[int, int] ---------------------------------------------
const
@@ -100,7 +102,7 @@ proc skipConvCastAndClosure*(n: PNode): PNode =
result = result[1]
else: break
proc sameValue*[T: AnyNode](a, b: T): bool =
proc sameValue*(a, b: PNode): bool =
result = false
case a.kind
of nkCharLit..nkUInt64Lit:
@@ -214,6 +216,10 @@ proc getNamedParamFromList*(list: PNode, ident: PIdent): PSym =
proc hashNode(p: RootRef): Hash =
result = hash(cast[pointer](p))
proc mustRehash(length, counter: int): bool =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
import std/tables
const backrefStyle = "\e[90m"
@@ -478,6 +484,12 @@ proc debug(n: PNode; conf: ConfigRef) =
this.value(n)
echo($this.res)
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = ((5 * h) + 1) and maxHash
# 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).
proc objectSetContains*(t: TObjectSet, obj: RootRef): bool =
# returns true whether n is in t
var h: Hash = hashNode(obj) and high(t.data) # start with real hash value
@@ -525,15 +537,101 @@ proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool =
inc(t.counter)
result = false
proc strTableContains*(t: TStrTable, n: PSym): bool =
var h: Hash = n.name.h and high(t.data) # start with real hash value
while t.data[h] != nil:
if (t.data[h] == n):
return true
h = nextTry(h, high(t.data))
result = false
proc strTableRawInsert(data: var seq[PSym], n: PSym) =
var h: Hash = n.name.h and high(data)
while data[h] != nil:
if data[h] == n:
# allowed for 'export' feature:
#InternalError(n.info, "StrTableRawInsert: " & n.name.s)
return
h = nextTry(h, high(data))
assert(data[h] == nil)
data[h] = n
proc symTabReplaceRaw(data: var seq[PSym], prevSym: PSym, newSym: PSym) =
assert prevSym.name.h == newSym.name.h
var h: Hash = prevSym.name.h and high(data)
while data[h] != nil:
if data[h] == prevSym:
data[h] = newSym
return
h = nextTry(h, high(data))
assert false
proc symTabReplace*(t: var TStrTable, prevSym: PSym, newSym: PSym) =
symTabReplaceRaw(t.data, prevSym, newSym)
proc strTableEnlarge(t: var TStrTable) =
var n: seq[PSym]
newSeq(n, t.data.len * GrowthFactor)
for i in 0..high(t.data):
if t.data[i] != nil: strTableRawInsert(n, t.data[i])
swap(t.data, n)
proc strTableAdd*(t: var TStrTable, n: PSym) =
if mustRehash(t.data.len, t.counter): strTableEnlarge(t)
strTableRawInsert(t.data, n)
inc(t.counter)
proc strTableInclReportConflict*(t: var TStrTable, n: PSym;
onConflictKeepOld = false): PSym =
# if `t` has a conflicting symbol (same identifier as `n`), return it
# otherwise return `nil`. Incl `n` to `t` unless `onConflictKeepOld = true`
# and a conflict was found.
assert n.name != nil
var h: Hash = n.name.h and high(t.data)
var replaceSlot = -1
while true:
var it = t.data[h]
if it == nil: break
# Semantic checking can happen multiple times thanks to templates
# and overloading: (var x=@[]; x).mapIt(it).
# So it is possible the very same sym is added multiple
# times to the symbol table which we allow here with the 'it == n' check.
if it.name.id == n.name.id:
if it == n: return nil
replaceSlot = h
h = nextTry(h, high(t.data))
if replaceSlot >= 0:
result = t.data[replaceSlot] # found it
if not onConflictKeepOld:
t.data[replaceSlot] = n # overwrite it with newer definition!
return result # but return the old one
elif mustRehash(t.data.len, t.counter):
strTableEnlarge(t)
strTableRawInsert(t.data, n)
else:
assert(t.data[h] == nil)
t.data[h] = n
inc(t.counter)
result = nil
proc strTableIncl*(t: var TStrTable, n: PSym;
onConflictKeepOld = false): bool {.discardable.} =
result = strTableInclReportConflict(t, n, onConflictKeepOld) != nil
proc strTableGet*(t: TStrTable, name: PIdent): PSym =
var h: Hash = name.h and high(t.data)
while true:
result = t.data[h]
if result == nil: break
if result.name.id == name.id: break
h = nextTry(h, high(t.data))
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 +649,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 +736,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 +769,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):
@@ -740,7 +829,7 @@ proc listSymbolNames*(symbols: openArray[PSym]): string =
result.add ", "
result.add sym.name.s
proc isDiscriminantField*(n: AnyNode): bool =
if n.kind == nkCheckedFieldExpr: sfDiscriminant in n.firstSon.secondSon.sym.flags
elif n.kind == nkDotExpr: sfDiscriminant in n.secondSon.sym.flags
proc isDiscriminantField*(n: PNode): bool =
if n.kind == nkCheckedFieldExpr: sfDiscriminant in n[0][1].sym.flags
elif n.kind == nkDotExpr: sfDiscriminant in n[1].sym.flags
else: false

File diff suppressed because it is too large Load Diff

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)

File diff suppressed because it is too large Load Diff

View File

@@ -1,335 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## `BodyNav` — a scope-chained navigator over a `.bif` routine body.
##
## Ported from Nimony's `nimony/typenav.nim` (`TypeCache` / `TypeScope`). The
## idea being stolen is not the type algebra — we do not need it, `typ` returns
## a fully materialized `PType` — but the SHAPE of the resolution context:
##
## * a chain of scope frames, each a small table, linked to its parent;
## * `openScope` / `closeScope` / `registerLocal`, called BY THE TRAVERSAL as it
## descends and as it walks past each definition;
## * a lookup that consults the chain and, on a miss, falls through to the
## module index (`typenav`'s `tryLoadSym`; here the decoder's own
## `symFromCursor`).
##
## The consequence is the point: the scope is a PRODUCT OF THE WALK. Nothing is
## snapshotted, so nothing can be stale, and a reader that starts at the top of
## a body and descends always has exactly the definitions it has already passed.
##
## WHAT THIS REPLACES. `ast2nif.PendingBody` stashes `localSyms` — a COPY of the
## enclosing sym def's local symbols, taken when the body was deferred — and
## `bnode`'s `BodyScope` then copies it again. `materializeLazyBody` loads the
## body with its own `var pb`, so every definition the load creates lands in a
## table that is discarded on return. A cursor-side reader holding the earlier
## copy therefore cannot see them, and would mint its own `PSym` for the same
## name: two objects, one symbol.
##
## HOW BIG THAT PROBLEM ACTUALLY IS, measured rather than assumed. Build with
## `-d:icLocalSymStats` and every process reports its `localSyms` traffic on
## exit. Over a full `--ic:on` build of the standard-library closure (104
## backend processes):
##
## localHit=0 fieldStub=2 miss=0 sdReg=5902 extractReg=45
##
## Definitions register constantly and NOT ONE use ever resolves through the
## table. The reason is `ast2nif.isLocalSym`, which returns a hardwired `false`:
## every symbol is emitted with a module suffix and resolves through the
## decoder's global `syms` memo, so both spellings get the same `PSym` whatever
## either one has cached. The 5902 registrations are object FIELDS, whose uses
## deliberately go to `loadFieldStub` instead.
##
## So the stale snapshot is a LATENT hazard, not a live bug, and this module is
## not a bug fix — it is the mechanism that keeps it latent once `isLocalSym`
## stops being `false`, or once a body-local name appears for any other reason.
## Said plainly so nobody has to re-derive it: today the nav changes no answers,
## and the grinder in `cgen` proves that by requiring the navigated symbol to be
## the same object the `PNode` loader produced, at every node of every body.
##
## It is not decorative either, and that also has a number. Over the same build,
## the grinder's traversal reports `navHits=42236 navFallbacks=12658
## navRegistered=311`: the chain answers 77% of lookups, and 311 definitions are
## registered by the walk rather than read from a table someone filled in
## earlier. Sabotaging the key (truncating it to three characters, so
## `c_fwrite` and `c_fflush` collide) makes the grinder fail on the first body
## it reaches — so a clean run means the resolution is right, not that the
## lookup never happened.
##
## FIELDS ARE NOT REGISTERED, and that is deliberate. `loadFieldStub` mints a
## fresh stub per use because two distinct fields can share a name (and a
## position) across types — `a.x` and `b.x` in one body are two different
## symbols. Caching a field by its bare name would hand the second use the first
## one's stub, and its type. The nav skips field names entirely and leaves that
## path exactly as it was.
import std / tables
import ast, ast2nif
when defined(nimPreviewSlimSystem):
import std / assertions
import "../dist/nimony/src/lib/nifcore" except pool
type
NavScopeKind* = enum
nsBlock, ## an ordinary nested scope
nsRoutine ## a routine boundary — see `crossedRoutines`
NavScope {.acyclic.} = ref object
locals: Table[string, PSym]
parent: NavScope
kind: NavScopeKind
BridgeTables* = ref object
## The side tables of an IN-PROCESS bridged buffer (`nodebridge.nim`).
## A `.bif` names its symbols because the reader is a different process; a
## buffer built and read inside ONE process does not have to, and paying the
## name round trip anyway would be worse than pointless — it is what makes
## the file path unable to give a field a stable identity (`loadFieldStub`
## mints per use). Here a symbol reference is an index and resolution hands
## back the very same object, so `symAt` is exact and idempotent for every
## symbol kind, fields included.
syms*: seq[PSym]
types*: seq[PType]
origins*: Table[int, PNode]
## Token position -> the `PNode` encoded there, so a cursor can name the
## node it came from. Lives here rather than in `BridgeBuf` because the
## lookup has to be reachable from wherever a location is built, which is
## everywhere in the generator — the same reason `syms` is here.
buf*: ptr TokenBuf
## The buffer `origins` is keyed against; `cursorToPosition` needs it.
## Borrowed, not owned: it points into the `BridgeBuf` that a scoped
## `withBridge` is currently reading, and never outlives it.
BodyNav* = object
## The resolution context for ONE routine body. `base` is what the decoder
## itself needs (the owning module plus a table `loadSymStub` can write
## into); the frame chain on top of it is this module's contribution.
##
## `bridge` is non-nil only while reading a bridged buffer. It is consulted
## FIRST and, when it answers, it answers exactly — there is no fallback,
## because a `(bsym …)` index that the tables cannot resolve is a corrupt
## buffer, not a cache miss.
base*: BodyScope
bridge*: BridgeTables
current: NavScope
hits*: int ## resolved from the chain
fallbacks*: int ## resolved through the decoder
registered*: int ## definitions the walk registered
proc originAt*(t: BridgeTables; c: Cursor): PNode =
## The source node a cursor was encoded from, or nil when there is none (a
## `DotToken`, or a cursor that is not at a node head).
if t == nil or t.buf == nil: return nil
result = t.origins.getOrDefault(cursorToPosition(t.buf[], c), nil)
proc initBodyNav*(base: sink BodyScope): BodyNav =
## A nav over a body, seeded with whatever resolution context the decoder
## handed out. The root frame is a routine frame: a body IS one.
result = BodyNav(base: base,
current: NavScope(locals: initTable[string, PSym](),
parent: nil, kind: nsRoutine))
proc initBridgeNav*(tables: BridgeTables): BodyNav =
## A nav over an in-process bridged buffer. `base` stays empty — a bridged
## buffer names nothing, so there is nothing for the decoder to resolve — but
## the ROOT FRAME still has to exist: a walk brackets its descent with
## `openScope`/`closeScope`, and a nav without a root frame makes the first
## `closeScope` pop past the bottom.
result = BodyNav(bridge: tables,
current: NavScope(locals: initTable[string, PSym](),
parent: nil, kind: nsRoutine))
proc openScope*(nav: var BodyNav; kind = nsBlock) {.inline.} =
nav.current = NavScope(locals: initTable[string, PSym](),
parent: nav.current, kind: kind)
proc closeScope*(nav: var BodyNav) {.inline.} =
doAssert nav.current.parent != nil, "closeScope past the root frame"
nav.current = nav.current.parent
template withScope*(nav: var BodyNav; kind: NavScopeKind; body: untyped) =
openScope(nav, kind)
try:
body
finally:
closeScope(nav)
proc registerLocal*(nav: var BodyNav; name: string; s: PSym) {.inline.} =
## Record a definition the walk has just passed, in the innermost frame.
nav.current.locals[name] = s
inc nav.registered
proc lookupLocal*(nav: BodyNav; name: string): PSym =
## The chain only. `nil` when nothing in scope carries this name.
var it {.cursor.} = nav.current
while it != nil:
let s = it.locals.getOrDefault(name)
if s != nil: return s
it = it.parent
result = nil
proc crossedRoutines*(nav: BodyNav; name: string): int =
## How many routine frames separate the use from the definition — 0 when the
## definition is in the current routine. `typenav` computes the same thing as
## `LocalInfo.crossedProc`, and it is what tells a closure pass that a name is
## captured rather than local. Nothing consumes it here yet; it is the reason
## the frames carry a kind at all, and dropping the kind would make it
## unrecoverable later.
var it {.cursor.} = nav.current
var crossed = 0
while it != nil:
if it.locals.getOrDefault(name) != nil: return crossed
if it.kind == nsRoutine: inc crossed
it = it.parent
result = -1
# ---------------------------------------------------------------------------
# Names
#
# A symbol reaches the reader in four shapes and they all NAME the same thing;
# `navName` is the one place that knows which token holds the name, so the
# lookup key is derived identically no matter which wrapper the writer chose.
proc navName*(n: Cursor): string =
## The NIF name a token denotes, or `""` when the token names no symbol.
case nifcore.kind(n)
of Symbol, SymbolDef:
result = symName(n)
of TagLit:
let tag = n.tags.tagName(cursorTagId(n))
if tag == symDefTagName:
let name = childCursor(n)
result = if nifcore.kind(name) in {Symbol, SymbolDef}: symName(name) else: ""
elif tag == hiddenTypeTagName:
# `(ht <type> <sym>)`
var inner = childCursor(n)
skip inner
result = navName(inner)
elif tag == symNodeFlagsTagName:
# `(nflags <flags> <symnode>)`
var inner = childCursor(n)
skip inner
result = navName(inner)
else:
result = ""
else:
result = ""
proc symToken*(n: Cursor): Cursor =
## The token that actually NAMES the symbol, with the wrappers stripped.
## `loadSymStub` accepts a `Symbol`, a `SymbolDef` or an `(sd ...)` and
## rejects everything else, so the `(ht ...)` / `(nflags ...)` forms have to be
## peeled here rather than at each call site — the same peeling `navName` does
## for the key, kept beside it so the two cannot drift apart.
result = n
while nifcore.kind(result) == TagLit:
let tag = result.tags.tagName(cursorTagId(result))
if tag == hiddenTypeTagName or tag == symNodeFlagsTagName:
var inner = childCursor(result)
skip inner # the explicit type / the node flags
result = inner
else:
break
proc cacheFrame(nav: var BodyNav): NavScope =
## Where a decoder-resolved name is remembered: the nearest ROUTINE frame.
## Not the innermost frame — a `.bif` name is unique within its module (see
## `isLocalSym`), so its meaning cannot change between frames, and caching it
## deeper would only throw it away sooner. Not the root either, so that a
## nested routine's names die with the nested routine.
result = nav.current
while result.kind != nsRoutine and result.parent != nil:
result = result.parent
proc bridgeIndex(n: Cursor; tag: string): int =
## The `<intlit>` payload of a `(bsym …)` / `(btyp …)` token, or -1 when `n`
## is not that shape.
result = -1
if nifcore.kind(n) == TagLit and n.tags.tagName(cursorTagId(n)) == tag:
let payload = childCursor(n)
if nifcore.kind(payload) == IntLit:
result = int(nifcore.intVal(payload))
proc symAt*(nav: var BodyNav; n: Cursor): PSym =
## The symbol a token names: the bridge first (exact), then the chain, then
## the decoder.
if nav.bridge != nil:
let idx = bridgeIndex(symToken(n), bridgeSymTagName)
if idx >= 0:
doAssert idx < nav.bridge.syms.len,
"bridged sym index out of range: " & $idx
inc nav.hits
return nav.bridge.syms[idx]
let name = navName(n)
if name.len > 0:
let cached = lookupLocal(nav, name)
if cached != nil:
inc nav.hits
return cached
inc nav.fallbacks
result = symFromCursor(program, symToken(n), nav.base)
if result != nil and name.len > 0 and not isFieldNifName(name):
cacheFrame(nav).locals[name] = result
proc typeAt*(nav: var BodyNav; n: Cursor): PType =
## Types are not navigated: `ast2nif` already materializes them lazily from
## the module's type index, keyed by name, so there is no per-body state to
## keep and nothing a frame could cache that the decoder does not already.
if nav.bridge != nil:
if nifcore.kind(n) == DotToken: return nil
let idx = bridgeIndex(n, bridgeTypeTagName)
if idx >= 0:
doAssert idx < nav.bridge.types.len,
"bridged type index out of range: " & $idx
return nav.bridge.types[idx]
result = typeFromCursor(program, n, nav.base)
# ---------------------------------------------------------------------------
# Registration during a walk
proc registerDefHere*(nav: var BodyNav; n: Cursor): bool {.discardable.} =
## Register `n` if `n` ITSELF is a definition; do not descend. This is the
## incremental half: a walk calls it on each child before recursing into it,
## so a use can only resolve from the chain to a definition the walk has
## already passed. A use that precedes its definition simply misses and falls
## through to the decoder, which is the behaviour there was before — the nav
## degrades to the old path rather than answering wrongly.
result = false
if nifcore.kind(n) == TagLit and
n.tags.tagName(cursorTagId(n)) == symDefTagName:
let name = navName(n)
if name.len > 0 and not isFieldNifName(name):
let s = symFromCursor(program, n, nav.base)
if s != nil:
registerLocal(nav, name, s)
result = true
proc registerDefs*(nav: var BodyNav; n: Cursor) =
## Register every definition in the SUBTREE at `n` — `typenav.registerLocals`
## with the recursion left in, because a Nim body puts `nkIdentDefs` under an
## `nkVarSection` under the statement list rather than declaring at one level.
##
## Call it on entering a scope to get the eager behaviour (every definition
## known before any use is resolved, which is what a RANDOM-ACCESS reader
## needs), or per statement to get the incremental one (only definitions
## already walked past are visible, which is what a real pass wants and what
## makes use-before-def detectable rather than silently working).
if nifcore.kind(n) == TagLit and
n.tags.tagName(cursorTagId(n)) == symDefTagName:
let name = navName(n)
if name.len > 0 and not isFieldNifName(name):
let s = symFromCursor(program, n, nav.base)
if s != nil: registerLocal(nav, name, s) # `(sd ...)` needs no peeling
return
var c = childCursor(n)
while c.hasMore:
registerDefs(nav, c)
skip c

View File

@@ -69,7 +69,7 @@ proc copyHalf[Key, Val](h, result: Node[Key, Val]) =
result.links[j] = h.links[Mhalf + j]
else:
for j in 0..<Mhalf:
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
result.vals[j] = move h.vals[Mhalf + j]
else:
shallowCopy(result.vals[j], h.vals[Mhalf + j])
@@ -92,7 +92,7 @@ proc insert[Key, Val](h: Node[Key, Val], key: Key, val: Val): Node[Key, Val] =
if less(key, h.keys[j]): break
inc j
for i in countdown(h.entries, j+1):
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
h.vals[i] = move h.vals[i-1]
else:
shallowCopy(h.vals[i], h.vals[i-1])

View File

@@ -154,14 +154,14 @@ template addField(builder: var Builder, constr: var StructInitializer, name: str
# no name, can just add value
valueBody
of siOrderedStruct:
# positional init - name not used in output (empty allowed for anonymous unions)
# no name, can just add value on C
assert name.len != 0, "name has to be given for struct initializer field"
valueBody
of siNamedStruct:
# designated init - empty name for anonymous unions (skips .name = prefix)
if name.len != 0:
builder.add(".")
builder.add(name)
builder.add(" = ")
assert name.len != 0, "name has to be given for struct initializer field"
builder.add(".")
builder.add(name)
builder.add(" = ")
valueBody
proc finishStructInitializer(builder: var Builder, constr: StructInitializer) =
@@ -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

@@ -9,19 +9,9 @@
#
# included from cgen.nim
proc canRaiseDisp(p: BProc; n: AnyNode): bool =
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,22 +21,8 @@ proc canRaiseDisp(p: BProc; n: AnyNode): 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`. Whether it came back INTACT is not something
# the `BNode`/`PNode` grinder can answer — both spellings ask the same
# `PType` and so agree however wrong it is. The only oracle 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: PNode; ri: AnyNode): bool =
## `dest` and `le` stay `PNode`s: they are DESTINATIONS, which the whole call
## family keeps as `PNode`s so they can be nil and so they can be handed to
## the alias analysis, and it is also what keeps the `warnObservableStores`
## message able to RENDER `le` — rendering being a capability the cursor seam
## does not have at all. `ri`, the call being generated, is a cursor.
proc preventNrvo(p: BProc; dest, le, ri: PNode): bool =
proc locationEscapes(p: BProc; le: PNode; inTryStmt: bool): bool =
result = false
var n = le
@@ -64,31 +40,31 @@ proc preventNrvo(p: BProc; dest, le: PNode; ri: AnyNode): 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):
# `isPartOf` compares field symbols by identity and so has not moved to
# the seam; `origin` hands it the same nodes it always compared.
if isPartOf(le, origin(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, origin(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: AnyNode): bool {.inline.} =
result = call.firstSon.kind == nkSym and sfNoInit in call.firstSon.sym.flags
proc hasNoInit(call: PNode): bool {.inline.} =
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:
@@ -119,16 +95,12 @@ proc cleanupTemp(p: BProc; returnType: PType, tmp: TLoc): bool =
else:
result = false
# `le` — the assignment DESTINATION — stays a `PNode` throughout this family.
# It is nilable (`genCall` passes nil, and a cursor has no standalone nil), and
# it is what `preventNrvo` and `isPartOf` are handed, both of which are still
# `PNode`-typed. `ri`, the expression being generated, is the part that moves.
proc fixupCall(p: BProc, le: PNode, ri: AnyNode, 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}:
@@ -207,14 +179,14 @@ proc fixupCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc,
proc genBoundsCheck(p: BProc; arr, a, b: TLoc; arrTyp: PType)
proc reifiedOpenArray(n: AnyNode): bool {.inline.} =
proc reifiedOpenArray(n: PNode): bool {.inline.} =
var x = n
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:
@@ -222,10 +194,10 @@ proc reifiedOpenArray(n: AnyNode): bool {.inline.} =
else:
result = true
proc genOpenArraySlice(p: BProc; q: AnyNode; 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))
proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareForMutation = false): (Rope, Rope) =
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
@@ -251,60 +223,51 @@ proc genOpenArraySlice(p: BProc; q: AnyNode; formalType, destType: PType; prepar
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))
proc openArrayLoc(p: BProc, formalType: PType, n: AnyNode; result: var Builder) =
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)
@@ -324,22 +287,11 @@ proc openArrayLoc(p: BProc, formalType: PType, n: AnyNode; 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)
@@ -363,14 +315,9 @@ proc openArrayLoc(p: BProc, formalType: PType, n: AnyNode; 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)
@@ -384,7 +331,7 @@ proc withTmpIfNeeded(p: BProc, a: TLoc, needsTmp: bool): TLoc =
# Bug https://github.com/status-im/nimbus-eth2/issues/1549
# Aliasing is preferred over stack overflows.
# Also don't regress for non ARC-builds, too risky.
if needsTmp and a.lode.typ != nil and p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and
if needsTmp and a.lode.typ != nil and p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} and
getSize(p.config, a.lode.typ) < 1024:
result = getTemp(p, a.lode.typ, needsInit=false)
genAssignment(p, result, a, {})
@@ -395,20 +342,19 @@ proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc =
result = getTemp(p, a.lode.typ, needsInit=false)
genAssignment(p, result, a, {})
proc genArgStringToCString(p: BProc, n: AnyNode; 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
proc genArgStringToCString(p: BProc, n: PNode; result: var Builder; needsTmp: bool) {.inline.} =
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: AnyNode, param: PSym; call: AnyNode; result: var Builder; needsTmp = false) =
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; needsTmp = false) =
var a: TLoc
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}:
@@ -420,22 +366,16 @@ proc genArg(p: BProc, n: AnyNode, param: PSym; call: AnyNode; result: var Builde
# 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
# A REWRITE, and one that has to be followed. The node's type is replaced in
# place, and a cursor would keep reading the type slot as it was ENCODED —
# the buffer does not see the mutation. So from here this site works on the
# origin, which is the node being mutated and therefore the one that has the
# new type.
let nn = origin(n)
let needsIndirect = mapType(p.config, n[0].typ, mapTypeChooser(n[0]) == skParam) != ctArray
if needsIndirect:
nn.typ = copyType(nn.typ, p.module.idgen, nn.typ.owner)
nn.typ.incl tfVarIsPtr
a = initLocExprSingleUse(p, nn)
n.typ() = n.typ.exactReplica
n.typ.flags.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
@@ -452,7 +392,7 @@ proc genArg(p: BProc, n: AnyNode, param: PSym; call: AnyNode; result: var Builde
addRdLoc(withTmpIfNeeded(p, a, needsTmp), result)
#assert result != nil
proc genArgNoParam(p: BProc, n: AnyNode; result: var Builder; needsTmp = false) =
proc genArgNoParam(p: BProc, n: PNode; result: var Builder; needsTmp = false) =
var a: TLoc
if n.kind == nkStringToCString:
genArgStringToCString(p, n, result, needsTmp)
@@ -462,81 +402,65 @@ proc genArgNoParam(p: BProc, n: AnyNode; result: var Builder; needsTmp = false)
import aliasanalysis
proc potentialAlias(n: AnyNode, potentialWrites: seq[PNode]): bool =
proc potentialAlias(n: PNode, potentialWrites: seq[PNode]): bool =
result = false
for p in potentialWrites:
if p.aliases(n) != no or n.aliases(p) != no:
return true
proc skipTrivialIndirections[T: AnyNode](n: T): T =
## Explicitly generic rather than `(n: AnyNode): AnyNode`: two occurrences of
## a type class in one signature are two INDEPENDENT parameters, so that
## spelling would let the result type drift from the argument's.
proc skipTrivialIndirections(n: PNode): PNode =
result = n
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: AnyNode; result: var seq[PNode]) =
proc getPotentialReads(n: PNode; result: var seq[PNode]) =
case n.kind:
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: AnyNode, typ: PType; result: var Builder, argBuilder: var CallBuilder) =
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, which a `Cursor` cannot do and
# which costs a re-walk per step even on a `PNode`. Materialize them in one
# forward pass and index that; `needTmp` already allocates per call, so this
# is the same order of work.
#
# The arguments are materialized as `PNode`s, not cursors, because the alias
# analysis below (`potentialAlias`, `getPotentialReads`) carries a
# `seq[PNode]` beside the node and has not moved to the seam — see the
# mixed-representation blocker in `bnode`'s module doc. `origin` gives the
# same objects the tree-driven build used, so this is the argument list it
# always was; when that analysis moves, this becomes `seq[AnyNode]`.
var args: seq[PNode] = @[]
for it in sonsFrom(ri, 1): args.add origin(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))
@@ -546,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: AnyNode, 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: AnyNode, 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")
@@ -574,7 +498,7 @@ proc genClosureCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
else:
cCall(p, params, e)
cIfExpr(e,
eCall,
eCall,
cCall(cCast(pTyp, p), params))
template callIter(rp, params: Snippet): Snippet =
@@ -587,22 +511,16 @@ proc genClosureCall(p: BProc, le: PNode, ri: AnyNode, 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)
@@ -611,7 +529,9 @@ proc genClosureCall(p: BProc, le: PNode, ri: AnyNode, 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:
@@ -662,27 +582,27 @@ proc genClosureCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
genCallPattern()
if canRaise: raiseExit(p)
proc genOtherArg(p: BProc; ri: AnyNode; i: int; typ: PType; result: var Builder;
proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder;
argBuilder: var CallBuilder) =
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++
@@ -721,80 +641,80 @@ y.v() --> y.v() is correct
"""
proc skipAddrDeref[T: AnyNode](node: T): T =
proc skipAddrDeref(node: PNode): PNode =
var n = node
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
proc genThisArg(p: BProc; ri: AnyNode; i: int; typ: PType; result: var Builder) =
proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder) =
# for better or worse c2nim translates the 'this' argument to a 'var T'.
# 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: AnyNode; pat: string; typ: PType; result: var Builder) =
proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Builder) =
var i = 0
var j = 1
while i < pat.len:
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(")")
@@ -805,8 +725,8 @@ proc genPatternCall(p: BProc; ri: AnyNode; pat: string; typ: PType; result: var
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:
@@ -828,19 +748,19 @@ proc genPatternCall(p: BProc; ri: AnyNode; pat: string; typ: PType; result: var
if i - 1 >= start:
result.add(substr(pat, start, i - 1))
proc genInfixCall(p: BProc, le: PNode, ri: AnyNode, 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
@@ -865,20 +785,20 @@ proc genInfixCall(p: BProc, le: PNode, ri: AnyNode, 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: AnyNode, d: var TLoc) =
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:
@@ -886,25 +806,25 @@ proc genNamedParamCall(p: BProc, ri: AnyNode, 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(" ")
@@ -936,11 +856,11 @@ proc genNamedParamCall(p: BProc, ri: AnyNode, d: var TLoc) =
p.s(cpsStmts).addStmt():
p.s(cpsStmts).add(extract(pl))
proc notYetAlive(n: AnyNode): bool {.inline.} =
proc notYetAlive(n: PNode): bool {.inline.} =
let r = getRoot(n)
result = r != nil and r.loc.lode == nil
proc isInactiveDestructorCall(p: BProc, e: AnyNode): bool =
proc isInactiveDestructorCall(p: BProc, e: PNode): bool =
#[ Consider this example.
var :tmpD_3281815
@@ -957,29 +877,19 @@ proc isInactiveDestructorCall(p: BProc, e: AnyNode): 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: AnyNode, 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)
proc genCall(p: BProc, e: AnyNode, d: var TLoc) = genAsgnCall(p, nil, e, d)
proc genCall(p: BProc, e: PNode, d: var TLoc) = genAsgnCall(p, nil, e, d)

File diff suppressed because it is too large Load Diff

View File

@@ -16,17 +16,19 @@
## implementation.
template detectVersion(field, corename) =
if m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
result = 2
else:
result = 1
if m.g.field == 0:
let core = getCompilerProc(m.g.graph, corename)
if core == nil or core.kind != skConst:
m.g.field = 1
else:
m.g.field = toInt(ast.getInt(core.astdef))
result = m.g.field
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 --------------------------------
@@ -53,11 +55,11 @@ proc genStringLiteralDataOnlyV1(m: BModule, s: string; result: var Rope) =
res.add(makeCString(s))
m.s[cfsStrData].add(extract(res))
proc genStringLiteralV1(m: BModule; n: AnyNode; result: var Builder) =
proc genStringLiteralV1(m: BModule; n: PNode; result: var Builder) =
if s.isNil:
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil))
else:
let id = nodeTableTestOrSet(m.dataCache, origin(n), m.labels)
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var name: string = ""
if id == m.labels:
# string literal not found in the cache:
@@ -85,8 +87,8 @@ proc genStringLiteralDataOnlyV2(m: BModule, s: string; result: Rope; isConst: bo
res.add(makeCString(s))
m.s[cfsStrData].add(extract(res))
proc genStringLiteralV2(m: BModule; n: AnyNode; isConst: bool; result: var Builder) =
let id = nodeTableTestOrSet(m.dataCache, origin(n), m.labels)
proc genStringLiteralV2(m: BModule; n: PNode; isConst: bool; result: var Builder) =
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var litName: string
if id == m.labels:
cgsym(m, "NimStrPayload")
@@ -111,8 +113,8 @@ proc genStringLiteralV2(m: BModule; n: AnyNode; isConst: bool; result: var Build
res.add(cCast(ptrType("NimStrPayload"), cAddr(litName)))
m.s[cfsStrData].add(extract(res))
proc genStringLiteralV2Const(m: BModule; n: AnyNode; isConst: bool; result: var Builder) =
let id = nodeTableTestOrSet(m.dataCache, origin(n), m.labels)
proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Builder) =
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var pureLit: Rope
if id == m.labels:
pureLit = getTempName(m)
@@ -129,182 +131,25 @@ proc genStringLiteralV2Const(m: BModule; n: AnyNode; isConst: bool; result: var
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: AnyNode; 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: AnyNode; 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, origin(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))
proc genStringLiteral(m: BModule; n: AnyNode; result: var Builder) =
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

@@ -18,7 +18,7 @@ type
proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType)
proc genCaseRange(p: BProc, branch: AnyNode, info: var SwitchCaseBuilder)
proc genCaseRange(p: BProc, branch: PNode, info: var SwitchCaseBuilder)
proc getTemp(p: BProc, t: PType, needsInit=false): TLoc
proc visit(p: BProc, data, visitor: Snippet) =
@@ -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):

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
import
ast, types, msgs, wordrecg,
platform, trees, options, cgendata, mangleutils, renderer, modulegraphs, bnode
platform, trees, options, cgendata, mangleutils, renderer
import std/[hashes, strutils, formatfloat]
@@ -22,38 +22,18 @@ 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
proc stmtsContainPragma*(n: AnyNode, w: TSpecialWord): bool =
## Deliberately NOT `getPragmaStmt(n, w) != nil`, and the reason is the one
## shape the `AnyNode` seam cannot serve: a proc that returns a node OR nil.
## `.bif` spells a missing child as a `DotToken` *inside* a tree, so there is
## no nil token to hand back as a return value, and a `Cursor` is not nilable.
## Predicates split out from such a proc are the way across.
##
## The duplicated traversal is the cost, and it is checked rather than
## trusted: `grindPredicates` asserts this answers exactly
## `getPragmaStmt(n, w) != nil` at every node, so the two cannot drift apart
## silently.
case n.kind
of nkStmtList:
result = false
for it in sons(n):
if stmtsContainPragma(it, w): return true
of nkPragma:
result = false
for it in sons(n):
if whichPragma(it) == w: return true
else:
result = false
proc stmtsContainPragma*(n: PNode, w: TSpecialWord): bool =
result = getPragmaStmt(n, w) != nil
proc hashString*(conf: ConfigRef; s: string): BiggestInt =
# has to be the same algorithm as strmantle.hashString!
@@ -112,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,
@@ -132,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
@@ -168,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)
@@ -180,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"
@@ -191,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
@@ -120,9 +117,9 @@ type
BModuleList* = ref object of RootObj
mainModProcs*, mainModInit*, otherModsInit*, mainDatInit*: Builder
mapping*: Rope # the generated mapping file (if requested)
mods*: seq[BModule] # list of all compiled modules
modules*: seq[BModule] # list of all compiled modules
modulesClosed*: seq[BModule] # list of the same compiled modules, but in the order they were closed
forwardedProcs*: seq[PSym] # procs that did not yet have a body
forwardedProcs*: seq[PSym] # proc:s that did not yet have a body
generatedHeader*: BModule
typeInfoMarker*: TypeCacheWithOwner
typeInfoMarkerV2*: TypeCacheWithOwner
@@ -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,13 +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
typeInfoMarker*: TypeCache # needed for generating type information
@@ -189,30 +172,12 @@ 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
template config*(p: BProc): ConfigRef = p.module.g.config
template vccAndC*(p: BProc): bool = p.module.config.cCompiler == ccVcc and p.module.config.backend == backendC
proc delayedCodegen*(m: BModule): bool {.inline.} =
useAliveDataFromDce in m.flags or m.config.globalOptions.contains(optCompress)
proc includeHeader*(this: BModule; header: string) =
if not this.headerFiles.contains header:
this.headerFiles.add header
@@ -245,8 +210,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

@@ -55,7 +55,7 @@ proc methodCall*(n: PNode; conf: ConfigRef): PNode =
# replace ordinary method by dispatcher method:
let disp = getDispatcher(result[0].sym)
if disp != nil:
result[0].typ = disp.typ
result[0].typ() = disp.typ
result[0].sym = disp
# change the arguments to up/downcasts to fit the dispatcher's parameters:
for i in 1..<result.len:
@@ -123,8 +123,8 @@ proc attachDispatcher(s: PSym, dispatcher: PNode) =
proc createDispatcher(s: PSym; g: ModuleGraph; idgen: IdGenerator): PSym =
var disp = copySym(s, idgen)
incl(disp, sfDispatcher)
excl(disp, sfExported)
incl(disp.flags, sfDispatcher)
excl(disp.flags, sfExported)
let old = disp.typ
disp.typ = copyType(disp.typ, idgen, disp.typ.owner)
copyTypeProps(g, idgen.module, disp.typ, old)
@@ -133,7 +133,7 @@ proc createDispatcher(s: PSym; g: ModuleGraph; idgen: IdGenerator): PSym =
if disp.typ.callConv == ccInline: disp.typ.callConv = ccNimCall
disp.ast = copyTree(s.ast)
disp.ast[bodyPos] = newNodeI(nkEmpty, s.info)
disp.locImpl.snippet = ""
disp.loc.snippet = ""
if s.typ.returnType != nil:
if disp.ast.len > resultPos:
disp.ast[resultPos].sym = copySym(s.ast[resultPos].sym, idgen)
@@ -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

@@ -64,8 +64,7 @@
# the target state is `except` block. For all states in `except` block
# the target state is `finally` block. For all other states there is no
# target state (0, as the first state can never be except nor finally).
# - env var :curExc is created, where "current" exception within the iterator is stored,
# also finallies use it to decide their exit logic
# - env var :curExcLevel is created, finallies use it to decide their exit logic
# - if there are finallies, env var :finallyPath is created. It contains exit state labels
# for every finally level, and is changed in runtime in try, except, break, and return
# nodes to control finally exit behavior.
@@ -112,6 +111,7 @@
# :state = 2 # And we continue to our finally
# break :stateLoop
# of 1: # Except
# inc(:curExcLevel, -1) # Exception is caught
# yield 1
# :tmpResult = 3 # Return
# :finalyPath[LEVEL] = 0 # Configure finally path.
@@ -123,7 +123,7 @@
# of 2: # Finally
# yield 2
# if :finallyPath[LEVEL] == 0: # This node is created by `newEndFinallyNode`
# if :curExc == nil:
# if :curExcLevel == 0:
# :state = -1
# return result = :tmpResult
# else:
@@ -139,7 +139,8 @@
import
ast, msgs, idents,
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos, trees
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos,
options
import std/tables
@@ -164,10 +165,7 @@ type
fn: PSym
tmpResultSym: PSym # Used when we return, but finally has to interfere
finallyPathSym: PSym
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
curExcLevelSym: PSym # Current exception level (because exceptions are stacked)
states: seq[State] # The resulting states. Label is int literal.
finallyPathStack: seq[FinallyTarget] # Stack of split blocks, whiles and finallies
@@ -180,9 +178,6 @@ type
varStates: Table[ItemId, int] # Used to detect if local variable belongs to multiple states
finallyPathLen: PNode # int literal
nullifyCurExc: PNode # Empty node, if no yields in tries
restoreExternExc: PNode # Empty node, id no yields in tries
const
nkSkip = {nkEmpty..nkNilLit, nkTemplateDef, nkTypeSection, nkStaticStmt,
nkCommentStmt, nkMixinStmt, nkBindStmt, nkTypeOfExpr} + procDefs
@@ -201,7 +196,7 @@ proc newStateAssgn(ctx: var Ctx, toValue: PNode): PNode =
proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym =
result = newSym(skVar, getIdent(ctx.g.cache, name), ctx.idgen, ctx.fn, ctx.fn.info)
result.typ = typ
result.flagsImpl.incl sfNoInit
result.flags.incl sfNoInit
assert(not typ.isNil, "Env var needs a type")
let envParam = getEnvParam(ctx.fn)
@@ -247,15 +242,13 @@ proc newFinallyPathAssign(ctx: var Ctx, level: int, label: PNode, info: TLineInf
let fp = newFinallyPathAccess(ctx, level, info)
result = newTree(nkAsgn, fp, label)
proc newCurExcAccess(ctx: var Ctx): PNode =
if ctx.curExcSym.isNil:
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
ctx.curExcSym = ctx.newEnvVar(":curExc", getCurExc.typ)
ctx.newEnvVarAccess(ctx.curExcSym)
proc newCurExcLevelAccess(ctx: var Ctx): PNode =
if ctx.curExcLevelSym.isNil:
ctx.curExcLevelSym = ctx.newEnvVar(":curExcLevel", ctx.g.getSysType(ctx.fn.info, tyInt16))
ctx.newEnvVarAccess(ctx.curExcLevelSym)
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
@@ -291,15 +284,6 @@ proc newTempVar(ctx: var Ctx, typ: PType, parent: PNode, initialValue: PNode = n
assert(not typ.isNil, "Temp var needs a type")
parent.add(ctx.newTempVarDef(result, initialValue))
proc newExternExcAccess(ctx: var Ctx): PNode =
if ctx.externExcSym == nil:
ctx.externExcSym = newSym(skVar, getIdent(ctx.g.cache, ":externExc"), ctx.idgen, ctx.fn, ctx.fn.info)
ctx.externExcSym.typ = ctx.curExcSym.typ
newSymNode(ctx.externExcSym, ctx.fn.info)
proc newRestoreExternException(ctx: var Ctx): PNode =
ctx.g.callCodegenProc("closureIterSetExc", ctx.fn.info, ctx.newExternExcAccess())
proc hasYields(n: PNode): bool =
# TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt.
case n.kind
@@ -314,13 +298,21 @@ proc hasYields(n: PNode): bool =
result = true
break
proc newNullifyCurExc(ctx: var Ctx, info: TLineInfo): PNode =
# :curExc = nil
let curExc = ctx.newCurExcAccess()
proc newNullifyCurExcLevel(ctx: var Ctx, info: TLineInfo, decrement = false): PNode =
# :curEcx = 0
let curExc = ctx.newCurExcLevelAccess()
curExc.info = info
let nilnode = newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil))
let nilnode = ctx.g.newIntLit(info, 0)
result = newTree(nkAsgn, curExc, nilnode)
proc newChangeCurExcLevel(ctx: var Ctx, info: TLineInfo, by: int): PNode =
# inc(:curEcxLevel, by)
let curExc = ctx.newCurExcLevelAccess()
curExc.info = info
result = newTreeIT(nkCall, info, ctx.g.getSysType(info, tyVoid),
newSymNode(ctx.g.getSysMagic(info, "inc", mInc)), curExc,
ctx.g.newIntLit(info, by))
proc newOr(g: ModuleGraph, a, b: PNode): PNode {.inline.} =
result = newTreeIT(nkCall, a.info, g.getSysType(a.info, tyBool),
newSymNode(g.getSysMagic(a.info, "or", mOr)), a, b)
@@ -336,14 +328,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
@@ -357,7 +344,7 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
else:
ifBranch = newNodeI(nkElse, c.info)
ifBranch.add(c[^1])
ifBranch.add(newTreeI(nkStmtList, c.info, ctx.newChangeCurExcLevel(c.info, -1), c[^1]))
ifStmt.add(ifBranch)
if ifStmt.len != 0:
@@ -365,10 +352,9 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
else:
result = ctx.g.emptyNode
proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode): PNode =
proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode) =
# We should adjust finallyPath to gotoOut if exception is handled
# if there is no finally node next to this except, gotoOut must be nil
result = n
if n.kind == nkStmtList:
if n[0].kind == nkIfStmt and n[0][^1].kind != nkElse:
# Not all cases are covered, which means exception is not handled
@@ -391,7 +377,6 @@ proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode): PNode =
# raised one.
n.add newTree(nkCall,
newSymNode(ctx.g.getCompilerProc("popCurrentException")))
n.add ctx.newNullifyCurExc(n.info)
if gotoOut != nil:
# We have a finally node following this except block, and exception is handled
# Configure its path to continue normally
@@ -465,7 +450,7 @@ proc newNotCall(g: ModuleGraph; e: PNode): PNode =
proc boolLit(g: ModuleGraph; info: TLineInfo; value: bool): PNode =
result = newIntLit(g, info, ord value)
result.typ = getSysType(g, info, tyBool)
result.typ() = getSysType(g, info, tyBool)
proc captureVar(c: var Ctx, s: PSym) =
if c.varStates.getOrDefault(s.itemId) != localRequiresLifting:
@@ -600,7 +585,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:
@@ -732,7 +720,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
n[0] = ex
result.add(n)
of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv, nkObjUpConv,
of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv,
nkDerefExpr, nkHiddenDeref:
var ns = false
for i in ord(n.kind == nkCast)..<n.len:
@@ -823,7 +811,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
let (st, ex) = exprToStmtList(n[1])
n.transitionSonsKind(nkBlockStmt)
n.typ = nil
n.typ() = nil
n[1] = st
result.add(n)
result.add(ex)
@@ -835,7 +823,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
# Generate the following code:
# if :finallyPath[FINALLY_LEVEL] == 0:
# if :curExc == nil:
# if :curExcLevel == 0:
# :state = -1
# return result = :tmpResult
# else:
@@ -849,9 +837,9 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
let excNilCmp = newTreeIT(nkCall,
info, ctx.g.getSysType(info, tyBool),
newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info),
ctx.newCurExcAccess(),
newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil)))
newSymNode(ctx.g.getSysMagic(info, "==", mEqI), info),
ctx.newCurExcLevelAccess(),
ctx.g.newIntLit(info, 0))
let retStmt =
block:
@@ -868,7 +856,7 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
retStmt.flags.incl(nfNoRewrite)
let ifBody = newTree(nkIfStmt,
newTree(nkElifBranch, excNilCmp, newTree(nkStmtList, ctx.newRestoreExternException(), retStmt)),
newTree(nkElifBranch, excNilCmp, retStmt),
newTree(nkElse,
newTree(nkStmtList,
newTreeI(nkRaiseStmt, info, ctx.g.emptyNode))))
@@ -923,15 +911,14 @@ proc transformBreakStmt(ctx: var Ctx, n: PNode): PNode =
result = n
proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode =
# "Returning" involves jumping along all the current finally path.
# "Returning" involves jumping along all the cureent finally path.
# The last finally should exit to state 0 which is a special case for last exit
# (either return or propagating exception to the caller).
# It is eccounted for in newEndFinallyNode.
result = newNodeI(nkStmtList, n.info)
# Returns prevent exception propagation
result.add(ctx.nullifyCurExc)
result.add(ctx.newNullifyCurExcLevel(n.info))
var finallyChain = newSeq[PNode]()
@@ -955,7 +942,6 @@ proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode =
result.add(ctx.newJumpAlongFinallyChain(finallyChain, n.info))
else:
# There are no (split) finallies on the path, so we can return right away
result.add(ctx.restoreExternExc)
result.add(n)
proc transformBreaksAndReturns(ctx: var Ctx, n: PNode): PNode =
@@ -966,7 +952,7 @@ proc transformBreaksAndReturns(ctx: var Ctx, n: PNode): PNode =
# of nkContinueStmt: # By this point all relevant continues should be
# lowered to breaks in transf.nim.
of nkReturnStmt:
if nfNoRewrite notin n.flags:
if ctx.curFinallyLevel > 0 and nfNoRewrite notin n.flags:
result = ctx.transformReturnStmt(n)
else:
for i in 0..<n.len:
@@ -990,14 +976,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:
@@ -1005,7 +986,6 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
of nkYieldStmt:
result = addGotoOut(result, gotoOut)
result = newTree(nkStmtList, ctx.restoreExternExc, result)
of nkElse, nkElseExpr:
result[0] = addGotoOut(result[0], gotoOut)
@@ -1075,7 +1055,7 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
result.add(tryLabel)
var tryBody = toStmtList(n[0])
var exceptBody = ctx.collectExceptState(n)
let exceptBody = ctx.collectExceptState(n)
var finallyBody = ctx.getFinallyNode(n)
var exceptLabel, finallyLabel = ctx.g.emptyNode
@@ -1114,7 +1094,8 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
inc ctx.curFinallyLevel
ctx.finallyPathStack.add(FinallyTarget(n: n[^1], label: finallyLabel))
tryBody = ctx.transformClosureIteratorBody(tryBody, tryOut)
if ctx.transformClosureIteratorBody(tryBody, tryOut) != tryBody:
internalError(ctx.g.config, "transformClosureIteratorBody != tryBody")
if exceptBody.kind != nkEmpty:
ctx.curExcLandingState = if finallyBody.kind != nkEmpty: finallyLabel
@@ -1122,9 +1103,10 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
discard ctx.newState(exceptBody, false, exceptLabel)
let normalOut = if finallyBody.kind != nkEmpty: gotoOut else: nil
exceptBody = ctx.addElseToExcept(exceptBody, normalOut)
ctx.addElseToExcept(exceptBody, normalOut)
# echo "EXCEPT: ", renderTree(exceptBody)
exceptBody = ctx.transformClosureIteratorBody(exceptBody, tryOut)
if ctx.transformClosureIteratorBody(exceptBody, tryOut) != exceptBody:
internalError(ctx.g.config, "transformClosureIteratorBody != exceptBody")
ctx.curExcLandingState = oldExcLandingState
@@ -1132,17 +1114,10 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
discard ctx.finallyPathStack.pop()
discard ctx.newState(finallyBody, false, finallyLabel)
let finallyExit = newTree(nkGotoState, ctx.newFinallyPathAccess(ctx.curFinallyLevel - 1, finallyBody.info))
finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit)
if ctx.transformClosureIteratorBody(finallyBody, finallyExit) != finallyBody:
internalError(ctx.g.config, "transformClosureIteratorBody != finallyBody")
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)
@@ -1226,6 +1201,34 @@ proc createExceptionTable(ctx: var Ctx): PNode {.inline.} =
for i in 0 .. ctx.states.high:
result.add(ctx.states[i].excLandingState)
proc newExceptBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} =
# Generates code:
# :state = exceptionTable[:state]
# if :state == 0:
# raise
result = newNodeI(nkStmtList, info)
let intTyp = ctx.g.getSysType(info, tyInt)
let boolTyp = ctx.g.getSysType(info, tyBool)
# :state = exceptionTable[:state]
result.add ctx.newStateAssgn(
newTreeIT(nkBracketExpr, info, intTyp,
ctx.createExceptionTable(),
ctx.newStateAccess()))
# if :state == 0: raise
block:
let cond = newTreeIT(nkCall, info, boolTyp,
ctx.g.getSysMagic(info, "==", mEqI).newSymNode(),
ctx.newStateAccess(),
newIntTypeNode(0, intTyp))
let raiseStmt = newTree(nkRaiseStmt, ctx.g.emptyNode)
let ifBranch = newTree(nkElifBranch, cond, raiseStmt)
let ifStmt = newTree(nkIfStmt, ifBranch)
result.add(ifStmt)
proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
# Generates code:
# var :tmp = nil
@@ -1233,45 +1236,24 @@ proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
# body
# except:
# :state = exceptionTable[:state]
# :curExc = getCurrentException()
# if :state == 0:
# closureIterSetExc(:externExc)
# raise
# if :state == 0:
# raise
# :tmp = getCurrentException()
#
# pushCurrentException(:curExc)
# pushCurrentException(:tmp)
let info = ctx.fn.info
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
let exceptBody = newTreeI(nkStmtList, info,
ctx.newStateAssgn(
newTreeIT(nkBracketExpr, info, ctx.g.getSysType(info, tyInt),
ctx.createExceptionTable(),
ctx.newStateAccess())),
newTreeI(nkFastAsgn, info, ctx.newCurExcAccess(), getCurExc))
let tryBody = newTree(nkStmtList, n)
let exceptBody = ctx.newExceptBody(ctx.fn.info)
let exceptBranch = newTree(nkExceptBranch, exceptBody)
result = newTree(nkStmtList)
result.add newTree(nkTryStmt,
newTree(nkStmtList, n),
newTree(nkExceptBranch, exceptBody))
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
let tempExc = ctx.newTempVar(getCurExc.typ, result)
result.add newTree(nkTryStmt, tryBody, exceptBranch)
exceptBody.add ctx.newTempVarAsgn(tempExc, getCurExc)
# if :state == 0:
# closureIterSetExc(:externExc)
# raise
block:
let boolTyp = ctx.g.getSysType(info, tyBool)
let intTyp = ctx.g.getSysType(info, tyInt)
let cond = newTreeIT(nkCall, info, boolTyp,
ctx.g.getSysMagic(info, "==", mEqI).newSymNode(),
ctx.newStateAccess(),
newIntTypeNode(0, intTyp))
let raiseStmt = newTree(nkRaiseStmt, ctx.newCurExcAccess())
let ifBody = newTree(nkStmtList, ctx.newRestoreExternException(), raiseStmt)
let ifBranch = newTree(nkElifBranch, cond, ifBody)
let ifStmt = newTree(nkIfStmt, ifBranch)
result.add(ifStmt)
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newCurExcAccess())
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newTempVarAccess(tempExc))
result.add ctx.newChangeCurExcLevel(n.info, 1)
proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
# while true:
@@ -1294,19 +1276,6 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
blockStmt.add(blockBody)
loopBody.add(blockStmt)
if ctx.hasExceptions:
# Since we have yields in tries, we must switch current exception
# between the iter and "outer world"
# var :externExc = getCurrentException()
# closureIterSetExc(:curExc)
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
discard ctx.newExternExcAccess()
let setCurExc = ctx.g.callCodegenProc("closureIterSetExc", n.info, ctx.newCurExcAccess())
result = newTreeI(nkStmtList, n.info,
ctx.newTempVarDef(ctx.externExcSym, getCurExc),
setCurExc,
result)
proc countStateOccurences(ctx: var Ctx, n: PNode, stateOccurences: var openArray[int]) =
## Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
## For every such node increment stateOccurences[stateIdx]
@@ -1324,15 +1293,16 @@ proc countStateOccurences(ctx: var Ctx, n: PNode, stateOccurences: var openArray
proc replaceDeletedStates(ctx: var Ctx, n: PNode): PNode =
result = n
if n.kind == nkIntLit:
let idx = n.intVal
if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == n and ctx.states[idx].deletable:
let gt = ctx.replaceDeletedStates(skipStmtList(ctx.states[idx].body))
assert(gt.kind == nkGotoState)
result = gt[0]
else:
for i in 0 ..< n.safeLen:
n[i] = ctx.replaceDeletedStates(n[i])
for i in 0 ..< n.safeLen:
let c = n[i]
if c.kind == nkIntLit:
let idx = c.intVal
if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == c and ctx.states[idx].deletable:
let gt = ctx.replaceDeletedStates(skipStmtList(ctx.states[idx].body))
assert(gt.kind == nkGotoState)
n[i] = gt[0]
else:
n[i] = ctx.replaceDeletedStates(c)
proc replaceInlinedStates(ctx: var Ctx, n: PNode): PNode =
## Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
@@ -1363,7 +1333,6 @@ proc optimizeStates(ctx: var Ctx) =
# Replace deletable state labels to labels of respective non-empty states
for i in 0 .. ctx.states.high:
ctx.states[i].body = ctx.replaceDeletedStates(ctx.states[i].body)
ctx.states[i].excLandingState = ctx.replaceDeletedStates(ctx.states[i].excLandingState)
# Remove deletable states
var i = 0
@@ -1408,34 +1377,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:
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
@@ -1501,16 +1454,10 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
ctx.curExcLandingState = ctx.newStateLabel()
ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), idgen, fn, fn.info)
ctx.nullifyCurExc = newTree(nkStmtList)
ctx.restoreExternExc = newTree(nkStmtList)
var n = n.toStmtList
# echo "transformed into ", n
discard ctx.newState(n, false, nil)
let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1))
var ns = false
@@ -1523,10 +1470,6 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
# Splitting transformation
discard ctx.transformClosureIteratorBody(n, gotoOut)
if ctx.hasExceptions:
ctx.nullifyCurExc.add(ctx.newNullifyCurExc(fn.info))
ctx.restoreExternExc.add(ctx.newRestoreExternException())
# Assign state label indexes
for i in 0 .. ctx.states.high:
ctx.states[i].label.intVal = i
@@ -1544,9 +1487,7 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
let body = ctx.transformStateAssignments(s.body)
caseDispatcher.add newTreeI(nkOfBranch, body.info, s.label, body)
caseDispatcher.add newTreeI(nkElse, n.info,
newTree(nkStmtList, ctx.restoreExternExc,
newTreeI(nkReturnStmt, n.info, g.emptyNode)))
caseDispatcher.add newTreeI(nkElse, n.info, newTreeI(nkReturnStmt, n.info, g.emptyNode))
result = wrapIntoStateLoop(ctx, caseDispatcher)
result = liftLocals(ctx, result)

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
@@ -118,7 +118,7 @@ const
errInvalidCmdLineOption = "invalid command line option: '$1'"
errOnOrOffExpectedButXFound = "'on' or 'off' expected, but '$1' found"
errOnOffOrListExpectedButXFound = "'on', 'off' or 'list' expected, but '$1' found"
errOffHintsError = "'off', 'hint', 'warning', 'error' or 'usages' expected, but '$1' found"
errOffHintsError = "'off', 'hint', 'error' or 'usages' expected, but '$1' found"
proc invalidCmdLineOption(conf: ConfigRef; pass: TCmdLinePass, switch: string, info: TLineInfo) =
if switch == " ": localError(conf, info, errInvalidCmdLineOption % "-")
@@ -245,12 +245,11 @@ proc processCompile(conf: ConfigRef; filename: string) =
extccomp.addExternalFileToCompile(conf, found)
const
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'yrc', 'atomicArc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'atomicArc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found"
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)
@@ -267,7 +266,6 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
of "markandsweep": result = conf.selectedGC == gcMarkAndSweep
of "destructors", "arc": result = conf.selectedGC == gcArc
of "orc": result = conf.selectedGC == gcOrc
of "yrc": result = conf.selectedGC == gcYrc
of "hooks": result = conf.selectedGC == gcHooks
of "go": result = conf.selectedGC == gcGo
of "none": result = conf.selectedGC == gcNone
@@ -307,13 +305,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)
@@ -373,7 +364,6 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
result = false
of "panics": result = contains(conf.globalOptions, optPanics)
of "jsbigint64": result = contains(conf.globalOptions, optJsBigInt64)
of "mangle": result = contains(conf.globalOptions, optItaniumMangle)
else:
result = false
invalidCmdLineOption(conf, passCmd1, switch, info)
@@ -503,13 +493,10 @@ proc parseCommand*(command: string): Command =
of "gendepend": cmdGendepend
of "dump": cmdDump
of "parse": cmdParse
of "rod": cmdRod
of "secret": cmdInteractive
of "nop", "help": cmdNop
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) =
@@ -522,11 +509,6 @@ proc setCmd*(conf: ConfigRef, cmd: Command) =
of cmdCompileToOC: conf.backend = backendObjc
of cmdCompileToJS: conf.backend = backendJs
of cmdCompileToNif: conf.backend = backendNif
of cmdNifC:
conf.backend = backendC # NIF to C compilation
of cmdM:
# cmdM requires optCompress for proper IC handling (include files, etc.)
conf.globalOptions.incl optCompress
else: discard
proc setCommandEarly*(conf: ConfigRef, command: string) =
@@ -581,7 +563,6 @@ proc unregisterArcOrc*(conf: ConfigRef) =
undefSymbol(conf.symbols, "gcdestructors")
undefSymbol(conf.symbols, "gcarc")
undefSymbol(conf.symbols, "gcorc")
undefSymbol(conf.symbols, "gcyrc")
undefSymbol(conf.symbols, "gcatomicarc")
undefSymbol(conf.symbols, "nimSeqsV2")
undefSymbol(conf.symbols, "nimV2")
@@ -615,10 +596,6 @@ proc processMemoryManagementOption(switch, arg: string, pass: TCmdLinePass,
conf.selectedGC = gcOrc
defineSymbol(conf.symbols, "gcorc")
registerArcOrc(pass, conf)
of "yrc":
conf.selectedGC = gcYrc
defineSymbol(conf.symbols, "gcyrc")
registerArcOrc(pass, conf)
of "atomicarc":
conf.selectedGC = gcAtomicArc
defineSymbol(conf.symbols, "gcatomicarc")
@@ -627,11 +604,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 +632,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 +684,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 +737,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)
@@ -820,18 +762,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.globalOptions.excl optCDebug
else:
localError(conf, info, "expected native|gdb|on|off but found " & arg)
of "mangle":
case arg.normalize
of "nim":
conf.globalOptions.excl optItaniumMangle
of "cpp":
conf.globalOptions.incl optItaniumMangle
else:
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 +777,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,69 +881,14 @@ 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}:
let m = findModule(conf, arg, toFullPath(conf, info)).string
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 findModule(conf, arg, toFullPath(conf, info)).string
of "include":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
let m = findModule(conf, arg, toFullPath(conf, info)).string
if m.len == 0:
localError(conf, info, "Cannot resolve filename: " & arg)
else:
conf.implicitIncludes.add m
conf.implicitIncludes.add findModule(conf, arg, toFullPath(conf, info)).string
of "listcmd":
processOnOffSwitchG(conf, {optListCmd}, arg, pass, info)
of "asm":
@@ -1046,7 +920,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,17 +970,11 @@ 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
of "on": conf.symbolFiles = v2Sf
of "off": conf.symbolFiles = disabledSf
of "writeonly": conf.symbolFiles = writeOnlySf
of "readonly": conf.symbolFiles = readOnlySf
@@ -1214,9 +1082,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
of "shownonexports":
expectNoArg(conf, switch, arg, pass, info)
showNonExportedFields(conf)
of "raw":
expectNoArg(conf, switch, arg, pass, info)
docRawOutput(conf)
of "exceptions":
case arg.normalize
of "cpp": conf.exc = excCpp
@@ -1248,10 +1113,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
defineSymbol(conf.symbols, "nimSeqsV2")
of "stylecheck":
case arg.normalize
of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError, optStyleWarning}
of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} - {optStyleError, optStyleWarning}
of "warning": conf.globalOptions = conf.globalOptions + {optStyleWarning} - {optStyleHint, optStyleError}
of "error": conf.globalOptions = conf.globalOptions + {optStyleError} - {optStyleHint, optStyleWarning}
of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError}
of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} - {optStyleError}
of "error": conf.globalOptions = conf.globalOptions + {optStyleError}
of "usages": conf.globalOptions.incl optStyleUsages
else: localError(conf, info, errOffHintsError % arg)
of "showallmismatches":
@@ -1341,16 +1205,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,10 +11,9 @@
## 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, astalgo, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
import std/sets
import std/intsets
when defined(nimPreviewSlimSystem):
import std/assertions
@@ -30,7 +29,7 @@ proc declareSelf(c: PContext; info: TLineInfo) =
let ow = getCurrOwner(c)
let s = newSym(skType, getIdent(c.cache, "Self"), c.idgen, ow, info)
s.typ = newType(tyTypeDesc, c.idgen, ow)
s.typ.incl {tfUnresolved, tfPacked}
s.typ.flags.incl {tfUnresolved, tfPacked}
s.typ.add newType(tyEmpty, c.idgen, ow)
addDecl(c, s, info)
@@ -72,23 +71,20 @@ 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]
## Pair of (concept type id, implementation type id) used for cycle detection
MatchCon = object ## Context we pass around during concept matching.
bindings: LayeredIdTable
marker: HashSet[ConceptTypePair] ## Tracks (concept, type) pairs being checked to detect cycles.
marker: IntSet ## Some protection against wild runaway recursions.
potentialImplementation: PType ## the concrete type that might match the concept we try to match.
magic: TMagic ## mArrGet and mArrPut is wrong in system.nim and
## cannot be fixed that easily.
## Thus we special case it here.
concpt: PType ## current concept being evaluated
depthCount = 0
flags: set[MatchFlags]
MatchKind = enum
mkNoMatch, mkSubset, mkSame
@@ -141,7 +137,7 @@ proc bindParam(c: PContext, m: var MatchCon; key, v: PType): bool {. discardable
# check previously bound value
if not matchType(c, old, value, m):
return false
elif key.hasElementType and not key.elementType.isNil and key.elementType.kind != tyNone:
elif key.hasElementType and key.elementType.kind != tyNone:
# check constaint
if matchType(c, unrollGenericParam(key), value, m) == false:
return false
@@ -192,48 +188,32 @@ iterator traverseTyOr(t: PType): PType {. closure .}=
proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon): bool =
assert not(potentialImpl.reduceToBase.kind == tyConcept)
let concpt = f.reduceToBase
# Handle self-referential concepts: when a concept references itself in its body
# (e.g., `A = concept; proc test(x: Self, y: A)`), the inner type A has n=nil.
# We detect this by checking if the concept has the same symbol name as the
# one we're currently matching and has no body (n=nil).
if concpt.n.isNil:
if concpt.sym != nil and m.concpt.sym != nil and
concpt.sym == m.concpt.sym:
# Self-reference: check if potentialImpl matches what we're already checking
return potentialImpl.id == m.potentialImplementation.id
# Concept without body that's not a self-reference - cannot match
return false
# 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)
if pair in m.marker:
if m.depthCount > 0:
# concepts that are more then 2 levels deep are treated like
# tyAnything to stop dependencies from getting out of control
return true
m.marker.incl pair
var efPot = potentialImpl
if potentialImpl.isSelf:
if m.concpt.n == concpt.n:
m.marker.excl pair
return true
efPot = m.potentialImplementation
var oldBindings = m.bindings
m.bindings = newTypeMapLayer(m.bindings)
let oldPotentialImplementation = m.potentialImplementation
m.potentialImplementation = efPot
let oldConcept = m.concpt
m.concpt = concpt
var invocation: PType = nil
if f.kind in {tyGenericInvocation, tyGenericInst}:
invocation = f
inc m.depthCount
result = processConcept(c, concpt, invocation, oldBindings, m)
dec m.depthCount
m.potentialImplementation = oldPotentialImplementation
m.concpt = oldConcept
m.bindings = oldBindings
m.marker.excl pair
proc cmpConceptDefs(c: PContext, fn, an: PNode, m: var MatchCon): bool=
if fn.kind != an.kind:
@@ -271,8 +251,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:
@@ -281,22 +263,6 @@ proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind =
return mkNoMatch
return mkSubset
proc isObjectSubtype(f, a: PType): bool =
var t = a
result = false
while t != nil:
t = t.baseClass
if t == nil:
break
t = t.skipTypes({tyPtr,tyRef})
if t == nil:
break
if t.kind != tyObject:
break
if sameObjectTypes(f, t):
result = true
break
proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
## The heart of the concept matching process. 'f' is the formal parameter of some
## routine inside the concept that we're looking for. 'a' is the formal parameter
@@ -330,10 +296,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:
@@ -358,8 +327,6 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
result = a.base.sym == f.sym
else:
result = sameType(f, a)
if not result and f.kind == tyObject and a.kind == tyObject:
result = isObjectSubtype(f, a)
of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid:
result = a.skipTypes(ignorableForArgType).kind == f.kind
of tyBool, tyChar, tyInt..tyUInt64:
@@ -391,14 +358,6 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
if not matchType(c, f[i], ea[i], m):
result = false
break
elif f.kind == tyGenericInvocation:
# bind potential generic constraints into body
let body = f.base
for i in 1 ..< len(f):
bindParam(c,m,body[i-1], f[i])
result = matchType(c, body, a, m)
else: # tyGenericInst
result = matchType(c, f.last, a, m)
of tyOrdinal:
result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam
of tyStatic:
@@ -575,17 +534,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 +546,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 +563,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 +572,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
@@ -653,7 +584,7 @@ proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable
## `C[S, T]` parent type that we look for. We need this because we need to store bindings
## for 'S' and 'T' inside 'bindings' on a successful match. It is very important that
## we do not add any bindings at all on an unsuccessful match!
var m = MatchCon(bindings: bindings, potentialImplementation: arg, concpt: concpt, flags: flags, marker: initHashSet[ConceptTypePair]())
var m = MatchCon(bindings: bindings, potentialImplementation: arg, concpt: concpt, flags: flags)
if arg.isConcept:
result = conceptsMatch(c, concpt.reduceToBase, arg.reduceToBase, m) >= mkSubset
elif arg.acceptsAllTypes:

View File

@@ -175,5 +175,3 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasSetLengthSeqUninitMagic")
defineSymbol("nimHasPreviewDuplicateModuleError")
defineSymbol("nimHasImplicitRangeConversion")

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'"
@@ -483,7 +483,7 @@ proc constructCfg*(s: PSym; body: PNode; root: PSym): ControlFlowGraph =
gen(c, body)
if root.kind == skResult:
genImplicitReturn(c)
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
result = c.code # will move
else:
shallowCopy(result, c.code)

View File

@@ -19,7 +19,7 @@ import
wordrecg, syntaxes, renderer, lexer,
packages/docutils/[rst, rstidx, rstgen, dochelpers],
trees, types,
typesrenderer, lineinfos,
typesrenderer, astalgo, lineinfos,
pathutils, nimpaths, renderverbatim, packages
import packages/docutils/rstast except FileIndex, TLineInfo
@@ -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,11 +425,14 @@ 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:
return newRstLeaf(n.comment)
d.sharedState.currFileIdx = addRstFileIndex(d, n.info)
try:
result = parseRst(n.comment,
@@ -534,11 +537,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])
@@ -1174,12 +1176,8 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind, nonExports = false):
"col": %n.info.col}
)
if comm != nil:
if optDocRaw in d.conf.globalOptions:
result.json["description"] = %comm.text
else:
result.rst = comm
result.rstField = "description"
result.rst = comm
result.rstField = "description"
if r.buf.len > 0:
result.json["code"] = %r.buf
if k in routineKinds:
@@ -1322,7 +1320,7 @@ proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, id
if t.startsWith("ref "): t = substr(t, 4)
effects[i] = newIdentNode(getIdent(cache, t), n.info)
# set the type so that the following analysis doesn't screw up:
effects[i].typ = real[i].typ
effects[i].typ() = real[i].typ
result = newTreeI(nkExprColonExpr, n.info,
newIdentNode(getIdent(cache, $effectType), n.info), effects)
@@ -1420,7 +1418,7 @@ proc generateDoc*(d: PDoc, n, orig: PNode, config: ConfigRef, docFlags: DocFlags
of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept"
of nkFromStmt, nkImportExceptStmt: traceDeps(d, n[0])
of nkCallKinds:
var comm = default(ItemPre)
var comm: ItemPre = default(ItemPre)
getAllRunnableExamples(d, n, comm)
if comm.len != 0: d.modDescPre.add(comm)
else: discard

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)
@@ -47,5 +47,68 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
n[bodyPos] = body
n[resultPos] = newSymNode(res)
result.ast = n
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}
setHookDisamb(g, result, "$enumtostr", t)
incl result.flags, sfFromGeneric
incl result.flags, sfNeverRaises
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.flags, sfFromGeneric
incl result.flags, sfNeverRaises

View File

@@ -275,7 +275,7 @@ proc unpackObject(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
# the nkPar node:
if n.isNil:
result = newNode(nkTupleConstr)
result.typ = typ
result.typ() = typ
if typ.n.isNil:
internalError(conf, "cannot unpack unnamed tuple")
unpackObjectAdd(conf, x, typ.n, result)
@@ -298,7 +298,7 @@ proc unpackObject(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
proc unpackArray(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
if n.isNil:
result = newNode(nkBracket)
result.typ = typ
result.typ() = typ
newSeq(result.sons, lengthOrd(conf, typ).toInt)
else:
result = n
@@ -319,7 +319,7 @@ proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
template aw(k, v, field: untyped): untyped =
if n.isNil:
result = newNode(k)
result.typ = typ
result.typ() = typ
else:
# check we have the right field:
result = n
@@ -333,12 +333,12 @@ proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
template setNil() =
if n.isNil:
result = newNode(nkNilLit)
result.typ = typ
result.typ() = typ
else:
reset n[]
result = n
result[] = TNode(kind: nkNilLit)
result.typ = typ
result.typ() = typ
template awi(kind, v: untyped): untyped = aw(kind, v, intVal)
template awf(kind, v: untyped): untyped = aw(kind, v, floatVal)
@@ -427,7 +427,7 @@ proc fficast*(conf: ConfigRef, x: PNode, destTyp: PType): PNode =
# cast through a pointer needs a new inner object:
let y = if x.kind == nkRefTy: newNodeI(nkRefTy, x.info, 1)
else: x.copyTree
y.typ = x.typ
y.typ() = x.typ
result = unpack(conf, a, destTyp, y)
dealloc a
@@ -481,7 +481,7 @@ proc callForeignFunction*(conf: ConfigRef, fn: PNode, fntyp: PType,
if aTyp.isNil:
internalAssert conf, i+1 < fntyp.len
aTyp = fntyp[i+1]
args[i+start].typ = aTyp
args[i+start].typ() = aTyp
sig[i] = mapType(conf, aTyp)
if sig[i].isNil: globalError(conf, info, "cannot map FFI type")

View File

@@ -182,7 +182,7 @@ proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode =
d.add newSymNode(sym, info)
result.add d
result.add res
result.typ = res.typ
result.typ() = res.typ
proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
conf: ConfigRef;

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}:
@@ -1104,7 +1104,7 @@ proc settype(n: PNode): PType =
proc buildOf(it, loc: PNode; o: Operators): PNode =
var s = newNodeI(nkCurly, it.info, it.len-1)
s.typ = settype(loc)
s.typ() = settype(loc)
for i in 0..<it.len-1: s[i] = it[i]
result = newNodeI(nkCall, it.info, 3)
result[0] = newSymNode(o.opContains)
@@ -1170,7 +1170,7 @@ proc buildProperFieldCheck(access, check: PNode; o: Operators): PNode =
# set field name to discriminator field name
a[1] = check[2]
# set discriminator field type: important for `neg`
a.typ = check[2].typ
a.typ() = check[2].typ
result[2] = a
# 'access.kind != nkDotExpr' can happen for object constructors
# which we don't check yet

View File

@@ -10,7 +10,7 @@
# This include implements the high level optimization pass.
# included from sem.nim
proc hlo(c: PContext, n: PNode, loopDetector: int): PNode
proc hlo(c: PContext, n: PNode): PNode
proc evalPattern(c: PContext, n, orig: PNode): PNode =
internalAssert c.config, n.kind == nkCall and n[0].kind == nkSym
@@ -61,11 +61,10 @@ proc applyPatterns(c: PContext, n: PNode): PNode =
# activate this pattern again:
c.patterns[i] = pattern
proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
proc hlo(c: PContext, n: PNode): PNode =
inc(c.hloLoopDetector)
# simply stop and do not perform any further transformations:
if loopDetector > 300:
message(c.config, n.info, warnUser, "term rewrite macro instantiation too nested")
return n
if c.hloLoopDetector > 300: return n
case n.kind
of nkMacroDef, nkTemplateDef, procDefs:
# already processed (special cases in semstmts.nim)
@@ -81,7 +80,7 @@ proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
# no optimization applied, try subtrees:
for i in 0..<result.safeLen:
let a = result[i]
let h = hlo(c, a, loopDetector)
let h = hlo(c, a)
if h != a: result[i] = h
else:
# perform type checking, so that the replacement still fits:
@@ -91,15 +90,17 @@ proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
result = fitNode(c, n.typ, result, n.info)
# optimization has been applied so check again:
result = commonOptimizations(c.graph, c.idgen, c.module, result)
result = hlo(c, result, loopDetector + 1)
result = hlo(c, result)
result = commonOptimizations(c.graph, c.idgen, c.module, result)
proc hloBody(c: PContext, n: PNode): PNode =
# fast exit:
if c.patterns.len == 0 or optTrMacros notin c.config.options: return n
result = hlo(c, n, 0)
c.hloLoopDetector = 0
result = hlo(c, n)
proc hloStmt(c: PContext, n: PNode): PNode =
# fast exit:
if c.patterns.len == 0 or optTrMacros notin c.config.options: return n
result = hlo(c, n, 0)
c.hloLoopDetector = 0
result = hlo(c, n)

178
compiler/ic/bitabs.nim Normal file
View File

@@ -0,0 +1,178 @@
## A BiTable is a table that can be seen as an optimized pair
## of `(Table[LitId, Val], Table[Val, LitId])`.
import std/hashes
import rodfiles
when defined(nimPreviewSlimSystem):
import std/assertions
type
LitId* = distinct uint32
BiTable*[T] = object
vals: seq[T] # indexed by LitId
keys: seq[LitId] # indexed by hash(val)
proc initBiTable*[T](): BiTable[T] = BiTable[T](vals: @[], keys: @[])
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash
template maxHash(t): untyped = high(t.keys)
template isFilled(x: LitId): bool = x.uint32 > 0'u32
proc `$`*(x: LitId): string {.borrow.}
proc `<`*(x, y: LitId): bool {.borrow.}
proc `<=`*(x, y: LitId): bool {.borrow.}
proc `==`*(x, y: LitId): bool {.borrow.}
proc hash*(x: LitId): Hash {.borrow.}
proc len*[T](t: BiTable[T]): int = t.vals.len
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
const
idStart = 1
template idToIdx(x: LitId): int = x.int - idStart
proc hasLitId*[T](t: BiTable[T]; x: LitId): bool =
let idx = idToIdx(x)
result = idx >= 0 and idx < t.vals.len
proc enlarge[T](t: var BiTable[T]) =
var n: seq[LitId]
newSeq(n, len(t.keys) * 2)
swap(t.keys, n)
for i in 0..high(n):
let eh = n[i]
if isFilled(eh):
var j = hash(t.vals[idToIdx eh]) and maxHash(t)
while isFilled(t.keys[j]):
j = nextTry(j, maxHash(t))
t.keys[j] = move n[i]
proc getKeyId*[T](t: BiTable[T]; v: T): LitId =
let origH = hash(v)
var h = origH and maxHash(t)
if t.keys.len != 0:
while true:
let litId = t.keys[h]
if not isFilled(litId): break
if t.vals[idToIdx t.keys[h]] == v: return litId
h = nextTry(h, maxHash(t))
return LitId(0)
proc getOrIncl*[T](t: var BiTable[T]; v: T): LitId =
let origH = hash(v)
var h = origH and maxHash(t)
if t.keys.len != 0:
while true:
let litId = t.keys[h]
if not isFilled(litId): break
if t.vals[idToIdx t.keys[h]] == v: return litId
h = nextTry(h, maxHash(t))
# not found, we need to insert it:
if mustRehash(t.keys.len, t.vals.len):
enlarge(t)
# recompute where to insert:
h = origH and maxHash(t)
while true:
let litId = t.keys[h]
if not isFilled(litId): break
h = nextTry(h, maxHash(t))
else:
setLen(t.keys, 16)
h = origH and maxHash(t)
result = LitId(t.vals.len + idStart)
t.keys[h] = result
t.vals.add v
proc `[]`*[T](t: var BiTable[T]; litId: LitId): var T {.inline.} =
let idx = idToIdx litId
assert idx < t.vals.len
result = t.vals[idx]
proc `[]`*[T](t: BiTable[T]; litId: LitId): lent T {.inline.} =
let idx = idToIdx litId
assert idx < t.vals.len
result = t.vals[idx]
proc hash*[T](t: BiTable[T]): Hash =
## as the keys are hashes of the values, we simply use them instead
var h: Hash = 0
for i, n in pairs t.keys:
h = h !& hash((i, n))
result = !$h
proc store*[T](f: var RodFile; t: BiTable[T]) =
storeSeq(f, t.vals)
storeSeq(f, t.keys)
proc load*[T](f: var RodFile; t: var BiTable[T]) =
loadSeq(f, t.vals)
loadSeq(f, t.keys)
proc sizeOnDisc*(t: BiTable[string]): int =
result = 4
for x in t.vals:
result += x.len + 4
result += t.keys.len * sizeof(LitId)
when isMainModule:
var t: BiTable[string]
echo getOrIncl(t, "hello")
echo getOrIncl(t, "hello")
echo getOrIncl(t, "hello3")
echo getOrIncl(t, "hello4")
echo getOrIncl(t, "helloasfasdfdsa")
echo getOrIncl(t, "hello")
echo getKeyId(t, "hello")
echo getKeyId(t, "none")
for i in 0 ..< 100_000:
discard t.getOrIncl($i & "___" & $i)
for i in 0 ..< 100_000:
assert t.getOrIncl($i & "___" & $i).idToIdx == i + 4
echo "begin"
echo t.vals.len
echo t.vals[0]
echo t.vals[1004]
echo "middle"
var tf: BiTable[float]
discard tf.getOrIncl(0.4)
discard tf.getOrIncl(16.4)
discard tf.getOrIncl(32.4)
echo getKeyId(tf, 32.4)
var f2 = open("testblah.bin", fmWrite)
echo store(f2, tf)
f2.close
var f1 = open("testblah.bin", fmRead)
var t2: BiTable[float]
echo f1.load(t2)
echo t2.vals.len
echo getKeyId(t2, 32.4)
echo "end"
f1.close

180
compiler/ic/cbackend.nim Normal file
View File

@@ -0,0 +1,180 @@
#
#
# The Nim Compiler
# (c) Copyright 2021 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## New entry point into our C/C++ code generator. Ideally
## somebody would rewrite the old backend (which is 8000 lines of crufty Nim code)
## to work on packed trees directly and produce the C code as an AST which can
## then be rendered to text in a very simple manner. Unfortunately nobody wrote
## this code. So instead we wrap the existing cgen.nim and its friends so that
## we call directly into the existing code generation logic but avoiding the
## naive, outdated `passes` design. Thus you will see some
## `useAliveDataFromDce in flags` checks in the old code -- the old code is
## also doing cross-module dependency tracking and DCE that we don't need
## anymore. DCE is now done as prepass over the entire packed module graph.
import std/[packedsets, algorithm, tables]
when defined(nimPreviewSlimSystem):
import std/assertions
import ".."/[ast, options, lineinfos, modulegraphs, cgendata, cgen,
pathutils, extccomp, msgs, modulepaths]
import packed_ast, ic, dce, rodfiles
proc unpackTree(g: ModuleGraph; thisModule: int;
tree: PackedTree; n: NodePos): PNode =
var decoder = initPackedDecoder(g.config, g.cache)
result = loadNodes(decoder, g.packed, thisModule, tree, n)
proc setupBackendModule(g: ModuleGraph; m: var LoadedModule) =
if g.backend == nil:
g.backend = cgendata.newModuleList(g)
assert g.backend != nil
var bmod = cgen.newModule(BModuleList(g.backend), m.module, g.config)
bmod.idgen = idgenFromLoadedModule(m)
proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var AliveSyms) =
var bmod = BModuleList(g.backend).modules[m.module.position]
assert bmod != nil
bmod.flags.incl useAliveDataFromDce
bmod.alive = move alive[m.module.position]
for p in allNodes(m.fromDisk.topLevel):
let n = unpackTree(g, m.module.position, m.fromDisk.topLevel, p)
cgen.genTopLevelStmt(bmod, n)
finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
for disp in getDispatchers(g):
genProcAux(bmod, disp)
m.fromDisk.backendFlags = cgen.whichInitProcs(bmod)
proc replayTypeInfo(g: ModuleGraph; m: var LoadedModule; origin: FileIndex) =
for x in mitems(m.fromDisk.emittedTypeInfo):
#echo "found type ", x, " for file ", int(origin)
g.emittedTypeInfo[x] = origin
proc addFileToLink(config: ConfigRef; m: PSym) =
let filename = AbsoluteFile toFullPath(config, m.position.FileIndex)
let ext =
if config.backend == backendCpp: ".nim.cpp"
elif config.backend == backendObjc: ".nim.m"
else: ".nim.c"
let cfile = changeFileExt(completeCfilePath(config,
mangleModuleName(config, filename).AbsoluteFile), ext)
let objFile = completeCfilePath(config, toObjFile(config, cfile))
if fileExists(objFile):
var cf = Cfile(nimname: m.name.s, cname: cfile,
obj: objFile,
flags: {CfileFlag.Cached})
addFileToCompile(config, cf)
when defined(debugDce):
import os, std/packedsets
proc storeAliveSymsImpl(asymFile: AbsoluteFile; s: seq[int32]) =
var f = rodfiles.create(asymFile.string)
f.storeHeader()
f.storeSection aliveSymsSection
f.storeSeq(s)
close f
template prepare {.dirty.} =
let asymFile = toRodFile(config, AbsoluteFile toFullPath(config, position.FileIndex), ".alivesyms")
var s = newSeqOfCap[int32](alive[position].len)
for a in items(alive[position]): s.add int32(a)
sort(s)
proc storeAliveSyms(config: ConfigRef; position: int; alive: AliveSyms) =
prepare()
storeAliveSymsImpl(asymFile, s)
proc aliveSymsChanged(config: ConfigRef; position: int; alive: AliveSyms): bool =
prepare()
var f2 = rodfiles.open(asymFile.string)
f2.loadHeader()
f2.loadSection aliveSymsSection
var oldData: seq[int32] = @[]
f2.loadSeq(oldData)
f2.close
if f2.err == ok and oldData == s:
result = false
else:
when defined(debugDce):
let oldAsSet = toPackedSet[int32](oldData)
let newAsSet = toPackedSet[int32](s)
echo "set of live symbols changed ", asymFile.changeFileExt("rod"), " ", position, " ", f2.err
echo "in old but not in new ", oldAsSet.difference(newAsSet), " number of entries in old ", oldAsSet.len
echo "in new but not in old ", newAsSet.difference(oldAsSet), " number of entries in new ", newAsSet.len
#if execShellCmd(getAppFilename() & " rod " & quoteShell(asymFile.changeFileExt("rod"))) != 0:
# echo "command failed"
result = true
storeAliveSymsImpl(asymFile, s)
proc genPackedModule(g: ModuleGraph, i: int; alive: var AliveSyms) =
# case statement here to enforce exhaustive checks.
case g.packed[i].status
of undefined:
discard "nothing to do"
of loading, stored:
assert false
of storing, outdated:
storeAliveSyms(g.config, g.packed[i].module.position, alive)
generateCodeForModule(g, g.packed[i], alive)
closeRodFile(g, g.packed[i].module)
of loaded:
if g.packed[i].loadedButAliveSetChanged:
generateCodeForModule(g, g.packed[i], alive)
else:
addFileToLink(g.config, g.packed[i].module)
replayTypeInfo(g, g.packed[i], FileIndex(i))
if g.backend == nil:
g.backend = cgendata.newModuleList(g)
registerInitProcs(BModuleList(g.backend), g.packed[i].module, g.packed[i].fromDisk.backendFlags)
proc generateCode*(g: ModuleGraph) =
## The single entry point, generate C(++) code for the entire
## Nim program aka `ModuleGraph`.
resetForBackend(g)
var alive = computeAliveSyms(g.packed, g.config)
when false:
for i in 0..<len(g.packed):
echo i, " is of status ", g.packed[i].status, " ", toFullPath(g.config, FileIndex(i))
# First pass: Setup all the backend modules for all the modules that have
# changed:
for i in 0..<len(g.packed):
# case statement here to enforce exhaustive checks.
case g.packed[i].status
of undefined:
discard "nothing to do"
of loading, stored:
assert false
of storing, outdated:
setupBackendModule(g, g.packed[i])
of loaded:
# Even though this module didn't change, DCE might trigger a change.
# Consider this case: Module A uses symbol S from B and B does not use
# S itself. A is then edited not to use S either. Thus we have to
# recompile B in order to remove S from the final result.
if aliveSymsChanged(g.config, g.packed[i].module.position, alive):
g.packed[i].loadedButAliveSetChanged = true
setupBackendModule(g, g.packed[i])
# Second pass: Code generation.
let mainModuleIdx = g.config.projectMainIdx2.int
# We need to generate the main module last, because only then
# all init procs have been registered:
for i in 0..<len(g.packed):
if i != mainModuleIdx:
genPackedModule(g, i, alive)
if mainModuleIdx >= 0:
genPackedModule(g, mainModuleIdx, alive)

169
compiler/ic/dce.nim Normal file
View File

@@ -0,0 +1,169 @@
#
#
# The Nim Compiler
# (c) Copyright 2021 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Dead code elimination (=DCE) for IC.
import std/[intsets, tables]
when defined(nimPreviewSlimSystem):
import std/assertions
import ".." / [ast, options, lineinfos, types]
import packed_ast, ic, bitabs
type
AliveSyms* = seq[IntSet]
AliveContext* = object ## Purpose is to fill the 'alive' field.
stack: seq[(int, TOptions, NodePos)] ## A stack for marking symbols as alive.
decoder: PackedDecoder ## We need a PackedDecoder for module ID address translations.
thisModule: int ## The module we're currently analysing for DCE.
alive: AliveSyms ## The final result of our computation.
options: TOptions
compilerProcs: Table[string, (int, int32)]
proc isExportedToC(c: var AliveContext; g: PackedModuleGraph; symId: int32): bool =
## "Exported to C" procs are special (these are marked with '.exportc') because these
## must not be optimized away!
let symPtr = unsafeAddr g[c.thisModule].fromDisk.syms[symId]
let flags = symPtr.flags
# due to a bug/limitation in the lambda lifting, unused inner procs
# are not transformed correctly; issue (#411). However, the whole purpose here
# is to eliminate unused procs. So there is no special logic required for this case.
if sfCompileTime notin flags:
if ({sfExportc, sfCompilerProc} * flags != {}) or
(symPtr.kind == skMethod):
result = true
else:
result = false
# XXX: This used to be a condition to:
# (sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
if sfCompilerProc in flags:
c.compilerProcs[g[c.thisModule].fromDisk.strings[symPtr.name]] = (c.thisModule, symId)
else:
result = false
template isNotGeneric(n: NodePos): bool = ithSon(tree, n, genericParamsPos).kind == nkEmpty
proc followLater(c: var AliveContext; g: PackedModuleGraph; module: int; item: int32) =
## Marks a symbol 'item' as used and later in 'followNow' the symbol's body will
## be analysed.
if not c.alive[module].containsOrIncl(item):
var body = g[module].fromDisk.syms[item].ast
if body != emptyNodeId:
let opt = g[module].fromDisk.syms[item].options
if g[module].fromDisk.syms[item].kind in routineKinds:
body = NodeId ithSon(g[module].fromDisk.bodies, NodePos body, bodyPos)
c.stack.add((module, opt, NodePos(body)))
when false:
let nid = g[module].fromDisk.syms[item].name
if nid != LitId(0):
let name = g[module].fromDisk.strings[nid]
if name in ["nimFrame", "callDepthLimitReached"]:
echo "I was called! ", name, " body exists: ", body != emptyNodeId, " ", module, " ", item
proc requestCompilerProc(c: var AliveContext; g: PackedModuleGraph; name: string) =
let (module, item) = c.compilerProcs[name]
followLater(c, g, module, item)
proc loadTypeKind(t: PackedItemId; c: AliveContext; g: PackedModuleGraph; toSkip: set[TTypeKind]): TTypeKind =
template kind(t: ItemId): TTypeKind = g[t.module].fromDisk.types[t.item].kind
var t2 = translateId(t, g, c.thisModule, c.decoder.config)
result = t2.kind
while result in toSkip:
t2 = translateId(g[t2.module].fromDisk.types[t2.item].types[^1], g, t2.module, c.decoder.config)
result = t2.kind
proc rangeCheckAnalysis(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
## Replicates the logic of `ccgexprs.genRangeChck`.
## XXX Refactor so that the duplicated logic is avoided. However, for now it's not clear
## the approach has enough merit.
var dest = loadTypeKind(n.typ, c, g, abstractVar)
if optRangeCheck notin c.options or dest in {tyUInt..tyUInt64}:
discard "no need to generate a check because it was disabled"
else:
let n0t = loadTypeKind(n.firstSon.typ, c, g, {})
if n0t in {tyUInt, tyUInt64}:
c.requestCompilerProc(g, "raiseRangeErrorNoArgs")
else:
let raiser =
case loadTypeKind(n.typ, c, g, abstractVarRange)
of tyUInt..tyUInt64, tyChar: "raiseRangeErrorU"
of tyFloat..tyFloat128: "raiseRangeErrorF"
else: "raiseRangeErrorI"
c.requestCompilerProc(g, raiser)
proc aliveCode(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
## Marks the symbols we encounter when we traverse the AST at `tree[n]` as alive, unless
## it is purely in a declarative context (type section etc.).
case n.kind
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
discard "ignore non-sym atoms"
of nkSym:
# This symbol is alive and everything its body references.
followLater(c, g, c.thisModule, tree[n].soperand)
of nkModuleRef:
let (n1, n2) = sons2(tree, n)
assert n1.kind == nkNone
assert n2.kind == nkNone
let m = n1.litId
let item = tree[n2].soperand
let otherModule = toFileIndexCached(c.decoder, g, c.thisModule, m).int
followLater(c, g, otherModule, item)
of nkMacroDef, nkTemplateDef, nkTypeSection, nkTypeOfExpr,
nkCommentStmt, nkIncludeStmt,
nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
nkFromStmt, nkStaticStmt:
discard
of nkVarSection, nkLetSection, nkConstSection:
# XXX ignore the defining local variable name?
for son in sonsReadonly(tree, n):
aliveCode(c, g, tree, son)
of nkChckRangeF, nkChckRange64, nkChckRange:
rangeCheckAnalysis(c, g, tree, n)
of nkProcDef, nkConverterDef, nkMethodDef, nkFuncDef, nkIteratorDef:
if n.firstSon.kind == nkSym and isNotGeneric(n):
let item = tree[n.firstSon].soperand
if isExportedToC(c, g, item):
# This symbol is alive and everything its body references.
followLater(c, g, c.thisModule, item)
else:
for son in sonsReadonly(tree, n):
aliveCode(c, g, tree, son)
proc followNow(c: var AliveContext; g: PackedModuleGraph) =
## Mark all entries in the stack. Marking can add more entries
## to the stack but eventually we have looked at every alive symbol.
while c.stack.len > 0:
let (modId, opt, ast) = c.stack.pop()
c.thisModule = modId
c.options = opt
aliveCode(c, g, g[modId].fromDisk.bodies, ast)
proc computeAliveSyms*(g: PackedModuleGraph; conf: ConfigRef): AliveSyms =
## Entry point for our DCE algorithm.
var c = AliveContext(stack: @[], decoder: PackedDecoder(config: conf),
thisModule: -1, alive: newSeq[IntSet](g.len),
options: conf.options)
for i in countdown(len(g)-1, 0):
if g[i].status != undefined:
c.thisModule = i
for p in allNodes(g[i].fromDisk.topLevel):
aliveCode(c, g, g[i].fromDisk.topLevel, p)
followNow(c, g)
result = move(c.alive)
proc isAlive*(a: AliveSyms; module: int, item: int32): bool =
## Backends use this to query if a symbol is `alive` which means
## we need to produce (C/C++/etc) code for it.
result = a[module].contains(item)

56
compiler/ic/design.rst Normal file
View File

@@ -0,0 +1,56 @@
====================================
Incremental Recompilations
====================================
We split the Nim compiler into a frontend and a backend.
The frontend produces a set of `.rod` files. Every `.nim` module
produces its own `.rod` file.
- The IR must be a faithful representation of the AST in memory.
- The backend can do its own caching but doesn't have to. In the
current implementation the backend also caches its results.
Advantage of the "set of files" vs the previous global database:
- By construction, we either read from the `.rod` file or from the
`.nim` file, there can be no inconsistency. There can also be no
partial updates.
- No dependency to external packages (SQLite). SQLite simply is too
slow and the old way of serialization was too slow too. We use a
format designed for Nim and expect to base further tools on this
file format.
References to external modules must be (moduleId, symId) pairs.
The symbol IDs are module specific. This way no global ID increment
mechanism needs to be implemented that we could get wrong. ModuleIds
are rod-file specific too.
Global state
------------
There is no global state.
Rod File Format
---------------
It's a simple binary file format. `rodfiles.nim` contains some details.
Backend
-------
Nim programmers have to come to enjoy whole-program dead code elimination,
by default. Since this is a "whole program" optimization, it does break
modularity. However, thanks to the packed AST representation we can perform
this global analysis without having to unpack anything. This is basically
a mark&sweep GC algorithm:
- Start with the top level statements. Every symbol that is referenced
from a top level statement is not "dead" and needs to be compiled by
the backend.
- Every symbol referenced from a referenced symbol also has to be
compiled.
Caching logic: Only if the set of alive symbols is different from the
last run, the module has to be regenerated.

File diff suppressed because it is too large Load Diff

1343
compiler/ic/ic.nim Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
#
#
# The Nim Compiler
# (c) Copyright 2024 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# For the line information we use 32 bits. They are used as follows:
# Bit 0 (AsideBit): If we have inline line information or not. If not, the
# remaining 31 bits are used as an index into a seq[(LitId, int, int)].
#
# We use 10 bits for the "file ID", this means a program can consist of as much
# as 1024 different files. (If it uses more files than that, the overflow bit
# would be set.)
# This means we have 21 bits left to encode the (line, col) pair. We use 7 bits for the column
# so 128 is the limit and 14 bits for the line number.
# The packed representation supports files with up to 16384 lines.
# Keep in mind that whenever any limit is reached the AsideBit is set and the real line
# information is kept in a side channel.
import std / assertions
const
AsideBit = 1
FileBits = 10
LineBits = 14
ColBits = 7
FileMax = (1 shl FileBits) - 1
LineMax = (1 shl LineBits) - 1
ColMax = (1 shl ColBits) - 1
static:
assert AsideBit + FileBits + LineBits + ColBits == 32
import .. / ic / [bitabs, rodfiles] # for LitId
type
PackedLineInfo* = distinct uint32
LineInfoManager* = object
aside: seq[(LitId, int32, int32)]
const
NoLineInfo* = PackedLineInfo(0'u32)
proc pack*(m: var LineInfoManager; file: LitId; line, col: int32): PackedLineInfo =
if file.uint32 <= FileMax.uint32 and line <= LineMax and col <= ColMax:
let col = if col < 0'i32: 0'u32 else: col.uint32
let line = if line < 0'i32: 0'u32 else: line.uint32
# use inline representation:
result = PackedLineInfo((file.uint32 shl 1'u32) or (line shl uint32(AsideBit + FileBits)) or
(col shl uint32(AsideBit + FileBits + LineBits)))
else:
result = PackedLineInfo((m.aside.len shl 1) or AsideBit)
m.aside.add (file, line, col)
proc unpack*(m: LineInfoManager; i: PackedLineInfo): (LitId, int32, int32) =
let i = i.uint32
if (i and 1'u32) == 0'u32:
# inline representation:
result = (LitId((i shr 1'u32) and FileMax.uint32),
int32((i shr uint32(AsideBit + FileBits)) and LineMax.uint32),
int32((i shr uint32(AsideBit + FileBits + LineBits)) and ColMax.uint32))
else:
result = m.aside[int(i shr 1'u32)]
proc getFileId*(m: LineInfoManager; i: PackedLineInfo): LitId =
result = unpack(m, i)[0]
proc store*(r: var RodFile; m: LineInfoManager) = storeSeq(r, m.aside)
proc load*(r: var RodFile; m: var LineInfoManager) = loadSeq(r, m.aside)
when isMainModule:
var m = LineInfoManager(aside: @[])
for i in 0'i32..<16388'i32:
for col in 0'i32..<100'i32:
let packed = pack(m, LitId(1023), i, col)
let u = unpack(m, packed)
assert u[0] == LitId(1023)
assert u[1] == i
assert u[2] == col
echo m.aside.len

155
compiler/ic/integrity.nim Normal file
View File

@@ -0,0 +1,155 @@
#
#
# The Nim Compiler
# (c) Copyright 2021 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Integrity checking for a set of .rod files.
## The set must cover a complete Nim project.
import std/[sets, tables]
when defined(nimPreviewSlimSystem):
import std/assertions
import ".." / [ast, modulegraphs]
import packed_ast, bitabs, ic
type
CheckedContext = object
g: ModuleGraph
thisModule: int32
checkedSyms: HashSet[ItemId]
checkedTypes: HashSet[ItemId]
proc checkType(c: var CheckedContext; typeId: PackedItemId)
proc checkForeignSym(c: var CheckedContext; symId: PackedItemId)
proc checkNode(c: var CheckedContext; tree: PackedTree; n: NodePos)
proc checkTypeObj(c: var CheckedContext; typ: PackedType) =
for child in typ.types:
checkType(c, child)
if typ.n != emptyNodeId:
checkNode(c, c.g.packed[c.thisModule].fromDisk.bodies, NodePos typ.n)
if typ.sym != nilItemId:
checkForeignSym(c, typ.sym)
if typ.owner != nilItemId:
checkForeignSym(c, typ.owner)
checkType(c, typ.typeInst)
proc checkType(c: var CheckedContext; typeId: PackedItemId) =
if typeId == nilItemId: return
let itemId = translateId(typeId, c.g.packed, c.thisModule, c.g.config)
if not c.checkedTypes.containsOrIncl(itemId):
let oldThisModule = c.thisModule
c.thisModule = itemId.module
checkTypeObj c, c.g.packed[itemId.module].fromDisk.types[itemId.item]
c.thisModule = oldThisModule
proc checkSym(c: var CheckedContext; s: PackedSym) =
if s.name != LitId(0):
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId s.name
checkType c, s.typ
if s.ast != emptyNodeId:
checkNode(c, c.g.packed[c.thisModule].fromDisk.bodies, NodePos s.ast)
if s.owner != nilItemId:
checkForeignSym(c, s.owner)
proc checkLocalSym(c: var CheckedContext; item: int32) =
let itemId = ItemId(module: c.thisModule, item: item)
if not c.checkedSyms.containsOrIncl(itemId):
checkSym c, c.g.packed[c.thisModule].fromDisk.syms[item]
proc checkForeignSym(c: var CheckedContext; symId: PackedItemId) =
let itemId = translateId(symId, c.g.packed, c.thisModule, c.g.config)
if not c.checkedSyms.containsOrIncl(itemId):
let oldThisModule = c.thisModule
c.thisModule = itemId.module
checkSym c, c.g.packed[itemId.module].fromDisk.syms[itemId.item]
c.thisModule = oldThisModule
proc checkNode(c: var CheckedContext; tree: PackedTree; n: NodePos) =
let t = findType(tree, n)
if t != nilItemId:
checkType(c, t)
case n.kind
of nkEmpty, nkNilLit, nkType, nkNilRodNode:
discard
of nkIdent:
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId n.litId
of nkSym:
checkLocalSym(c, tree[n].soperand)
of directIntLit:
discard
of externIntLit, nkFloatLit..nkFloat128Lit:
assert c.g.packed[c.thisModule].fromDisk.numbers.hasLitId n.litId
of nkStrLit..nkTripleStrLit:
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId n.litId
of nkModuleRef:
let (n1, n2) = sons2(tree, n)
assert n1.kind == nkNone
assert n2.kind == nkNone
checkForeignSym(c, PackedItemId(module: n1.litId, item: tree[n2].soperand))
else:
for n0 in sonsReadonly(tree, n):
checkNode(c, tree, n0)
proc checkTree(c: var CheckedContext; t: PackedTree) =
for p in allNodes(t): checkNode(c, t, p)
proc checkLocalSymIds(c: var CheckedContext; m: PackedModule; symIds: seq[int32]) =
for symId in symIds:
assert symId >= 0 and symId < m.syms.len, $symId & " " & $m.syms.len
proc checkModule(c: var CheckedContext; m: PackedModule) =
# We check that:
# - Every symbol references existing types and symbols.
# - Every tree node references existing types and symbols.
for _, v in pairs(m.syms):
checkLocalSym c, v.id
checkTree c, m.toReplay
checkTree c, m.topLevel
for e in m.exports:
#assert e[1] >= 0 and e[1] < m.syms.len
assert e[0] == m.syms[e[1]].name
for e in m.compilerProcs:
#assert e[1] >= 0 and e[1] < m.syms.len
assert e[0] == m.syms[e[1]].name
checkLocalSymIds c, m, m.converters
checkLocalSymIds c, m, m.methods
checkLocalSymIds c, m, m.trmacros
checkLocalSymIds c, m, m.pureEnums
#[
To do: Check all these fields:
reexports*: seq[(LitId, PackedItemId)]
macroUsages*: seq[(PackedItemId, PackedLineInfo)]
typeInstCache*: seq[(PackedItemId, PackedItemId)]
procInstCache*: seq[PackedInstantiation]
attachedOps*: seq[(TTypeAttachedOp, PackedItemId, PackedItemId)]
methodsPerGenericType*: seq[(PackedItemId, int, PackedItemId)]
enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
methodsPerType*: seq[(PackedItemId, PackedItemId)]
dispatchers*: seq[PackedItemId]
]#
proc checkIntegrity*(g: ModuleGraph) =
var c = CheckedContext(g: g)
for i in 0..<len(g.packed):
# case statement here to enforce exhaustive checks.
case g.packed[i].status
of undefined:
discard "nothing to do"
of loading:
assert false, "cannot check integrity: Module still loading"
of stored, storing, outdated, loaded:
c.thisModule = int32 i
checkModule(c, g.packed[i].fromDisk)

183
compiler/ic/navigator.nim Normal file
View File

@@ -0,0 +1,183 @@
#
#
# The Nim Compiler
# (c) Copyright 2021 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Supports the "nim check --ic:on --defusages:FILE,LINE,COL"
## IDE-like features. It uses the set of .rod files to accomplish
## its task. The set must cover a complete Nim project.
import std/[sets, tables]
from std/os import nil
from std/private/miscdollars import toLocation
when defined(nimPreviewSlimSystem):
import std/assertions
import ".." / [ast, modulegraphs, msgs, options]
import iclineinfos
import packed_ast, bitabs, ic
type
UnpackedLineInfo = object
file: LitId
line, col: int
NavContext = object
g: ModuleGraph
thisModule: int32
trackPos: UnpackedLineInfo
alreadyEmitted: HashSet[string]
outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
proc isTracked(man: LineInfoManager; current: PackedLineInfo, trackPos: UnpackedLineInfo, tokenLen: int): bool =
let (currentFile, currentLine, currentCol) = man.unpack(current)
if currentFile == trackPos.file and currentLine == trackPos.line:
let col = trackPos.col
if col >= currentCol and col < currentCol+tokenLen:
result = true
else:
result = false
else:
result = false
proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
result = s.name != LitId(0) and
isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[c.thisModule].fromDisk.strings[s.name].len)
proc searchForeignSym(c: var NavContext; s: ItemId; info: PackedLineInfo): bool =
let name = c.g.packed[s.module].fromDisk.syms[s.item].name
result = name != LitId(0) and
isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[s.module].fromDisk.strings[name].len)
const
EmptyItemId = ItemId(module: -1'i32, item: -1'i32)
proc search(c: var NavContext; tree: PackedTree): ItemId =
# We use the linear representation here directly:
for i in 0..<len(tree):
let i = NodePos(i)
case tree[i].kind
of nkSym:
let item = tree[i].soperand
if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.syms[item], tree[i].info):
return ItemId(module: c.thisModule, item: item)
of nkModuleRef:
let (currentFile, currentLine, currentCol) = c.g.packed[c.thisModule].fromDisk.man.unpack(tree[i].info)
if currentLine == c.trackPos.line and currentFile == c.trackPos.file:
let (n1, n2) = sons2(tree, i)
assert n1.kind == nkInt32Lit
assert n2.kind == nkInt32Lit
let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
if searchForeignSym(c, itemId, tree[i].info):
return itemId
else: discard
return EmptyItemId
proc isDecl(tree: PackedTree; n: NodePos): bool =
# XXX This is not correct yet.
const declarativeNodes = procDefs + {nkMacroDef, nkTemplateDef,
nkLetSection, nkVarSection, nkUsingStmt, nkConstSection, nkTypeSection,
nkIdentDefs, nkEnumTy, nkVarTuple}
result = n.int >= 0 and tree[n].kind in declarativeNodes
proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
let (fileId, line, col) = unpack(c.g.packed[c.thisModule].fromDisk.man, info)
var m = ""
var file = c.g.packed[c.thisModule].fromDisk.strings[fileId]
if c.outputSep == ' ':
file = os.extractFilename file
toLocation(m, file, line, col + ColOffset)
if not c.alreadyEmitted.containsOrIncl(m):
msgWriteln c.g.config, (if isDecl: "def" else: "usage") & c.outputSep & m
proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
for i in 0..<len(tree):
let i = NodePos(i)
case tree[i].kind
of nkSym:
let item = tree[i].soperand
if sym.item == item and sym.module == c.thisModule:
usage(c, tree[i].info, isDecl(tree, parent(i)))
of nkModuleRef:
let (n1, n2) = sons2(tree, i)
assert n1.kind == nkNone
assert n2.kind == nkNone
let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
if itemId.item == sym.item and sym.module == itemId.module:
usage(c, tree[i].info, isDecl(tree, parent(i)))
else: discard
proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
for i in 0..<len(g.packed):
for k in 1..high(g.packed[i].fromDisk.includes):
# we start from 1 because the first "include" file is
# the module's filename.
if os.cmpPaths(g.packed[i].fromDisk.strings[g.packed[i].fromDisk.includes[k][0]], fullPath) == 0:
return i
return -1
proc nav(g: ModuleGraph) =
# translate the track position to a packed position:
let unpacked = g.config.m.trackPos
var mid = unpacked.fileIndex.int
let fullPath = toFullPath(g.config, unpacked.fileIndex)
if g.packed[mid].status == undefined:
# check if 'mid' is an include file of some other module:
mid = searchForIncludeFile(g, fullPath)
if mid < 0:
localError(g.config, unpacked, "unknown file name: " & fullPath)
return
let fileId = g.packed[mid].fromDisk.strings.getKeyId(fullPath)
if fileId == LitId(0):
internalError(g.config, unpacked, "cannot find a valid file ID")
return
var c = NavContext(
g: g,
thisModule: int32 mid,
trackPos: UnpackedLineInfo(line: unpacked.line.int, col: unpacked.col.int, file: fileId),
outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
)
var symId = search(c, g.packed[mid].fromDisk.topLevel)
if symId == EmptyItemId:
symId = search(c, g.packed[mid].fromDisk.bodies)
if symId == EmptyItemId:
localError(g.config, unpacked, "no symbol at this position")
return
for i in 0..<len(g.packed):
# case statement here to enforce exhaustive checks.
case g.packed[i].status
of undefined:
discard "nothing to do"
of loading:
assert false, "cannot check integrity: Module still loading"
of stored, storing, outdated, loaded:
c.thisModule = int32 i
list(c, g.packed[i].fromDisk.topLevel, symId)
list(c, g.packed[i].fromDisk.bodies, symId)
proc navDefinition*(g: ModuleGraph) = nav(g)
proc navUsages*(g: ModuleGraph) = nav(g)
proc navDefusages*(g: ModuleGraph) = nav(g)
proc writeRodFiles*(g: ModuleGraph) =
for i in 0..<len(g.packed):
case g.packed[i].status
of undefined, loading, stored, loaded:
discard "nothing to do"
of storing, outdated:
closeRodFile(g, g.packed[i].module)

367
compiler/ic/packed_ast.nim Normal file
View File

@@ -0,0 +1,367 @@
#
#
# The Nim Compiler
# (c) Copyright 2020 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Packed AST representation, mostly based on a seq of nodes.
## For IC support. Far future: Rewrite the compiler passes to
## use this representation directly in all the transformations,
## it is superior.
import std/[hashes, tables, strtabs]
import bitabs, rodfiles
import ".." / [ast, options]
import iclineinfos
when defined(nimPreviewSlimSystem):
import std/assertions
type
SymId* = distinct int32
ModuleId* = distinct int32
NodePos* = distinct int
NodeId* = distinct int32
PackedItemId* = object
module*: LitId # 0 if it's this module
item*: int32 # same as the in-memory representation
const
nilItemId* = PackedItemId(module: LitId(0), item: 0.int32)
const
emptyNodeId* = NodeId(-1)
type
PackedLib* = object
kind*: TLibKind
generated*: bool
isOverridden*: bool
name*: LitId
path*: NodeId
PackedSym* = object
id*: int32
kind*: TSymKind
name*: LitId
typ*: PackedItemId
flags*: TSymFlags
magic*: TMagic
info*: PackedLineInfo
ast*: NodeId
owner*: PackedItemId
guard*: PackedItemId
bitsize*: int
alignment*: int # for alignment
options*: TOptions
position*: int
offset*: int32
disamb*: int32
externalName*: LitId # instead of TLoc
locFlags*: TLocFlags
annex*: PackedLib
when hasFFI:
cname*: LitId
constraint*: NodeId
instantiatedFrom*: PackedItemId
PackedType* = object
id*: int32
kind*: TTypeKind
callConv*: TCallingConvention
#nodekind*: TNodeKind
flags*: TTypeFlags
types*: seq[PackedItemId]
n*: NodeId
#nodeflags*: TNodeFlags
sym*: PackedItemId
owner*: PackedItemId
size*: BiggestInt
align*: int16
paddingAtEnd*: int16
# not serialized: loc*: TLoc because it is backend-specific
typeInst*: PackedItemId
nonUniqueId*: int32
PackedNode* = object # 8 bytes
x: uint32
info*: PackedLineInfo
PackedTree* = object ## usually represents a full Nim module
nodes: seq[PackedNode]
withFlags: seq[(int32, TNodeFlags)]
withTypes: seq[(int32, PackedItemId)]
PackedInstantiation* = object
key*, sym*: PackedItemId
concreteTypes*: seq[PackedItemId]
const
NodeKindBits = 8'u32
NodeKindMask = (1'u32 shl NodeKindBits) - 1'u32
template kind*(n: PackedNode): TNodeKind = TNodeKind(n.x and NodeKindMask)
template uoperand*(n: PackedNode): uint32 = (n.x shr NodeKindBits)
template soperand*(n: PackedNode): int32 = int32(uoperand(n))
template toX(k: TNodeKind; operand: uint32): uint32 =
uint32(k) or (operand shl NodeKindBits)
template toX(k: TNodeKind; operand: LitId): uint32 =
uint32(k) or (operand.uint32 shl NodeKindBits)
template typeId*(n: PackedNode): PackedItemId = n.typ
proc `==`*(a, b: SymId): bool {.borrow.}
proc hash*(a: SymId): Hash {.borrow.}
proc `==`*(a, b: NodePos): bool {.borrow.}
#proc `==`*(a, b: PackedItemId): bool {.borrow.}
proc `==`*(a, b: NodeId): bool {.borrow.}
proc newTreeFrom*(old: PackedTree): PackedTree =
result = PackedTree(nodes: @[])
when false: result.sh = old.sh
proc addIdent*(tree: var PackedTree; s: LitId; info: PackedLineInfo) =
tree.nodes.add PackedNode(x: toX(nkIdent, uint32(s)), info: info)
proc addSym*(tree: var PackedTree; s: int32; info: PackedLineInfo) =
tree.nodes.add PackedNode(x: toX(nkSym, cast[uint32](s)), info: info)
proc addSymDef*(tree: var PackedTree; s: SymId; info: PackedLineInfo) =
tree.nodes.add PackedNode(x: toX(nkSym, cast[uint32](s)), info: info)
proc isAtom*(tree: PackedTree; pos: int): bool {.inline.} = tree.nodes[pos].kind <= nkNilLit
type
PatchPos = distinct int
proc addNode*(t: var PackedTree; kind: TNodeKind; operand: int32;
typeId: PackedItemId = nilItemId; info: PackedLineInfo;
flags: TNodeFlags = {}) =
t.nodes.add PackedNode(x: toX(kind, cast[uint32](operand)), info: info)
if flags != {}:
t.withFlags.add (t.nodes.len.int32 - 1, flags)
if typeId != nilItemId:
t.withTypes.add (t.nodes.len.int32 - 1, typeId)
proc prepare*(tree: var PackedTree; kind: TNodeKind; flags: TNodeFlags; typeId: PackedItemId; info: PackedLineInfo): PatchPos =
result = PatchPos tree.nodes.len
tree.addNode(kind = kind, flags = flags, operand = 0, info = info, typeId = typeId)
proc prepare*(dest: var PackedTree; source: PackedTree; sourcePos: NodePos): PatchPos =
result = PatchPos dest.nodes.len
dest.nodes.add source.nodes[sourcePos.int]
proc patch*(tree: var PackedTree; pos: PatchPos) =
let pos = pos.int
let k = tree.nodes[pos].kind
assert k > nkNilLit
let distance = int32(tree.nodes.len - pos)
assert distance > 0
tree.nodes[pos].x = toX(k, cast[uint32](distance))
proc len*(tree: PackedTree): int {.inline.} = tree.nodes.len
proc `[]`*(tree: PackedTree; i: NodePos): lent PackedNode {.inline.} =
tree.nodes[i.int]
template rawSpan(n: PackedNode): int = int(uoperand(n))
proc nextChild(tree: PackedTree; pos: var int) {.inline.} =
if tree.nodes[pos].kind > nkNilLit:
assert tree.nodes[pos].uoperand > 0
inc pos, tree.nodes[pos].rawSpan
else:
inc pos
iterator sonsReadonly*(tree: PackedTree; n: NodePos): NodePos =
var pos = n.int
assert tree.nodes[pos].kind > nkNilLit
let last = pos + tree.nodes[pos].rawSpan
inc pos
while pos < last:
yield NodePos pos
nextChild tree, pos
iterator sons*(dest: var PackedTree; tree: PackedTree; n: NodePos): NodePos =
let patchPos = prepare(dest, tree, n)
for x in sonsReadonly(tree, n): yield x
patch dest, patchPos
iterator isons*(dest: var PackedTree; tree: PackedTree;
n: NodePos): (int, NodePos) =
var i = 0
for ch0 in sons(dest, tree, n):
yield (i, ch0)
inc i
iterator sonsFrom1*(tree: PackedTree; n: NodePos): NodePos =
var pos = n.int
assert tree.nodes[pos].kind > nkNilLit
let last = pos + tree.nodes[pos].rawSpan
inc pos
if pos < last:
nextChild tree, pos
while pos < last:
yield NodePos pos
nextChild tree, pos
iterator sonsWithoutLast2*(tree: PackedTree; n: NodePos): NodePos =
var count = 0
for child in sonsReadonly(tree, n):
inc count
var pos = n.int
assert tree.nodes[pos].kind > nkNilLit
let last = pos + tree.nodes[pos].rawSpan
inc pos
while pos < last and count > 2:
yield NodePos pos
dec count
nextChild tree, pos
proc parentImpl(tree: PackedTree; n: NodePos): NodePos =
# finding the parent of a node is rather easy:
var pos = n.int - 1
while pos >= 0 and (isAtom(tree, pos) or (pos + tree.nodes[pos].rawSpan - 1 < n.int)):
dec pos
#assert pos >= 0, "node has no parent"
result = NodePos(pos)
template parent*(n: NodePos): NodePos = parentImpl(tree, n)
proc hasXsons*(tree: PackedTree; n: NodePos; x: int): bool =
var count = 0
if tree.nodes[n.int].kind > nkNilLit:
for child in sonsReadonly(tree, n): inc count
result = count == x
proc hasAtLeastXsons*(tree: PackedTree; n: NodePos; x: int): bool =
if tree.nodes[n.int].kind > nkNilLit:
var count = 0
for child in sonsReadonly(tree, n):
inc count
if count >= x: return true
return false
proc firstSon*(tree: PackedTree; n: NodePos): NodePos {.inline.} =
NodePos(n.int+1)
proc kind*(tree: PackedTree; n: NodePos): TNodeKind {.inline.} =
tree.nodes[n.int].kind
proc litId*(tree: PackedTree; n: NodePos): LitId {.inline.} =
LitId tree.nodes[n.int].uoperand
proc info*(tree: PackedTree; n: NodePos): PackedLineInfo {.inline.} =
tree.nodes[n.int].info
proc findType*(tree: PackedTree; n: NodePos): PackedItemId =
for x in tree.withTypes:
if x[0] == int32(n): return x[1]
if x[0] > int32(n): return nilItemId
return nilItemId
proc findFlags*(tree: PackedTree; n: NodePos): TNodeFlags =
for x in tree.withFlags:
if x[0] == int32(n): return x[1]
if x[0] > int32(n): return {}
return {}
template typ*(n: NodePos): PackedItemId =
tree.findType(n)
template flags*(n: NodePos): TNodeFlags =
tree.findFlags(n)
template uoperand*(n: NodePos): uint32 =
tree.nodes[n.int].uoperand
proc span*(tree: PackedTree; pos: int): int {.inline.} =
if isAtom(tree, pos): 1 else: tree.nodes[pos].rawSpan
proc sons2*(tree: PackedTree; n: NodePos): (NodePos, NodePos) =
assert(not isAtom(tree, n.int))
let a = n.int+1
let b = a + span(tree, a)
result = (NodePos a, NodePos b)
proc sons3*(tree: PackedTree; n: NodePos): (NodePos, NodePos, NodePos) =
assert(not isAtom(tree, n.int))
let a = n.int+1
let b = a + span(tree, a)
let c = b + span(tree, b)
result = (NodePos a, NodePos b, NodePos c)
proc ithSon*(tree: PackedTree; n: NodePos; i: int): NodePos =
result = default(NodePos)
if tree.nodes[n.int].kind > nkNilLit:
var count = 0
for child in sonsReadonly(tree, n):
if count == i: return child
inc count
assert false, "node has no i-th child"
when false:
proc `@`*(tree: PackedTree; lit: LitId): lent string {.inline.} =
tree.sh.strings[lit]
template kind*(n: NodePos): TNodeKind = tree.nodes[n.int].kind
template info*(n: NodePos): PackedLineInfo = tree.nodes[n.int].info
template litId*(n: NodePos): LitId = LitId tree.nodes[n.int].uoperand
template symId*(n: NodePos): SymId = SymId tree.nodes[n.int].soperand
proc firstSon*(n: NodePos): NodePos {.inline.} = NodePos(n.int+1)
const
externIntLit* = {nkCharLit,
nkIntLit,
nkInt8Lit,
nkInt16Lit,
nkInt32Lit,
nkInt64Lit,
nkUIntLit,
nkUInt8Lit,
nkUInt16Lit,
nkUInt32Lit,
nkUInt64Lit}
externSIntLit* = {nkIntLit, nkInt8Lit, nkInt16Lit, nkInt32Lit, nkInt64Lit}
externUIntLit* = {nkUIntLit, nkUInt8Lit, nkUInt16Lit, nkUInt32Lit, nkUInt64Lit}
directIntLit* = nkNone
template copyInto*(dest, n, body) =
let patchPos = prepare(dest, tree, n)
body
patch dest, patchPos
template copyIntoKind*(dest, kind, info, body) =
let patchPos = prepare(dest, kind, info)
body
patch dest, patchPos
proc getNodeId*(tree: PackedTree): NodeId {.inline.} = NodeId tree.nodes.len
iterator allNodes*(tree: PackedTree): NodePos =
var p = 0
while p < tree.len:
yield NodePos(p)
let s = span(tree, p)
inc p, s
proc toPackedItemId*(item: int32): PackedItemId {.inline.} =
PackedItemId(module: LitId(0), item: item)
proc load*(f: var RodFile; t: var PackedTree) =
loadSeq f, t.nodes
loadSeq f, t.withFlags
loadSeq f, t.withTypes
proc store*(f: var RodFile; t: PackedTree) =
storeSeq f, t.nodes
storeSeq f, t.withFlags
storeSeq f, t.withTypes

View File

@@ -14,82 +14,15 @@
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`.
import packed_ast, ic, bitabs
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 +66,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]
@@ -157,36 +89,83 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
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
proc replayBackendProcs*(g: ModuleGraph; module: int) =
for it in mitems(g.packed[module].fromDisk.attachedOps):
let key = translateId(it[0], g.packed, module, g.config)
let op = it[1]
let tmp = translateId(it[2], g.packed, module, g.config)
let symId = FullId(module: tmp.module, packed: it[2])
g.attachedOps[op][key] = LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.enumToStringProcs):
let key = translateId(it[0], g.packed, module, g.config)
let tmp = translateId(it[1], g.packed, module, g.config)
let symId = FullId(module: tmp.module, packed: it[1])
g.enumToStringProcs[key] = LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.methodsPerType):
let key = translateId(it[0], g.packed, module, g.config)
let tmp = translateId(it[1], g.packed, module, g.config)
let symId = FullId(module: tmp.module, packed: it[1])
g.methodsPerType.mgetOrPut(key, @[]).add LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.dispatchers):
let tmp = translateId(it, g.packed, module, g.config)
let symId = FullId(module: tmp.module, packed: it)
g.dispatchers.add LazySym(id: symId, sym: nil)
proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
## We remember the generic instantiations a module performed
## in order to to avoid the code bloat that generic code tends
## to imply. This is cheaper than deduplication of identical
## generic instantiations. However, deduplication is more
## powerful and general and I hope to implement it soon too
## (famous last words).
assert g.packed[module].status == loaded
for it in g.packed[module].fromDisk.typeInstCache:
let key = translateId(it[0], g.packed, module, g.config)
g.typeInstCache.mgetOrPut(key, @[]).add LazyType(id: FullId(module: module, packed: it[1]), typ: nil)
for it in mitems(g.packed[module].fromDisk.procInstCache):
let key = translateId(it.key, g.packed, module, g.config)
let sym = translateId(it.sym, g.packed, module, g.config)
var concreteTypes = newSeq[FullId](it.concreteTypes.len)
for i in 0..high(it.concreteTypes):
let tmp = translateId(it.concreteTypes[i], g.packed, module, g.config)
concreteTypes[i] = FullId(module: tmp.module, packed: it.concreteTypes[i])
g.procInstCache.mgetOrPut(key, @[]).add LazyInstantiation(
module: module, sym: FullId(module: sym.module, packed: it.sym),
concreteTypes: concreteTypes, inst: nil)
for it in mitems(g.packed[module].fromDisk.methodsPerGenericType):
let key = translateId(it[0], g.packed, module, g.config)
let col = it[1]
let tmp = translateId(it[2], g.packed, module, g.config)
let symId = FullId(module: tmp.module, packed: it[2])
g.methodsPerGenericType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
replayBackendProcs(g, module)
for it in mitems(g.packed[module].fromDisk.methods):
let sym = loadSymFromId(g.config, g.cache, g.packed, module,
PackedItemId(module: LitId(0), item: it))
methodDef(g, g.idgen, sym)
when false:
# not used anymore:
for it in mitems(g.packed[module].fromDisk.compilerProcs):
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it[1]))
g.lazyCompilerprocs[g.packed[module].fromDisk.sh.strings[it[0]]] = symId
for it in mitems(g.packed[module].fromDisk.converters):
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
g.ifaces[module].converters.add LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.trmacros):
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
g.ifaces[module].patterns.add LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.pureEnums):
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
g.ifaces[module].pureEnums.add LazySym(id: symId, sym: nil)

283
compiler/ic/rodfiles.nim Normal file
View File

@@ -0,0 +1,283 @@
#
#
# The Nim Compiler
# (c) Copyright 2020 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Low level binary format used by the compiler to store and load various AST
## and related data.
##
## NB: this is incredibly low level and if you're interested in how the
## compiler works and less a storage format, you're probably looking for
## the `ic` or `packed_ast` modules to understand the logical format.
from std/typetraits import supportsCopyMem
when defined(nimPreviewSlimSystem):
import std/[syncio, assertions]
import std / tables
## Overview
## ========
## `RodFile` represents a Rod File (versioned binary format), and the
## associated data for common interactions such as IO and error tracking
## (`RodFileError`). The file format broken up into sections (`RodSection`)
## and preceded by a header (see: `cookie`). The precise layout, section
## ordering and data following the section are determined by the user. See
## `ic.loadRodFile`.
##
## A basic but "wrong" example of the lifecycle:
## ---------------------------------------------
## 1. `create` or `open` - create a new one or open an existing
## 2. `storeHeader` - header info
## 3. `storePrim` or `storeSeq` - save your stuff
## 4. `close` - and we're done
##
## Now read the bits below to understand what's missing.
##
## ### Issues with the Example
## Missing Sections:
## This is a low level API, so headers and sections need to be stored and
## loaded by the user, see `storeHeader` & `loadHeader` and `storeSection` &
## `loadSection`, respectively.
##
## No Error Handling:
## The API is centered around IO and prone to error, each operation checks or
## sets the `RodFile.err` field. A user of this API needs to handle these
## appropriately.
##
## API Notes
## =========
##
## Valid inputs for Rod files
## --------------------------
## ASTs, hopes, dreams, and anything as long as it and any children it may have
## support `copyMem`. This means anything that is not a pointer and that does not contain a pointer. At a glance these are:
## * string
## * objects & tuples (fields are recursed)
## * sequences AKA `seq[T]`
##
## Note on error handling style
## ----------------------------
## A flag based approach is used where operations no-op in case of a
## preexisting error and set the flag if they encounter one.
##
## Misc
## ----
## * 'Prim' is short for 'primitive', as in a non-sequence type
type
RodSection* = enum
versionSection
configSection
stringsSection
checkSumsSection
depsSection
numbersSection
exportsSection
hiddenSection
reexportsSection
compilerProcsSection
trmacrosSection
convertersSection
methodsSection
pureEnumsSection
toReplaySection
topLevelSection
bodiesSection
symsSection
typesSection
typeInstCacheSection
procInstCacheSection
attachedOpsSection
methodsPerGenericTypeSection
enumToStringProcsSection
methodsPerTypeSection
dispatchersSection
typeInfoSection # required by the backend
backendFlagsSection
aliveSymsSection # beware, this is stored in a `.alivesyms` file.
sideChannelSection
namespaceSection
symnamesSection
RodFileError* = enum
ok, tooBig, cannotOpen, ioFailure, wrongHeader, wrongSection, configMismatch,
includeFileChanged
RodFile* = object
f*: File
currentSection*: RodSection # for error checking
err*: RodFileError # little experiment to see if this works
# better than exceptions.
const
RodVersion = 2
defaultCookie = [byte(0), byte('R'), byte('O'), byte('D'),
byte(sizeof(int)*8), byte(system.cpuEndian), byte(0), byte(RodVersion)]
proc setError(f: var RodFile; err: RodFileError) {.inline.} =
f.err = err
#raise newException(IOError, "IO error")
proc storePrim*(f: var RodFile; s: string) =
## Stores a string.
## The len is prefixed to allow for later retreival.
if f.err != ok: return
if s.len >= high(int32):
setError f, tooBig
return
var lenPrefix = int32(s.len)
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
setError f, ioFailure
else:
if s.len != 0:
if writeBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
setError f, ioFailure
proc storePrim*[T](f: var RodFile; x: T) =
## Stores a non-sequence/string `T`.
## If `T` doesn't support `copyMem` and is an object or tuple then the fields
## are written -- the user from context will need to know which `T` to load.
if f.err != ok: return
when supportsCopyMem(T):
if writeBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
setError f, ioFailure
elif T is tuple:
for y in fields(x):
storePrim(f, y)
elif T is object:
for y in fields(x):
when y is seq:
storeSeq(f, y)
else:
storePrim(f, y)
else:
{.error: "unsupported type for 'storePrim'".}
proc storeSeq*[T](f: var RodFile; s: seq[T]) =
## Stores a sequence of `T`s, with the len as a prefix for later retrieval.
if f.err != ok: return
if s.len >= high(int32):
setError f, tooBig
return
var lenPrefix = int32(s.len)
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
setError f, ioFailure
else:
for i in 0..<s.len:
storePrim(f, s[i])
proc storeOrderedTable*[K, T](f: var RodFile; s: OrderedTable[K, T]) =
if f.err != ok: return
if s.len >= high(int32):
setError f, tooBig
return
var lenPrefix = int32(s.len)
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
setError f, ioFailure
else:
for _, v in s:
storePrim(f, v)
proc loadPrim*(f: var RodFile; s: var string) =
## Read a string, the length was stored as a prefix
if f.err != ok: return
var lenPrefix = int32(0)
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
setError f, ioFailure
else:
s = newString(lenPrefix)
if lenPrefix > 0:
if readBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
setError f, ioFailure
proc loadPrim*[T](f: var RodFile; x: var T) =
## Load a non-sequence/string `T`.
if f.err != ok: return
when supportsCopyMem(T):
if readBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
setError f, ioFailure
elif T is tuple:
for y in fields(x):
loadPrim(f, y)
elif T is object:
for y in fields(x):
when y is seq:
loadSeq(f, y)
else:
loadPrim(f, y)
else:
{.error: "unsupported type for 'loadPrim'".}
proc loadSeq*[T](f: var RodFile; s: var seq[T]) =
## `T` must be compatible with `copyMem`, see `loadPrim`
if f.err != ok: return
var lenPrefix = int32(0)
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
setError f, ioFailure
else:
s = newSeq[T](lenPrefix)
for i in 0..<lenPrefix:
loadPrim(f, s[i])
proc loadOrderedTable*[K, T](f: var RodFile; s: var OrderedTable[K, T]) =
## `T` must be compatible with `copyMem`, see `loadPrim`
if f.err != ok: return
var lenPrefix = int32(0)
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
setError f, ioFailure
else:
s = initOrderedTable[K, T](lenPrefix)
for i in 0..<lenPrefix:
var x = default T
loadPrim(f, x)
s[x.id] = x
proc storeHeader*(f: var RodFile; cookie = defaultCookie) =
## stores the header which is described by `cookie`.
if f.err != ok: return
if f.f.writeBytes(cookie, 0, cookie.len) != cookie.len:
setError f, ioFailure
proc loadHeader*(f: var RodFile; cookie = defaultCookie) =
## Loads the header which is described by `cookie`.
if f.err != ok: return
var thisCookie: array[cookie.len, byte] = default(array[cookie.len, byte])
if f.f.readBytes(thisCookie, 0, thisCookie.len) != thisCookie.len:
setError f, ioFailure
elif thisCookie != cookie:
setError f, wrongHeader
proc storeSection*(f: var RodFile; s: RodSection) =
## update `currentSection` and writes the bytes value of s.
if f.err != ok: return
assert f.currentSection < s
f.currentSection = s
storePrim(f, s)
proc loadSection*(f: var RodFile; expected: RodSection) =
## read the bytes value of s, sets and error if the section is incorrect.
if f.err != ok: return
var s: RodSection = default(RodSection)
loadPrim(f, s)
if expected != s and f.err == ok:
setError f, wrongSection
proc create*(filename: string): RodFile =
## create the file and open it for writing
result = default(RodFile)
if not open(result.f, filename, fmWrite):
setError result, cannotOpen
proc close*(f: var RodFile) = close(f.f)
proc open*(filename: string): RodFile =
## open the file for reading
result = default(RodFile)
if not open(result.f, filename, fmRead):
setError result, cannotOpen

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,114 +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 — `bnode` needs it for the accessors,
## `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: the accessors turned out to be 700k calls worth 8ms, while `info`
## was 259k calls worth 1.36s. Neither number alone would have found that.
when defined(icBNodeProf):
import std / [envvars, exitprocs, syncio, monotimes]
from std / times import inNanoseconds
type
ProfSlot* = enum
pKind, pTagKindHit, pTagKindMiss, pAstChildren, pSkip, pSon, pLen,
pLastSon, pIterYield, pSym, pTyp, pTypTagLit, pOrigin, pNilType,
pGenBodyCalls, pInfo, pIfaceExported, pIfaceHidden, pIfaceModules,
pTopNodes, pExportSyms, pPeekKind, pPeekFallback, pPeekLoaded,
pTopToolingSkip
TimeSlot* = enum
tLoadClosure, tModuleId, tBifLoad, tPosIndex, tTopLevel, tInterfTables,
tTransform, tHandOff, tGenBody, tAnalyses,
tSym, tTyp, tInfo, tOrigin, 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, `tTyp` reaches `tSym`. 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

@@ -10,10 +10,10 @@
## This module implements the symbol importing mechanism.
import
ast, msgs, options, idents, lookups,
ast, astalgo, 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]
@@ -108,8 +108,8 @@ proc rawImportSymbol(c: PContext, s, origin: PSym; importSet: var IntSet) =
else:
importPureEnumField(c, e)
else:
if s.kind == skConverter: addConverter(c, s)
if hasPattern(s): addPattern(c, s)
if s.kind == skConverter: addConverter(c, LazySym(sym: s))
if hasPattern(s): addPattern(c, LazySym(sym: s))
if s.owner != origin:
c.exportIndirections.incl((origin.id, s.id))
@@ -190,19 +190,22 @@ proc addImport(c: PContext; im: sink ImportedModule) =
template addUnnamedIt(c: PContext, fromMod: PSym; filter: untyped) {.dirty.} =
for it in mitems c.graph.ifaces[fromMod.position].converters:
if filter:
if sfExported in it.flags:
loadPackedSym(c.graph, it)
if sfExported in it.sym.flags:
addConverter(c, it)
for it in mitems c.graph.ifaces[fromMod.position].patterns:
if filter:
if sfExported in it.flags:
loadPackedSym(c.graph, it)
if sfExported in it.sym.flags:
addPattern(c, it)
for it in mitems c.graph.ifaces[fromMod.position].pureEnums:
if filter:
importPureEnumFields(c, it, it.typ)
loadPackedSym(c.graph, it)
importPureEnumFields(c, it.sym, it.sym.typ)
proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) =
c.addImport ImportedModule(m: fromMod, mode: importExcept, exceptSet: exceptSet)
addUnnamedIt(c, fromMod, it.name.id notin exceptSet)
addUnnamedIt(c, fromMod, it.sym.name.id notin exceptSet)
proc importAllSymbols*(c: PContext, fromMod: PSym) =
c.addImport ImportedModule(m: fromMod, mode: importAll)
@@ -242,8 +245,7 @@ proc importModuleAs(c: PContext; n: PNode, realModule: PSym, importHidden, track
# avoids modifying `realModule`, see D20201209T194412 for `import {.all.}`
result = createModuleAliasImpl(realModule.name)
if importHidden:
ensureMutable result
result.optionsImpl.incl optImportHidden
result.options.incl optImportHidden
let moduleIdent = if n.kind in {nkInfix, nkImportAs}: n[^1] else: n
result.info = moduleIdent.info
if trackUnusedImport:
@@ -289,8 +291,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
c.recursiveDep = err
let trackUnusedImport = warnUnusedImportX in c.config.notes
var realModule: PSym
discard pushOptionEntry(c)
let realModule = c.graph.importModuleCallback(c.graph, c.module, f)
realModule = c.graph.importModuleCallback(c.graph, c.module, f)
result = importModuleAs(c, n, realModule, transf.importHidden, trackUnusedImport)
popOptionEntry(c)
@@ -304,9 +307,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
@@ -69,14 +69,12 @@ proc hasDestructor(c: Con; t: PType): bool {.inline.} =
result = ast.hasDestructor(t)
when toDebug.len > 0:
# for more effective debugging
if not result and c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
if not result and c.graph.config.selectedGC in {gcArc, gcOrc, 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)
@@ -167,18 +165,30 @@ proc isLastReadImpl(n: PNode; c: var Con; scope: var Scope): bool =
template hasDestructorOrAsgn(c: var Con, typ: PType): bool =
# bug #23354; an object type could have a non-trivial assignements when it is passed to a sink parameter
hasDestructor(c, typ) or (c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc} and
hasDestructor(c, typ) or (c.graph.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
typ.kind == tyObject and not isTrivial(getAttachedOp(c.graph, typ, attachedAsgn)))
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:
@@ -331,21 +329,21 @@ proc isCriticalLink(dest: PNode): bool {.inline.} =
result = dest.kind != nkSym
proc finishCopy(c: var Con; result, dest: PNode; flags: set[MoveOrCopyFlag]; isFromSink: bool) =
if c.graph.config.selectedGC in {gcOrc, gcYrc} and IsExplicitSink notin flags:
if c.graph.config.selectedGC == gcOrc and IsExplicitSink notin flags:
# add cyclic flag, but not to sink calls, which IsExplicitSink generates
let t = dest.typ.skipTypes(tyUserTypeClasses + {tyGenericInst, tyAlias, tySink, tyDistinct})
if cyclicType(c.graph, t):
result.add boolLit(c.graph, result.info, isFromSink or isCriticalLink(dest))
proc genMarkCyclic(c: var Con; result, dest: PNode) =
if c.graph.config.selectedGC in {gcOrc, gcYrc}:
if c.graph.config.selectedGC == gcOrc:
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
if cyclicType(c.graph, t):
if t.kind == tyRef:
result.add callCodegenProc(c.graph, "nimMarkCyclic", dest.info, dest)
else:
let xenv = genBuiltin(c.graph, c.idgen, mAccessEnv, "accessEnv", dest)
xenv.typ = getSysType(c.graph, dest.info, tyPointer)
xenv.typ() = getSysType(c.graph, dest.info, tyPointer)
result.add callCodegenProc(c.graph, "nimMarkCyclic", dest.info, xenv)
proc genCopyNoCheck(c: var Con; dest, ri: PNode; a: TTypeAttachedOp): PNode =
@@ -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))
@@ -421,21 +419,7 @@ proc genWasMoved(c: var Con, n: PNode): PNode =
proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
result = newNodeI(nkCall, info)
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
result.typ() = t
proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
# generate: (let tmp = v; reset(v); tmp)
@@ -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)
@@ -477,50 +457,49 @@ proc isCapturedVar(n: PNode): bool =
else: result = false
proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
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 c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
let tmp = c.getTemp(s, nTyp, n.info)
if hasDestructorOrAsgn(c, nTyp):
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)
else:
if c.graph.config.selectedGC in {gcArc, gcOrc, 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)
result.add newTree(nkAsgn, tmp, p(n, c, s, normal))
# Since we know somebody will take over the produced copy, there is
# no need to destroy it.
result.add tmp
@@ -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}:
@@ -863,9 +825,9 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
n[1].typ.skipTypes(abstractInst-{tyOwned}).kind == tyOwned:
# allow conversions from owned to unowned via this little hack:
let nTyp = n[1].typ
n[1].typ = n.typ
n[1].typ() = n.typ
result[1] = p(n[1], c, s, sinkArg)
result[1].typ = nTyp
result[1].typ() = nTyp
else:
result[1] = p(n[1], c, s, sinkArg)
elif n.kind in {nkObjDownConv, nkObjUpConv}:
@@ -964,7 +926,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
result[0] = copyTree(n[0])
if c.graph.config.selectedGC in {gcHooks, gcArc, gcAtomicArc, gcOrc, gcYrc}:
if c.graph.config.selectedGC in {gcHooks, gcArc, gcAtomicArc, gcOrc}:
let destroyOld = c.genDestroy(result[1])
result = newTree(nkStmtList, destroyOld, result)
else:
@@ -1002,14 +964,14 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
s.locals.add v.sym
pVarTopLevel(v, c, s, result)
if ri.kind != nkEmpty:
let isGlobalPragma = v.kind == nkSym and
let isGlobalPragma = v.kind == nkSym and
{sfPure, sfGlobal} <= v.sym.flags and
isInProc
let value = moveOrCopy(v, ri, c, s, if v.kind == nkSym: {IsDecl} else: {})
if isGlobalPragma:
c.graph.procGlobals.add newTree(nkFastAsgn, v, ri)
c.graph.procGlobals.add value
else:
let value = moveOrCopy(v, ri, c, s, if v.kind == nkSym: {IsDecl} else: {})
result.add value
elif ri.kind == nkEmpty and c.inLoop > 0:
let skipInit = v.kind == nkDotExpr and # Closure var
@@ -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)
@@ -1079,9 +1036,9 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
n[1].typ.skipTypes(abstractInst-{tyOwned}).kind == tyOwned:
# allow conversions from owned to unowned via this little hack:
let nTyp = n[1].typ
n[1].typ = n.typ
n[1].typ() = n.typ
result[1] = p(n[1], c, s, mode)
result[1].typ = nTyp
result[1].typ() = nTyp
else:
result[1] = p(n[1], 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,8 +1154,8 @@ 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)
tmp.sym.flagsImpl.incl sfSingleUsedTemp
let tmp = c.getTemp(s, n.typ, n.info)
tmp.sym.flags.incl sfSingleUsedTemp
result.add newTree(nkFastAsgn, tmp, copyTree(n))
s.final.add c.genDestroy(tmp)
n[] = tmp[]
@@ -1365,7 +1330,7 @@ proc addSinkCopy(c: var Con; s: var Scope; sinkParams: seq[PSym]; n: PNode): PNo
for param in sinkParams:
if param.id in mutatedSet:
let newSym = newSym(skTemp, getIdent(c.graph.cache, "sinkCopy"), c.idgen, param.owner, n.info)
newSym.flagsImpl.incl sfFromGeneric
newSym.flags.incl sfFromGeneric
newSym.typ = param.typ.elementType
mapping[param.id] = newSym
let v = newNodeI(nkVarSection, n.info)

View File

@@ -1,122 +0,0 @@
proc copySymdef(n: PNode; locals: var Table[int, PSym]; idgen: IdGenerator; owner: PSym): PNode =
case n.kind
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
result = n
of nkSym:
let oldSym = n.sym
let newSym = copySym(oldSym, idgen)
setOwner(newSym, owner)
locals[oldSym.id] = newSym
result = newSymNode(newSym, oldSym.info)
else:
result = shallowCopy(n)
for i in 0..<n.len:
result[i] = copySymdef(n[i], locals, idgen, owner)
proc copyInlineProcBody(n: PNode; locals: var Table[int, PSym]; idgen: IdGenerator; owner: PSym): PNode =
case n.kind
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
result = n
of nkSym:
let sym = locals.getOrDefault(n.sym.id)
if sym != nil:
result = newSymNode(sym, n.info)
else:
result = n
of nkLetSection, nkVarSection:
result = shallowCopy(n)
for i in 0..<n.len:
let it = n[i]
if it.kind == nkCommentStmt:
result[i] = it
elif it.kind in {nkIdentDefs, nkConstDef}:
result[i] = shallowCopy(it)
for j in 0..<it.len-2:
result[i][j] = copySymdef(it[j], locals, idgen, owner)
for j in it.len-2..<it.len:
result[i][j] = copyInlineProcBody(it[j], locals, idgen, owner)
else:
assert it.kind == nkVarTuple
result[i] = shallowCopy(it)
for j in 0..<it.len-2:
assert it[j].kind == nkSym
let oldSym = it[j].sym
let newSym = copySym(oldSym, idgen)
setOwner(newSym, owner)
locals[oldSym.id] = newSym
result[i][j] = newSymNode(newSym, oldSym.info)
for j in it.len-2..<it.len:
result[i][j] = copyInlineProcBody(it[j], locals, idgen, owner)
of nkForStmt, nkParForStmt:
result = shallowCopy(n)
for i in 0..<n.len-2:
assert n[i].kind == nkSym
let oldSym = n[i].sym
let newSym = copySym(oldSym, idgen)
setOwner(newSym, owner)
locals[oldSym.id] = newSym
result[i] = newSymNode(newSym, oldSym.info)
result[n.len-2] = copyInlineProcBody(n[n.len-2], locals, idgen, owner)
result[n.len-1] = copyInlineProcBody(n[n.len-1], locals, idgen, owner)
of routineDefs, nkTypeSection, nkTypeOfExpr, nkMixinStmt, nkBindStmt, nkConstSection:
result = n
else:
result = shallowCopy(n)
for i in 0..<n.len:
result[i] = copyInlineProcBody(n[i], locals, idgen, owner)
proc copyParams(n: PNode; locals: var Table[int, PSym]; idgen: IdGenerator; owner: PSym): PNode =
result = shallowCopy(n)
result[0] = n[0] # return type
for i in 1..<n.len:
let it = n[i]
assert it.kind == nkIdentDefs
result[i] = shallowCopy(it)
for j in 0..<it.len-2:
assert it[j].kind == nkSym
let oldSym = it[j].sym
let newSym = copySym(oldSym, idgen)
setOwner(newSym, owner)
locals[oldSym.id] = newSym
result[i][j] = newSymNode(newSym, oldSym.info)
owner.typ.addParam newSym
for j in it.len-2..<it.len:
result[i][j] = copyInlineProcBody(it[j], locals, idgen, owner)
proc copyInlineProc(prc: PSym; idgen: IdGenerator): PSym =
result = copySym(prc, idgen)
var locals = initTable[int, PSym]()
var a = shallowCopy(prc.ast)
if resultPos < prc.ast.len and prc.ast[resultPos].kind == nkSym:
let oldRes = prc.ast[resultPos].sym
let newRes = copySym(oldRes, idgen)
setOwner(newRes, result)
locals[oldRes.id] = newRes
a[resultPos] = newSymNode(newRes, oldRes.info)
result.typ = copyType(prc.typ, idgen, result)
result.typ.n = newNodeI(prc.typ.n.kind, prc.typ.n.info)
if prc.typ.n.len > 0:
result.typ.n.add copyNode(prc.typ.n[0])
for i in 1..<prc.typ.n.len:
let it = prc.typ.n[i]
assert it.kind == nkSym
let oldSym = it.sym
let newSym = copySym(oldSym, idgen)
setOwner(newSym, result)
locals[oldSym.id] = newSym
result.typ.addParam newSym
for i in 0..<prc.ast.len:
if i == paramsPos:
a[i] = copyTree(prc.ast[i])
elif i == resultPos and prc.ast[i].kind == nkSym:
discard "handled above"
else:
a[i] = copyInlineProcBody(prc.ast[i], locals, idgen, result)
result.ast = a
#echo "Produced: ", renderTree(result.ast, {renderIds})

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
@@ -272,8 +277,7 @@ proc mangleName(m: BModule, s: PSym): Rope =
else:
result.add("_")
result.add(rope(s.id))
ensureMutable s
s.locImpl.snippet = result
s.loc.snippet = result
proc escapeJSString(s: string): string =
result = newStringOfCap(s.len + s.len shr 2)
@@ -589,6 +593,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)
@@ -715,47 +728,44 @@ proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
of mShrI:
let typ = n[1].typ.skipTypes(abstractVarRange)
if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
applyFormat("BigInt.asIntN(64, BigInt.asUintN(64, $1) >> (BigInt($2) & 63n))")
applyFormat("BigInt.asIntN(64, BigInt.asUintN(64, $1) >> BigInt($2))")
elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
applyFormat("($1 >> (BigInt($2) & 63n))")
applyFormat("($1 >> BigInt($2))")
else:
let bitmask = typ.size * 8 - 1
if typ.kind in {tyInt..tyInt32}:
let trimmerU = unsignedTrimmer(typ.size)
let trimmerS = signedTrimmer(typ.size)
r.res = "((($1 $2) >>> ($3 & $5)) $4)" % [xLoc, trimmerU, yLoc, trimmerS, $bitmask]
r.res = "((($1 $2) >>> $3) $4)" % [xLoc, trimmerU, yLoc, trimmerS]
else:
r.res = "($1 >>> ($2 & $3))" % [xLoc, yLoc, $bitmask]
applyFormat("($1 >>> $2)")
of mShlI:
let typ = n[1].typ.skipTypes(abstractVarRange)
if typ.size == 8:
if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
applyFormat("BigInt.asIntN(64, $1 << (BigInt($2) & 63n))")
applyFormat("BigInt.asIntN(64, $1 << BigInt($2))")
elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
applyFormat("BigInt.asUintN(64, $1 << (BigInt($2) & 63n))")
applyFormat("BigInt.asUintN(64, $1 << BigInt($2))")
else:
applyFormat("($1 * Math.pow(2, ($2 & 63)))")
applyFormat("($1 * Math.pow(2, $2))")
else:
let bitmask = typ.size * 8 - 1
if typ.kind in {tyUInt..tyUInt32}:
let trimmer = unsignedTrimmer(typ.size)
r.res = "(($1 << ($2 & $4)) $3)" % [xLoc, yLoc, trimmer, $bitmask]
r.res = "(($1 << $2) $3)" % [xLoc, yLoc, trimmer]
else:
let trimmer = signedTrimmer(typ.size)
r.res = "(($1 << ($2 & $4)) $3)" % [xLoc, yLoc, trimmer, $bitmask]
r.res = "(($1 << $2) $3)" % [xLoc, yLoc, trimmer]
of mAshrI:
let typ = n[1].typ.skipTypes(abstractVarRange)
if typ.size == 8:
if optJsBigInt64 in p.config.globalOptions:
applyFormat("($1 >> (BigInt($2) & 63n))")
applyFormat("($1 >> BigInt($2))")
else:
applyFormat("Math.floor($1 / Math.pow(2, ($2 & 63)))")
applyFormat("Math.floor($1 / Math.pow(2, $2))")
else:
let bitmask = typ.size * 8 - 1
if typ.kind in {tyUInt..tyUInt32}:
r.res = "($1 >>> ($2 & $3)))" % [xLoc, yLoc, $bitmask]
applyFormat("($1 >>> $2)")
else:
r.res = "($1 >> ($2 & $3))" % [xLoc, yLoc, $bitmask]
applyFormat("($1 >> $2)")
of mBitandI: bitwiseExpr("&")
of mBitorI: bitwiseExpr("|")
of mBitxorI: bitwiseExpr("^")
@@ -992,8 +1002,7 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
# If some branch requires a local alias introduce it here. This is needed
# since JS cannot do ``catch x as y``.
if excAlias != nil:
ensureMutable excAlias.sym
excAlias.sym.locImpl.snippet = mangleName(p.module, excAlias.sym)
excAlias.sym.loc.snippet = mangleName(p.module, excAlias.sym)
lineF(p, "var $1 = lastJSError;$n", excAlias.sym.loc.snippet)
gen(p, n[i][^1], a)
moveInto(p, a, r)
@@ -1126,8 +1135,7 @@ proc genBlock(p: PProc, n: PNode, r: var TCompRes) =
# named block?
if (n[0].kind != nkSym): internalError(p.config, n.info, "genBlock")
var sym = n[0].sym
ensureMutable sym
sym.locImpl.k = locOther
sym.loc.k = locOther
sym.position = idx+1
let labl = p.unique
lineF(p, "Label$1: {$n", [labl.rope])
@@ -1168,6 +1176,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
@@ -1225,8 +1234,7 @@ proc generateHeader(p: PProc, prc: PSym): Rope =
# to keep it simple
let env = prc.ast[paramsPos].lastSon
assert env.kind == nkSym, "env is missing"
ensureMutable env.sym
env.sym.locImpl.snippet = "this"
env.sym.loc.snippet = "this"
for i in 1..<typ.n.len:
assert(typ.n[i].kind == nkSym)
@@ -1240,6 +1248,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 +1291,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")
@@ -1354,15 +1371,12 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
r.typ = etyBaseIndex
let b = if n.kind == nkHiddenAddr: n[0] else: n
gen(p, b[0], a)
if skipTypes(b[0].typ, abstractVarRange + tyTypeClasses).kind == tyTuple:
# ref #25227 about `+ tyTypeClasses`
if skipTypes(b[0].typ, abstractVarRange).kind == tyTuple:
r.res = makeJSString("Field" & $getFieldPosition(p, b[1]))
else:
if b[1].kind != nkSym: internalError(p.config, b[1].info, "genFieldAddr")
var f = b[1].sym
if f.loc.snippet == "":
ensureMutable f
f.locImpl.snippet = mangleName(p.module, f)
if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f)
r.res = makeJSString($f.loc.snippet)
internalAssert p.config, a.typ != etyBaseIndex
r.address = a.res
@@ -1390,9 +1404,7 @@ proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) =
else:
if n[1].kind != nkSym: internalError(p.config, n[1].info, "genFieldAccess")
var f = n[1].sym
if f.loc.snippet == "":
ensureMutable f
f.locImpl.snippet = mangleName(p.module, f)
if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f)
r.res = "$1.$2" % [r.res, f.loc.snippet]
mkTemp(1)
r.kind = resExpr
@@ -1413,15 +1425,11 @@ proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) =
# Field symbol
var field = accessExpr[1].sym
internalAssert p.config, field.kind == skField
if field.loc.snippet == "":
ensureMutable field
field.locImpl.snippet = mangleName(p.module, field)
if field.loc.snippet == "": field.loc.snippet = mangleName(p.module, field)
# Discriminant symbol
let disc = checkExpr[2].sym
internalAssert p.config, disc.kind == skField
if disc.loc.snippet == "":
ensureMutable disc
disc.locImpl.snippet = mangleName(p.module, disc)
if disc.loc.snippet == "": disc.loc.snippet = mangleName(p.module, disc)
var setx: TCompRes = default(TCompRes)
gen(p, checkExpr[1], setx)
@@ -1450,20 +1458,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 +1466,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 +1528,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:
@@ -1605,11 +1586,7 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
of nkObjDownConv:
gen(p, n[0], r)
of nkHiddenDeref, nkDerefExpr:
if n.kind in {nkAddr, nkHiddenAddr}:
# addr ( deref ( x )) --> x
gen(p, n[0][0], r)
else:
gen(p, n[0], r)
gen(p, n[0], r)
of nkHiddenAddr:
gen(p, n[0], r)
of nkConv:
@@ -1753,47 +1730,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) =
var a: TCompRes = default(TCompRes)
if 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:
@@ -1803,13 +1741,6 @@ proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int =
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:
@@ -1844,6 +1775,12 @@ 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;
@@ -1900,9 +1837,7 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) =
# don't call '$' here for efficiency:
let f = n[0].sym
if f.loc.snippet == "":
ensureMutable f
f.locImpl.snippet = mangleName(p.module, f)
if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f)
if sfInfixCall in f.flags:
let pat = $n[0].sym.loc.snippet
internalAssert p.config, pat.len > 0
@@ -2061,12 +1996,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("}")
@@ -2135,8 +2066,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")
@@ -2376,6 +2306,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)
@@ -2396,8 +2329,8 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
r.res = "if (null != $1) { if (null == $2) $2 = $3; else $2 += $3; }" %
[b, lhs.rdLoc, tmp]
else:
useMagic(p, "nimAddStrStr")
r.res = "nimAddStrStr($1, $2);" % [lhs.rdLoc, rhs.rdLoc]
let (a, tmp) = maybeMakeTemp(p, n[1], lhs)
r.res = "$1.push.apply($3, $2);" % [a, rhs.rdLoc, tmp]
r.kind = resExpr
of mAppendSeqElem:
var x, y: TCompRes = default(TCompRes)
@@ -2444,21 +2377,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:
@@ -2467,9 +2392,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:
@@ -2552,24 +2475,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)
@@ -2655,6 +2565,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]
@@ -2662,9 +2573,7 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
let val = it[1]
gen(p, val, a)
var f = it[0].sym
if f.loc.snippet == "":
ensureMutable f
f.locImpl.snippet = mangleName(p.module, f)
if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f)
fieldIDs.incl(lookupFieldAgain(n.typ.skipTypes({tyDistinct}), f).id)
let typ = val.typ.skipTypes(abstractInst)
@@ -2861,11 +2770,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)
@@ -2971,8 +2875,6 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) =
elif dest.kind in tyFloat..tyFloat64:
if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
r.res = "Number($1)" % [r.res]
elif dest.kind == tyChar and (fromInt or fromUint):
r.res = "($1 & 255)" % [r.res]
elif (src.kind == tyPtr and mapType(p, src) == etyObject) and dest.kind == tyPointer:
r.address = r.res
r.res = "null"

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)
@@ -145,7 +150,6 @@ template isIterator*(owner: PSym): bool =
proc createEnvObj(g: ModuleGraph; idgen: IdGenerator; owner: PSym; info: TLineInfo): PType =
result = createObj(g, idgen, owner, info, final=false)
result.incl tfFinal
if owner.isIterator:
rawAddField(result, createStateField(g, owner, idgen))
@@ -156,24 +160,12 @@ proc getClosureIterResult*(g: ModuleGraph; iter: PSym; idgen: IdGenerator): PSym
# XXX a bit hacky:
result = newSym(skResult, getIdent(g.cache, ":result"), idgen, iter, iter.info, {})
result.typ = iter.typ.returnType
incl(result.flagsImpl, sfUsed)
incl(result.flags, 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 +176,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 +215,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 +227,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.flags.incl sfInjectDestructors
template liftingHarmful(conf: ConfigRef; owner: PSym): bool =
## lambda lifting can be harmful for JS-like code generators.
@@ -252,7 +239,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.flags.incl sfInjectDestructors
proc genCreateEnv(env: PNode): PNode =
var c = newNodeIT(nkObjConstr, env.info, env.typ)
@@ -283,6 +270,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 +289,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)
incl(owner.typ.flags, tfCapturesEnv)
if not isEnv:
owner.typ.callConv = ccClosure
@@ -367,7 +335,7 @@ proc asOwnedRef(c: var DetectionPass; t: PType): PType =
if optOwnedRefs in c.graph.config.globalOptions:
assert t.kind == tyRef
result = newType(tyOwned, c.idgen, t.owner)
result.incl tfHasOwned
result.flags.incl tfHasOwned
result.rawAddSon t
else:
result = t
@@ -439,29 +407,17 @@ 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
let t = c.getEnvTypeForOwner(owner, info)
if cp == nil:
cp = newSym(skParam, getIdent(c.graph.cache, paramName), c.idgen, fn, fn.info)
incl(cp.flagsImpl, sfFromGeneric)
incl(cp.flags, sfFromGeneric)
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 =
@@ -653,7 +609,7 @@ proc rawClosureCreation(owner: PSym;
let unowned = c.unownedEnvVars[owner.id]
assert unowned != nil
let env2 = copyTree(env)
env2.typ = unowned.typ
env2.typ() = unowned.typ
result.add newAsgnStmt(unowned, env2, env.info)
createTypeBoundOpsLL(d.graph, unowned.typ, env.info, d.idgen, owner)
@@ -667,7 +623,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.flags.incl sfInjectDestructors
let upField = lookupInRecord(env.typ.skipTypes({tyOwned, tyRef, tyPtr}).n, getIdent(d.graph.cache, upName))
if upField != nil:
@@ -675,17 +631,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))
@@ -720,7 +665,7 @@ proc closureCreationForIter(owner: PSym, iter: PNode;
result = newNodeIT(nkStmtListExpr, iter.info, iter.sym.typ)
let iterOwner = iter.sym.skipGenericOwner
var v = newSym(skVar, getIdent(d.graph.cache, envName), d.idgen, iterOwner, iter.info)
incl(v.flagsImpl, sfShadowed)
incl(v.flags, sfShadowed)
v.typ = asOwnedRef(d, getHiddenParam(d.graph, iter.sym).typ)
var vnode: PNode
if iterOwner.isIterator:
@@ -743,10 +688,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)
@@ -845,7 +786,7 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: var DetectionPass;
let oldInContainer = c.inContainer
c.inContainer = 0
let m = newSymNode(n[namePos].sym)
m.typ = n.typ
m.typ() = n.typ
result = liftCapturedVars(m, owner, d, c)
c.inContainer = oldInContainer
of nkHiddenStdConv:
@@ -1033,20 +974,6 @@ proc liftForLoop*(g: ModuleGraph; body: PNode; idgen: IdGenerator; owner: PSym):
for i in 0..<op.len-1:
result.add op[i]
elif op.kind != nkSym: # might have side effects
# bug #25046
# create a temp for the closure
# var :closureTemp
# :closureTemp = ...
let tempSym = newSym(skLet, getIdent(g.cache, ":closureTemp"), idgen, owner, body.info)
tempSym.typ = call[0].typ
let temp = newSymNode(tempSym)
var v = newNodeI(nkVarSection, body.info)
addVar(v, temp)
result.add(v)
result.add newAsgnStmt(temp, call[0], body.info)
call[0] = temp
var loopBody = newNodeI(nkStmtList, body.info, 3)
var whileLoop = newNodeI(nkWhileStmt, body.info, 2)
whileLoop[0] = newIntTypeNode(1, getSysType(g, body.info, tyBool))

View File

@@ -1,3 +1,4 @@
import std/[tables]
import ast, astalgo
type
@@ -46,11 +47,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 +63,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 +73,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 +82,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

@@ -316,28 +316,6 @@ proc getNumber(L: var Lexer, result: var Token) =
L.bufpos = msgPos
lexMessage(L, msgKind, msg % t.literal)
proc checkBitWidth(L: var Lexer, base: NumericalBase, tokType: TokType,
numDigits: int, startpos: int) =
# Check bit width for non-base-10 literals
# Warn if the digit count exceeds what can fit in the target type
let bitsPerDigit = case base
of base2: 1
of base8: 3
of base16: 4
else: raiseAssert "unreachable"
let bitWidth = case tokType
of tkInt8Lit, tkUInt8Lit: 8
of tkInt16Lit, tkUInt16Lit: 16
of tkInt32Lit, tkUInt32Lit: 32
of tkInt64Lit, tkUIntLit, tkIntLit, tkUInt64Lit: 64
else: raiseAssert "unreachable"
# Maximum digits = ceil(bitWidth / bitsPerDigit) = (bitWidth + bitsPerDigit - 1) div bitsPerDigit
let maxDigits = (bitWidth + bitsPerDigit - 1) div bitsPerDigit
if numDigits > maxDigits:
lexMessageLitNum(L,
"number has " & $numDigits & " digits but type only supports " &
$maxDigits & " digits: '$1'", startpos, warnLongLiterals)
var
xi: BiggestInt
isBase10 = true
@@ -513,11 +491,6 @@ proc getNumber(L: var Lexer, result: var Token) =
setNumber result.fNumber, (cast[ptr float64](addr(xi)))[]
else: internalError(L.config, getLineInfo(L), "getNumber")
# Check bit width for non-base-10 literals
# Warn if the digit count exceeds what can fit in the target type
if result.base != base10 and result.tokType in {tkIntLit..tkUInt64Lit} and numDigits > 0:
checkBitWidth(L, result.base, result.tokType, numDigits, startpos)
# Bounds checks. Non decimal literals are allowed to overflow the range of
# the datatype as long as their pattern don't overflow _bitwise_, hence
# below checks of signed sizes against uint*.high is deliberate:
@@ -735,11 +708,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 +818,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 +851,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':
@@ -928,7 +896,7 @@ proc getSymbol(L: var Lexer, tok: var Token) =
tok.tokType = tkSymbol
else:
tok.tokType = TokType(tok.ident.id + ord(tkSymbol))
if suspicious and {optStyleHint, optStyleError, optStyleWarning} * L.config.globalOptions != {}:
if suspicious and {optStyleHint, optStyleError} * L.config.globalOptions != {}:
lintReport(L.config, getLineInfo(L), tok.ident.s.normalize, tok.ident.s)
L.bufpos = pos
@@ -1354,7 +1322,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

@@ -49,7 +49,7 @@ proc at(a, i: PNode, elemType: PType): PNode =
result = newNodeI(nkBracketExpr, a.info, 2)
result[0] = a
result[1] = i
result.typ = elemType
result.typ() = elemType
proc destructorOverridden(g: ModuleGraph; t: PType): bool =
let op = getAttachedOp(g, t, attachedDestructor)
@@ -68,7 +68,7 @@ proc dotField(x: PNode, f: PSym): PNode =
else:
result[0] = x
result[1] = newSymNode(f, x.info)
result.typ = f.typ
result.typ() = f.typ
proc newAsgnStmt(le, ri: PNode): PNode =
result = newNodeI(nkAsgn, le.info, 2)
@@ -88,34 +88,24 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
body.add newAsgnStmt(x, y)
elif c.kind == attachedDestructor and c.addMemReset:
let call = genBuiltin(c, mDefault, "default", x)
call.typ = t
call.typ() = t
body.add newAsgnStmt(x, call)
elif c.kind == attachedWasMoved:
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 =
result = newNodeI(nkWhileStmt, c.info, 2)
let cmp = genBuiltin(c, mLtI, "<", i)
cmp.add genLen(c.g, dest)
cmp.typ = getSysType(c.g, c.info, tyBool)
cmp.typ() = getSysType(c.g, c.info, tyBool)
result[0] = cmp
result[1] = newNodeI(nkStmtList, c.info)
@@ -137,10 +127,10 @@ proc genContainerOf(c: var TLiftCtx; objType: PType, field, x: PSym): PNode =
dotExpr.add newSymNode(field)
let offsetOf = genBuiltin(c, mOffsetOf, "offsetof", dotExpr)
offsetOf.typ = intType
offsetOf.typ() = intType
let minusExpr = genBuiltin(c, mSubI, "-", castExpr1)
minusExpr.typ = intType
minusExpr.typ() = intType
minusExpr.add offsetOf
let objPtr = makePtrType(objType.owner, objType, c.idgen)
@@ -173,7 +163,7 @@ proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode; enforceDefaultOp: bool,
if c.filterDiscriminator != nil: return
let f = n.sym
let b = if c.kind == attachedTrace: y else: y.dotField(f)
if (sfCursor in f.flags and c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc, gcHooks}) or
if (sfCursor in f.flags and c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcHooks}) or
enforceDefaultOp:
defaultOp(c, f.typ, body, x.dotField(f), b)
else:
@@ -290,11 +280,11 @@ proc fillBodyObjT(c: var TLiftCtx; t: PType, body, x, y: PNode) =
# because the wasMoved(dest) call would zero out src, if dest aliases src.
var cond = newTree(nkCall, newSymNode(c.g.getSysMagic(c.info, "==", mEqRef)),
newTreeIT(nkAddr, c.info, makePtrType(c.fn, x.typ, c.idgen), x), newTreeIT(nkAddr, c.info, makePtrType(c.fn, y.typ, c.idgen), y))
cond.typ = getSysType(c.g, x.info, tyBool)
cond.typ() = getSysType(c.g, x.info, tyBool)
body.add genIf(c, cond, newTreeI(nkReturnStmt, c.info, newNodeI(nkEmpty, c.info)))
var temp = newSym(skTemp, getIdent(c.g.cache, lowerings.genPrefix), c.idgen, c.fn, c.info)
temp.typ = x.typ
incl(temp, sfFromGeneric)
incl(temp.flags, sfFromGeneric)
var v = newNodeI(nkVarSection, c.info)
let blob = newSymNode(temp)
v.addVar(blob, x)
@@ -322,7 +312,7 @@ proc fillBodyObjT(c: var TLiftCtx; t: PType, body, x, y: PNode) =
proc boolLit*(g: ModuleGraph; info: TLineInfo; value: bool): PNode =
result = newIntLit(g, info, ord value)
result.typ = getSysType(g, info, tyBool)
result.typ() = getSysType(g, info, tyBool)
proc getCycleParam(c: TLiftCtx): PNode =
assert c.kind in {attachedAsgn, attachedDup}
@@ -390,10 +380,6 @@ proc requiresDestructor(c: TLiftCtx; t: PType): bool {.inline.} =
proc instantiateGeneric(c: var TLiftCtx; op: PSym; t, typeInst: PType): PSym =
if c.c != nil and typeInst != nil:
result = c.c.instTypeBoundOp(c.c, op, typeInst, c.info, attachedAsgn, 1)
elif typeInst != nil and getAttachedOp(c.g, typeInst, c.kind) != nil:
# c.c == nil in lambdalifting
# hooks are already insted
result = getAttachedOp(c.g, typeInst, c.kind)
else:
localError(c.g.config, c.info,
"cannot generate destructor for generic type: " & typeToString(t))
@@ -407,8 +393,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
if op != nil and op != c.fn and
(sfOverridden in op.flags or destructorOverridden):
if sfError in op.flags:
ensureMutable c.fn
incl c.fn.flagsImpl, sfError
incl c.fn.flags, sfError
#else:
# markUsed(c.g.config, c.info, op, c.g.usageSym)
onUse(c.info, op)
@@ -434,8 +419,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
if op == nil:
op = produceSym(c.g, c.c, t, c.kind, c.info, c.idgen)
if sfError in op.flags:
ensureMutable c.fn
incl c.fn.flagsImpl, sfError
incl c.fn.flags, sfError
#else:
# markUsed(c.g.config, c.info, op, c.g.usageSym)
onUse(c.info, op)
@@ -551,7 +535,7 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
proc declareCounter(c: var TLiftCtx; body: PNode; first: BiggestInt): PNode =
var temp = newSym(skTemp, getIdent(c.g.cache, lowerings.genPrefix), c.idgen, c.fn, c.info)
temp.typ = getSysType(c.g, body.info, tyInt)
incl(temp.flagsImpl, sfFromGeneric)
incl(temp.flags, sfFromGeneric)
var v = newNodeI(nkVarSection, c.info)
result = newSymNode(temp)
@@ -561,29 +545,13 @@ proc declareCounter(c: var TLiftCtx; body: PNode; first: BiggestInt): PNode =
proc declareTempOf(c: var TLiftCtx; body: PNode; value: PNode): PNode =
var temp = newSym(skTemp, getIdent(c.g.cache, lowerings.genPrefix), c.idgen, c.fn, c.info)
temp.typ = value.typ
incl(temp.flagsImpl, sfFromGeneric)
incl(temp.flags, sfFromGeneric)
var v = newNodeI(nkVarSection, c.info)
result = newSymNode(temp)
v.addVar(result, value)
body.add v
proc considerInferDupFromCopy(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
## For `=dup`, if no explicit hook exists, try to infer from `=copy` hook
## to maintain backward compatibility. Returns true if inference was applied.
if c.kind == attachedDup:
var op2 = getAttachedOp(c.g, t, attachedAsgn)
if op2 != nil and sfOverridden in op2.flags:
#markUsed(c.g.config, c.info, op, c.g.usageSym)
onUse(c.info, op2)
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
body.add newHookCall(c, op2, x, y)
result = true
else:
result = false
else:
result = false
proc addIncStmt(c: var TLiftCtx; body, i: PNode) =
let incCall = genBuiltin(c, mInc, "inc", i)
incCall.add lowerings.newIntLit(c.g, c.info, 1)
@@ -593,15 +561,19 @@ proc newSeqCall(c: var TLiftCtx; x, y: PNode): PNode =
# don't call genAddr(c, x) here:
result = genBuiltin(c, mNewSeq, "newSeq", x)
let lenCall = genBuiltin(c, mLengthSeq, "len", y)
lenCall.typ = getSysType(c.g, x.info, tyInt)
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)
lenCall.typ() = getSysType(c.g, x.info, tyInt)
var op = getSysMagic(c.g, x.info, "setLen", mSetLengthSeq)
op = instantiateGeneric(c, op, t, t)
result = newTree(nkCall, newSymNode(op, x.info), x, lenCall)
@@ -623,38 +595,14 @@ proc checkSelfAssignment(c: var TLiftCtx; t: PType; body, x, y: PNode) =
newTreeIT(nkAddr, c.info, makePtrType(c.fn, x.typ, c.idgen), x),
newTreeIT(nkAddr, c.info, makePtrType(c.fn, y.typ, c.idgen), y)
)
cond.typ = getSysType(c.g, c.info, tyBool)
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 +611,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 +658,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 +679,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:
@@ -784,73 +715,28 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
dest[] = source
decRef tmp
For YRC the write barrier is more complicated still and must be:
let tmp = dest
# assignment must come first so that the collector sees the most-recent graph:
atomic: dest[] = source
# Then teach the cycle collector about the changes edge (these use locks, see yrc.nim):
incRef source
decRef tmp
This is implemented as a single runtime call (nimAsgnYrc / nimSinkYrc).
]#
var actions = newNodeI(nkStmtList, c.info)
let elemType = t.elementType
createTypeBoundOps(c.g, c.c, elemType, c.info, c.idgen)
let isCyclic = c.g.config.selectedGC == gcOrc and types.canFormAcycle(c.g, elemType)
# 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):
let desc =
if isFinal(elemType):
let ti = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
ti.typ = getSysType(c.g, c.info, tyPointer)
ti
else:
newNodeIT(nkNilLit, c.info, getSysType(c.g, c.info, tyPointer))
case c.kind
of attachedAsgn, attachedDup:
body.add callCodegenProc(c.g, "nimAsgnYrc", c.info, genAddr(c, x), y, desc)
return
of attachedSink:
body.add callCodegenProc(c.g, "nimSinkYrc", c.info, genAddr(c, x), y, desc)
return
else: discard # fall through for destructor, trace, wasMoved
let isCyclic = c.g.config.selectedGC in {gcOrc, gcYrc} and types.canFormAcycle(c.g, elemType)
let isInheritableAcyclicRef = c.g.config.selectedGC in {gcOrc, gcYrc} and
let isInheritableAcyclicRef = c.g.config.selectedGC == gcOrc and
(not isPureObject(elemType)) and
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)
alignOf.typ() = getSysType(c.g, c.info, tyInt)
actions.add callCodegenProc(c.g, "nimRawDispose", c.info, tmp, alignOf)
else:
addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(tmp, nkDerefExpr))
@@ -858,9 +744,9 @@ 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)
typInfo.typ() = getSysType(c.g, c.info, tyPointer)
cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicStatic", c.info, tmp, typInfo)
else:
cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicDyn", c.info, tmp)
@@ -868,7 +754,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
cond = callCodegenProc(c.g, "nimDecRefIsLastDyn", c.info, x)
else:
cond = callCodegenProc(c.g, "nimDecRefIsLast", c.info, x)
cond.typ = getSysType(c.g, x.info, tyBool)
cond.typ() = getSysType(c.g, x.info, tyBool)
case c.kind
of attachedSink:
@@ -893,9 +779,9 @@ 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)
typInfo.typ() = getSysType(c.g, c.info, tyPointer)
body.add callCodegenProc(c.g, "nimTraceRef", c.info, genAddrOf(x, c.idgen), typInfo, y)
else:
# If the ref is polymorphic we have to account for this
@@ -916,28 +802,9 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
## Closures are really like refs except they always use a virtual destructor
## and we need to do the refcounting only on the ref field which we call 'xenv':
let xenv = genBuiltin(c, mAccessEnv, "accessEnv", x)
xenv.typ = getSysType(c.g, c.info, tyPointer)
xenv.typ() = getSysType(c.g, c.info, tyPointer)
# Closures are (fnPtr, env) pairs. nimAsgnYrc/nimSinkYrc handle the env pointer
# (atomic store + buffered inc/dec). We also need newAsgnStmt to copy the fnPtr.
if c.g.config.selectedGC == gcYrc:
let nilDesc = newNodeIT(nkNilLit, c.info, getSysType(c.g, c.info, tyPointer))
let yenv = genBuiltin(c, mAccessEnv, "accessEnv", y)
yenv.typ = getSysType(c.g, c.info, tyPointer)
case c.kind
of attachedAsgn, attachedDup:
# nimAsgnYrc: save old env, atomic store new env, inc new env, dec old env
body.add callCodegenProc(c.g, "nimAsgnYrc", c.info, genAddr(c, xenv), yenv, nilDesc)
# Raw struct copy to also update the function pointer (env write is redundant but benign)
body.add newAsgnStmt(x, y)
return
of attachedSink:
body.add callCodegenProc(c.g, "nimSinkYrc", c.info, genAddr(c, xenv), yenv, nilDesc)
body.add newAsgnStmt(x, y)
return
else: discard # fall through for destructor, trace, wasMoved
let isCyclic = c.g.config.selectedGC in {gcOrc, gcYrc}
let isCyclic = c.g.config.selectedGC == gcOrc
let tmp =
if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}:
declareTempOf(c, body, xenv)
@@ -951,7 +818,7 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
if isCyclic: "nimDecRefIsLastCyclicDyn"
else: "nimDecRefIsLast"
let cond = callCodegenProc(c.g, decRefProc, c.info, tmp)
cond.typ = getSysType(c.g, x.info, tyBool)
cond.typ() = getSysType(c.g, x.info, tyBool)
case c.kind
of attachedSink:
@@ -963,18 +830,19 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
body.add newAsgnStmt(x, y)
of attachedAsgn:
let yenv = genBuiltin(c, mAccessEnv, "accessEnv", y)
yenv.typ = getSysType(c.g, c.info, tyPointer)
yenv.typ() = getSysType(c.g, c.info, tyPointer)
if isCyclic:
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRefCyclic", c.info, yenv, getCycleParam(c)))
body.add newAsgnStmt(x, y)
body.add genIf(c, cond, actions)
else:
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv))
body.add genIf(c, cond, actions)
body.add newAsgnStmt(x, y)
of attachedDup:
let yenv = genBuiltin(c, mAccessEnv, "accessEnv", y)
yenv.typ = getSysType(c.g, c.info, tyPointer)
yenv.typ() = getSysType(c.g, c.info, tyPointer)
if isCyclic:
body.add newAsgnStmt(x, y)
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRefCyclic", c.info, yenv, getCycleParam(c)))
@@ -1026,7 +894,7 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
if isFinal(elemType):
addDestructorCall(c, elemType, actions, genDeref(x, nkDerefExpr))
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
alignOf.typ = getSysType(c.g, c.info, tyInt)
alignOf.typ() = getSysType(c.g, c.info, tyInt)
actions.add callCodegenProc(c.g, "nimRawDispose", c.info, x, alignOf)
else:
addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(x, nkDerefExpr))
@@ -1049,14 +917,14 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# a big problem is that we don't know the environment's type here, so we
# have to go through some indirection; we delegate this to the codegen:
let call = newNodeI(nkCall, c.info, 2)
call.typ = t
call.typ() = t
call[0] = newSymNode(createMagic(c.g, c.idgen, "deepCopy", mDeepCopy))
call[1] = y
body.add newAsgnStmt(x, call)
elif (optOwnedRefs in c.g.config.globalOptions and
optRefCheck in c.g.config.options) or c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
optRefCheck in c.g.config.options) or c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
let xx = genBuiltin(c, mAccessEnv, "accessEnv", x)
xx.typ = getSysType(c.g, c.info, tyPointer)
xx.typ() = getSysType(c.g, c.info, tyPointer)
case c.kind
of attachedSink:
# we 'nil' y out afterwards so we *need* to take over its reference
@@ -1065,13 +933,13 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
body.add newAsgnStmt(x, y)
of attachedAsgn:
let yy = genBuiltin(c, mAccessEnv, "accessEnv", y)
yy.typ = getSysType(c.g, c.info, tyPointer)
yy.typ() = getSysType(c.g, c.info, tyPointer)
body.add genIf(c, yy, callCodegenProc(c.g, "nimIncRef", c.info, yy))
body.add genIf(c, xx, callCodegenProc(c.g, "nimDecWeakRef", c.info, xx))
body.add newAsgnStmt(x, y)
of attachedDup:
let yy = genBuiltin(c, mAccessEnv, "accessEnv", y)
yy.typ = getSysType(c.g, c.info, tyPointer)
yy.typ() = getSysType(c.g, c.info, tyPointer)
body.add newAsgnStmt(x, y)
body.add genIf(c, yy, callCodegenProc(c.g, "nimIncRef", c.info, yy))
of attachedDestructor:
@@ -1086,7 +954,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
let xx = genBuiltin(c, mAccessEnv, "accessEnv", x)
xx.typ = getSysType(c.g, c.info, tyPointer)
xx.typ() = getSysType(c.g, c.info, tyPointer)
var actions = newNodeI(nkStmtList, c.info)
#discard addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(xx))
actions.add callCodegenProc(c.g, "nimDestroyAndDispose", c.info, xx)
@@ -1105,20 +973,11 @@ 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}:
if c.g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
atomicRefOp(c, t, body, x, y)
elif (optOwnedRefs in c.g.config.globalOptions and
optRefCheck in c.g.config.options):
@@ -1127,7 +986,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
defaultOp(c, t, body, x, y)
of tyProc:
if t.callConv == ccClosure:
if c.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
if c.g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
atomicClosureOp(c, t, body, x, y)
else:
closureOp(c, t, body, x, y)
@@ -1185,15 +1044,20 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
else:
fillBodyObjT(c, t, body, x, y)
elif tfUnion in t.flags: # bug #25236
defaultOp(c, t, body, x, y)
else:
if not considerInferDupFromCopy(c, t, body, x, y):
if c.kind == attachedDup:
var op2 = getAttachedOp(c.g, t, attachedAsgn)
if op2 != nil and sfOverridden in op2.flags:
#markUsed(c.g.config, c.info, op, c.g.usageSym)
onUse(c.info, op2)
body.add newHookCall(c, t.assignment, x, y)
else:
fillBodyObjT(c, t, body, x, y)
else:
fillBodyObjT(c, t, body, x, y)
of tyDistinct:
if not considerUserDefinedOp(c, t, body, x, y):
if not considerInferDupFromCopy(c, t, body, x, y):
fillBody(c, t.elementType, body, x, y)
fillBody(c, t.elementType, body, x, y)
of tyTuple:
fillBodyTup(c, t, body, x, y)
of tyVarargs, tyOpenArray:
@@ -1233,14 +1097,14 @@ 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)
result.typ.addParam src
if g.config.selectedGC in {gcOrc, gcYrc} and
if g.config.selectedGC == gcOrc and
cyclicType(g, typ.skipTypes(abstractInst)):
let cycleParam = newSym(skParam, getIdent(g.cache, "cyclic"),
idgen, result, info)
@@ -1254,8 +1118,8 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
n[bodyPos] = newNodeI(nkStmtList, info)
n[resultPos] = newSymNode(res)
result.ast = n
incl result.flagsImpl, {sfFromGeneric, sfGeneratedOp}
setHookDisamb(g, result, AttachedOpToStr[kind], typ)
incl result.flags, sfFromGeneric
incl result.flags, sfGeneratedOp
proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp;
info: TLineInfo; idgen: IdGenerator; isDiscriminant = false): PSym =
@@ -1268,7 +1132,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
let src = newSym(skParam, getIdent(g.cache, if kind == attachedTrace: "env" else: "src"),
idgen, result, info)
if kind == attachedDestructor and g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc} and
if kind == attachedDestructor and g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
((g.config.isDefined("nimPreviewNonVarDestructor") and not isDiscriminant) or (typ.kind in {tyRef, tyString, tySequence})):
dest.typ = typ
else:
@@ -1279,13 +1143,12 @@ 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
if kind == attachedAsgn and g.config.selectedGC in {gcOrc, gcYrc} and
if kind == attachedAsgn and g.config.selectedGC == gcOrc and
cyclicType(g, typ.skipTypes(abstractInst)):
let cycleParam = newSym(skParam, getIdent(g.cache, "cyclic"),
idgen, result, info)
@@ -1298,37 +1161,23 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
n[paramsPos] = result.typ.n
n[bodyPos] = newNodeI(nkStmtList, info)
result.ast = n
incl result.flagsImpl, sfFromGeneric
incl result.flagsImpl, sfGeneratedOp
incl result.flags, sfFromGeneric
incl result.flags, sfGeneratedOp
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)
incl result.flags, sfNoSideEffect
incl result.typ.flags, tfNoSideEffect
proc genTypeFieldCopy(c: var TLiftCtx; t: PType; body, x, y: PNode) =
let xx = genBuiltin(c, mAccessTypeField, "accessTypeField", x)
let yy = genBuiltin(c, mAccessTypeField, "accessTypeField", y)
xx.typ = getSysType(c.g, c.info, tyPointer)
yy.typ = xx.typ
xx.typ() = getSysType(c.g, c.info, tyPointer)
yy.typ() = xx.typ
body.add newAsgnStmt(xx, yy)
proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
info: TLineInfo; idgen: IdGenerator): PSym =
if typ.kind == tyDistinct:
# For =dup, if the distinct type has a user-defined =copy, don't delegate
# to the base type. Instead fall through to the normal produceSym logic
# so that fillBody -> considerInferDupFromCopy can synthesize =dup from =copy.
if kind == attachedDup:
let copyOp = getAttachedOp(g, typ, attachedAsgn)
if copyOp != nil and sfOverridden in copyOp.flags:
discard "fall through to normal produceSym logic"
else:
return produceSymDistinctType(g, c, typ, kind, info, idgen)
else:
return produceSymDistinctType(g, c, typ, kind, info, idgen)
return produceSymDistinctType(g, c, typ, kind, info, idgen)
result = getAttachedOp(g, typ, kind)
if result == nil:
@@ -1349,48 +1198,35 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
if kind == attachedSink and destructorOverridden(g, typ):
## compiler can use a combination of `=destroy` and memCopy for sink op
ensureMutable dest
dest.flagsImpl.incl sfCursor
dest.flags.incl sfCursor
let op = getAttachedOp(g, typ, attachedDestructor)
result.ast[bodyPos].add newOpCall(a, op, if op.typ.firstParamType.kind == tyVar: d[0] else: d)
result.ast[bodyPos].add newAsgnStmt(d, src)
else:
var tk: TTypeKind
var skipped: PType = nil
if g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcHooks, gcAtomicArc}:
skipped = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink})
tk = skipped.kind
if g.config.selectedGC in {gcArc, gcOrc, gcHooks, gcAtomicArc}:
tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind
else:
tk = tyNone # no special casing for strings and seqs
case tk
of tySequence:
let needsYrcLock = g.config.selectedGC == gcYrc and
kind in {attachedDestructor, attachedSink, attachedAsgn, attachedDeepCopy, attachedDup} and
types.canFormAcycle(g, skipped.elementType)
# YRC: topology-changing seq ops must hold the mutator (read) lock
if needsYrcLock:
result.ast[bodyPos].add callCodegenProc(g, "acquireMutatorLock", info)
fillSeqOp(a, typ, result.ast[bodyPos], d, src)
if needsYrcLock:
result.ast[bodyPos].add callCodegenProc(g, "releaseMutatorLock", info)
of tyString:
fillStrOp(a, typ, result.ast[bodyPos], d, src)
else:
fillBody(a, typ, result.ast[bodyPos], d, src)
if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not isObjLackingTypeField(skipped):
if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not isObjLackingTypeField(typ):
# bug #19205: Do not forget to also copy the hidden type field:
genTypeFieldCopy(a, typ, result.ast[bodyPos], d, src)
if not a.canRaise:
ensureMutable result
incl result.flagsImpl, sfNeverRaises
incl result.flags, sfNeverRaises
result.ast[pragmasPos] = newNodeI(nkPragma, info)
result.ast[pragmasPos].add newTree(nkExprColonExpr,
newIdentNode(g.cache.getIdent("raises"), info), newNodeI(nkBracket, info))
if kind == attachedDestructor:
ensureMutable result
incl result.optionsImpl, optQuirky
incl result.options, optQuirky
completePartialOp(g, idgen.module, typ, kind, result)
@@ -1399,7 +1235,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
@@ -1416,9 +1251,7 @@ proc produceDestructorForDiscriminator*(g: ModuleGraph; typ: PType; field: PSym,
result.ast[bodyPos].add v
let placeHolder = newNodeIT(nkSym, info, getSysType(g, info, tyPointer))
fillBody(a, typ, result.ast[bodyPos], d, placeHolder)
if not a.canRaise:
ensureMutable result
incl result.flagsImpl, sfNeverRaises
if not a.canRaise: incl result.flags, sfNeverRaises
template liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) =
@@ -1462,13 +1295,11 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
## to ensure we lift assignment, destructors and moves properly.
## The later 'injectdestructors' pass depends on it.
if orig == nil or {tfCheckedForDestructor, tfHasMeta} * orig.flags != {}: return
# IC: review this solution again later
incl orig.flagsImpl, tfCheckedForDestructor
incl orig.flags, tfCheckedForDestructor
# for user defined generic destructors:
let origRoot = genericRoot(orig)
if origRoot != nil:
# IC: review this solution again later
incl origRoot.flagsImpl, tfGenericHasDestructor
incl origRoot.flags, tfGenericHasDestructor
let skipped = orig.skipTypes({tyGenericInst, tyAlias, tySink})
if isEmptyContainer(skipped) or skipped.kind == tyStatic: return
@@ -1488,13 +1319,13 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
# we do not generate '=trace' procs if we
# have the cycle detection disabled, saves code size.
let lastAttached = if g.config.selectedGC in {gcOrc, gcYrc}: attachedTrace
let lastAttached = if g.config.selectedGC == gcOrc: attachedTrace
else: attachedSink
# bug #15122: We need to produce all prototypes before entering the
# mind boggling recursion. Hacks like these imply we should rewrite
# this module.
var generics = default(array[attachedWasMoved..attachedTrace, bool])
var generics: array[attachedWasMoved..attachedTrace, bool] = default(array[attachedWasMoved..attachedTrace, bool])
for k in attachedWasMoved..lastAttached:
generics[k] = getAttachedOp(g, canon, k) != nil
if not generics[k]:
@@ -1513,6 +1344,5 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
if not isTrivial(getAttachedOp(g, orig, attachedDestructor)):
#or not isTrivial(orig.assignment) or
# not isTrivial(orig.sink):
# IC: review this solution again later
orig.flagsImpl.incl tfHasAsgn
orig.flags.incl tfHasAsgn
# ^ XXX Breaks IC!

View File

@@ -32,12 +32,12 @@ proc interestingVar(s: PSym): bool {.inline.} =
proc lookupOrAdd(c: var Ctx; s: PSym; info: TLineInfo): PNode =
let field = addUniqueField(c.objType, s, c.cache, c.idgen)
var deref = newNodeI(nkHiddenDeref, info)
deref.typ = c.objType
deref.typ() = c.objType
deref.add(newSymNode(c.partialParam, info))
result = newNodeI(nkDotExpr, info)
result.add(deref)
result.add(newSymNode(field))
result.typ = field.typ
result.typ() = field.typ
proc liftLocals(n: PNode; i: int; c: var Ctx) =
let it = n[i]

View File

@@ -93,14 +93,10 @@ type
warnBareExcept = "BareExcept",
warnImplicitDefaultValue = "ImplicitDefaultValue",
warnIgnoredSymbolInjection = "IgnoredSymbolInjection",
warnStdPrefix = "StdPrefix",
warnUnknownNotes = "UnknownNotes",
warnLongLiterals = "LongLiterals",
warnStdPrefix = "StdPrefix"
warnUnknownNotes = "UnknownNotes"
warnUser = "User",
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
warnImplicitRangeConversion = "ImplicitRangeConversion",
warnSystemRangeConversion = "SystemRangeConversion",
warnInvalidCmpOp = "InvalidCmpOp",
# hints
hintSuccess = "Success", hintSuccessX = "SuccessX",
hintCC = "CC",
@@ -206,12 +202,8 @@ const
warnIgnoredSymbolInjection: "$1",
warnStdPrefix: "$1 needs the 'std' prefix",
warnUnknownNotes: "$1",
warnLongLiterals: "$1",
warnUser: "$1",
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",
@@ -266,9 +258,9 @@ type
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[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix}
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,
@@ -281,10 +273,6 @@ const
errFloatToString* = "cannot convert '$1' to '$2'"
type
FileInfoKind* = enum
fikSource, ## A real source file path
fikNifModule ## A NIF module suffix (not a real path)
TFileInfo* = object
fullPath*: AbsoluteFile # This is a canonical full filesystem path
projPath*: RelativeFile # This is relative to the project's root
@@ -303,7 +291,6 @@ type
# for 'nimsuggest'
hash*: string # the checksum of the file
dirty*: bool # for 'nimpretty' like tooling
kind*: FileInfoKind # distinguishes real files from NIF suffixes
when defined(nimpretty):
fullContent*: string
FileIndex* = distinct int32

View File

@@ -95,7 +95,7 @@ proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
template styleCheckDef*(ctx: PContext; info: TLineInfo; sym: PSym; k: TSymKind) =
## Check symbol definitions adhere to NEP1 style rules.
if optStyleCheck in ctx.config.options and # ignore if styleChecks are off
{optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # check only if hint/error/warning is enabled
{optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # check only if hint/error is enabled
hintName in ctx.config.notes and # ignore if name checks are not requested
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)) and # ignore foreign packages
optStyleUsages notin ctx.config.globalOptions and # ignore if requested to only check name usage
@@ -136,7 +136,7 @@ proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) =
template styleCheckUse*(ctx: PContext; info: TLineInfo; sym: PSym) =
## Check symbol uses match their definition's style.
if {optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
hintName in ctx.config.notes and # ignore if name checks are not requested
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)) and # ignore foreign packages
sym.kind != skTemp and # ignore temporary variables created by the compiler
@@ -152,7 +152,7 @@ proc checkPragmaUseImpl(conf: ConfigRef; info: TLineInfo; w: TSpecialWord; pragm
template checkPragmaUse*(ctx: PContext; info: TLineInfo; w: TSpecialWord; pragmaName: string, sym: PSym) =
## Check builtin pragma uses match their definition's style.
## Note: This only applies to builtin pragmas, not user pragmas.
if {optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
hintName in ctx.config.notes and # ignore if name checks are not requested
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)): # ignore foreign packages
checkPragmaUseImpl(ctx.config, info, w, pragmaName)

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

@@ -311,7 +311,7 @@ proc errorSym*(c: PContext, ident: PIdent, info: TLineInfo): PSym =
## creates an error symbol to avoid cascading errors (for IDE support)
result = newSym(skError, ident, c.idgen, getCurrOwner(c), info, {})
result.typ = errorType(c)
incl(result.flagsImpl, sfDiscardable)
incl(result.flags, sfDiscardable)
# pretend it's from the top level scope to prevent cascading errors:
if c.config.cmd != cmdInteractive and c.compilesContextId == 0:
c.moduleScope.addSym(result)
@@ -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])
@@ -415,6 +412,8 @@ proc addDecl*(c: PContext, sym: PSym) {.inline.} =
proc addPrelimDecl*(c: PContext, sym: PSym) =
discard c.currentScope.addUniqueSym(sym)
from ic / ic import addHidden
proc addInterfaceDeclAux(c: PContext, sym: PSym) =
## adds symbol to the module for either private or public access.
if sfExported in sym.flags:
@@ -423,6 +422,8 @@ proc addInterfaceDeclAux(c: PContext, sym: PSym) =
else: internalError(c.config, sym.info, "addInterfaceDeclAux")
elif sym.kind in ExportableSymKinds and c.module != nil and isTopLevelInsideDeclaration(c, sym):
strTableAdd(semtabAll(c.graph, c.module), sym)
if c.config.symbolFiles != disabledSf:
addHidden(c.encoder, c.packedRepr, sym)
proc addInterfaceDeclAt*(c: PContext, scope: PScope, sym: PSym) =
## adds a symbol on the scope and the interface if appropriate
@@ -462,15 +463,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

@@ -82,7 +82,7 @@ proc lowerTupleUnpacking*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: P
var temp = newSym(skTemp, getIdent(g.cache, genPrefix), idgen,
owner, value.info, g.config.options)
temp.typ = skipTypes(value.typ, abstractInst)
incl(temp.flagsImpl, sfFromGeneric)
incl(temp.flags, sfFromGeneric)
tempAsNode = newSymNode(temp)
var v = newNodeI(nkVarSection, value.info)
@@ -103,7 +103,7 @@ proc evalOnce*(g: ModuleGraph; value: PNode; idgen: IdGenerator; owner: PSym): P
var temp = newSym(skTemp, getIdent(g.cache, genPrefix), idgen,
owner, value.info, g.config.options)
temp.typ = skipTypes(value.typ, abstractInst)
incl(temp.flagsImpl, sfFromGeneric)
incl(temp.flags, sfFromGeneric)
var v = newNodeI(nkLetSection, value.info)
let tempAsNode = newSymNode(temp)
@@ -127,8 +127,8 @@ proc lowerSwap*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PNod
# note: cannot use 'skTemp' here cause we really need the copy for the VM :-(
var temp = newSym(skVar, getIdent(g.cache, genPrefix), idgen, owner, n.info, owner.options)
temp.typ = n[1].typ
incl(temp.flagsImpl, sfFromGeneric)
incl(temp.flagsImpl, sfGenSym)
incl(temp.flags, sfFromGeneric)
incl(temp.flags, sfGenSym)
var v = newNodeI(nkVarSection, n.info)
let tempAsNode = newSymNode(temp)
@@ -147,13 +147,13 @@ proc createObj*(g: ModuleGraph; idgen: IdGenerator; owner: PSym, info: TLineInfo
result = newType(tyObject, idgen, owner)
if final:
rawAddSon(result, nil)
incl result, tfFinal
incl result.flags, tfFinal
else:
rawAddSon(result, getCompilerProc(g, "RootObj").typ)
result.n = newNodeI(nkRecList, info)
let s = newSym(skType, getIdent(g.cache, "Env_" & toFilename(g.config, info) & "_" & $owner.name.s),
idgen, owner, info, owner.options)
incl s.flagsImpl, sfAnon
incl s.flags, sfAnon
s.typ = result
result.sym = s
@@ -174,12 +174,12 @@ proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
# returns a[].field as a node
assert field.kind == skField
var deref = newNodeI(nkHiddenDeref, info)
deref.typ = a.typ.skipTypes(abstractInst)[0]
deref.typ() = a.typ.skipTypes(abstractInst)[0]
deref.add a
result = newNodeI(nkDotExpr, info)
result.add deref
result.add newSymNode(field)
result.typ = field.typ
result.typ() = field.typ
proc rawDirectAccess*(obj, field: PSym): PNode =
# returns a.field as a node
@@ -187,7 +187,7 @@ proc rawDirectAccess*(obj, field: PSym): PNode =
result = newNodeI(nkDotExpr, field.info)
result.add newSymNode(obj)
result.add newSymNode(field)
result.typ = field.typ
result.typ() = field.typ
proc lookupInRecord(n: PNode, id: ItemId): PSym =
result = nil
@@ -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
@@ -305,12 +250,12 @@ proc newDotExpr*(obj, b: PSym): PNode =
assert field != nil, b.name.s
result.add newSymNode(obj)
result.add newSymNode(field)
result.typ = field.typ
result.typ() = field.typ
proc indirectAccess*(a: PNode, b: ItemId, info: TLineInfo): PNode =
# returns a[].b as a node
var deref = newNodeI(nkHiddenDeref, info)
deref.typ = a.typ.skipTypes(abstractInst).elementType
deref.typ() = a.typ.skipTypes(abstractInst).elementType
var t = deref.typ.skipTypes(abstractInst)
var field: PSym
while true:
@@ -328,12 +273,12 @@ proc indirectAccess*(a: PNode, b: ItemId, info: TLineInfo): PNode =
result = newNodeI(nkDotExpr, info)
result.add deref
result.add newSymNode(field)
result.typ = field.typ
result.typ() = field.typ
proc indirectAccess*(a: PNode, b: string, info: TLineInfo; cache: IdentCache): PNode =
# returns a[].b as a node
var deref = newNodeI(nkHiddenDeref, info)
deref.typ = a.typ.skipTypes(abstractInst).elementType
deref.typ() = a.typ.skipTypes(abstractInst).elementType
var t = deref.typ.skipTypes(abstractInst)
var field: PSym
let bb = getIdent(cache, b)
@@ -352,7 +297,7 @@ proc indirectAccess*(a: PNode, b: string, info: TLineInfo; cache: IdentCache): P
result = newNodeI(nkDotExpr, info)
result.add deref
result.add newSymNode(field)
result.typ = field.typ
result.typ() = field.typ
proc getFieldFromObj*(t: PType; v: PSym): PSym =
assert v.kind != skField
@@ -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)
@@ -385,7 +320,7 @@ proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode =
proc genAddrOf*(n: PNode; idgen: IdGenerator; typeKind = tyPtr): PNode =
result = newNodeI(nkAddr, n.info, 1)
result[0] = n
result.typ = newType(typeKind, idgen, n.typ.owner)
result.typ() = newType(typeKind, idgen, n.typ.owner)
result.typ.rawAddSon(n.typ)
proc genDeref*(n: PNode; k = nkHiddenDeref): PNode =
@@ -409,18 +344,18 @@ proc callCodegenProc*(g: ModuleGraph; name: string;
if optionalArgs != nil:
for i in 1..<optionalArgs.len-2:
result.add optionalArgs[i]
result.typ = sym.typ.returnType
result.typ() = sym.typ.returnType
proc newIntLit*(g: ModuleGraph; info: TLineInfo; value: BiggestInt): PNode =
result = nkIntLit.newIntNode(value)
result.typ = getSysType(g, info, tyInt)
result.typ() = getSysType(g, info, tyInt)
proc genHigh*(g: ModuleGraph; n: PNode): PNode =
if skipTypes(n.typ, abstractVar).kind == tyArray:
result = newIntLit(g, n.info, toInt64(lastOrd(g.config, skipTypes(n.typ, abstractVar))))
else:
result = newNodeI(nkCall, n.info, 2)
result.typ = getSysType(g, n.info, tyInt)
result.typ() = getSysType(g, n.info, tyInt)
result[0] = newSymNode(getSysMagic(g, n.info, "high", mHigh))
result[1] = n
@@ -429,7 +364,7 @@ proc genLen*(g: ModuleGraph; n: PNode): PNode =
result = newIntLit(g, n.info, toInt64(lastOrd(g.config, skipTypes(n.typ, abstractVar)) + 1))
else:
result = newNodeI(nkCall, n.info, 2)
result.typ = getSysType(g, n.info, tyInt)
result.typ() = getSysType(g, n.info, tyInt)
result[0] = newSymNode(getSysMagic(g, n.info, "len", mLengthSeq))
result[1] = n

View File

@@ -10,8 +10,8 @@
# Built-in types and compilerprocs are registered here.
import
ast, msgs, platform, idents,
modulegraphs, lineinfos, types
ast, astalgo, msgs, platform, idents,
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)
@@ -166,4 +166,4 @@ proc makeAddr*(n: PNode; idgen: IdGenerator): PNode =
result = n
else:
result = newTree(nkHiddenAddr, n)
result.typ = makePtrType(n.typ.skipTypes({tySink}), idgen)
result.typ() = makePtrType(n.typ.skipTypes({tySink}), idgen)

View File

@@ -26,16 +26,11 @@ import
when defined(nimPreviewSlimSystem):
import std/[syncio, assertions]
import ic / [cbackend, integrity, navigator, ic]
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
@@ -100,6 +95,14 @@ proc commandCheck(graph: ModuleGraph) =
setPipeLinePass(graph, SemPass)
compilePipelineProject(graph)
if conf.symbolFiles != disabledSf:
case conf.ideCmd
of ideDef: navDefinition(graph)
of ideUse: navUsages(graph)
of ideDus: navDefusages(graph)
else: discard
writeRodFiles(graph)
when not defined(leanCompiler):
proc commandDoc2(graph: ModuleGraph; ext: string) =
handleDocOutputOptions graph.config
@@ -130,22 +133,6 @@ proc commandCompileToNif(graph: ModuleGraph) =
setPipeLinePass(graph, NifgenPass)
compilePipelineProject(graph)
proc commandNifC(graph: ModuleGraph) =
## Generate C code from precompiled NIF files.
## This is the new IC approach: compile modules to NIF first with `nim m`,
## then generate C code from the entry.nif file with whole-program DCE.
when not defined(nimKochBootstrap):
let conf = graph.config
extccomp.initVars(conf)
if not extccomp.ccHasSaneOverflow(conf):
conf.symbols.defineSymbol("nimEmulateOverflowChecks")
# Use the NIF backend to generate C code
nifbackend.generateCode(graph, conf.projectMainIdx)
else:
rawMessage(graph.config, errGenerated, "NIF backend not available during bootstrap build")
proc commandCompileToC(graph: ModuleGraph) =
let conf = graph.config
extccomp.initVars(conf)
@@ -166,7 +153,15 @@ proc commandCompileToC(graph: ModuleGraph) =
compilePipelineProject(graph)
if graph.config.errorCounter > 0:
return # issue #9933
cgenWriteModules(graph.backend, conf)
if conf.symbolFiles == disabledSf:
cgenWriteModules(graph.backend, conf)
else:
if isDefined(conf, "nimIcIntegrityChecks"):
checkIntegrity(graph)
generateCode(graph)
# graph.backend can be nil under IC when nothing changed at all:
if graph.backend != nil:
cgenWriteModules(graph.backend, conf)
if conf.cmd != cmdTcc and graph.backend != nil:
extccomp.callCCompiler(conf)
# for now we do not support writing out a .json file with the build instructions when HCR is on
@@ -205,11 +200,31 @@ proc commandInteractive(graph: ModuleGraph) =
discard graph.compilePipelineModule(fileInfoIdx(graph.config, graph.config.projectFull), {})
else:
var m = graph.makeStdinModule()
incl(m, sfMainModule)
incl(m.flags, sfMainModule)
var idgen = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0)
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)
proc commandView(graph: ModuleGraph) =
let f = toAbsolute(mainCommandArg(graph.config), AbsoluteDir getCurrentDir()).addFileExt(RodExt)
rodViewer(f, graph.config, graph.cache)
const
PrintRopeCacheStats = false
@@ -270,28 +285,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)
@@ -329,6 +322,8 @@ proc mainCommand*(graph: ModuleGraph) =
case conf.cmd
of cmdBackends:
compileToBackend()
when BenchIC:
echoTimes graph.packed
of cmdTcc:
when hasTinyCBackend:
extccomp.setCC(conf, "tcc", unknownLineInfo)
@@ -424,57 +419,17 @@ 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)
of cmdNifC:
setUseIc(true)
excl conf.features, Feature.vtables
# Generate C code from NIF files
wantMainModule(conf)
setOutFile(conf)
commandNifC(graph)
of cmdIc:
# 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)
graph.config.symbolFiles = v2Sf
setUseIc(graph.config.symbolFiles != disabledSf)
commandCheck(graph)
of cmdParse:
wantMainModule(conf)
discard parseFile(conf.projectMainIdx, cache, conf)
of cmdRod:
wantMainModule(conf)
commandView(graph)
#msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!")
of cmdInteractive: commandInteractive(graph)
of cmdNimscript:
if conf.projectIsCmd or conf.projectIsStdin: discard
@@ -485,17 +440,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 #

File diff suppressed because it is too large Load Diff

View File

@@ -109,11 +109,9 @@ proc mangleModuleName*(conf: ConfigRef; path: AbsoluteFile): string =
of FromSearchPath: "@p"
of FromNimblePath: "@n"
# Note: We encode ".." specially as "@d" to avoid issues with changeFileExt
# which would misinterpret ".." as "name.ext" and strip the second part.
prefix & best.multiReplace(
{"..": "@d", $os.DirSep: "@s", $os.AltSep: "@s", "#": "@h", "@": "@@", ":": "@c"})
{$os.DirSep: "@s", $os.AltSep: "@s", "#": "@h", "@": "@@", ":": "@c"})
proc demangleModuleName*(path: string): string =
## Demangle a relative module path.
result = path.multiReplace({"@@": "@", "@d": "..", "@h": "#", "@s": "/", "@m": "", "@p": "", "@n": "", "@c": ":"})
result = path.multiReplace({"@@": "@", "@h": "#", "@s": "/", "@m": "", "@p": "", "@n": "", "@c": ":"})

View File

@@ -32,9 +32,9 @@ 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(kind: skModule, itemId: ItemId(module: int32(fileIdx), item: 0'i32),
name: getModuleIdent(graph, filename),
infoImpl: newLineInfo(fileIdx, 1, 1))
info: newLineInfo(fileIdx, 1, 1))
if not isNimIdentifier(result.name.s):
rawMessage(graph.config, errGenerated, "invalid module name: '" & result.name.s &
"'; a module name must be a valid Nim identifier.")

View File

@@ -24,8 +24,13 @@ 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
# xxx one edge case not yet handled is when `printf` is called at CT with `compiletimeFFI`.
let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr
let stdOrrKind = toStdOrrKind(stdOrr)
if stdOrrKind in conf.lastMsgWasDot:
@@ -47,7 +52,7 @@ proc makeCString*(s: string): Rope =
result = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
result.add("\"")
for i in 0..<s.len:
# line wrapping of string literals in cgen'd code was a bad idea, e.g. causes: bug #16265
# line wrapping of string litterals in cgen'd code was a bad idea, e.g. causes: bug #16265
# It also makes reading c sources or grepping harder, for zero benefit.
# const MaxLineLength = 64
# if (i + 1) mod MaxLineLength == 0:
@@ -55,12 +60,12 @@ proc makeCString*(s: string): Rope =
toCChar(s[i], result)
result.add('\"')
proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile; kind = fikSource): TFileInfo =
proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo =
result = TFileInfo(fullPath: fullPath, projPath: projPath,
shortName: fullPath.extractFilename,
quotedFullName: fullPath.string.makeCString,
lines: @[],
kind: kind)
lines: @[]
)
result.quotedName = result.shortName.makeCString
when defined(nimpretty):
if not result.fullPath.isEmpty:
@@ -79,8 +84,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,42 +126,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)
proc registerNifSuffix*(conf: ConfigRef; suffix: string; isKnownFile: var bool): FileIndex =
result = conf.m.filenameToIndexTbl.getOrDefault(suffix, InvalidFileIdx)
if result == InvalidFileIdx:
isKnownFile = false
result = conf.m.fileInfos.len.FileIndex
conf.m.fileInfos.add(newFileInfo(AbsoluteFile suffix, RelativeFile suffix, fikNifModule))
conf.m.filenameToIndexTbl[suffix] = result
else:
isKnownFile = true
proc fileInfoKind*(conf: ConfigRef; fileIdx: FileIndex): FileInfoKind =
## Returns the kind of a FileIndex (source file or NIF module suffix).
if fileIdx.int >= 0 and fileIdx.int < conf.m.fileInfos.len:
result = conf.m.fileInfos[fileIdx.int].kind
else:
result = fikSource # Default to source for unknown indices
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), dummy)
proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
result = TLineInfo(fileIndex: fileInfoIdx)
@@ -250,7 +224,7 @@ proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile)
proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
assert fileIdx.int32 >= 0
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
conf.m.fileInfos[fileIdx.int32].hash = hash
else:
shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
@@ -258,7 +232,7 @@ proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
assert fileIdx.int32 >= 0
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
result = conf.m.fileInfos[fileIdx.int32].hash
else:
shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
@@ -348,7 +322,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 +428,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 +443,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 +474,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 +495,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 +629,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)
@@ -677,9 +648,7 @@ template internalAssert*(conf: ConfigRef, e: bool) =
template lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string, extraMsg = "") =
let m = "'$1' should be: '$2'$3" % [got, beau, extraMsg]
let msg = if optStyleError in conf.globalOptions: errGenerated
elif optStyleWarning in conf.globalOptions: warnUser
else: hintName
let msg = if optStyleError in conf.globalOptions: errGenerated else: hintName
liMessage(conf, info, msg, m, doNothing, instLoc())
proc quotedFilename*(conf: ConfigRef; fi: FileIndex): Rope =

View File

@@ -1,983 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2025 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## NIF-based C/C++ code generator backend.
##
## This module implements C code generation from precompiled NIF files.
## It traverses the module dependency graph starting from the main module
## and generates C code for all reachable modules.
##
## Usage:
## 1. Compile modules to NIF: nim m mymodule.nim
## 2. Generate C from NIF: nim nifc myproject.nim
import std/[intsets, tables, sets, os, algorithm, syncio, times, strutils]
when defined(nimPreviewSlimSystem):
import std/assertions
import ast, options, lineinfos, modulegraphs, cgendata, cgen,
pathutils, extccomp, msgs, modulepaths, idents, types, ast2nif, typekeys,
cnif, icmodnames
from cgmeth import generateIfMethodDispatchers
from transf import transformBody
from injectdestructors import injectDestructorCalls
import icprof
import ic / replayer
proc systemNifSuffix(conf: ConfigRef): string =
## The system module's NIF suffix, derived from `system.nim`'s path EXACTLY as
## the frontend derives it (deps.nim's `toPair` on `libpath/system.nim`), so the
## backend loads the very `.s.bif` the frontend wrote. It must NOT be a constant:
## `moduleSuffix` (icmodnames) now hashes the absolute path, so the system suffix
## is install-dependent (was hardcoded `sysma2dyk`, valid only for the old
## relative-path scheme where `system.nim` always relativized to `system.nim`).
moduleSuffix((conf.libpath / RelativeFile"system.nim").string,
cast[seq[string]](conf.searchPaths))
proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex;
nifFiles: var seq[string];
depFlags: set[LoadFlag] = {LoadFullAst}): seq[PrecompiledModule] =
## Traverse the module dependency graph using a stack.
## Returns all modules that need code generation, in dependency order.
##
## The main module is always loaded with its full AST (it is the codegen
## target). `depFlags` governs the rest: the whole-program backend needs every
## module's full AST (it generates code for all of them), but a per-module
## stage codegens only one target, so it loads the others interface-only
## (`depFlags = {}`) — the interface, hooks, methods and the `(replay ...)`
## directives are loaded regardless of `LoadFullAst`, and demanded bodies are
## fetched lazily from the kept-open stream, so the per-module proc-body ASTs
## (the bulk of the memory) are never materialized for non-targets.
# The main module is loaded by its SOURCE FileIndex, but its serialized
# symbols carry the module's NIF suffix. Pre-alias the suffix to the source
# index so that `registerNifSuffix` does not allocate a second FileIndex for
# the same module, which would split its codegen across two C translation
# units (top-level globals in one, procs in the other → undeclared symbols).
g.config.m.filenameToIndexTbl[cachedModuleSuffix(g.config, mainFileIdx)] = mainFileIdx
let mainModule = moduleFromNifFile(g, mainFileIdx, {LoadFullAst})
nifFiles.add toNifFilename(g.config, mainFileIdx)
var stack: seq[ModuleSuffix] = @[]
result = @[]
if mainModule.module != nil:
incl mainModule.module.flagsImpl, sfMainModule
for dep in mainModule.deps:
stack.add dep
var visited = initHashSet[string]()
while stack.len > 0:
let suffix = stack.pop()
if not visited.containsOrIncl(suffix.string):
var isKnownFile = false
let fileIdx = g.config.registerNifSuffix(suffix.string, isKnownFile)
let precomp = moduleFromNifFile(g, fileIdx, depFlags)
if precomp.module != nil:
result.add precomp
nifFiles.add toNifFilename(g.config, fileIdx)
for dep in precomp.deps:
if not visited.contains(dep.string):
stack.add dep
else:
assert false, "Recompiling module is not implemented."
if mainModule.module != nil:
result.add mainModule
proc setupNifBackendModule(g: ModuleGraph; module: PSym): BModule =
## Set up a BModule for code generation from a NIF module.
if g.backend == nil:
g.backend = cgendata.newModuleList(g)
result = cgen.newModule(BModuleList(g.backend), module, g.config, idGeneratorForBackend(module))
proc isMetaIter(t: PType, closure: RootRef): bool =
# openArray/varargs hooks are sem bookkeeping: no real flow ever demands
# them, and generating one pollutes the TU's type cache with a struct
# descriptor for what must remain a (ptr, len) parameter expansion
t.kind in tyMetaTypes + {tyTyped, tyUntyped, tyNone, tyVarargs, tyOpenArray}
proc finishModule(g: ModuleGraph; bmod: BModule) =
# Finalize the module (this adds it to modulesClosed)
# Create an empty stmt list as the init body - genInitCode in writeModule will set it up properly
let initStmt = newNode(nkStmtList)
finalCodegenActions(g, bmod, initStmt)
# NB: the method dispatchers are emitted in `emitMethodDispatchers`,
# between the module loop and this finish loop: their bodies demand the
# method definitions, which can in turn demand definitions from modules
# the backend never loaded — and a TU demand-created during the LAST
# finishModule call would miss `modulesClosed` and never be written.
proc emitMethodDispatchers(g: ModuleGraph) =
## Synthesizes the method dispatcher bodies from the replayed dispatch
## buckets (`registerLoadedMethod`) and emits their definitions into the
## main TU. Main is regenerated on every run, so a dispatcher — whose
## body enumerates the whole program's method set — can never go stale
## inside a cached TU; cross-TU callers prototype it (see genProcLvl3).
let bl = BModuleList(g.backend)
var mainMod: BModule = nil
for m in bl.mods:
if m != nil and m.module != nil and sfMainModule in m.module.flags:
mainMod = m
break
if mainMod == nil: return
generateIfMethodDispatchers(g, mainMod.idgen)
for disp in getDispatchers(g):
if not containsOrIncl(mainMod.declaredThings, disp.id):
genProcLvl3(mainMod, disp)
proc generateCodeForModule(g: ModuleGraph; precomp: PrecompiledModule) =
## Generate C code for a single module.
let moduleId = precomp.module.position
var bmod = BModuleList(g.backend).mods[moduleId]
if bmod == nil:
bmod = setupNifBackendModule(g, precomp.module)
# Apply the module's recorded C compile/link directives (passl/passc/...)
# before generating code: the link step needs them (e.g. math's -lm).
replayBackendActions(g, precomp.module, precomp.topLevel)
# Generate code for the module's top-level statements
if precomp.topLevel != nil:
cgen.genTopLevelStmt(bmod, precomp.topLevel)
# Per-module backend: emit the bodies of the routines this module OWNS, not
# only the ones its top-level happens to demand. Procs are serialized as lazy
# `(sd ...)` defs (never as `nkProcDef` statements), so `genTopLevelStmt` never
# reaches them; a routine called only from *other* modules would otherwise be
# emitted by nobody, because every module now merely prototypes its foreign
# callees instead of funnelling their bodies (see `cgen.emitsBodyInThisModule`).
# The merge stage's DCE drops whatever turns out globally dead.
if g.config.cmd == cmdNifC and g.config.icBackendStage == "cg":
let modPos = precomp.module.position
for s in moduleSymbolStubs(ast.program, FileIndex modPos):
if ownsRuntimeRoutine(s, modPos):
requestProcDef(bmod, s)
proc loadBackendModules(g: ModuleGraph; mainFileIdx: FileIndex):
tuple[modules: seq[PrecompiledModule], precompSys: PrecompiledModule,
nifFiles: seq[string]] =
## Shared by the per-module `cg` and `emit` stages: load system + the main
## module's whole import closure and set up a `BModule` for each, so every
## type/symbol resolves and `getCFile` yields the same path both stages use.
## The main module is loaded by its source index (its NIF suffix is aliased to
## it in `loadModuleDependencies`), so it gets exactly one `BModule`.
##
## Only the main module — the codegen target of the stages that use this — is
## loaded with its full AST; every other module is loaded interface-only so
## the whole program's proc bodies are not materialized into this process (that
## was ~1.8 GB for the compiler's main `cg`). The `link` stage codegens nothing
## and only needs each module's `(replay ...)` directives, which load anyway.
resetForBackend(g)
var isKnownFile = false
let systemFileIdx = registerNifSuffix(g.config, systemNifSuffix(g.config), isKnownFile)
g.config.m.systemFileIdx = systemFileIdx
var precompSys = moduleFromNifFile(g, systemFileIdx, {AlwaysLoadInterface})
g.systemModule = precompSys.module
if precompSys.module != nil:
# The precompiled-load path does not restore `sfSystemModule` (mirror of the
# `sfMainModule` re-add above). `registerReusedModuleToMain` keys on it to put
# the system module's init right after its datInit AND to emit
# `initStackBottomWith` into `mainDatInit` — so that the main thread's stack
# bottom is set before any module's init runs. Without the flag the system
# init is mis-routed into the regular `otherModsInit` bucket and
# `initStackBottomWith` is never registered, so a GC cycle during a module's
# init (under refc) scans the stack with a nil bottom and crashes.
incl precompSys.module.flagsImpl, sfSystemModule
var nifFiles: seq[string] = @[toNifFilename(g.config, systemFileIdx)]
var modules = loadModuleDependencies(g, mainFileIdx, nifFiles, depFlags = {})
# loadModuleDependencies traverses the project's import closure and stops at
# system. The whole-program backend then demand-loads system's own closure
# (locks, allocators, threads, …) during codegen; the per-module backend
# instead makes every one of those a first-class cg/emit target, so load that
# closure here too — otherwise `findTargetModule` cannot resolve their suffix.
block:
var visited = initHashSet[string]()
visited.incl systemNifSuffix(g.config)
for m in modules:
visited.incl cachedModuleSuffix(g.config, FileIndex m.module.position)
var stack: seq[ModuleSuffix] = @[]
if precompSys.module != nil:
for dep in precompSys.deps: stack.add dep
while stack.len > 0:
let suffix = stack.pop()
if not visited.containsOrIncl(suffix.string):
var isKnown = false
let fileIdx = registerNifSuffix(g.config, suffix.string, isKnown)
let precomp = moduleFromNifFile(g, fileIdx, {})
if precomp.module != nil:
modules.add precomp
nifFiles.add toNifFilename(g.config, fileIdx)
for dep in precomp.deps: stack.add dep
flushMethodReplays(g)
for m in modules:
discard setupNifBackendModule(g, m.module)
if precompSys.module != nil:
discard setupNifBackendModule(g, precompSys.module)
result = (modules, precompSys, nifFiles)
proc loadDepClosure(g: ModuleGraph; targetSuffixes: seq[string]):
tuple[modules: seq[PrecompiledModule], precompSys: PrecompiledModule,
targets: seq[PrecompiledModule]] =
## Per-module `lower`/`cg`/`emit` for a NON-main batch: load system + every
## module in the batch + their transitive import closure ONLY — not the whole
## program. This is the "process the files it is passed" model (à la Nimony's
## `hexer c file.nif`): the foreign symbols a target's codegen demands are
## loaded lazily by `ast2nif.moduleId`, which opens any referenced module's NIF
## index on first touch, so a body in a not-loaded module still resolves. The
## closure is loaded as full `BModule`s only so that the incidental
## `g.mods[pos]` accesses during codegen resolve; system's own internal closure
## (allocators, locks, …) is included because a target's emit-everywhere
## codegen can demand those without importing them directly.
##
## The whole program is no longer loaded in this process, which is what bounds
## per-process memory under nifmake's parallel fan-out (the main module's `cg`,
## which still loads everything for NimMain's init list and the method
## dispatchers, runs essentially alone since every other `.c.nif` precedes it).
##
## The batch is loaded as ONE closure: `resetForBackend`, the system load and
## the closure walk happen once no matter how many targets share the process,
## and a module in two targets' closures is loaded once. That amortization is
## the reason batches exist — a per-module process spends far more time here
## than it spends generating code.
resetForBackend(g)
var isKnownFile = false
let systemFileIdx = registerNifSuffix(g.config, systemNifSuffix(g.config), isKnownFile)
g.config.m.systemFileIdx = systemFileIdx
let precompSys = moduleFromNifFile(g, systemFileIdx, {AlwaysLoadInterface})
g.systemModule = precompSys.module
var modules: seq[PrecompiledModule] = @[]
var visited = initHashSet[string]()
visited.incl systemNifSuffix(g.config)
# Only the batch is codegen'd, so only it needs full ASTs; the surrounding
# closure is loaded interface-only (demanded bodies come lazily from the
# kept-open streams), which is what keeps the process light under fan-out.
var targets: seq[PrecompiledModule] = @[]
var stack: seq[ModuleSuffix] = @[]
# Separate from `visited`, which exists to keep the closure walk off modules
# already loaded. System is in `visited` from the start yet can perfectly well
# BE a batch member — it is a live node with its own `.t.bif` and `.c.nif` —
# and then it needs the full-AST load like any other member, on top of the
# interface-only load above. Reusing `visited` to deduplicate members skipped
# it and produced a batch with nothing in it.
var claimed = initHashSet[string]()
for targetSuffix in targetSuffixes:
if claimed.containsOrIncl(targetSuffix): continue
var isKnown = false
let targetIdx = registerNifSuffix(g.config, targetSuffix, isKnown)
let target = moduleFromNifFile(g, targetIdx, {LoadFullAst})
targets.add target
# A member that is also another member's dependency must keep its full AST,
# so claim it before the closure walk can load it interface-only.
visited.incl targetSuffix
if target.module != nil:
modules.add target
for dep in target.deps: stack.add dep
if precompSys.module != nil:
for dep in precompSys.deps: stack.add dep
while stack.len > 0:
let suffix = stack.pop()
if not visited.containsOrIncl(suffix.string):
var isKnown2 = false
let fileIdx = registerNifSuffix(g.config, suffix.string, isKnown2)
let precomp = moduleFromNifFile(g, fileIdx, {})
if precomp.module != nil:
modules.add precomp
for dep in precomp.deps: stack.add dep
flushMethodReplays(g)
for m in modules:
discard setupNifBackendModule(g, m.module)
if precompSys.module != nil:
discard setupNifBackendModule(g, precompSys.module)
result = (modules, precompSys, targets)
proc findTargetModule(g: ModuleGraph; modules: seq[PrecompiledModule];
precompSys: PrecompiledModule; suffix: string): PrecompiledModule =
## The loaded module whose NIF suffix is `suffix` (the `--icBackendModule`
## value), or a nil module if none matches.
result = PrecompiledModule(module: nil)
for m in modules:
if cachedModuleSuffix(g.config, FileIndex m.module.position) == suffix:
return m
if precompSys.module != nil and
cachedModuleSuffix(g.config, FileIndex precompSys.module.position) == suffix:
return precompSys
proc backendBatch(conf: ConfigRef; mainSuffix: string):
tuple[members: seq[string], isMain: bool] =
## The module suffixes this invocation processes, and whether it is the
## main-module invocation. Main is never batched with anything else: it loads
## the WHOLE program (NimMain's init list and the method dispatchers are
## whole-program facts), so putting another module in with it would defeat the
## bound on per-process memory that the per-module split exists to provide.
let members = conf.icBackendModules
result = (members: members,
isMain: members.len == 0 or
(members.len == 1 and members[0] == mainSuffix))
proc setNestedClosureBodies(g: ModuleGraph; idgen: IdGenerator; n: PNode;
owner: PSym; seen: var IntSet) =
## A closure routine nested in `owner` (the `:anonymous` proc lambda-lifting
## minted, plus any deeper nesting) gets its captured-var→env rewrite produced
## as part of the OWNER's `transformBody`. The nested proc is a module-indexed
## sym whose `.s.nif` sdef carries its PRE-lift body, so without help the whole
## module re-serializer would write that pre-lift body and cg would lose the
## capture mapping (it accesses `x` directly instead of `ClE_0->x0`). Walk the
## owner's transformed body and cache each nested closure's transformed body on
## its sym so `writeSymDef` serializes the lifted body into the routine's
## 2-way-body slot.
if n == nil: return
if n.kind == nkSym:
let s = n.sym
if s != nil and s.kind in routineKinds and s != owner and
s.skipGenericOwner != nil and s.skipGenericOwner.kind != skModule and
not seen.containsOrIncl(s.id):
# Covers ALL nested routines, not only ccClosure ones. A NIMCALL nested proc
# the async transform mints (e.g. workNimAsyncContinue) already has its
# lifted body set by the OWNER's transformBody, but it is NOT in the owned
# loop (owner is a proc, not the module). Without injecting it HERE it is
# serialized transform-only; cg loads it (wasLoaded) and skips injection, so
# a closure-env store stays a raw field assign with no incref -> the env is
# freed before the async callback runs -> "yielded nil". `seen` (shared
# across the owned loop) injects each routine exactly once.
if s.ast != nil and getBody(g, s).kind != nkEmpty:
# Only ccClosure routines are safe to `transformBody` standalone here; a
# nimcall nested proc already has its lifted body from the owner's lift,
# and transforming an arbitrary nested routine with no cached body crashes
# (not in a standalone-transformable state).
let weTransformed = s.transformedBody == nil and
s.typ != nil and s.typ.callConv == ccClosure
if weTransformed:
s.transformedBody = transformBody(g, idgen, s, {})
if s.transformedBody != nil:
# Inject destructors so cg loads a fully-lowered body and never rebuilds
# (mirrors non-IC, which injects every nested proc separately). The
# importer `n2` skField collision this used to trigger is fixed at the
# NIF-naming layer (toNifSymName gives derived env fields a unique
# disamb), so injecting ccClosure nested procs here is safe.
if sfInjectDestructors in s.flags:
s.transformedBody = injectDestructorCalls(g, idgen, s, s.transformedBody)
setNestedClosureBodies(g, idgen, s.transformedBody, s, seen)
else:
for i in 0 ..< n.safeLen:
setNestedClosureBodies(g, idgen, n[i], owner, seen)
proc reownFromTwin(n: PNode; twin, s: PSym) =
## Re-own to `s` every entity the frontend attributed to `s`'s forward-decl
## `twin` (found via the result's owner). lambda-lifting compares owners by
## reference, so a twin-owned `result` is rejected as `illegalCapture`
## ("'result' ... cannot be captured") and, once that is fixed, twin-owned
## locals go missing from `s`'s env ("environment misses: ..."). Both are
## pervasive on chronos `{.async.}` methods. Re-owning to `s` matches the
## single-sym non-IC case. `twin` is ONE specific sym, so only THIS routine's
## result-twin-owned entities match — re-owning entities of OTHER same-name
## twins proved too blunt (it disrupts env construction and reintroduces the
## very capture errors it should fix). `n.sym != s` guards self-ownership.
if n == nil: return
if n.kind == nkSym and n.sym != nil and n.sym != s and n.sym.owner == twin:
setOwner(n.sym, s)
for i in 0 ..< n.safeLen:
reownFromTwin(n[i], twin, s)
proc lowerOneModule(g: ModuleGraph; target: PrecompiledModule;
seenNested: var IntSet)
proc generateLowerStage(g: ModuleGraph; mainFileIdx: FileIndex) =
## Backend lowering for this invocation's batch
## (`--icBackendStage:lower --icBackendModules:<a,b,c>`):
## enumerate the routines this module OWNS and write them to `<module>.t.nif`.
## Eventually this transforms each owned routine once, in the owner's id space,
## so `cg` reads the result instead of re-deriving it (re-derivation per
## parallel `cg` process is the root of the closure-`:env` identity drift).
## Runs per module in parallel on the shallow backend dep-graph — NOT folded
## into the dense, mostly-serial sem stage.
##
## gate `newSymNode`'s lazy-type marking to the backend (see astdef) — the
## transform builds sym nodes off not-yet-typed stubs, exactly as the `cg`
## stage does.
nifcBackendActive = true
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
let batch = backendBatch(g.config, mainSuffix)
var modules: seq[PrecompiledModule]
var precompSys: PrecompiledModule
var targets: seq[PrecompiledModule]
if batch.isMain:
var nifFiles: seq[string]
(modules, precompSys, nifFiles) = loadBackendModules(g, mainFileIdx)
if modules.len == 0:
rawMessage(g.config, errGenerated,
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
return
targets = @[findTargetModule(g, modules, precompSys, mainSuffix)]
else:
(modules, precompSys, targets) = block:
icProfStart(tLoadClosure)
let r = loadDepClosure(g, batch.members)
icProfStop(tLoadClosure)
r
# ONE PSym graph for the whole batch, so the guard against transforming a
# nested routine twice has to span it: two members reaching the same nested
# closure would otherwise inject its destructors twice into the same `PSym`.
# (In the one-module-per-process fan-out the two members are two processes
# with two copies, and each injects once.)
var seenNested = initIntSet()
for target in targets:
lowerOneModule(g, target, seenNested)
proc lowerOneModule(g: ModuleGraph; target: PrecompiledModule;
seenNested: var IntSet) =
## Lower the routines `target` OWNS and write its `.t.bif`. One batch member.
if target.module == nil:
rawMessage(g.config, errGenerated,
"per-module lowering: module not found for suffix")
return
let modPos = target.module.position
let tb = BModuleList(g.backend).mods[modPos]
if tb == nil:
rawMessage(g.config, errGenerated,
"per-module lowering: no backend module for suffix: " &
cachedModuleSuffix(g.config, FileIndex modPos))
return
# Transform every owned routine ONCE in this single process's id space and
# re-serialize the ENTIRE module as a proper indexed NIF (`writeLoweredModule`)
# with the transformed bodies baked into the routine `(sd)` entries. `cg` loads
# it through the normal module loader, so nested procs (incl. async state
# machines) arrive as real defs with their lifted bodies — no re-derivation.
# This single-writer-per-owner is what keeps closure-`:env` identity stable
# across the parallel `cg` processes (re-derivation per process was the root of
# the `:env` identity drift). `transformBody` with flags {} mirrors the cg call
# (cgen.nim); `injectDestructorCalls` is NOT run here — it stays in `cg` on the
# loaded body.
#
# `transformBody`/lambda-lifting LIFTS the closure env's type-bound ops
# (`=destroy` etc.) into `g.opsLog`; snapshot its length so we serialize exactly
# the ops THIS stage created (not those loaded from `.s.nif`).
# Per MEMBER, not per batch: each member's `.t.bif` must carry exactly the ops
# ITS lowering lifted, the way its own process would have written them.
let opsLogStart = g.opsLog.len
# `seenNested` comes from the caller and spans the whole batch — see the
# comment at its declaration. Within one module it already served to transform
# + destructor-inject a nested routine reachable from more than one owner
# EXACTLY once (double injection would emit two `=destroy`/`=copy` runs).
icProfStart(tLowerOwned)
for s in moduleSymbolStubs(ast.program, FileIndex modPos):
if ownsRuntimeRoutine(s, modPos):
# REUSE path (`icReuseSemLowering` ON): a routine already transformed during
# sem (CT eval / macro / VM transform) carries its lowered body in the
# `.s.nif` slot (loaded into `transformedBody`) — don't re-transform it.
# Default OFF: the slot is never loaded (see loadSymFromCursor), so
# `transformedBody` is nil here and we always re-derive below. See
# doc/ic_backend_simplify.md §6a/§6b.
if icReuseSemLowering(g.config) and s.transformedBody != nil: continue
# A routine serialized as a forward-decl + impl pair (writeSymDef's
# "separate forward declaration and implementation") loads as TWO syms; the
# impl `s` we transform here can carry body entities (`result`, locals,
# nested routines) owned by its fwd-decl TWIN, not by `s`. lambda-lifting
# compares owners by reference → `illegalCapture` rejects a twin-owned
# `result` and the lifting pass can't find twin-owned locals in `s`'s env.
# Pervasive on chronos `{.async.}` methods. Re-own them to `s`, matching the
# single-sym non-IC case. Backend-only, so frontend effect/exception
# inference is untouched.
if s.ast != nil and s.ast.len > resultPos and
s.ast[resultPos].kind == nkSym and s.ast[resultPos].sym.owner != s:
reownFromTwin(s.ast, s.ast[resultPos].sym.owner, s)
# Retain the transformed body on the sym so `writeSymDef` serializes it in
# the routine's `(sd)` 2-way-body slot.
s.transformedBody = transformBody(g, tb.idgen, s, {})
# Run the destructor injection HERE so the `.t.bif` body is FULLY lowered:
# `injectDestructorCalls` is demand-driven (it decides where destructors go
# by move analysis) and LIFTS the type-bound ops it needs (e.g. a nested
# closure env's `=destroy`) into `g.opsLog` — which the `hooks` collection
# below then serializes. Done in `cg` instead, those ops were lifted per-cg
# process, owned by nobody, and emitted as a prototype-only → undefined at
# link (the `eqdestroy__c<n>` gap). cg must NOT re-inject a loaded body
# (see genProcLvl3's `wasLoaded` gate) so this stays the single injection.
if sfInjectDestructors in s.flags:
s.transformedBody = injectDestructorCalls(g, tb.idgen, s, s.transformedBody)
# Cache the lifted+injected body on nested ccClosure routines too, so a
# module-indexed nested closure serializes its lifted (capture-rewritten,
# destructor-injected) body.
setNestedClosureBodies(g, tb.idgen, s.transformedBody, s, seenNested)
# Collect the hooks this stage lifted, and transform each hook ROUTINE's body
# too (it is itself lowered into NIFC). The hooks' `(sd)` + transformed body go
# into the `.t.nif`; `cg` re-attaches them so `injectDestructorCalls` resolves
# the loaded env's `=destroy`. Iterate to a fixpoint: a hook body can lift
# further hooks (a field's `=destroy`).
icProfStop(tLowerOwned)
icProfStart(tLowerHooks)
var hooks: seq[LogEntry] = @[]
var i = opsLogStart
while i < g.opsLog.len:
let e = g.opsLog[i]
if e.kind == HookEntry and e.sym != nil and e.sym.kind in routineKinds and
e.sym.transformedBody == nil:
hooks.add e
# Transform the hook routine's body and cache it on the sym so `writeSymDef`
# serializes it in the hook's `(sd)` transformed-body slot (`transformBody
# {}` returns the body but does not cache it). Inject the hook's own
# destructors here too (it can destroy fields/temporaries) so cg loads a
# fully-lowered hook and never re-injects.
e.sym.transformedBody = transformBody(g, tb.idgen, e.sym, {})
if sfInjectDestructors in e.sym.flags:
e.sym.transformedBody = injectDestructorCalls(g, tb.idgen, e.sym, e.sym.transformedBody)
inc i
# Re-serialize the whole module to its suffix-based `.t.nif` (the path
# `toNifFilename` resolves for the cg/emit stages). `writeLoweredModule` seals
# routines itself.
icProfStop(tLowerHooks)
let suffix = cachedModuleSuffix(g.config, FileIndex modPos)
let wholeArtifact = toGeneratedFile(g.config, AbsoluteFile(suffix), ".t.bif").string
timed tLowerWrite:
writeLoweredModule(ast.program, g.config, target, hooks, wholeArtifact)
if isDefined(g.config, "icDceCheck"):
stderr.writeLine "[icLower] " & extractFilename(wholeArtifact) & " " &
$hooks.len & " hooks"
proc visitDep(suffix: string;
suffixToMod: Table[string, PrecompiledModule];
visited: var HashSet[string]; bl: BModuleList;
ordered: var seq[BModule]) =
## Post-order DFS over a module's import closure used to reconstruct the
## dependency (init) order: a dependency's init must be registered before its
## importer's. Appends each reachable non-main module's `BModule` to `ordered`.
if visited.containsOrIncl(suffix): return
let pm = suffixToMod.getOrDefault(suffix)
if pm.module == nil: return
for dep in pm.deps: # dependencies first (post-order)
visitDep(dep.string, suffixToMod, visited, bl, ordered)
if sfMainModule notin pm.module.flags:
let bm = bl.mods[pm.module.position]
if bm != nil: ordered.add bm
proc cgGenerateModule(g: ModuleGraph; target: PrecompiledModule)
proc cgFinishModule(g: ModuleGraph; target: PrecompiledModule;
modules: seq[PrecompiledModule];
precompSys: PrecompiledModule)
proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) =
## Backend codegen for this invocation's batch
## (`--icBackendStage:cg --icBackendModules:<a,b,c>`): generate C for each
## member and write its `.c.nif` artifact (no merge, no `.c` render, no
## cc/link — those are separate nifmake rules).
##
## `findPendingModule` routes a demand to its owner when the owner is in the
## batch and into the demanding TU otherwise (emit-everywhere).
##
## A NON-main target loads only its own import closure (`loadDepClosure`); the
## whole program is no longer pulled into every parallel `cg` process. The main
## module still loads everything (`loadBackendModules`) because NimMain's init
## list and the method dispatchers are whole-program; its `cg` runs essentially
## alone (every other `.c.nif` precedes it), so it does not contend for memory.
# gate `newSymNode`'s lazy-type marking to this stage only (see astdef)
nifcBackendActive = true
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
let batch = backendBatch(g.config, mainSuffix)
var modules: seq[PrecompiledModule]
var precompSys: PrecompiledModule
var targets: seq[PrecompiledModule]
if batch.isMain:
var nifFiles: seq[string]
(modules, precompSys, nifFiles) = loadBackendModules(g, mainFileIdx)
if modules.len == 0:
rawMessage(g.config, errGenerated,
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
return
# No whole-program DCE here: each module emits the routines it owns and the
# MERGE stage recomputes the one program-wide live set across all `.c.nif`s.
# Running a whole-program liveness pass over all ~260 NIFs in the main `cg`
# would cost ~900 MB for a result the merge stage throws away.
targets = @[findTargetModule(g, modules, precompSys, mainSuffix)]
else:
# No whole-program load, hence no whole-program DCE: each member emits its
# full demanded closure and the merge stage drops what is globally dead.
(modules, precompSys, targets) = block:
icProfStart(tLoadClosure)
let r = loadDepClosure(g, batch.members)
icProfStop(tLoadClosure)
r
for i, target in targets:
if target.module == nil:
rawMessage(g.config, errGenerated,
"per-module codegen: module not found for suffix: " &
(if i < batch.members.len: batch.members[i] else: mainSuffix))
return
let bl = BModuleList(g.backend)
# Declare which modules this process writes a TU for, BEFORE any code is
# generated: `findPendingModule` consults the set on the very first demand, so
# a member added later would have its definitions routed into whichever TU
# asked first — which is precisely what the set exists to prevent.
for target in targets:
bl.icEmitted.incl target.module.position
# Generate EVERY member before finishing ANY of them. `finishModule` closes a
# TU (`finalCodegenActions` puts it in `modulesClosed`), and a later member's
# codegen routes definitions it does not own INTO an earlier member's TU — see
# `findPendingModule`. Finishing as we went closed those TUs first, and the
# definitions that arrived afterwards were silently dropped: 18 undefined
# symbols at link, all of them `_u`-flagged uniques whose owner happened to
# sort earlier in its batch.
timed tCgGen:
for target in targets:
cgGenerateModule(g, target)
timed tCgFinish:
for target in targets:
cgFinishModule(g, target, modules, precompSys)
# Writes each batch member's `.c.nif` (every other loaded module's TU is empty,
# so `cgenWriteModules` emits no artifact for it). cc/link are NOT run here.
timed tCgWrite:
cgenWriteModules(g.backend, g.config)
# Always leave a `.c.nif` for every member, even one whose module has no code
# (a leaf library whose procs all emit into their users): the nifmake graph
# declares a `.c.nif` output per member, so a missing one would re-fire the
# rule forever. An empty artifact renders to an empty `.c`.
for target in targets:
let tb = bl.mods[target.module.position]
if tb != nil:
let artifact = getCFile(tb).string & ".nif"
if not fileExists(artifact):
writeCnifArtifact("", artifact,
semmedNif = toNifFilename(g.config, FileIndex target.module.position),
moduleBase = $getSomeNameForModule(tb))
proc cgGenerateModule(g: ModuleGraph; target: PrecompiledModule) =
## Generate ONE batch member's code. Does NOT finish its TU — see the caller.
# The `lower` stage already wrote each module's transformed bodies + lifted
# hooks into its `.t.nif`, which the loaders above read directly (toNifFilename
# resolves the `.t.nif`); transformed bodies arrive via loadSymFromCursor and
# lifted hooks via moduleFromNifFile's registerLoadedHooks. Nothing to apply.
generateCodeForModule(g, target)
let bl = BModuleList(g.backend)
if sfMainModule notin target.module.flags:
# This module's top-level `var`s with a `=destroy` registered their teardown
# in `graph.globalDestructors` during `genTopLevelStmt` above. Main's `cg` is
# a different process and never sees them, so emit them as this TU's own
# exported proc and announce the name in the meta head. Stays HERE, in the
# generate pass: it consumes the destructors this module just registered.
let tbm = bl.mods[target.module.position]
if tbm != nil:
tbm.icGlobalDtorName = genIcModuleDestroyGlobals(g, tbm)
proc cgFinishModule(g: ModuleGraph; target: PrecompiledModule;
modules: seq[PrecompiledModule];
precompSys: PrecompiledModule) =
## Close ONE batch member's translation unit, once every member of the batch
## has generated. The artifact write is not here: `cgenWriteModules` is a
## single whole-list operation the caller runs after the whole batch.
let bl = BModuleList(g.backend)
# The main module also owns the whole-program method dispatchers + NimMain.
if sfMainModule in target.module.flags:
icProfStart(tCgInit)
emitMethodDispatchers(g)
# NimMain (generated when the main module is finished) must call every other
# module's init/datInit. Those translation units are produced by their own
# `cg` processes, so the calls are registered here from each `.c.nif` meta
# head — which is why the main module's `cg` runs last, after every other
# `.c.nif` exists. Modules without init code (no `.c.nif`) register nothing.
#
# The registration order IS the runtime init order, and it must be the
# DEPENDENCY (post-order) order: an imported module's init has to run before
# its importer's. The whole-program backend gets this for free — it iterates
# `modulesClosed`, built in module-FINISH order (a post-order DFS over
# imports). Iterating `bl.mods` by position is WRONG: an importer gets a
# LOWER position than the modules it imports (its file is registered before
# its `import` statements are processed), so position order runs importers
# before their dependencies. That left chronicles' `topics_registry` — whose
# init sets `mainThreadId` — running AFTER a module that calls `registerTopic`
# from its own init, tripping the `getThreadId() == mainThreadId` assert at
# startup. So reconstruct the post-order DFS over the import closure here.
#
# NOTE: this is deliberately a SEPARATE traversal rather than reusing the
# module LOAD order — the per-module backend's C emit is sensitive to load
# order (it determines the main TU's header composition), so the loader must
# keep its existing order and the init order is derived independently here.
var suffixToMod = initTable[string, PrecompiledModule]()
for pm in modules:
if pm.module != nil:
suffixToMod[cachedModuleSuffix(g.config, FileIndex pm.module.position)] = pm
if precompSys.module != nil:
suffixToMod[cachedModuleSuffix(g.config, FileIndex precompSys.module.position)] = precompSys
var visited = initHashSet[string]()
var ordered: seq[BModule] = @[]
# System (and its include/import closure) must initialize FIRST: its init
# runs `initGC()` (top-level code in `threadimpl`, included into system),
# and every other module's init may allocate — an allocation before the GC
# heap is set up triggers a collection over an uninitialized region and
# crashes (e.g. nim-metrics' `newRegistry` in its init). System is the
# IMPLICIT universal import and appears in no module's explicit `deps`, so a
# DFS rooted at main never reaches it; seed the traversal from system first.
if precompSys.module != nil:
visitDep(cachedModuleSuffix(g.config, FileIndex precompSys.module.position),
suffixToMod, visited, bl, ordered)
# Then order the whole import closure rooted at the main module; main itself
# is excluded above (its init body becomes NimMain).
for pm in modules:
if pm.module != nil and sfMainModule in pm.module.flags:
visitDep(cachedModuleSuffix(g.config, FileIndex pm.module.position),
suffixToMod, visited, bl, ordered)
# Defensive: any loaded module not reachable from main's import closure
# (demand-loaded system internals) keeps its init registered, appended last
# — nothing imports it, so its relative order does not matter.
for m in bl.mods:
if m != nil and sfMainModule notin m.module.flags:
let suffix = cachedModuleSuffix(g.config, FileIndex m.module.position)
if not visited.containsOrIncl(suffix):
ordered.add m
for m in ordered:
let heads = readCnifHeads(getCFile(m).string & ".nif")
registerReusedModuleToMain(bl, m, heads.initRequired, heads.datInitRequired)
if heads.globalDtor.len > 0: g.icModuleDtors.add heads.globalDtor
# `ordered` is dependency (post-order) init order; teardown runs in reverse,
# so an importer's globals are destroyed before the ones it may still point
# at. This mirrors whole-program cgen, which walks its single accumulated
# `globalDestructors` list backwards. Main's own destructors come first and
# are added by `finalCodegenActions` itself.
reverse g.icModuleDtors
icProfStop(tCgInit)
let tb = bl.mods[target.module.position]
if tb != nil:
finishModule(g, tb)
# Record this module's C compile/link directives next to its `.c` so the
# `link` stage can recover them without loading the module graph. See
# `replayer.writeBackendActions`.
writeBackendActions(g, target.module, target.topLevel,
getCFile(tb).string & BackendActionsExt)
proc generateMergeStage(g: ModuleGraph) =
## Per-module backend merge (`--icBackendStage:merge`): a pure artifact
## operation, no module graph loaded. Reads every `.c.nif` the `cg` stages
## wrote, computes the global live set and — for each `'u'`-flagged unique
## definition that several `cg` processes emitted (emit-everywhere) — the one
## artifact allowed to embed its body, and writes the decision the `emit`
## stages consume — the cross-process replacement for what used to be
## in-process first-claimant/DCE coordination.
let nimcache = getNimcacheDir(g.config).string
var files: seq[string] = @[]
# The driver lists the live modules' artifacts explicitly (deps.nim's
# `writeLiveModules`); only fall back to globbing when that manifest is
# absent (a cache written by an older compiler). Globbing merges whatever
# `.c.nif` happens to sit in the directory, which is wrong the moment the
# cache is shared with another program — see `LiveModulesFile`.
let manifest = nimcache / LiveModulesFile
if fileExists(manifest):
for line in lines(manifest):
let p = line.strip()
if p.len > 0: files.add p
else:
for artifact in walkFiles(nimcache / ("*" & icCFileExt(g.config) & ".nif")):
files.add artifact
sort files
let decision = computeMergeDecision(files)
if decision.broken:
rawMessage(g.config, errGenerated,
"per-module backend merge: a .c.nif artifact is missing or unparsable")
return
writeMergeDecision(nimcache / MergeDecisionFile, decision)
if isDefined(g.config, "icDceCheck"):
stderr.writeLine "[icMerge] artifacts: " & $files.len &
" live: " & $decision.live.len & " defs: " & $decision.defs &
" liveDefs: " & $decision.liveDefs & " owned: " & $decision.owners.len
proc emitOneModule(g: ModuleGraph; mainFileIdx: FileIndex; member: string;
isMain: bool; decision: MergeDecision)
proc generateEmitStage(g: ModuleGraph; mainFileIdx: FileIndex) =
## Backend emit for this invocation's batch
## (`--icBackendStage:emit --icBackendModules:<a,b,c>`):
## render the target module's final `.c` from its `.c.nif` and the merge
## decision. Loads the target the same way `cg` does so `getCFile` returns the
## identical path `cg` wrote to (the main module's source-vs-suffix aliasing in
## particular); no codegen runs. A non-main target loads only its own closure
## (`loadDepClosure`) so emit, like `cg`, stays bounded under parallel fan-out.
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
let batch = backendBatch(g.config, mainSuffix)
# emit renders a module's final `.c` PURELY from its own `.c.nif` and the merge
# decision (see `renderCFromArtifact` — text filtering, no AST is touched). It
# used to load the target's whole transitive import closure as BModules solely
# to reach `getCFile(bmod)` for the output path. Under the fire-all-every-edit
# merge barrier (every `emit` re-fires whenever `merge` bumps the decision's
# mtime — deliberate insurance so a decision change re-renders all `.c`
# consistently) that per-process `loadDepClosure` was the bulk of a warm
# rebuild's cost: 240 processes each re-parsing a module closure only to filter
# a handful of `.c.nif`s whose bytes are usually unchanged. Derive the `.c`
# path directly instead — the SAME pure computation `deps.nim.backendCFile`
# uses to DECLARE this stage's output (`getCFile` == that formula) — so an emit
# process loads nothing and the fire-all costs process-startup, not a graph load.
# The decision is read ONCE for the batch: it is a whole-program artifact, and
# re-reading it per member was a per-process cost the batch exists to remove.
let decision = readMergeDecision(getNimcacheDir(g.config).string / MergeDecisionFile)
if decision.broken:
rawMessage(g.config, errGenerated,
"per-module emit: missing or unparsable merge decision " & MergeDecisionFile)
return
let members = if batch.members.len == 0: @[mainSuffix] else: batch.members
for member in members:
# Per MEMBER, not per batch. `backendBatch.isMain` answers "is this
# invocation the main-module invocation", which is the right question for
# `lower`/`cg` (main loads the whole program, so it is never batched with
# anything). emit has no such constraint and batches freely, so main can sit
# in a batch with others — and then the batch-wide flag sent main's `.c` to
# the path derived from its SUFFIX rather than from its source file, and its
# `.c` was never written.
emitOneModule(g, mainFileIdx, member, member == mainSuffix, decision)
proc emitOneModule(g: ModuleGraph; mainFileIdx: FileIndex; member: string;
isMain: bool; decision: MergeDecision) =
## Render ONE batch member's final `.c` from its `.c.nif` and the batch's
## merge decision.
let cfilename =
if isMain: AbsoluteFile toFullPath(g.config, mainFileIdx)
else: AbsoluteFile member
let cfile = changeFileExt(completeCfilePath(g.config,
mangleModuleName(g.config, cfilename).AbsoluteFile), icCFileExt(g.config)).string
let artifact = cfile & ".nif"
if not fileExists(artifact):
rawMessage(g.config, errGenerated,
"per-module emit: missing .c.nif artifact for suffix: " & member)
return
var dropped = 0
let code = renderCFromArtifact(artifact, decision, extractFilename(artifact), dropped)
# Write the `.c` content-stably. `merge` re-runs on any edit and bumps the
# decision file's mtime, so nifmake re-fires every `emit` (the filter is cheap);
# but the FILTERED output is usually byte-identical for modules unaffected by
# the edit. Rewriting it unconditionally would bump every `.c`'s mtime and make
# `callCCompiler` recompile every `.o`. Writing only on a real change preserves
# the mtime, so the C compiler recompiles exactly the modules whose `.c` changed
# — the same DCE model as Nimony's. Safe here (unlike a content-stable merge
# decision): a `.c` is a per-module LEAF consumed only by the C compiler's own
# up-to-date check, not a shared prerequisite in nifmake's mtime ordering.
if not fileExists(cfile) or readFile(cfile) != code:
writeFile(cfile, code)
# ... but nifmake needs SOME output whose mtime proves "this rule ran since its
# inputs last moved". With the `.c` as the only output, the content-stable write
# above is indistinguishable from not having run: `merge` rewrites the decision
# file unconditionally, so every `emit` whose `.c` came out byte-identical stays
# older than a declared input and re-fires on every warm build from then on
# (measured: all 218 emit rules of a 219-module program, on a NO-OP build).
# The stamp is written unconditionally and is the rule's freshness proof; the
# `.c` keeps its content-stable mtime so `callCCompiler` still reuses the `.o`.
writeFile(cfile & ".stamp", $code.len & " " & $dropped & "\n")
if isDefined(g.config, "icDceCheck"):
stderr.writeLine "[icEmit] " & extractFilename(cfile) & " dropped " &
$dropped & " bodies (" & $code.len & " bytes)"
proc generateLinkStage(g: ModuleGraph; mainFileIdx: FileIndex) =
## Per-module backend link (`--icBackendStage:link`): the `emit` stages have
## written every module's `.c`; register them and run the C compiler + linker
## once via `extccomp.callCCompiler` (which parallelizes the per-file cc and
## skips up-to-date objects itself). No codegen runs and NO MODULE GRAPH IS
## LOADED.
##
## It used to load the whole import closure (`loadBackendModules`) for two
## things only: each module's `.c` path via `getCFile`, and its recorded C
## directives via `replayBackendActions`. That was 3.7s of the ~11s serial
## backend critical path on a 219-module program — a whole-program
## deserialization to recover a list of paths and a handful of strings. Both
## are now read from artifacts the earlier stages already produce:
## * the driver's `LiveModulesFile` manifest lists every live module's
## `.c.nif`, and the `.c` sits beside it (`emit`'s output);
## * each module's `cg` wrote its directives to a `.cflags` sidecar.
let nimcache = getNimcacheDir(g.config).string
var cfiles: seq[string] = @[]
let manifest = nimcache / LiveModulesFile
if fileExists(manifest):
for line in lines(manifest):
let p = line.strip()
if p.len > 0 and p.endsWith(".nif"): cfiles.add p[0 ..< p.len - ".nif".len]
else:
# A cache written by an older compiler has no manifest; fall back to the
# `.c` files sitting next to the artifacts.
for artifact in walkFiles(nimcache / ("*" & icCFileExt(g.config) & ".nif")):
cfiles.add artifact[0 ..< artifact.len - ".nif".len]
sort cfiles
var addedCFiles = initHashSet[string]()
for cpath in cfiles:
# Only modules that are their own cg/emit target produced a `.c`; the rest
# had their code emit-everywhere'd into the targets, so there is nothing to
# compile for them.
if not fileExists(cpath): continue
addedCFiles.incl extractFilename(cpath)
# The directives this module recorded (`{.passL: "-lm".}` etc.); without
# them math's `-lm` is lost -> undefined `floor`/`pow`/… at link.
applyBackendActions(g, cpath & BackendActionsExt)
let cfile = AbsoluteFile cpath
var cf = Cfile(nimname: splitFile(cfile).name, cname: cfile,
obj: completeCfilePath(g.config, toObjFile(g.config, cfile)),
flags: {})
# `addExternalFileToCompile` (not `addFileToCompile`) gates each `.c` on its
# SHA1 footprint: an unchanged `.c` keeps its `.o` and is flagged Cached, so
# `callCCompiler` skips its compile but still links the existing object. This
# is what makes a localized edit recompile only the handful of `.c`s the
# `emit` stage actually rewrote, instead of every object every time.
addExternalFileToCompile(g.config, cf)
# deps.nim's static scanner can keep a CONDITIONALLY-imported module as a build
# node (e.g. `net`'s `when defineSsl: import openssl`) that the manifest above
# may not cover. Such a node still emitted a `.c`, and it can OWN a live generic
# instance that a REACHABLE module reuses (openssl owns `toHex[uint8]`, reused
# by `strutils.escape`) — so its body must be at link or that reference is
# undefined. Link every emitted `.c` the merge decision says OWNS a LIVE symbol;
# a node that owns nothing live (a Windows-only winsock node on Linux) is
# correctly skipped.
block:
let decision = readMergeDecision(nimcache / MergeDecisionFile)
if not decision.broken:
var liveOwners = initHashSet[string]()
for cname, owner in decision.owners:
if owner.endsWith(icCFileExt(g.config) & ".nif") and cname in decision.live:
liveOwners.incl owner
for owner in liveOwners:
let cbase = owner[0 ..< owner.len - ".nif".len] # "@m….nim.c.nif" -> ".c"
if addedCFiles.containsOrIncl(cbase): continue
let cfile = AbsoluteFile(nimcache / cbase)
if not fileExists(cfile.string): continue
applyBackendActions(g, cfile.string & BackendActionsExt)
var cf = Cfile(nimname: cbase, cname: cfile,
obj: completeCfilePath(g.config, toObjFile(g.config, cfile)),
flags: {})
addExternalFileToCompile(g.config, cf)
if g.config.cmd != cmdTcc:
extccomp.callCCompiler(g.config)
proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
## Main entry point for NIF-based C code generation.
## Traverses the module dependency graph and generates C code.
when defined(icBNodeProf): profStageName = g.config.icBackendStage
if g.config.icBackendStage == "lower":
timed tStage: generateLowerStage(g, mainFileIdx)
return
elif g.config.icBackendStage == "cg":
timed tStage: generateCgStage(g, mainFileIdx)
return
elif g.config.icBackendStage == "merge":
timed tStage:
timed tMergeStage:
generateMergeStage(g)
return
elif g.config.icBackendStage == "emit":
timed tStage:
timed tEmitRender:
generateEmitStage(g, mainFileIdx)
return
elif g.config.icBackendStage == "link":
timed tStage:
timed tLinkStage:
generateLinkStage(g, mainFileIdx)
return
else:
rawMessage(g.config, errGenerated,
"the per-module NIF backend requires --icBackendStage:lower|cg|merge|emit|link")

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
@@ -983,7 +983,7 @@ proc genericParamToNif(n: PNode; parent: PNode; c: var TranslationContext) =
toNif n, parent, c
proc addExternName(sym: PSym; c: var TranslationContext) =
if sym.loc.snippet != "":
if sym.loc.snippet != nil:
c.b.addStrLit sym.loc.snippet
else:
c.b.addStrLit sym.name.s

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
@@ -913,7 +919,7 @@ proc infix(ctx: NilCheckerContext, l: PNode, r: PNode, magic: TMagic): PNode =
newSymNode(op, r.info),
l,
r)
result.typ = newType(tyBool, ctx.idgen, nil)
result.typ() = newType(tyBool, ctx.idgen, nil)
proc prefixNot(ctx: NilCheckerContext, node: PNode): PNode =
var cache = newIdentCache()
@@ -923,7 +929,7 @@ proc prefixNot(ctx: NilCheckerContext, node: PNode): PNode =
result = nkPrefix.newTree(
newSymNode(op, node.info),
node)
result.typ = newType(tyBool, ctx.idgen, nil)
result.typ() = newType(tyBool, ctx.idgen, nil)
proc infixEq(ctx: NilCheckerContext, l: PNode, r: PNode): PNode =
infix(ctx, l, r, mEqRef)

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
@@ -70,7 +65,3 @@ define:useStdoutAsStdmsg
@if nimHasVtables:
experimental:vtables
@end
@if nimHasImplicitRangeConversion:
warning[ImplicitRangeConversion]:off
@end

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 == cmdGendepend:
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]
@@ -207,6 +207,7 @@ proc parseAssignment(L: var Lexer, tok: var Token;
checkSymbol(L, tok)
val.add($tok)
confTok(L, tok, config, condStack)
config.currentConfigDir = parentDir(filename.string)
if percent:
processSwitch(s, strtabs.`%`(val, config.configVars,
{useEnvironment, useEmpty}), passPP, info, config)
@@ -246,20 +247,8 @@ 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
setConfigVar(conf, "selfDir", conf.currentConfigDir)
if readConfigFile(configPath, cache, conf):
conf.configFiles.add(configPath)
@@ -316,7 +305,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

@@ -128,7 +128,7 @@ proc createInterpreter*(scriptName: string;
if conf.libpath.isEmpty: conf.libpath = AbsoluteDir p
var m = graph.makeModule(scriptName)
incl(m, sfMainModule)
incl(m.flags, sfMainModule)
var idgen = idGeneratorFromModule(m)
var vm = newCtx(m, cache, graph, idgen)
vm.mode = emRepl
@@ -168,7 +168,7 @@ proc runRepl*(r: TLLRepl;
if supportNimscript: defineSymbol(conf.symbols, "nimconfig")
when hasFFI: defineSymbol(graph.config.symbols, "nimffi")
var m = graph.makeStdinModule()
incl(m, sfMainModule)
incl(m.flags, sfMainModule)
var idgen = idGeneratorFromModule(m)
if supportNimscript: graph.vm = setupVM(m, cache, "stdin", graph, idgen)

View File

@@ -84,7 +84,7 @@ proc toTreeSet*(conf: ConfigRef; s: TBitSet, settype: PType, info: TLineInfo): P
elemType = settype[0]
first = firstOrd(conf, elemType).toInt64
result = newNodeI(nkCurly, info)
result.typ = settype
result.typ() = settype
result.info = info
e = 0
while e < s.len * ElemSize:
@@ -101,7 +101,7 @@ proc toTreeSet*(conf: ConfigRef; s: TBitSet, settype: PType, info: TLineInfo): P
result.add aa
else:
n = newNodeI(nkRange, info)
n.typ = elemType
n.typ() = elemType
n.add aa
let bb = newIntTypeNode(b + first, elemType)
bb.info = info

View File

@@ -1,340 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## `PNode` <-> `TokenBuf`, in one process.
##
## WHY THIS EXISTS. The backend splits in two along a line that is not the one
## the migration to `BNode` was drawn along. Passes that REWRITE — transf,
## destructor injection, closure lifting, the tree the code generator builds as
## it goes — construct new nodes, and a `Cursor` is a read cursor into a shared
## token buffer, so they cannot be expressed against it and there is no reason
## to try. Passes that READ want the cursor. The bridge is the seam between
## them: a rewriting pass keeps producing a `PNode`, and anything that only
## reads gets a `TokenBuf`, from which a `Cursor` — and so a `BNode` — is a
## pointer.
##
## HOW IT DIFFERS FROM THE `.bif` FORMAT, and why that is the point. A `.bif`
## is read by a DIFFERENT PROCESS, so every symbol and type has to be written as
## a NAME the reader can look up again. A bridged buffer is read by the process
## that built it, so it does not: a symbol reference is `(bsym <idx>)`, an index
## into a side table holding the very `PSym` the encoder was handed, and the
## type slot is `(btyp <idx>)` the same way.
##
## Three consequences, and the middle one is the reason to prefer this over
## routing rewrites back through the file format:
##
## * It is LOSSLESS. No name mangling, no module index, no stubs, so nothing can
## be lost or renamed on the way through. `toPNode(toTokenBuf(n))` is `n`
## again, and `cgen`'s grinder checks the stronger property — that the cursor
## answers identically to the ORIGINAL `PNode` at every node, with no
## tolerated differences at all, unlike the file path which needs two.
## * `sym` IS IDEMPOTENT HERE, FIELDS INCLUDED. On the file path it is not, and
## cannot be: a cross-context field reference has no index entry, so
## `loadFieldStub` mints a fresh `skField` stub per use because two distinct
## fields can share a name and a position across types. That is what blocks
## `aliases.isPartOf` from moving to the seam (see `bnode.sym`). A bridged
## buffer hands back the same object every time, so code that compares field
## identity is correct on it.
## * The ENCODER is cheap, and that part is measured: no string formatting, no
## pool lookups for names, no index seeks, just a tree walk and two `seq.add`s.
## Building a buffer for every routine and NOT reading it costs 6.79s against a
## 6.75s baseline on a 50-module target — inside the noise.
##
## READING is not free, and that is where the cost of the whole seam sits.
## Driving the generator off cursors takes the same target from 6.75s to 8.85s,
## **+31%**, stable across interleaved runs. Since a compile is mostly frontend,
## codegen itself is slowed by considerably more than 31%. The suspects are the
## per-access costs a `PNode` does not have: `son(n, i)` is O(i) because it skips
## from the first child, `kind` checks the tag pool and indexes a memo on every
## call, `sym`/`typ` go through the nav, and `origin` is a hash lookup on every
## location built. None of that is inherent — `son` could cache, `origin` could
## key on something cheaper — but none of it has been optimised, and the number
## is here so nobody has to rediscover it before deciding whether to.
##
## WHAT IT IS NOT. The buffer is transient and process-local: `(bsym …)` means
## nothing without the tables beside it, so a bridged buffer must never be
## written to a file. The `.bif` writer in `ast2nif` is still the only thing
## that serializes, and it is a different job — it has to name things precisely
## because the reader cannot see this process's heap.
##
## USE:
##
## var b = toTokenBuf(n, conf)
## withBridge(b.tables):
## let root = BNode(b.rootCursor) # read it like any other `BNode`
## ...
## let back = toPNode(b) # a fresh `PNode` tree, if a rewrite needs one
##
## `withBridge` and `BNode` live in `bnode.nim` and exist only under
## `-d:newIcBackend`; this module is below that seam and does not depend on it,
## so the encoder and the round trip are usable either way.
import std / tables
import ast, astdef, idents, options, msgs, lineinfos
import icnifcore, ast2nif
import ic / enum2nif
import "../dist/nimony/src/lib/nifcore" except pool
import bodynav
when defined(nimPreviewSlimSystem):
import std / assertions
type
BridgeBuf* = object
## An encoded tree plus everything needed to read it back. Not copyable —
## it owns a `TokenBuf`.
bld*: IcBuilder
tables*: BridgeTables
conf: ConfigRef
symIdx: Table[int, int] ## PSym identity -> index into `tables.syms`
typeIdx: Table[int, int] ## PType identity -> index into `tables.types`
proc initBridgeBuf*(conf: ConfigRef; cap = 64): BridgeBuf =
BridgeBuf(bld: newIcBuilder(cap), tables: BridgeTables(), conf: conf,
symIdx: initTable[int, int](), typeIdx: initTable[int, int]())
# ---------------------------------------------------------------------------
# Encode
#
# The shape mirrors the `.bif` node encoding exactly — `(<kind> <flags> <type>
# <child|payload>…)` — so `bnode` reads a bridged buffer with the accessors it
# already has. Only the two leaves that would have been NAMES differ.
proc symIndex(b: var BridgeBuf; s: PSym): int =
## Symbols are deduplicated by identity, so the same `PSym` referenced twenty
## times costs one table slot and twenty equal indices — which is also what
## makes `sym` idempotent on the way back.
let key = cast[int](s)
result = b.symIdx.getOrDefault(key, -1)
if result < 0:
result = b.tables.syms.len
b.tables.syms.add s
b.symIdx[key] = result
proc typeIndex(b: var BridgeBuf; t: PType): int =
let key = cast[int](t)
result = b.typeIdx.getOrDefault(key, -1)
if result < 0:
result = b.tables.types.len
b.tables.types.add t
b.typeIdx[key] = result
proc emitInfo(b: var BridgeBuf; info: TLineInfo) =
## Line info goes through the SAME filename pool the `.bif` writer uses
## (`icPool.filenames`, keyed by full path), so `bnode.info` — which resolves
## through the decoder's `oldLineInfo` — needs no bridge-specific path.
if info == unknownLineInfo: return
b.bld.lineInfo(msgs.toFullPath(b.conf, info.fileIndex),
info.line.int32, info.col.int32)
proc emitFlags(b: var BridgeBuf; flags: TNodeFlags) =
var asIdent = ""
genFlags(flags, asIdent)
if asIdent.len > 0: b.bld.addIdent asIdent
else: b.bld.addDotToken()
proc emitTypeSlot(b: var BridgeBuf; t: PType) =
if t == nil:
b.bld.addDotToken()
else:
b.bld.openTag bridgeTypeTagName
b.bld.addIntLit typeIndex(b, t).int64
b.bld.closeTag()
proc encodeNode(b: var BridgeBuf; n: PNode)
proc encodeSym(b: var BridgeBuf; n: PNode) =
## `(nflags <flags> (ht <type> (bsym <idx>)))`, always the full chain.
##
## The wrappers are unconditional on purpose. The `.bif` writer emits them
## only when the node differs from its symbol, which is what creates the
## `(ht . <sym>)` shape whose nil is load-bearing and whose meaning depends on
## whether the symbol was loaded yet — a real ambiguity that cost a reverted
## commit on this branch. A bridge has no reason to inherit it: spelling the
## node's own type and flags out every time costs four tokens and makes the
## answer exact by construction.
b.bld.openTag symNodeFlagsTagName
b.emitInfo(n.info)
b.emitFlags(n.flags)
b.bld.openTag hiddenTypeTagName
b.emitTypeSlot(n.typ) # the LAZY-AWARE accessor: what `ast.typ` says
b.bld.openTag bridgeSymTagName
b.bld.addIntLit symIndex(b, n.sym).int64
b.bld.closeTag() # bsym
b.bld.closeTag() # ht
b.bld.closeTag() # nflags
proc encodeNode(b: var BridgeBuf; n: PNode) =
if n == nil:
# A nil child is a `DotToken` and has no origin: there is no node to
# remember, and `originOf` answering nil for it is the right answer.
b.bld.addDotToken()
return
# ORIGIN TRACKING. `len` is where this node's head token is about to land, and
# `cursorToPosition` is its inverse — nifcore documents that index as a stable
# key for exactly this. Recording it is what keeps `TLoc.lode` a `PNode`: a
# cursor-driven generator can still put the ORIGINAL node in a location, so
# the identity comparisons that already exist (`preventNrvo`'s `dest != le`,
# `isPartOf(d.lode, …)`) keep meaning what they meant. Without this the
# generator could not migrate without `TLoc` itself changing representation —
# and `TLoc` lives in `astdef`, at the bottom of the module graph, so that
# would push the seam far below the backend.
b.tables.origins[b.bld.buf.len] = n
if n.kind == nkSym and n.sym != nil:
encodeSym(b, n)
return
b.bld.openTag toNifTag(n.kind)
b.emitInfo(n.info)
b.emitFlags(n.flags)
b.emitTypeSlot(n.typ)
case n.kind
of nkCharLit:
b.bld.addCharLit char(n.intVal)
of nkIntLit..nkInt64Lit:
b.bld.addIntLit n.intVal
of nkUIntLit..nkUInt64Lit:
b.bld.addUIntLit cast[uint64](n.intVal)
of nkFloatLit..nkFloat128Lit:
b.bld.addFloatLit n.floatVal
of nkStrLit..nkTripleStrLit:
b.bld.addStrLit n.strVal
of nkIdent:
b.bld.addIdent n.ident.s
of nkSym:
# `n.sym == nil`, which `encodeSym` cannot express. It is a broken node
# either way; encode it as a childless `nkSym` so the walk stays total.
discard
of nkNone, nkEmpty, nkNilLit, nkType, nkCommentStmt:
discard
else:
for child in sons(n): encodeNode(b, child)
b.bld.closeTag()
proc toTokenBuf*(n: PNode; conf: ConfigRef): BridgeBuf =
## Encode a whole tree. `n` is not modified and not retained: the buffer holds
## tokens, and the tables hold the `PSym`/`PType` objects the tree pointed at.
result = initBridgeBuf(conf)
encodeNode(result, n)
# The tables carry a BORROWED pointer to the buffer so `originAt` can key
# against it. Set once, here, after encoding is finished and the buffer will
# not be reallocated out from under it.
result.tables.buf = addr result.bld.buf
proc originOf*(b: var BridgeBuf; c: Cursor): PNode {.inline.} =
## The `PNode` that was encoded at `c`, or nil when `c` is a `DotToken` (a nil
## child) or does not point at a node head. Identity-preserving: this is the
## very object the encoder was handed, not a copy, which is the whole point.
b.tables.buf = addr b.bld.buf
originAt(b.tables, c)
proc rootCursor*(b: var BridgeBuf): Cursor {.inline.} =
## A read cursor at the encoded root. `beginRead` asserts every tag was
## closed, so a mis-nested encode is caught here rather than as nonsense
## further along.
beginRead(b.bld.buf)
# ---------------------------------------------------------------------------
# Decode
#
# The other direction, for a rewriting pass that has a cursor and needs a tree
# it can mutate. Deliberately NOT written against `bnode`: this module is below
# it (`bnode` reads through a nav, which is exactly the state a decoder should
# not need), and the shape is the encoder's, right here, so the two stay
# legible as a pair.
proc decodeNode(b: BridgeBuf; c: var Cursor): PNode
proc decodeTypeSlot(b: BridgeBuf; c: var Cursor): PType =
if nifcore.kind(c) == DotToken:
result = nil
skip c
else:
doAssert nifcore.kind(c) == TagLit and
c.tags.tagName(cursorTagId(c)) == bridgeTypeTagName,
"bridge: type slot expected"
let payload = childCursor(c)
doAssert nifcore.kind(payload) == IntLit, "bridge: (btyp) payload expected"
let idx = int(nifcore.intVal(payload))
doAssert idx < b.tables.types.len, "bridge: type index out of range"
result = b.tables.types[idx]
skip c
proc decodeFlags(c: var Cursor): TNodeFlags =
result = nodeFlagsFromCursor(c)
skip c
proc decodeSym(b: BridgeBuf; c: var Cursor): PNode =
## Unwinds exactly what `encodeSym` wrote.
var outer = childCursor(c) # inside (nflags
let flags = decodeFlags(outer)
doAssert nifcore.kind(outer) == TagLit and
outer.tags.tagName(cursorTagId(outer)) == hiddenTypeTagName,
"bridge: (ht) expected inside (nflags)"
var ht = childCursor(outer) # inside (ht
let typ = decodeTypeSlot(b, ht)
doAssert nifcore.kind(ht) == TagLit and
ht.tags.tagName(cursorTagId(ht)) == bridgeSymTagName,
"bridge: (bsym) expected inside (ht)"
let payload = childCursor(ht)
doAssert nifcore.kind(payload) == IntLit, "bridge: (bsym) payload expected"
let idx = int(nifcore.intVal(payload))
doAssert idx < b.tables.syms.len, "bridge: sym index out of range"
result = newSymNode(b.tables.syms[idx], lineInfoFromCursor(program, c))
result.typField = typ
result.flags = flags
skip c
proc decodeNode(b: BridgeBuf; c: var Cursor): PNode =
case nifcore.kind(c)
of DotToken:
result = nil
skip c
of TagLit:
let tag = c.tags.tagName(cursorTagId(c))
if tag == symNodeFlagsTagName:
return decodeSym(b, c)
let kind = parse(TNodeKind, tag)
let info = lineInfoFromCursor(program, c)
var inner = childCursor(c)
let flags = decodeFlags(inner)
let typ = decodeTypeSlot(b, inner)
result = newNodeI(kind, info)
result.flags = flags
result.typField = typ
case kind
of nkCharLit..nkUInt64Lit:
result.intVal =
case nifcore.kind(inner)
of CharLit: BiggestInt(ord(charLit(inner)))
of UIntLit: cast[BiggestInt](nifcore.uintVal(inner))
else: BiggestInt(nifcore.intVal(inner))
of nkFloatLit..nkFloat128Lit:
result.floatVal = nifcore.floatVal(inner)
of nkStrLit..nkTripleStrLit:
result.strVal = strVal(inner)
of nkIdent:
result.ident = identFromCursor(program, inner)
else:
while inner.hasMore:
result.sons.add decodeNode(b, inner)
skip c
else:
raiseAssert "bridge: unexpected token " & $nifcore.kind(c)
proc toPNode*(b: var BridgeBuf): PNode =
## The tree the buffer encodes, as fresh `PNode`s sharing the ORIGINAL
## `PSym`s and `PType`s. Round-tripping is therefore identity-preserving for
## symbols and types and structure-preserving for everything else, which is
## what a rewriting pass needs: it can rebuild a subtree without the symbols
## underneath it changing identity.
var c = rootCursor(b)
result = decodeNode(b, c)

View File

@@ -25,46 +25,10 @@ const
useEffectSystem* = true
useWriteTracking* = false
hasFFI* = defined(nimHasLibFFI)
copyrightYear* = "2026"
copyrightYear* = "2025"
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**
@@ -104,7 +68,6 @@ type # please make sure we have under 32 options
optUseNimcache, # save artifacts (including binary) in $nimcache
optStyleHint, # check that the names adhere to NEP-1
optStyleError, # enforce that the names adhere to NEP-1
optStyleWarning, # emit style checks as warnings
optStyleUsages, # only enforce consistent **usages** of the symbol
optSkipSystemConfigFile, # skip the system's cfg/nims config file
optSkipProjConfigFile, # skip the project's cfg/nims config file
@@ -147,11 +110,6 @@ type # please make sure we have under 32 options
optEnableDeepCopy # ORC specific: enable 'deepcopy' for all types.
optShowNonExportedFields # for documentation: show fields that are not exported
optJsBigInt64 # use bigints for 64-bit integers in JS
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 +152,8 @@ type
cmdCheck # semantic checking for whole project
cmdM # only compile a single
cmdParse # parse a single file (for debugging)
cmdRod # .rod to some text representation (for debugging)
cmdIdeTools # ide tools (e.g. nimsuggest)
cmdNimscript # evaluate nimscript
cmdDoc0
cmdDoc # convert .nim doc comments to HTML
@@ -213,10 +173,6 @@ type
cmdJsonscript # compile a .json build file
# old unused: cmdInterpret, cmdDef: def feature (find definition for IDEs)
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,
@@ -233,7 +189,6 @@ type
gcRegions = "regions"
gcArc = "arc"
gcOrc = "orc"
gcYrc = "yrc" # thread-safe ORC (concurrent cycle collector)
gcAtomicArc = "atomicArc"
gcMarkAndSweep = "markAndSweep"
gcHooks = "hooks"
@@ -297,14 +252,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 +260,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 +354,16 @@ 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 +410,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
@@ -628,7 +508,7 @@ const
optHints, optStackTrace, optLineTrace, # consider adding `optStackTraceMsgs`
optTrMacros, optStyleCheck, optCursorInference}
DefaultGlobalOptions* = {optThreadAnalysis, optExcessiveStackTrace,
optJsBigInt64, optItaniumMangle}
optJsBigInt64}
proc getSrcTimestamp(): DateTime =
try:
@@ -686,7 +566,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 +588,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 +641,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 +688,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 +811,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"
@@ -1194,9 +1040,6 @@ proc isDynlibOverride*(conf: ConfigRef; lib: string): bool =
proc showNonExportedFields*(conf: ConfigRef) =
incl(conf.globalOptions, optShowNonExportedFields)
proc docRawOutput*(conf: ConfigRef) =
incl(conf.globalOptions, optDocRaw)
proc expandDone*(conf: ConfigRef): bool =
result = conf.ideCmd == ideExpand and conf.expandLevels == 0 and conf.expandProgress

View File

@@ -27,12 +27,13 @@ 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)
newSym(skPackage, pkgIdent, idGeneratorForPackage(int32(fileIdx)), nil, info)
proc getPackageSymbol*(sym: PSym): PSym =
func getPackageSymbol*(sym: PSym): PSym =
## Return the owning package symbol.
assert sym != nil
result = sym
@@ -40,18 +41,18 @@ proc getPackageSymbol*(sym: PSym): PSym =
result = result.owner
assert result != nil, repr(sym.info)
proc getPackageId*(sym: PSym): int =
func getPackageId*(sym: PSym): int =
## Return the owning package ID.
sym.getPackageSymbol.id
proc belongsToProjectPackage*(conf: ConfigRef, sym: PSym): bool =
func belongsToProjectPackage*(conf: ConfigRef, sym: PSym): bool =
## Return whether the symbol belongs to the project's package.
##
## See Also:
## * `modulegraphs.belongsToStdlib`
conf.mainPackageId == sym.getPackageId
proc belongsToProjectPackageMaybeNil*(conf: ConfigRef, sym: PSym): bool =
func belongsToProjectPackageMaybeNil*(conf: ConfigRef, sym: PSym): bool =
## Return whether the symbol belongs to the project's package.
## Returns `false` if `sym` is nil.
##

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