mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 19:33:42 +00:00
Compare commits
1 Commits
devel
...
pr_temp_in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c38fab3576 |
2
.github/workflows/bisects.yml
vendored
2
.github/workflows/bisects.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
name: ${{ matrix.platform }}-bisects
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install OpenSSL (Windows)
|
||||
if: |
|
||||
|
||||
4
.github/workflows/ci_docs.yml
vendored
4
.github/workflows/ci_docs.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
if: |
|
||||
github.event_name == 'push' && github.ref == 'refs/heads/devel' &&
|
||||
matrix.target == 'linux'
|
||||
uses: crazy-max/ghaction-github-pages@v5
|
||||
uses: crazy-max/ghaction-github-pages@v4
|
||||
with:
|
||||
build_dir: doc/html
|
||||
env:
|
||||
|
||||
10
.github/workflows/ci_packages.yml
vendored
10
.github/workflows/ci_packages.yml
vendored
@@ -18,12 +18,12 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
batch: ["0_3", "1_3", "2_3"] # list of `index_num`
|
||||
os: [ubuntu-latest, macos-14]
|
||||
batch: ["allowed_failures", "0_3", "1_3", "2_3"] # list of `index_num`
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
cpu: amd64
|
||||
- os: macos-latest
|
||||
- os: macos-14
|
||||
cpu: arm64
|
||||
name: '${{ matrix.os }} (batch: ${{ matrix.batch }})'
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -33,12 +33,12 @@ jobs:
|
||||
NIM_TESTAMENT_BATCH: ${{ matrix.batch }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js'
|
||||
uses: actions/setup-node@v7
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
|
||||
6
.github/workflows/ci_publish.yml
vendored
6
.github/workflows/ci_publish.yml
vendored
@@ -17,12 +17,12 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js'
|
||||
uses: actions/setup-node@v7
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
run: nim c -r -d:release ci/action.nim
|
||||
|
||||
- name: 'Comment'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v11
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
days-before-pr-stale: 365
|
||||
days-before-pr-close: 30
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -87,7 +87,6 @@ tweeter_test.db
|
||||
|
||||
/tests/megatest.nim
|
||||
/tests/ic/*_temp.nim
|
||||
/tests/ic/*_mm/
|
||||
/tests/navigator/*_temp.nim
|
||||
|
||||
|
||||
|
||||
41
changelog.md
41
changelog.md
@@ -35,23 +35,10 @@ errors.
|
||||
|
||||
- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`.
|
||||
|
||||
- Procedure compatibility also checks the backend representation of the
|
||||
parameter and result types, not just their source-level shape. Use
|
||||
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
|
||||
|
||||
## Standard library additions and changes
|
||||
|
||||
[//]: # "Additions:"
|
||||
|
||||
- Added `system.readRawDataStable`, a companion to `readRawData` that returns a
|
||||
raw `ptr UncheckedArray[char]` into a string's character data which stays valid
|
||||
across moves and copies of the string value. It is available under every string
|
||||
implementation (refc, ARC/ORC and `--strings:sso`) with the same signature, so
|
||||
code can pin an interior buffer pointer today and be ready for `--strings:sso`
|
||||
without `when declared` guards. Under `--strings:sso` it promotes a small inline
|
||||
string to its heap representation first; under the other implementations the data
|
||||
is already heap-resident, so it is equivalent to `readRawData`.
|
||||
|
||||
- `setutils.symmetricDifference` along with its operator version
|
||||
`` setutils.`-+-` `` and in-place version `setutils.toggle` have been added
|
||||
to more efficiently calculate the symmetric difference of bitsets.
|
||||
@@ -73,33 +60,17 @@ parameter and result types, not just their source-level shape. Use
|
||||
- `copyDirWithPermissions` to recursively preserve attributes
|
||||
|
||||
- `system.setLenUninit` now supports refc, JS and VM backends.
|
||||
- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth.
|
||||
|
||||
- `std/parseopt` now supports multiple parser modes via a `CliMode` enum.
|
||||
Modes include `Nim` (default, fully compatible) and two new experimental modes:
|
||||
`Lax` and `Gnu` for different option parsing behaviors.
|
||||
|
||||
- `std/symlinks.expandSymlink` now supports Windows symlinks and junctions with
|
||||
POSIX-like single-hop `readlink` semantics.
|
||||
- `std/nre2` is added to replace deprecated NRE.
|
||||
|
||||
- `system.typeof` adds a new parameter `modifierMode` to specify how type modifiers are handled.
|
||||
|
||||
[//]: # "Changes:"
|
||||
|
||||
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.
|
||||
- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function.
|
||||
- `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation.
|
||||
- `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`.
|
||||
- `std/re` and `std/nre` are deprecated as PCRE library is obsolete.
|
||||
Use https://github.com/nitely/nim-regex or `std/nre2`.
|
||||
See: https://github.com/nim-lang/Nim/issues/23668.
|
||||
- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style
|
||||
terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``)
|
||||
works without single-quoting.
|
||||
- `std/uri`: The `?` operator now appends query parameters to an existing query
|
||||
string instead of replacing it. Fixes [#19782](https://github.com/nim-lang/Nim/issues/19782).
|
||||
- `std/jsonutils`: `fromJson` now throws an exception when converting to `array`/`seq` if the JSON isn't an array instead of silently failing
|
||||
|
||||
## Language changes
|
||||
|
||||
@@ -138,11 +109,6 @@ parameter and result types, not just their source-level shape. Use
|
||||
See the [experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#typeminusbound-overloads)
|
||||
for more information.
|
||||
|
||||
- Seven more Unicode characters are now parsed as operators, implementing the RFC
|
||||
https://github.com/nim-lang/RFCs/issues/571: `⟑ ⟇ ⩓ ⩔ ■ □ ☆`. They all have the
|
||||
same priority as `*` (multiplication). As with the other Unicode operators, Nim
|
||||
only lexes them; their meaning is up to user code.
|
||||
|
||||
## Compiler changes
|
||||
|
||||
- Fixed a bug where `sizeof(T)` inside a `typedesc` template called from a generic type's
|
||||
@@ -150,13 +116,6 @@ parameter and result types, not just their source-level shape. Use
|
||||
The issue was that `hasValuelessStatics` in `semtypinst.nim` didn't recognize
|
||||
`tyTypeDesc(tyGenericParam)` as an unresolved generic parameter.
|
||||
|
||||
- The JS backend now implements write-through for `var openArray` parameters that
|
||||
receive a `toOpenArray` view (bug #15952): mutations reach the caller's storage
|
||||
instead of silently writing to a copy. Fixed homogeneous numeric arrays
|
||||
(`array[N, T]`, JS typed arrays) slice via `subarray`; `seq` and non-numeric
|
||||
arrays slice via a `{base, off, len}` view. This also covers seq/non-numeric-array
|
||||
write-through, pass-through, re-slicing and `@` (openArray-to-seq) of such views.
|
||||
|
||||
## Tool changes
|
||||
|
||||
- Added `--raw` flag when generating JSON docs to not render markup.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
310
compiler/ast.nim
310
compiler/ast.nim
@@ -36,13 +36,6 @@ proc setupProgram*(config: ConfigRef; cache: IdentCache) =
|
||||
when not defined(nimKochBootstrap):
|
||||
program = createDecodeContext(config, cache)
|
||||
|
||||
proc setIcMainModule*(fileIdx: FileIndex) =
|
||||
## Tells the IC loader which module is being compiled fresh, so that
|
||||
## re-exports of that module's symbols by dependencies are not loaded as
|
||||
## duplicate stubs.
|
||||
when not defined(nimKochBootstrap):
|
||||
ast2nif.setMainModule(program, fileIdx)
|
||||
|
||||
template loadSym(s: PSym) =
|
||||
## Loads a symbol from NIF file if it's in Partial state.
|
||||
when not defined(nimKochBootstrap):
|
||||
@@ -77,21 +70,11 @@ proc backendEnsureMutable*(t: PType) {.inline.} =
|
||||
# ^ IC review this later
|
||||
if t.state == Partial: loadType(t)
|
||||
|
||||
proc unsealForTransform*(t: PType) {.inline.} =
|
||||
## The transformer/lambda lifting also run inside `nim m` when the VM
|
||||
## compiles a LOADED routine (macro evaluation, `getImpl`). Their mutations
|
||||
## are process-local — transformed bodies are never written back to a NIF —
|
||||
## so downgrade the loaded type to mutable, mirroring the `cmdNifC` loader
|
||||
## which loads everything `Complete` for exactly this reason (see
|
||||
## `ast2nif.loadedState`).
|
||||
if t.state == Partial: loadType(t)
|
||||
if t.state == Sealed: t.state = Complete
|
||||
|
||||
proc owner*(s: PSym): lent PSym {.inline.} =
|
||||
proc owner*(s: PSym): PSym {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.ownerFieldImpl
|
||||
|
||||
proc owner*(s: PType): lent PSym {.inline.} =
|
||||
proc owner*(s: PType): PSym {.inline.} =
|
||||
if s.state == Partial: loadType(s)
|
||||
result = s.ownerFieldImpl
|
||||
|
||||
@@ -114,7 +97,7 @@ proc `kind=`*(s: PSym, val: TSymKind) {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.kindImpl = val
|
||||
|
||||
proc gcUnsafetyReason*(s: PSym): lent PSym {.inline.} =
|
||||
proc gcUnsafetyReason*(s: PSym): PSym {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.gcUnsafetyReasonImpl
|
||||
|
||||
@@ -123,7 +106,7 @@ proc `gcUnsafetyReason=`*(s: PSym, val: PSym) {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.gcUnsafetyReasonImpl = val
|
||||
|
||||
proc transformedBody*(s: PSym): lent PNode {.inline.} =
|
||||
proc transformedBody*(s: PSym): PNode {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.transformedBodyImpl
|
||||
|
||||
@@ -133,7 +116,7 @@ proc `transformedBody=`*(s: PSym, val: PNode) {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.transformedBodyImpl = val
|
||||
|
||||
proc guard*(s: PSym): lent PSym {.inline.} =
|
||||
proc guard*(s: PSym): PSym {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.guardImpl
|
||||
|
||||
@@ -169,7 +152,7 @@ proc `magic=`*(s: PSym, val: TMagic) {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.magicImpl = val
|
||||
|
||||
proc typ*(s: PSym): lent PType {.inline.} =
|
||||
proc typ*(s: PSym): PType {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.typImpl
|
||||
|
||||
@@ -215,7 +198,7 @@ proc `flags=`*(s: PSym, val: TSymFlags) {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.flagsImpl = val
|
||||
|
||||
proc ast*(s: PSym): lent PNode {.inline.} =
|
||||
proc ast*(s: PSym): PNode {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.astImpl
|
||||
|
||||
@@ -238,10 +221,7 @@ proc position*(s: PSym): int {.inline.} =
|
||||
result = s.positionImpl
|
||||
|
||||
proc `position=`*(s: PSym, val: int) {.inline.} =
|
||||
# No `Sealed` guard: the VM reuses `position` as a register slot while compiling
|
||||
# a macro for execution (see `vmgen.genGenericParams`), which under IC may be a
|
||||
# macro loaded from a NIF file. The macro is run, not code-generated, so this
|
||||
# scratch mutation is harmless.
|
||||
assert s.state != Sealed
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.positionImpl = val
|
||||
|
||||
@@ -263,7 +243,7 @@ proc `loc=`*(s: PSym, val: TLoc) {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.locImpl = val
|
||||
|
||||
proc annex*(s: PSym): lent PLib {.inline.} =
|
||||
proc annex*(s: PSym): PLib {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.annexImpl
|
||||
|
||||
@@ -282,7 +262,7 @@ when hasFFI:
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.cnameImpl = val
|
||||
|
||||
proc constraint*(s: PSym): lent PNode {.inline.} =
|
||||
proc constraint*(s: PSym): PNode {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.constraintImpl
|
||||
|
||||
@@ -291,7 +271,7 @@ proc `constraint=`*(s: PSym, val: PNode) {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.constraintImpl = val
|
||||
|
||||
proc instantiatedFrom*(s: PSym): lent PSym {.inline.} =
|
||||
proc instantiatedFrom*(s: PSym): PSym {.inline.} =
|
||||
if s.state == Partial: loadSym(s)
|
||||
result = s.instantiatedFromImpl
|
||||
|
||||
@@ -332,10 +312,7 @@ when defined(nimsuggest):
|
||||
result = s.allUsagesImpl
|
||||
|
||||
proc `allUsages=`*(s: PSym, val: sink seq[TLineInfo]) {.inline.} =
|
||||
# No `assert s.state != Sealed`: `allUsagesImpl` is nimsuggest-only usage
|
||||
# tracking, NOT part of the NIF-serialized symbol. nimsuggest loads symbols
|
||||
# as `Sealed` (ast2nif.loadedState under cmdM) yet `suggestSym` legitimately
|
||||
# records usages on them; the getter likewise doesn't assert.
|
||||
assert s.state != Sealed
|
||||
if s.state == Partial: loadSym(s)
|
||||
s.allUsagesImpl = val
|
||||
|
||||
@@ -359,18 +336,6 @@ proc `flags=`*(t: PType, val: TTypeFlags) {.inline.} =
|
||||
t.flagsImpl = val
|
||||
|
||||
proc sons*(t: PType): var TTypeSeq {.inline.} =
|
||||
## The RAW child seq. Despite the name this is NOT the counterpart of the
|
||||
## `sons` ITERATOR over a `PNode`, and it is not the way to walk a type's
|
||||
## children — use `kids` / `ikids` / `paramTypes` / `signature`, or the named
|
||||
## accessors (`returnType`, `baseClass`, `elementType`, `indexType`,
|
||||
## `genericHead`, ...), which say WHICH child they mean.
|
||||
##
|
||||
## The difference is not cosmetic. A `tyProc` keeps its parameter types in
|
||||
## `n`, not here — `setSons` asserts `sonsImpl.len <= 1` for one — so `[]`,
|
||||
## `len` and every iterator built on them route parameters through
|
||||
## `n[i].sym.typ`, while this seq holds only the return type. `for x in
|
||||
## t.sons` therefore compiles, looks like the `PNode` idiom, and silently
|
||||
## visits a different set of types.
|
||||
if t.state == Partial: loadType(t)
|
||||
result = t.sonsImpl
|
||||
|
||||
@@ -379,7 +344,7 @@ proc `sons=`*(t: PType, val: sink TTypeSeq) {.inline.} =
|
||||
if t.state == Partial: loadType(t)
|
||||
t.sonsImpl = val
|
||||
|
||||
proc n*(t: PType): lent PNode {.inline.} =
|
||||
proc n*(t: PType): PNode {.inline.} =
|
||||
if t.state == Partial: loadType(t)
|
||||
result = t.nImpl
|
||||
|
||||
@@ -388,7 +353,7 @@ proc `n=`*(t: PType, val: PNode) {.inline.} =
|
||||
if t.state == Partial: loadType(t)
|
||||
t.nImpl = val
|
||||
|
||||
proc sym*(t: PType): lent PSym {.inline.} =
|
||||
proc sym*(t: PType): PSym {.inline.} =
|
||||
if t.state == Partial: loadType(t)
|
||||
result = t.symImpl
|
||||
|
||||
@@ -430,7 +395,7 @@ proc `loc=`*(t: PType, val: TLoc) {.inline.} =
|
||||
if t.state == Partial: loadType(t)
|
||||
t.locImpl = val
|
||||
|
||||
proc typeInst*(t: PType): lent PType {.inline.} =
|
||||
proc typeInst*(t: PType): PType {.inline.} =
|
||||
if t.state == Partial: loadType(t)
|
||||
result = t.typeInstImpl
|
||||
|
||||
@@ -459,7 +424,7 @@ proc excl*(t: PType; flags: set[TTypeFlag]) {.inline.} =
|
||||
if t.state == Partial: loadType(t)
|
||||
t.flagsImpl.excl(flags)
|
||||
|
||||
proc typ*(n: PNode): lent PType {.inline.} =
|
||||
proc typ*(n: PNode): PType {.inline.} =
|
||||
result = n.typField
|
||||
if result == nil and nfLazyType in n.flags:
|
||||
result = n.sym.typ
|
||||
@@ -480,18 +445,12 @@ var gconfig {.threadvar.}: Gconfig
|
||||
proc setUseIc*(useIc: bool) = gconfig.useIc = useIc
|
||||
|
||||
proc comment*(n: PNode): string =
|
||||
if nfHasComment in n.flags:
|
||||
# NIF-based IC doesn't serialize comments, but the comment table is keyed by
|
||||
# the node's address (`nodeId`), which is unique among live nodes; a loaded
|
||||
# node that carries `nfHasComment` simply has no entry here (its comment was
|
||||
# set in another process), so `getOrDefault` safely returns "" for it while
|
||||
# in-process VM macro nodes (e.g. newCommentStmtNode) still round-trip.
|
||||
result = gconfig.comments.getOrDefault(n.nodeId)
|
||||
if nfHasComment in n.flags and not gconfig.useIc:
|
||||
# IC doesn't track comments, see `packed_ast`, so this could fail
|
||||
result = gconfig.comments[n.nodeId]
|
||||
else:
|
||||
result = ""
|
||||
|
||||
nodeCommentReader = proc(n: PNode): string {.nimcall.} = comment(n)
|
||||
|
||||
proc `comment=`*(n: PNode, a: string) =
|
||||
let id = n.nodeId
|
||||
if a.len > 0:
|
||||
@@ -507,8 +466,6 @@ proc `comment=`*(n: PNode, a: string) =
|
||||
n.flags.excl nfHasComment
|
||||
gconfig.comments.del(id)
|
||||
|
||||
nodeCommentWriter = proc(n: PNode; s: string) {.nimcall.} = n.comment = s
|
||||
|
||||
# BUGFIX: a module is overloadable so that a proc can have the
|
||||
# same name as an imported module. This is necessary because of
|
||||
# the poor naming choices in the standard library.
|
||||
@@ -521,8 +478,14 @@ proc getPIdent*(a: PNode): PIdent {.inline.} =
|
||||
of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: a.sons[0].sym.name
|
||||
else: nil
|
||||
|
||||
template id*(a: PSym): int = toId(a.itemId)
|
||||
template id*(a: PType): int = toId(a.bindingId)
|
||||
const
|
||||
moduleShift = when defined(cpu32): 20 else: 24
|
||||
|
||||
template toId*(a: ItemId): int =
|
||||
let x = a
|
||||
(x.module.int shl moduleShift) + x.item.int
|
||||
|
||||
template id*(a: PType | PSym): int = toId(a.itemId)
|
||||
|
||||
type
|
||||
IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here.
|
||||
@@ -530,62 +493,28 @@ type
|
||||
symId*: int32
|
||||
typeId*: int32
|
||||
sealed*: bool
|
||||
backendMinted*: bool
|
||||
disambTable*: CountTable[PIdent]
|
||||
|
||||
const
|
||||
PackageModuleId* = -3'i32
|
||||
|
||||
proc idGeneratorFromModule*(m: PSym): IdGenerator =
|
||||
assert m.kind == skModule
|
||||
result = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0, disambTable: initCountTable[PIdent]())
|
||||
result.disambTable.inc m.name
|
||||
|
||||
proc idGeneratorForBackend*(m: PSym): IdGenerator =
|
||||
## Like `idGeneratorFromModule`, but for IC codegen (`nim nifc`): symbols and
|
||||
## types minted fresh during codegen (transf labels/temps, lifted hooks, type
|
||||
## copies) must not collide with the itemIds the NIF loader synthesizes for
|
||||
## lazily-loaded symbols/types of the same module — those come from a
|
||||
## per-module load-order counter that keeps running while codegen mints its
|
||||
## own ids. A collision corrupts itemId-keyed tables, e.g. `transf`'s inline
|
||||
## iterator mapping then substitutes a random loaded sym (a call's callee)
|
||||
## with a `:tmp` block label. Backend-minted ids carry a marker bit in the
|
||||
## module half (see `itemids.backendItemId`), so the two id spaces are
|
||||
## disjoint by construction.
|
||||
assert m.kind == skModule
|
||||
result = IdGenerator(module: m.itemId.module, symId: 0, typeId: 0,
|
||||
backendMinted: true, disambTable: initCountTable[PIdent]())
|
||||
result.disambTable.inc m.name
|
||||
|
||||
proc idGeneratorForPackage*(nextIdWillBe: int32): IdGenerator =
|
||||
result = IdGenerator(module: PackageModuleId, symId: nextIdWillBe - 1'i32, typeId: 0, disambTable: initCountTable[PIdent]())
|
||||
|
||||
proc nextSymId(x: IdGenerator): ItemId {.inline.} =
|
||||
assert(not x.sealed)
|
||||
when not defined(nimKochBootstrap):
|
||||
if x.backendMinted:
|
||||
# Share the loader's per-module backend counter so a freshly-minted
|
||||
# backend sym never collides with an `@bk` sym loaded from the module's
|
||||
# `.t.bif` (see ast2nif.nextBackendSymItem).
|
||||
let it = nextBackendSymItem(program, x.module)
|
||||
if it >= 0'i32:
|
||||
return backendItemId(x.module, it)
|
||||
inc x.symId
|
||||
result = if x.backendMinted: backendItemId(x.module, x.symId)
|
||||
else: itemId(x.module, x.symId)
|
||||
result = ItemId(module: x.module, item: x.symId)
|
||||
|
||||
proc nextTypeId*(x: IdGenerator): ItemId {.inline.} =
|
||||
assert(not x.sealed)
|
||||
when not defined(nimKochBootstrap):
|
||||
if x.backendMinted:
|
||||
# Share the loader's per-module backend TYPE counter (seeded from the
|
||||
# module's `(unusedid)`) so a freshly-minted backend type sits ABOVE every
|
||||
# loaded type — never colliding with a frontend type's `toId` (the bug that
|
||||
# crashed cgen's `getTypeDescAux` cycle check on `AsyncBufferRef`). Mirrors
|
||||
# `nextSymId` (see ast2nif.nextBackendTypeItem).
|
||||
let it = nextBackendTypeItem(program, x.module)
|
||||
if it >= 0'i32:
|
||||
return backendItemId(x.module, it)
|
||||
inc x.typeId
|
||||
result = if x.backendMinted: backendItemId(x.module, x.typeId)
|
||||
else: itemId(x.module, x.typeId)
|
||||
result = ItemId(module: x.module, item: x.typeId)
|
||||
|
||||
when false:
|
||||
proc nextId*(x: IdGenerator): ItemId {.inline.} =
|
||||
@@ -777,28 +706,10 @@ when false:
|
||||
echo k
|
||||
echo v
|
||||
|
||||
when defined(icSymCount):
|
||||
import std / [syncio, exitprocs, tables as symCountTables]
|
||||
var symMints*: symCountTables.CountTable[string]
|
||||
var symMintTotal*: int
|
||||
var symCountHooked = false
|
||||
|
||||
proc newSym*(symKind: TSymKind, name: PIdent, idgen: IdGenerator; owner: PSym,
|
||||
info: TLineInfo; options: TOptions = {}): PSym =
|
||||
# generates a symbol and initializes the hash field too
|
||||
assert not name.isNil
|
||||
when defined(icSymCount):
|
||||
# Counting symbol MINTS, not their names in the output: a gensym's number is
|
||||
# its item id, so one extra symbol anywhere shifts every later name. A count
|
||||
# is therefore far more sensitive than diffing generated C, and it localises
|
||||
# the extra mint by kind instead of by whatever file happened to show it.
|
||||
inc symMintTotal
|
||||
symMints.inc $symKind
|
||||
if not symCountHooked:
|
||||
symCountHooked = true
|
||||
addExitProc proc () =
|
||||
stderr.writeLine "SYMMINT total=" & $symMintTotal
|
||||
for k, v in symMints: stderr.writeLine "SYMMINT " & k & "=" & $v
|
||||
let id = nextSymId idgen
|
||||
result = PSym(name: name, kindImpl: symKind, flagsImpl: {}, infoImpl: info, itemId: id,
|
||||
optionsImpl: options, ownerFieldImpl: owner, offsetImpl: defaultOffset,
|
||||
@@ -880,10 +791,6 @@ proc newSymNode*(sym: PSym): PNode =
|
||||
result = newNode(nkSym)
|
||||
result.sym = sym
|
||||
result.typField = sym.typ
|
||||
if result.typField == nil and nifcBackendActive:
|
||||
# See the two-arg overload in astdef: in the NIF backend cg stage a sym node
|
||||
# built from a not-yet-typed stub must track the symbol's type lazily.
|
||||
result.flags.incl nfLazyType
|
||||
result.info = sym.info
|
||||
|
||||
proc newOpenSym*(n: PNode): PNode {.inline.} =
|
||||
@@ -897,7 +804,7 @@ proc newIntNode*(kind: TNodeKind, intVal: Int128): PNode =
|
||||
result = newNode(kind)
|
||||
result.intVal = castToInt64(intVal)
|
||||
|
||||
proc lastSon*(n: PNode): lent PNode {.inline.} = n.sons[^1]
|
||||
proc lastSon*(n: PNode): PNode {.inline.} = n.sons[^1]
|
||||
template setLastSon*(n: PNode, s: PNode) = n.sons[^1] = s
|
||||
|
||||
template firstSon*(n: PNode): PNode = n.sons[0]
|
||||
@@ -919,29 +826,29 @@ proc last*(n: PType): PType {.inline.} =
|
||||
else:
|
||||
n.sonsImpl[^1]
|
||||
|
||||
proc elementType*(n: PType): lent PType {.inline.} =
|
||||
proc elementType*(n: PType): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
result = n.sonsImpl[^1]
|
||||
n.sonsImpl[^1]
|
||||
|
||||
proc skipModifier*(n: PType): lent PType {.inline.} =
|
||||
proc skipModifier*(n: PType): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
result = n.sonsImpl[^1]
|
||||
n.sonsImpl[^1]
|
||||
|
||||
proc indexType*(n: PType): lent PType {.inline.} =
|
||||
proc indexType*(n: PType): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
result = n.sonsImpl[0]
|
||||
n.sonsImpl[0]
|
||||
|
||||
proc baseClass*(n: PType): lent PType {.inline.} =
|
||||
proc baseClass*(n: PType): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
result = n.sonsImpl[0]
|
||||
n.sonsImpl[0]
|
||||
|
||||
proc base*(t: PType): lent PType {.inline.} =
|
||||
proc base*(t: PType): PType {.inline.} =
|
||||
if t.state == Partial: loadType(t)
|
||||
result = t.sonsImpl[0]
|
||||
|
||||
proc returnType*(n: PType): lent PType {.inline.} =
|
||||
proc returnType*(n: PType): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
result = n.sonsImpl[0]
|
||||
n.sonsImpl[0]
|
||||
|
||||
proc setReturnType*(n, r: PType) {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
@@ -958,17 +865,17 @@ proc firstParamType*(n: PType): PType {.inline.} =
|
||||
else:
|
||||
n.sonsImpl[1]
|
||||
|
||||
proc firstGenericParam*(n: PType): lent PType {.inline.} =
|
||||
proc firstGenericParam*(n: PType): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
result = n.sonsImpl[1]
|
||||
n.sonsImpl[1]
|
||||
|
||||
proc typeBodyImpl*(n: PType): lent PType {.inline.} =
|
||||
proc typeBodyImpl*(n: PType): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
result = n.sonsImpl[^1]
|
||||
n.sonsImpl[^1]
|
||||
|
||||
proc genericHead*(n: PType): lent PType {.inline.} =
|
||||
proc genericHead*(n: PType): PType {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
result = n.sonsImpl[0]
|
||||
n.sonsImpl[0]
|
||||
|
||||
proc skipTypes*(t: PType, kinds: TTypeKinds): PType =
|
||||
## Used throughout the compiler code to test whether a type tree contains or
|
||||
@@ -1128,7 +1035,7 @@ proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType
|
||||
let id = nextTypeId idgen
|
||||
result = PType(kind: kind, ownerFieldImpl: owner, sizeImpl: defaultSize,
|
||||
alignImpl: defaultAlignment, itemId: id,
|
||||
bindingId: id, sonsImpl: @[])
|
||||
uniqueId: id, sonsImpl: @[])
|
||||
if son != nil:
|
||||
assert kind != tyProc
|
||||
result.sonsImpl.add son
|
||||
@@ -1136,11 +1043,6 @@ proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType
|
||||
if result.itemId.module == 55 and result.itemId.item == 2:
|
||||
echo "KNID ", kind
|
||||
writeStackTrace()
|
||||
when defined(icDbg):
|
||||
if kind == tyOpenArray:
|
||||
echo "NEWTYPE openArray id=", id.module, ".", id.item,
|
||||
" owner=", (if owner != nil: owner.name.s else: "nil")
|
||||
echo getStackTrace()
|
||||
|
||||
proc setSons*(dest: PType; sons: sink seq[PType]) {.inline.} =
|
||||
assert dest.kind != tyProc or sons.len <= 1
|
||||
@@ -1203,24 +1105,10 @@ proc copyType*(t: PType, idgen: IdGenerator, owner: PSym): PType =
|
||||
assignType(result, t)
|
||||
result.symImpl = t.sym # backend-info should not be copied
|
||||
|
||||
proc exactReplica*(t: PType; idgen: IdGenerator): PType =
|
||||
## Copy that INHERITS `bindingId` — the generic-param binding tables
|
||||
## (`LayeredIdTable`) key on it, so the copy must keep matching its original
|
||||
## there — while getting its own `itemId`, like every other type. The two
|
||||
## remaining callers are `semtypinst.instCopyType` (a partially instantiated
|
||||
## meta type must still bind in the next instantiation round) and the
|
||||
## `tfUnresolved` typedesc replica in `semtypes.semTypeIdent`; everything
|
||||
## else that used to come through here is a plain `copyType`.
|
||||
##
|
||||
## Do not "simplify" this to share `itemId` as well: `itemId` is the
|
||||
## serialization identity, and replicas sharing it serialized as duplicate
|
||||
## defs under one NIF name, which the loader collapsed into a single type —
|
||||
## losing their flag differences (use-site `tfUnresolved` typedescs) or
|
||||
## their structure (meta instance bodies shadowing a generic's canonical
|
||||
## body).
|
||||
proc exactReplica*(t: PType): PType =
|
||||
result = PType(kind: t.kind, ownerFieldImpl: t.owner, sizeImpl: defaultSize,
|
||||
alignImpl: defaultAlignment, itemId: nextTypeId(idgen),
|
||||
bindingId: t.bindingId)
|
||||
alignImpl: defaultAlignment, itemId: t.itemId,
|
||||
uniqueId: t.uniqueId)
|
||||
assignType(result, t)
|
||||
result.symImpl = t.sym # backend-info should not be copied
|
||||
|
||||
@@ -1308,12 +1196,7 @@ proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
|
||||
let o2 = owner.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||
if o2.kind in {tyTuple, tyObject, tyArray,
|
||||
tySequence, tyString, tySet, tyDistinct}:
|
||||
if o2.state == Sealed:
|
||||
# During the original compilation, propagateToOwner set tfHasAsgn/tfHasOwned on the type before it was sealed
|
||||
# On IC reload, the sealed type already has those flags
|
||||
assert mask <= o2.flags, "IC bug: sealed type missing propagated flags"
|
||||
else:
|
||||
o2.incl mask
|
||||
o2.incl mask
|
||||
owner.incl mask
|
||||
|
||||
if owner.kind notin {tyProc, tyGenericInst, tyGenericBody,
|
||||
@@ -1383,15 +1266,11 @@ proc transitionNoneToSym*(n: PNode) =
|
||||
transitionNodeKindCommon(nkSym)
|
||||
|
||||
template transitionSymKindCommon*(k: TSymKind) =
|
||||
# Under IC the symbol may still be an unloaded stub (`skStub`); materialise it
|
||||
# first so its kind-specific fields (read below as `obj.*`) actually exist.
|
||||
if s.state == Partial: loadSym(s)
|
||||
let obj {.inject.} = s[]
|
||||
s[] = TSym(kindImpl: k, itemId: obj.itemId, magicImpl: obj.magicImpl, typImpl: obj.typImpl, name: obj.name,
|
||||
infoImpl: obj.infoImpl, ownerFieldImpl: obj.ownerFieldImpl, flagsImpl: obj.flagsImpl, astImpl: obj.astImpl,
|
||||
optionsImpl: obj.optionsImpl, positionImpl: obj.positionImpl, offsetImpl: obj.offsetImpl,
|
||||
disamb: obj.disamb, locImpl: obj.locImpl, annexImpl: obj.annexImpl, constraintImpl: obj.constraintImpl,
|
||||
instantiatedFromImpl: obj.instantiatedFromImpl)
|
||||
locImpl: obj.locImpl, annexImpl: obj.annexImpl, constraintImpl: obj.constraintImpl)
|
||||
when hasFFI:
|
||||
s.cnameImpl = obj.cnameImpl
|
||||
when defined(nimsuggest):
|
||||
@@ -1664,7 +1543,7 @@ proc isImportedException*(t: PType; conf: ConfigRef): bool =
|
||||
result = base.sym != nil and {sfCompileToCpp, sfImportc} * base.sym.flags != {}
|
||||
|
||||
proc isInfixAs*(n: PNode): bool =
|
||||
return n.kind == nkInfix and n.firstSon.kind == nkIdent and n.firstSon.ident.id == ord(wAs)
|
||||
return n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.id == ord(wAs)
|
||||
|
||||
proc skipColon*(n: PNode): PNode =
|
||||
result = n
|
||||
@@ -1744,83 +1623,28 @@ proc addParam*(procType: PType; param: PSym) =
|
||||
const magicsThatCanRaise = {
|
||||
mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst, mEcho}
|
||||
|
||||
# `canRaise` reaches the effect list through `effectsOf` / `raisesNothing`
|
||||
# rather than by subscripting `fn.typ.n`, so the layout is written down in one
|
||||
# place. Under `--ic:on` that list came back from a `.bif`, and whether it came
|
||||
# back intact is checked separately: `-d:icCanRaiseLog` logs every verdict, and
|
||||
# the same program built with and without `--ic:on` must produce the same ones.
|
||||
|
||||
when defined(icCanRaiseLog):
|
||||
var canRaiseBranch* = 0
|
||||
## Which branch decided the last answer: 1 = the symbol's magic/flags,
|
||||
## 2 = `mEcho`, 3 = the EFFECT LIST reached through `effectsOf`, 4 = the
|
||||
## conservative predicate, 5 = short-circuited in `canRaiseDisp` before
|
||||
## either predicate ran, 0 = fell through. Only branch 3 reads anything
|
||||
## that had to survive a `.bif` round trip, so a differential in which no
|
||||
## callee reaches it would prove nothing about the writer — which is the
|
||||
## whole point of running the differential. See `-d:icCanRaiseLog`.
|
||||
|
||||
template markCanRaiseBranch*(n: int) =
|
||||
when defined(icCanRaiseLog): canRaiseBranch = n
|
||||
|
||||
proc canRaiseConservative*(fn: PNode): bool =
|
||||
markCanRaiseBranch 4
|
||||
result = not (fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise)
|
||||
|
||||
proc effectsOf*(t: PType): PNode {.inline.} =
|
||||
## The `nkEffectList` a proc type carries as child 0 of its formal-params
|
||||
## node, with the parameters following from index 1 (`newProcType` builds it
|
||||
## that way; `cgen` reads the params back with `sonsFrom(prc.typ.n, 1)`).
|
||||
##
|
||||
## Named rather than subscripted so that the layout is written down in ONE
|
||||
## place. `.n` here is a TYPE's node, never a routine body, so it is always
|
||||
## fully materialised and `firstSon` is safe — the `nfLazyBody` hazard that
|
||||
## makes raw child access dangerous elsewhere (see `astdef.sons`) cannot reach
|
||||
## it. A proc type always has this child; `t.n` with no children is not a
|
||||
## shape the writer or sem produces, and this deliberately does not paper over
|
||||
## one appearing.
|
||||
result = if t.n == nil: nil else: t.n.firstSon
|
||||
|
||||
proc raisesNothing*(effects: PNode): bool =
|
||||
## Whether an effect list says DEFINITIVELY that nothing is raised: it is long
|
||||
## enough to have a raises slot at all, the slot is present, and it is empty.
|
||||
##
|
||||
## Every other shape — a list too short to carry the slot, an absent slot, a
|
||||
## non-empty one — means the effects are unspecified or non-empty, and a
|
||||
## caller must assume a raise. Stating it as the NEGATIVE is the point: the
|
||||
## safe default has to be "can raise", so the one narrow case that licenses
|
||||
## dropping an exception check is the one spelled out here, and a shape nobody
|
||||
## anticipated falls on the conservative side by construction rather than by
|
||||
## luck.
|
||||
result = effects != nil and effects.len >= effectListLen and
|
||||
effects[exceptionEffects] != nil and
|
||||
effects[exceptionEffects].safeLen == 0
|
||||
if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise:
|
||||
result = false
|
||||
else:
|
||||
result = true
|
||||
|
||||
proc canRaise*(fn: PNode): bool =
|
||||
if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or
|
||||
{sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or
|
||||
sfGeneratedOp in fn.sym.flags):
|
||||
markCanRaiseBranch 1
|
||||
result = false
|
||||
elif fn.kind == nkSym and fn.sym.magic == mEcho:
|
||||
markCanRaiseBranch 2
|
||||
result = true
|
||||
elif fn.typ != nil and fn.typ.kind == tyProc and fn.typ.n != nil:
|
||||
markCanRaiseBranch 3
|
||||
let effects = effectsOf(fn.typ)
|
||||
if effects.kind == nkSym:
|
||||
# The historical shape: slot 0 used to be an `nkType` before the effects
|
||||
# moved in (see `newProcType`). Nothing to read, so nothing licenses a
|
||||
# raise.
|
||||
# TODO check for n having sons? or just return false for now if not
|
||||
if fn.typ.n[0].kind == nkSym:
|
||||
result = false
|
||||
else:
|
||||
# A proc-typed value with no explicit raises slot still has
|
||||
# unspecified effects, which sempass2 treats conservatively.
|
||||
# Codegen needs to do the same in order to keep goto-exception
|
||||
# checks after indirect/closure calls.
|
||||
result = not raisesNothing(effects)
|
||||
result = ((fn.typ.n[0].len < effectListLen) or
|
||||
(fn.typ.n[0][exceptionEffects] != nil and
|
||||
fn.typ.n[0][exceptionEffects].safeLen > 0))
|
||||
else:
|
||||
markCanRaiseBranch 0
|
||||
result = false
|
||||
|
||||
proc toHumanStrImpl[T](kind: T, num: static int): string =
|
||||
@@ -1837,7 +1661,7 @@ proc toHumanStr*(kind: TTypeKind): string =
|
||||
result = toHumanStrImpl(kind, 2)
|
||||
|
||||
proc skipHiddenAddr*(n: PNode): PNode {.inline.} =
|
||||
(if n.kind == nkHiddenAddr: n.firstSon else: n)
|
||||
(if n.kind == nkHiddenAddr: n[0] else: n)
|
||||
|
||||
proc isNewStyleConcept*(n: PNode): bool {.inline.} =
|
||||
assert n.kind == nkTypeClassTy
|
||||
|
||||
4157
compiler/ast2nif.nim
4157
compiler/ast2nif.nim
File diff suppressed because it is too large
Load Diff
@@ -529,11 +529,8 @@ proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool =
|
||||
type
|
||||
TIdentIter* = object # iterator over all syms with same identifier
|
||||
h*: Hash # current hash
|
||||
name* {.cursor.}: PIdent
|
||||
name*: PIdent
|
||||
|
||||
# String tables are always initialized with non-empty, power-of-two storage,
|
||||
# and every probe is masked by `high(tab.data)`.
|
||||
{.push boundChecks: off.}
|
||||
proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
|
||||
# hot spots
|
||||
var h = ti.h and high(tab.data)
|
||||
@@ -551,7 +548,6 @@ proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
|
||||
else:
|
||||
result = nil
|
||||
ti.h = nextTry(h, high(tab.data))
|
||||
{.pop.}
|
||||
|
||||
proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym =
|
||||
ti.h = s.h
|
||||
@@ -639,14 +635,9 @@ proc getOrDefault*[T](t: TIdTable[T], key: ItemId): T =
|
||||
if index >= 0: result = t.data[index].val
|
||||
else: result = default(T)
|
||||
|
||||
template idTableGet*[T](t: TIdTable[T], key: PSym): T =
|
||||
template idTableGet*[T](t: TIdTable[T], key: PType | PSym): T =
|
||||
getOrDefault(t, key.itemId)
|
||||
|
||||
template idTableGet*[T](t: TIdTable[T], key: PType): T =
|
||||
## Type-keyed tables are BINDING tables: an `exactReplica` must find what its
|
||||
## original bound, hence `bindingId` and not the type's own identity.
|
||||
getOrDefault(t, key.bindingId)
|
||||
|
||||
proc idTableRawInsert[T](data: var TIdPairSeq[T], key: ItemId, val: T) =
|
||||
var h: Hash
|
||||
let keyId = toId(key)
|
||||
@@ -677,12 +668,9 @@ proc `[]=`*[T](t: var TIdTable[T], key: ItemId, val: T) =
|
||||
idTableRawInsert(t.data, key, val)
|
||||
inc(t.counter)
|
||||
|
||||
template idTablePut*[T](t: var TIdTable[T], key: PSym, val: T) =
|
||||
template idTablePut*[T](t: var TIdTable[T], key: PType | PSym, val: T) =
|
||||
t[key.itemId] = val
|
||||
|
||||
template idTablePut*[T](t: var TIdTable[T], key: PType, val: T) =
|
||||
t[key.bindingId] = val
|
||||
|
||||
iterator idTablePairs*[T](t: TIdTable[T]): tuple[key: ItemId, val: T] =
|
||||
for i in 0..high(t.data):
|
||||
if not isNil(t.data[i].key):
|
||||
@@ -741,6 +729,6 @@ proc listSymbolNames*(symbols: openArray[PSym]): string =
|
||||
result.add sym.name.s
|
||||
|
||||
proc isDiscriminantField*(n: PNode): bool =
|
||||
if n.kind == nkCheckedFieldExpr: sfDiscriminant in n.firstSon.secondSon.sym.flags
|
||||
elif n.kind == nkDotExpr: sfDiscriminant in n.secondSon.sym.flags
|
||||
if n.kind == nkCheckedFieldExpr: sfDiscriminant in n[0][1].sym.flags
|
||||
elif n.kind == nkDotExpr: sfDiscriminant in n[1].sym.flags
|
||||
else: false
|
||||
|
||||
@@ -17,21 +17,9 @@ when defined(nimPreviewSlimSystem):
|
||||
|
||||
export int128
|
||||
|
||||
var nifcBackendActive* = false
|
||||
## Set only while the per-module NIF backend codegen stage runs
|
||||
## (`nifbackend.generateCgStage`, `cmd == cmdNifC`). It gates `newSymNode`'s
|
||||
## lazy-type marking so it applies ONLY in the backend — where syms are loaded
|
||||
## from NIF and a cg-stage transform can build a sym node from a not-yet-typed
|
||||
## stub — and never during frontend sem, where the same marking would perturb
|
||||
## effect/exception inference (it diverges from a non-IC build, e.g.
|
||||
## `times.toDateTimeByWeek` gaining a spurious unlisted `Exception`).
|
||||
|
||||
import nodekinds
|
||||
export nodekinds
|
||||
|
||||
import itemids
|
||||
export itemids
|
||||
|
||||
type
|
||||
TCallingConvention* = enum
|
||||
ccNimCall = "nimcall" # nimcall, also the default
|
||||
@@ -339,14 +327,6 @@ type
|
||||
# because openSym experimental switch is disabled
|
||||
# gives warning instead
|
||||
nfLazyType # node has a lazy type
|
||||
nfLazyBody # IC: this node is a placeholder for a routine body (bodyPos son)
|
||||
# not yet materialized. Reading its children (via `len`/`safeLen`)
|
||||
# triggers `forceLazyBodyHook`. Process-local, stripped on serialize.
|
||||
nfBroadcast # this `nkBracket` is a *broadcast* default array: a single son
|
||||
# standing for `lengthOrd` identical zero copies (see
|
||||
# `broadcastArrayThreshold`). The flag disambiguates it from an
|
||||
# ordinary 1-element collection (e.g. a seq value that happens to
|
||||
# carry an array type), so it must survive copies + serialization.
|
||||
|
||||
TNodeFlags* = set[TNodeFlag]
|
||||
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 47)
|
||||
@@ -591,6 +571,23 @@ const
|
||||
generatedMagics* = {mNone, mIsolate, mFinished, mOpenArrayToSeq}
|
||||
## magics that are generated as normal procs in the backend
|
||||
|
||||
type
|
||||
ItemId* = object
|
||||
module*: int32
|
||||
item*: int32
|
||||
|
||||
proc `$`*(x: ItemId): string =
|
||||
"(module: " & $x.module & ", item: " & $x.item & ")"
|
||||
|
||||
proc `==`*(a, b: ItemId): bool {.inline.} =
|
||||
a.item == b.item and a.module == b.module
|
||||
|
||||
proc hash*(x: ItemId): Hash =
|
||||
var h: Hash = hash(x.module)
|
||||
h = h !& hash(x.item)
|
||||
result = !$h
|
||||
|
||||
|
||||
type
|
||||
PNode* = ref TNode
|
||||
TNodeSeq* = seq[PNode]
|
||||
@@ -681,10 +678,6 @@ type
|
||||
TInstantiation* = object
|
||||
sym*: PSym
|
||||
concreteTypes*: seq[PType]
|
||||
bindings*: seq[tuple[key: ItemId, value: PType]]
|
||||
## An optional exact snapshot of the matcher bindings. In-process
|
||||
## instances use it for a fast cache probe; serialized instances fall
|
||||
## back to comparing the fully instantiated signature.
|
||||
genericParamsCount*: int # for terrible reasons `concreteTypes` contains all the types,
|
||||
# so we need to know how many generic params there were
|
||||
# this is not serialized for IC and that is fine.
|
||||
@@ -708,7 +701,6 @@ type
|
||||
|
||||
PLib* = ref TLib
|
||||
TSym* {.acyclic.} = object # Keep in sync with ast2nif.nim
|
||||
# Check `transitionSymKindCommon` in ast.nim when add a new field.
|
||||
itemId*: ItemId
|
||||
# proc and type instantiations are cached in the generic symbol
|
||||
state*: ItemState
|
||||
@@ -784,16 +776,11 @@ type
|
||||
# same id; there may be multiple copies of a type
|
||||
# in memory!
|
||||
# Keep in sync with PackedType
|
||||
itemId*: ItemId # THE identity of this type: unique per instance, forever.
|
||||
# Names the type in the NIF cache and decides which
|
||||
# module owns its definition.
|
||||
itemId*: ItemId
|
||||
kind*: TTypeKind # kind of type
|
||||
state*: ItemState
|
||||
bindingId*: ItemId # the id of the type this one is a REPLICA of (its own
|
||||
# `itemId` when it is not a replica). Only the generic
|
||||
# binding tables (`LayeredIdTable` & friends) key on it:
|
||||
# `exactReplica` produces a copy that must keep matching
|
||||
# its original in those tables. Never an identity.
|
||||
uniqueId*: ItemId # due to a design mistake, we need to keep the real ID here as it
|
||||
# is required by the --incremental:on mode.
|
||||
callConvImpl*: TCallingConvention # for procs
|
||||
flagsImpl*: TTypeFlags # flags of the type
|
||||
sonsImpl*: TTypeSeq # base types, etc.
|
||||
@@ -883,8 +870,7 @@ const
|
||||
nfFromTemplate, nfDefaultRefsParam,
|
||||
nfExecuteOnReload, nfLastRead,
|
||||
nfFirstWrite, nfSkipFieldChecking,
|
||||
nfDisabledOpenSym, nfLazyType,
|
||||
nfBroadcast}
|
||||
nfDisabledOpenSym, nfLazyType}
|
||||
namePos* = 0
|
||||
patternPos* = 1 # empty except for term rewriting macros
|
||||
genericParamsPos* = 2
|
||||
@@ -921,24 +907,7 @@ const
|
||||
defaultOffset* = -1
|
||||
|
||||
|
||||
var forceLazyBodyHook*: proc (n: PNode) {.nimcall, raises: [], tags: [], gcsafe.}
|
||||
## Set by the IC loader (ast2nif). When a node carries `nfLazyBody`, any access
|
||||
## to its children through `len` materializes the deferred routine body in place.
|
||||
## `safeLen` delegates to `len`, so it is covered transitively; a lazy body is
|
||||
## never a leaf kind, so the `{nkNone..nkNilLit}` short-circuit never hides it.
|
||||
##
|
||||
## The type MUST be effect-free (`raises: []`/`tags: []`): `len` is a fundamental
|
||||
## `PNode` accessor that the whole compiler — and every compiler-as-library
|
||||
## consumer (nimble, nimsuggest, ...) — assumes cannot raise. An unannotated
|
||||
## `proc` var defaults to `raises: [Exception]`, so the indirect call tainted
|
||||
## `len`/`safeLen`/`items` with `Exception`, breaking any iterator/`{.raises.}`
|
||||
## over a `PNode` (e.g. nimble's `extract {.raises: [CatchableError].}`).
|
||||
## Materialization is a pure in-memory buffer transform; a corrupt buffer is a
|
||||
## `Defect` (`raiseAssert`), which is outside exception tracking.
|
||||
|
||||
proc len*(n: PNode): int {.inline.} =
|
||||
if nfLazyBody in n.flags and forceLazyBodyHook != nil:
|
||||
forceLazyBodyHook(n)
|
||||
result = n.sons.len
|
||||
|
||||
proc safeLen*(n: PNode): int {.inline.} =
|
||||
@@ -955,57 +924,6 @@ template `[]=`*(n: PNode, i: BackwardsIndex; x: PNode) = n[n.len - i.int] = x
|
||||
iterator items*(n: PNode): PNode =
|
||||
for i in 0..<n.safeLen: yield n[i]
|
||||
|
||||
iterator sons*(n: PNode): PNode =
|
||||
## Iterates over the children of `n`. Preferred over `for i in 0..<n.len: n[i]`
|
||||
## as it does not rely on random indexed access, and over `for x in n.sons`,
|
||||
## which reads the raw FIELD and so skips the `len` hook that materialises a
|
||||
## deferred `nfLazyBody` body — over such a body that loop silently visits
|
||||
## nothing.
|
||||
for i in 0..<n.safeLen: yield n[i]
|
||||
|
||||
iterator isons*(n: PNode; start = 0): tuple[i: int, n: PNode] =
|
||||
## Like `sons` but also yields the child index, and optionally skips the first
|
||||
## `start` children. Replaces `for i in start..<n.len: ... n[i] ...` when `i`
|
||||
## itself is still needed — for a parameter position, a `needTmp[i-1]` lookup,
|
||||
## a parallel index into the routine's `PType`, and so on. `start` is almost
|
||||
## always 1, to step over a call's callee or a case statement's selector.
|
||||
##
|
||||
## Use `sonsFrom` instead when the index is only ever used to subscript `n`.
|
||||
for i in start..<n.safeLen: yield (i, n[i])
|
||||
|
||||
iterator sonsFrom*(n: PNode; start: int): PNode =
|
||||
## `sons` skipping the first `start` children. Replaces
|
||||
## `for i in start..<n.len: ... n[i] ...`, which is by far the commonest
|
||||
## indexed shape in the code generator — `start` is almost always 1, to step
|
||||
## over a case/try statement's selector or a call's callee.
|
||||
for i in start..<n.safeLen: yield n[i]
|
||||
|
||||
iterator sonsButLast*(n: PNode; count = 1): PNode =
|
||||
## `sons` without the last `count` children. Replaces `for i in 0..<n.len-1:
|
||||
## ... n[i] ...`, which is what an `nkOfBranch`/`nkExceptBranch` walk looks
|
||||
## like: the last child is the branch BODY, the ones before it are the labels
|
||||
## it matches. `count = 2` is the `nkVarTuple`/`nkIdentDefs` shape, whose last
|
||||
## two children are the type and the value. A `Cursor` can serve this with a
|
||||
## single pass and `count` nodes of lookahead; the indexed form has to re-walk
|
||||
## the children for every label.
|
||||
##
|
||||
## Use `isonsButLast` instead when the index is still needed.
|
||||
for i in 0 ..< n.safeLen - count: yield n[i]
|
||||
|
||||
iterator isonsButLast*(n: PNode; count = 1): tuple[i: int, n: PNode] =
|
||||
## Like `sonsButLast` but also yields the child index — for a tuple field
|
||||
## position, a parallel index into the tuple's `PType`, and so on.
|
||||
for i in 0 ..< n.safeLen - count: yield (i, n[i])
|
||||
|
||||
template son*(n: PNode; i: int): PNode =
|
||||
## Named indexed access to child `i`, for the small constant positions that
|
||||
## `firstSon`/`secondSon`/`lastSon` do not cover.
|
||||
n[i]
|
||||
|
||||
template hasSons*(n: PNode): bool =
|
||||
## Emptiness test; goes through `safeLen` so a deferred body is materialised.
|
||||
n.safeLen > 0
|
||||
|
||||
when defined(useNodeIds):
|
||||
const nodeIdToDebug* = -1 # 2322968
|
||||
var gNodeId: int
|
||||
@@ -1065,14 +983,6 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
|
||||
result = newNode(nkSym)
|
||||
result.sym = sym
|
||||
result.typField = sym.typImpl
|
||||
if result.typField == nil and nifcBackendActive:
|
||||
# In the per-module NIF backend cg stage a transform (chronos async
|
||||
# closure-iterator lowering) builds `result = …` sym nodes from a not-yet-typed
|
||||
# NIF stub; snapshotting the nil here would leave the node permanently typeless
|
||||
# and the backend later reads `t.flags` off it and SIGSEGVs (injectdestructors
|
||||
# hasDestructor). Mark it lazy so `typ` re-reads `sym.typ` once resolved. Gated
|
||||
# on `nifcBackendActive` so frontend sem is untouched (see the flag's doc).
|
||||
result.flags.incl nfLazyType
|
||||
result.info = info
|
||||
|
||||
proc newStrNode*(kind: TNodeKind, strVal: string): PNode =
|
||||
@@ -1087,56 +997,9 @@ proc newStrNode*(strVal: string; info: TLineInfo): PNode =
|
||||
# handling for IC, they end up in IC indexes etc. Thus we "log" them in the module graph
|
||||
# and to pass them around to the NIF writer. This is not very elegant but it works.
|
||||
|
||||
const
|
||||
InstanceDisambBit* = 0x4000_0000'i32
|
||||
## Set in the `disamb` of routine instances whose value is content-derived
|
||||
## (see `modulegraphs.setInstanceDisamb`); keeps them disjoint from the
|
||||
## small counter range ordinary symbols draw from, so the NIF name
|
||||
## `name.disamb.module` stays collision-free within a module.
|
||||
HookDisambBit* = 0x2000_0000'i32
|
||||
## Set in the `disamb` of synthesized type-bound operators and `$enum`
|
||||
## procs whose value is content-derived (see `modulegraphs.setHookDisamb`);
|
||||
## disjoint from both the small counter range and `InstanceDisambBit`.
|
||||
##
|
||||
## Both live here rather than in `modulegraphs` because `ast2nif` — which
|
||||
## cannot import that module — names symbols by them.
|
||||
|
||||
proc backendMintedDisamb*(s: PSym): int32 {.inline.} =
|
||||
## The integer that identifies a BACKEND-MINTED symbol (`isBackendMinted`) in
|
||||
## every name derived from it: its NIF name (`ast2nif.toNifSymName`) and its C
|
||||
## name (`mangleutils.mangleProcNameExt`, `ccgutils.makeUnique`).
|
||||
##
|
||||
## Two cases, and the whole point of having ONE function is that all three
|
||||
## sites take the same one:
|
||||
##
|
||||
## * A lifted HOOK's `disamb` is CONTENT-derived (`modulegraphs.setHookDisamb`),
|
||||
## so it is identical in every process. Such a hook really does cross process
|
||||
## boundaries — `lower` mints the env hooks of nested routines while `cg`
|
||||
## mints those of the module's top level, and both land in the same
|
||||
## translation unit — and its C name is also baked into emit-everywhere RTTI
|
||||
## tables. `itemId.item` would differ per process, so two unrelated hooks
|
||||
## collided on one `_c<item>` and the merge stage kept a single body for both
|
||||
## (C accepted the mistyped call, C++ rejected it).
|
||||
## * Otherwise `itemId.item` — the writer's dedup identity, unique per `@bk`
|
||||
## sym. `disamb` cannot serve here: a module's `:env` syms are minted from TWO
|
||||
## id spaces (the backend `lower` stage's idgen and sem's `vmTransfIdgen`)
|
||||
## whose `disambTable`s each start `:env` at the same low count, so a
|
||||
## macro-lowered and a backend-lowered `:env` collide on `:env.2.<mod>@bk`.
|
||||
##
|
||||
## The loader copies the name's numeric component back into `disamb`, so after a
|
||||
## round trip `disamb` equals this value and `ast2nif.globalName` — which always
|
||||
## reads `disamb` — agrees with the name the writer produced.
|
||||
##
|
||||
## This rule used to be written out at each of the three sites. They drifted:
|
||||
## `toNifSymName` lacked the hook exception, so a content-derived value was
|
||||
## overwritten by the loader and two backend hooks merged into one C function.
|
||||
if (s.disamb and HookDisambBit) != 0'i32: s.disamb
|
||||
else: s.itemId.item
|
||||
|
||||
type
|
||||
LogEntryKind* = enum
|
||||
HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry,
|
||||
PureEnumEntry, CppMemberEntry
|
||||
HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry
|
||||
LogEntry* = object
|
||||
kind*: LogEntryKind
|
||||
op*: TTypeAttachedOp
|
||||
@@ -1183,7 +1046,7 @@ proc forcePartial*(s: PSym) =
|
||||
proc forcePartial*(t: PType) =
|
||||
## Resets all impl-fields to their default values and sets state to Partial.
|
||||
## This is useful for creating a stub type that can be lazily loaded later.
|
||||
## The fields itemId, kind, bindingId are preserved.
|
||||
## The fields itemId, kind, uniqueId are preserved.
|
||||
t.state = Partial
|
||||
t.callConvImpl = ccNimCall
|
||||
t.flagsImpl = {}
|
||||
@@ -1201,11 +1064,8 @@ const # for all kind of hash tables:
|
||||
GrowthFactor* = 2 # must be power of 2, > 0
|
||||
StartSize* = 8 # must be power of 2, > 0
|
||||
|
||||
{.push overflowChecks: off.}
|
||||
proc nextTry*(h, maxHash: Hash): Hash {.inline.} =
|
||||
# Overflow is intentional: only the low bits selected by maxHash are used.
|
||||
result = ((5 * h) + 1) and maxHash
|
||||
{.pop.}
|
||||
# For any initial h in range(maxHash), repeating that maxHash times
|
||||
# generates each int in range(maxHash) exactly once (see any text on
|
||||
# random-number generation for proof).
|
||||
@@ -1302,11 +1162,3 @@ proc strTableGet*(t: TStrTable, name: PIdent): PSym =
|
||||
if result == nil: break
|
||||
if result.name.id == name.id: break
|
||||
h = nextTry(h, high(t.data))
|
||||
|
||||
# --- doc-comment bridge for the NIF serializer -------------------------------
|
||||
# `ast2nif` (the NIF reader/writer) cannot import `ast` (where the comment
|
||||
# accessor and its `gconfig.comments` side table live) because `ast` imports
|
||||
# `ast2nif`. These hooks are assigned by `ast` and let the serializer carry a
|
||||
# decl's `##` doc comment across a NIF round-trip.
|
||||
var nodeCommentReader*: proc(n: PNode): string {.nimcall.}
|
||||
var nodeCommentWriter*: proc(n: PNode; s: string) {.nimcall.}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -11,17 +11,7 @@
|
||||
|
||||
proc canRaiseDisp(p: BProc; n: PNode): bool =
|
||||
# we assume things like sysFatal cannot raise themselves
|
||||
# 5 = "decided here, neither predicate ran". Without resetting, the marker
|
||||
# keeps whatever the PREVIOUS call left in it and the early return below
|
||||
# attributes this answer to a branch that did not execute — which is how the
|
||||
# first run of this differential came to claim effect-list coverage it did
|
||||
# not have. Both short-circuits below leave it at 5.
|
||||
markCanRaiseBranch 5
|
||||
if n.kind == nkSym and n.sym.kind == skMethod:
|
||||
# A base method may be overridden by a branch with a wider exception set.
|
||||
# Its inferred effects describe only the base body, not every vtable target.
|
||||
result = true
|
||||
elif n.kind == nkSym and {sfNeverRaises, sfImportc, sfCompilerProc} * n.sym.flags != {}:
|
||||
if n.kind == nkSym and {sfNeverRaises, sfImportc, sfCompilerProc} * n.sym.flags != {}:
|
||||
result = false
|
||||
elif optPanics in p.config.globalOptions or
|
||||
(n.kind == nkSym and sfSystemModule in getModule(n.sym).flags and
|
||||
@@ -31,13 +21,6 @@ proc canRaiseDisp(p: BProc; n: PNode): bool =
|
||||
else:
|
||||
# we have to be *very* conservative:
|
||||
result = canRaiseConservative(n)
|
||||
when defined(icCanRaiseLog):
|
||||
# `canRaise` reads the raises spec off `fn.typ.n`, and under `--ic:on` that
|
||||
# node came back from a `.bif`. The only oracle for whether it came back
|
||||
# INTACT is the same program built without IC. Log the verdict per callee;
|
||||
# the two builds must produce the same one.
|
||||
if n.kind == nkSym:
|
||||
logCanRaise(n.sym, result)
|
||||
|
||||
proc preventNrvo(p: BProc; dest, le, ri: PNode): bool =
|
||||
proc locationEscapes(p: BProc; le: PNode; inTryStmt: bool): bool =
|
||||
@@ -57,29 +40,31 @@ proc preventNrvo(p: BProc; dest, le, ri: PNode): bool =
|
||||
return false
|
||||
of nkDotExpr, nkBracketExpr, nkObjUpConv, nkObjDownConv,
|
||||
nkCheckedFieldExpr:
|
||||
n = n.firstSon
|
||||
n = n[0]
|
||||
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
|
||||
n = n.secondSon
|
||||
n = n[1]
|
||||
else:
|
||||
# cannot analyse the location; assume the worst
|
||||
return true
|
||||
|
||||
result = false
|
||||
if le != nil:
|
||||
for r in sonsFrom(ri, 1):
|
||||
if isPartOf(le, r, {pfStructural}) != arNo: return true
|
||||
for i in 1..<ri.len:
|
||||
let r = ri[i]
|
||||
if isPartOf(le, r) != arNo: return true
|
||||
# we use the weaker 'canRaise' here in order to prevent too many
|
||||
# annoying warnings, see #14514
|
||||
if canRaise(ri.firstSon) and
|
||||
if canRaise(ri[0]) and
|
||||
locationEscapes(p, le, p.nestedTryStmts.len > 0):
|
||||
message(p.config, le.info, warnObservableStores, $le)
|
||||
# bug #19613 prevent dangerous aliasing too:
|
||||
if dest != nil and dest != le:
|
||||
for r in sonsFrom(ri, 1):
|
||||
if isPartOf(dest, r, {pfStructural}) != arNo: return true
|
||||
for i in 1..<ri.len:
|
||||
let r = ri[i]
|
||||
if isPartOf(dest, r) != arNo: return true
|
||||
|
||||
proc hasNoInit(call: PNode): bool {.inline.} =
|
||||
result = call.firstSon.kind == nkSym and sfNoInit in call.firstSon.sym.flags
|
||||
result = call[0].kind == nkSym and sfNoInit in call[0].sym.flags
|
||||
|
||||
proc isHarmlessStore(p: BProc; canRaise: bool; d: TLoc): bool =
|
||||
if d.k in {locTemp, locNone} or not canRaise:
|
||||
@@ -110,12 +95,12 @@ proc cleanupTemp(p: BProc; returnType: PType, tmp: TLoc): bool =
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc fixupCall(p: BProc, le: PNode, ri: PNode, d: var TLoc,
|
||||
proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc,
|
||||
result: var Builder, call: var CallBuilder) =
|
||||
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri.firstSon)
|
||||
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri[0])
|
||||
genLineDir(p, ri)
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri.firstSon.typ, abstractInst)
|
||||
var typ = skipTypes(ri[0].typ, abstractInst)
|
||||
if typ.returnType != nil:
|
||||
var flags: TAssignmentFlags = {}
|
||||
if typ.returnType.kind in {tyOpenArray, tyVarargs}:
|
||||
@@ -199,9 +184,9 @@ proc reifiedOpenArray(n: PNode): bool {.inline.} =
|
||||
while true:
|
||||
case x.kind
|
||||
of {nkAddr, nkHiddenAddr, nkHiddenDeref}:
|
||||
x = x.firstSon
|
||||
x = x[0]
|
||||
of nkHiddenStdConv:
|
||||
x = x.secondSon
|
||||
x = x[1]
|
||||
else:
|
||||
break
|
||||
if x.kind == nkSym and x.sym.kind == skParam:
|
||||
@@ -210,9 +195,9 @@ proc reifiedOpenArray(n: PNode): bool {.inline.} =
|
||||
result = true
|
||||
|
||||
proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareForMutation = false): (Rope, Rope) =
|
||||
var a = initLocExpr(p, q.secondSon)
|
||||
var b = initLocExpr(p, son(q, 2))
|
||||
var c = initLocExpr(p, son(q, 3))
|
||||
var a = initLocExpr(p, q[1])
|
||||
var b = initLocExpr(p, q[2])
|
||||
var c = initLocExpr(p, q[3])
|
||||
# bug #23321: In the function mapType, ptrs (tyPtr, tyVar, tyLent, tyRef)
|
||||
# are mapped into ctPtrToArray, the dereference of which is skipped
|
||||
# in the `genDeref`. We need to skip these ptrs here
|
||||
@@ -238,36 +223,27 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareF
|
||||
let lit = cIntLiteral(first)
|
||||
result = (cCast(ptrType(dest), cOp(Add, NimInt, ra, cOp(Sub, NimInt, rb, lit))), lengthExpr)
|
||||
of tyOpenArray, tyVarargs:
|
||||
let data = if reifiedOpenArray(q.secondSon): dotField(ra, "Field0") else: ra
|
||||
let data = if reifiedOpenArray(q[1]): dotField(ra, "Field0") else: ra
|
||||
result = (cCast(ptrType(dest), cOp(Add, NimInt, data, rb)), lengthExpr)
|
||||
of tyUncheckedArray, tyCstring:
|
||||
result = (cCast(ptrType(dest), cOp(Add, NimInt, ra, rb)), lengthExpr)
|
||||
of tyString, tySequence:
|
||||
let atyp = skipTypes(a.t, abstractInst)
|
||||
if formalType.skipTypes(abstractInst).kind in {tyVar} and atyp.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
|
||||
optSeqDestructors in p.config.globalOptions:
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
|
||||
bra)
|
||||
if p.config.usesSso() and
|
||||
skipTypes(a.t, abstractVar + abstractInst).kind == tyString:
|
||||
let strPtr = if atyp.kind in {tyVar} and not compileToCpp(p.module): ra
|
||||
else: addrLoc(p.config, a)
|
||||
result = (
|
||||
cCast(ptrType(dest), cOp(Add, NimInt,
|
||||
cCall(cgsymValue(p.module, "nimStrData"), strPtr), rb)),
|
||||
lengthExpr)
|
||||
var val: Snippet
|
||||
if atyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
val = cDeref(ra)
|
||||
else:
|
||||
var val: Snippet
|
||||
if atyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
val = cDeref(ra)
|
||||
else:
|
||||
val = ra
|
||||
result = (
|
||||
cIfExpr(dataFieldAccessor(p, val),
|
||||
cCast(ptrType(dest), cOp(Add, NimInt, dataField(p, val), rb)),
|
||||
NimNil),
|
||||
lengthExpr)
|
||||
val = ra
|
||||
result = (
|
||||
cIfExpr(dataFieldAccessor(p, val),
|
||||
cCast(ptrType(dest), cOp(Add, NimInt, dataField(p, val), rb)),
|
||||
NimNil),
|
||||
lengthExpr)
|
||||
else:
|
||||
result = ("", "")
|
||||
internalError(p.config, "openArrayLoc: " & typeToString(a.t))
|
||||
@@ -275,23 +251,23 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareF
|
||||
proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
|
||||
var q = skipConv(n)
|
||||
var skipped = false
|
||||
while q.kind == nkStmtListExpr and q.hasSons:
|
||||
while q.kind == nkStmtListExpr and q.len > 0:
|
||||
skipped = true
|
||||
q = q.lastSon
|
||||
if getMagic(q) == mSlice:
|
||||
# magic: pass slice to openArray:
|
||||
if skipped:
|
||||
q = skipConv(n)
|
||||
while q.kind == nkStmtListExpr and q.hasSons:
|
||||
for it in sonsButLast(q):
|
||||
genStmts(p, it)
|
||||
while q.kind == nkStmtListExpr and q.len > 0:
|
||||
for i in 0..<q.len-1:
|
||||
genStmts(p, q[i])
|
||||
q = q.lastSon
|
||||
let (x, y) = genOpenArraySlice(p, q, formalType, n.typ.elementType)
|
||||
result.add(x)
|
||||
result.addArgumentSeparator()
|
||||
result.add(y)
|
||||
else:
|
||||
var a = initLocExpr(p, if n.kind == nkHiddenStdConv: n.secondSon else: n)
|
||||
var a = initLocExpr(p, if n.kind == nkHiddenStdConv: n[1] else: n)
|
||||
case skipTypes(a.t, abstractVar+{tyStatic}).kind
|
||||
of tyOpenArray, tyVarargs:
|
||||
let ra = rdLoc(a)
|
||||
@@ -311,22 +287,11 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
|
||||
of tyString, tySequence:
|
||||
let ntyp = skipTypes(n.typ, abstractInst)
|
||||
if formalType.skipTypes(abstractInst).kind in {tyVar} and ntyp.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
|
||||
optSeqDestructors in p.config.globalOptions:
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
|
||||
bra)
|
||||
if p.config.usesSso() and
|
||||
skipTypes(n.typ, abstractVar + abstractInst).kind == tyString:
|
||||
if ntyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
let ra = a.rdLoc
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrData"), ra))
|
||||
result.addArgumentSeparator()
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrLen"), cDeref(ra)))
|
||||
else:
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrData"), addrLoc(p.config, a)))
|
||||
result.addArgumentSeparator()
|
||||
result.add(lenExpr(p, a))
|
||||
elif ntyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
if ntyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
let ra = a.rdLoc
|
||||
var t = TLoc(snippet: cDeref(ra))
|
||||
let lt = lenExpr(p, t)
|
||||
@@ -350,14 +315,9 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
|
||||
let ra = a.rdLoc
|
||||
var t = TLoc(snippet: cDeref(ra))
|
||||
let lt = lenExpr(p, t)
|
||||
if p.config.usesSso():
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrData"), ra))
|
||||
result.addArgumentSeparator()
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrLen"), t.snippet))
|
||||
else:
|
||||
result.add(cIfExpr(dataFieldAccessor(p, t.snippet), dataField(p, t.snippet), NimNil))
|
||||
result.addArgumentSeparator()
|
||||
result.add(lt)
|
||||
result.add(cIfExpr(dataFieldAccessor(p, t.snippet), dataField(p, t.snippet), NimNil))
|
||||
result.addArgumentSeparator()
|
||||
result.add(lt)
|
||||
of tyArray:
|
||||
let ra = rdLoc(a)
|
||||
result.add(ra)
|
||||
@@ -383,9 +343,8 @@ proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc =
|
||||
genAssignment(p, result, a, {})
|
||||
|
||||
proc genArgStringToCString(p: BProc, n: PNode; result: var Builder; needsTmp: bool) {.inline.} =
|
||||
var a = initLocExpr(p, n.firstSon)
|
||||
let tmp = withTmpIfNeeded(p, a, needsTmp)
|
||||
let ra = if p.config.usesSso(): byRefLoc(p, tmp) else: tmp.rdLoc
|
||||
var a = initLocExpr(p, n[0])
|
||||
let ra = withTmpIfNeeded(p, a, needsTmp).rdLoc
|
||||
result.addCall(cgsymValue(p.module, "nimToCStringConv"), ra)
|
||||
|
||||
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; needsTmp = false) =
|
||||
@@ -393,9 +352,9 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n
|
||||
if n.kind == nkStringToCString:
|
||||
genArgStringToCString(p, n, result, needsTmp)
|
||||
elif skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs}:
|
||||
var n = if n.kind != nkHiddenAddr: n else: n.firstSon
|
||||
var n = if n.kind != nkHiddenAddr: n else: n[0]
|
||||
openArrayLoc(p, param.typ, n, result)
|
||||
elif ccgIntroducedPtr(p.config, param, call.firstSon.typ.returnType) and
|
||||
elif ccgIntroducedPtr(p.config, param, call[0].typ.returnType) and
|
||||
(optByRef notin param.options or not p.module.compileToCpp):
|
||||
a = initLocExpr(p, n)
|
||||
if n.kind in {nkCharLit..nkNilLit}:
|
||||
@@ -407,16 +366,16 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n
|
||||
# bug #23748: we need to introduce a temporary here. The expression type
|
||||
# will be a reference in C++ and we cannot create a temporary reference
|
||||
# variable. Thus, we create a temporary pointer variable instead.
|
||||
let needsIndirect = mapType(p.config, n.firstSon.typ, mapTypeChooser(n.firstSon) == skParam) != ctArray
|
||||
let needsIndirect = mapType(p.config, n[0].typ, mapTypeChooser(n[0]) == skParam) != ctArray
|
||||
if needsIndirect:
|
||||
n.typ = copyType(n.typ, p.module.idgen, n.typ.owner)
|
||||
n.typ = n.typ.exactReplica
|
||||
n.typ.incl tfVarIsPtr
|
||||
a = initLocExprSingleUse(p, n)
|
||||
a = withTmpIfNeeded(p, a, needsTmp)
|
||||
if needsIndirect: a.flags.incl lfIndirect
|
||||
# if the proc is 'importc'ed but not 'importcpp'ed then 'var T' still
|
||||
# means '*T'. See posix.nim for lots of examples that do that in the wild.
|
||||
let callee = call.firstSon
|
||||
let callee = call[0]
|
||||
if callee.kind == nkSym and
|
||||
{sfImportc, sfInfixCall, sfCompilerProc} * callee.sym.flags == {sfImportc} and
|
||||
{lfHeader, lfNoDecl} * callee.sym.loc.flags != {} and
|
||||
@@ -454,9 +413,9 @@ proc skipTrivialIndirections(n: PNode): PNode =
|
||||
while true:
|
||||
case result.kind
|
||||
of nkDerefExpr, nkHiddenDeref, nkAddr, nkHiddenAddr, nkObjDownConv, nkObjUpConv:
|
||||
result = result.firstSon
|
||||
result = result[0]
|
||||
of nkHiddenStdConv, nkHiddenSubConv:
|
||||
result = result.secondSon
|
||||
result = result[1]
|
||||
else: break
|
||||
|
||||
proc getPotentialReads(n: PNode; result: var seq[PNode]) =
|
||||
@@ -464,47 +423,44 @@ proc getPotentialReads(n: PNode; result: var seq[PNode]) =
|
||||
of nkLiterals, nkIdent, nkFormalParams: discard
|
||||
of nkSym: result.add n
|
||||
else:
|
||||
for s in sons(n):
|
||||
for s in n:
|
||||
getPotentialReads(s, result)
|
||||
|
||||
proc genParams(p: BProc, ri: PNode, typ: PType; result: var Builder, argBuilder: var CallBuilder) =
|
||||
# We must generate temporaries in cases like #14396
|
||||
# to keep the strict Left-To-Right evaluation
|
||||
# The arguments are walked BACKWARDS below; collect them once and index that.
|
||||
var args: seq[PNode] = @[]
|
||||
for it in sonsFrom(ri, 1): args.add it
|
||||
var needTmp = newSeq[bool](args.len)
|
||||
var needTmp = newSeq[bool](ri.len - 1)
|
||||
var potentialWrites: seq[PNode] = @[]
|
||||
for i in countdown(args.high, 0):
|
||||
if args[i].skipTrivialIndirections.kind == nkSym:
|
||||
needTmp[i] = potentialAlias(args[i], potentialWrites)
|
||||
for i in countdown(ri.len - 1, 1):
|
||||
if ri[i].skipTrivialIndirections.kind == nkSym:
|
||||
needTmp[i - 1] = potentialAlias(ri[i], potentialWrites)
|
||||
else:
|
||||
#if not args[i].typ.isCompileTimeOnly:
|
||||
#if not ri[i].typ.isCompileTimeOnly:
|
||||
var potentialReads: seq[PNode] = @[]
|
||||
getPotentialReads(args[i], potentialReads)
|
||||
getPotentialReads(ri[i], potentialReads)
|
||||
for n in potentialReads:
|
||||
if not needTmp[i]:
|
||||
needTmp[i] = potentialAlias(n, potentialWrites)
|
||||
getPotentialWrites(args[i], false, potentialWrites)
|
||||
if not needTmp[i - 1]:
|
||||
needTmp[i - 1] = potentialAlias(n, potentialWrites)
|
||||
getPotentialWrites(ri[i], false, potentialWrites)
|
||||
when false:
|
||||
# this optimization is wrong, see bug #23748
|
||||
if args[i].kind in {nkHiddenAddr, nkAddr}:
|
||||
if ri[i].kind in {nkHiddenAddr, nkAddr}:
|
||||
# Optimization: don't use a temp, if we would only take the address anyway
|
||||
needTmp[i] = false
|
||||
needTmp[i - 1] = false
|
||||
|
||||
for i, it in isons(ri, 1):
|
||||
for i in 1..<ri.len:
|
||||
if i < typ.n.len:
|
||||
assert(son(typ.n, i).kind == nkSym)
|
||||
let paramType = son(typ.n, i)
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
let paramType = typ.n[i]
|
||||
if not paramType.typ.isCompileTimeOnly:
|
||||
var arg = newBuilder("")
|
||||
genArg(p, it, paramType.sym, ri, arg, needTmp[i-1])
|
||||
genArg(p, ri[i], paramType.sym, ri, arg, needTmp[i-1])
|
||||
if arg.buf.len != 0:
|
||||
result.addArgument(argBuilder):
|
||||
result.add(extract(arg))
|
||||
else:
|
||||
var arg = newBuilder("")
|
||||
genArgNoParam(p, it, arg, needTmp[i-1])
|
||||
genArgNoParam(p, ri[i], arg, needTmp[i-1])
|
||||
if arg.buf.len != 0:
|
||||
result.addArgument(argBuilder):
|
||||
result.add(extract(arg))
|
||||
@@ -514,23 +470,23 @@ proc addActualSuffixForHCR(res: var Rope, module: PSym, sym: PSym) =
|
||||
(sym.typ.callConv == ccInline or sym.owner.id == module.id):
|
||||
res = res & "_actual".rope
|
||||
|
||||
proc genPrefixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
|
||||
proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
# this is a hotspot in the compiler
|
||||
var op = initLocExpr(p, ri.firstSon)
|
||||
var op = initLocExpr(p, ri[0])
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri.firstSon.typ, abstractInstOwned)
|
||||
var typ = skipTypes(ri[0].typ, abstractInstOwned)
|
||||
assert(typ.kind == tyProc)
|
||||
|
||||
var callee = rdLoc(op)
|
||||
if p.hcrOn and ri.firstSon.kind == nkSym:
|
||||
callee.addActualSuffixForHCR(p.module.module, ri.firstSon.sym)
|
||||
if p.hcrOn and ri[0].kind == nkSym:
|
||||
callee.addActualSuffixForHCR(p.module.module, ri[0].sym)
|
||||
|
||||
var res = newBuilder("")
|
||||
var call = initCallBuilder(res, callee)
|
||||
genParams(p, ri, typ, res, call)
|
||||
fixupCall(p, le, ri, d, res, call)
|
||||
|
||||
proc genClosureCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
|
||||
proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
|
||||
template callProc(rp, params, pTyp: Snippet): Snippet =
|
||||
let e = dotField(rp, "ClE_0")
|
||||
@@ -555,22 +511,16 @@ proc genClosureCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
|
||||
else:
|
||||
cCall(p, params, e)
|
||||
|
||||
var op = initLocExpr(p, ri.firstSon)
|
||||
var op = initLocExpr(p, ri[0])
|
||||
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri.firstSon.typ, abstractInstOwned)
|
||||
var typ = skipTypes(ri[0].typ, abstractInstOwned)
|
||||
assert(typ.kind == tyProc)
|
||||
|
||||
var params = newBuilder("")
|
||||
var argBuilder = default(CallBuilder) # not initCallBuilder, we just want the params
|
||||
genParams(p, ri, typ, params, argBuilder)
|
||||
|
||||
# `rawProc` is bound BEFORE the `{.dirty.}` template that uses it. Inside a
|
||||
# generic proc a dirty template's identifiers resolve at instantiation, and a
|
||||
# local declared after the template loses to the module-level `rawProc` proc
|
||||
# — which type-checks as a completely different thing.
|
||||
let rawProc = getClosureType(p.module, typ, clHalf)
|
||||
|
||||
template genCallPattern {.dirty.} =
|
||||
let rp = rdLoc(op)
|
||||
let pars = extract(params)
|
||||
@@ -579,7 +529,9 @@ proc genClosureCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
|
||||
p.s(cpsStmts).add(callIter(rp, pars))
|
||||
else:
|
||||
p.s(cpsStmts).add(callProc(rp, pars, rawProc))
|
||||
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri.firstSon)
|
||||
|
||||
let rawProc = getClosureType(p.module, typ, clHalf)
|
||||
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri[0])
|
||||
if typ.returnType != nil:
|
||||
if isInvalidReturnType(p.config, typ):
|
||||
# beware of 'result = p(result)'. We may need to allocate a temporary:
|
||||
@@ -635,22 +587,22 @@ proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder;
|
||||
if i < typ.n.len:
|
||||
# 'var T' is 'T&' in C++. This means we ignore the request of
|
||||
# any nkHiddenAddr when it's a 'var T'.
|
||||
let paramType = son(typ.n, i)
|
||||
let paramType = typ.n[i]
|
||||
assert(paramType.kind == nkSym)
|
||||
if paramType.typ.isCompileTimeOnly:
|
||||
discard
|
||||
elif paramType.typ.kind in {tyVar} and son(ri, i).kind == nkHiddenAddr:
|
||||
elif paramType.typ.kind in {tyVar} and ri[i].kind == nkHiddenAddr:
|
||||
result.addArgument(argBuilder):
|
||||
genArgNoParam(p, son(ri, i).firstSon, result)
|
||||
genArgNoParam(p, ri[i][0], result)
|
||||
else:
|
||||
result.addArgument(argBuilder):
|
||||
genArgNoParam(p, son(ri, i), result) #, son(typ.n, i).sym)
|
||||
genArgNoParam(p, ri[i], result) #, typ.n[i].sym)
|
||||
else:
|
||||
if tfVarargs notin typ.flags:
|
||||
localError(p.config, ri.info, "wrong argument count")
|
||||
else:
|
||||
result.addArgument(argBuilder):
|
||||
genArgNoParam(p, son(ri, i), result)
|
||||
genArgNoParam(p, ri[i], result)
|
||||
|
||||
discard """
|
||||
Dot call syntax in C++
|
||||
@@ -694,16 +646,16 @@ proc skipAddrDeref(node: PNode): PNode =
|
||||
var isAddr = false
|
||||
case n.kind
|
||||
of nkAddr, nkHiddenAddr:
|
||||
n = n.firstSon
|
||||
n = n[0]
|
||||
isAddr = true
|
||||
of nkDerefExpr, nkHiddenDeref:
|
||||
n = n.firstSon
|
||||
n = n[0]
|
||||
else: return n
|
||||
if n.kind == nkObjDownConv: n = n.firstSon
|
||||
if n.kind == nkObjDownConv: n = n[0]
|
||||
if isAddr and n.kind in {nkDerefExpr, nkHiddenDeref}:
|
||||
result = n.firstSon
|
||||
result = n[0]
|
||||
elif n.kind in {nkAddr, nkHiddenAddr}:
|
||||
result = n.firstSon
|
||||
result = n[0]
|
||||
else:
|
||||
result = node
|
||||
|
||||
@@ -712,34 +664,34 @@ proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder) =
|
||||
# However manual wrappers may also use 'ptr T'. In any case we support both
|
||||
# for convenience.
|
||||
internalAssert p.config, i < typ.n.len
|
||||
assert(son(typ.n, i).kind == nkSym)
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
# if the parameter is lying (tyVar) and thus we required an additional deref,
|
||||
# skip the deref:
|
||||
var ri = son(ri, i)
|
||||
while ri.kind == nkObjDownConv: ri = ri.firstSon
|
||||
var ri = ri[i]
|
||||
while ri.kind == nkObjDownConv: ri = ri[0]
|
||||
let t = typ[i].skipTypes({tyGenericInst, tyAlias, tySink})
|
||||
if t.kind in {tyVar}:
|
||||
let x = if ri.kind == nkHiddenAddr: ri.firstSon else: ri
|
||||
let x = if ri.kind == nkHiddenAddr: ri[0] else: ri
|
||||
if x.typ.kind == tyPtr:
|
||||
genArgNoParam(p, x, result)
|
||||
result.add("->")
|
||||
elif x.kind in {nkHiddenDeref, nkDerefExpr} and x.firstSon.typ.kind == tyPtr:
|
||||
genArgNoParam(p, x.firstSon, result)
|
||||
elif x.kind in {nkHiddenDeref, nkDerefExpr} and x[0].typ.kind == tyPtr:
|
||||
genArgNoParam(p, x[0], result)
|
||||
result.add("->")
|
||||
else:
|
||||
genArgNoParam(p, x, result)
|
||||
result.add(".")
|
||||
elif t.kind == tyPtr:
|
||||
if ri.kind in {nkAddr, nkHiddenAddr}:
|
||||
genArgNoParam(p, ri.firstSon, result)
|
||||
genArgNoParam(p, ri[0], result)
|
||||
result.add(".")
|
||||
else:
|
||||
genArgNoParam(p, ri, result)
|
||||
result.add("->")
|
||||
else:
|
||||
ri = skipAddrDeref(ri)
|
||||
if ri.kind in {nkAddr, nkHiddenAddr}: ri = ri.firstSon
|
||||
genArgNoParam(p, ri, result) #, son(typ.n, i).sym)
|
||||
if ri.kind in {nkAddr, nkHiddenAddr}: ri = ri[0]
|
||||
genArgNoParam(p, ri, result) #, typ.n[i].sym)
|
||||
result.add(".")
|
||||
|
||||
proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Builder) =
|
||||
@@ -749,20 +701,20 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Bu
|
||||
case pat[i]
|
||||
of '@':
|
||||
var callBuilder = default(CallBuilder) # not init call builder
|
||||
for k, _ in isons(ri, j):
|
||||
for k in j..<ri.len:
|
||||
genOtherArg(p, ri, k, typ, result, callBuilder)
|
||||
inc i
|
||||
of '#':
|
||||
if i+1 < pat.len and pat[i+1] in {'+', '@'}:
|
||||
let ri = son(ri, j)
|
||||
let ri = ri[j]
|
||||
if ri.kind in nkCallKinds:
|
||||
let typ = skipTypes(ri.firstSon.typ, abstractInst)
|
||||
if pat[i+1] == '+': genArgNoParam(p, ri.firstSon, result)
|
||||
let typ = skipTypes(ri[0].typ, abstractInst)
|
||||
if pat[i+1] == '+': genArgNoParam(p, ri[0], result)
|
||||
result.add("(")
|
||||
if 1 < ri.len:
|
||||
var callBuilder: CallBuilder = default(CallBuilder)
|
||||
genOtherArg(p, ri, 1, typ, result, callBuilder)
|
||||
for k, _ in isons(ri, j+1):
|
||||
for k in j+1..<ri.len:
|
||||
var callBuilder: CallBuilder = default(CallBuilder)
|
||||
genOtherArg(p, ri, k, typ, result, callBuilder)
|
||||
result.add(")")
|
||||
@@ -773,8 +725,8 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Bu
|
||||
genThisArg(p, ri, j, typ, result)
|
||||
inc i
|
||||
elif i+1 < pat.len and pat[i+1] == '[':
|
||||
var arg = son(ri, j).skipAddrDeref
|
||||
while arg.kind in {nkAddr, nkHiddenAddr, nkObjDownConv}: arg = arg.firstSon
|
||||
var arg = ri[j].skipAddrDeref
|
||||
while arg.kind in {nkAddr, nkHiddenAddr, nkObjDownConv}: arg = arg[0]
|
||||
genArgNoParam(p, arg, result)
|
||||
#result.add debugTree(arg, 0, 10)
|
||||
else:
|
||||
@@ -796,19 +748,19 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Bu
|
||||
if i - 1 >= start:
|
||||
result.add(substr(pat, start, i - 1))
|
||||
|
||||
proc genInfixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
|
||||
var op = initLocExpr(p, ri.firstSon)
|
||||
proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
var op = initLocExpr(p, ri[0])
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri.firstSon.typ, abstractInst)
|
||||
var typ = skipTypes(ri[0].typ, abstractInst)
|
||||
assert(typ.kind == tyProc)
|
||||
# don't call '$' here for efficiency:
|
||||
let pat = $ri.firstSon.sym.loc.snippet
|
||||
let pat = $ri[0].sym.loc.snippet
|
||||
internalAssert p.config, pat.len > 0
|
||||
if pat.contains({'#', '(', '@', '\''}):
|
||||
var pl = newBuilder("")
|
||||
genPatternCall(p, ri, pat, typ, pl)
|
||||
# simpler version of 'fixupCall' that works with the pl+params combination:
|
||||
var typ = skipTypes(ri.firstSon.typ, abstractInst)
|
||||
var typ = skipTypes(ri[0].typ, abstractInst)
|
||||
if typ.returnType != nil:
|
||||
if p.module.compileToCpp and lfSingleUse in d.flags:
|
||||
# do not generate spurious temporaries for C++! For C we're better off
|
||||
@@ -833,20 +785,20 @@ proc genInfixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
|
||||
pl.add(op.snippet)
|
||||
var res = newBuilder("")
|
||||
var call = initCallBuilder(res, extract(pl))
|
||||
for i, _ in isons(ri, 2):
|
||||
for i in 2..<ri.len:
|
||||
genOtherArg(p, ri, i, typ, res, call)
|
||||
fixupCall(p, le, ri, d, res, call)
|
||||
|
||||
proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
|
||||
# generates a crappy ObjC call
|
||||
var op = initLocExpr(p, ri.firstSon)
|
||||
var op = initLocExpr(p, ri[0])
|
||||
var pl = newBuilder("[")
|
||||
# getUniqueType() is too expensive here:
|
||||
var typ = skipTypes(ri.firstSon.typ, abstractInst)
|
||||
var typ = skipTypes(ri[0].typ, abstractInst)
|
||||
assert(typ.kind == tyProc)
|
||||
|
||||
# don't call '$' here for efficiency:
|
||||
let pat = $ri.firstSon.sym.loc.snippet
|
||||
let pat = $ri[0].sym.loc.snippet
|
||||
internalAssert p.config, pat.len > 0
|
||||
var start = 3
|
||||
if ' ' in pat:
|
||||
@@ -854,25 +806,25 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
|
||||
pl.add(op.snippet)
|
||||
if ri.len > 1:
|
||||
pl.add(": ")
|
||||
genArg(p, ri.secondSon, typ.n.secondSon.sym, ri, pl)
|
||||
genArg(p, ri[1], typ.n[1].sym, ri, pl)
|
||||
start = 2
|
||||
else:
|
||||
if ri.len > 1:
|
||||
genArg(p, ri.secondSon, typ.n.secondSon.sym, ri, pl)
|
||||
genArg(p, ri[1], typ.n[1].sym, ri, pl)
|
||||
pl.add(" ")
|
||||
pl.add(op.snippet)
|
||||
if ri.len > 2:
|
||||
pl.add(": ")
|
||||
genArg(p, son(ri, 2), son(typ.n, 2).sym, ri, pl)
|
||||
for i, it in isons(ri, start):
|
||||
genArg(p, ri[2], typ.n[2].sym, ri, pl)
|
||||
for i in start..<ri.len:
|
||||
if i >= typ.n.len:
|
||||
internalError(p.config, ri.info, "varargs for objective C method?")
|
||||
assert(son(typ.n, i).kind == nkSym)
|
||||
var param = son(typ.n, i).sym
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
var param = typ.n[i].sym
|
||||
pl.add(" ")
|
||||
pl.add(param.name.s)
|
||||
pl.add(": ")
|
||||
genArg(p, it, param, ri, pl)
|
||||
genArg(p, ri[i], param, ri, pl)
|
||||
if typ.returnType != nil:
|
||||
if isInvalidReturnType(p.config, typ):
|
||||
if ri.len > 1: pl.add(" ")
|
||||
@@ -925,27 +877,17 @@ proc isInactiveDestructorCall(p: BProc, e: PNode): bool =
|
||||
We want to return early but the 'finally' section is traversed before
|
||||
the 'let args = ...' statement. We exploit this to generate better
|
||||
code for 'return'. ]#
|
||||
result = e.safeLen == 2 and e.firstSon.kind == nkSym and
|
||||
e.firstSon.sym.name.s == "=destroy" and notYetAlive(e.secondSon.skipAddr)
|
||||
result = e.len == 2 and e[0].kind == nkSym and
|
||||
e[0].sym.name.s == "=destroy" and notYetAlive(e[1].skipAddr)
|
||||
|
||||
proc genAsgnCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
|
||||
proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
if p.withinBlockLeaveActions > 0 and isInactiveDestructorCall(p, ri):
|
||||
return
|
||||
when defined(icDbgHash):
|
||||
if ri.firstSon.typ == nil:
|
||||
echo "NILCALLEE kind=", ri.firstSon.kind,
|
||||
" sym=", (if ri.firstSon.kind == nkSym: ri.firstSon.sym.name.s else: "-"),
|
||||
" symKind=", (if ri.firstSon.kind == nkSym: $ri.firstSon.sym.kind else: "-"),
|
||||
" flags=", (if ri.firstSon.kind == nkSym: $ri.firstSon.sym.flags else: "-"),
|
||||
" lazy=", nfLazyType in ri.firstSon.flags,
|
||||
" inProc=", (if p.prc != nil: p.prc.name.s else: "NIL"),
|
||||
" module=", p.module.module.name.s
|
||||
raiseAssert "nil callee type, see NILCALLEE above"
|
||||
if ri.firstSon.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
|
||||
if ri[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
|
||||
genClosureCall(p, le, ri, d)
|
||||
elif ri.firstSon.kind == nkSym and sfInfixCall in ri.firstSon.sym.flags:
|
||||
elif ri[0].kind == nkSym and sfInfixCall in ri[0].sym.flags:
|
||||
genInfixCall(p, le, ri, d)
|
||||
elif ri.firstSon.kind == nkSym and sfNamedParamCall in ri.firstSon.sym.flags:
|
||||
elif ri[0].kind == nkSym and sfNamedParamCall in ri[0].sym.flags:
|
||||
genNamedParamCall(p, ri, d)
|
||||
else:
|
||||
genPrefixCall(p, le, ri, d)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,11 +22,10 @@ template detectVersion(field, corename) =
|
||||
result = 1
|
||||
|
||||
proc detectStrVersion(m: BModule): int =
|
||||
if m.g.config.usesSso() and
|
||||
m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
|
||||
result = 3
|
||||
else:
|
||||
detectVersion(strVersion, "nimStrVersion")
|
||||
detectVersion(strVersion, "nimStrVersion")
|
||||
|
||||
proc detectSeqVersion(m: BModule): int =
|
||||
detectVersion(seqVersion, "nimSeqVersion")
|
||||
|
||||
# ----- Version 1: GC'ed strings and seqs --------------------------------
|
||||
|
||||
@@ -129,175 +128,19 @@ proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Bu
|
||||
result.addField(strInit, name = "p"):
|
||||
result.add(cCast(ptrType("NimStrPayload"), cAddr(pureLit)))
|
||||
|
||||
proc ssoBytesLit(m: BModule; s: string; slen: int): string =
|
||||
## Compute the `bytes` field value for the new SmallString layout.
|
||||
## byte 0 = slen, bytes 1-7 = inline chars 0-6 (zero-padded).
|
||||
## On LE: slen in bits 0-7, char[i] in bits (i+1)*8..(i+1)*8+7.
|
||||
## On BE: slen in bits 56-63, char[i] in bits (6-i)*8..(6-i)*8+7.
|
||||
const AlwaysAvail = 7
|
||||
var val: uint64
|
||||
if CPU[m.g.config.target.targetCPU].endian == littleEndian:
|
||||
val = uint64(slen)
|
||||
for i in 0..<min(s.len, AlwaysAvail):
|
||||
val = val or (uint64(s[i]) shl (uint(i + 1) * 8))
|
||||
else:
|
||||
val = uint64(slen) shl 56
|
||||
for i in 0..<min(s.len, AlwaysAvail):
|
||||
val = val or (uint64(s[i]) shl (uint(AlwaysAvail - 1 - i) * 8))
|
||||
# Cast to NU (C name for Nim's uint, = NU64 on 64-bit). NU64 = uint64_t.
|
||||
result = cCast("NU", $val & "ULL")
|
||||
|
||||
proc ssoMoreLit(m: BModule; s: string): string =
|
||||
## For medium string literals (AlwaysAvail < len <= PayloadSize), encode
|
||||
## chars[AlwaysAvail..ptrSize-1] in the 'more' pointer field bit-pattern.
|
||||
## The last pointer byte is always '\0' (null terminator), guaranteed by
|
||||
## PayloadSize = AlwaysAvail + ptrSize - 1. slen <= PayloadSize guards
|
||||
## prevent any code from dereferencing this as an actual pointer.
|
||||
const AlwaysAvail = 7
|
||||
let ptrSize = m.g.config.target.ptrSize
|
||||
var val: uint64 = 0
|
||||
for i in 0..<ptrSize:
|
||||
let ch: uint64 = if AlwaysAvail + i < s.len: uint64(s[AlwaysAvail + i]) else: 0
|
||||
if CPU[m.g.config.target.targetCPU].endian == littleEndian:
|
||||
val = val or (ch shl (uint(i) * 8))
|
||||
else:
|
||||
val = val or (ch shl (uint(ptrSize - 1 - i) * 8))
|
||||
result = cCast(ptrType("LongString"), "(uintptr_t)" & $val)
|
||||
|
||||
proc genStringLiteralV3Const(m: BModule; n: PNode; isConst: bool; result: var Builder) =
|
||||
# Inline SmallString struct initializer for use inside const aggregate types.
|
||||
# Layout: {bytes: NimUint, more: ptr LongString}
|
||||
# bytes = slen (low byte) | char[0]<<8 | char[1]<<16 | ... | char[6]<<56
|
||||
const AlwaysAvail = 7
|
||||
let s = n.strVal
|
||||
|
||||
cgsym(m, "SmallString")
|
||||
cgsym(m, "LongString")
|
||||
|
||||
let payloadSize = AlwaysAvail + m.g.config.target.ptrSize - 1
|
||||
var si: StructInitializer
|
||||
result.addStructInitializer(si, kind = siOrderedStruct):
|
||||
if s.len <= AlwaysAvail:
|
||||
result.addField(si, name = "bytes"):
|
||||
result.add(ssoBytesLit(m, s, s.len))
|
||||
result.addField(si, name = "more"):
|
||||
result.add(NimNil)
|
||||
elif s.len <= payloadSize:
|
||||
# Medium string: bytes holds slen + chars 0-6; more holds chars 7..PayloadSize-1.
|
||||
result.addField(si, name = "bytes"):
|
||||
result.add(ssoBytesLit(m, s, s.len))
|
||||
result.addField(si, name = "more"):
|
||||
result.add(ssoMoreLit(m, s))
|
||||
else:
|
||||
# Emit the LongString block into cfsStrData and reference it inline.
|
||||
let dataName = getTempName(m)
|
||||
var res = newBuilder("")
|
||||
res.addVarWithTypeAndInitializer(
|
||||
if isConst: AlwaysConst else: Global,
|
||||
name = dataName):
|
||||
res.addSimpleStruct(m, name = "", baseType = ""):
|
||||
res.addField(name = "rc", typ = NimInt)
|
||||
res.addField(name = "fullLen", typ = NimInt)
|
||||
res.addField(name = "capImpl", typ = NimInt)
|
||||
res.addArrayField(name = "data", elementType = NimChar, len = s.len + 1)
|
||||
do:
|
||||
var di: StructInitializer
|
||||
res.addStructInitializer(di, kind = siOrderedStruct):
|
||||
res.addField(di, name = "fullLen"):
|
||||
res.addIntValue(s.len)
|
||||
res.addField(di, name = "rc"):
|
||||
res.addIntValue(1)
|
||||
res.addField(di, name = "capImpl"):
|
||||
res.addIntValue(0) # static, never freed
|
||||
res.addField(di, name = "data"):
|
||||
res.add(makeCString(s))
|
||||
m.s[cfsStrData].add(extract(res))
|
||||
# slen = StaticSlen (254): marks this as a static (never-freed) long string.
|
||||
result.addField(si, name = "bytes"):
|
||||
result.add(ssoBytesLit(m, s, 254))
|
||||
result.addField(si, name = "more"):
|
||||
result.add(cCast(ptrType("LongString"), cAddr(dataName)))
|
||||
|
||||
# ------ Version 3: SmallString (SSO) strings --------------------------------
|
||||
|
||||
proc genStringLiteralV3(m: BModule; n: PNode; isConst: bool; result: var Builder) =
|
||||
# SmallString literal. Always generate a fresh SmallString variable (like v2
|
||||
# always generates a fresh outer NimStringV2). For long strings, cache the
|
||||
# LongString payload to avoid duplicates within a module.
|
||||
const AlwaysAvail = 7 # must match strs_v3.nim
|
||||
let s = n.strVal
|
||||
let tmp = getTempName(m)
|
||||
result.add tmp
|
||||
|
||||
cgsym(m, "SmallString")
|
||||
cgsym(m, "LongString")
|
||||
|
||||
let payloadSize = AlwaysAvail + m.g.config.target.ptrSize - 1
|
||||
var res = newBuilder("")
|
||||
if s.len <= AlwaysAvail:
|
||||
# Short: bytes holds slen + all chars (zero-padded), more = NULL.
|
||||
res.addVarWithInitializer(
|
||||
if isConst: AlwaysConst else: Global,
|
||||
name = tmp, typ = "SmallString"):
|
||||
var si: StructInitializer
|
||||
res.addStructInitializer(si, kind = siOrderedStruct):
|
||||
res.addField(si, name = "bytes"):
|
||||
res.add(ssoBytesLit(m, s, s.len))
|
||||
res.addField(si, name = "more"):
|
||||
res.add(NimNil)
|
||||
elif s.len <= payloadSize:
|
||||
# Medium: bytes holds slen + chars 0-6; more holds chars 7..PayloadSize-1 as raw bits.
|
||||
res.addVarWithInitializer(
|
||||
if isConst: AlwaysConst else: Global,
|
||||
name = tmp, typ = "SmallString"):
|
||||
var si: StructInitializer
|
||||
res.addStructInitializer(si, kind = siOrderedStruct):
|
||||
res.addField(si, name = "bytes"):
|
||||
res.add(ssoBytesLit(m, s, s.len))
|
||||
res.addField(si, name = "more"):
|
||||
res.add(ssoMoreLit(m, s))
|
||||
else:
|
||||
# Long: cache the LongString block to emit it only once per module per string.
|
||||
# Always generate a fresh SmallString pointing at the (possibly cached) block.
|
||||
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
|
||||
var dataName: string
|
||||
if id == m.labels:
|
||||
dataName = getTempName(m)
|
||||
res.addVarWithTypeAndInitializer(
|
||||
if isConst: AlwaysConst else: Global,
|
||||
name = dataName):
|
||||
res.addSimpleStruct(m, name = "", baseType = ""):
|
||||
res.addField(name = "rc", typ = NimInt)
|
||||
res.addField(name = "fullLen", typ = NimInt)
|
||||
res.addField(name = "capImpl", typ = NimInt)
|
||||
res.addArrayField(name = "data", elementType = NimChar, len = s.len + 1)
|
||||
do:
|
||||
var di: StructInitializer
|
||||
res.addStructInitializer(di, kind = siOrderedStruct):
|
||||
res.addField(di, name = "fullLen"):
|
||||
res.addIntValue(s.len)
|
||||
res.addField(di, name = "rc"):
|
||||
res.addIntValue(1)
|
||||
res.addField(di, name = "capImpl"):
|
||||
res.addIntValue(0) # bit 0 = 0: static, never freed
|
||||
res.addField(di, name = "data"):
|
||||
res.add(makeCString(s))
|
||||
else:
|
||||
dataName = m.tmpBase & $id
|
||||
# slen = StaticSlen (254): marks this as a static (never-freed) long string.
|
||||
res.addVarWithInitializer(
|
||||
if isConst: AlwaysConst else: Global,
|
||||
name = tmp, typ = "SmallString"):
|
||||
var si: StructInitializer
|
||||
res.addStructInitializer(si, kind = siOrderedStruct):
|
||||
res.addField(si, name = "bytes"):
|
||||
res.add(ssoBytesLit(m, s, 254))
|
||||
res.addField(si, name = "more"):
|
||||
res.add(cCast(ptrType("LongString"), cAddr(dataName)))
|
||||
m.s[cfsStrData].add(extract(res))
|
||||
|
||||
# ------ Version selector ---------------------------------------------------
|
||||
|
||||
proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo;
|
||||
isConst: bool; result: var Rope) =
|
||||
case detectStrVersion(m)
|
||||
of 0, 1: genStringLiteralDataOnlyV1(m, s, result)
|
||||
of 2:
|
||||
let tmp = getTempName(m)
|
||||
genStringLiteralDataOnlyV2(m, s, tmp, isConst)
|
||||
result.add tmp
|
||||
else:
|
||||
localError(m.config, info, "cannot determine how to produce code for string literal")
|
||||
|
||||
proc genNilStringLiteral(m: BModule; info: TLineInfo; result: var Builder) =
|
||||
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil))
|
||||
|
||||
@@ -305,6 +148,5 @@ proc genStringLiteral(m: BModule; n: PNode; result: var Builder) =
|
||||
case detectStrVersion(m)
|
||||
of 0, 1: genStringLiteralV1(m, n, result)
|
||||
of 2: genStringLiteralV2(m, n, isConst = true, result)
|
||||
of 3: genStringLiteralV3(m, n, isConst = true, result)
|
||||
else:
|
||||
localError(m.config, n.info, "cannot determine how to produce code for string literal")
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -59,10 +59,10 @@ proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string =
|
||||
result = "_Z" # Common prefix in Itanium ABI
|
||||
var params = ""
|
||||
var staticLists = ""
|
||||
if s.typ.paramsLen > 0: # we dont care about the return param
|
||||
for _, pt in paramTypes(s.typ):
|
||||
if pt.isNil: continue
|
||||
params.add encodeType(m, pt, staticLists)
|
||||
if s.typ.len > 1: #we dont care about the return param
|
||||
for i in 1..<s.typ.len:
|
||||
if s.typ[i].isNil: continue
|
||||
params.add encodeType(m, s.typ[i], staticLists)
|
||||
|
||||
result.add encodeSym(m, s, makeUnique, staticLists)
|
||||
result.add params
|
||||
@@ -72,67 +72,20 @@ proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string =
|
||||
else:
|
||||
m.g.mangledPrcs.incl(result)
|
||||
|
||||
proc sharedInstanceCName(m: BModule; s: PSym): string =
|
||||
## The module-free canonical C name for a content-keyed generic instance,
|
||||
## or "" when the symbol must keep its module-suffixed name. With a shared
|
||||
## name, every TU that instantiated the same generic with the same type
|
||||
## arguments calls one extern definition (first claimant's TU embeds it,
|
||||
## see `genProcLvl3`) instead of compiling its own static copy.
|
||||
##
|
||||
## The name is program-unique only if the 30-bit content hash does not
|
||||
## collide for same-named instances of *different* instantiations across
|
||||
## modules — the per-module probe in `setInstanceDisamb` cannot see that.
|
||||
## Claimants therefore must present the same signature; on mismatch the
|
||||
## later one keeps its module-suffixed name (no merge, still correct).
|
||||
## Residual risk: same name and signature, different generic args, AND a
|
||||
## 30-bit collision — vanishingly unlikely; a full-typeKey verification
|
||||
## channel can close it later.
|
||||
result = ""
|
||||
if m.config.cmd == cmdNifC and s.kind in routineKinds and
|
||||
(s.disamb and InstanceDisambBit) != 0'i32 and
|
||||
s.typ != nil and s.typ.callConv != ccInline and not m.hcrOn and
|
||||
{sfImportc, sfExportc, sfCodegenDecl} * s.flags == {}:
|
||||
# The content-derived `disamb` is unique per process (collision-probed in
|
||||
# `setInstanceDisamb`), so the mint-site-independent `_i<disamb>` name is
|
||||
# safe to use directly; identical instances across modules collide on it
|
||||
# exactly and the merge stage keeps one.
|
||||
result = s.name.s.mangle & "_i" & $s.disamb
|
||||
|
||||
proc isSharedInstanceCName(m: BModule; s: PSym): bool =
|
||||
m.config.cmd == cmdNifC and s.kind in routineKinds and
|
||||
(s.disamb and InstanceDisambBit) != 0'i32 and
|
||||
stripCnifMarks(s.loc.snippet) == s.name.s.mangle & "_i" & $s.disamb
|
||||
|
||||
proc fillBackendName(m: BModule; s: PSym) =
|
||||
if s.loc.snippet == "":
|
||||
var result: Rope
|
||||
if s.kind in routineKinds and {optCDebug, optItaniumMangle} * m.g.config.globalOptions == {optCDebug, optItaniumMangle} and
|
||||
m.g.config.symbolFiles == disabledSf:
|
||||
# Under the per-module IC backend the bare-name uniqueness probe
|
||||
# (`m.g.mangledPrcs`) only sees the routines of the CURRENT module, so the
|
||||
# clean-vs-`makeUnique` decision is made independently per process: a
|
||||
# method base mangles clean at its owner but loses the in-module race to
|
||||
# its same-signature dispatcher elsewhere (clean `speak` defined twice ->
|
||||
# "multiple definition"; demanders call `speak_u<n>` that nobody defines).
|
||||
# Force the stable, disamb-based unique name so every process agrees.
|
||||
result = mangleProc(m, s, makeUnique = m.config.cmd == cmdNifC).rope
|
||||
result = mangleProc(m, s, false).rope
|
||||
else:
|
||||
let shared = sharedInstanceCName(m, s)
|
||||
if shared.len > 0:
|
||||
result = shared.rope
|
||||
else:
|
||||
result = s.name.s.mangle.rope
|
||||
result.add mangleProcNameExt(m.g.graph, s)
|
||||
result = s.name.s.mangle.rope
|
||||
result.add mangleProcNameExt(m.g.graph, s)
|
||||
if m.hcrOn:
|
||||
result.add '_'
|
||||
result.add(idOrSig(s, m.module.name.s.mangle, m.sigConflicts, m.config))
|
||||
backendEnsureMutable s
|
||||
if m.config.cmd == cmdNifC:
|
||||
# mark the name so the cnif artifact writer can turn every occurrence
|
||||
# into a Symbol token; stripped from the actual C output in genModule
|
||||
s.locImpl.snippet = markCName(result)
|
||||
else:
|
||||
s.locImpl.snippet = result
|
||||
s.locImpl.snippet = result
|
||||
|
||||
proc fillParamName(m: BModule; s: PSym) =
|
||||
if s.loc.snippet == "":
|
||||
@@ -311,7 +264,7 @@ proc isInvalidReturnType(conf: ConfigRef; typ: PType, isProc = true): bool =
|
||||
var rettype = typ
|
||||
var isAllowedCall = true
|
||||
if isProc:
|
||||
rettype = rettype.returnType
|
||||
rettype = rettype[0]
|
||||
isAllowedCall = typ.callConv in {ccClosure, ccInline, ccNimCall}
|
||||
if rettype == nil or (isAllowedCall and
|
||||
getSize(conf, rettype) > conf.target.floatSize*3):
|
||||
@@ -356,7 +309,7 @@ proc addAbiCheck(m: BModule; t: PType, name: Rope) =
|
||||
|
||||
|
||||
proc fillResult(conf: ConfigRef; param: PNode, proctype: PType) =
|
||||
backendEnsureMutable param.sym
|
||||
ensureMutable param.sym
|
||||
fillLoc(param.sym.locImpl, locParam, param, "Result",
|
||||
OnStack)
|
||||
let t = param.sym.typ
|
||||
@@ -386,10 +339,6 @@ proc getSimpleTypeDesc(m: BModule; typ: PType): Rope =
|
||||
cgsym(m, "NimStrPayload")
|
||||
cgsym(m, "NimStringV2")
|
||||
result = typeNameOrLiteral(m, typ, "NimStringV2")
|
||||
of 3:
|
||||
cgsym(m, "LongString")
|
||||
cgsym(m, "SmallString")
|
||||
result = typeNameOrLiteral(m, typ, "SmallString")
|
||||
else:
|
||||
cgsym(m, "NimStringDesc")
|
||||
result = typeNameOrLiteral(m, typ, "NimStringDesc*")
|
||||
@@ -420,12 +369,6 @@ proc getSimpleTypeDesc(m: BModule; typ: PType): Rope =
|
||||
m.typeCache[sig] = result
|
||||
|
||||
proc pushType(m: BModule; typ: PType) =
|
||||
when defined(icDbgRefc):
|
||||
if typ.kind == tySequence and
|
||||
typ.elementType.skipTypes({tyGenericInst, tyAlias, tySink}).kind == tyGenericParam:
|
||||
echo "[icRefc] pushType seq-of-genericparam t=", typeToString(typ),
|
||||
" itemId=", typ.itemId.module, ".", typ.itemId.item, " mod=", m.module.name.s
|
||||
echo getStackTrace()
|
||||
for i in 0..high(m.typeStack):
|
||||
# pointer equality is good enough here:
|
||||
if m.typeStack[i] == typ: return
|
||||
@@ -480,7 +423,7 @@ proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet; kind: TypeDescKind
|
||||
of tySequence:
|
||||
let sig = hashType(t, m.config)
|
||||
if optSeqDestructors in m.config.globalOptions:
|
||||
if skipTypes(etB.elementType, typedescInst).kind == tyEmpty:
|
||||
if skipTypes(etB[0], typedescInst).kind == tyEmpty:
|
||||
internalError(m.config, "cannot map the empty seq type to a C type")
|
||||
|
||||
result = cacheGetType(m.forwTypeCache, sig)
|
||||
@@ -510,21 +453,13 @@ proc getSeqPayloadType(m: BModule; t: PType): Rope =
|
||||
result = getTypeDescWeak(m, t, check, dkParam) & "_Content"
|
||||
#result = getTypeForward(m, t, hashType(t)) & "_Content"
|
||||
|
||||
proc seqPayloadElem(m: BModule; t: PType): Snippet =
|
||||
## Returns the C type name for a seq's element as stored in the payload,
|
||||
## suitable for sizeof()/alignof(). Must use dkVar, not the dkParam default,
|
||||
## because reified openArrays (experimental views) differ: dkParam gives a
|
||||
## bare pointer (T*) while dkVar gives the two-word struct actually stored.
|
||||
var check = initIntSet()
|
||||
result = getTypeDescAux(m, t.elementType, check, dkVar)
|
||||
|
||||
proc seqV2ContentType(m: BModule; t: PType; check: var IntSet) =
|
||||
let sig = hashType(t, m.config)
|
||||
let result = cacheGetType(m.typeCache, sig)
|
||||
if result == "":
|
||||
discard getTypeDescAux(m, t, check, dkVar)
|
||||
else:
|
||||
let dataTyp = getTypeDescAux(m, t.skipTypes(abstractInst).elementType, check, dkVar)
|
||||
let dataTyp = getTypeDescAux(m, t.skipTypes(abstractInst)[0], check, dkVar)
|
||||
m.s[cfsTypes].addSimpleStruct(m, name = result & "_Content", baseType = ""):
|
||||
m.s[cfsTypes].addField(name = "cap", typ = NimInt)
|
||||
m.s[cfsTypes].addField(name = "data",
|
||||
@@ -598,10 +533,10 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
|
||||
rettype = runtimeFormat(rettype.replace("'0", "$1"), [getTypeDescAux(m, t.returnType, check, dkResult)])
|
||||
var types, names, args: seq[string] = @[]
|
||||
if not isCtor:
|
||||
var this = t.n.secondSon.sym
|
||||
backendEnsureMutable this
|
||||
var this = t.n[1].sym
|
||||
ensureMutable this
|
||||
fillParamName(m, this)
|
||||
fillLoc(this.locImpl, locParam, t.n.secondSon,
|
||||
fillLoc(this.locImpl, locParam, t.n[1],
|
||||
this.paramStorageLoc)
|
||||
if this.typ.kind == tyPtr:
|
||||
this.locImpl.snippet = "this"
|
||||
@@ -611,9 +546,9 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
|
||||
types.add getTypeDescWeak(m, this.typ, check, dkParam)
|
||||
|
||||
let firstParam = if isCtor: 1 else: 2
|
||||
for it in sonsFrom(t.n, firstParam):
|
||||
if it.kind != nkSym: internalError(m.config, t.n.info, "genMemberProcParams")
|
||||
var param = it.sym
|
||||
for i in firstParam..<t.n.len:
|
||||
if t.n[i].kind != nkSym: internalError(m.config, t.n.info, "genMemberProcParams")
|
||||
var param = t.n[i].sym
|
||||
var descKind = dkParam
|
||||
if optByRef in param.options:
|
||||
if param.typ.kind == tyGenericInst:
|
||||
@@ -621,9 +556,9 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
|
||||
else:
|
||||
descKind = dkRefParam
|
||||
var typ, name: string
|
||||
backendEnsureMutable param
|
||||
ensureMutable param
|
||||
fillParamName(m, param)
|
||||
fillLoc(param.locImpl, locParam, it,
|
||||
fillLoc(param.locImpl, locParam, t.n[i],
|
||||
param.paramStorageLoc)
|
||||
if ccgIntroducedPtr(m.config, param, t.returnType) and descKind == dkParam:
|
||||
typ = getTypeDescWeak(m, param.typ, check, descKind) & "*"
|
||||
@@ -668,21 +603,9 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
|
||||
rettype = getTypeDescWeak(m, t.returnType, check, dkResult)
|
||||
var paramBuilder: ProcParamBuilder
|
||||
params.addProcParams(paramBuilder):
|
||||
for child in sonsFrom(t.n, 1):
|
||||
if child.kind != nkSym: internalError(m.config, t.n.info, "genProcParams")
|
||||
var param = child.sym
|
||||
# The hidden closure environment param (`:envP`) is not a real C parameter:
|
||||
# the environment is passed via the trailing `ClE_0` (added below) and
|
||||
# `closureSetup` materialises `:envP` as a local cast of it. In a from-source
|
||||
# build `:envP` only lives in the routine's AST params, never in the proc
|
||||
# *type's* `n`, so it never reaches here. Under IC `closureParams` re-shares
|
||||
# the AST param node with `typ.n`, so the lifted `:envP` leaks into `t.n`;
|
||||
# emitting it would produce a bogus extra parameter that collides with the
|
||||
# `closureSetup` local (the "redeclared as different kind of symbol" / env
|
||||
# pointer-type mismatch). We still must fill its name/loc (later passes such
|
||||
# as `assignParam` and `closureSetup` reference it), but it is omitted from
|
||||
# the C signature to match the from-source ABI.
|
||||
let isClosureEnv = t.callConv == ccClosure and param.name.s == ":envP"
|
||||
for i in 1..<t.n.len:
|
||||
if t.n[i].kind != nkSym: internalError(m.config, t.n.info, "genProcParams")
|
||||
var param = t.n[i].sym
|
||||
var descKind = dkParam
|
||||
if m.config.backend == backendCpp and optByRef in param.options:
|
||||
if param.typ.kind == tyGenericInst:
|
||||
@@ -692,9 +615,8 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
|
||||
if isCompileTimeOnly(param.typ): continue
|
||||
backendEnsureMutable param
|
||||
fillParamName(m, param)
|
||||
fillLoc(param.locImpl, locParam, child,
|
||||
fillLoc(param.locImpl, locParam, t.n[i],
|
||||
param.paramStorageLoc)
|
||||
if isClosureEnv: continue # name/loc filled, but not part of the C signature
|
||||
var typ: Rope
|
||||
if ccgIntroducedPtr(m.config, param, t.returnType) and descKind == dkParam:
|
||||
typ = ptrType(getTypeDescWeak(m, param.typ, check, descKind))
|
||||
@@ -715,7 +637,7 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
|
||||
# need to pass hidden parameter:
|
||||
params.addParam(paramBuilder, name = param.locImpl.snippet & "Len_" & $j, typ = NimInt)
|
||||
inc(j)
|
||||
arr = arr.elementType.skipTypes({tySink})
|
||||
arr = arr[0].skipTypes({tySink})
|
||||
if t.returnType != nil and isInvalidReturnType(m.config, t):
|
||||
var arr = t.returnType
|
||||
var typ: Snippet
|
||||
@@ -742,8 +664,8 @@ proc mangleRecFieldName(m: BModule; field: PSym): Rope =
|
||||
|
||||
proc hasCppCtor(m: BModule; typ: PType): bool =
|
||||
result = false
|
||||
if m.compileToCpp and typ != nil and typ.bindingId in m.g.graph.memberProcsPerType:
|
||||
for prc in m.g.graph.memberProcsPerType[typ.bindingId]:
|
||||
if m.compileToCpp and typ != nil and typ.itemId in m.g.graph.memberProcsPerType:
|
||||
for prc in m.g.graph.memberProcsPerType[typ.itemId]:
|
||||
if sfConstructor in prc.flags:
|
||||
return true
|
||||
|
||||
@@ -752,8 +674,8 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): string
|
||||
proc genCppInitializer(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): string =
|
||||
#To avoid creating a BProc per test when called inside a struct nil BProc is allowed
|
||||
result = "{}"
|
||||
if typ.bindingId in m.g.graph.initializersPerType:
|
||||
let call = m.g.graph.initializersPerType[typ.bindingId]
|
||||
if typ.itemId in m.g.graph.initializersPerType:
|
||||
let call = m.g.graph.initializersPerType[typ.itemId]
|
||||
if call != nil:
|
||||
var p = prc
|
||||
if p == nil:
|
||||
@@ -767,20 +689,20 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
|
||||
check: var IntSet; result: var Builder; unionPrefix = "") =
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
for ni in sons(n):
|
||||
genRecordFieldsAux(m, ni, rectype, check, result, unionPrefix)
|
||||
for i in 0..<n.len:
|
||||
genRecordFieldsAux(m, n[i], rectype, check, result, unionPrefix)
|
||||
of nkRecCase:
|
||||
if n.firstSon.kind != nkSym: internalError(m.config, n.info, "genRecordFieldsAux")
|
||||
genRecordFieldsAux(m, n.firstSon, rectype, check, result, unionPrefix)
|
||||
if n[0].kind != nkSym: internalError(m.config, n.info, "genRecordFieldsAux")
|
||||
genRecordFieldsAux(m, n[0], rectype, check, result, unionPrefix)
|
||||
# prefix mangled name with "_U" to avoid clashes with other field names,
|
||||
# since identifiers are not allowed to start with '_'
|
||||
var unionBody = newBuilder("")
|
||||
for i, it in isons(n, 1):
|
||||
case it.kind
|
||||
for i in 1..<n.len:
|
||||
case n[i].kind
|
||||
of nkOfBranch, nkElse:
|
||||
let k = lastSon(it)
|
||||
let k = lastSon(n[i])
|
||||
if k.kind != nkSym:
|
||||
let structName = "_" & mangleRecFieldName(m, n.firstSon.sym) & "_" & $i
|
||||
let structName = "_" & mangleRecFieldName(m, n[0].sym) & "_" & $i
|
||||
var a = newBuilder("")
|
||||
genRecordFieldsAux(m, k, rectype, check, a, unionPrefix & $structName & ".")
|
||||
if a.buf.len != 0:
|
||||
@@ -819,11 +741,7 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
|
||||
# don't use fieldType here because we need the
|
||||
# tyGenericInst for C++ template support
|
||||
let noInit = sfNoInit in field.flags or (field.typ.sym != nil and sfNoInit in field.typ.sym.flags)
|
||||
# Under `nim ic`, object fields are local NIF syms restored without an
|
||||
# `owner`; `rectype` is the owning record type, so fall back to it rather
|
||||
# than deref a nil `field.owner`.
|
||||
let ownerTyp = if field.owner != nil: field.owner.typ else: rectype
|
||||
if not noInit and (fieldType.isOrHasImportedCppType() or hasCppCtor(m, ownerTyp)):
|
||||
if not noInit and (fieldType.isOrHasImportedCppType() or hasCppCtor(m, field.owner.typ)):
|
||||
var didGenTemp = false
|
||||
initializer = genCppInitializer(m, nil, fieldType, didGenTemp)
|
||||
result.addField(field, sname, typ, isFlexArray, initializer)
|
||||
@@ -833,8 +751,8 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
|
||||
|
||||
proc addRecordFields(result: var Builder; m: BModule; typ: PType, check: var IntSet) =
|
||||
genRecordFieldsAux(m, typ.n, typ, check, result)
|
||||
if typ.bindingId in m.g.graph.memberProcsPerType:
|
||||
let procs = m.g.graph.memberProcsPerType[typ.bindingId]
|
||||
if typ.itemId in m.g.graph.memberProcsPerType:
|
||||
let procs = m.g.graph.memberProcsPerType[typ.itemId]
|
||||
var isDefaultCtorGen, isCtorGen: bool = false
|
||||
for prc in procs:
|
||||
if sfConstructor in prc.flags:
|
||||
@@ -882,8 +800,6 @@ proc getTupleDesc(m: BModule; typ: PType, name: Rope,
|
||||
var res = newBuilder("")
|
||||
res.addStruct(m, typ, name, ""):
|
||||
for i, a in typ.ikids:
|
||||
# Do not produce code for void types
|
||||
if isEmptyType(a): continue
|
||||
res.addField(
|
||||
name = "Field" & $i,
|
||||
typ = getTypeDescAux(m, a, check, dkField))
|
||||
@@ -915,7 +831,7 @@ proc resolveStarsInCppType(typ: PType, idx, stars: int): PType =
|
||||
result = typ[idx]
|
||||
for i in 1..stars:
|
||||
if result != nil and result.kidsLen > 0:
|
||||
result = if result.kind == tyGenericInst: result.firstGenericParam
|
||||
result = if result.kind == tyGenericInst: result[FirstGenericParamAt]
|
||||
else: result.elemType
|
||||
|
||||
proc getOpenArrayDesc(m: BModule; t: PType, check: var IntSet; kind: TypeDescKind): Rope =
|
||||
@@ -1075,9 +991,9 @@ proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDes
|
||||
let owner = hashOwner(t.sym)
|
||||
if not gDebugInfo.hasEnum(t.sym.name.s, t.sym.info.line, owner):
|
||||
var vals: seq[(string, int)] = @[]
|
||||
for son in sons(t.n):
|
||||
assert(son.kind == nkSym)
|
||||
let field = son.sym
|
||||
for i in 0..<t.n.len:
|
||||
assert(t.n[i].kind == nkSym)
|
||||
let field = t.n[i].sym
|
||||
vals.add((field.name.s, field.position.int))
|
||||
gDebugInfo.registerEnum(EnumDesc(size: size, owner: owner, id: t.sym.id,
|
||||
name: t.sym.name.s, values: vals))
|
||||
@@ -1178,11 +1094,6 @@ proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDes
|
||||
tyUserTypeClass, tyUserTypeClassInst, tyInferred:
|
||||
result = getTypeDescAux(m, skipModifier(t), check, kind)
|
||||
else:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icRefc] getTypeDescAux ", t.kind, " t=", typeToString(t),
|
||||
" origTyp=", typeToString(origTyp), " t.itemId=", t.itemId.module, ".", t.itemId.item,
|
||||
" sym=", (if t.sym != nil: t.sym.name.s else: "nil"),
|
||||
" owner=", (if t.owner != nil: t.owner.name.s else: "nil")
|
||||
internalError(m.config, "getTypeDescAux(" & $t.kind & ')')
|
||||
result = ""
|
||||
# fixes bug #145:
|
||||
@@ -1221,10 +1132,6 @@ proc finishTypeDescriptions(m: BModule) =
|
||||
var check = initIntSet()
|
||||
while i < m.typeStack.len:
|
||||
let t = m.typeStack[i]
|
||||
when defined(icDbgRefc):
|
||||
echo "[icRefc] finishTypeDescriptions[", i, "] mod=", m.module.name.s,
|
||||
" t=", typeToString(t), " kind=", t.kind,
|
||||
" itemId=", t.itemId.module, ".", t.itemId.item
|
||||
if optSeqDestructors in m.config.globalOptions and t.skipTypes(abstractInst).kind == tySequence:
|
||||
seqV2ContentType(m, t, check)
|
||||
else:
|
||||
@@ -1266,8 +1173,8 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
|
||||
let isCtor = sfConstructor in prc.flags
|
||||
var check = initIntSet()
|
||||
fillBackendName(m, prc)
|
||||
backendEnsureMutable prc
|
||||
fillLoc(prc.locImpl, locProc, son(prc.ast, namePos), OnUnknown)
|
||||
ensureMutable prc
|
||||
fillLoc(prc.locImpl, locProc, prc.ast[namePos], OnUnknown)
|
||||
var memberOp = "#." #only virtual
|
||||
var typ: PType
|
||||
if isCtor:
|
||||
@@ -1289,14 +1196,6 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
|
||||
name = typDesc
|
||||
if isFnConst:
|
||||
fnConst = " const"
|
||||
if not isCtor:
|
||||
# The call-site form (`x->salute(@)`), not the mangled Nim name. Set it on
|
||||
# BOTH paths: whole-program cgen always emitted the out-of-class definition
|
||||
# (the `else` branch) before any caller, but the per-module backend emits a
|
||||
# foreign member proc's body in ITS OWN module, so the caller's TU only ever
|
||||
# reaches the in-class declaration below — and called the member by the
|
||||
# mangled name (`loo->salute_u0__vireouyks1()`, "struct Loo has no member").
|
||||
prc.locImpl.snippet = "$1$2(@)" % [memberOp, name]
|
||||
if isFwdDecl:
|
||||
if isStatic:
|
||||
result.add "static "
|
||||
@@ -1306,7 +1205,9 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
|
||||
override = " override"
|
||||
superCall = ""
|
||||
else:
|
||||
if isCtor and superCall != "":
|
||||
if not isCtor:
|
||||
prc.locImpl.snippet = "$1$2(@)" % [memberOp, name]
|
||||
elif superCall != "":
|
||||
superCall = " : " & superCall
|
||||
|
||||
name = "$1::$2" % [typDesc, name]
|
||||
@@ -1321,7 +1222,7 @@ proc genProcHeader(m: BModule; prc: PSym; result: var Builder; visibility: var D
|
||||
var check = initIntSet()
|
||||
fillBackendName(m, prc)
|
||||
backendEnsureMutable prc
|
||||
fillLoc(prc.locImpl, locProc, son(prc.ast, namePos), OnUnknown)
|
||||
fillLoc(prc.locImpl, locProc, prc.ast[namePos], OnUnknown)
|
||||
var rettype: Snippet = ""
|
||||
var desc = newBuilder("")
|
||||
genProcParams(m, prc.typ, rettype, desc, check, true, false)
|
||||
@@ -1345,9 +1246,7 @@ proc genProcHeader(m: BModule; prc: PSym; result: var Builder; visibility: var D
|
||||
elif prc.typ.callConv == ccInline or isNonReloadable(m, prc):
|
||||
visibility = StaticProc
|
||||
elif sfImportc notin prc.flags:
|
||||
if not isSharedInstanceCName(m, prc):
|
||||
visibility = Private
|
||||
# else: plain extern — the definition is shared across TUs
|
||||
visibility = Private
|
||||
if asPtr:
|
||||
result.addProcVar(m, prc, name, params, rettype, isStatic = isStaticVar, ignoreAttributes = true)
|
||||
else:
|
||||
@@ -1425,24 +1324,8 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType;
|
||||
m.hcrCreateTypeInfosProc.addCast(typ = ptrType(CPointer)):
|
||||
m.hcrCreateTypeInfosProc.add(cAddr(name))
|
||||
else:
|
||||
if m.config.cmd == cmdNifC:
|
||||
# Emit-everywhere (see genTypeInfoV1's perModuleCg gate): every demanding
|
||||
# `cg` process emits this type info's tentative definition. Declare it
|
||||
# `extern` first (the data analogue of a proc prototype) so a TU whose copy
|
||||
# the merge stage drops still has a valid declaration; wrap the definition
|
||||
# as a droppable `'d'` unit the merge stage assigns to a single owner so
|
||||
# exactly one external-linkage tentative definition survives (preserving
|
||||
# the RTTI pointer identity refc relies on).
|
||||
m.s[cfsStrData].addDeclWithVisibility(Extern):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
|
||||
m.s[cfsStrData].add(cnifDefDirective(name, "d", icNifName(m, origType)))
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
|
||||
m.s[cfsStrData].add(cnifEndDefs())
|
||||
m.icDataDefs.add (name, icNifName(m, origType))
|
||||
else:
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimType")
|
||||
|
||||
proc genTypeInfoAux(m: BModule; typ, origType: PType, name: Rope;
|
||||
info: TLineInfo) =
|
||||
@@ -1462,11 +1345,13 @@ proc discriminatorTableName(m: BModule; objtype: PType, d: PSym): Rope =
|
||||
# bugfix: we need to search the type that contains the discriminator:
|
||||
var objtype = objtype.skipTypes(abstractPtrs)
|
||||
while lookupInRecord(objtype.n, d.name) == nil:
|
||||
objtype = objtype.baseClass.skipTypes(abstractPtrs)
|
||||
objtype = objtype[0].skipTypes(abstractPtrs)
|
||||
if objtype.sym == nil:
|
||||
internalError(m.config, d.info, "anonymous obj with discriminator")
|
||||
result = "NimDT_$1_$2" % [rope($hashType(objtype, m.config)), rope(d.name.s.mangle)]
|
||||
|
||||
proc rope(arg: Int128): Rope = rope($arg)
|
||||
|
||||
proc discriminatorTableDecl(m: BModule; objtype: PType, d: PSym, result: var Builder) =
|
||||
cgsym(m, "TNimNode")
|
||||
var tmp = discriminatorTableName(m, objtype, d)
|
||||
@@ -1501,14 +1386,14 @@ proc genObjectFields(m: BModule; typ, origType: PType, n: PNode, expr: Rope;
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
if n.len == 1:
|
||||
genObjectFields(m, typ, origType, n.firstSon, expr, info)
|
||||
genObjectFields(m, typ, origType, n[0], expr, info)
|
||||
elif n.len > 0:
|
||||
var tmp = getTempName(m) & "_" & $n.len
|
||||
genTNimNodeArray(m, tmp, n.len)
|
||||
for i, ni in isons(n):
|
||||
for i in 0..<n.len:
|
||||
var tmp2 = getNimNode(m)
|
||||
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(i), cAddr(tmp2))
|
||||
genObjectFields(m, typ, origType, ni, tmp2, info)
|
||||
genObjectFields(m, typ, origType, n[i], tmp2, info)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", n.len)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "kind", 2)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "sons",
|
||||
@@ -1517,8 +1402,8 @@ proc genObjectFields(m: BModule; typ, origType: PType, n: PNode, expr: Rope;
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", n.len)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "kind", 2)
|
||||
of nkRecCase:
|
||||
assert(n.firstSon.kind == nkSym)
|
||||
var field = n.firstSon.sym
|
||||
assert(n[0].kind == nkSym)
|
||||
var field = n[0].sym
|
||||
var tmp = discriminatorTableName(m, typ, field)
|
||||
var L = lengthOrd(m.config, field.typ)
|
||||
assert L > 0
|
||||
@@ -1533,41 +1418,25 @@ proc genObjectFields(m: BModule; typ, origType: PType, n: PNode, expr: Rope;
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "name", makeCString(field.name.s))
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "sons", cAddr(subscript(tmp, cIntValue(0))))
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", L)
|
||||
if m.config.cmd == cmdNifC:
|
||||
# The discriminator table has a content-addressed name
|
||||
# (`NimDT_<hashType>_<field>`) and is emitted by every module that demands
|
||||
# this variant type's RTTI (emit-everywhere; RTTI has no single owner —
|
||||
# emission is lazy and often skipped). Declare it `extern` + wrap the
|
||||
# tentative definition as a droppable `'d'` unit so the merge stage keeps
|
||||
# exactly one external-linkage definition (mirrors the `TNimType` var and
|
||||
# consts); otherwise the identical name collides across modules at link.
|
||||
m.s[cfsData].addDeclWithVisibility(Extern):
|
||||
m.s[cfsData].addArrayVar(kind = Local, name = tmp,
|
||||
elementType = ptrType("TNimNode"), len = toInt(L)+1)
|
||||
m.s[cfsData].add(cnifDefDirective(tmp, "d", ""))
|
||||
m.s[cfsData].addArrayVar(kind = Local, name = tmp,
|
||||
elementType = ptrType("TNimNode"), len = toInt(L)+1)
|
||||
m.s[cfsData].add(cnifEndDefs())
|
||||
m.icDataDefs.add (tmp, "")
|
||||
else:
|
||||
m.s[cfsData].addArrayVar(kind = Local, name = tmp,
|
||||
elementType = ptrType("TNimNode"), len = toInt(L)+1)
|
||||
for b in sonsFrom(n, 1):
|
||||
m.s[cfsData].addArrayVar(kind = Local, name = tmp,
|
||||
elementType = ptrType("TNimNode"), len = toInt(L)+1)
|
||||
for i in 1..<n.len:
|
||||
var b = n[i] # branch
|
||||
var tmp2 = getNimNode(m)
|
||||
genObjectFields(m, typ, origType, lastSon(b), tmp2, info)
|
||||
case b.kind
|
||||
of nkOfBranch:
|
||||
if b.len < 2:
|
||||
internalError(m.config, b.info, "genObjectFields; nkOfBranch broken")
|
||||
for label in sonsButLast(b):
|
||||
if label.kind == nkRange:
|
||||
var x = toInt(getOrdValue(label.firstSon))
|
||||
var y = toInt(getOrdValue(label.secondSon))
|
||||
for j in 0..<b.len - 1:
|
||||
if b[j].kind == nkRange:
|
||||
var x = toInt(getOrdValue(b[j][0]))
|
||||
var y = toInt(getOrdValue(b[j][1]))
|
||||
while x <= y:
|
||||
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(x), cAddr(tmp2))
|
||||
inc(x)
|
||||
else:
|
||||
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(getOrdValue(label)), cAddr(tmp2))
|
||||
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(getOrdValue(b[j])), cAddr(tmp2))
|
||||
of nkElse:
|
||||
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(L), cAddr(tmp2))
|
||||
else: internalError(m.config, n.info, "genObjectFields(nkRecCase)")
|
||||
@@ -1603,38 +1472,27 @@ proc genObjectInfo(m: BModule; typ, origType: PType, name: Rope; info: TLineInfo
|
||||
t.incl tfObjHasKids
|
||||
t = t.baseClass
|
||||
|
||||
proc validTupleTypeFields(t: PType): int =
|
||||
# we want to treat tuples with only void fields as empty, so we need to exclude void types here:
|
||||
result = 0
|
||||
for a in t.kids:
|
||||
if not isEmptyType(a): inc result
|
||||
|
||||
proc genTupleInfo(m: BModule; typ, origType: PType, name: Rope; info: TLineInfo) =
|
||||
genTypeInfoAuxBase(m, typ, typ, name, cIntValue(0), info)
|
||||
var expr = getNimNode(m)
|
||||
let nonVoidKids = validTupleTypeFields(typ)
|
||||
if nonVoidKids > 0:
|
||||
var tmp = getTempName(m) & "_" & $nonVoidKids
|
||||
genTNimNodeArray(m, tmp, nonVoidKids)
|
||||
var j = 0
|
||||
if not typ.isEmptyTupleType:
|
||||
var tmp = getTempName(m) & "_" & $typ.kidsLen
|
||||
genTNimNodeArray(m, tmp, typ.kidsLen)
|
||||
for i, a in typ.ikids:
|
||||
# Do not produce code for void types
|
||||
if isEmptyType(a): continue
|
||||
var tmp2 = getNimNode(m)
|
||||
let fieldTypInfo = genTypeInfoV1(m, a, info)
|
||||
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(j), cAddr(tmp2))
|
||||
m.s[cfsTypeInit3].addSubscriptAssignment(tmp, cIntValue(i), cAddr(tmp2))
|
||||
m.s[cfsTypeInit3].addFieldAssignment(tmp2, "kind", 1)
|
||||
m.s[cfsTypeInit3].addFieldAssignmentWithValue(tmp2, "offset"):
|
||||
m.s[cfsTypeInit3].addOffsetof(getTypeDesc(m, origType, dkVar), "Field" & $i)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(tmp2, "typ", fieldTypInfo)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(tmp2, "name", "\"Field" & $i & "\"")
|
||||
inc j
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", nonVoidKids)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", typ.kidsLen)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "kind", 2)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "sons",
|
||||
cAddr(subscript(tmp, cIntValue(0))))
|
||||
else:
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", cIntValue(0))
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "len", typ.kidsLen)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(expr, "kind", 2)
|
||||
m.s[cfsTypeInit3].addFieldAssignment(tiNameForHcr(m, name), "node", cAddr(expr))
|
||||
|
||||
@@ -1652,9 +1510,9 @@ proc genEnumInfo(m: BModule; typ: PType, name: Rope; info: TLineInfo) =
|
||||
var firstNimNode = m.typeNodes
|
||||
var hasHoles = false
|
||||
enumNames.addStructInitializer(enumNamesInit, kind = siArray):
|
||||
for i, son in isons(typ.n):
|
||||
assert(son.kind == nkSym)
|
||||
var field = son.sym
|
||||
for i in 0..<typ.n.len:
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
var field = typ.n[i].sym
|
||||
var elemNode = getNimNode(m)
|
||||
enumNames.addField(enumNamesInit, name = ""):
|
||||
if field.ast == nil:
|
||||
@@ -1744,13 +1602,8 @@ proc declareNimType(m: BModule; name: string; str: Rope, module: int) =
|
||||
m.s[cfsTypeInit1].addArgument(hcrGlobal):
|
||||
m.s[cfsTypeInit1].add("\"" & str & "\"")
|
||||
else:
|
||||
# cnif-mark the name: this extern declaration is the reference the
|
||||
# def-retention check consults when the defining TU regenerates and
|
||||
# the typeinfo cannot be re-demanded (type vanished) — the referencing
|
||||
# TU must lose its reuse then instead of producing a link error
|
||||
let declName = if m.config.cmd == cmdNifC: markCName(str) else: str
|
||||
m.s[cfsStrData].addDeclWithVisibility(Extern):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = declName, typ = nr)
|
||||
m.s[cfsStrData].addVar(kind = Local, name = str, typ = nr)
|
||||
|
||||
proc genTypeInfo2Name(m: BModule; t: PType): Rope =
|
||||
var it = t
|
||||
@@ -1785,7 +1638,7 @@ proc generateRttiDestructor(g: ModuleGraph; typ: PType; owner: PSym; kind: TType
|
||||
|
||||
dest.typ = getSysType(g, info, tyPointer)
|
||||
|
||||
result.typ = newProcType(info, idgen, result)
|
||||
result.typ = newProcType(info, idgen, owner)
|
||||
result.typ.addParam dest
|
||||
|
||||
var n = newNodeI(nkProcDef, info, bodyPos+1)
|
||||
@@ -1814,16 +1667,6 @@ proc generateRttiDestructor(g: ModuleGraph; typ: PType; owner: PSym; kind: TType
|
||||
|
||||
incl result.flagsImpl, sfFromGeneric
|
||||
incl result.flagsImpl, sfGeneratedOp
|
||||
# Under IC the `rttiDestroy` wrapper is generated independently in every cg
|
||||
# process that emits `typ`'s RTTI (the type-info is emit-everywhere). A plain
|
||||
# counter `disamb` renumbers per process, so the RTTI table baked in module A
|
||||
# references `rttiDestroy_c<n>` while module B (the =destroy owner) defines a
|
||||
# different number → undefined at link. Give it a content-derived `disamb`
|
||||
# (stable across processes) + `HookDisambBit`, exactly like `symPrototype` does
|
||||
# for the hook itself: same `typ` ⇒ same C name everywhere, and the bit makes
|
||||
# `emitsBodyInThisModule` emit the body in every demander (merge dedups). The
|
||||
# `"rttiDestroy"` op-name keeps its key disjoint from the real `=destroy` hook's.
|
||||
setHookDisamb(g, result, "rttiDestroy", typ)
|
||||
|
||||
proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp; result: var Builder) =
|
||||
let theProc = getAttachedOp(m.g.graph, t, op)
|
||||
@@ -1861,7 +1704,7 @@ proc getObjDepth(t: PType): int16 =
|
||||
result = -1
|
||||
while x != nil:
|
||||
x = skipTypes(x, skipPtrs)
|
||||
x = x.baseClass
|
||||
x = x[0]
|
||||
inc(result)
|
||||
|
||||
proc genDisplayElem(d: MD5Digest): uint32 =
|
||||
@@ -1877,7 +1720,7 @@ proc genDisplay(result: var Builder, m: BModule; t: PType, depth: int) =
|
||||
while x != nil:
|
||||
x = skipTypes(x, skipPtrs)
|
||||
seqs[i] = cIntValue(genDisplayElem(MD5Digest(hashType(x, m.config))))
|
||||
x = x.baseClass
|
||||
x = x[0]
|
||||
inc i
|
||||
|
||||
var arr: StructInitializer
|
||||
@@ -1896,30 +1739,9 @@ proc genVTable(result: var Builder, seqs: seq[PSym]) =
|
||||
result.add(cCast(CPointer, seqs[i].loc.snippet))
|
||||
|
||||
proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLineInfo) =
|
||||
## The C++/HCR flavour: C++ has no designated initializers, so the RTTI record
|
||||
## is a bare variable that the module's `DatInit` fills field by field.
|
||||
cgsym(m, "TNimTypeV2")
|
||||
if m.config.cmd == cmdNifC:
|
||||
# Same emit-everywhere split as `genTypeInfoV2Impl`: every `cg` process that
|
||||
# demands this type declares it `extern`, and the DEFINITION is a droppable
|
||||
# `'d'` unit the merge stage gives a single owner. Without the split the bare
|
||||
# `TNimTypeV2 x;` in each TU is a tentative definition — which C's linker
|
||||
# merges but C++'s does not, so `nim cpp --ic:on` died at link with
|
||||
# "multiple definition of NTIv2__…". The field ASSIGNMENTS stay in every
|
||||
# TU's `DatInit`: they are top-level code, not a definition, and every module
|
||||
# computes the same values.
|
||||
m.s[cfsStrData].addDeclWithVisibility(Extern):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
|
||||
m.s[cfsVars].add(cnifDefDirective(name, "d", icNifName(m, origType)))
|
||||
var def = newBuilder("")
|
||||
def.addDeclWithVisibility(Private):
|
||||
def.addVar(kind = Local, name = name, typ = "TNimTypeV2")
|
||||
m.s[cfsVars].add extract(def)
|
||||
m.s[cfsVars].add(cnifEndDefs())
|
||||
m.icDataDefs.add (name, icNifName(m, origType))
|
||||
else:
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
|
||||
|
||||
var flags = 0
|
||||
if not canFormAcycle(m.g.graph, t): flags = flags or 1
|
||||
@@ -1982,15 +1804,8 @@ proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLin
|
||||
|
||||
proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineInfo) =
|
||||
cgsym(m, "TNimTypeV2")
|
||||
# Under `nim nifc` every `cg` process that demands this type's RTTI emits its
|
||||
# definition (emit-everywhere). The forward declaration must therefore be a
|
||||
# real `extern` (not a tentative definition) so a TU whose copy the merge
|
||||
# stage drops still only *declares* it; the definition itself is wrapped as a
|
||||
# droppable `'d'` unit below and assigned to a single owner.
|
||||
m.s[cfsStrData].addDeclWithVisibility(if m.config.cmd == cmdNifC: Extern else: Private):
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
|
||||
if m.config.cmd == cmdNifC:
|
||||
m.icDataDefs.add (name, icNifName(m, origType))
|
||||
|
||||
var flags = 0
|
||||
if not canFormAcycle(m.g.graph, t): flags = flags or 1
|
||||
@@ -2051,12 +1866,7 @@ proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineIn
|
||||
else:
|
||||
typeEntry.addField(typeInit, name = "flags"):
|
||||
typeEntry.addIntValue(flags)
|
||||
if m.config.cmd == cmdNifC:
|
||||
m.s[cfsVars].add(cnifDefDirective(name, "d", icNifName(m, origType)))
|
||||
m.s[cfsVars].add extract(typeEntry)
|
||||
m.s[cfsVars].add(cnifEndDefs())
|
||||
else:
|
||||
m.s[cfsVars].add extract(typeEntry)
|
||||
m.s[cfsVars].add extract(typeEntry)
|
||||
|
||||
if t.kind == tyObject and t.baseClass != nil and optEnableDeepCopy in m.config.globalOptions:
|
||||
discard genTypeInfoV1(m, t, info)
|
||||
@@ -2094,14 +1904,8 @@ proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
result = "NTIv2$1_" % [rope($sig)]
|
||||
m.typeInfoMarkerV2[sig] = result
|
||||
|
||||
let owner = t.skipTypes(typedescPtrs).bindingId.module
|
||||
# In the per-module backend (`cg`) RTTI is emit-everywhere like procs and
|
||||
# consts: every demanding module emits the `'d'` definition (deduped to one
|
||||
# owner by the merge stage). The owner-routing below would instead push the
|
||||
# definition into the owner module's *unwritten* backend module (discarded in
|
||||
# this process) and emit only an extern here, leaving the symbol undefined.
|
||||
let perModuleCg = m.config.cmd == cmdNifC and m.config.icBackendStage == "cg"
|
||||
if not perModuleCg and owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
|
||||
let owner = t.skipTypes(typedescPtrs).itemId.module
|
||||
if owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
|
||||
# make sure the type info is created in the owner module
|
||||
discard genTypeInfoV2(m.g.mods[owner], origType, info)
|
||||
# reference the type info as extern here
|
||||
@@ -2168,10 +1972,6 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
|
||||
let marker = m.g.typeInfoMarker.getOrDefault(sig)
|
||||
if marker.str != "":
|
||||
when defined(icDbgRefc):
|
||||
if "catchableerror" in marker.str:
|
||||
echo "[icNti] ", marker.str, " in mod=", m.module.name.s,
|
||||
" -> extern:globalMarker owner=", marker.owner
|
||||
cgsym(m, "TNimType")
|
||||
cgsym(m, "TNimNode")
|
||||
declareNimType(m, "TNimType", marker.str, marker.owner)
|
||||
@@ -2182,32 +1982,15 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
result = "NTI$1$2_" % [rope(typeToC(t)), rope($sig)]
|
||||
m.typeInfoMarker[sig] = result
|
||||
|
||||
when defined(icDbgRefc):
|
||||
template dbgNti(branch: string) =
|
||||
if "catchableerror" in result:
|
||||
echo "[icNti] ", result, " in mod=", m.module.name.s, " -> ", branch
|
||||
else:
|
||||
template dbgNti(branch: string) = discard
|
||||
|
||||
let old = m.g.graph.emittedTypeInfo.getOrDefault($result)
|
||||
if old != FileIndex(0):
|
||||
dbgNti "extern:emittedTypeInfo"
|
||||
cgsym(m, "TNimType")
|
||||
cgsym(m, "TNimNode")
|
||||
declareNimType(m, "TNimType", result, old.int)
|
||||
return prefixTI(result)
|
||||
|
||||
var owner = t.skipTypes(typedescPtrs).bindingId.module
|
||||
# In the per-module backend (`cg`) V1 RTTI is emit-everywhere like procs,
|
||||
# consts and V2 type info: every demanding module emits the `'d'` definition
|
||||
# (deduped to one owner by the merge stage). The owner-routing below would
|
||||
# instead push the definition into the owner module's *unwritten* backend
|
||||
# module (discarded in this process) and emit only an extern here, leaving the
|
||||
# symbol undefined at link — the refc `NTI*` undefined-reference bug. (V2 got
|
||||
# this gate in 8e0dd4bfb; V1, only reached under `--mm:refc`, was missed.)
|
||||
let perModuleCg = m.config.cmd == cmdNifC and m.config.icBackendStage == "cg"
|
||||
if not perModuleCg and owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
|
||||
dbgNti "extern:ownerRouted"
|
||||
var owner = t.skipTypes(typedescPtrs).itemId.module
|
||||
if owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
|
||||
# make sure the type info is created in the owner module
|
||||
discard genTypeInfoV1(m.g.mods[owner], origType, info)
|
||||
# reference the type info as extern here
|
||||
@@ -2218,7 +2001,6 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
else:
|
||||
owner = m.module.position.int32
|
||||
|
||||
dbgNti "DEFINED-HERE"
|
||||
m.g.typeInfoMarker[sig] = (str: result, owner: owner)
|
||||
#rememberEmittedTypeInfo(m.g.graph, FileIndex(owner), $result)
|
||||
|
||||
@@ -2289,8 +2071,8 @@ proc genTypeInfo*(config: ConfigRef, m: BModule; t: PType; info: TLineInfo): Rop
|
||||
|
||||
proc retrieveSym(n: PNode): PSym =
|
||||
case n.kind
|
||||
of nkPostfix: result = retrieveSym(n.secondSon)
|
||||
of nkPragmaExpr, nkTypeDef: result = retrieveSym(n.firstSon)
|
||||
of nkPostfix: result = retrieveSym(n[1])
|
||||
of nkPragmaExpr, nkTypeDef: result = retrieveSym(n[0])
|
||||
of nkSym: result = n.sym
|
||||
else: result = nil
|
||||
|
||||
@@ -2305,21 +2087,3 @@ proc genTypeSection(m: BModule, n: PNode) =
|
||||
discard getTypeDescAux(m, s.typ, intSet, descKindFromSymKind(s.kind))
|
||||
if m.g.generatedHeader != nil:
|
||||
discard getTypeDescAux(m.g.generatedHeader, s.typ, intSet, descKindFromSymKind(s.kind))
|
||||
|
||||
# Unlike genCppInitializer which returns just the braced value list (e.g. "{a, b}"),
|
||||
# genCppConstructorExpr returns a full type-prefixed expression (e.g. "Foo(a, b)").
|
||||
# This is used when a standalone construction expression is needed — e.g. on the
|
||||
# right-hand side of an assignment — whereas genCppInitializer is used in variable
|
||||
# declarations where the type is already written separately before the initializer.
|
||||
proc genCppConstructorExpr(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): Snippet =
|
||||
var params = ""
|
||||
if typ.bindingId in m.g.graph.initializersPerType:
|
||||
let call = m.g.graph.initializersPerType[typ.bindingId]
|
||||
if call != nil:
|
||||
var p = prc
|
||||
if p == nil:
|
||||
p = BProc(module: m)
|
||||
params = genCppParamsForCtor(p, call, didGenTemp)
|
||||
if prc == nil:
|
||||
assert p.blocks.len == 0, "BProc belongs to a struct doesnt have blocks"
|
||||
result = getTypeDesc(m, typ, dkVar) & "(" & params & ")"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import
|
||||
ast, types, msgs, wordrecg,
|
||||
platform, trees, options, cgendata, mangleutils, renderer, modulegraphs
|
||||
platform, trees, options, cgendata, mangleutils, renderer
|
||||
|
||||
import std/[hashes, strutils, formatfloat]
|
||||
|
||||
@@ -22,13 +22,13 @@ proc getPragmaStmt*(n: PNode, w: TSpecialWord): PNode =
|
||||
case n.kind
|
||||
of nkStmtList:
|
||||
result = nil
|
||||
for it in sons(n):
|
||||
result = getPragmaStmt(it, w)
|
||||
for i in 0..<n.len:
|
||||
result = getPragmaStmt(n[i], w)
|
||||
if result != nil: break
|
||||
of nkPragma:
|
||||
result = nil
|
||||
for it in sons(n):
|
||||
if whichPragma(it) == w: return it
|
||||
for i in 0..<n.len:
|
||||
if whichPragma(n[i]) == w: return n[i]
|
||||
else:
|
||||
result = nil
|
||||
|
||||
@@ -92,7 +92,7 @@ proc ccgIntroducedPtr*(conf: ConfigRef; s: PSym, retType: PType): bool =
|
||||
result = true
|
||||
elif (optByRef in s.options) or (getSize(conf, pt) > conf.target.floatSize * 3):
|
||||
result = true # requested anyway
|
||||
elif (tfFinal in pt.flags) and (pt.baseClass == nil):
|
||||
elif (tfFinal in pt.flags) and (pt[0] == nil):
|
||||
result = false # no need, because no subtyping possible
|
||||
else:
|
||||
result = true # ordinary objects are always passed by reference,
|
||||
@@ -112,26 +112,10 @@ proc encodeName*(name: string): string =
|
||||
|
||||
proc makeUnique(m: BModule; s: PSym, name: string = ""): string =
|
||||
result = if name == "": s.name.s else: name
|
||||
# keep backend-minted ids out of the `_u` namespace; their item counter
|
||||
# restarts at 0 and would collide with loaded symbols' ids. Which integer
|
||||
# identifies such a symbol is decided ONCE, in `astdef.backendMintedDisamb`,
|
||||
# shared with `mangleProcNameExt` and `ast2nif.toNifSymName`.
|
||||
if s.itemId.isBackendMinted:
|
||||
result.add "_c"
|
||||
result.add $backendMintedDisamb(s)
|
||||
else:
|
||||
result.add "_u"
|
||||
# Mirror `mangleProcNameExt`: use the per-(module,name) `disamb`, NOT
|
||||
# `itemId.item`. Under the per-module IC backend the same symbol is loaded
|
||||
# from a NIF in many processes and `itemId.item` is a fresh, load-order
|
||||
# dependent counter — so a method base would mangle to `_u1` in one module,
|
||||
# `_u3` in another and clean at its owner, none of which link. `disamb` is
|
||||
# assigned deterministically per (module, name) and is serialized, so every
|
||||
# process that touches the symbol derives the identical C name.
|
||||
result.add $s.disamb
|
||||
# module suffix LAST (a strippable trailing token; see `mangleProcNameExt`)
|
||||
result.add "__"
|
||||
result.add m.g.graph.ifaces[s.itemId.module].uniqueName
|
||||
result.add "_u"
|
||||
result.add $s.itemId.item
|
||||
|
||||
proc encodeSym*(m: BModule; s: PSym; makeUnique: bool = false; extra: string = ""): string =
|
||||
#Module::Type
|
||||
@@ -148,7 +132,7 @@ proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
|
||||
of tyObject, tyEnum, tyDistinct, tyUserTypeClass, tyGenericParam:
|
||||
result = encodeSym(m, t.sym)
|
||||
of tyGenericInst, tyUserTypeClassInst, tyGenericBody:
|
||||
result = encodeName(t.genericHead.sym.name.s)
|
||||
result = encodeName(t[0].sym.name.s)
|
||||
result.add "I"
|
||||
for i in 1..<t.len - 1:
|
||||
result.add encodeType(m, t[i], staticLists)
|
||||
@@ -160,7 +144,8 @@ proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
|
||||
of tySequence: encodeName("seq")
|
||||
else: encodeName(kindName)
|
||||
result.add "I"
|
||||
for s in kids(t):
|
||||
for i in 0..<t.len:
|
||||
let s = t[i]
|
||||
if s.isNil: continue
|
||||
result.add encodeType(m, s, staticLists)
|
||||
result.add "E"
|
||||
@@ -171,12 +156,12 @@ proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
|
||||
raiseAssert "unreachable"
|
||||
of tyRange:
|
||||
var val = "range_"
|
||||
if t.n.firstSon.typ.kind in {tyFloat..tyFloat128}:
|
||||
val.addFloat t.n.firstSon.floatVal
|
||||
if t.n[0].typ.kind in {tyFloat..tyFloat128}:
|
||||
val.addFloat t.n[0].floatVal
|
||||
val.add "_"
|
||||
val.addFloat t.n.secondSon.floatVal
|
||||
val.addFloat t.n[1].floatVal
|
||||
else:
|
||||
val.add $t.n.firstSon.intVal & "_" & $t.n.secondSon.intVal
|
||||
val.add $t.n[0].intVal & "_" & $t.n[1].intVal
|
||||
result = encodeName(val)
|
||||
of tyString..tyUInt64, tyPointer, tyBool, tyChar, tyVoid, tyAnything, tyNil, tyEmpty:
|
||||
result = encodeName(kindName)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -75,13 +75,10 @@ type
|
||||
flags*: set[TCProcFlag]
|
||||
lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements
|
||||
currLineInfo*: TLineInfo # AST codegen will make this superfluous
|
||||
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]]
|
||||
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]]
|
||||
# in how many nested try statements we are
|
||||
# (the vars must be volatile then)
|
||||
# `inExcept` is true when we are in the except part of a try block.
|
||||
# `isHidden` is true for compiler-injected `nkHiddenTryStmt` wrappers
|
||||
# (e.g. ARC's destructor try/finally around `except T as e:` bodies);
|
||||
# finallyActions walks past such wrappers to reach the user's try.
|
||||
# bool is true when are in the except part of a try block
|
||||
finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when
|
||||
# using return in finally statements
|
||||
labels*: Natural # for generating unique labels in the C proc
|
||||
@@ -142,13 +139,6 @@ type
|
||||
# not a list of IDs nor can it be made to be one.
|
||||
mangledPrcs*: HashSet[string]
|
||||
|
||||
icEmitted*: IntSet
|
||||
## Under `--icBackendStage:cg`: the positions of the modules THIS process
|
||||
## writes a translation unit for. `cgen.findPendingModule` consults it to
|
||||
## decide where a demanded definition goes — see the comment there. Empty
|
||||
## outside that stage, which is why every other backend keeps the ordinary
|
||||
## whole-program routing.
|
||||
|
||||
TCGen = object of PPassContext # represents a C source file
|
||||
s*: TCFileSections # sections of the C file
|
||||
flags*: set[CodegenFlag]
|
||||
@@ -165,12 +155,6 @@ type
|
||||
forwTypeCache*: TypeCache # cache for forward declarations of types
|
||||
declaredThings*: IntSet # things we have declared in this .c file
|
||||
declaredProtos*: IntSet # prototypes we have declared in this .c file
|
||||
emittedContentDefs*: HashSet[string]
|
||||
# cmdNifC per-module backend: content-addressed C names (generic
|
||||
# instances and synthesized hooks) whose body this TU already emitted.
|
||||
# Distinct symbols (minted in different source modules) can share one
|
||||
# `_i<disamb>` name; `declaredThings` keys on symbol id and lets the
|
||||
# second one through, so we dedup the body by name here instead.
|
||||
queue*: seq[PSym] # queue of procs to generate
|
||||
alive*: IntSet # symbol IDs of alive data as computed by `dce.nim`
|
||||
headerFiles*: seq[string] # needed headers to include
|
||||
@@ -189,21 +173,6 @@ type
|
||||
extensionLoaders*: array['0'..'9', Builder] # special procs for the
|
||||
# OpenGL wrapper
|
||||
sigConflicts*: CountTable[SigHash]
|
||||
icImplMods*: IntSet # module ids whose routine BODIES this TU
|
||||
# embeds (redirected defs, shared instances,
|
||||
# hooks); recorded as the artifact's cdeps so
|
||||
# the reuse gate can check their impl cookies
|
||||
icGlobalDtorName*: string # per-module backend: the C name of this
|
||||
# module's global-destructor proc, recorded in
|
||||
# the artifact's meta head so the main module's
|
||||
# `cg` — a different process — can call it
|
||||
icDataDefs*: seq[tuple[cname, nifname: string]]
|
||||
# C names of data definitions (consts, globals,
|
||||
# RTTI) this TU embeds plus their NIF symbol
|
||||
# names (empty for RTTI, which has no symbol);
|
||||
# recorded in the cnif artifact so a later run
|
||||
# can reuse the TU and re-demand definitions
|
||||
# that cached TUs still reference
|
||||
g*: BModuleList
|
||||
|
||||
template config*(m: BModule): ConfigRef = m.g.config
|
||||
@@ -245,8 +214,7 @@ proc newProc*(prc: PSym, module: BModule): BProc =
|
||||
|
||||
proc newModuleList*(g: ModuleGraph): BModuleList =
|
||||
BModuleList(typeInfoMarker: initTable[SigHash, tuple[str: Rope, owner: int32]](),
|
||||
config: g.config, graph: g, nimtvDeclared: initIntSet(),
|
||||
icEmitted: initIntSet())
|
||||
config: g.config, graph: g, nimtvDeclared: initIntSet())
|
||||
|
||||
iterator cgenModules*(g: BModuleList): BModule =
|
||||
for m in g.modulesClosed:
|
||||
|
||||
@@ -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 &
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
|
||||
import
|
||||
ast, msgs, idents,
|
||||
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos, trees
|
||||
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos
|
||||
|
||||
import std/tables
|
||||
|
||||
@@ -167,8 +167,6 @@ type
|
||||
curExcSym: PSym # Current exception
|
||||
externExcSym: PSym # Extern exception: what would getCurrentException() return outside of closure iter
|
||||
|
||||
enclosingPragmas: seq[PNode] # stack of pragma blocks wrapping stmtlist
|
||||
|
||||
states: seq[State] # The resulting states. Label is int literal.
|
||||
finallyPathStack: seq[FinallyTarget] # Stack of split blocks, whiles and finallies
|
||||
stateLoopLabel: PSym # Label to break on, when jumping between states.
|
||||
@@ -254,8 +252,7 @@ proc newCurExcAccess(ctx: var Ctx): PNode =
|
||||
ctx.newEnvVarAccess(ctx.curExcSym)
|
||||
|
||||
proc newStateLabel(ctx: Ctx): PNode =
|
||||
result = nkIntLit.newIntNode(0)
|
||||
result.typ = getSysType(ctx.g, TLineInfo(), tyInt16)
|
||||
ctx.g.newIntLit(TLineInfo(), 0)
|
||||
|
||||
proc newState(ctx: var Ctx, n: PNode, inlinable: bool, label: PNode): PNode =
|
||||
# Creates a new state, adds it to the context
|
||||
@@ -336,14 +333,9 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
||||
var cond: PNode = nil
|
||||
for i in 0..<c.len - 1:
|
||||
assert(c[i].kind == nkType)
|
||||
# Use the :curExc env field (set by the wrapper before entering the
|
||||
# except landing state) instead of calling getCurrentException():
|
||||
# injectdestructors does not process the args of this raw generic
|
||||
# `of` magic call, so an owning getCurrentException() temp would
|
||||
# never be destroyed and the caught exception would leak (#23615).
|
||||
let nextCond = newTreeIT(nkCall, c.info, ctx.g.getSysType(c.info, tyBool),
|
||||
newSymNode(g.getSysMagic(c.info, "of", mOf)),
|
||||
ctx.newCurExcAccess(),
|
||||
g.callCodegenProc("getCurrentException"),
|
||||
c[i])
|
||||
|
||||
cond = if cond.isNil: nextCond
|
||||
@@ -600,7 +592,10 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
let branch = n[i]
|
||||
case branch.kind
|
||||
of nkExceptBranch:
|
||||
branch[^1] = ctx.convertExprBodyToAsgn(branch[^1], tmp)
|
||||
if branch[0].kind == nkType:
|
||||
branch[1] = ctx.convertExprBodyToAsgn(branch[1], tmp)
|
||||
else:
|
||||
branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp)
|
||||
of nkFinally:
|
||||
discard
|
||||
else:
|
||||
@@ -990,14 +985,9 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
for j in i + 1..<n.len:
|
||||
s.add(n[j])
|
||||
|
||||
var body = s
|
||||
for pragma in ctx.enclosingPragmas:
|
||||
body = newTreeI(nkPragmaBlock, n[i + 1].info,
|
||||
pragma[0].copyTree, body)
|
||||
|
||||
n.sons.setLen(i + 1)
|
||||
discard ctx.newState(body, true, label)
|
||||
if ctx.transformClosureIteratorBody(body, gotoOut) != body:
|
||||
discard ctx.newState(s, true, label)
|
||||
if ctx.transformClosureIteratorBody(s, gotoOut) != s:
|
||||
internalError(ctx.g.config, "transformClosureIteratorBody != s")
|
||||
break
|
||||
else:
|
||||
@@ -1135,14 +1125,6 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit)
|
||||
dec ctx.curFinallyLevel
|
||||
|
||||
of nkPragmaBlock:
|
||||
# Propagate the pragma blocks so that blocks like {.cast(uncheckedAssign).}
|
||||
# remain effective
|
||||
ctx.enclosingPragmas.add(n)
|
||||
n[1] = ctx.transformClosureIteratorBody(n[1], gotoOut)
|
||||
discard ctx.enclosingPragmas.pop()
|
||||
result = n
|
||||
|
||||
of nkGotoState, nkForStmt:
|
||||
internalError(ctx.g.config, "closure iter " & $n.kind)
|
||||
|
||||
@@ -1408,34 +1390,18 @@ proc optimizeStates(ctx: var Ctx) =
|
||||
for i in 0 .. ctx.states.high:
|
||||
ctx.states[i].label.intVal = i
|
||||
|
||||
proc detectCapturedSym(c: var Ctx, s: PSym, stateIdx: int) =
|
||||
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym:
|
||||
let vs = c.varStates.getOrDefault(s.itemId, localNotSeen)
|
||||
if vs == localNotSeen: # First seing this variable
|
||||
c.varStates[s.itemId] = stateIdx
|
||||
elif vs == localRequiresLifting:
|
||||
discard # Sym already marked
|
||||
elif vs != stateIdx:
|
||||
c.captureVar(s)
|
||||
|
||||
proc isClosureIterLocal(c: Ctx, s: PSym): bool =
|
||||
s.kind in {skResult, skVar, skLet, skForVar, skTemp} and
|
||||
sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym
|
||||
|
||||
proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) =
|
||||
case n.kind
|
||||
of nkSym:
|
||||
let s = n.sym
|
||||
detectCapturedSym(c, s, stateIdx)
|
||||
of nkAddr, nkHiddenAddr:
|
||||
let s = getRoot(n)
|
||||
if s != nil and isClosureIterLocal(c, s):
|
||||
detectCapturedSym(c, s, stateIdx)
|
||||
# bug #25596; lifetime extension for `addr`-taken locals as
|
||||
# we claim ARC/ORC do destruction based on scopes, not on last-usages.
|
||||
c.captureVar(s)
|
||||
for i in 0 ..< n.safeLen:
|
||||
detectCapturedVars(c, n[i], stateIdx)
|
||||
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym:
|
||||
let vs = c.varStates.getOrDefault(s.itemId, localNotSeen)
|
||||
if vs == localNotSeen: # First seing this variable
|
||||
c.varStates[s.itemId] = stateIdx
|
||||
elif vs == localRequiresLifting:
|
||||
discard # Sym already marked
|
||||
elif vs != stateIdx:
|
||||
c.captureVar(s)
|
||||
of nkReturnStmt:
|
||||
if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}:
|
||||
# we have a `result = result` expression produced by the closure
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -24,7 +24,7 @@ bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep")
|
||||
bootSwitch(usedGoGC, defined(gogc), "--gc:go")
|
||||
bootSwitch(usedNoGC, defined(nogc), "--gc:none")
|
||||
|
||||
import std/[setutils, sets, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
|
||||
import std/[setutils, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
|
||||
import
|
||||
msgs, options, nversion, condsyms, extccomp, platform,
|
||||
wordrecg, nimblecmd, lineinfos, pathutils
|
||||
@@ -250,7 +250,6 @@ const
|
||||
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
|
||||
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
|
||||
errInvalidFeatureButXFound = Feature.toSeq.map(proc(val:Feature): string = "'$1'" % $val).join(", ") & " expected, but '$1' found"
|
||||
errDefaultOrSsoExpectedButXFound = "'default' or 'sso' expected, but '$1' found"
|
||||
|
||||
template warningOptionNoop(switch: string) =
|
||||
warningDeprecated(conf, info, "'$#' is deprecated, now a noop" % switch)
|
||||
@@ -307,13 +306,6 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errInvalidExceptionSystem % arg)
|
||||
of "strings":
|
||||
case arg.normalize
|
||||
of "default": result = conf.selectedStrings == stringDefault
|
||||
of "sso": result = conf.selectedStrings == stringSso
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
|
||||
of "experimental":
|
||||
try:
|
||||
result = conf.features.contains parseEnum[Feature](arg)
|
||||
@@ -508,8 +500,6 @@ proc parseCommand*(command: string): Command =
|
||||
of "jsonscript": cmdJsonscript
|
||||
of "nifc": cmdNifC # generate C from NIF files
|
||||
of "ic": cmdIc # generate .build.nif for nifmake
|
||||
of "icconfig": cmdIcConfig # produce the precompiled config artifact
|
||||
of "track": cmdTrack # IDE goto-def / find-usages over `nim ic`'s NIF output
|
||||
else: cmdUnknown
|
||||
|
||||
proc setCmd*(conf: ConfigRef, cmd: Command) =
|
||||
@@ -627,11 +617,7 @@ proc processMemoryManagementOption(switch, arg: string, pass: TCmdLinePass,
|
||||
conf.selectedGC = gcHooks
|
||||
defineSymbol(conf.symbols, "gchooks")
|
||||
incl conf.globalOptions, optSeqDestructors
|
||||
# (The `arg` here is the mm MODE — "hooks" — so feeding it to an on/off
|
||||
# switch made `--mm:hooks` fail outright with "'on' or 'off' expected, but
|
||||
# 'hooks' found". The `incl` above is what that call was meant to do.
|
||||
# Reachable only via the explicit switch: `--newruntime` sets
|
||||
# `selectedGC` directly, which is why this stayed hidden.)
|
||||
processOnOffSwitchG(conf, {optSeqDestructors}, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
of "go":
|
||||
@@ -659,18 +645,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf: ConfigRef) =
|
||||
var key = ""
|
||||
var val = ""
|
||||
# Record config-file switches so the `nim ic` driver can serialise them into a
|
||||
# precompiled-config artifact and have its per-module child processes replay
|
||||
# them instead of re-parsing the `nim.cfg` chain (and re-running `config.nims`
|
||||
# in the VM) on every invocation. Only `passPP` (config-file) switches are
|
||||
# captured; command-line switches are forwarded by the build graph as usual.
|
||||
# Path-search switches are skipped: their net effect already lives in the
|
||||
# resolved `searchPaths` the driver forwards as `--path`, and replaying their
|
||||
# raw (often relative-to-config-dir) arguments here would misresolve.
|
||||
if pass == passPP and switch.normalize notin
|
||||
["path", "p", "nimblepath", "lazypath", "excludepath",
|
||||
"nonimblepath", "clearnimblepath", "nimcache"]:
|
||||
conf.icConfigSwitches.add (switch, arg)
|
||||
case switch.normalize
|
||||
of "eval":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
@@ -723,14 +697,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf.outDir = processPath(conf, arg, info, notRelativeToProj=true)
|
||||
of "usenimcache":
|
||||
processOnOffSwitchG(conf, {optUseNimcache}, arg, pass, info)
|
||||
of "ideimports":
|
||||
# nimsuggest: where the import closure comes from. IC is opt-in.
|
||||
# nif|on load unchanged imports from precompiled NIF (cmdM)
|
||||
# source|off (default) recompile the whole closure from source (cmdCheck)
|
||||
case arg.normalize
|
||||
of "nif", "on", "": conf.ideImportsFromNif = true
|
||||
of "source", "off": conf.ideImportsFromNif = false
|
||||
else: localError(conf, info, "'--ideImports' expects 'nif' or 'source', got: '$1'" % arg)
|
||||
of "docseesrcurl":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.docSeeSrcUrl = arg
|
||||
@@ -784,17 +750,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "mm":
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "strings":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
case arg.normalize
|
||||
of "default":
|
||||
conf.selectedStrings = stringDefault
|
||||
of "sso":
|
||||
conf.selectedStrings = stringSso
|
||||
defineSymbol(conf.symbols, "nimsso")
|
||||
else:
|
||||
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
|
||||
of "warnings", "w":
|
||||
if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
|
||||
of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
|
||||
@@ -830,8 +785,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
localError(conf, info, "expected nim|cpp but found " & arg)
|
||||
of "compress":
|
||||
conf.globalOptions.incl optCompress
|
||||
of "genbif":
|
||||
processOnOffSwitchG(conf, {optGenBif}, arg, pass, info)
|
||||
of "g": # alias for --debugger:native
|
||||
conf.globalOptions.incl optCDebug
|
||||
conf.options.incl optLineDir
|
||||
@@ -847,7 +800,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "hotcodereloading":
|
||||
processOnOffSwitchG(conf, {optHotCodeReloading}, arg, pass, info)
|
||||
if conf.hcrOn:
|
||||
warningDeprecated(conf, info, "hotCodeReloading is deprecated, see https://github.com/nim-lang/RFCs/issues/573 for further information")
|
||||
defineSymbol(conf.symbols, "hotcodereloading")
|
||||
defineSymbol(conf.symbols, "useNimRtl")
|
||||
# hardcoded linking with dynamic runtime for MSVC for smaller binaries
|
||||
@@ -952,53 +904,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
else: localError(conf, info, errOnOrOffExpectedButXFound % arg)
|
||||
of "noimportdoc":
|
||||
processOnOffSwitchG(conf, {optNoImportdoc}, arg, pass, info)
|
||||
of "ismainmodule":
|
||||
# `nim m` (IC) only: marks the single module being checked as the program's
|
||||
# real entry point so that `isMainModule` and `when isMainModule:` resolve
|
||||
# correctly even though every module is compiled with `sfMainModule` set.
|
||||
conf.isMainModule = switchOn(arg)
|
||||
of "icgroup":
|
||||
# `nim m` only: register a module that belongs to the current strongly-
|
||||
# connected import group, so it is compiled from source (not loaded from a
|
||||
# precompiled NIF) and gets its own NIF written. `deps.nim` emits one
|
||||
# `--icGroup:<path>` per member of a dependency cycle. The argument is an
|
||||
# absolute .nim path produced by the dependency scanner.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icGroup.incl(canonicalizePath(conf, AbsoluteFile arg).string)
|
||||
of "icproject":
|
||||
# `nim m`/`nim nifc` only: the ORIGINAL project file (see options.icProject)
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icProject = canonicalizePath(conf, AbsoluteFile arg).string
|
||||
of "icpreparsedconfig":
|
||||
# `nim m`/`nim nifc` only: path of the precompiled-config artifact (see
|
||||
# options.icPreparsedConfig). Read in `passCmd1`, before `loadConfigs`, so
|
||||
# config loading can replay it instead of re-parsing the `nim.cfg` chain.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.icPreparsedConfig = arg
|
||||
of "icconfigout":
|
||||
# `nim icconfig` only: where to write the precompiled config artifact (see
|
||||
# options.icConfigOut). The `nim ic` driver spawns the producer with this.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.icConfigOut = arg
|
||||
of "icbackendstage":
|
||||
# `nim nifc` only: per-module backend stage, one of cg|merge|emit (see
|
||||
# options.icBackendStage). Empty (switch unused) keeps the whole-program
|
||||
# backend. Emitted by `deps.nim`'s backend build file.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icBackendStage = arg
|
||||
of "icbackendmodule", "icbackendmodules":
|
||||
# `nim nifc` only: the NIF module suffixes the lower/cg/emit stage operates
|
||||
# on, comma-separated — the invocation's batch (see
|
||||
# options.icBackendModules). The singular spelling is the same switch: a
|
||||
# one-module batch is what the per-module fan-out passes.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icBackendModules = @[]
|
||||
for suffix in arg.split(','):
|
||||
if suffix.len > 0: conf.icBackendModules.add suffix
|
||||
of "import":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
@@ -1006,7 +911,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
if m.len == 0:
|
||||
localError(conf, info, "Cannot resolve filename: " & arg)
|
||||
else:
|
||||
conf.implicitImports.add(if arg.startsWith(stdPrefix): arg else: m)
|
||||
conf.implicitImports.add m
|
||||
of "include":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
@@ -1046,7 +951,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
var value: int = 10_000_000
|
||||
discard parseSaturatedNatural(arg, value)
|
||||
if value <= 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero")
|
||||
if not value > 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero")
|
||||
conf.maxLoopIterationsVM = value
|
||||
of "maxcalldepthvm":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
@@ -1096,14 +1001,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
helpOnError(conf, pass)
|
||||
of "symbolfiles", "incremental", "ic":
|
||||
if pass in {passCmd2, passPP} and switch.normalize == "symbolfiles":
|
||||
deprecatedAlias(switch, "incremental")
|
||||
if switch.normalize == "symbolfiles": deprecatedAlias(switch, "incremental")
|
||||
# xxx maybe also ic, since not in help?
|
||||
# `--ic:on` is read in passCmd1 too: `nim.nim` decides BEFORE config loading
|
||||
# whether this run is an IC driver (`ensureIcConfig` must produce the
|
||||
# precompiled config the driver itself then replays), and passCmd1 is the
|
||||
# only pass that has run by then.
|
||||
if pass in {passCmd1, passCmd2, passPP}:
|
||||
if pass in {passCmd2, passPP}:
|
||||
case arg.normalize
|
||||
of "on": conf.ic = true
|
||||
of "legacy": conf.symbolFiles = v2Sf
|
||||
@@ -1341,16 +1241,8 @@ proc processArgument*(pass: TCmdLinePass; p: OptParser;
|
||||
# support UNIX style filenames everywhere for portable build scripts:
|
||||
if config.projectName.len == 0:
|
||||
config.projectName = unixToNativePath(p.key)
|
||||
if config.cmd == cmdTrack:
|
||||
# `nim track PROJ --def:...`: unlike a normal command (where everything
|
||||
# after the project file is passed to the compiled program), `track`
|
||||
# accepts its IDE-query switches AFTER the project — the natural,
|
||||
# nimsuggest-like invocation form. So don't swallow the rest of the line
|
||||
# into `arguments`; keep parsing the remaining tokens as switches.
|
||||
result = false
|
||||
else:
|
||||
config.arguments = cmdLineRest(p)
|
||||
result = true
|
||||
config.arguments = cmdLineRest(p)
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
inc argsCount
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
## for details. Note this is a first implementation and only the "Concept matching"
|
||||
## section has been implemented.
|
||||
|
||||
import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types,
|
||||
layeredtable, semtypinst
|
||||
import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
|
||||
|
||||
import std/sets
|
||||
|
||||
@@ -72,8 +71,7 @@ proc semConceptDeclaration*(c: PContext; n: PNode): PNode =
|
||||
|
||||
type
|
||||
MatchFlags* = enum
|
||||
mfDontBind # Do not export bindings from the concept match
|
||||
mfBindGenericParam # Export inferred invocation parameters despite mfDontBind
|
||||
mfDontBind # Do not bind generic parameters
|
||||
mfCheckGeneric # formal <- formal comparison as opposed to formal <- operand
|
||||
|
||||
ConceptTypePair = tuple[conceptId, typeId: ItemId]
|
||||
@@ -207,7 +205,7 @@ proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon):
|
||||
|
||||
# Cycle detection: track (concept, type) pairs to prevent infinite recursion.
|
||||
# Returns true on cycle (coinductive semantics) to support co-dependent concepts.
|
||||
let pair: ConceptTypePair = (concpt.bindingId, potentialImpl.bindingId)
|
||||
let pair: ConceptTypePair = (concpt.itemId, potentialImpl.itemId)
|
||||
if pair in m.marker:
|
||||
return true
|
||||
m.marker.incl pair
|
||||
@@ -271,8 +269,10 @@ proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind =
|
||||
let
|
||||
fn = fc.conceptBody
|
||||
an = ac.conceptBody
|
||||
sameLen = fc.len == ac.len
|
||||
var match = false
|
||||
for fdef in fn:
|
||||
var cmpResult = false
|
||||
for ia, ndef in an:
|
||||
match = cmpConceptDefs(c, fdef, ndef, m)
|
||||
if match:
|
||||
@@ -330,10 +330,13 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
|
||||
result = matchType(c, f.skipModifier, a, m)
|
||||
of tyTypeDesc:
|
||||
if isSelf(f):
|
||||
let ua = a.skipTypes(asymmetricConceptParamMods)
|
||||
if m.magic in {mArrPut, mArrGet}:
|
||||
if m.potentialImplementation.reduceToBase.kind in arrPutGetMagicApplies:
|
||||
bindParam(c, m, a, last m.potentialImplementation)
|
||||
result = true
|
||||
#elif ua.isConcept:
|
||||
# result = matchType(c, m.concpt, ua, m)
|
||||
else:
|
||||
result = matchType(c, a.skipTypes(ignorableForArgType), m.potentialImplementation, m)
|
||||
else:
|
||||
@@ -575,17 +578,7 @@ proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool =
|
||||
# error was reported earlier.
|
||||
result = false
|
||||
|
||||
proc resolvedBinding(c: PContext; t: PType; m: MatchCon): PType =
|
||||
## An inferred concept parameter can refer to an implementation-local
|
||||
## generic parameter, for example `Elem[Impl.T]`. Resolve it while the
|
||||
## matcher's private bindings (`Impl.T -> int`) are still available.
|
||||
if t.containsUnresolvedType:
|
||||
prepareMetatypeForSigmatch(c, m.bindings, m.concpt.sym.info, t)
|
||||
else:
|
||||
t
|
||||
|
||||
proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
|
||||
invocation: PType; m: var MatchCon) =
|
||||
proc fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; m: var MatchCon) =
|
||||
# invocation != nil means we have a non-atomic concept:
|
||||
if invocation != nil and invocation.kind == tyGenericInvocation:
|
||||
assert concpt.sym.typ.kind == tyGenericBody
|
||||
@@ -597,9 +590,8 @@ proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
|
||||
continue
|
||||
let found = m.bindings.lookup(thisSym)
|
||||
if found != nil:
|
||||
let resolved = resolvedBinding(c, found, m)
|
||||
when logBindings: echo "Invocation bind: ", thisSym, " ", resolved
|
||||
bindings.put(thisSym, resolved)
|
||||
when logBindings: echo "Invocation bind: ", thisSym, " ", found
|
||||
bindings.put(thisSym, found)
|
||||
|
||||
# bind even more generic parameters
|
||||
let genBody = invocation.base
|
||||
@@ -615,20 +607,6 @@ proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
|
||||
bindings.put(invocation[i], boundV)
|
||||
bindings.put(concpt, m.potentialImplementation)
|
||||
|
||||
proc fixConstraintBindings(c: PContext; bindings: var LayeredIdTable;
|
||||
invocation: PType; m: MatchCon) =
|
||||
## Propagates only the dependent parameters of a concept constraint. The
|
||||
## concept itself and its private matcher bindings must remain unbound so
|
||||
## that independent constraints using the same concept don't get coupled.
|
||||
if invocation != nil and invocation.kind == tyGenericInvocation:
|
||||
let genBody = invocation.base
|
||||
assert genBody.kind == tyGenericBody
|
||||
for i in FirstGenericParamAt ..< invocation.kidsLen:
|
||||
if lookup(bindings, invocation[i]) == nil:
|
||||
let boundValue = m.bindings.lookup(genBody[i - 1])
|
||||
if boundValue != nil:
|
||||
bindings.put(invocation[i], resolvedBinding(c, boundValue, m))
|
||||
|
||||
proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool =
|
||||
m.bindings = m.bindings.newTypeMapLayer()
|
||||
if invocation != nil and invocation.kind == tyGenericInst:
|
||||
@@ -638,11 +616,8 @@ proc processConcept(c: PContext; concpt, invocation: PType, bindings: var Layere
|
||||
if invocation[i].kind != tyVoid:
|
||||
bindParam(c, m, genericBody[i-1], invocation[i])
|
||||
result = conceptMatchNode(c, concpt.conceptBody, m)
|
||||
if result:
|
||||
if mfDontBind notin m.flags:
|
||||
fixBindings(c, bindings, concpt, invocation, m)
|
||||
elif mfBindGenericParam in m.flags:
|
||||
fixConstraintBindings(c, bindings, invocation, m)
|
||||
if result and mfDontBind notin m.flags:
|
||||
fixBindings(bindings, concpt, invocation, m)
|
||||
|
||||
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable; invocation: PType, flags: set[MatchFlags] = {}): bool =
|
||||
## Entry point from sigmatch. 'concpt' is the concept we try to match (here still a PType but
|
||||
|
||||
1782
compiler/deps.nim
1782
compiler/deps.nim
File diff suppressed because it is too large
Load Diff
@@ -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'"
|
||||
|
||||
@@ -148,7 +148,7 @@ proc cmpDecimalsIgnoreCase(a, b: string): int =
|
||||
limitB = iB
|
||||
while limitA < aLen and isDigit(a[limitA]): inc limitA
|
||||
while limitB < bLen and isDigit(b[limitB]): inc limitB
|
||||
var pos = max(limitA-iA, limitB-iB)
|
||||
var pos = max(limitA-iA, limitB-iA)
|
||||
while pos > 0:
|
||||
if limitA-pos < iA: # digit in `a` is 0 effectively
|
||||
result = ord('0') - ord(b[limitB-pos])
|
||||
@@ -425,6 +425,12 @@ template dispA(conf: ConfigRef; dest: var string, xml, tex: string,
|
||||
if not conf.isLatexCmd: dest.addf(xml, args)
|
||||
else: dest.addf(tex, args)
|
||||
|
||||
proc getVarIdx(varnames: openArray[string], id: string): int =
|
||||
for i in 0..high(varnames):
|
||||
if cmpIgnoreStyle(varnames[i], id) == 0:
|
||||
return i
|
||||
result = -1
|
||||
|
||||
proc genComment(d: PDoc, n: PNode): PRstNode =
|
||||
if n.comment.len > 0:
|
||||
if optDocRaw in d.conf.globalOptions:
|
||||
@@ -534,11 +540,10 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var string;
|
||||
elif s != nil and s.kind in {skType, skVar, skLet, skConst} and
|
||||
sfExported in s.flags and s.owner != nil and
|
||||
belongsToProjectPackage(d.conf, s.owner) and d.target == outHtml:
|
||||
let href = (if d.module == s.owner: ""
|
||||
else: externalDep(d, s.owner).changeFileExt("html")
|
||||
) & "#" & literal
|
||||
result.addf "<a href=\"$1\"><span class=\"Identifier\">$2</span></a>",
|
||||
[href, escLit]
|
||||
let external = externalDep(d, s.owner)
|
||||
result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
|
||||
[changeFileExt(external, "html"), literal,
|
||||
escLit]
|
||||
else:
|
||||
dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
|
||||
"\\spanIdentifier{$1}", [escLit])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
|
||||
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
|
||||
res.typ = getSysType(g, info, tyString)
|
||||
|
||||
result.typ = newType(tyProc, idgen, result)
|
||||
result.typ = newType(tyProc, idgen, t.owner)
|
||||
result.typ.n = newNodeI(nkFormalParams, info)
|
||||
rawAddSon(result.typ, res.typ)
|
||||
result.typ.n.add newNodeI(nkEffectList, info)
|
||||
@@ -48,4 +48,65 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
|
||||
n[resultPos] = newSymNode(res)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}
|
||||
setHookDisamb(g, result, "$enumtostr", t)
|
||||
|
||||
proc searchObjCaseImpl(obj: PNode; field: PSym): PNode =
|
||||
case obj.kind
|
||||
of nkSym:
|
||||
result = nil
|
||||
of nkElse, nkOfBranch:
|
||||
result = searchObjCaseImpl(obj.lastSon, field)
|
||||
else:
|
||||
if obj.kind == nkRecCase and obj[0].kind == nkSym and obj[0].sym == field:
|
||||
result = obj
|
||||
else:
|
||||
result = nil
|
||||
for x in obj:
|
||||
result = searchObjCaseImpl(x, field)
|
||||
if result != nil: break
|
||||
|
||||
proc searchObjCase(t: PType; field: PSym): PNode =
|
||||
result = searchObjCaseImpl(t.n, field)
|
||||
if result == nil and t.baseClass != nil:
|
||||
result = searchObjCase(t.baseClass.skipTypes({tyAlias, tyGenericInst, tyRef, tyPtr}), field)
|
||||
doAssert result != nil
|
||||
|
||||
proc genCaseObjDiscMapping*(t: PType; field: PSym; info: TLineInfo; g: ModuleGraph; idgen: IdGenerator): PSym =
|
||||
result = newSym(skProc, getIdent(g.cache, "objDiscMapping"), idgen, t.owner, info)
|
||||
|
||||
let dest = newSym(skParam, getIdent(g.cache, "e"), idgen, result, info)
|
||||
dest.typ = field.typ
|
||||
|
||||
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
|
||||
res.typ = getSysType(g, info, tyUInt8)
|
||||
|
||||
result.typ = newType(tyProc, idgen, t.owner)
|
||||
result.typ.n = newNodeI(nkFormalParams, info)
|
||||
rawAddSon(result.typ, res.typ)
|
||||
result.typ.n.add newNodeI(nkEffectList, info)
|
||||
|
||||
result.typ.addParam dest
|
||||
|
||||
var body = newNodeI(nkStmtList, info)
|
||||
var caseStmt = newNodeI(nkCaseStmt, info)
|
||||
caseStmt.add(newSymNode dest)
|
||||
|
||||
let subObj = searchObjCase(t, field)
|
||||
for i in 1..<subObj.len:
|
||||
let ofBranch = subObj[i]
|
||||
var newBranch = newNodeI(ofBranch.kind, ofBranch.info)
|
||||
for j in 0..<ofBranch.len-1:
|
||||
newBranch.add ofBranch[j]
|
||||
|
||||
newBranch.add newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res), newIntNode(nkInt8Lit, i)))
|
||||
caseStmt.add newBranch
|
||||
|
||||
body.add(caseStmt)
|
||||
|
||||
var n = newNodeI(nkProcDef, info, bodyPos+2)
|
||||
for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)
|
||||
n[namePos] = newSymNode(result)
|
||||
n[paramsPos] = result.typ.n
|
||||
n[bodyPos] = body
|
||||
n[resultPos] = newSymNode(res)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}
|
||||
|
||||
@@ -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}:
|
||||
|
||||
@@ -1473,8 +1473,6 @@ proc genFlags*(s: set[TNodeFlag]; dest: var string) =
|
||||
of nfSkipFieldChecking: dest.add "s0"
|
||||
of nfDisabledOpenSym: dest.add "d3"
|
||||
of nfLazyType: dest.add "l1"
|
||||
of nfLazyBody: discard # process-local placeholder; never serialized
|
||||
of nfBroadcast: dest.add "v"
|
||||
|
||||
|
||||
proc parse*(t: typedesc[TNodeFlag]; s: string): set[TNodeFlag] =
|
||||
@@ -1535,7 +1533,6 @@ proc parse*(t: typedesc[TNodeFlag]; s: string): set[TNodeFlag] =
|
||||
inc i
|
||||
else: result.incl nfSem
|
||||
of 't': result.incl nfTransf
|
||||
of 'v': result.incl nfBroadcast
|
||||
of 'w': result.incl nfFirstWrite
|
||||
else: discard
|
||||
inc i
|
||||
|
||||
@@ -14,82 +14,13 @@
|
||||
import ".." / [ast, modulegraphs, trees, extccomp, btrees,
|
||||
msgs, lineinfos, pathutils, options, cgmeth]
|
||||
|
||||
import std/[tables, os, strutils, syncio]
|
||||
import std/tables
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
const BackendActionsExt* = ".cflags"
|
||||
## Sidecar written by a module's `cg` stage next to its `.c`, carrying the C
|
||||
## compile/link directives that module's `{.passL.}`/`{.compile.}`/… pragmas
|
||||
## recorded. See `writeBackendActions`.
|
||||
|
||||
proc writeBackendActions*(g: ModuleGraph; module: PSym; list: PNode;
|
||||
outfile: string) =
|
||||
## Serialize the backend-relevant replay actions of ONE module to `outfile`,
|
||||
## one tab-separated action per line.
|
||||
##
|
||||
## The `link` stage used to recover these by loading the whole import closure
|
||||
## as `PrecompiledModule`s and re-running `replayBackendActions` over each —
|
||||
## a 3.7s whole-program graph load, per link, purely to recover a handful of
|
||||
## strings and the modules' `.c` paths. The producing `cg` process already has
|
||||
## them in hand, so it writes them down instead and `link` reads them back
|
||||
## (`applyBackendActions`). Written unconditionally, even when empty: it is a
|
||||
## declared nifmake output of the `cg` rule, and a missing output re-fires the
|
||||
## rule for ever.
|
||||
##
|
||||
## `localpassc` needs the module's own source path, which only the writer can
|
||||
## resolve, so it is baked in here as a third field.
|
||||
var content = ""
|
||||
if list != nil:
|
||||
for n in list:
|
||||
if n.kind == nkReplayAction and n.len >= 2 and
|
||||
n[0].kind == nkStrLit and n[1].kind == nkStrLit:
|
||||
case n[0].strVal
|
||||
of "compile":
|
||||
if n.len == 4 and n[2].kind == nkStrLit and n[3].kind == nkStrLit:
|
||||
content.add "compile\t" & n[1].strVal & "\t" & n[2].strVal & "\t" &
|
||||
n[3].strVal & "\n"
|
||||
of "link", "passl", "passc", "cppdefine":
|
||||
content.add n[0].strVal & "\t" & n[1].strVal & "\n"
|
||||
of "localpassc":
|
||||
content.add "localpassc\t" & n[1].strVal & "\t" &
|
||||
toFullPathConsiderDirty(g.config, module.info.fileIndex).string & "\n"
|
||||
else: discard
|
||||
writeFile(outfile, content)
|
||||
|
||||
proc applyBackendActions*(g: ModuleGraph; infile: string) =
|
||||
## Apply one module's recorded C directives (see `writeBackendActions`). The
|
||||
## `link` stage's replacement for loading that module and replaying its AST.
|
||||
if not fileExists(infile): return
|
||||
for line in lines(infile):
|
||||
if line.len == 0: continue
|
||||
let f = line.split('\t')
|
||||
case f[0]
|
||||
of "compile":
|
||||
if f.len == 4:
|
||||
let cname = AbsoluteFile f[1]
|
||||
var cf = Cfile(nimname: splitFile(cname).name, cname: cname,
|
||||
obj: AbsoluteFile f[2],
|
||||
flags: {CfileFlag.External}, customArgs: f[3])
|
||||
extccomp.addExternalFileToCompile(g.config, cf)
|
||||
of "link":
|
||||
if f.len == 2: extccomp.addExternalFileToLink(g.config, AbsoluteFile f[1])
|
||||
of "passl":
|
||||
if f.len == 2: extccomp.addLinkOption(g.config, f[1])
|
||||
of "passc":
|
||||
if f.len == 2: extccomp.addCompileOption(g.config, f[1])
|
||||
of "localpassc":
|
||||
if f.len == 3: extccomp.addLocalCompileOption(g.config, f[1], AbsoluteFile f[2])
|
||||
of "cppdefine":
|
||||
if f.len == 2: options.cppDefine(g.config, f[1])
|
||||
else: discard
|
||||
|
||||
proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
|
||||
## `list` is an `nkStmtList` of `nkReplayAction` nodes (macro-cache puts/incs/
|
||||
## adds/incls and a few pragmas) recorded for `module`. Under the NIF backend a
|
||||
## loaded module's `ast` is never reconstructed, so the caller passes the replay
|
||||
## actions it parsed out of the module's NIF directly.
|
||||
proc replayStateChanges*(module: PSym; g: ModuleGraph) =
|
||||
let list = module.ast
|
||||
assert list != nil
|
||||
assert list.kind == nkStmtList
|
||||
for n in list:
|
||||
@@ -133,9 +64,8 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
|
||||
g.cacheTables[destKey] = initBTree[string, PNode]()
|
||||
if not contains(g.cacheTables[destKey], key):
|
||||
g.cacheTables[destKey].add(key, val)
|
||||
# else: the same key was already replayed. Under IC the import closure is
|
||||
# replayed (direct module + transitive deps), so the same registration can
|
||||
# legitimately be reached twice; re-applying it is a no-op, not an error.
|
||||
else:
|
||||
internalError(g.config, n.info, "key already exists: " & key)
|
||||
of "incl":
|
||||
let destKey = n[1].strVal
|
||||
let val = n[2]
|
||||
@@ -156,37 +86,3 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
|
||||
g.cacheSeqs[destKey].add val
|
||||
else:
|
||||
internalAssert g.config, false
|
||||
|
||||
proc replayBackendActions*(g: ModuleGraph; module: PSym; list: PNode) =
|
||||
## Applies the backend-relevant replay actions (C compile/link directives)
|
||||
## found in a NIF-loaded module's top-level statement list. The `nifc`
|
||||
## backend loads modules without going through sem's `replayStateChanges`,
|
||||
## so e.g. math's `{.passL: "-lm".}` was lost and the final link failed
|
||||
## with undefined references. VM cache actions are deliberately NOT
|
||||
## replayed here — codegen does not run macros.
|
||||
if list == nil: return
|
||||
for n in list:
|
||||
if n.kind == nkReplayAction and n.len >= 2 and
|
||||
n[0].kind == nkStrLit and n[1].kind == nkStrLit:
|
||||
case n[0].strVal
|
||||
of "compile":
|
||||
if n.len == 4 and n[2].kind == nkStrLit:
|
||||
let cname = AbsoluteFile n[1].strVal
|
||||
var cf = Cfile(nimname: splitFile(cname).name, cname: cname,
|
||||
obj: AbsoluteFile n[2].strVal,
|
||||
flags: {CfileFlag.External},
|
||||
customArgs: n[3].strVal)
|
||||
extccomp.addExternalFileToCompile(g.config, cf)
|
||||
of "link":
|
||||
extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal)
|
||||
of "passl":
|
||||
extccomp.addLinkOption(g.config, n[1].strVal)
|
||||
of "passc":
|
||||
extccomp.addCompileOption(g.config, n[1].strVal)
|
||||
of "localpassc":
|
||||
extccomp.addLocalCompileOption(g.config, n[1].strVal,
|
||||
toFullPathConsiderDirty(g.config, module.info.fileIndex))
|
||||
of "cppdefine":
|
||||
options.cppDefine(g.config, n[1].strVal)
|
||||
else:
|
||||
discard
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -1,111 +0,0 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2026 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Opt-in instrumentation for the IC backend, enabled with `-d:icBNodeProf`.
|
||||
## Off, every template below is `discard` and nothing is linked in.
|
||||
##
|
||||
## It lives in its own module with NO compiler imports so that any stage can
|
||||
## use it without creating a cycle — `ast2nif` for the loader, `nifbackend` for
|
||||
## the stage phases, `cgen` for what happens per routine.
|
||||
##
|
||||
## Each backend process appends ONE line to `$NIM_IC_BNODE_PROF` at exit (or to
|
||||
## stderr when that is unset), because a `--ic:on` build fans out a process per
|
||||
## module per stage and interleaved writes would tear. Use `-d:icNoParallel`
|
||||
## when the numbers need to be attributable to a particular module.
|
||||
##
|
||||
## Counts are for volume, timings for cost, and the two answer different
|
||||
## questions: a call count alone once pointed at the wrong accessor (700k calls
|
||||
## worth 8ms) while the real cost was 259k `info` resolutions worth 1.36s.
|
||||
|
||||
when defined(icBNodeProf):
|
||||
import std / [envvars, exitprocs, syncio, monotimes]
|
||||
from std / times import inNanoseconds
|
||||
|
||||
type
|
||||
ProfSlot* = enum
|
||||
pTyp, pIfaceExported, pIfaceHidden, pIfaceModules,
|
||||
pTopNodes, pExportSyms, pPeekKind, pPeekFallback, pPeekLoaded,
|
||||
pTopToolingSkip
|
||||
TimeSlot* = enum
|
||||
tLoadClosure, tModuleId, tBifLoad, tPosIndex, tTopLevel, tInterfTables,
|
||||
tTransform, tGenBody, tExportBranch, tResolveSym, tEnumFields,
|
||||
# Coarse phases, added to find where a backend process spends the time
|
||||
# that none of the slots above account for. `tStage` is the whole stage
|
||||
# body, so `Process - tStage` is everything before it: exec, the Nim
|
||||
# runtime, config replay, `registerNifSuffix`/graph setup.
|
||||
tStage,
|
||||
tLowerOwned, tLowerHooks, tLowerWrite,
|
||||
tCgGen, tCgInit, tCgFinish, tCgWrite,
|
||||
tMergeStage, tEmitRender, tLinkStage,
|
||||
# `nim m` (the frontend): the sem pass as a whole, and writing the module's
|
||||
# `.s.bif`. `Stage - WriteNif - <the loading slots>` is then sem proper.
|
||||
tWriteNif,
|
||||
# `processTopLevel`'s branches: which part of a module HEADER costs what.
|
||||
tTopReplay, tTopLogOps, tTopOffers, tTopStmts
|
||||
|
||||
let procStart = getMonoTime()
|
||||
## Set when this module initialises, i.e. essentially at process start, so
|
||||
## the dump can report total process wall time and the startup share can be
|
||||
## derived as `Process - Stage`.
|
||||
|
||||
var profStageName* = "frontend"
|
||||
## Which invocation this is: the backend stage name, or "frontend" for a
|
||||
## `nim m` process, which arms the profiler through ast2nif but never enters
|
||||
## a backend stage. Without it the `Process - Stage` startup figure is
|
||||
## meaningless — 204 frontend processes' whole runtime lands in it.
|
||||
|
||||
var profCounts: array[ProfSlot, int]
|
||||
var profNanos: array[TimeSlot, int64]
|
||||
var profStart: array[TimeSlot, MonoTime]
|
||||
var profArmed = false
|
||||
|
||||
proc profDump() =
|
||||
var line = "BNODEPROF stage=" & profStageName
|
||||
for s in ProfSlot: line.add " " & ($s)[1..^1] & "=" & $profCounts[s]
|
||||
for s in TimeSlot: line.add " " & ($s)[1..^1] & "ms=" & $(profNanos[s] div 1_000_000)
|
||||
line.add " Processms=" & $((getMonoTime() - procStart).inNanoseconds div 1_000_000)
|
||||
let f = getEnv("NIM_IC_BNODE_PROF")
|
||||
if f.len > 0:
|
||||
let h = open(f, fmAppend)
|
||||
h.writeLine line
|
||||
h.close()
|
||||
else:
|
||||
stderr.writeLine line
|
||||
|
||||
template armProf() =
|
||||
if not profArmed:
|
||||
profArmed = true
|
||||
addExitProc profDump
|
||||
|
||||
template prof*(s: ProfSlot; n = 1) =
|
||||
armProf()
|
||||
inc profCounts[s], n
|
||||
template icProfStart*(s: TimeSlot) =
|
||||
armProf()
|
||||
profStart[s] = getMonoTime()
|
||||
template icProfStop*(s: TimeSlot) =
|
||||
profNanos[s] += (getMonoTime() - profStart[s]).inNanoseconds
|
||||
|
||||
template timed*(s: TimeSlot; body: untyped) =
|
||||
## Leaf timing. NOT re-entrant, and the phase slots are not disjoint —
|
||||
## `tTransform` contains body materialization. Read them as nested, not
|
||||
## additive.
|
||||
##
|
||||
## Arms the dump like `prof`/`icProfStart` do. It did not, and so a process
|
||||
## whose ONLY instrumentation is a `timed` never reported at all: the
|
||||
## `merge`, `emit` and `link` stages were silently absent from every profile.
|
||||
armProf()
|
||||
let t0 = getMonoTime()
|
||||
body
|
||||
profNanos[s] += (getMonoTime() - t0).inNanoseconds
|
||||
else:
|
||||
template prof*(s: untyped; n = 1) = discard
|
||||
template icProfStart*(s: untyped) = discard
|
||||
template icProfStop*(s: untyped) = discard
|
||||
template timed*(s: untyped; body: untyped) = body
|
||||
@@ -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)
|
||||
@@ -13,7 +13,7 @@ import
|
||||
ast, msgs, options, idents, lookups,
|
||||
semdata, modulepaths, sigmatch, lineinfos,
|
||||
modulegraphs, wordrecg
|
||||
from std/strutils import `%`, startsWith, replace
|
||||
from std/strutils import `%`, startsWith
|
||||
from std/sequtils import addUnique
|
||||
import std/[sets, tables, intsets]
|
||||
|
||||
@@ -304,9 +304,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
|
||||
var prefix = ""
|
||||
if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; "
|
||||
message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated")
|
||||
let moduleNameNorm = getModuleName(c.config, n).replace("\\", "/")
|
||||
if belongsToStdlib(c.graph, result) and not startsWith(moduleNameNorm, stdPrefix) and
|
||||
not startsWith(moduleNameNorm, "system/") and not startsWith(moduleNameNorm, "packages/"):
|
||||
let moduleName = getModuleName(c.config, n)
|
||||
if belongsToStdlib(c.graph, result) and not startsWith(moduleName, stdPrefix) and
|
||||
not startsWith(moduleName, "system/") and not startsWith(moduleName, "packages/"):
|
||||
message(c.config, n.info, warnStdPrefix, realModule.name.s)
|
||||
|
||||
proc suggestMod(n: PNode; s: PSym) =
|
||||
|
||||
@@ -24,7 +24,7 @@ import std/[strtabs, tables, strutils, intsets]
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
from trees import exprStructuralEquivalent, getRoot, isCursor, whichPragma, getPotentialWrites
|
||||
from trees import exprStructuralEquivalent, getRoot, whichPragma, getPotentialWrites
|
||||
|
||||
type
|
||||
Con = object
|
||||
@@ -72,11 +72,9 @@ proc hasDestructor(c: Con; t: PType): bool {.inline.} =
|
||||
if not result and c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
assert(not containsGarbageCollectedRef(t))
|
||||
|
||||
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo; needsInit: bool): PNode =
|
||||
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo): PNode =
|
||||
let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.idgen, c.owner, info)
|
||||
sym.typ = typ
|
||||
if not needsInit:
|
||||
sym.incl sfNoInit
|
||||
s.vars.add(sym)
|
||||
result = newSymNode(sym)
|
||||
|
||||
@@ -173,12 +171,24 @@ template hasDestructorOrAsgn(c: var Con, typ: PType): bool =
|
||||
proc isLastRead(n: PNode; c: var Con; s: var Scope): bool =
|
||||
if not hasDestructorOrAsgn(c, n.typ): return true
|
||||
|
||||
let m = skipConvDfa(n)
|
||||
result = isLastReadImpl(n, c, s)
|
||||
|
||||
proc isFirstWrite(n: PNode; c: var Con): bool =
|
||||
let m = skipConvDfa(n)
|
||||
result = nfFirstWrite in m.flags
|
||||
|
||||
proc isCursor(n: PNode): bool =
|
||||
case n.kind
|
||||
of nkSym:
|
||||
sfCursor in n.sym.flags
|
||||
of nkDotExpr:
|
||||
isCursor(n[1])
|
||||
of nkCheckedFieldExpr:
|
||||
isCursor(n[0])
|
||||
else:
|
||||
false
|
||||
|
||||
template isFullyUnpackedTuple(n: PNode): bool =
|
||||
## we move out all elements of unpacked tuples,
|
||||
## hence unpacked tuples themselves don't need to be destroyed
|
||||
@@ -233,18 +243,6 @@ proc genOp(c: var Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode
|
||||
let canon = c.graph.canonTypes.getOrDefault(h)
|
||||
if canon != nil:
|
||||
op = getAttachedOp(c.graph, canon, kind)
|
||||
if op == nil or op.ast.isGenericRoutine:
|
||||
# IC: injectDestructorCalls is demand-driven and runs HERE (cg), not in the
|
||||
# `lower` stage, so a structural, env-agnostic op the lower stage never had
|
||||
# reason to serialize — most often a closure PROC type's `=destroy`/`=sink`
|
||||
# (which act on the `(ClP_0, ClE_0)` tuple, NOT the concrete env) — must be
|
||||
# lifted on demand, exactly as the lazy path's cg does. This is safe now:
|
||||
# closure-env identity resolves via `attachedOps[itemId]`/env-erased typeKey,
|
||||
# env objects load complete, and atomicRefOp's type-erased path covers any
|
||||
# still-incomplete env (so the lift never walks a nil field).
|
||||
excl t.flagsImpl, tfCheckedForDestructor
|
||||
createTypeBoundOps(c.graph, nil, t, dest.info, c.idgen)
|
||||
op = getAttachedOp(c.graph, t, kind)
|
||||
if op == nil:
|
||||
#echo dest.typ.id
|
||||
globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] &
|
||||
@@ -304,7 +302,7 @@ proc genSink(c: var Con; s: var Scope; dest, ri: PNode; flags: set[MoveOrCopyFla
|
||||
if deepAliases(dest, ri):
|
||||
# consider: x = x + y, it is wrong to destroy the destination first!
|
||||
# tmp to support self assignments
|
||||
let tmp = c.getTemp(s, dest.typ, dest.info, needsInit = false)
|
||||
let tmp = c.getTemp(s, dest.typ, dest.info)
|
||||
result = newTree(nkStmtList, newTree(nkFastAsgn, tmp, dest), newTree(nkFastAsgn, dest, ri),
|
||||
c.genDestroy(tmp))
|
||||
else:
|
||||
@@ -373,7 +371,7 @@ proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode =
|
||||
# but fields within active case branch might need destruction
|
||||
|
||||
# tmp to support self assignments
|
||||
let tmp = c.getTemp(s, n[1].typ, n.info, needsInit = false)
|
||||
let tmp = c.getTemp(s, n[1].typ, n.info)
|
||||
|
||||
result = newTree(nkStmtList)
|
||||
result.add newTree(nkFastAsgn, tmp, p(n[1], c, s, consumed))
|
||||
@@ -423,20 +421,6 @@ proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
|
||||
result.add(newSymNode(createMagic(c.graph, c.idgen, "default", mDefault)))
|
||||
result.typ = t
|
||||
|
||||
proc stabilizeBracketIndex(n: PNode; c: var Con; body: var PNode): PNode =
|
||||
## Evaluate a side-effecting index once and return the stable access.
|
||||
doAssert n.kind == nkBracketExpr and not isAtom(n[1])
|
||||
let temp = newSym(skLet, getIdent(c.graph.cache, "bracketTmp"), c.idgen,
|
||||
c.owner, n[1].info)
|
||||
temp.typ = n[1].typ
|
||||
let tempAsNode = newSymNode(temp)
|
||||
body.add newTree(nkLetSection, n[1].info,
|
||||
newTree(nkIdentDefs, tempAsNode,
|
||||
newNodeI(nkEmpty, tempAsNode.info), n[1]))
|
||||
result = copyNode(n)
|
||||
result.add n[0]
|
||||
result.add tempAsNode
|
||||
|
||||
proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
|
||||
# generate: (let tmp = v; reset(v); tmp)
|
||||
if (not hasDestructor(c, n.typ)) and c.inEnsureMove == 0:
|
||||
@@ -448,10 +432,6 @@ proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
|
||||
else:
|
||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||
|
||||
var n = n
|
||||
if n.kind == nkBracketExpr and not isAtom(n[1]):
|
||||
n = stabilizeBracketIndex(n, c, result)
|
||||
|
||||
var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.idgen, c.owner, n.info)
|
||||
temp.typ = n.typ
|
||||
var v = newNodeI(nkLetSection, n.info)
|
||||
@@ -478,52 +458,51 @@ proc isCapturedVar(n: PNode): bool =
|
||||
|
||||
proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
|
||||
let nTyp = n.typ.skipTypes(tyUserTypeClasses)
|
||||
if not hasDestructorOrAsgn(c, nTyp):
|
||||
# Non-managed (plain-old-data) type: no ownership transfer is needed.
|
||||
# Return the expression directly — no temp required.
|
||||
if hasDestructorOrAsgn(c, nTyp):
|
||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||
let tmp = c.getTemp(s, nTyp, n.info)
|
||||
let typ = nTyp.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||
let op = getAttachedOp(c.graph, typ, attachedDup)
|
||||
if op != nil and tfHasOwned notin typ.flags:
|
||||
if sfError in op.flags:
|
||||
c.checkForErrorPragma(nTyp, n, "=dup")
|
||||
else:
|
||||
let copyOp = getAttachedOp(c.graph, typ, attachedAsgn)
|
||||
if copyOp != nil and sfError in copyOp.flags and
|
||||
sfOverridden notin op.flags:
|
||||
c.checkForErrorPragma(nTyp, n, "=dup", inferredFromCopy = true)
|
||||
|
||||
let src = p(n, c, s, normal)
|
||||
var newCall = newTreeIT(nkCall, src.info, src.typ,
|
||||
newSymNode(op),
|
||||
src)
|
||||
c.finishCopy(newCall, n, {}, isFromSink = true)
|
||||
result.add newTreeI(nkFastAsgn,
|
||||
src.info, tmp,
|
||||
newCall
|
||||
)
|
||||
else:
|
||||
result.add c.genWasMoved(tmp)
|
||||
var m = c.genCopy(tmp, n, {})
|
||||
m.add p(n, c, s, normal)
|
||||
c.finishCopy(m, n, {}, isFromSink = true)
|
||||
result.add m
|
||||
if isLValue(n) and not isCapturedVar(n) and nTyp.skipTypes(abstractInst).kind != tyRef and c.inSpawn == 0:
|
||||
message(c.graph.config, n.info, hintPerformance,
|
||||
("passing '$1' to a sink parameter introduces an implicit copy; " &
|
||||
"if possible, rearrange your program's control flow to prevent it") % $n)
|
||||
if c.inEnsureMove > 0:
|
||||
localError(c.graph.config, n.info, errFailedMove,
|
||||
("cannot move '$1', passing '$1' to a sink parameter introduces an implicit copy") % $n)
|
||||
# Since we know somebody will take over the produced copy, there is
|
||||
# no need to destroy it.
|
||||
result.add tmp
|
||||
else:
|
||||
if c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
assert(not containsManagedMemory(nTyp))
|
||||
if nTyp.skipTypes(abstractInst).kind in {tyOpenArray, tyVarargs}:
|
||||
localError(c.graph.config, n.info, "cannot create an implicit openArray copy to be passed to a sink parameter")
|
||||
return p(n, c, s, normal)
|
||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||
let tmp = c.getTemp(s, nTyp, n.info, needsInit = false)
|
||||
let typ = nTyp.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||
let op = getAttachedOp(c.graph, typ, attachedDup)
|
||||
if op != nil and tfHasOwned notin typ.flags:
|
||||
if sfError in op.flags:
|
||||
c.checkForErrorPragma(nTyp, n, "=dup")
|
||||
else:
|
||||
let copyOp = getAttachedOp(c.graph, typ, attachedAsgn)
|
||||
if copyOp != nil and sfError in copyOp.flags and
|
||||
sfOverridden notin op.flags:
|
||||
c.checkForErrorPragma(nTyp, n, "=dup", inferredFromCopy = true)
|
||||
|
||||
let src = p(n, c, s, normal)
|
||||
var newCall = newTreeIT(nkCall, src.info, src.typ,
|
||||
newSymNode(op),
|
||||
src)
|
||||
c.finishCopy(newCall, n, {}, isFromSink = true)
|
||||
result.add newTreeI(nkFastAsgn,
|
||||
src.info, tmp,
|
||||
newCall
|
||||
)
|
||||
else:
|
||||
result.add c.genWasMoved(tmp)
|
||||
var m = c.genCopy(tmp, n, {})
|
||||
m.add p(n, c, s, normal)
|
||||
c.finishCopy(m, n, {}, isFromSink = true)
|
||||
result.add m
|
||||
if isLValue(n) and not isCapturedVar(n) and nTyp.skipTypes(abstractInst).kind != tyRef and c.inSpawn == 0:
|
||||
message(c.graph.config, n.info, hintPerformance,
|
||||
("passing '$1' to a sink parameter introduces an implicit copy; " &
|
||||
"if possible, rearrange your program's control flow to prevent it") % $n)
|
||||
if c.inEnsureMove > 0:
|
||||
localError(c.graph.config, n.info, errFailedMove,
|
||||
("cannot move '$1', passing '$1' to a sink parameter introduces an implicit copy") % $n)
|
||||
# Since we know somebody will take over the produced copy, there is
|
||||
# no need to destroy it.
|
||||
result.add tmp
|
||||
result = p(n, c, s, normal)
|
||||
|
||||
proc isDangerousSeq(t: PType): bool {.inline.} =
|
||||
let t = t.skipTypes(abstractInst)
|
||||
@@ -551,7 +530,7 @@ proc ensureDestruction(arg, orig: PNode; c: var Con; s: var Scope): PNode =
|
||||
# produce temp creation for (fn, env). But we need to move 'env'?
|
||||
# This was already done in the sink parameter handling logic.
|
||||
result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
|
||||
let tmp = c.getTemp(s, arg.typ, arg.info, true)
|
||||
let tmp = c.getTemp(s, arg.typ, arg.info)
|
||||
result.add c.genSink(s, tmp, arg, {IsDecl})
|
||||
result.add tmp
|
||||
s.final.add c.genDestroy(tmp)
|
||||
@@ -630,7 +609,7 @@ template processScopeExpr(c: var Con; s: var Scope; ret: PNode, processCall: unt
|
||||
# There is a possibility to do this check: s.wasMoved.len > 0 or s.final.len > 0
|
||||
# later and use it to eliminate the temporary when theres no need for it, but its
|
||||
# tricky because you would have to intercept moveOrCopy at a certain point
|
||||
let tmp = c.getTemp(s.parent[], ret.typ, ret.info, needsInit = true)
|
||||
let tmp = c.getTemp(s.parent[], ret.typ, ret.info)
|
||||
tmp.sym.flags = tmpFlags
|
||||
let cpy = if hasDestructor(c, ret.typ) and
|
||||
ret.typ.kind notin {tyOpenArray, tyVarargs}:
|
||||
@@ -791,7 +770,7 @@ proc pRaiseStmt(n: PNode, c: var Con; s: var Scope): PNode =
|
||||
result = copyNode(n)
|
||||
result.add call
|
||||
else:
|
||||
let tmp = c.getTemp(s, n[0].typ, n.info, needsInit = true)
|
||||
let tmp = c.getTemp(s, n[0].typ, n.info)
|
||||
var m = c.genCopyNoCheck(tmp, n[0], attachedAsgn)
|
||||
m.add p(n[0], c, s, normal)
|
||||
c.finishCopy(m, n[0], {}, isFromSink = false)
|
||||
@@ -821,23 +800,6 @@ proc hasCustomDestructor(c: Con, t: PType): bool =
|
||||
obj = skipTypes(obj.baseClass, abstractPtrs)
|
||||
result = result or isCustomDestructor(c, obj)
|
||||
|
||||
const
|
||||
exprBranchKinds = {nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt,
|
||||
nkTryStmt, nkPragmaBlock}
|
||||
|
||||
proc distributeAsgn(asgnKind: TNodeKind; dest, ri: PNode; c: var Con; s: var Scope): PNode =
|
||||
## Distributes an assignment ``dest = ri`` into the leaf expressions of
|
||||
## ``ri`` when ``ri`` is an expression-based control flow construct. This
|
||||
## avoids creating pointless intermediate temporaries (bug #25850). The
|
||||
## descent is recursive so that nestings like ``block: ...; if c: a else: b``
|
||||
## assign directly to ``dest`` instead of going through a temp per branch.
|
||||
if ri.kind in exprBranchKinds:
|
||||
template process(child, s): untyped =
|
||||
distributeAsgn(asgnKind, dest, child, c, s)
|
||||
handleNestedTempl(ri, process, willProduceStmt = true)
|
||||
else:
|
||||
result = newTree(asgnKind, dest, p(ri, c, s, consumed))
|
||||
|
||||
proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSingleUsedTemp}; inReturn = false): PNode =
|
||||
if n.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt,
|
||||
nkIfExpr, nkCaseStmt, nkWhen, nkWhileStmt, nkParForStmt, nkTryStmt, nkPragmaBlock}:
|
||||
@@ -1039,11 +1001,6 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
|
||||
result = moveOrCopy(p(n[0], c, s, mode), n[1], c, s, flags)
|
||||
elif isDiscriminantField(n[0]):
|
||||
result = c.genDiscriminantAsgn(s, n)
|
||||
elif n[1].kind in exprBranchKinds:
|
||||
# Distribute the assignment into each branch to avoid
|
||||
# creating pointless temporaries for expression-based control flow.
|
||||
let dest = p(n[0], c, s, mode)
|
||||
result = distributeAsgn(n.kind, dest, n[1], c, s)
|
||||
else:
|
||||
result = copyNode(n)
|
||||
result.add p(n[0], c, s, mode)
|
||||
@@ -1137,11 +1094,6 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
|
||||
result[i] = n[i]
|
||||
of nkGotoState, nkState, nkAsmStmt:
|
||||
result = n
|
||||
of nkReplayAction:
|
||||
# A `.rod`/NIF replay record. It only ever appears in a NIF-loaded
|
||||
# module's TOP-LEVEL statements (the loader prepends the `(replay ...)`
|
||||
# entries there); cgen discards it, so pass it through untouched.
|
||||
result = n
|
||||
else:
|
||||
result = nil
|
||||
internalError(c.graph.config, n.info, "cannot inject destructors to node kind: " & $n.kind)
|
||||
@@ -1173,11 +1125,24 @@ proc sameLocation*(a, b: PNode): bool =
|
||||
else: false
|
||||
|
||||
proc genFieldAccessSideEffects(c: var Con; s: var Scope; dest, ri: PNode; flags: set[MoveOrCopyFlag] = {}): PNode =
|
||||
result = newNodeI(nkStmtList, ri.info)
|
||||
let newAccess = stabilizeBracketIndex(ri, c, result)
|
||||
let snk = c.genSink(s, dest, newAccess, flags)
|
||||
result.add snk
|
||||
result.add c.genWasMoved(newAccess)
|
||||
# with side effects
|
||||
var temp = newSym(skLet, getIdent(c.graph.cache, "bracketTmp"), c.idgen, c.owner, ri[1].info)
|
||||
temp.typ = ri[1].typ
|
||||
var v = newNodeI(nkLetSection, ri[1].info)
|
||||
let tempAsNode = newSymNode(temp)
|
||||
|
||||
var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
|
||||
vpart[0] = tempAsNode
|
||||
vpart[1] = newNodeI(nkEmpty, tempAsNode.info)
|
||||
vpart[2] = ri[1]
|
||||
v.add(vpart)
|
||||
|
||||
var newAccess = copyNode(ri)
|
||||
newAccess.add ri[0]
|
||||
newAccess.add tempAsNode
|
||||
|
||||
var snk = c.genSink(s, dest, newAccess, flags)
|
||||
result = newTree(nkStmtList, v, snk, c.genWasMoved(newAccess))
|
||||
|
||||
proc ownsData(c: var Con; s: var Scope; orig: PNode; flags: set[MoveOrCopyFlag]): PNode =
|
||||
var n = orig
|
||||
@@ -1189,7 +1154,7 @@ proc ownsData(c: var Con; s: var Scope; orig: PNode; flags: set[MoveOrCopyFlag])
|
||||
break
|
||||
if n.kind in nkCallKinds and n.typ != nil and hasDestructor(c, n.typ):
|
||||
result = newNodeIT(nkStmtListExpr, orig.info, orig.typ)
|
||||
let tmp = c.getTemp(s, n.typ, n.info, needsInit = true)
|
||||
let tmp = c.getTemp(s, n.typ, n.info)
|
||||
tmp.sym.flagsImpl.incl sfSingleUsedTemp
|
||||
result.add newTree(nkFastAsgn, tmp, copyTree(n))
|
||||
s.final.add c.genDestroy(tmp)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -34,7 +34,7 @@ import
|
||||
ropes, wordrecg, renderer,
|
||||
cgmeth, lowerings, sighashes, modulegraphs, lineinfos,
|
||||
transf, injectdestructors, sourcemap, astmsgs, pushpoppragmas,
|
||||
mangleutils, varpartitions
|
||||
mangleutils
|
||||
|
||||
import pipelineutils
|
||||
|
||||
@@ -148,6 +148,11 @@ proc newGlobals(): PGlobals =
|
||||
typeInfoGenerated: initIntSet()
|
||||
)
|
||||
|
||||
proc initCompRes(): TCompRes =
|
||||
result = TCompRes(address: "", res: "",
|
||||
tmpLoc: "", typ: etyNone, kind: resNone
|
||||
)
|
||||
|
||||
proc rdLoc(a: TCompRes): Rope {.inline.} =
|
||||
if a.typ != etyBaseIndex:
|
||||
result = a.res
|
||||
@@ -589,6 +594,15 @@ proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string,
|
||||
r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer]
|
||||
r.kind = resExpr
|
||||
|
||||
template ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
|
||||
var x, y, z: TCompRes
|
||||
useMagic(p, magic)
|
||||
gen(p, n[1], x)
|
||||
gen(p, n[2], y)
|
||||
gen(p, n[3], z)
|
||||
r.res = frmt % [x.rdLoc, y.rdLoc, z.rdLoc]
|
||||
r.kind = resExpr
|
||||
|
||||
template unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
|
||||
# $1 binds to n[1], if $2 is present it will be substituted to a tmp of $1
|
||||
useMagic(p, magic)
|
||||
@@ -1168,6 +1182,7 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode; isAsmStmt = false) =
|
||||
of nkStrLit..nkTripleStrLit:
|
||||
p.body.add(it.strVal)
|
||||
of nkSym:
|
||||
let v = it.sym
|
||||
# for backwards compatibility we don't deref syms here :-(
|
||||
if false:
|
||||
discard
|
||||
@@ -1240,6 +1255,17 @@ proc generateHeader(p: PProc, prc: PSym): Rope =
|
||||
result.add(name)
|
||||
result.add("_Idx")
|
||||
|
||||
proc countJsParams(typ: PType): int =
|
||||
result = 0
|
||||
for i in 1..<typ.n.len:
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
var param = typ.n[i].sym
|
||||
if isCompileTimeOnly(param.typ): continue
|
||||
if mapType(param.typ) == etyBaseIndex:
|
||||
inc result, 2
|
||||
else:
|
||||
inc result
|
||||
|
||||
const
|
||||
nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit,
|
||||
nkFloatLit..nkFloat64Lit, nkPar, nkStringToCString,
|
||||
@@ -1272,16 +1298,14 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
|
||||
xtyp = etySeq
|
||||
case xtyp
|
||||
of etySeq:
|
||||
if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or
|
||||
(x.kind == nkSym and sfCursor in x.sym.flags):
|
||||
if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
|
||||
lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
|
||||
else:
|
||||
useMagic(p, "nimCopy")
|
||||
lineF(p, "$1 = nimCopy(null, $2, $3);$n",
|
||||
[a.rdLoc, b.res, genTypeInfo(p, y.typ)])
|
||||
of etyObject:
|
||||
if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or
|
||||
(x.kind == nkSym and sfCursor in x.sym.flags):
|
||||
if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
|
||||
lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
|
||||
else:
|
||||
useMagic(p, "nimCopy")
|
||||
@@ -1450,20 +1474,6 @@ proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) =
|
||||
r.res = "$1.$2" % [tmp, field.loc.snippet]
|
||||
r.kind = resExpr
|
||||
|
||||
proc isVarOpenArrayParam(n: PNode): bool =
|
||||
## True if `n` resolves to a `var openArray` parameter. The JS backend
|
||||
## represents such parameters as a `{base, off, len}` slice view so that
|
||||
## writes through a `toOpenArray` view reach the caller's storage (bug #15952).
|
||||
var it = n
|
||||
while true:
|
||||
case it.kind
|
||||
of nkHiddenDeref, nkDerefExpr, nkHiddenAddr, nkAddr: it = it[0]
|
||||
of nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv: it = it[1]
|
||||
else: break
|
||||
result = it.kind == nkSym and it.sym.kind == skParam and
|
||||
it.sym.typ != nil and it.sym.typ.kind == tyVar and
|
||||
it.sym.typ.len > 0 and it.sym.typ[0].kind == tyOpenArray
|
||||
|
||||
proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
|
||||
var
|
||||
a, b: TCompRes = default(TCompRes)
|
||||
@@ -1472,19 +1482,6 @@ proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
|
||||
let m = if n.kind == nkHiddenAddr: n[0] else: n
|
||||
gen(p, m[0], a)
|
||||
gen(p, m[1], b)
|
||||
if isVarOpenArrayParam(m[0]):
|
||||
# `var openArray` param is a `{base, off, len}` view; index the base with
|
||||
# the offset applied. `m[0]` is a plain param name, safe to reference
|
||||
# repeatedly (no side effects, so no temp needed).
|
||||
let pn = a.rdLoc
|
||||
r.address = "($1).base" % [pn]
|
||||
if optBoundsCheck in p.options:
|
||||
useMagic(p, "chckIndx")
|
||||
r.res = "($1).off + chckIndx($2, 0, ($1).len - 1)" % [pn, b.rdLoc]
|
||||
else:
|
||||
r.res = "($1).off + ($2)" % [pn, b.rdLoc]
|
||||
r.kind = resExpr
|
||||
return
|
||||
#internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex
|
||||
let (x, tmp) = maybeMakeTemp(p, m[0], a)
|
||||
r.address = x
|
||||
@@ -1547,7 +1544,7 @@ proc genSymAddr(p: PProc, n: PNode, typ: PType, r: var TCompRes) =
|
||||
r.res = s.loc.snippet
|
||||
r.address = ""
|
||||
r.typ = etyNone
|
||||
of skVar, skLet, skResult, skTemp, skForVar:
|
||||
of skVar, skLet, skResult:
|
||||
r.kind = resExpr
|
||||
let jsType = mapType(p):
|
||||
if typ.isNil:
|
||||
@@ -1753,49 +1750,8 @@ proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) =
|
||||
else:
|
||||
r.res.add(a.res)
|
||||
|
||||
proc genVarOpenArrayArg(p: PProc, n: PNode, r: var TCompRes) =
|
||||
## Emit a `{base, off, len}` slice view for an argument to a `var openArray`
|
||||
## parameter (bug #15952). The view always aliases the base storage, so writes
|
||||
## through the callee's `openArray` reach the caller's array/seq/typed array.
|
||||
var b, lo, hi, v: TCompRes = default(TCompRes)
|
||||
# the argument reaches codegen as `addr(toOpenArray(x, lo, hi))` (possibly
|
||||
# under conversions); unwrap to the actual `toOpenArray` call.
|
||||
var sl = n
|
||||
while true:
|
||||
case sl.kind
|
||||
of nkHiddenAddr, nkAddr, nkHiddenDeref, nkDerefExpr: sl = sl[0]
|
||||
of nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv: sl = sl[1]
|
||||
else: break
|
||||
if sl.kind in nkCallKinds and getMagic(sl) == mSlice:
|
||||
gen(p, sl[1], b)
|
||||
gen(p, sl[2], lo)
|
||||
gen(p, sl[3], hi)
|
||||
if isVarOpenArrayParam(sl[1]):
|
||||
# slicing a `var openArray` view: rebase onto the same underlying storage
|
||||
r.res = "{base: ($1).base, off: ($1).off + $2, len: $3 - $2 + 1}" % [
|
||||
b.rdLoc, lo.rdLoc, hi.rdLoc]
|
||||
else:
|
||||
r.res = "{base: $1, off: $2, len: $3 - $2 + 1}" % [
|
||||
b.rdLoc, lo.rdLoc, hi.rdLoc]
|
||||
elif isVarOpenArrayParam(sl):
|
||||
# already a view from another `var openArray` param: forward it unchanged
|
||||
gen(p, sl, b)
|
||||
r.res = b.rdLoc
|
||||
else:
|
||||
# a whole array/seq/typed-array value: wrap with a zero offset
|
||||
gen(p, n, v)
|
||||
r.res = "{base: $1, off: 0, len: ($1).length}" % [v.rdLoc]
|
||||
r.kind = resExpr
|
||||
|
||||
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes;
|
||||
emitted: ptr int = nil; skipVarOpenArray = false) =
|
||||
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = nil) =
|
||||
var a: TCompRes = default(TCompRes)
|
||||
if (not skipVarOpenArray) and param.typ != nil and param.typ.kind == tyVar and
|
||||
param.typ[0].kind == tyOpenArray:
|
||||
# `var openArray` params are passed as a `{base, off, len}` slice view.
|
||||
genVarOpenArrayArg(p, n, a)
|
||||
r.res.add(a.rdLoc)
|
||||
return
|
||||
gen(p, n, a)
|
||||
if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and
|
||||
a.typ == etyBaseIndex:
|
||||
@@ -1805,13 +1761,6 @@ proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes;
|
||||
r.res.add(", ")
|
||||
r.res.add(a.res)
|
||||
if emitted != nil: inc emitted[]
|
||||
elif skipTypes(param.typ, abstractVar).kind == tyOpenArray and
|
||||
isVarOpenArrayParam(n):
|
||||
# a `var openArray` view passed to a read-only `openArray` param: materialize
|
||||
# a snapshot so the callee sees a plain array.
|
||||
var w: TCompRes = default(TCompRes)
|
||||
gen(p, n, w)
|
||||
r.res.add("(($1).base).slice(($1).off, ($1).off + ($1).len)" % [w.rdLoc])
|
||||
elif n.typ.kind in {tyVar, tyPtr, tyRef, tyLent, tyOwned} and
|
||||
n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex:
|
||||
# this fixes bug #5608:
|
||||
@@ -1846,11 +1795,16 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) =
|
||||
inc emitted
|
||||
hasArgs = true
|
||||
r.res.add(")")
|
||||
when false:
|
||||
# XXX look into this:
|
||||
let jsp = countJsParams(typ)
|
||||
if emitted != jsp and tfVarargs notin typ.flags:
|
||||
localError(p.config, n.info, "wrong number of parameters emitted; expected: " & $jsp &
|
||||
" but got: " & $emitted)
|
||||
r.kind = resExpr
|
||||
|
||||
proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
|
||||
generated: var int; r: var TCompRes;
|
||||
skipVarOpenArray = false) =
|
||||
generated: var int; r: var TCompRes) =
|
||||
if i >= n.len:
|
||||
globalError(p.config, n.info, "wrong importcpp pattern; expected parameter at position " & $i &
|
||||
" but got only: " & $(n.len-1))
|
||||
@@ -1863,12 +1817,11 @@ proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
|
||||
if paramType.isNil:
|
||||
genArgNoParam(p, it, r)
|
||||
else:
|
||||
genArg(p, it, paramType.sym, r, skipVarOpenArray = skipVarOpenArray)
|
||||
genArg(p, it, paramType.sym, r)
|
||||
inc generated
|
||||
|
||||
proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
|
||||
r: var TCompRes) =
|
||||
let skipVarOpenArray = sfImportc in n[0].sym.flags
|
||||
var i = 0
|
||||
var j = 1
|
||||
r.kind = resExpr
|
||||
@@ -1878,11 +1831,11 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
|
||||
var generated = 0
|
||||
for k in j..<n.len:
|
||||
if generated > 0: r.res.add(", ")
|
||||
genOtherArg(p, n, k, typ, generated, r, skipVarOpenArray)
|
||||
genOtherArg(p, n, k, typ, generated, r)
|
||||
inc i
|
||||
of '#':
|
||||
var generated = 0
|
||||
genOtherArg(p, n, j, typ, generated, r, skipVarOpenArray)
|
||||
genOtherArg(p, n, j, typ, generated, r)
|
||||
inc j
|
||||
inc i
|
||||
of '\31':
|
||||
@@ -2065,12 +2018,8 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope =
|
||||
if indirect: result = "[$1]" % [result]
|
||||
of tyTuple:
|
||||
result = rope("{")
|
||||
var first = true
|
||||
for i in 0..<t.len:
|
||||
# Do not produce code for void types
|
||||
if isEmptyType(t[i]): continue
|
||||
if not first: result.add(", ")
|
||||
first = false
|
||||
if i > 0: result.add(", ")
|
||||
result.addf("Field$1: $2", [i.rope,
|
||||
createVar(p, t[i], false)])
|
||||
result.add("}")
|
||||
@@ -2139,8 +2088,7 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
|
||||
gen(p, n, a)
|
||||
case mapType(p, v.typ)
|
||||
of etyObject, etySeq:
|
||||
if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n) or
|
||||
sfCursor in v.flags:
|
||||
if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n):
|
||||
s = a.res
|
||||
else:
|
||||
useMagic(p, "nimCopy")
|
||||
@@ -2380,6 +2328,9 @@ proc genJSArrayConstr(p: PProc, n: PNode, r: var TCompRes) =
|
||||
r.res.add("]")
|
||||
|
||||
proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
|
||||
var
|
||||
a: TCompRes
|
||||
line, filen: Rope
|
||||
var op = n[0].sym.magic
|
||||
case op
|
||||
of mOr: genOr(p, n[1], n[2], r)
|
||||
@@ -2448,21 +2399,13 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
|
||||
useMagic(p, "nimCopy")
|
||||
r.res = "nimCopy(null, $1, $2)" % [x.rdLoc, genTypeInfo(p, n.typ)]
|
||||
of mOpenArrayToSeq:
|
||||
if isVarOpenArrayParam(n[1]):
|
||||
var x: TCompRes = default(TCompRes)
|
||||
gen(p, n[1], x)
|
||||
r.res = "(($1).base).slice(($1).off, ($1).off + ($1).len)" % [x.rdLoc]
|
||||
r.kind = resExpr
|
||||
else:
|
||||
genCall(p, n, r)
|
||||
genCall(p, n, r)
|
||||
of mDestroy, mTrace: discard "ignore calls to the default destructor"
|
||||
of mOrd: genOrd(p, n, r)
|
||||
of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray:
|
||||
var x: TCompRes = default(TCompRes)
|
||||
gen(p, n[1], x)
|
||||
if isVarOpenArrayParam(n[1]):
|
||||
r.res = "($1).len" % [x.rdLoc]
|
||||
elif skipTypes(n[1].typ, abstractInst).kind == tyCstring:
|
||||
if skipTypes(n[1].typ, abstractInst).kind == tyCstring:
|
||||
let (a, tmp) = maybeMakeTemp(p, n[1], x)
|
||||
r.res = "(($1) == null ? 0 : ($2).length)" % [a, tmp]
|
||||
else:
|
||||
@@ -2471,9 +2414,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
|
||||
of mHigh:
|
||||
var x: TCompRes = default(TCompRes)
|
||||
gen(p, n[1], x)
|
||||
if isVarOpenArrayParam(n[1]):
|
||||
r.res = "($1).len - 1" % [x.rdLoc]
|
||||
elif skipTypes(n[1].typ, abstractInst).kind == tyCstring:
|
||||
if skipTypes(n[1].typ, abstractInst).kind == tyCstring:
|
||||
let (a, tmp) = maybeMakeTemp(p, n[1], x)
|
||||
r.res = "(($1) == null ? -1 : ($2).length - 1)" % [a, tmp]
|
||||
else:
|
||||
@@ -2556,24 +2497,11 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
|
||||
genCall(p, n, r)
|
||||
of mSlice:
|
||||
# arr.slice([begin[, end]]): 'end' is exclusive
|
||||
# Fixed homogeneous numeric arrays lower to JS typed arrays; `slice`
|
||||
# copies, which silently breaks `var openArray` write-through (bug #15952).
|
||||
# `subarray` returns a live shared-buffer view with the same
|
||||
# exclusive-end signature, so use it there; keep `slice` for seqs/strings.
|
||||
var x, y, z: TCompRes = default(TCompRes)
|
||||
gen(p, n[1], x)
|
||||
gen(p, n[2], y)
|
||||
gen(p, n[3], z)
|
||||
if isVarOpenArrayParam(n[1]):
|
||||
# re-slicing a `var openArray` view: materialize from the view's base/offset
|
||||
r.res = "(($1).base).slice(($1).off + $2, ($1).off + $3 + 1)" % [
|
||||
x.rdLoc, y.rdLoc, z.rdLoc]
|
||||
else:
|
||||
let baseTy = skipTypes(n[1].typ, abstractVarRange + {tyLent})
|
||||
if baseTy.kind == tyArray and arrayTypeForElemType(p.config, elemType(baseTy)).len > 0:
|
||||
r.res = "($1.subarray($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
|
||||
else:
|
||||
r.res = "($1.slice($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
|
||||
r.res = "($1.slice($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
|
||||
r.kind = resExpr
|
||||
of mMove:
|
||||
genMove(p, n, r)
|
||||
@@ -2659,6 +2587,7 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
|
||||
r.kind = resExpr
|
||||
var initList : Rope = ""
|
||||
var fieldIDs = initIntSet()
|
||||
let nTyp = n.typ.skipTypes(abstractInst)
|
||||
for i in 1..<n.len:
|
||||
if i > 1: initList.add(", ")
|
||||
var it = n[i]
|
||||
@@ -2865,11 +2794,6 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
|
||||
var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, {})
|
||||
if sfInjectDestructors in prc.flags:
|
||||
transformedBody = injectDestructorCalls(p.module.graph, p.module.idgen, prc, transformedBody)
|
||||
else:
|
||||
# JS has a GC, so the destructor pass is off; but the cursor (alias) analysis
|
||||
# is independent of ownership and always memory-safe on a traced target.
|
||||
# Running it lets last-use `var b = a` aliases skip the deep `nimCopy`.
|
||||
computeCursors(prc, transformedBody, p.module.graph)
|
||||
|
||||
p.nested: genStmt(p, transformedBody)
|
||||
|
||||
|
||||
@@ -126,6 +126,11 @@ const
|
||||
paramName* = ":envP"
|
||||
envName* = ":env"
|
||||
|
||||
proc newCall(a: PSym, b: PNode): PNode =
|
||||
result = newNodeI(nkCall, a.info)
|
||||
result.add newSymNode(a)
|
||||
result.add b
|
||||
|
||||
proc createClosureIterStateType*(g: ModuleGraph; iter: PSym; idgen: IdGenerator): PType =
|
||||
var n = newNodeI(nkRange, iter.info)
|
||||
n.add newIntNode(nkIntLit, -1)
|
||||
@@ -159,21 +164,9 @@ proc getClosureIterResult*(g: ModuleGraph; iter: PSym; idgen: IdGenerator): PSym
|
||||
incl(result.flagsImpl, sfUsed)
|
||||
iter.ast.add newSymNode(result)
|
||||
|
||||
proc closureParams(routine: PSym): PNode =
|
||||
## The formal parameters node lambda lifting reads and extends. In a
|
||||
## from-source compilation `routine.ast[paramsPos]` and `routine.typ.n` are the
|
||||
## very same node (see the `typ.n.len` based position math below). Under IC the
|
||||
## loaded proc AST omits the parameters (they are kept only in `typ.n`), so
|
||||
## restore the shared node here.
|
||||
result = routine.ast[paramsPos]
|
||||
if (result == nil or result.kind == nkEmpty) and routine.typ != nil and
|
||||
routine.typ.n != nil and routine.ast.len > paramsPos:
|
||||
result = routine.typ.n
|
||||
routine.ast[paramsPos] = result
|
||||
|
||||
proc addHiddenParam*(routine: PSym, param: PSym) =
|
||||
proc addHiddenParam(routine: PSym, param: PSym) =
|
||||
assert param.kind == skParam
|
||||
var params = closureParams(routine)
|
||||
var params = routine.ast[paramsPos]
|
||||
# -1 is correct here as param.position is 0 based but we have at position 0
|
||||
# some nkEffect node:
|
||||
param.position = routine.typ.n.len-1
|
||||
@@ -184,8 +177,7 @@ proc addHiddenParam*(routine: PSym, param: PSym) =
|
||||
|
||||
proc getEnvParam*(routine: PSym): PSym =
|
||||
if routine.ast.isNil: return nil
|
||||
let params = closureParams(routine)
|
||||
if params == nil or params.len == 0: return nil
|
||||
let params = routine.ast[paramsPos]
|
||||
let hidden = lastSon(params)
|
||||
if hidden.kind == nkSym and hidden.sym.kind == skParam and hidden.sym.name.s == paramName:
|
||||
result = hidden.sym
|
||||
@@ -224,10 +216,6 @@ proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode =
|
||||
result[0] = le
|
||||
result[1] = ri
|
||||
|
||||
proc markInjectDestructors(s: PSym) {.inline.} =
|
||||
backendEnsureMutable s
|
||||
s.flagsImpl.incl sfInjectDestructors
|
||||
|
||||
proc makeClosure*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; env: PNode; info: TLineInfo): PNode =
|
||||
result = newNodeIT(nkClosure, info, prc.typ)
|
||||
result.add(newSymNode(prc))
|
||||
@@ -240,7 +228,7 @@ proc makeClosure*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; env: PNode; inf
|
||||
#if isClosureIterator(result.typ):
|
||||
createTypeBoundOps(g, nil, result.typ, info, idgen)
|
||||
if tfHasAsgn in result.typ.flags or optSeqDestructors in g.config.globalOptions:
|
||||
markInjectDestructors(prc)
|
||||
prc.incl sfInjectDestructors
|
||||
|
||||
template liftingHarmful(conf: ConfigRef; owner: PSym): bool =
|
||||
## lambda lifting can be harmful for JS-like code generators.
|
||||
@@ -252,7 +240,7 @@ proc createTypeBoundOpsLL(g: ModuleGraph; refType: PType; info: TLineInfo; idgen
|
||||
createTypeBoundOps(g, nil, refType.elementType, info, idgen)
|
||||
createTypeBoundOps(g, nil, refType, info, idgen)
|
||||
if tfHasAsgn in refType.flags or optSeqDestructors in g.config.globalOptions:
|
||||
markInjectDestructors(owner)
|
||||
owner.incl sfInjectDestructors
|
||||
|
||||
proc genCreateEnv(env: PNode): PNode =
|
||||
var c = newNodeIT(nkObjConstr, env.info, env.typ)
|
||||
@@ -283,6 +271,7 @@ proc liftIterSym*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PN
|
||||
addVar(v, env)
|
||||
result.add(v)
|
||||
# add 'new' statement:
|
||||
#result.add newCall(getSysSym(g, n.info, "internalNew"), env)
|
||||
result.add genCreateEnv(env)
|
||||
createTypeBoundOpsLL(g, env.typ, n.info, idgen, owner)
|
||||
result.add makeClosure(g, idgen, iter, env, n.info)
|
||||
@@ -301,27 +290,7 @@ proc markAsClosure(g: ModuleGraph; owner: PSym; n: PNode) =
|
||||
elif not (owner.typ.isClosure or owner.isNimcall and not owner.isExplicitCallConv or isEnv):
|
||||
localError(g.config, n.info, "illegal capture '$1' because '$2' has the calling convention: <$3>" %
|
||||
[s.name.s, owner.name.s, $owner.typ.callConv])
|
||||
unsealForTransform(owner.typ)
|
||||
incl(owner.typ, tfCapturesEnv)
|
||||
# A closure proc type that captures an env owns a REF to it: copying the closure
|
||||
# value must incref the env and destroying it must decref. That is exactly what
|
||||
# `tfHasAsgn` signals to `injectDestructorCalls` (so a closure assignment becomes
|
||||
# `=copy`, not a raw field store).
|
||||
#
|
||||
# Set it HERE (closure-type creation) so the flag is DETERMINISTIC and serializes
|
||||
# with the type — but ONLY under `nim ic`. The per-module `lower` stage is a
|
||||
# separate process that lowers routines in index order; if a consumer (e.g.
|
||||
# `workNimAsyncContinue`) was lowered before the closure type's ops were lifted,
|
||||
# its env store emitted a RAW assign with no incref → freed env → async
|
||||
# "yielded `nil`". A normal single-process `nim c` build does NOT need this —
|
||||
# `createTypeBoundOps` sets the flag lazily, in lift order, before it matters
|
||||
# (the old `liftdestructors ~1498` "XXX Breaks IC!" side effect) — and setting it
|
||||
# eagerly there REGRESSES codegen: a `=destroy` hook gets generated against the
|
||||
# bare `void(*)(void)` proc representation but is then called with closure structs
|
||||
# (`eqdestroy__u2__stdZtypedthreads` type mismatch — broke megatest). So gate on
|
||||
# `cmdNifC`; normal builds keep the lazy (devel) behavior.
|
||||
if g.config.cmd == cmdNifC:
|
||||
incl(owner.typ, tfHasAsgn)
|
||||
if not isEnv:
|
||||
owner.typ.callConv = ccClosure
|
||||
|
||||
@@ -439,12 +408,6 @@ Consider:
|
||||
proc isTypeOf(n: PNode): bool =
|
||||
n.kind == nkSym and n.sym.magic in {mTypeOf, mType}
|
||||
|
||||
proc isEnvTypeForRoutine(envTyp: PType; routine: PSym): bool =
|
||||
## True if `envTyp` is (maybe wrapped) env object type owned by `routine`, as
|
||||
## created by `getEnvTypeForOwner` / `createEnvObj`.
|
||||
let obj = envTyp.skipTypes({tyOwned, tyRef, tyPtr})
|
||||
result = obj.kind == tyObject and obj.owner.id == routine.id
|
||||
|
||||
proc addClosureParam(c: var DetectionPass; fn: PSym; info: TLineInfo) =
|
||||
var cp = getEnvParam(fn)
|
||||
let owner = if fn.kind == skIterator: fn else: fn.skipGenericOwner
|
||||
@@ -455,13 +418,7 @@ proc addClosureParam(c: var DetectionPass; fn: PSym; info: TLineInfo) =
|
||||
cp.typ = t
|
||||
addHiddenParam(fn, cp)
|
||||
elif cp.typ != t and fn.kind != skIterator:
|
||||
# Nested `liftLambdas` uses a fresh `DetectionPass`, so `getEnvTypeForOwner`
|
||||
# can allocate another PType for the same logical env; the hidden param from
|
||||
# the inner pass is authoritative (bug #21242).
|
||||
if isEnvTypeForRoutine(cp.typ, owner) and isEnvTypeForRoutine(t, owner):
|
||||
c.ownerToType[owner.id] = cp.typ
|
||||
else:
|
||||
localError(c.graph.config, fn.info, "internal error: inconsistent environment type")
|
||||
localError(c.graph.config, fn.info, "internal error: inconsistent environment type")
|
||||
#echo "adding closure to ", fn.name.s
|
||||
|
||||
proc iterEnvHasUpField(g: ModuleGraph, iter: PSym): bool =
|
||||
@@ -667,7 +624,7 @@ proc rawClosureCreation(owner: PSym;
|
||||
if owner.kind != skMacro:
|
||||
createTypeBoundOps(d.graph, nil, fieldAccess.typ, env.info, d.idgen)
|
||||
if tfHasAsgn in fieldAccess.typ.flags or optSeqDestructors in d.graph.config.globalOptions:
|
||||
markInjectDestructors(owner)
|
||||
owner.incl sfInjectDestructors
|
||||
|
||||
let upField = lookupInRecord(env.typ.skipTypes({tyOwned, tyRef, tyPtr}).n, getIdent(d.graph.cache, upName))
|
||||
if upField != nil:
|
||||
@@ -675,17 +632,6 @@ proc rawClosureCreation(owner: PSym;
|
||||
if up != nil and upField.typ.skipTypes({tyOwned, tyRef, tyPtr}) == up.typ.skipTypes({tyOwned, tyRef, tyPtr}):
|
||||
result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info),
|
||||
up, env.info))
|
||||
# That assignment stores a real `ref`, so `injectDestructorCalls` has to
|
||||
# find the up-field type's ops — otherwise it stays a raw pointer store,
|
||||
# the enclosing env's refcount is one too low, and at teardown the two
|
||||
# envs' mutually recursive `=destroy`s each believe they hold the last
|
||||
# reference and recurse until the stack is gone. Whole-program cgen never
|
||||
# noticed: some LATER lifting pass creates this very ref type's ops, and it
|
||||
# runs before any routine's destructor injection. The per-module backend
|
||||
# injects a routine right after lifting it (the `lower` stage), long before
|
||||
# the module's top level is transformed at all (that is `cg`).
|
||||
if up.typ != nil and up.typ.kind == tyRef and up.typ.elementType != nil:
|
||||
createTypeBoundOpsLL(d.graph, up.typ, env.info, d.idgen, owner)
|
||||
#elif oldenv != nil and oldenv.typ == upField.typ:
|
||||
# result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info),
|
||||
# oldenv, env.info))
|
||||
@@ -743,10 +689,6 @@ proc closureCreationForIter(owner: PSym, iter: PNode;
|
||||
if u != nil and u.typ.skipTypes({tyOwned, tyRef, tyPtr}) == expectedUpTyp:
|
||||
result.add(newAsgnStmt(rawIndirectAccess(vnode, upField, iter.info),
|
||||
u, iter.info))
|
||||
# See the identical call in `rawClosureCreation`: the up-field's ops must
|
||||
# exist by the time this assignment is destructor-injected.
|
||||
if u.typ != nil and u.typ.kind == tyRef and u.typ.elementType != nil:
|
||||
createTypeBoundOpsLL(d.graph, u.typ, iter.info, d.idgen, owner)
|
||||
else:
|
||||
localError(d.graph.config, iter.info, "internal error: cannot create up reference for iter")
|
||||
result.add makeClosure(d.graph, d.idgen, iter.sym, vnode, iter.info)
|
||||
|
||||
@@ -46,11 +46,12 @@ proc setToPreviousLayer*(pt: var LayeredIdTable) {.inline.} =
|
||||
when useRef:
|
||||
pt = pt.nextLayer
|
||||
else:
|
||||
# Must read nextLayer into a temp before destroying pt:
|
||||
# `pt = pt.nextLayer[]` would call eqcopy(&pt, &(*pt.nextLayer)) which
|
||||
# decrements pt.nextLayer's rc (freeing it) before reading pt.nextLayer.nextLayer.
|
||||
let tmp = pt.nextLayer[]
|
||||
pt = tmp
|
||||
when defined(gcDestructors):
|
||||
pt = pt.nextLayer[]
|
||||
else:
|
||||
# workaround refc
|
||||
let tmp = pt.nextLayer[]
|
||||
pt = tmp
|
||||
|
||||
iterator pairs*(pt: LayeredIdTable): (ItemId, PType) =
|
||||
var tm = pt
|
||||
@@ -61,17 +62,6 @@ iterator pairs*(pt: LayeredIdTable): (ItemId, PType) =
|
||||
break
|
||||
tm.setToPreviousLayer
|
||||
|
||||
proc lookupById*(typeMap: LayeredIdTable, key: ItemId): PType =
|
||||
## Looks up an ItemId directly, observing the same layer shadowing rules as
|
||||
## `lookup`. This form is useful when a binding key was previously captured.
|
||||
result = nil
|
||||
var tm = typeMap
|
||||
while true:
|
||||
result = getOrDefault(tm.topLayer, key)
|
||||
if result != nil or tm.nextLayer == nil:
|
||||
return
|
||||
tm.setToPreviousLayer
|
||||
|
||||
proc lookup(typeMap: ref LayeredIdTableObj, key: ItemId): PType =
|
||||
result = nil
|
||||
var tm = typeMap
|
||||
@@ -82,7 +72,7 @@ proc lookup(typeMap: ref LayeredIdTableObj, key: ItemId): PType =
|
||||
|
||||
template lookup*(typeMap: ref LayeredIdTableObj, key: PType): PType =
|
||||
## recursively looks up binding of `key` in all parent layers
|
||||
lookup(typeMap, key.bindingId)
|
||||
lookup(typeMap, key.itemId)
|
||||
|
||||
when not useRef:
|
||||
proc lookup(typeMap: LayeredIdTableObj, key: ItemId): PType {.inline.} =
|
||||
@@ -91,11 +81,11 @@ when not useRef:
|
||||
result = lookup(typeMap.nextLayer, key)
|
||||
|
||||
template lookup*(typeMap: LayeredIdTableObj, key: PType): PType =
|
||||
lookup(typeMap, key.bindingId)
|
||||
lookup(typeMap, key.itemId)
|
||||
|
||||
proc put(typeMap: var LayeredIdTable, key: ItemId, value: PType) {.inline.} =
|
||||
typeMap.topLayer[key] = value
|
||||
|
||||
template put*(typeMap: var LayeredIdTable, key, value: PType) =
|
||||
## binds `key` to `value` only in current layer
|
||||
put(typeMap, key.bindingId, value)
|
||||
put(typeMap, key.itemId, value)
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -735,11 +735,17 @@ proc getEscapedChar(L: var Lexer, tok: var Token) =
|
||||
else: lexMessage(L, errGenerated, "invalid character constant")
|
||||
|
||||
proc handleCRLF(L: var Lexer, pos: int): int =
|
||||
result =
|
||||
case L.buf[pos]
|
||||
of CR: nimlexbase.handleCR(L, pos)
|
||||
of LF: nimlexbase.handleLF(L, pos)
|
||||
else: pos
|
||||
template registerLine =
|
||||
let col = L.getColNumber(pos)
|
||||
|
||||
case L.buf[pos]
|
||||
of CR:
|
||||
registerLine()
|
||||
result = nimlexbase.handleCR(L, pos)
|
||||
of LF:
|
||||
registerLine()
|
||||
result = nimlexbase.handleLF(L, pos)
|
||||
else: result = pos
|
||||
|
||||
type
|
||||
StringMode = enum
|
||||
@@ -839,8 +845,8 @@ proc getCharacter(L: var Lexer; tok: var Token) =
|
||||
|
||||
const
|
||||
UnicodeOperatorStartChars = {'\226', '\194', '\195'}
|
||||
# the allowed unicode characters ("∙ ∘ × ★ ☆ ⊗ ⊘ ⊙ ⊛ ⊠ ⊡ ∩ ∧ ⊓ ⟑ ⟇ ⩓ ⩔ ■ □
|
||||
# ± ⊕ ⊖ ⊞ ⊟ ∪ ∨ ⊔") all start with one of these.
|
||||
# the allowed unicode characters ("∙ ∘ × ★ ⊗ ⊘ ⊙ ⊛ ⊠ ⊡ ∩ ∧ ⊓ ± ⊕ ⊖ ⊞ ⊟ ∪ ∨ ⊔")
|
||||
# all start with one of these.
|
||||
|
||||
type
|
||||
UnicodeOprPred = enum
|
||||
@@ -872,18 +878,7 @@ proc unicodeOprLen(buf: cstring; pos: int): (int8, UnicodeOprPred) =
|
||||
elif buf[pos+2] == '\159': result = 3.a # ⊟
|
||||
elif buf[pos+2] == '\160': result = 3.m # ⊠
|
||||
elif buf[pos+2] == '\161': result = 3.m # ⊡
|
||||
elif buf[pos+1] == '\150':
|
||||
if buf[pos+2] == '\160': result = 3.m # ■
|
||||
elif buf[pos+2] == '\161': result = 3.m # □
|
||||
elif buf[pos+1] == '\152':
|
||||
if buf[pos+2] == '\133': result = 3.m # ★
|
||||
elif buf[pos+2] == '\134': result = 3.m # ☆
|
||||
elif buf[pos+1] == '\159':
|
||||
if buf[pos+2] == '\135': result = 3.m # ⟇
|
||||
elif buf[pos+2] == '\145': result = 3.m # ⟑
|
||||
elif buf[pos+1] == '\169':
|
||||
if buf[pos+2] == '\147': result = 3.m # ⩓
|
||||
elif buf[pos+2] == '\148': result = 3.m # ⩔
|
||||
elif buf[pos+1] == '\152' and buf[pos+2] == '\133': result = 3.m # ★
|
||||
of '\194':
|
||||
if buf[pos+1] == '\177': result = 2.a # ±
|
||||
of '\195':
|
||||
@@ -1354,7 +1349,7 @@ proc rawGetTok*(L: var Lexer, tok: var Token) =
|
||||
lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
|
||||
of '-':
|
||||
if L.buf[L.bufpos+1] in {'0'..'9'} and
|
||||
(L.bufpos == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
|
||||
(L.bufpos-1 == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
|
||||
# x)-23 # binary minus
|
||||
# ,-23 # unary minus
|
||||
# \n-78 # unary minus? Yes.
|
||||
|
||||
@@ -94,21 +94,11 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
|
||||
proc genAddr(c: var TLiftCtx; x: PNode): PNode =
|
||||
# These synthesized addresses are always passed to codegen procs that expect a
|
||||
# genuine pointer (nimAsgnYrc, nimSinkYrc, destructors, ...). `addr(deref x)`
|
||||
# collapses to `x` only when `x` is a real pointer; on the C++ backend a `var`
|
||||
# parameter is a C++ reference, so we must keep the `nkHiddenAddr` to actually
|
||||
# take its address (`&dest`) instead of passing the reference's value. Likewise
|
||||
# `tfVarIsPtr` keeps the C++ backend from lowering the synthesized address back
|
||||
# to a reference and dropping the `&` (e.g. a closure's `tyPointer` env). See
|
||||
# #26026 CI (yrc + cpp).
|
||||
if x.kind == nkHiddenDeref and c.g.config.backend != backendCpp:
|
||||
if x.kind == nkHiddenDeref:
|
||||
checkSonsLen(x, 1, c.g.config)
|
||||
result = x[0]
|
||||
else:
|
||||
let addrTyp = makeVarType(x.typ.owner, x.typ, c.idgen)
|
||||
addrTyp.incl tfVarIsPtr
|
||||
result = newNodeIT(nkHiddenAddr, x.info, addrTyp)
|
||||
result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ, c.idgen))
|
||||
result.add x
|
||||
|
||||
proc genWhileLoop(c: var TLiftCtx; i, dest: PNode): PNode =
|
||||
@@ -596,12 +586,16 @@ proc newSeqCall(c: var TLiftCtx; x, y: PNode): PNode =
|
||||
lenCall.typ = getSysType(c.g, x.info, tyInt)
|
||||
result.add lenCall
|
||||
|
||||
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode; noinit = false): PNode =
|
||||
proc setLenStrCall(c: var TLiftCtx; x, y: PNode): PNode =
|
||||
let lenCall = genBuiltin(c, mLengthStr, "len", y)
|
||||
lenCall.typ = getSysType(c.g, x.info, tyInt)
|
||||
result = genBuiltin(c, mSetLengthStr, "setLen", x) # genAddr(g, x))
|
||||
result.add lenCall
|
||||
|
||||
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode): PNode =
|
||||
let lenCall = genBuiltin(c, mLengthSeq, "len", y)
|
||||
lenCall.typ = getSysType(c.g, x.info, tyInt)
|
||||
let name = if noinit: "setLenUninit" else: "setLen"
|
||||
let magic = if noinit: mSetLengthSeqUninit else: mSetLengthSeq
|
||||
var op = getSysMagic(c.g, x.info, name, magic)
|
||||
var op = getSysMagic(c.g, x.info, "setLen", mSetLengthSeq)
|
||||
op = instantiateGeneric(c, op, t, t)
|
||||
result = newTree(nkCall, newSymNode(op, x.info), x, lenCall)
|
||||
|
||||
@@ -626,35 +620,11 @@ proc checkSelfAssignment(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
cond.typ = getSysType(c.g, c.info, tyBool)
|
||||
body.add genIf(c, cond, newTreeI(nkReturnStmt, c.info, newNodeI(nkEmpty, c.info)))
|
||||
|
||||
proc genBulkCopySeq(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
## Generates a call to nimCopySeqPayload for bulk memcpy of seq data.
|
||||
let elemType = t.elementType
|
||||
let sym = magicsys.getCompilerProc(c.g, "nimCopySeqPayload")
|
||||
if sym == nil:
|
||||
localError(c.g.config, c.info, "system module needs: nimCopySeqPayload")
|
||||
return
|
||||
var sizeOf = genBuiltin(c, mSizeOf, "sizeof", newNodeIT(nkType, c.info, elemType))
|
||||
sizeOf.typ = getSysType(c.g, c.info, tyInt)
|
||||
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
|
||||
alignOf.typ = getSysType(c.g, c.info, tyInt)
|
||||
let call = newNodeI(nkCall, c.info)
|
||||
call.add newSymNode(sym)
|
||||
call.add newTreeIT(nkAddr, c.info, makePtrType(c.fn, x.typ, c.idgen), x)
|
||||
call.add newTreeIT(nkAddr, c.info, makePtrType(c.fn, y.typ, c.idgen), y)
|
||||
call.add sizeOf
|
||||
call.add alignOf
|
||||
call.typ = sym.typ.returnType
|
||||
body.add call
|
||||
|
||||
proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
case c.kind
|
||||
of attachedDup:
|
||||
let bulkCopy = supportsCopyMem(t.elementType)
|
||||
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
|
||||
if bulkCopy:
|
||||
genBulkCopySeq(c, t, body, x, y)
|
||||
else:
|
||||
forallElements(c, t, body, x, y)
|
||||
body.add setLenSeqCall(c, t, x, y)
|
||||
forallElements(c, t, body, x, y)
|
||||
of attachedAsgn, attachedDeepCopy:
|
||||
# we generate:
|
||||
# if x.p == y.p:
|
||||
@@ -663,14 +633,9 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
# var i = 0
|
||||
# while i < y.len: dest[i] = y[i]; inc(i)
|
||||
# This is usually more efficient than a destroy/create pair.
|
||||
# For trivially copyable types, use bulk copyMem instead of element loop.
|
||||
checkSelfAssignment(c, t, body, x, y)
|
||||
let bulkCopy = supportsCopyMem(t.elementType)
|
||||
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
|
||||
if bulkCopy:
|
||||
genBulkCopySeq(c, t, body, x, y)
|
||||
else:
|
||||
forallElements(c, t, body, x, y)
|
||||
body.add setLenSeqCall(c, t, x, y)
|
||||
forallElements(c, t, body, x, y)
|
||||
of attachedSink:
|
||||
let moveCall = genBuiltin(c, mMove, "move", x)
|
||||
moveCall.add y
|
||||
@@ -715,11 +680,6 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
doAssert t.asink != nil
|
||||
body.add newHookCall(c, t.asink, x, y)
|
||||
of attachedDestructor:
|
||||
when defined(icDbg):
|
||||
if t.destructor == nil:
|
||||
echo "MISSING destructor: ", typeToString(t), " kind=", t.kind,
|
||||
" itemId=", t.itemId, " bindingId=", t.bindingId, " state=", t.state,
|
||||
" owner=", (if t.owner != nil: t.owner.name.s else: "nil")
|
||||
doAssert t.destructor != nil
|
||||
body.add destructorCall(c, t.destructor, x)
|
||||
of attachedTrace:
|
||||
@@ -741,18 +701,11 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedAsgn, attachedDeepCopy, attachedDup:
|
||||
body.add callCodegenProc(c.g, "nimAsgnStrV2", c.info, genAddr(c, x), y)
|
||||
of attachedSink:
|
||||
if c.g.config.usesSso():
|
||||
# SmallString: destroy old dst, then bit-copy src (no rc increment — this is a move).
|
||||
# No .p aliasing check needed; rc-based destroy handles COW sharing correctly.
|
||||
doAssert t.destructor != nil
|
||||
body.add destructorCall(c, t.destructor, x)
|
||||
body.add newAsgnStmt(x, y)
|
||||
else:
|
||||
let moveCall = genBuiltin(c, mMove, "move", x)
|
||||
moveCall.add y
|
||||
doAssert t.destructor != nil
|
||||
moveCall.add destructorCall(c, t.destructor, x)
|
||||
body.add moveCall
|
||||
let moveCall = genBuiltin(c, mMove, "move", x)
|
||||
moveCall.add y
|
||||
doAssert t.destructor != nil
|
||||
moveCall.add destructorCall(c, t.destructor, x)
|
||||
body.add moveCall
|
||||
of attachedDestructor:
|
||||
body.add genBuiltin(c, mDestroy, "destroy", x)
|
||||
of attachedTrace:
|
||||
@@ -800,22 +753,8 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
|
||||
createTypeBoundOps(c.g, c.c, elemType, c.info, c.idgen)
|
||||
|
||||
# YRC uses dedicated runtime procs for the entire write barrier -- but ONLY
|
||||
# for refs that can actually form cycles. Routing an acyclic ref through
|
||||
# `nimAsgnYrc` defeats the entire purpose of `.acyclic`: the barrier defers
|
||||
# the dec into a stripe queue, `drainStripe` then hands the cell to
|
||||
# `registerLocal`, and it enters the collector as a capture ROOT -- so a
|
||||
# type annotated precisely to stay out of the cycle collector gets traced
|
||||
# by it anyway. (The collector never reaches such a cell by TRAVERSAL: the
|
||||
# attachedTrace hook below only emits `nimTraceRef` when `isCyclic`. The
|
||||
# queued dec was the only way in.)
|
||||
#
|
||||
# Falling through instead gives acyclic refs the same prompt arc-style
|
||||
# reclamation they get under --mm:arc/orc, which is also what lets a thread
|
||||
# that avoids cycles at compile time avoid the collector entirely at run
|
||||
# time. `canFormAcycle` is the same predicate ccgtypes.nim:1903 uses to set
|
||||
# the descriptor's acyclic flag, so codegen and runtime cannot disagree.
|
||||
if c.g.config.selectedGC == gcYrc and types.canFormAcycle(c.g, elemType):
|
||||
# YRC uses dedicated runtime procs for the entire write barrier:
|
||||
if c.g.config.selectedGC == gcYrc:
|
||||
let desc =
|
||||
if isFinal(elemType):
|
||||
let ti = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
|
||||
@@ -839,15 +778,13 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
tfAcyclic in skipTypes(elemType, abstractInst+{tyOwned}-{tyTypeDesc}).flags
|
||||
# dynamic Acyclic refs need to use dyn decRef
|
||||
|
||||
let useStatic = isFinal(elemType)
|
||||
|
||||
let tmp =
|
||||
if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}:
|
||||
declareTempOf(c, body, x)
|
||||
else:
|
||||
x
|
||||
|
||||
if useStatic:
|
||||
if isFinal(elemType):
|
||||
addDestructorCall(c, elemType, actions, genDeref(tmp, nkDerefExpr))
|
||||
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
|
||||
alignOf.typ = getSysType(c.g, c.info, tyInt)
|
||||
@@ -858,7 +795,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
|
||||
var cond: PNode
|
||||
if isCyclic:
|
||||
if useStatic:
|
||||
if isFinal(elemType):
|
||||
let typInfo = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
|
||||
typInfo.typ = getSysType(c.g, c.info, tyPointer)
|
||||
cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicStatic", c.info, tmp, typInfo)
|
||||
@@ -893,7 +830,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedDeepCopy: assert(false, "cannot happen")
|
||||
of attachedTrace:
|
||||
if isCyclic:
|
||||
if useStatic:
|
||||
if isFinal(elemType):
|
||||
let typInfo = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
|
||||
typInfo.typ = getSysType(c.g, c.info, tyPointer)
|
||||
body.add callCodegenProc(c.g, "nimTraceRef", c.info, genAddrOf(x, c.idgen), typInfo, y)
|
||||
@@ -1105,17 +1042,8 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
case t.kind
|
||||
of tyNone, tyEmpty, tyVoid: discard
|
||||
of tyUncheckedArray:
|
||||
# An UncheckedArray has no known length, so it cannot be copied, moved or
|
||||
# destroyed as a value: it only ever lives behind a pointer and its bytes
|
||||
# are managed manually (element ops for seqs/strings go through the
|
||||
# seq/string hooks, which know the length). Emitting `x = y` for it (as the
|
||||
# pointer-like group below does) produces an assignment of an unsized array,
|
||||
# which the C backend cannot lower (genAssignment: tyUncheckedArray). So all
|
||||
# value hooks for it are no-ops.
|
||||
discard
|
||||
of tyPointer, tySet, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCstring,
|
||||
tyPtr, tyVar, tyLent:
|
||||
tyPtr, tyUncheckedArray, tyVar, tyLent:
|
||||
defaultOp(c, t, body, x, y)
|
||||
of tyRef:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
@@ -1233,7 +1161,7 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
|
||||
res.typ = typ
|
||||
src.typ = typ
|
||||
|
||||
result.typ = newType(tyProc, idgen, result)
|
||||
result.typ = newType(tyProc, idgen, owner)
|
||||
result.typ.n = newNodeI(nkFormalParams, info)
|
||||
rawAddSon(result.typ, res.typ)
|
||||
result.typ.n.add newNodeI(nkEffectList, info)
|
||||
@@ -1255,7 +1183,6 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
|
||||
n[resultPos] = newSymNode(res)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, {sfFromGeneric, sfGeneratedOp}
|
||||
setHookDisamb(g, result, AttachedOpToStr[kind], typ)
|
||||
|
||||
proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp;
|
||||
info: TLineInfo; idgen: IdGenerator; isDiscriminant = false): PSym =
|
||||
@@ -1279,8 +1206,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
|
||||
else:
|
||||
src.typ = typ
|
||||
|
||||
# the hook OWNS its signature, like any routine sem'd from source
|
||||
result.typ = newProcType(info, idgen, result)
|
||||
result.typ = newProcType(info, idgen, owner)
|
||||
result.typ.addParam dest
|
||||
if kind notin {attachedDestructor, attachedWasMoved}:
|
||||
result.typ.addParam src
|
||||
@@ -1303,10 +1229,6 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
|
||||
if kind == attachedWasMoved:
|
||||
incl result.flagsImpl, sfNoSideEffect
|
||||
incl result.typ, tfNoSideEffect
|
||||
if not isDiscriminant:
|
||||
# discriminant destructors derive their body from the enclosing object
|
||||
# AND the selected field; their key is set at the call site
|
||||
setHookDisamb(g, result, AttachedOpToStr[kind], typ)
|
||||
|
||||
proc genTypeFieldCopy(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
let xx = genBuiltin(c, mAccessTypeField, "accessTypeField", x)
|
||||
@@ -1399,7 +1321,6 @@ proc produceDestructorForDiscriminator*(g: ModuleGraph; typ: PType; field: PSym,
|
||||
assert(typ.skipTypes({tyAlias, tyGenericInst}).kind == tyObject)
|
||||
# discrimantor assignments needs pointers to destroy fields; alas, we cannot use non-var destructor here
|
||||
result = symPrototype(g, field.typ, typ.owner, attachedDestructor, info, idgen, isDiscriminant = true)
|
||||
setHookDisamb(g, result, "=destroy¦" & field.name.s & "¦" & $field.position, typ)
|
||||
var a = TLiftCtx(info: info, g: g, kind: attachedDestructor, asgnForType: typ, idgen: idgen,
|
||||
fn: result)
|
||||
a.asgnForType = typ
|
||||
|
||||
@@ -100,7 +100,6 @@ type
|
||||
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
|
||||
warnImplicitRangeConversion = "ImplicitRangeConversion",
|
||||
warnSystemRangeConversion = "SystemRangeConversion",
|
||||
warnInvalidCmpOp = "InvalidCmpOp",
|
||||
# hints
|
||||
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
||||
hintCC = "CC",
|
||||
@@ -211,7 +210,6 @@ const
|
||||
warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable",
|
||||
warnImplicitRangeConversion: "implicit range conversion $1",
|
||||
warnSystemRangeConversion: "implicit range conversion $1",
|
||||
warnInvalidCmpOp: "$1",
|
||||
hintSuccess: "operation successful: $#",
|
||||
# keep in sync with `testament.isSuccess`
|
||||
hintSuccessX: "$build\n$loc lines; ${sec}s; $mem; proj: $project; out: $output",
|
||||
@@ -268,7 +266,7 @@ proc computeNotesVerbosity(): array[0..3, TNoteKinds] =
|
||||
result = default(array[0..3, TNoteKinds])
|
||||
result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix, warnSystemRangeConversion}
|
||||
result[2] = result[3] - {hintStackTrace, hintExtendedContext, hintDeclaredLoc, hintProcessingStmt}
|
||||
result[1] = result[2] - {warnImplicitRangeConversion, warnProveField, warnProveIndex,
|
||||
result[1] = result[2] - {warnProveField, warnProveIndex,
|
||||
warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd,
|
||||
hintSource, hintGlobalVar, hintGCStats, hintMsgOrigin, hintPerformance}
|
||||
result[0] = result[1] - {hintSuccessX, hintSuccess, hintConf,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -378,9 +378,6 @@ proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string;
|
||||
conflictsWith: TLineInfo, note = errGenerated) =
|
||||
## Emit a redefinition error if in non-interactive mode
|
||||
if c.config.cmd != cmdInteractive:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icRedef] ", s
|
||||
echo getStackTrace()
|
||||
localError(c.config, info, note,
|
||||
"redefinition of '$1'; previous declaration here: $2" %
|
||||
[s, c.config $ conflictsWith])
|
||||
@@ -462,15 +459,6 @@ proc openShadowScope*(c: PContext) =
|
||||
symbols: initStrTable(),
|
||||
depthLevel: c.scopeDepth)
|
||||
|
||||
proc rememberShadowDefs*(c: PContext) =
|
||||
## bug #25693: a template/macro operand's local definitions are sem-checked in
|
||||
## a shadow scope that is then discarded. Record those definitions so that a
|
||||
## later re-emission (e.g. a captured `typed` fragment expanded more than once)
|
||||
## can be detected as a redefinition rather than silently miscompiled.
|
||||
for s in c.currentScope.symbols:
|
||||
if s.kind in {skVar, skLet, skForVar} and {sfGenSym, sfWasGenSym} * s.flags == {}:
|
||||
c.shadowDiscardedDefs.incl s.id
|
||||
|
||||
proc closeShadowScope*(c: PContext) =
|
||||
## closes the shadow scope, but doesn't merge any of the symbols
|
||||
## Does not check for unused symbols or missing forward decls since a macro
|
||||
|
||||
@@ -207,70 +207,15 @@ proc lookupInRecord(n: PNode, id: ItemId): PSym =
|
||||
if result != nil: return
|
||||
else: discard
|
||||
of nkSym:
|
||||
if matchesDerivedFieldId(n.sym.itemId, id): result = n.sym
|
||||
else: discard
|
||||
|
||||
proc lookupCapturedField(n: PNode, s: PSym): PSym =
|
||||
## Find an env field that `addField` would have produced for the captured
|
||||
## local `s`. Used as a fallback when the derived-itemId match fails because
|
||||
## `s` is a macro-generated gensym whose process-local id diverges from the
|
||||
## loaded env field's (see `addField`). `addField` always names a field
|
||||
## `s.name & $field.position`, so that pair uniquely identifies the field for a
|
||||
## local of this name without relying on the (unstable) item id.
|
||||
result = nil
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
for i in 0..<n.len:
|
||||
result = lookupCapturedField(n[i], s)
|
||||
if result != nil: return
|
||||
of nkRecCase:
|
||||
if n[0].kind != nkSym: return
|
||||
result = lookupCapturedField(n[0], s)
|
||||
if result != nil: return
|
||||
for i in 1..<n.len:
|
||||
case n[i].kind
|
||||
of nkOfBranch, nkElse:
|
||||
result = lookupCapturedField(lastSon(n[i]), s)
|
||||
if result != nil: return
|
||||
else: discard
|
||||
of nkSym:
|
||||
if n.sym.kind == skField and n.sym.name.s == s.name.s & $n.sym.position:
|
||||
result = n.sym
|
||||
if n.sym.itemId.module == id.module and n.sym.itemId.item == -abs(id.item): result = n.sym
|
||||
else: discard
|
||||
|
||||
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
|
||||
# Idempotent w.r.t. the captured symbol (mirrors `addUniqueField`): re-lifting
|
||||
# a LOADED routine re-derives its transformed body (never serialized under IC)
|
||||
# and re-captures the same locals, but the env object loaded from the NIF
|
||||
# already carries their fields. Re-adding would duplicate the field and, worse,
|
||||
# mutate a Sealed loaded type via `propagateToOwner` (the `t.state != Sealed`
|
||||
# crash). Return the existing field instead.
|
||||
let existing = lookupInRecord(obj.n, s.itemId)
|
||||
if existing != nil:
|
||||
return existing
|
||||
# Re-lifting a LOADED routine during a VM transform (its transformed body is
|
||||
# re-derived per process, never serialized) re-captures the same locals, but
|
||||
# for a macro-generated gensym (e.g. libp2p `p2pProtocolBackendImpl`'s
|
||||
# `msgVar`) its process-local id diverges from the one baked into the loaded
|
||||
# env field, so the id match above misses. Reuse the existing same-named field
|
||||
# rather than appending a divergent duplicate, which keeps the re-derived
|
||||
# closure consistent (else a stale `:env` access reaches `cannotEval`).
|
||||
# Confined to a loaded (Sealed) env: in a freshly built env ids are consistent,
|
||||
# and two distinct same-named captures legitimately get distinct fields there.
|
||||
if obj.state == Sealed:
|
||||
let byName = lookupCapturedField(obj.n, s)
|
||||
if byName != nil:
|
||||
return byName
|
||||
# Genuinely new field. Under IC the env may be a loaded Sealed type whose
|
||||
# transform-time mutation is process-local (the body is discarded after the
|
||||
# macro runs), so downgrade it to mutable instead of crashing on
|
||||
# `t.state != Sealed` (mirrors `markAsClosure`).
|
||||
unsealForTransform(obj)
|
||||
# because of 'gensym' support, we have to mangle the name with its ID.
|
||||
# This is hacky but the clean solution is much more complex than it looks.
|
||||
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len),
|
||||
idgen, s.owner, s.info, s.options)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
field.itemId = ItemId(module: s.itemId.module, item: -s.itemId.item)
|
||||
let t = skipIntLit(s.typ, idgen)
|
||||
field.typ = t
|
||||
if s.kind in {skLet, skVar, skField, skForVar}:
|
||||
@@ -290,7 +235,7 @@ proc addUniqueField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator)
|
||||
if result == nil:
|
||||
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), idgen,
|
||||
s.owner, s.info, s.options)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
field.itemId = ItemId(module: s.itemId.module, item: -s.itemId.item)
|
||||
let t = skipIntLit(s.typ, idgen)
|
||||
field.typ = t
|
||||
assert t.kind != tyTyped
|
||||
@@ -361,16 +306,6 @@ proc getFieldFromObj*(t: PType; v: PSym): PSym =
|
||||
assert t.kind == tyObject
|
||||
result = lookupInRecord(t.n, v.itemId)
|
||||
if result != nil: break
|
||||
# A LOADED (Sealed) env object carries fields baked by the producer process;
|
||||
# re-lifting a NIF-loaded routine in a consumer (e.g. a macro VM-evaluating an
|
||||
# imported `p2pProtocolBackendImpl`) re-captures the same local under a
|
||||
# divergent process-local id, so the derived-itemId match misses. Fall back to
|
||||
# the name+position identity `addField` uses — SYMMETRIC with `addField`'s
|
||||
# Sealed by-name reuse — so the access resolves the field `addField` produced
|
||||
# instead of failing with `not part of closure object type`.
|
||||
if t.state == Sealed:
|
||||
result = lookupCapturedField(t.n, v)
|
||||
if result != nil: break
|
||||
t = t.baseClass
|
||||
if t == nil: break
|
||||
t = t.skipTypes(skipPtrs)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import
|
||||
ast, msgs, platform, idents,
|
||||
modulegraphs, lineinfos, types
|
||||
modulegraphs, lineinfos
|
||||
|
||||
export createMagic
|
||||
|
||||
@@ -134,7 +134,7 @@ proc getNimScriptSymbol*(g: ModuleGraph; name: string): PSym =
|
||||
proc resetNimScriptSymbols*(g: ModuleGraph) = g.exposed = initStrTable()
|
||||
|
||||
proc getMagicEqSymForType*(g: ModuleGraph; t: PType; info: TLineInfo): PSym =
|
||||
case t.skipTypes(abstractRange).kind
|
||||
case t.kind
|
||||
of tyInt, tyInt8, tyInt16, tyInt32, tyInt64,
|
||||
tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64:
|
||||
result = getSysMagic(g, info, "==", mEqI)
|
||||
|
||||
@@ -29,13 +29,10 @@ when defined(nimPreviewSlimSystem):
|
||||
import ../dist/checksums/src/checksums/sha1
|
||||
|
||||
import pipelines
|
||||
import icprof
|
||||
from icconfig import produceIcConfig, ensureIcConfig
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
import nifbackend
|
||||
import deps
|
||||
import idetools
|
||||
|
||||
when not defined(leanCompiler):
|
||||
import docgen
|
||||
@@ -210,6 +207,22 @@ proc commandInteractive(graph: ModuleGraph) =
|
||||
let s = llStreamOpenStdIn(onPrompt = proc() = flushDot(graph.config))
|
||||
discard processPipelineModule(graph, m, idgen, s)
|
||||
|
||||
proc commandScan(cache: IdentCache, config: ConfigRef) =
|
||||
var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt)
|
||||
var stream = llStreamOpen(f, fmRead)
|
||||
if stream != nil:
|
||||
var
|
||||
L: Lexer = default(Lexer)
|
||||
tok: Token = default(Token)
|
||||
openLexer(L, f, stream, cache, config)
|
||||
while true:
|
||||
rawGetTok(L, tok)
|
||||
printTok(config, tok)
|
||||
if tok.tokType == tkEof: break
|
||||
closeLexer(L)
|
||||
else:
|
||||
rawMessage(config, errGenerated, "cannot open file: " & f.string)
|
||||
|
||||
const
|
||||
PrintRopeCacheStats = false
|
||||
|
||||
@@ -270,28 +283,6 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
|
||||
proc compileToBackend() =
|
||||
customizeForBackend(conf.backend)
|
||||
if isIcDriver(conf):
|
||||
# `nim c --ic:on` / `nim cpp --ic:on`: same driver as `nim ic`, entered
|
||||
# through the ordinary compile command so every backend switch the user
|
||||
# already knows keeps working (`nim cpp`, `--exceptions:`, `-d:`, ...).
|
||||
# `customizeForBackend` above has already defined the backend symbol and
|
||||
# picked the exception model, which is exactly what the per-module
|
||||
# children must inherit — `computeForwardedArgs` forwards both.
|
||||
setUseIc(true)
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
when not defined(nimKochBootstrap):
|
||||
if conf.icPreparsedConfig.len == 0:
|
||||
# `--ic:on` came from a `nim.cfg`/`config.nims` rather than the command
|
||||
# line, so `nim.nim` could not see it before config loading and the
|
||||
# precompiled config the children replay does not exist yet. Produce it
|
||||
# now. (The driver then keeps the config IT parsed instead of replaying
|
||||
# the artifact; both come from the same files.)
|
||||
ensureIcConfig(conf)
|
||||
commandIc(conf)
|
||||
else:
|
||||
rawMessage(conf, errGenerated, "--ic:on not available in bootstrap build")
|
||||
return
|
||||
setOutFile(conf)
|
||||
case conf.backend
|
||||
of backendC: commandCompileToC(graph)
|
||||
@@ -424,34 +415,13 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
for it in conf.searchPaths: msgWriteln(conf, it.string)
|
||||
of cmdCheck:
|
||||
commandCheck(graph)
|
||||
of cmdTrack:
|
||||
# `nim track --def:/--usages:/--track:` — IDE goto-definition / find-usages.
|
||||
# Runs `nim ic`'s incremental frontend (nifler + per-module `nim m`, so only
|
||||
# changed modules recompile and each writes a faithful, VM-executed `.s.bif`
|
||||
# — covering stdlib too), then scans those NIF files (idetools.runIdeQuery).
|
||||
# Shares the `nim ic` nimcache dir, so a prior `nim ic` build is reused.
|
||||
setUseIc(true)
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
when not defined(nimKochBootstrap):
|
||||
commandIc(conf, frontendOnly = true)
|
||||
runIdeQuery(conf)
|
||||
else:
|
||||
rawMessage(conf, errGenerated, "nim track not available in bootstrap build")
|
||||
of cmdM:
|
||||
# cmdM uses NIF files, not ROD files
|
||||
graph.config.symbolFiles = disabledSf
|
||||
setUseIc(true)
|
||||
# vtable dispatch needs a whole-program vtable layout, which the
|
||||
# per-module compilation model cannot provide (yet); methods dispatch
|
||||
# through the classic if-chain dispatchers instead
|
||||
excl conf.features, Feature.vtables
|
||||
# `tStage` for a `nim m` process, so `Process - Stage` is its real startup
|
||||
# (exec, runtime init, config replay) rather than its whole runtime.
|
||||
timed tStage: commandCheck(graph)
|
||||
commandCheck(graph)
|
||||
of cmdNifC:
|
||||
setUseIc(true)
|
||||
excl conf.features, Feature.vtables
|
||||
# Generate C code from NIF files
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
@@ -460,18 +430,10 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
# Generate .build.nif for nifmake
|
||||
setUseIc(true)
|
||||
wantMainModule(conf)
|
||||
# Resolve the output binary path (honoring `--out`) up front, like cmdNifC:
|
||||
# the backend build file derives the link target from `conf.absOutFile`.
|
||||
setOutFile(conf)
|
||||
when not defined(nimKochBootstrap):
|
||||
commandIc(conf)
|
||||
else:
|
||||
rawMessage(conf, errGenerated, "nim deps not available in bootstrap build")
|
||||
of cmdIcConfig:
|
||||
# Produce the precompiled config artifact for `nim ic` (config already
|
||||
# parsed by the normal pipeline); a separate process spawned by the driver.
|
||||
wantMainModule(conf)
|
||||
produceIcConfig(conf)
|
||||
of cmdParse:
|
||||
wantMainModule(conf)
|
||||
discard parseFile(conf.projectMainIdx, cache, conf)
|
||||
@@ -485,17 +447,10 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
of cmdJsonscript:
|
||||
setOutFile(graph.config)
|
||||
commandJsonScript(graph)
|
||||
of cmdUnknown, cmdNone:
|
||||
of cmdUnknown, cmdNone, cmdIdeTools:
|
||||
rawMessage(conf, errGenerated, "invalid command: " & conf.command)
|
||||
|
||||
if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop, cmdM} and
|
||||
not (conf.cmd == cmdNifC and conf.icBackendStage.len > 0):
|
||||
# The IC build runs hundreds of internal per-module child processes — the
|
||||
# frontend `nim m` (cmdM) and the per-module backend stages (cg/emit/merge/
|
||||
# link). Each would print a `[SuccessX]` summary that is pure noise (and
|
||||
# misleading: `out: unknownOutput`, or `out: <the whole compiler>` for a
|
||||
# step that only wrote one `.c.nif`/`.c`). The driving `nim ic` (and koch)
|
||||
# reports the real result.
|
||||
if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop}:
|
||||
if optProfileVM in conf.globalOptions:
|
||||
echo conf.dump(conf.vmProfileData)
|
||||
genSuccessX(conf)
|
||||
|
||||
@@ -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 #
|
||||
|
||||
@@ -11,14 +11,13 @@
|
||||
## represents a complete Nim project. Single modules can either be kept in RAM
|
||||
## or stored in a rod-file.
|
||||
|
||||
import std/[intsets, tables, hashes, strtabs, os, strutils, parseutils, sets]
|
||||
import std/[intsets, tables, hashes, strtabs, os, strutils, parseutils]
|
||||
import ../dist/checksums/src/checksums/md5
|
||||
import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages, suggestsymdb
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
import ast2nif
|
||||
import nifstreams
|
||||
import "../dist/nimony/src/lib" / bitabs
|
||||
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
|
||||
|
||||
import typekeys
|
||||
|
||||
@@ -36,10 +35,6 @@ type
|
||||
pureEnums*: seq[PSym]
|
||||
interf: TStrTable
|
||||
interfHidden: TStrTable
|
||||
hiddenPending: bool ## `interfHidden` holds only the exported half so far;
|
||||
## `ensureHiddenIface` materialises the hidden-only
|
||||
## symbols on first use. See
|
||||
## `ast2nif.buildHiddenInterface`.
|
||||
uniqueName*: Rope
|
||||
|
||||
Operators* = object
|
||||
@@ -71,44 +66,7 @@ type
|
||||
memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual,member and ctor so far).
|
||||
initializersPerType*: Table[ItemId, PNode] # Type ID, AST call to the default ctor (c++ only)
|
||||
enumToStringProcs*: Table[ItemId, PSym]
|
||||
loadedEnumToStringProcs: Table[string, PSym]
|
||||
emittedTypeInfo*: Table[string, FileIndex]
|
||||
instDisambs: Table[(int, int32), ItemId] # (name id, content disamb) ->
|
||||
# instance, for collision probing in
|
||||
# `setInstanceDisamb`
|
||||
icCnifFiles*: seq[string] # `.c.nif` artifacts written by this run
|
||||
pendingMethodReplays*: seq[PSym] # method registrations loaded under
|
||||
# `nim nifc`, bucketed only after every
|
||||
# module is loaded (`flushMethodReplays`)
|
||||
icImplDeps*: IntSet # NeedsImpl edge tracking under `nim m`:
|
||||
# module ids (FileIndex) whose routine BODIES
|
||||
# this compilation consumed at compile time.
|
||||
# Written to the `.edges` sidecar; deps.nim
|
||||
# then gates the dependent on those modules'
|
||||
# IMPL cookie instead of the iface cookie, so
|
||||
# e.g. `const x = dep.foo()` re-sems when foo's
|
||||
# body changes. Uniform across body-access
|
||||
# kinds — the iface cookie hashes signatures
|
||||
# ONLY (see ast2nif.cookieSd), so every body
|
||||
# consumer records an edge here: VM-compiled /
|
||||
# getImpl'ed bodies (recordIcImplDep from vm/
|
||||
# vmgen), expanded templates (semTemplateExpr)
|
||||
# and instantiated generics (generateInstance).
|
||||
# Inline iterators / `inline` procs are NOT
|
||||
# tracked: they are inlined at codegen, where
|
||||
# the nifc backend's NIF-mtime invalidation
|
||||
# already re-codegens their users.
|
||||
icQualIfaces*: IntSet # module positions whose interface tables were
|
||||
# populated ONLY for qualified access through a
|
||||
# module re-export (`import x; export x`); the
|
||||
# Iface.module stays nil so a later direct
|
||||
# import still takes the full load path
|
||||
inVMTransform*: int # >0 while the VM compiles a routine body
|
||||
# (vmgen.genProc's transformBody): hooks lifted
|
||||
# there (e.g. for closure-env types of LOADED
|
||||
# routines) are process-local VM artifacts —
|
||||
# serializing them would embed references to
|
||||
# derived env-field syms that no module defines
|
||||
|
||||
packageSyms*: TStrTable
|
||||
deps*: IntSet # the dependency graph or potentially its transitive closure.
|
||||
@@ -141,10 +99,6 @@ type
|
||||
systemModule*: PSym
|
||||
sysTypes*: array[TTypeKind, PType]
|
||||
compilerprocs*: TStrTable
|
||||
missingCompilerProcs*: HashSet[string]
|
||||
# `nim nifc` only: compilerproc names no
|
||||
# loaded module defines, so the whole-program
|
||||
# index scan in `loadCompilerProc` runs once
|
||||
exposed*: TStrTable
|
||||
packageTypes*: TStrTable
|
||||
emptyNode*: PNode
|
||||
@@ -155,49 +109,22 @@ type
|
||||
cacheSeqs*: Table[string, PNode] # state that is shared to support the 'macrocache' API; IC: implemented
|
||||
cacheCounters*: Table[string, BiggestInt] # IC: implemented
|
||||
cacheTables*: Table[string, BTree[string, PNode]] # IC: implemented
|
||||
pendingNifInit*: seq[tuple[module: PSym; topLevel: PNode]]
|
||||
# EVERY module loaded from a NIF — whether a direct import (moduleFromNifFile)
|
||||
# or only a dep-of-a-dep (loadTransitiveHooks) — is recorded here with its
|
||||
# serialized top-level AST. The sem driver drains it once
|
||||
# (pipelines.finalizeLoadedModules) and applies the module's VM-level load
|
||||
# effects UNIFORMLY: macro-cache replay (std/macrocache put/inc/add/incl) and
|
||||
# eager `{.compileTime.}` global init. This is the single place "what a loaded
|
||||
# module does to global state" lives, so a transitively-reached module — which
|
||||
# never passes through compilePipelineModule — gets the SAME treatment as a
|
||||
# direct import instead of silently skipping it (its macrocache state would be
|
||||
# lost; its CT globals would stay nil and a macro splicing one, e.g.
|
||||
# chronicles' `chroniclesBlockName`, emits `break nil` / `nil == 0`). To add a
|
||||
# new per-load VM effect, extend the drain — never a parallel buffer.
|
||||
passes*: seq[TPass]
|
||||
pipelinePass*: PipelinePass
|
||||
onDefinition*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
|
||||
onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
|
||||
onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
|
||||
globalDestructors*: seq[PNode]
|
||||
icModuleDtors*: seq[string] # per-module backend: the C names of the
|
||||
# other modules' global-destructor procs
|
||||
# (`genIcModuleDestroyGlobals`), already in
|
||||
# call order; only the main module's `cg`
|
||||
# fills this, from the `.c.nif` meta heads
|
||||
strongSemCheck*: proc (graph: ModuleGraph; owner: PSym; body: PNode) {.nimcall.}
|
||||
compatibleProps*: proc (graph: ModuleGraph; formal, actual: PType): bool {.nimcall.}
|
||||
idgen*: IdGenerator
|
||||
vmTransfIdgen*: IdGenerator # process-local backend idgen for closure envs
|
||||
# minted while the VM compiles a routine body
|
||||
# (inVMTransform); see lambdalifting / ast2nif @bk
|
||||
operators*: Operators
|
||||
|
||||
cachedFiles*: StringTableRef
|
||||
|
||||
procGlobals*: seq[PNode]
|
||||
nifReplayActions*: Table[int32, seq[PNode]] # module position -> replay actions for NIF
|
||||
nifExpansions*: Table[int32, seq[(PSym, TLineInfo)]]
|
||||
# module position -> (template/macro sym, call-site info) for every expansion
|
||||
# in that module. Templates/macros leave no trace in the sem'checked AST, so
|
||||
# this side-channel (written into the `.bif`, see ast2nif) is what lets
|
||||
# `nim track --usages`/`--def` find them. Populated by `rememberExpansion`.
|
||||
cachedMods: IntSet
|
||||
hookClosure: IntSet # modules whose serialized hooks were already registered
|
||||
|
||||
TPassContext* = object of RootObj # the pass's context
|
||||
idgen*: IdGenerator
|
||||
@@ -220,7 +147,6 @@ proc resetForBackend*(g: ModuleGraph) =
|
||||
a.clear()
|
||||
g.methodsPerGenericType.clear()
|
||||
g.enumToStringProcs.clear()
|
||||
g.loadedEnumToStringProcs.clear()
|
||||
g.dispatchers.setLen(0)
|
||||
g.methodsPerType.clear()
|
||||
for a in mitems(g.loadedOps):
|
||||
@@ -261,25 +187,6 @@ proc toBase64a(s: cstring, len: int): string =
|
||||
result.add cb64[a shr 2]
|
||||
result.add cb64[(a and 3) shl 4]
|
||||
|
||||
proc ensureHiddenIface(g: ModuleGraph; pos: int) =
|
||||
## Materialise a loaded module's hidden-only interface the first time anything
|
||||
## asks for it. Every READ of `interfHidden` goes through `interfSelect`, so
|
||||
## guarding those sites is complete.
|
||||
if g.ifaces[pos].hiddenPending:
|
||||
when not defined(nimKochBootstrap):
|
||||
# By SUFFIX: `c.mods` and `g.ifaces` use different FileIndexes for the
|
||||
# same module (see `buildHiddenInterface`). Into a LOCAL table, because
|
||||
# loading symbols can grow `g.ifaces` and a `var` alias into it would then
|
||||
# point at the freed buffer. Cleared only on success, so an import whose
|
||||
# `.s.bif` does not exist yet is retried rather than written off.
|
||||
var tab = g.ifaces[pos].interfHidden
|
||||
if buildHiddenInterface(ast.program,
|
||||
cachedModuleSuffix(g.config, FileIndex pos), tab):
|
||||
g.ifaces[pos].interfHidden = tab
|
||||
g.ifaces[pos].hiddenPending = false
|
||||
else:
|
||||
g.ifaces[pos].hiddenPending = false
|
||||
|
||||
template interfSelect(iface: Iface, importHidden: bool): TStrTable =
|
||||
var ret = iface.interf.addr # without intermediate ptr, it creates a copy and compiler becomes 15x slower!
|
||||
if importHidden: ret = iface.interfHidden.addr
|
||||
@@ -315,7 +222,6 @@ proc initModuleIter*(mi: var ModuleIter; g: ModuleGraph; m: PSym; name: PIdent):
|
||||
assert m.kind == skModule
|
||||
mi.modIndex = m.position
|
||||
mi.importHidden = optImportHidden in m.options
|
||||
if mi.importHidden: ensureHiddenIface(g, mi.modIndex)
|
||||
result = initIdentIter(mi.ti, g.ifaces[mi.modIndex].interfSelect(mi.importHidden), name)
|
||||
|
||||
proc nextModuleIter*(mi: var ModuleIter; g: ModuleGraph): PSym =
|
||||
@@ -323,48 +229,16 @@ proc nextModuleIter*(mi: var ModuleIter; g: ModuleGraph): PSym =
|
||||
|
||||
iterator allSyms*(g: ModuleGraph; m: PSym): PSym =
|
||||
let importHidden = optImportHidden in m.options
|
||||
if importHidden: ensureHiddenIface(g, m.position)
|
||||
for s in g.ifaces[m.position].interfSelect(importHidden).data:
|
||||
if s != nil:
|
||||
yield s
|
||||
|
||||
proc reexportedModuleSyms*(g: ModuleGraph; m: PSym): seq[(string, string)] =
|
||||
## (name, NIF module suffix) of MODULE syms in `m`'s interface — these are
|
||||
## re-exports (`import x; export x`, added by `reexportSym`) acting as
|
||||
## qualifiers (`m.x.sym`). Consumed by the NIF writer; semExport does not
|
||||
## put them into the nkExportStmt children, so the AST walk cannot see them.
|
||||
result = @[]
|
||||
var seen = initIntSet()
|
||||
for s in g.ifaces[m.position].interf.data:
|
||||
if s != nil and s.kind == skModule and s.position != m.position and
|
||||
not seen.containsOrIncl(s.position):
|
||||
result.add (s.name.s, cachedModuleSuffix(g.config, FileIndex s.position))
|
||||
|
||||
proc reexportedLocalSyms*(g: ModuleGraph; m: PSym): seq[ItemId] =
|
||||
## Symbols DEFINED in `m` that reached `m`'s interface through an explicit
|
||||
## `export s` rather than through a `*` marker on their declaration.
|
||||
##
|
||||
## `semExport` re-exports by `reexportSym`, which adds to the interface table
|
||||
## and does NOT set `sfExported` — so a symbol can be importable while its
|
||||
## declaration says otherwise. The NIF writer decides importability from
|
||||
## `sfExported` alone and therefore missed exactly these. `std/random` does it
|
||||
## (`proc initRand(): Rand` private, then `since (1, 5, 1): export initRand`),
|
||||
## which is why `--ic:on` could not compile anything that reached
|
||||
## `std/tempfiles` — `initRand()` was undeclared in the importer.
|
||||
result = @[]
|
||||
for s in g.ifaces[m.position].interf.data:
|
||||
if s != nil and s.kind != skModule and sfExported notin s.flags and
|
||||
s.itemId.module == m.position:
|
||||
result.add s.itemId
|
||||
|
||||
proc someSym*(g: ModuleGraph; m: PSym; name: PIdent): PSym =
|
||||
let importHidden = optImportHidden in m.options
|
||||
if importHidden: ensureHiddenIface(g, m.position)
|
||||
result = strTableGet(g.ifaces[m.position].interfSelect(importHidden), name)
|
||||
|
||||
proc someSymAmb*(g: ModuleGraph; m: PSym; name: PIdent; amb: var bool): PSym =
|
||||
let importHidden = optImportHidden in m.options
|
||||
if importHidden: ensureHiddenIface(g, m.position)
|
||||
var ti: TIdentIter = default(TIdentIter)
|
||||
result = initIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden), name)
|
||||
if result != nil and nextIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden)) != nil:
|
||||
@@ -397,75 +271,29 @@ iterator procInstCacheItems*(g: ModuleGraph; s: PSym): PInstantiation =
|
||||
proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
|
||||
## returns the requested attached operation for type `t`. Can return nil
|
||||
## if no such operation exists.
|
||||
if g.attachedOps[op].contains(t.bindingId):
|
||||
result = g.attachedOps[op][t.bindingId]
|
||||
if g.attachedOps[op].contains(t.itemId):
|
||||
result = g.attachedOps[op][t.itemId]
|
||||
elif g.config.cmd in {cmdNifC, cmdM}:
|
||||
# Fall back to key-based lookup for NIF-loaded hooks
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
result = g.loadedOps[op].getOrDefault(key)
|
||||
#echo "fallback ", key, " ", op, " ", result
|
||||
when defined(icDbgHash):
|
||||
if result == nil and op == attachedDestructor:
|
||||
echo "HOOK MISS key=", key, " table.len=", g.loadedOps[op].len,
|
||||
" kind=", t.kind, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL")
|
||||
if key.len > 10:
|
||||
let probe = key[3 ..< min(key.len, 18)]
|
||||
for k in g.loadedOps[op].keys:
|
||||
if probe in k: echo " candidate: ", k
|
||||
else:
|
||||
result = nil
|
||||
|
||||
proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
|
||||
## we also need to record this to the packed module.
|
||||
# Key-based deduplication for opsLog: different type objects (e.g. canon vs
|
||||
# orig) can have different itemIds but the same structural key.
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
if g.inVMTransform > 0 and g.config.cmd == cmdM:
|
||||
# hook lifted while the VM compiles a routine body (closure-env types of
|
||||
# loaded routines): register it for in-process lookup but keep it out of
|
||||
# the serialized log — it is a process-local artifact whose type graph
|
||||
# references derived env-field syms that no module's NIF defines
|
||||
if g.loadedOps[op].getOrDefault(key) == nil:
|
||||
if not g.attachedOps[op].contains(t.itemId):
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
# Use key-based deduplication for opsLog because different type objects
|
||||
# (e.g. canon vs orig) can have different itemIds but same structural key
|
||||
if key notin g.loadedOps[op]:
|
||||
# Hooks should be written to the module where the type is defined,
|
||||
# not the module that triggered the registration
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: ownerModule, key: key, sym: value)
|
||||
g.loadedOps[op][key] = value
|
||||
g.attachedOps[op][t.bindingId] = value
|
||||
return
|
||||
let existing = g.loadedOps[op].getOrDefault(key)
|
||||
if existing == nil:
|
||||
# Stamp the entry with the module whose compilation produced the hook
|
||||
# (`module`), NOT the type's def module: each `nim m` is a separate
|
||||
# process, so a hook lifted while compiling a *downstream* module simply
|
||||
# does not exist in the def module's process — stamping it with the def
|
||||
# module produced a `LogEntry` that no module ever writes (the def
|
||||
# module's writer ran in another process that never lifted it; this
|
||||
# module's writer skips it because `op.module != thisModule`) and codegen
|
||||
# failed with "'=destroy' operator not found" (e.g. astdef's `TStrTable`,
|
||||
# whose destroy is first needed by modulegraphs). This holds for nominal
|
||||
# types as much as for generic/structural instances. Duplicate
|
||||
# registrations across lifting modules are reconciled deterministically
|
||||
# at load time (see the HookEntry replay in `replayStateChanges`).
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: module, key: key, sym: value)
|
||||
g.loadedOps[op][key] = value
|
||||
elif existing != value:
|
||||
# Re-registration replacing an earlier sym for the same key. This happens
|
||||
# legitimately: `createTypeBoundOps` first registers empty `symPrototype`
|
||||
# placeholders, then `produceSym` replaces them — in particular
|
||||
# `produceSymDistinctType` replaces a distinct type's placeholder with the
|
||||
# BASE type's hook (a `distinct string` uses string's `=sink`). The log
|
||||
# must follow the replacement, otherwise the NIF ships the dead,
|
||||
# empty-bodied prototype and codegen in another process calls a no-op
|
||||
# `=sink`/`=copy`, silently losing the value (e.g. `conf.projectPath`
|
||||
# ended up empty: "cannot open '/'").
|
||||
g.loadedOps[op][key] = value
|
||||
var updated = false
|
||||
for e in mitems(g.opsLog):
|
||||
if e.kind == HookEntry and e.op == op and e.key == key:
|
||||
e.sym = value
|
||||
e.module = module
|
||||
updated = true
|
||||
break
|
||||
if not updated:
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: module, key: key, sym: value)
|
||||
g.attachedOps[op][t.bindingId] = value
|
||||
g.attachedOps[op][t.itemId] = value
|
||||
|
||||
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
|
||||
## Overload that takes ItemId directly, useful for registering hooks from NIF index.
|
||||
@@ -473,7 +301,7 @@ proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttach
|
||||
|
||||
proc setAttachedOpPartial*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
|
||||
## we also need to record this to the packed module.
|
||||
g.attachedOps[op][t.bindingId] = value
|
||||
g.attachedOps[op][t.itemId] = value
|
||||
|
||||
proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) {.inline.} =
|
||||
discard
|
||||
@@ -486,6 +314,10 @@ proc addDispatchers*(g: ModuleGraph, value: PSym) =
|
||||
# TODO: add it for packed modules
|
||||
g.dispatchers.add value
|
||||
|
||||
iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[PSym]): PSym =
|
||||
for it in list.mitems:
|
||||
yield it
|
||||
|
||||
proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[PSym]) =
|
||||
# TODO: add it for packed modules
|
||||
g.methodsPerType[id] = methods
|
||||
@@ -495,122 +327,31 @@ proc addNifReplayAction*(g: ModuleGraph; module: int32; n: PNode) =
|
||||
g.nifReplayActions.mgetOrPut(module, @[]).add n
|
||||
|
||||
iterator getMethodsPerType*(g: ModuleGraph; t: PType): PSym =
|
||||
if g.methodsPerType.contains(t.bindingId):
|
||||
for it in mitems g.methodsPerType[t.bindingId]:
|
||||
if g.methodsPerType.contains(t.itemId):
|
||||
for it in mitems g.methodsPerType[t.itemId]:
|
||||
yield it
|
||||
|
||||
proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
|
||||
result = g.enumToStringProcs.getOrDefault(t.bindingId)
|
||||
if result == nil and g.config.cmd in {cmdNifC, cmdM}:
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
result = g.loadedEnumToStringProcs.getOrDefault(key)
|
||||
result = g.enumToStringProcs[t.itemId]
|
||||
assert result != nil
|
||||
|
||||
proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
|
||||
g.enumToStringProcs[t.bindingId] = value
|
||||
g.enumToStringProcs[t.itemId] = value
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
# Stamp with the module that owns the generated proc, not the enum's def
|
||||
# module: the def module's process may never have generated it (same
|
||||
# "written by nobody" failure as hook entries, see setAttachedOp).
|
||||
g.opsLog.add LogEntry(kind: EnumToStrEntry, module: value.itemId.module.int, key: key, sym: value)
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: value.itemId.module.int
|
||||
g.opsLog.add LogEntry(kind: EnumToStrEntry, module: ownerModule, key: key, sym: value)
|
||||
|
||||
iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
|
||||
if g.methodsPerGenericType.contains(t.bindingId):
|
||||
for it in mitems g.methodsPerGenericType[t.bindingId]:
|
||||
if g.methodsPerGenericType.contains(t.itemId):
|
||||
for it in mitems g.methodsPerGenericType[t.itemId]:
|
||||
yield (it[0], it[1])
|
||||
|
||||
proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) =
|
||||
g.methodsPerGenericType.mgetOrPut(t.bindingId, @[]).add (col, m)
|
||||
g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, m)
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, module: ownerModule, key: key, sym: m)
|
||||
|
||||
proc logMethodDef*(g: ModuleGraph; s: PSym) =
|
||||
## Log a method registration (`cgmeth.methodDef`) so that importers and
|
||||
## the backend can rebuild the dispatch buckets (`g.methods`) from the
|
||||
## NIF replay log — the serialized method ast carries its dispatcher sym
|
||||
## at `dispatcherPos`, so replay reuses the original dispatcher that all
|
||||
## call sites reference by name (see `registerLoadedMethod`).
|
||||
if g.config.cmd in {cmdNifC, cmdM}:
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, module: s.itemId.module.int,
|
||||
key: "", sym: s)
|
||||
|
||||
proc logCppMember*(g: ModuleGraph; s: PSym) =
|
||||
## Log a C++ `{.member.}`/`{.virtual.}`/`{.constructor.}` registration (and the
|
||||
## `importcpp` default-initializer flavour) so the NIF backend can rebuild
|
||||
## `memberProcsPerType`/`initializersPerType`, which live only in the sem
|
||||
## process. Without them the per-module backend emitted the struct WITHOUT its
|
||||
## in-class member declarations and the out-of-class definitions did not match
|
||||
## ("no declaration matches 'void Doo::memberProc()'").
|
||||
##
|
||||
## No type key: `replayCppMember` re-derives the type from the routine's
|
||||
## signature exactly as `semCppMember` does, so nothing has to survive the
|
||||
## round trip except the routine itself.
|
||||
if g.config.cmd in {cmdNifC, cmdM}:
|
||||
g.opsLog.add LogEntry(kind: CppMemberEntry, module: s.itemId.module.int,
|
||||
key: "", sym: s)
|
||||
|
||||
proc replayCppMember*(g: ModuleGraph; s: PSym) =
|
||||
## Inverse of `logCppMember`, mirroring `semstmts.semCppMember`'s derivation.
|
||||
if s == nil or s.typ == nil: return
|
||||
if sfImportc notin s.flags:
|
||||
var typ = if sfConstructor in s.flags: s.typ.returnType else: s.typ.firstParamType
|
||||
if typ != nil and typ.kind == tyPtr and sfConstructor notin s.flags:
|
||||
typ = typ.elementType
|
||||
if typ != nil and typ.kind == tyObject:
|
||||
let procs = addr g.memberProcsPerType.mgetOrPut(typ.bindingId, @[])
|
||||
for prc in procs[]:
|
||||
if prc == s: return
|
||||
procs[].add s
|
||||
else:
|
||||
let typ = s.typ.returnType
|
||||
if typ != nil and typ.kind == tyObject and
|
||||
typ.bindingId notin g.initializersPerType and s.typ.n != nil:
|
||||
# The default values sem read off the `nkIdentDefs` live on the param syms.
|
||||
var call = newTree(nkCall, newSymNode(s))
|
||||
var isInitializer = s.typ.n.len > 1
|
||||
for i in 1 ..< s.typ.n.len:
|
||||
let p = s.typ.n[i]
|
||||
if p.kind != nkSym or p.sym.ast == nil or p.sym.ast.kind == nkEmpty:
|
||||
isInitializer = false
|
||||
break
|
||||
call.add p.sym.ast
|
||||
if isInitializer:
|
||||
g.initializersPerType[typ.bindingId] = call
|
||||
|
||||
proc registerLoadedMethod*(g: ModuleGraph; m: PSym) =
|
||||
## Rebuild the dispatch buckets from a serialized method registration.
|
||||
## Buckets group the methods sharing a dispatcher; the dispatcher's BODY
|
||||
## does not exist in serialized form — `generateIfMethodDispatchers`
|
||||
## synthesizes it in the backend from the complete bucket.
|
||||
template dbg(msg: string) =
|
||||
when defined(icDbgMeth):
|
||||
echo "[icMeth] replay ", (if m != nil: m.name.s else: "nil"), ": ", msg
|
||||
if m == nil or sfDispatcher in m.flags: dbg "skip self/nil"; return
|
||||
if m.ast == nil or dispatcherPos >= m.ast.len:
|
||||
dbg "no dispatcherPos (len " & $(if m.ast != nil: m.ast.len else: -1) & ")"
|
||||
return
|
||||
let dn = m.ast[dispatcherPos]
|
||||
if dn == nil or dn.kind != nkSym or dn.sym == nil: dbg "empty dispatcher slot"; return
|
||||
let disp = dn.sym
|
||||
if sfDispatcher notin disp.flags: dbg "slot sym not a dispatcher"; return
|
||||
dbg "ok -> bucket of " & disp.name.s & "." & $disp.disamb
|
||||
for i in 0..<g.methods.len:
|
||||
if g.methods[i].dispatcher.itemId == disp.itemId:
|
||||
for existing in g.methods[i].methods:
|
||||
if existing.itemId == m.itemId: return
|
||||
g.methods[i].methods.add m
|
||||
return
|
||||
g.methods.add (methods: @[m], dispatcher: disp)
|
||||
|
||||
proc flushMethodReplays*(g: ModuleGraph) =
|
||||
## Builds the dispatch buckets from the method registrations collected
|
||||
## during module loading; called once every module of the program is
|
||||
## loaded (`nifbackend.generateCode`).
|
||||
for s in g.pendingMethodReplays:
|
||||
registerLoadedMethod(g, s)
|
||||
g.pendingMethodReplays.setLen 0
|
||||
|
||||
proc logGenericInstance*(g: ModuleGraph; inst: PSym) =
|
||||
## Log a generic instance so it gets written to the NIF file.
|
||||
## This is needed when generic instances are created during compile-time
|
||||
@@ -619,83 +360,9 @@ proc logGenericInstance*(g: ModuleGraph; inst: PSym) =
|
||||
let ownerModule = inst.itemId.module.int
|
||||
g.opsLog.add LogEntry(kind: GenericInstEntry, module: ownerModule, sym: inst)
|
||||
|
||||
|
||||
proc setInstanceDisamb*(g: ModuleGraph; inst, generic: PSym;
|
||||
concreteTypes: openArray[PType]) =
|
||||
## Under IC, replace a fresh routine instance's counter-based `disamb` with
|
||||
## a content-derived one: a hash of the generic's identity plus the
|
||||
## `typeKey` of every concrete type argument — exactly the identity the
|
||||
## instantiation cache compares. The instance's NIF name
|
||||
## `name.disamb.modsuffix` then differs only in the module suffix when the
|
||||
## same instantiation is made by different modules, which is the
|
||||
## prerequisite for cross-module generic-instance merging (and gives the
|
||||
## dce analysis its `offers` keys). The hash is computed once, here; it is
|
||||
## never recomputed — the value travels in the serialized `disamb` field.
|
||||
if g.config.cmd notin {cmdNifC, cmdM}: return
|
||||
if isDefined(g.config, "icNoInstKey"): return
|
||||
var key = generic.name.s
|
||||
key.add '.'
|
||||
key.addInt generic.disamb
|
||||
key.add '.'
|
||||
key.add modname(generic.itemId.module, g.config)
|
||||
for t in concreteTypes:
|
||||
key.add '|'
|
||||
key.add typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
let d = toMD5(key)
|
||||
var h = (int32(d[0]) or (int32(d[1]) shl 8) or (int32(d[2]) shl 16) or
|
||||
(int32(d[3] and 0x3F'u8) shl 24)) or InstanceDisambBit
|
||||
# Same-name hash collisions inside this process get probed to the next
|
||||
# free value; the loser stays correct (its name keeps the module suffix),
|
||||
# it merely won't merge cross-module.
|
||||
while true:
|
||||
let probe = (inst.name.id, h)
|
||||
if g.instDisambs.hasKey(probe):
|
||||
if g.instDisambs[probe] == inst.itemId: break
|
||||
h = if h == high(int32): InstanceDisambBit else: h + 1
|
||||
else:
|
||||
g.instDisambs[probe] = inst.itemId
|
||||
break
|
||||
inst.disamb = h
|
||||
|
||||
proc setHookDisamb*(g: ModuleGraph; hook: PSym; opName: string; typ: PType) =
|
||||
## Under IC, replace a synthesized hook's counter-based `disamb` with a
|
||||
## content-derived one: a hash of the operation name plus the `typeKey` of
|
||||
## the type it is bound to. Counter disambs renumber whenever an *earlier*
|
||||
## hook appears in a re-semmed module, so cached translation units keep
|
||||
## calling the old `_u<disamb>` C name while the regenerated producer
|
||||
## defines a new one — the hook flavor of the backend def-migration hole.
|
||||
## With a content-derived value the hook's NIF name (and hence its C name)
|
||||
## is stable as long as the type itself is unchanged.
|
||||
if g.config.cmd notin {cmdNifC, cmdM}: return
|
||||
if isDefined(g.config, "icNoHookKey"): return
|
||||
var key = opName
|
||||
key.add '|'
|
||||
key.add typeKey(typ, g.config, loadTypeCallback, loadSymCallback)
|
||||
let d = toMD5(key)
|
||||
var h = (int32(d[0]) or (int32(d[1]) shl 8) or (int32(d[2]) shl 16) or
|
||||
(int32(d[3] and 0x1F'u8) shl 24)) or HookDisambBit
|
||||
# Same-name hash collisions inside this process get probed to the next
|
||||
# free value (staying below InstanceDisambBit); the loser merely loses
|
||||
# cross-run name stability.
|
||||
while true:
|
||||
let probe = (hook.name.id, h)
|
||||
if g.instDisambs.hasKey(probe):
|
||||
if g.instDisambs[probe] == hook.itemId: break
|
||||
h = if h == InstanceDisambBit - 1'i32: HookDisambBit else: h + 1
|
||||
else:
|
||||
g.instDisambs[probe] = hook.itemId
|
||||
break
|
||||
hook.disamb = h
|
||||
|
||||
proc hasDisabledOp(g: ModuleGraph; t: PType; kind: TTypeAttachedOp): bool =
|
||||
let op = getAttachedOp(g, t, kind)
|
||||
result = op != nil and sfError in op.flags
|
||||
|
||||
proc hasDisabledAsgn*(g: ModuleGraph; t: PType): bool =
|
||||
result = hasDisabledOp(g, t, attachedAsgn)
|
||||
|
||||
proc hasDisabledDup*(g: ModuleGraph; t: PType): bool =
|
||||
result = hasDisabledOp(g, t, attachedDup)
|
||||
let op = getAttachedOp(g, t, attachedAsgn)
|
||||
result = op != nil and sfError in op.flags
|
||||
|
||||
proc copyTypeProps*(g: ModuleGraph; module: int; dest, src: PType) =
|
||||
for k in low(TTypeAttachedOp)..high(TTypeAttachedOp):
|
||||
@@ -710,49 +377,21 @@ proc loadCompilerProc*(g: ModuleGraph; name: string): PSym =
|
||||
when not defined(nimKochBootstrap):
|
||||
# Try to resolve from NIF for both cmdNifC and cmdM (which uses NIF files)
|
||||
if g.config.cmd in {cmdNifC, cmdM}:
|
||||
# First try system module (most compilerprocs are there).
|
||||
# Only consult the NIF if it actually exists: under nimsuggest's cold
|
||||
# cache (ideActive) system is compiled from source and has no NIF yet,
|
||||
# in which case the proc is already registered in-memory and the caller
|
||||
# found/falls back to it — so degrade to nil instead of asserting.
|
||||
# First try system module (most compilerprocs are there)
|
||||
let systemFileIdx = g.config.m.systemFileIdx
|
||||
if systemFileIdx != InvalidFileIdx and not g.withinSystem and
|
||||
fileExists(toNifFilename(g.config, systemFileIdx)):
|
||||
if systemFileIdx != InvalidFileIdx and not g.withinSystem:
|
||||
# Only try to load from NIF if the file exists (it may not during initial ic build)
|
||||
result = tryResolveCompilerProc(ast.program, name, systemFileIdx)
|
||||
if result != nil:
|
||||
strTableAdd(g.compilerprocs, result)
|
||||
return result
|
||||
|
||||
# `nim nifc`: a module loaded from a NIF is named by its mangled suffix
|
||||
# (`thrkxstl4`), not by its source name, and its file index resolves to
|
||||
# that suffix too — so the `"threadpool"` match below can never fire and
|
||||
# `spawn`, expanded at codegen time, died on `system module needs:
|
||||
# nimArgsPassingDone`. The backend loads the WHOLE program before
|
||||
# codegen starts, so just consult every loaded module's index; a miss is
|
||||
# final for the rest of the process (nothing more gets loaded) and is
|
||||
# remembered, because `getCompilerProc` is also used as a mere presence
|
||||
# probe and would otherwise rescan every index on every call.
|
||||
if g.config.cmd == cmdNifC:
|
||||
if name in g.missingCompilerProcs: return nil
|
||||
for moduleIdx in 0..<g.ifaces.len:
|
||||
let module = g.ifaces[moduleIdx].module
|
||||
if module == nil or module.position.FileIndex == systemFileIdx: continue
|
||||
if not fileExists(toNifFilename(g.config, module.position.FileIndex)):
|
||||
continue
|
||||
result = tryResolveCompilerProc(ast.program, name, module.position.FileIndex)
|
||||
if result != nil:
|
||||
strTableAdd(g.compilerprocs, result)
|
||||
return result
|
||||
g.missingCompilerProcs.incl name
|
||||
return nil
|
||||
|
||||
# Try threadpool module (some compilerprocs like FlowVar are there)
|
||||
# Find threadpool module by searching loaded modules
|
||||
for moduleIdx in 0..<g.ifaces.len:
|
||||
let module = g.ifaces[moduleIdx].module
|
||||
if module != nil and module.name.s == "threadpool":
|
||||
let threadpoolFileIdx = module.position.FileIndex
|
||||
if not fileExists(toNifFilename(g.config, threadpoolFileIdx)): break
|
||||
result = tryResolveCompilerProc(ast.program, name, threadpoolFileIdx)
|
||||
if result != nil:
|
||||
strTableAdd(g.compilerprocs, result)
|
||||
@@ -772,6 +411,10 @@ proc hash*(u: SigHash): Hash =
|
||||
|
||||
proc hash*(x: FileIndex): Hash {.borrow.}
|
||||
|
||||
template getPContext(): untyped =
|
||||
when c is PContext: c
|
||||
else: c.c
|
||||
|
||||
when defined(nimsuggest):
|
||||
template onUse*(info: TLineInfo; s: PSym; isGenericInstance = false) = discard
|
||||
template onDefResolveForward*(info: TLineInfo; s: PSym) = discard
|
||||
@@ -895,7 +538,6 @@ proc initModuleGraphFields(result: ModuleGraph) =
|
||||
result.emittedTypeInfo = initTable[string, FileIndex]()
|
||||
result.cachedFiles = newStringTable()
|
||||
result.cachedMods = initIntSet()
|
||||
result.hookClosure = initIntSet()
|
||||
|
||||
proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
|
||||
result = ModuleGraph()
|
||||
@@ -926,15 +568,6 @@ proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
proc moduleOpenForCodegen*(g: ModuleGraph; m: FileIndex): bool {.inline.} =
|
||||
result = true
|
||||
|
||||
proc recordIcImplDep*(g: ModuleGraph; s: PSym) =
|
||||
## NeedsImpl edge tracking, see `icImplDeps`. Called from the compile-time
|
||||
## body consumption sites (vmgen's proc compilation, the getImpl opcodes).
|
||||
## Own-module and group-member entries are filtered out when the `.edges`
|
||||
## sidecar is written.
|
||||
if g.config.cmd == cmdM and s != nil and s.kind in routineKinds and
|
||||
s.itemId.module >= 0 and not isBackendMinted(s.itemId):
|
||||
g.icImplDeps.incl module(s.itemId).int
|
||||
|
||||
proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b
|
||||
|
||||
proc addDep*(g: ModuleGraph; m: PSym, dep: FileIndex) =
|
||||
@@ -1017,140 +650,9 @@ proc needsCompilation*(g: ModuleGraph, fileIdx: FileIndex): bool =
|
||||
|
||||
proc getBody*(g: ModuleGraph; s: PSym): PNode {.inline.} =
|
||||
result = s.ast[bodyPos]
|
||||
if result != nil and nfLazyBody in result.flags and forceLazyBodyHook != nil:
|
||||
# Sanctioned body-access gate (see astdef.bodyPos): materialize the deferred
|
||||
# IC body so callers may safely touch `.sons` directly, not only via `len`.
|
||||
forceLazyBodyHook(result)
|
||||
assert result != nil
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
proc registerLoadedHooks*(g: ModuleGraph; logOps: seq[LogEntry]) =
|
||||
let mainSuffix = getMainModuleSuffix(ast.program)
|
||||
for x in logOps:
|
||||
# A dependency's NIF may carry hooks whose syms belong to the module we
|
||||
# are compiling fresh (e.g. a stale NIF of that very module written by an
|
||||
# earlier in-process compilation). Loading those would collide with the
|
||||
# freshly semchecked hook declarations.
|
||||
if mainSuffix.len > 0 and
|
||||
cachedModuleSuffix(g.config, x.sym.itemId.module.FileIndex) == mainSuffix:
|
||||
continue
|
||||
case x.kind
|
||||
of HookEntry:
|
||||
# The same structural hook may be serialized by several instantiating
|
||||
# modules (a generic/structural instance has no single def site, so each
|
||||
# using module owns its copy). Pick one deterministic program-wide winner
|
||||
# by the smaller owning-module name, so every lookup resolves to the same
|
||||
# sym regardless of module load order.
|
||||
let existing = g.loadedOps[x.op].getOrDefault(x.key)
|
||||
if existing == nil or
|
||||
cachedModuleSuffix(g.config, x.sym.itemId.module.FileIndex) <
|
||||
cachedModuleSuffix(g.config, existing.itemId.module.FileIndex):
|
||||
g.loadedOps[x.op][x.key] = x.sym
|
||||
of EnumToStrEntry:
|
||||
g.loadedEnumToStringProcs[x.key] = x.sym
|
||||
of CppMemberEntry:
|
||||
replayCppMember(g, x.sym)
|
||||
of MethodEntry:
|
||||
# only `methodDef` registrations (empty key) rebuild dispatch
|
||||
# buckets; the `addMethodToGeneric` flavor (typeKey key) announces
|
||||
# the uninstantiated generic method, which must never enter a
|
||||
# bucket (methodsPerGenericType replay is still a todo).
|
||||
# Under `nim nifc` the replay is deferred: building a bucket forces
|
||||
# the method's body, and a body loaded mid `loadModuleDependencies`
|
||||
# registers modules it references in a different path context than
|
||||
# the lazy loads during codegen do (`flushMethodReplays`).
|
||||
if x.key.len == 0:
|
||||
if g.config.cmd == cmdNifC:
|
||||
g.pendingMethodReplays.add x.sym
|
||||
else:
|
||||
registerLoadedMethod(g, x.sym)
|
||||
else:
|
||||
discard
|
||||
|
||||
proc loadTransitiveHooks(g: ModuleGraph; deps: seq[ModuleSuffix]) =
|
||||
## Registers the serialized hooks (and enum-to-string procs) of every module
|
||||
## in the import closure of `deps`. Deliberately does NOT use
|
||||
## `moduleFromNifFile`: that would register the dep as a fully loaded module
|
||||
## and a later direct import of it would then skip `replayStateChanges`.
|
||||
var stack = deps
|
||||
var interf = initStrTable()
|
||||
var interfHidden = initStrTable()
|
||||
while stack.len > 0:
|
||||
let suffix = stack.pop()
|
||||
var isKnownFile = false
|
||||
let fileIdx = g.config.registerNifSuffix(string suffix, isKnownFile)
|
||||
if not g.hookClosure.containsOrIncl(fileIdx.int):
|
||||
# `SkipInterfaceTables`: `interf`/`interfHidden` here are scratch tables
|
||||
# shared by every iteration and never read — this module is a
|
||||
# dep-of-a-dep, so none of its symbols are visible to the module being
|
||||
# semchecked. Building them called `loadSymFromIndexEntry` for every
|
||||
# index entry of every closure member.
|
||||
let precomp = loadNifModule(ast.program, suffix, interf, interfHidden,
|
||||
{SkipInterfaceTables})
|
||||
registerLoadedHooks(g, precomp.logOps)
|
||||
# Record this transitively-loaded module so the sem driver applies its
|
||||
# VM-level load effects (macro-cache replay + `{.compileTime.}` global init)
|
||||
# exactly as for a direct import — see `pendingNifInit`. A throwaway module
|
||||
# symbol (same shape as moduleFromNifFile's) gives the drain an idgen/info
|
||||
# context; it is not registered, so a later direct import still loads fully.
|
||||
if g.config.cmd == cmdM:
|
||||
let m = PSym(kindImpl: skModule, itemId: itemId(int32(fileIdx), 0'i32),
|
||||
name: getIdent(g.cache, splitFile(toFullPath(g.config, fileIdx)).name),
|
||||
infoImpl: newLineInfo(fileIdx, 1, 1), positionImpl: int(fileIdx))
|
||||
setOwner(m, getPackage(g.config, g.cache, fileIdx))
|
||||
g.pendingNifInit.add (m, precomp.topLevel)
|
||||
# Rebuild generic TYPE- and PROC-instance offers across the WHOLE closure,
|
||||
# not just direct imports (`moduleFromNifFile`). An instance is frozen at
|
||||
# the FIRST module to create it (in a scope where its body's symbols
|
||||
# resolve unambiguously); a consumer many imports away must REUSE it rather
|
||||
# than re-instantiate in its own scope, which may resolve a body symbol
|
||||
# differently — a divergent `compiles()`-dependent array bound (SSZ
|
||||
# `HashArray[8192, Gwei]`, type offer), or an ambiguous unqualified ident
|
||||
# leaked from an unrelated import (`fromRaw` -> `SkRawPublicKeySize` from
|
||||
# both `secp` and `secp256k1`, proc offer). Direct-only rebuild left the
|
||||
# deep offer invisible when the clean instance lives a transitive hop away.
|
||||
for off in precomp.typeOffers:
|
||||
g.typeInstCache.mgetOrPut(off.generic.itemId, @[]).add off.inst
|
||||
for off in precomp.genericOffers:
|
||||
g.procInstCache.mgetOrPut(off.generic.itemId, @[]).add PInstantiation(
|
||||
sym: off.inst, concreteTypes: off.concreteTypes,
|
||||
genericParamsCount: off.genericParamsCount, compilesId: 0)
|
||||
for d in precomp.deps: stack.add d
|
||||
|
||||
proc materializeReexportedModule(g: ModuleGraph; mname, msuffix: string): PSym =
|
||||
## A re-exported MODULE (`import x; export x`) acts as a qualifier in the
|
||||
## re-exporting module's interface (`asmm.x86.nd`). Reconstruct a module
|
||||
## symbol for it and make its interface tables available for qualified
|
||||
## lookup (`someSym` reads `g.ifaces[position]`) — WITHOUT registering
|
||||
## the module: `Iface.module` stays nil so a later direct import still
|
||||
## takes the full load path (replayStateChanges etc.).
|
||||
var isKnown = false
|
||||
let fIdx = g.config.registerNifSuffix(msuffix, isKnown)
|
||||
if fIdx.int >= g.ifaces.len: setLen(g.ifaces, fIdx.int + 1)
|
||||
if g.ifaces[fIdx.int].module != nil and
|
||||
g.ifaces[fIdx.int].module.name.s == mname:
|
||||
# properly registered already (directly imported earlier): reuse it
|
||||
return g.ifaces[fIdx.int].module
|
||||
result = PSym(kindImpl: skModule, itemId: itemId(int32(fIdx), 0'i32),
|
||||
name: getIdent(g.cache, mname),
|
||||
infoImpl: newLineInfo(fIdx, 1, 1),
|
||||
positionImpl: int(fIdx))
|
||||
setOwner(result, getPackage(g.config, g.cache, fIdx))
|
||||
if g.ifaces[fIdx.int].module == nil and
|
||||
not g.icQualIfaces.containsOrIncl(fIdx.int):
|
||||
var interf = initStrTable()
|
||||
var interfHidden = initStrTable()
|
||||
let precomp = loadNifModule(ast.program, ModuleSuffix(msuffix),
|
||||
interf, interfHidden, {})
|
||||
# chains: the re-exported module may itself re-export modules
|
||||
for (n2, s2) in precomp.reexportedModules:
|
||||
let inner = materializeReexportedModule(g, n2, s2)
|
||||
if inner != nil:
|
||||
strTableAdd(interf, inner)
|
||||
g.ifaces[fIdx.int].interf = interf
|
||||
g.ifaces[fIdx.int].interfHidden = interfHidden
|
||||
g.ifaces[fIdx.int].hiddenPending = true
|
||||
|
||||
proc moduleFromNifFile*(g: ModuleGraph; fileIdx: FileIndex;
|
||||
flags: set[LoadFlag] = {}): PrecompiledModule =
|
||||
## Returns 'nil' if the module needs to be recompiled.
|
||||
@@ -1159,141 +661,42 @@ when not defined(nimKochBootstrap):
|
||||
if not fileExists(toNifFilename(g.config, fileIdx)):
|
||||
return PrecompiledModule(module: nil)
|
||||
|
||||
# NOTE: direction-(c) experiment (refuse to NIF-serve include-bearing modules
|
||||
# under ideActive, forcing a source compile) is disabled — it reproduces the
|
||||
# known sibling-resolution corruption (system.string -> excpt.nim:746). The
|
||||
# cold-include *discovery* scan (scanIncludeGraph) stays; the round-trip
|
||||
# fidelity of included symbols is the separate, still-open loader problem.
|
||||
when false:
|
||||
if g.config.ideActive and not g.withinSystem and
|
||||
fileIdx != g.config.m.systemFileIdx and
|
||||
nifModuleHasIncludes(g.config, fileIdx):
|
||||
return PrecompiledModule(module: nil)
|
||||
|
||||
# Create module symbol
|
||||
let filename = AbsoluteFile toFullPath(g.config, fileIdx)
|
||||
|
||||
let m = PSym(
|
||||
kindImpl: skModule,
|
||||
itemId: itemId(int32(fileIdx), 0'i32),
|
||||
itemId: ItemId(module: int32(fileIdx), item: 0'i32),
|
||||
name: getIdent(g.cache, splitFile(filename).name),
|
||||
infoImpl: newLineInfo(fileIdx, 1, 1),
|
||||
positionImpl: int(fileIdx))
|
||||
setOwner(m, getPackage(g.config, g.cache, fileIdx))
|
||||
# Register module in graph
|
||||
registerModule(g, m)
|
||||
# ... and, in the BACKEND, bind its NIF name to THIS symbol before anything
|
||||
# in the file is decoded, so the loader never mints a second `skModule` for
|
||||
# it (see `registerModuleSelfSym`). Backend-only: under `nim m` a module is
|
||||
# loaded for its INTERFACE, and re-pointing the owner slot of every loaded
|
||||
# symbol at the freshly built module sym changes what sem sees for an
|
||||
# imported routine — `times.toDateTimeByWeek` then lost its inferred
|
||||
# `raises` and the importer failed with "can raise an unlisted exception".
|
||||
if g.config.cmd == cmdNifC:
|
||||
registerModuleSelfSym(ast.program, cachedModuleSuffix(g.config, fileIdx), m)
|
||||
|
||||
result = loadNifModule(ast.program, fileIdx,
|
||||
g.ifaces[fileIdx.int].interf,
|
||||
g.ifaces[fileIdx.int].interfHidden, flags)
|
||||
# The hidden-only half was not built; `ensureHiddenIface` will, if asked.
|
||||
g.ifaces[fileIdx.int].hiddenPending = true
|
||||
result.module = m
|
||||
# Restore the module symbol's persisted flags (see ast2nif `(modflags)`);
|
||||
# `cgen.genTopLevelStmt` gates the destructor pass on `sfInjectDestructors`.
|
||||
if (result.moduleFlags and ModFlagInjectDestructors) != 0:
|
||||
m.incl sfInjectDestructors
|
||||
for (mname, msuffix) in result.reexportedModules:
|
||||
let ms = materializeReexportedModule(g, mname, msuffix)
|
||||
if ms != nil:
|
||||
strTableAdd(g.ifaces[fileIdx.int].interf, ms)
|
||||
# Re-establish include->module mapping so nimsuggest's `parentModule` can map
|
||||
# a query in an included file back to this (NIF-loaded) module and recompile
|
||||
# it, exactly as it does for a from-source module. Without this the include
|
||||
# relationship is invisible for NIF-served modules.
|
||||
for incPath in result.includes:
|
||||
g.addIncludeDep(fileIdx, fileInfoIdx(g.config, AbsoluteFile incPath))
|
||||
|
||||
# Rebuild `procInstCache` from this module's generic-instance OFFERS so a
|
||||
# consumer's `genericCacheGet` finds the instance and SKIPS re-running
|
||||
# `instantiateBody` in its own module scope (which lacks symbols visible only
|
||||
# at the generic's definition site — see ast2nif's `(offer …)`).
|
||||
for off in result.genericOffers:
|
||||
g.procInstCache.mgetOrPut(off.generic.itemId, @[]).add PInstantiation(
|
||||
sym: off.inst, concreteTypes: off.concreteTypes,
|
||||
genericParamsCount: off.genericParamsCount, compilesId: 0)
|
||||
|
||||
# Rebuild `typeInstCache` from this module's generic TYPE-instance OFFERS so a
|
||||
# consumer's `searchInstTypes` reuses the baked instance (e.g. an SSZ
|
||||
# `HashArray` whose array bound depends on import-scope-sensitive `compiles()`)
|
||||
# rather than re-instantiating it with a divergent bound — see ast2nif's
|
||||
# `(toffer …)`. Keyed by the generic body sym's itemId, as `searchInstTypes`.
|
||||
for off in result.typeOffers:
|
||||
g.typeInstCache.mgetOrPut(off.generic.itemId, @[]).add off.inst
|
||||
|
||||
# Mark module as cached
|
||||
g.cachedMods.incl fileIdx.int
|
||||
g.hookClosure.incl fileIdx.int
|
||||
|
||||
# Register hooks from NIF index with the module graph
|
||||
registerLoadedHooks(g, result.logOps)
|
||||
for x in result.logOps:
|
||||
case x.kind
|
||||
of HookEntry:
|
||||
g.loadedOps[x.op][x.key] = x.sym
|
||||
of ConverterEntry:
|
||||
g.ifaces[fileIdx.int].converters.add x.sym
|
||||
of PureEnumEntry:
|
||||
# rebuild the pure-enum list (source path: `addPureEnum`) so importers can
|
||||
# offer this loaded `{.pure.}` enum's fields as the restricted pure-enum
|
||||
# fallback (`importPureEnumFields`).
|
||||
g.ifaces[fileIdx.int].pureEnums.add x.sym
|
||||
of MethodEntry:
|
||||
discard "dispatch buckets already rebuilt by registerLoadedHooks"
|
||||
discard "todo"
|
||||
of EnumToStrEntry:
|
||||
discard "todo"
|
||||
of GenericInstEntry:
|
||||
raiseAssert "GenericInstEntry should not be in the NIF index"
|
||||
of HookEntry, EnumToStrEntry, CppMemberEntry:
|
||||
discard "already done by registerLoadedHooks"
|
||||
# Register methods per type from NIF index
|
||||
discard "todo"
|
||||
# `nim m` loads only its *direct* imports through this proc, but a hook for
|
||||
# a structural type (e.g. `=destroy` for `seq[PNode]`) lives in the NIF of
|
||||
# whichever module first lifted it — possibly a dependency of a dependency
|
||||
# that the current module never imports directly. Walk the whole import
|
||||
# closure so every serialized hook is visible. (Codegen, `nim nifc`, already
|
||||
# walks the closure in nifbackend.loadModuleDependencies.)
|
||||
if g.config.cmd == cmdM:
|
||||
loadTransitiveHooks(g, result.deps)
|
||||
# Record the directly-loaded module for the same VM-level load effects as its
|
||||
# transitive deps (`pendingNifInit`). AFTER loadTransitiveHooks so the drain
|
||||
# applies deps before the dependent (macro-cache order).
|
||||
g.pendingNifInit.add (m, result.topLevel)
|
||||
|
||||
proc isModuleFile(g: ModuleGraph; fileIdx: FileIndex): bool =
|
||||
let i = fileIdx.int32
|
||||
i >= 0 and i < g.ifaces.len and g.ifaces[i].module != nil
|
||||
|
||||
proc registerIncluderFromNif*(g: ModuleGraph; fileIdx: FileIndex): bool =
|
||||
## Targeted cold-include discovery for nimsuggest: scan the nimcache NIFs
|
||||
## (`scanIncludeGraph`) for a module whose include-set contains *this* file
|
||||
## and register only that single include->module edge in `inclToMod`, so a
|
||||
## query inside the include file resolves its includer via `parentModule`.
|
||||
##
|
||||
## Deliberately targeted: registering *every* include relationship (i.e. also
|
||||
## `system`'s own `include`s) eagerly assigns FileIndexes and pollutes
|
||||
## `inclToMod`, which perturbs the NIF line-info decode of unrelated modules
|
||||
## (`system.string` then resolves into `excpt.nim`). Touch nothing but the
|
||||
## one edge we need.
|
||||
let target = toFullPath(g.config, fileIdx)
|
||||
for (includer, includes) in scanIncludeGraph(g.config):
|
||||
for incFile in includes:
|
||||
if cmpPaths(incFile, target) == 0:
|
||||
g.addIncludeDep(fileInfoIdx(g.config, AbsoluteFile includer), fileIdx)
|
||||
return true
|
||||
result = false
|
||||
|
||||
proc needsIncludeScan*(g: ModuleGraph; fileIdx: FileIndex): bool =
|
||||
## True when `fileIdx` is neither a known module of its own nor an
|
||||
## already-known include file — i.e. a cold-opened file whose includer we
|
||||
## must still discover via `registerIncluderFromNif`.
|
||||
not g.isModuleFile(fileIdx) and not g.inclToMod.hasKey(fileIdx)
|
||||
|
||||
proc configComplete*(g: ModuleGraph) =
|
||||
#rememberStartupConfig(g.startupPackedConfig, g.config)
|
||||
@@ -1322,16 +725,7 @@ proc getPackage*(graph: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
|
||||
proc belongsToStdlib*(graph: ModuleGraph, sym: PSym): bool =
|
||||
## Check if symbol belongs to the 'stdlib' package.
|
||||
# Compare the package *name* (an interned ident), not the package symbol's
|
||||
# `.id`. Under per-module IC (`nim m`) the system module is loaded from a NIF
|
||||
# in a process that does not compile it from source, so its package symbol is
|
||||
# reconstructed with a fresh `.id` that no longer matches the freshly-interned
|
||||
# package of a stdlib module compiled standalone here — making the old id
|
||||
# comparison wrongly report `false` and inject `--import`ed modules into the
|
||||
# stdlib. Both are canonically named `stdlib` (lib/stdlib.nimble); in a normal
|
||||
# `nim c` build (system compiled from source) the ids match too, so this is a
|
||||
# no-op there.
|
||||
sym.getPackageSymbol.name.id == graph.systemModule.getPackageSymbol.name.id
|
||||
sym.getPackageSymbol.getPackageId == graph.systemModule.getPackageId
|
||||
|
||||
proc fileSymbols*(graph: ModuleGraph, fileIdx: FileIndex): SuggestFileSymbolDatabase =
|
||||
result = graph.suggestSymbols.getOrDefault(fileIdx, newSuggestFileSymbolDatabase(fileIdx, optIdeExceptionInlayHints in graph.config.globalOptions))
|
||||
|
||||
@@ -32,7 +32,7 @@ proc newModule*(graph: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
let filename = AbsoluteFile toFullPath(graph.config, fileIdx)
|
||||
# We cannot call ``newSym`` here, because we have to circumvent the ID
|
||||
# mechanism, which we do in order to assign each module a persistent ID.
|
||||
result = PSym(kindImpl: skModule, itemId: itemId(int32(fileIdx), 0'i32),
|
||||
result = PSym(kindImpl: skModule, itemId: ItemId(module: int32(fileIdx), item: 0'i32),
|
||||
name: getModuleIdent(graph, filename),
|
||||
infoImpl: newLineInfo(fileIdx, 1, 1))
|
||||
if not isNimIdentifier(result.name.s):
|
||||
|
||||
@@ -24,6 +24,10 @@ template instLoc*(): InstantiationInfo = instantiationInfo(-2, fullPaths = true)
|
||||
template toStdOrrKind(stdOrr): untyped =
|
||||
if stdOrr == stdout: stdOrrStdout else: stdOrrStderr
|
||||
|
||||
proc toLowerAscii(a: var string) {.inline.} =
|
||||
for c in mitems(a):
|
||||
if isUpperAscii(c): c = char(uint8(c) xor 0b0010_0000'u8)
|
||||
|
||||
proc flushDot*(conf: ConfigRef) =
|
||||
## safe to call multiple times
|
||||
let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr
|
||||
@@ -79,8 +83,7 @@ proc canonicalCase(path: var string) {.inline.} =
|
||||
## the idea is to only use this for checking whether a path is already in
|
||||
## the table but otherwise keep the original case
|
||||
when FileSystemCaseSensitive: discard
|
||||
else:
|
||||
for c in mitems(path): c = toLowerAscii(c)
|
||||
else: toLowerAscii(path)
|
||||
|
||||
proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
|
||||
var
|
||||
@@ -122,25 +125,12 @@ proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
|
||||
var dummy: bool = false
|
||||
result = fileInfoIdx(conf, filename, dummy)
|
||||
|
||||
proc expandOrPseudo(filename: string): AbsoluteFile =
|
||||
# `expandFilename` raises OSError when the path does not exist on disk. That is
|
||||
# fine for a real source path, but a macro can legitimately set a node's
|
||||
# line-info file to a name that has no file behind it — e.g. the `???` sentinel
|
||||
# produced by `toFilename` for a NIF-loaded node whose `fileIndex` is unknown
|
||||
# (FileIndex(-1)). Falling back to the raw name lets the `AbsoluteFile` overload
|
||||
# register it as a pseudo-path (like `command line`/`stdin`) instead of crashing
|
||||
# the whole `nim m` child with an unhandled OSError.
|
||||
try:
|
||||
result = AbsoluteFile expandFilename(filename)
|
||||
except OSError:
|
||||
result = AbsoluteFile filename
|
||||
|
||||
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile; isKnownFile: var bool): FileIndex =
|
||||
fileInfoIdx(conf, expandOrPseudo(filename.string), isKnownFile)
|
||||
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), isKnownFile)
|
||||
|
||||
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile): FileIndex =
|
||||
var dummy: bool = false
|
||||
fileInfoIdx(conf, expandOrPseudo(filename.string), dummy)
|
||||
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), dummy)
|
||||
|
||||
proc registerNifSuffix*(conf: ConfigRef; suffix: string; isKnownFile: var bool): FileIndex =
|
||||
result = conf.m.filenameToIndexTbl.getOrDefault(suffix, InvalidFileIdx)
|
||||
@@ -348,7 +338,7 @@ proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
|
||||
|
||||
## This is used for 'nim dump' etc. where we don't have nimsuggest
|
||||
## support.
|
||||
#if conf.ideActive and optCDebug notin gGlobalOptions: return
|
||||
#if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
|
||||
let sep = if msgNoUnitSep notin flags: conf.unitSep else: ""
|
||||
if not isNil(conf.writelnHook) and msgSkipHook notin flags:
|
||||
conf.writelnHook(s & sep)
|
||||
@@ -454,8 +444,8 @@ To create a stacktrace, rerun compilation with './koch temp $1 <file>', see $2 f
|
||||
|
||||
proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string, ignoreMsg: bool) =
|
||||
if msg in fatalMsgs:
|
||||
if conf.ideActive: log(s)
|
||||
if not conf.ideActive or msg != errFatal:
|
||||
if conf.cmd == cmdIdeTools: log(s)
|
||||
if conf.cmd != cmdIdeTools or msg != errFatal:
|
||||
quit(conf, msg)
|
||||
if msg >= errMin and msg <= errMax or
|
||||
(msg in warnMin..hintMax and msg in conf.warningAsErrors and not ignoreMsg):
|
||||
@@ -469,7 +459,7 @@ proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string,
|
||||
raiseRecoverableError(s)
|
||||
else:
|
||||
quit(conf, msg)
|
||||
elif eh == doAbort and not conf.ideActive:
|
||||
elif eh == doAbort and conf.cmd != cmdIdeTools:
|
||||
quit(conf, msg)
|
||||
elif eh == doRaise:
|
||||
raiseRecoverableError(s)
|
||||
@@ -500,7 +490,7 @@ proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
|
||||
info = context.info
|
||||
|
||||
proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
|
||||
msg >= errGenerated and conf.ideActive and optIdeDebug notin conf.globalOptions
|
||||
msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
|
||||
|
||||
proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
|
||||
conf.m.fileInfos[fileIdx.int32].lines.add line
|
||||
@@ -521,9 +511,6 @@ proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
|
||||
## 1-based index (matches editor line numbers); 1st line is for i.line = 1
|
||||
## last valid line is `numLines` inclusive
|
||||
if i.fileIndex.int32 < 0: return ""
|
||||
# line 0 means "unknown": nodes synthesized from an IC-loaded template or
|
||||
# macro body carry no source position.
|
||||
if i.line.int < 1: return ""
|
||||
let num = numLines(conf, i.fileIndex)
|
||||
# can happen if the error points to EOF:
|
||||
if i.line.int > num: return ""
|
||||
@@ -658,7 +645,7 @@ proc warningDeprecated*(conf: ConfigRef, info: TLineInfo = gCmdLineInfo, msg = "
|
||||
message(conf, info, warnDeprecated, msg)
|
||||
|
||||
proc internalErrorImpl(conf: ConfigRef; info: TLineInfo, errMsg: string, info2: InstantiationInfo) =
|
||||
if (conf.ideActive or conf.cmd == cmdCheck) and conf.structuredErrorHook.isNil: return
|
||||
if conf.cmd in {cmdIdeTools, cmdCheck} and conf.structuredErrorHook.isNil: return
|
||||
writeContext(conf, info)
|
||||
liMessage(conf, info, errInternal, errMsg, doAbort, info2)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -28,13 +28,10 @@ import
|
||||
commands, options, msgs, extccomp, main, idents, lineinfos, cmdlinehelper,
|
||||
pathutils, modulegraphs
|
||||
|
||||
from ast2nif import registerNifAstTags
|
||||
from icconfig import ensureIcConfig
|
||||
|
||||
from std/browsers import openDefaultBrowser
|
||||
from nodejs import findNodeJs
|
||||
|
||||
when defined(tinyc): # == hasTinyCBackend; spelled out for the IC dep scanner
|
||||
when hasTinyCBackend:
|
||||
import tccgen
|
||||
|
||||
when defined(profiler) or defined(memProfiler):
|
||||
@@ -99,11 +96,6 @@ proc getNimRunExe(conf: ConfigRef): string =
|
||||
result = ""
|
||||
|
||||
proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
# NIF tag registration must not depend on module init order — the IC-built
|
||||
# compiler orders module init calls differently and the top-level
|
||||
# `registerTag` initializers then ran against a not-yet-initialized pool,
|
||||
# corrupting every written NIF (see registerNifAstTags).
|
||||
registerNifAstTags()
|
||||
let self = NimProg(
|
||||
supportsStdinFile: true,
|
||||
processCmdLine: processCmdLine
|
||||
@@ -115,14 +107,6 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
|
||||
self.processCmdLineAndProjectPath(conf)
|
||||
|
||||
# `nim ic` driver: ensure the precompiled config exists (produced by a separate
|
||||
# `nim icconfig` process, skipped when nothing changed) BEFORE config loading,
|
||||
# so `loadConfigs` replays it instead of re-parsing the `nim.cfg` chain — the
|
||||
# driver runs on the exact same config its children will. See icconfig.nim.
|
||||
when not defined(nimKochBootstrap):
|
||||
if conf.cmd in {cmdIc, cmdTrack} or isIcDriver(conf):
|
||||
ensureIcConfig(conf)
|
||||
|
||||
var graph = newModuleGraph(cache, conf)
|
||||
if not self.loadConfigsAndProcessCmdLine(cache, conf, graph):
|
||||
return
|
||||
@@ -134,14 +118,9 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
if conf.selectedGC == gcUnselected:
|
||||
if conf.backend in {backendC, backendCpp, backendObjc} or
|
||||
(conf.cmd in cmdDocLike and conf.backend != backendJs) or
|
||||
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM, cmdTrack}:
|
||||
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM}:
|
||||
initOrcDefines(conf)
|
||||
|
||||
if conf.selectedStrings == stringSso and
|
||||
conf.selectedGC notin {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
rawMessage(conf, errGenerated,
|
||||
"--strings:sso requires --mm:arc, --mm:orc, --mm:yrc, or --mm:atomicArc")
|
||||
|
||||
mainCommand(graph)
|
||||
if conf.hasHint(hintGCStats): echo(GC_getStatistics())
|
||||
#echo(GC_getStatistics())
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import
|
||||
llstream, commands, msgs, lexer, ast,
|
||||
options, idents, wordrecg, lineinfos, pathutils, scriptconfig, icconfig
|
||||
options, idents, wordrecg, lineinfos, pathutils, scriptconfig
|
||||
|
||||
import std/[os, strutils, strtabs]
|
||||
|
||||
@@ -246,16 +246,6 @@ proc getSystemConfigPath*(conf: ConfigRef; filename: RelativeFile): AbsoluteFile
|
||||
|
||||
proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef; idgen: IdGenerator) =
|
||||
setDefaultLibpath(conf)
|
||||
# The `nim ic` driver and its `nim m`/`nim nifc` children replay the precompiled
|
||||
# config (produced once by a separate `nim icconfig` process — see
|
||||
# `icconfig.ensureIcConfig`, which sets `icPreparsedConfig` for the driver
|
||||
# before this runs; the children get it as a forwarded `--icPreparsedConfig`
|
||||
# argument) instead of re-reading the `nim.cfg` chain and re-running
|
||||
# `config.nims` in the VM. A missing/format-incompatible artifact returns false:
|
||||
# fall through to a normal parse (this is also the path the `nim icconfig`
|
||||
# producer itself takes, since it runs with no `icPreparsedConfig`).
|
||||
if conf.icPreparsedConfig.len > 0 and applyIcConfig(conf, conf.icPreparsedConfig):
|
||||
return
|
||||
template readConfigFile(path) =
|
||||
let configPath = path
|
||||
conf.currentConfigDir = configPath.splitFile.dir.string
|
||||
@@ -316,7 +306,7 @@ proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef; idgen:
|
||||
if conf.cmd == cmdNimscript:
|
||||
showHintConf()
|
||||
conf.configFiles.setLen 0
|
||||
if not conf.ideActive and conf.cmd notin {cmdCheck, cmdDump}:
|
||||
if conf.cmd notin {cmdIdeTools, cmdCheck, cmdDump}:
|
||||
if conf.cmd == cmdNimscript:
|
||||
runNimScriptIfExists(conf.projectFull, isMain = true)
|
||||
else:
|
||||
|
||||
@@ -29,42 +29,6 @@ const
|
||||
|
||||
nimEnableCovariance* = defined(nimEnableCovariance)
|
||||
|
||||
icFormatVersion* = "38"
|
||||
## Version of the IC cache format (the sem-NIF module layout written by
|
||||
## ast2nif.nim plus the iface/impl/edges side files). Bump it whenever
|
||||
## that layout changes: `commandIc` wipes a nimcache whose `ic.version`
|
||||
## stamp differs, instead of letting a newer reader mis-parse records
|
||||
## written by an older compiler (nifmake's rebuild check is mtime-only
|
||||
## and knows nothing about format changes).
|
||||
## v2: iface cookie hashes routine SIGNATURES only (no inline-semantics
|
||||
## body folding); body access now records a NeedsImpl edge instead. A v1
|
||||
## cache mixes body-sensitive and body-insensitive cookies, so it must be
|
||||
## wiped rather than warm-rebuilt.
|
||||
## v3: added the `.s.deps` sidecar (real post-sem imports) and switched the
|
||||
## macro-generated-import discovery from `icmissing.txt` to it.
|
||||
## v4: backend C-name scheme change — the module suffix is now the trailing
|
||||
## token (`name_u<disamb>__<suffix>`, was `name__<suffix>_u<disamb>`), so
|
||||
## cached `.c.nif` artifacts hold incompatible names and must be wiped.
|
||||
## v5: data definitions (consts, RTTI) are now wrapped in droppable `'d'`
|
||||
## cdef directives with an always-present extern declaration, so the
|
||||
## per-module merge stage can assign them a single owner; old `.c.nif`
|
||||
## artifacts lack the wrappers.
|
||||
## v6: `signatureHash`/`hashType` of a builtin type class (`object`, `tuple`,
|
||||
## `proc`, ...) no longer mixes in the placeholder son's process-local type
|
||||
## id, so its hash is stable across the NIF boundary (was breaking
|
||||
## nim-serialization's auto-serialization lookup under IC). The sem-NIF
|
||||
## macrocache entries and baked generic-instance bodies hold the old hashes.
|
||||
## v7 (=31): anonymous wrapper types (`var T`, `lent T`, `sink T`, tuples)
|
||||
## are named by their CONTENT instead of `itemId.item`, the module-wide
|
||||
## type-mint counter (see ast2nif.CanonTypeKinds). Old caches name the same
|
||||
## type differently, so every `.s.bif` reference would dangle.
|
||||
## v8 (=32): the same for `tyProc`, except that a proc type which is a
|
||||
## routine's SIGNATURE is named after that routine rather than by content
|
||||
## (see ast2nif.sigRoutineOf). Renames types, so old caches dangle again.
|
||||
## v9 (=33): and for the per-module `int`/`float` LITERAL COPIES (see
|
||||
## ast2nif.CanonLitCopyKinds), the last mover that broke a build outright
|
||||
## (`symbol has no offset` out of a cached `.t.bif`). Renames types again.
|
||||
|
||||
type # please make sure we have under 32 options
|
||||
# (improves code efficiency a lot!)
|
||||
TOption* = enum # **keep binary compatible**
|
||||
@@ -150,7 +114,6 @@ type # please make sure we have under 32 options
|
||||
optDocRaw # for documentation: Don't render markdown for JSON output
|
||||
optItaniumMangle # mangling follows the Itanium spec
|
||||
optCompress # turn on AST compression by converting it to NIF
|
||||
optGenBif # generate semantic BIF alongside ordinary code generation
|
||||
optWithinConfigSystem # we still compile within the configuration system
|
||||
|
||||
TGlobalOptions* = set[TGlobalOption]
|
||||
@@ -194,6 +157,7 @@ type
|
||||
cmdCheck # semantic checking for whole project
|
||||
cmdM # only compile a single
|
||||
cmdParse # parse a single file (for debugging)
|
||||
cmdIdeTools # ide tools (e.g. nimsuggest)
|
||||
cmdNimscript # evaluate nimscript
|
||||
cmdDoc0
|
||||
cmdDoc # convert .nim doc comments to HTML
|
||||
@@ -215,8 +179,6 @@ type
|
||||
cmdCompileToNif
|
||||
cmdNifC # generate C code from NIF files
|
||||
cmdIc # generate .build.nif for nifmake
|
||||
cmdIcConfig # `nim ic`'s precompiled-config producer (writes ic_config.cfg.nif)
|
||||
cmdTrack # `nim track --def/--usages`: IC frontend build + NIF scan for IDE queries
|
||||
|
||||
const
|
||||
cmdBackends* = {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
|
||||
@@ -297,14 +259,6 @@ type
|
||||
## Old transformation for closures in JS backend
|
||||
noPanicOnExcept
|
||||
## don't panic on bare except
|
||||
procParamTypeBackendAliases
|
||||
## Keep the old proc type compatibility rules that ignore backend
|
||||
## c type aliases.
|
||||
injectedSymbolRedefinition
|
||||
## Allow a template to inject a symbol *definition* that is then emitted
|
||||
## more than once (e.g. a `typed` argument captured by a `{.dirty.}`
|
||||
## template and re-emitted). This is a redefinition and rejected by
|
||||
## default; enabling this restores the old, unsound behavior. See #25693.
|
||||
|
||||
SymbolFilesOption* = enum
|
||||
disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest
|
||||
@@ -313,10 +267,6 @@ type
|
||||
ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccBcc, ccVcc,
|
||||
ccTcc, ccEnv, ccIcl, ccIcc, ccClangCl, ccHipcc, ccNvcc
|
||||
|
||||
StringsMode* = enum
|
||||
stringDefault = "default"
|
||||
stringSso = "sso"
|
||||
|
||||
ExceptionSystem* = enum
|
||||
excNone, # no exception system selected yet
|
||||
excSetjmp, # setjmp based exception handling
|
||||
@@ -411,73 +361,17 @@ type
|
||||
evalMacroCounter*: int
|
||||
exitcode*: int8
|
||||
cmd*: Command # raw command parsed as enum
|
||||
ideActive*: bool # serving IDE tooling (nimsuggest): collect suggestions and
|
||||
# keep going after errors. Decoupled from `cmd` so the IDE
|
||||
# server can run under any compilation mode (cmdCheck, cmdM).
|
||||
ideImportsFromNif*: bool # nimsuggest: load the unchanged import closure from
|
||||
# precompiled NIF (run under cmdM) instead of recompiling it
|
||||
# from source (cmdCheck). IC is opt-in: default off (cmdCheck);
|
||||
# `--ideImports:nif` opts in.
|
||||
cmdInput*: string # input command
|
||||
projectIsCmd*: bool # whether we're compiling from a command input
|
||||
implicitCmd*: bool # whether some flag triggered an implicit `command`
|
||||
selectedGC*: TGCMode # the selected GC (+)
|
||||
exc*: ExceptionSystem
|
||||
selectedStrings*: StringsMode
|
||||
hintProcessingDots*: bool # true for dots, false for filenames
|
||||
verbosity*: int # how verbose the compiler is
|
||||
numberOfProcessors*: int # number of processors
|
||||
lastCmdTime*: float # when caas is enabled, we measure each command
|
||||
symbolFiles*: SymbolFilesOption
|
||||
ic*: bool # whether ic is enabled
|
||||
icGroup*: HashSet[string] # under `nim m`: absolute paths of the modules in
|
||||
# this strongly-connected import group. They are all
|
||||
# compiled from source in one process (so mutual
|
||||
# recursion resolves in-memory) and each gets its NIF
|
||||
# written, instead of being loaded from a precompiled
|
||||
# NIF. See `compiler/deps.nim` (SCC grouping).
|
||||
icProject*: string # under `nim m`/`nim nifc`: absolute path of the
|
||||
# ORIGINAL project file. The child's own project file
|
||||
# is the module being compiled, which would make that
|
||||
# module's package the "main package" and unfilter
|
||||
# foreign-package diagnostics; the real project
|
||||
# restores whole-program filtering semantics.
|
||||
icPreparsedConfig*: string # under the `nim ic` driver and its `nim m`/`nim nifc`
|
||||
# children: path of the precompiled config artifact.
|
||||
# When set, `loadConfigs` replays the recorded
|
||||
# config-file switches from it instead of re-reading
|
||||
# the `nim.cfg` chain and re-running `config.nims`
|
||||
# (which the VM makes expensive) per process. The
|
||||
# artifact itself is produced by a separate
|
||||
# `nim icconfig` process (see `cmdIcConfig`).
|
||||
icConfigOut*: string # under `nim icconfig`: the path to write the
|
||||
# precompiled config artifact to (set via `--o`).
|
||||
icConfigSwitches*: seq[tuple[switch, arg: string]]
|
||||
# the config-file (`passPP`) switches applied while
|
||||
# loading config, in order. Recorded by every nim
|
||||
# process; only the `ic` driver serialises them.
|
||||
# Path-search switches are excluded — the driver
|
||||
# forwards the resolved `searchPaths` as `--path`.
|
||||
icBackendStage*: string # under `nim nifc`: which stage of the per-module
|
||||
# backend this invocation runs — "cg" (codegen one
|
||||
# module to its `.c.nif`), "merge" (global liveness
|
||||
# + owner assignment across all `.c.nif`), "emit"
|
||||
# (render one module's `.c` from its `.c.nif` + the
|
||||
# merge decision), "link" (cc + link every emitted
|
||||
# `.c`). Empty = whole-program backend (load all,
|
||||
# codegen+DCE+cc+link in one process). The stages
|
||||
# are wired as nifmake rules by `deps.nim`'s backend
|
||||
# build file. See `compiler/nifbackend.nim`.
|
||||
icBackendModules*: seq[string]
|
||||
# under `nim nifc` with icBackendStage in
|
||||
# {lower,cg,emit}: the NIF module suffixes this
|
||||
# invocation processes — its BATCH. One entry is
|
||||
# the per-module fan-out; several share one process
|
||||
# and therefore ONE dependency-closure load between
|
||||
# them, which is the whole point (see
|
||||
# `nifbackend.loadDepClosure`). Every other module
|
||||
# is loaded only so types resolve; its definitions
|
||||
# are referenced extern. Empty = the main module.
|
||||
spellSuggestMax*: int # max number of spelling suggestions for typos
|
||||
|
||||
cppDefines*: HashSet[string] # (*)
|
||||
@@ -524,12 +418,6 @@ type
|
||||
lastMsgWasDot*: set[StdOrrKind] # the last compiler message was a single '.'
|
||||
projectMainIdx*: FileIndex # the canonical path id of the main module
|
||||
projectMainIdx2*: FileIndex # consider merging with projectMainIdx
|
||||
isMainModule*: bool # `nim m`/IC only: whether the single module being
|
||||
# semantically checked is the program's real entry point.
|
||||
# Under IC every module is compiled via `nim m` (which sets
|
||||
# `sfMainModule` so the module writes its own NIF), so
|
||||
# `sfMainModule` can no longer answer `isMainModule`. The IC
|
||||
# build file passes `--isMainModule:on` for the root module.
|
||||
command*: string # the main command (e.g. cc, check, scan, etc)
|
||||
commandArgs*: seq[string] # any arguments after the main command
|
||||
commandLine*: string
|
||||
@@ -686,7 +574,6 @@ proc newConfigRef*(): ConfigRef =
|
||||
arcToExpand: newStringTable(modeStyleInsensitive),
|
||||
m: initMsgConfig(),
|
||||
cppDefines: initHashSet[string](),
|
||||
icGroup: initHashSet[string](),
|
||||
headerFile: "", features: {}, legacyFeatures: {},
|
||||
configVars: newStringTable(modeStyleInsensitive),
|
||||
symbols: newStringTable(modeStyleInsensitive),
|
||||
@@ -709,7 +596,6 @@ proc newConfigRef*(): ConfigRef =
|
||||
command: "", # the main command (e.g. cc, check, scan, etc)
|
||||
commandArgs: @[], # any arguments after the main command
|
||||
commandLine: "",
|
||||
ideImportsFromNif: false, # IC opt-in; see `--ideImports`
|
||||
implicitImports: @[], # modules that are to be implicitly imported
|
||||
implicitIncludes: @[], # modules that are to be implicitly included
|
||||
docSeeSrcUrl: "",
|
||||
@@ -763,7 +649,6 @@ proc isDefined*(conf: ConfigRef; symbol: string): bool =
|
||||
of "x86": result = conf.target.targetCPU == cpuI386
|
||||
of "itanium": result = conf.target.targetCPU == cpuIa64
|
||||
of "x8664": result = conf.target.targetCPU == cpuAmd64
|
||||
of "wasm": result = conf.target.targetCPU in {cpuWasm32, cpuWasm64}
|
||||
of "posix", "unix":
|
||||
result = conf.target.targetOS in {osLinux, osMorphos, osSkyos, osIrix, osPalmos,
|
||||
osQnx, osAtari, osAix,
|
||||
@@ -811,20 +696,8 @@ template quitOrRaise*(conf: ConfigRef, msg = "") =
|
||||
else:
|
||||
quit(msg) # quits with QuitFailure
|
||||
|
||||
proc icReuseSemLowering*(conf: ConfigRef): bool {.inline.} =
|
||||
## When ON, the per-module `lower` backend stage REUSES the VM/CT lowering that
|
||||
## sem cached in the `.s.nif` 2-way-body slot (the non-IC single-lowering
|
||||
## semantics) instead of re-deriving the transform. Default OFF: the backend
|
||||
## re-derives every body from the pristine semchecked body (simpler; allowed by
|
||||
## the 2026-06-27 spec that VM-requested frontend transforms need not influence
|
||||
## the backend). The switch exists so caching can be restored if a target (e.g.
|
||||
## Nimbus) depends on the cached lowering being reused, not re-derived. See
|
||||
## doc/ic_backend_simplify.md §6b.
|
||||
isDefined(conf, "icReuseSemLowering")
|
||||
|
||||
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.ideActive or conf.cmd in cmdDocLike
|
||||
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in cmdDocLike + {cmdIdeTools}
|
||||
proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc
|
||||
proc usesSso*(conf: ConfigRef): bool {.inline.} = conf.selectedStrings == stringSso
|
||||
|
||||
template compilationCachePresent*(conf: ConfigRef): untyped =
|
||||
false
|
||||
@@ -946,28 +819,9 @@ proc getOsCacheDir(): string =
|
||||
else:
|
||||
result = getHomeDir() / genSubDir.string
|
||||
|
||||
proc isIcDriver*(conf: ConfigRef): bool =
|
||||
## True for `nim c --ic:on` / `nim cpp --ic:on`: this process is the `nim ic`
|
||||
## DRIVER (it builds the nifmake graph and spawns the per-module children),
|
||||
## not a compilation. `nim ic` itself keeps its own `cmdIc` branch.
|
||||
conf.ic and conf.cmd in {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC}
|
||||
|
||||
proc icCFileExt*(conf: ConfigRef): string =
|
||||
## The extension the per-module backend gives a module's translation unit.
|
||||
## Mirrors `cgen.getCFile` at BACKEND granularity, which is all the `nim ic`
|
||||
## driver can know: it DECLARES every module's `.c`/`.cpp` output to nifmake
|
||||
## without loading a single module, so a per-module `{.compile: cpp.}`
|
||||
## (`sfCompileToCpp`) is out of reach — and `nim cpp` selects the backend for
|
||||
## the whole program anyway.
|
||||
case conf.backend
|
||||
of backendCpp: ".nim.cpp"
|
||||
of backendObjc: ".nim.m"
|
||||
else: ".nim.c"
|
||||
|
||||
proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir =
|
||||
proc nimcacheSuffix(conf: ConfigRef): string =
|
||||
if conf.ideActive: "_nimsuggest" # dedicated cache, never shared with `nim c`
|
||||
elif conf.cmd == cmdCheck: "_check"
|
||||
if conf.cmd == cmdCheck: "_check"
|
||||
elif isDefined(conf, "release") or isDefined(conf, "danger"): "_r"
|
||||
else: "_d"
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ proc getPackage*(conf: ConfigRef; cache: IdentCache; fileIdx: FileIndex): PSym =
|
||||
## * `modulegraphs.getPackage`
|
||||
let
|
||||
filename = AbsoluteFile toFullPath(conf, fileIdx)
|
||||
name = getIdent(cache, splitFile(filename).name)
|
||||
info = newLineInfo(fileIdx, 1, 1)
|
||||
pkgName = getPackageName(conf, filename.string)
|
||||
pkgIdent = getIdent(cache, pkgName)
|
||||
|
||||
@@ -54,10 +54,7 @@ import
|
||||
|
||||
when not defined(nimCustomAst):
|
||||
import ast
|
||||
when defined(nimCustomAst):
|
||||
# NOTE: explicit negated `when` rather than `else:` — nifler's dep scanner
|
||||
# guards `when`/`elif` imports with their condition but emits `else:` imports
|
||||
# unconditionally, which would wrongly schedule this module under `nim ic`.
|
||||
else:
|
||||
import plugins / customast
|
||||
|
||||
import std/strutils
|
||||
@@ -2244,17 +2241,14 @@ proc parseTypeClassParam(p: var Parser): PNode =
|
||||
|
||||
proc parseTypeClass(p: var Parser): PNode =
|
||||
#| conceptParam = ('var' | 'out' | 'ptr' | 'ref' | 'static' | 'type')? symbol
|
||||
#| conceptDecl = 'concept' (conceptParam ^* ',' (pragma)?)? ('of' typeDesc ^* ',')?
|
||||
#| conceptDecl = 'concept' conceptParam ^* ',' (pragma)? ('of' typeDesc ^* ',')?
|
||||
#| &IND{>} stmt
|
||||
result = newNodeP(nkTypeClassTy, p)
|
||||
getTok(p)
|
||||
if p.tok.tokType == tkComment:
|
||||
skipComment(p, result)
|
||||
|
||||
if p.tok.tokType == tkOf and p.tok.indent < 0:
|
||||
# new-styled `concept of A, B` on the same line as `concept`
|
||||
result.add(p.emptyNode)
|
||||
elif p.tok.indent < 0:
|
||||
if p.tok.indent < 0:
|
||||
var args = newNodeP(nkArgList, p)
|
||||
result.add(args)
|
||||
args.add(p.parseTypeClassParam)
|
||||
@@ -2280,10 +2274,9 @@ proc parseTypeClass(p: var Parser): PNode =
|
||||
result.add(p.emptyNode)
|
||||
if p.tok.tokType == tkComment:
|
||||
skipComment(p, result)
|
||||
# an initial IND{>} HAS to follow, unless this concept inherits requirements:
|
||||
# an initial IND{>} HAS to follow:
|
||||
if not realInd(p):
|
||||
let hasParents = result[2].kind != nkEmpty
|
||||
if result.isNewStyleConcept and not hasParents:
|
||||
if result.isNewStyleConcept:
|
||||
parMessage(p, "routine expected, but found '$1' (empty new-styled concepts are not allowed)", p.tok)
|
||||
result.add(p.emptyNode)
|
||||
else:
|
||||
|
||||
@@ -6,18 +6,16 @@ import sem, cgen, modulegraphs, ast, llstream, parser, msgs,
|
||||
when not defined(nimKochBootstrap):
|
||||
import vmdef
|
||||
import ast2nif
|
||||
import nifstreams
|
||||
import "../dist/nimony/src/lib" / bitabs
|
||||
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
|
||||
|
||||
import pipelineutils
|
||||
import icprof
|
||||
|
||||
import ../dist/checksums/src/checksums/sha1
|
||||
|
||||
when not defined(leanCompiler):
|
||||
import jsgen, docgen2
|
||||
|
||||
import std/[syncio, objectdollar, assertions, tables, strutils, strtabs, sets, intsets]
|
||||
import std/[syncio, objectdollar, assertions, tables, strutils, strtabs]
|
||||
import renderer
|
||||
import ic/replayer
|
||||
|
||||
@@ -169,8 +167,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
s = stream
|
||||
graph.interactive = stream.kind == llsStdIn
|
||||
var topLevelStmts =
|
||||
if {optCompress, optGenBif} * graph.config.globalOptions != {} or
|
||||
graph.config.cmd == cmdM:
|
||||
if optCompress in graph.config.globalOptions or graph.config.cmd == cmdM:
|
||||
newNodeI(nkStmtList, module.info)
|
||||
else:
|
||||
nil
|
||||
@@ -246,31 +243,9 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
# For cmdM: only write NIF for the main module, not for imported modules
|
||||
# (imported modules should be loaded from existing NIF files). Members of the
|
||||
# current strongly-connected import group (`--icGroup`) are the exception:
|
||||
# they are compiled from source here, so each must write its own NIF.
|
||||
let shouldWriteNif =
|
||||
if graph.config.errorCounter > 0:
|
||||
# Never persist an artifact built from erroneous AST. `nim m` does exit
|
||||
# non-zero, but its outputs would still land on disk NEWER than their
|
||||
# inputs, so nifmake sees the rule as satisfied on the next run: the
|
||||
# build then "succeeds" from a poisoned NIF — a silently wrong binary,
|
||||
# or an internal error once codegen meets an `nkError` body. Leaving the
|
||||
# outputs missing keeps the rule dirty so it re-fires and re-reports.
|
||||
false
|
||||
elif graph.config.ideActive:
|
||||
# nimsuggest (cmdM): persist NIF for cleanly-compiled, SAVED modules so
|
||||
# later queries load them instead of recompiling. Never persist the
|
||||
# actively edited buffer (it may hold unsaved/incomplete code) nor a
|
||||
# module that failed to compile — that would poison the cache.
|
||||
graph.config.cmd == cmdM and graph.config.errorCounter == 0 and
|
||||
graph.config.m.fileInfos[module.position].dirtyFile.isEmpty
|
||||
else:
|
||||
({optCompress, optGenBif} * graph.config.globalOptions != {}) or
|
||||
(graph.config.cmd == cmdM and
|
||||
(sfMainModule in module.flags or
|
||||
(graph.config.icGroup.len > 0 and
|
||||
toFullPath(graph.config, module.position.FileIndex) in graph.config.icGroup)))
|
||||
# (imported modules should be loaded from existing NIF files)
|
||||
let shouldWriteNif = (optCompress in graph.config.globalOptions) or
|
||||
(graph.config.cmd == cmdM and sfMainModule in module.flags)
|
||||
if shouldWriteNif and not graph.config.isDefined("nimscript"):
|
||||
topLevelStmts.add finalNode
|
||||
# Collect replay actions from both pragma computations and VM state diff
|
||||
@@ -284,150 +259,16 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
if m == module:
|
||||
replayActions.add n
|
||||
|
||||
# NeedsImpl edge recording: which modules' bodies this process consumed
|
||||
# at compile time (VM/getImpl). For an --icGroup cycle every member gets
|
||||
# the union; intra-group entries are filtered by the writer.
|
||||
var implDeps: seq[int] = @[]
|
||||
for id in graph.icImplDeps: implDeps.add id
|
||||
# Generic-instance OFFERS: every instance THIS module created, so a
|
||||
# consumer reuses it rather than re-instantiating in its own scope (which
|
||||
# cannot see symbols visible only at the generic's definition site — e.g.
|
||||
# a distinct type's `==`). See ast2nif.writeNifModule / moduleFromNifFile.
|
||||
var genericOffers: seq[tuple[generic, inst: PSym;
|
||||
concreteTypes: seq[PType]; genericParamsCount: int]] = @[]
|
||||
for genItemId, instList in graph.procInstCache:
|
||||
for inst in instList:
|
||||
if inst.sym != nil and inst.sym.itemId.module == module.position and
|
||||
inst.sym.instantiatedFrom != nil and inst.compilesId == 0:
|
||||
# `concreteTypes` is pre-sized to `paramsLen+gp.len`; a tail slot can
|
||||
# stay nil (e.g. fewer materialized params than `paramsLen`). Such an
|
||||
# offer can't be serialized — skip it (the consumer re-instantiates,
|
||||
# the prior behaviour) rather than emit a nil type reference.
|
||||
var hasNil = false
|
||||
for ct in inst.concreteTypes:
|
||||
if ct == nil: hasNil = true; break
|
||||
if not hasNil:
|
||||
genericOffers.add (inst.sym.instantiatedFrom, inst.sym,
|
||||
inst.concreteTypes, inst.genericParamsCount)
|
||||
# Generic TYPE-instance OFFERS: every `tyGenericInst` THIS module created,
|
||||
# so a consumer reuses its baked structure (array bounds etc.) rather than
|
||||
# re-instantiating with a scope-divergent bound. See ast2nif.writeNifModule.
|
||||
var typeOffers: seq[tuple[generic: PSym; inst: PType]] = @[]
|
||||
for genItemId, instList in graph.typeInstCache:
|
||||
for inst in instList:
|
||||
if inst != nil and inst.itemId.module == module.position and
|
||||
inst.kidsLen > 0 and inst[0] != nil and
|
||||
inst[0].kind == tyGenericBody and inst[0].sym != nil:
|
||||
typeOffers.add (inst[0].sym, inst)
|
||||
# The module's REAL resolved direct imports (incl. macro/template-generated
|
||||
# ones with no surviving syntactic node). Passed to writeNifModule so the
|
||||
# NIF `deps` section is complete (the backend closure walk needs it), and
|
||||
# reused below for the `.s.deps` sidecar (frontend graph re-derivation).
|
||||
let resolvedImportDeps = graph.importDeps.getOrDefault(module.position.FileIndex, @[])
|
||||
# The frontend's highest used itemId (max of the sym and type counters):
|
||||
# the backend seeds its id minting ABOVE this so closure envs / RTTI hooks
|
||||
# never share a `toId` with a frontend sym/type. See ast2nif `(unusedid)`.
|
||||
let firstUnusedId = max(idgen.symId, idgen.typeId)
|
||||
var expansions: seq[(PSym, TLineInfo)] = @[]
|
||||
discard graph.nifExpansions.take(module.position.int32, expansions)
|
||||
# The module symbol's own backend-relevant flags. `sfInjectDestructors` is
|
||||
# set by sempass2 when the module's TOP-LEVEL statements need the
|
||||
# destructor pass; `moduleFromNifFile` builds a fresh module PSym, so
|
||||
# without persisting it `cgen.genTopLevelStmt` skipped
|
||||
# `injectDestructorCalls` and top-level locals were never destroyed.
|
||||
let moduleFlags =
|
||||
if sfInjectDestructors in module.flags: ModFlagInjectDestructors else: 0'i32
|
||||
timed tWriteNif:
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog,
|
||||
replayActions, implDeps, reexportedModuleSyms(graph, module),
|
||||
genericOffers, typeOffers, resolvedImportDeps, firstUnusedId,
|
||||
expansions, moduleFlags,
|
||||
reexportedLocalSyms(graph, module))
|
||||
# The module's REAL direct imports (incl. macro-generated) for `nim ic`'s
|
||||
# graph re-derivation; see ast2nif.writeSemDeps / semdata.addImportFileDep.
|
||||
var semDepPaths: seq[string] = @[]
|
||||
for f in resolvedImportDeps:
|
||||
semDepPaths.add toFullPath(graph.config, f)
|
||||
writeSemDeps(graph.config, module.position.int32, semDepPaths)
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog, replayActions)
|
||||
|
||||
result = true
|
||||
|
||||
proc loadedDefSym(defs: PNode): PSym =
|
||||
## The defined symbol of a let/var entry as it loads back from a NIF: the
|
||||
## section child is a bare `nkSym` (the `(sd …)` reference), but be defensive
|
||||
## about the from-source shapes too (`nkIdentDefs`, a pragma-wrapped name).
|
||||
case defs.kind
|
||||
of nkSym: result = defs.sym
|
||||
of nkPragmaExpr:
|
||||
result = if defs.len > 0: loadedDefSym(defs[0]) else: nil
|
||||
of nkIdentDefs, nkConstDef:
|
||||
result = if defs.len > 0: loadedDefSym(defs[0]) else: nil
|
||||
else: result = nil
|
||||
|
||||
proc initLoadedCompileTimeGlobals(graph: ModuleGraph; module: PSym; topLevel: PNode) =
|
||||
## Eagerly initialize the compile-time globals (`let/var {.compileTime.}`) of a
|
||||
## module restored from a NIF. In a normal sem these VM slots are filled by
|
||||
## `setupCompileTimeVar` (semstmts) as the section is semchecked; a NIF-loaded
|
||||
## module is never semchecked, so without this a macro or compile-time proc that
|
||||
## reads such a global finds a nil slot. The lazy `vmgen.genGlobalInit` fallback
|
||||
## is order-fragile across proc boundaries (it emits the init at the first
|
||||
## VM-gen'd reference, which need not be the first one executed), so the init has
|
||||
## to happen here, once, before any of the module's code can run. The symbol's
|
||||
## own `ast` is the `nkIdentDefs` (initializer included); re-wrap it in a section
|
||||
## exactly as semstmts does and hand it to the same evaluator.
|
||||
if topLevel == nil: return
|
||||
let idgen = idGeneratorFromModule(module)
|
||||
for stmt in topLevel:
|
||||
if stmt.kind notin {nkLetSection, nkVarSection}: continue
|
||||
for defs in stmt:
|
||||
let s = loadedDefSym(defs)
|
||||
if s != nil and s.kind in {skLet, skVar} and
|
||||
{sfCompileTime, sfGlobal} <= s.flags and
|
||||
s.ast != nil and s.ast.kind == nkIdentDefs:
|
||||
var sect = newNodeI(stmt.kind, s.info)
|
||||
sect.add s.ast
|
||||
setupCompileTimeVar(module, idgen, graph, sect)
|
||||
|
||||
proc finalizeLoadedModules(graph: ModuleGraph) =
|
||||
## Apply the VM-level load effects of every module just loaded from a NIF —
|
||||
## direct import OR dep-of-a-dep, both collected in `graph.pendingNifInit` by the
|
||||
## loader (modulegraphs.moduleFromNifFile / loadTransitiveHooks). This is the ONE
|
||||
## place that knows what loading a module does to global VM state, so a
|
||||
## transitively-reached module (which never passes through this proc's caller)
|
||||
## gets identical treatment. Modules are in dependency order (deps before
|
||||
## dependents), which is the correct macro-cache replay order.
|
||||
## 1. macro-cache replay: std/macrocache put/inc/add/incl recorded in the
|
||||
## module's top level (pragma replay actions are a backend concern, skipped).
|
||||
## 2. eager `{.compileTime.}` global init (see initLoadedCompileTimeGlobals).
|
||||
## To add a new per-load effect, extend this proc — do not add a parallel buffer.
|
||||
if graph.pendingNifInit.len == 0: return
|
||||
for (m, topLevel) in graph.pendingNifInit:
|
||||
if topLevel == nil: continue
|
||||
var replayList = newNodeI(nkStmtList, m.info)
|
||||
for n in topLevel:
|
||||
if n.kind == nkReplayAction and n.len >= 1 and n[0].kind == nkStrLit and
|
||||
n[0].strVal in ["put", "inc", "add", "incl"]:
|
||||
replayList.add n
|
||||
if replayList.len > 0:
|
||||
replayStateChanges(m, graph, replayList)
|
||||
initLoadedCompileTimeGlobals(graph, m, topLevel)
|
||||
graph.pendingNifInit.setLen 0
|
||||
|
||||
proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags; fromModule: PSym = nil): PSym =
|
||||
var flags = flags
|
||||
if fileIdx == graph.config.projectMainIdx2: flags.incl sfMainModule
|
||||
result = graph.getModule(fileIdx)
|
||||
|
||||
template processModuleAux(moduleStatus) =
|
||||
when defined(icDbg):
|
||||
block:
|
||||
let dbgf = open("/tmp/defdbg.txt", fmAppend)
|
||||
dbgf.writeLine toFullPath(graph.config, fileIdx) &
|
||||
" nimStackTraceOverride=" & $isDefined(graph.config, "nimStackTraceOverride") &
|
||||
" nimscript=" & $isDefined(graph.config, "nimscript") &
|
||||
" optCompress=" & $(optCompress in graph.config.globalOptions) &
|
||||
" cmd=" & $graph.config.cmd
|
||||
dbgf.close()
|
||||
onProcessing(graph, fileIdx, moduleStatus, fromModule = fromModule)
|
||||
var s: PLLStream = nil
|
||||
if sfMainModule in flags:
|
||||
@@ -437,57 +278,27 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
if result == nil:
|
||||
when not defined(nimKochBootstrap):
|
||||
# For cmdM: load imports from NIF files (but compile the main module from source)
|
||||
# Skip when withinSystem is true (compiling system.nim itself).
|
||||
# Also skip for members of the current strongly-connected import group
|
||||
# (`--icGroup`): those are mutually recursive with the main module and have
|
||||
# no precompiled NIF yet, so they must be compiled from source in this same
|
||||
# process (falling through below) — that resolves the cycle in-memory, the
|
||||
# same way the non-incremental compiler handles recursive module imports.
|
||||
# Skip when withinSystem is true (compiling system.nim itself)
|
||||
if graph.config.cmd == cmdM and
|
||||
sfMainModule notin flags and
|
||||
not graph.withinSystem and
|
||||
not graph.config.isDefined("nimscript") and
|
||||
(graph.config.icGroup.len == 0 or
|
||||
toFullPath(graph.config, fileIdx) notin graph.config.icGroup):
|
||||
not graph.config.isDefined("nimscript"):
|
||||
let precomp = moduleFromNifFile(graph, fileIdx)
|
||||
if precomp.module == nil:
|
||||
if graph.config.ideActive:
|
||||
# nimsuggest bootstrap: this import has no precompiled NIF yet (cold
|
||||
# cache, or it was invalidated). Don't error — fall through to the
|
||||
# source-compile path below; the pass-close emits a fresh NIF so the
|
||||
# next query loads it instead of recompiling.
|
||||
discard
|
||||
else:
|
||||
let nifPath = toNifFilename(graph.config, fileIdx)
|
||||
# Macro-generated imports (e.g. chronicles' parseStmt("import
|
||||
# chronicles/textlines") driven by the chronicles_sinks define) are
|
||||
# invisible to the static scanner, so this module's NIF was never
|
||||
# built. The importer already recorded this import via
|
||||
# addImportFileDep, so flush every module's `.s.deps`: `nim ic` reads
|
||||
# it, re-derives the graph with the missing node + edge, and reruns
|
||||
# the frontend. We still error — this process cannot finish sem
|
||||
# without the import — but the discovery is structured data now, not
|
||||
# a side-channel file.
|
||||
for importer, deps in graph.importDeps.pairs:
|
||||
var paths: seq[string] = @[]
|
||||
for f in deps: paths.add toFullPath(graph.config, f)
|
||||
writeSemDeps(graph.config, importer.int32, paths)
|
||||
globalError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for import: " & toFullPath(graph.config, fileIdx) &
|
||||
" (expected: " & nifPath & ")")
|
||||
return nil # Don't fall through to compile from source
|
||||
let nifPath = toNifFilename(graph.config, fileIdx)
|
||||
globalError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for import: " & toFullPath(graph.config, fileIdx) &
|
||||
" (expected: " & nifPath & ")")
|
||||
return nil # Don't fall through to compile from source
|
||||
else:
|
||||
# Module successfully loaded from NIF file - use it and skip processing
|
||||
result = precomp.module
|
||||
if sfSystemModule in flags:
|
||||
graph.systemModule = result
|
||||
partialInitModule(result, graph, fileIdx, AbsoluteFile(toFullPath(graph.config, fileIdx)))
|
||||
# Apply the VM-level load effects of this module AND every dep it pulled in
|
||||
# (moduleFromNifFile recorded them all in graph.pendingNifInit): macro-cache
|
||||
# replay (else a NIF-loaded module's macro cache is lost — e.g.
|
||||
# nim-serialization flavor registration) and eager `{.compileTime.}` global
|
||||
# init. Uniform for direct and transitive deps — see finalizeLoadedModules.
|
||||
finalizeLoadedModules(graph)
|
||||
# Replay state changes from the loaded NIF module
|
||||
if result.ast != nil:
|
||||
replayStateChanges(result, graph)
|
||||
return result # Return early, don't process from source
|
||||
let path = toFullPath(graph.config, fileIdx)
|
||||
let filename = AbsoluteFile path
|
||||
@@ -553,14 +364,7 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
|
||||
let projectFile = if projectFileIdx == InvalidFileIdx: conf.projectMainIdx else: projectFileIdx
|
||||
conf.projectMainIdx2 = projectFile
|
||||
|
||||
var packSym = getPackage(graph, projectFile)
|
||||
if graph.config.cmd in {cmdM, cmdNifC} and graph.config.icProject.len > 0:
|
||||
# per-module IC children: the process' project file is the MODULE being
|
||||
# compiled, which would make its package the "main package" and unfilter
|
||||
# foreign-package diagnostics (a vendored package's hintAsError promotion
|
||||
# then aborts builds the whole-program compilation accepts). Use the
|
||||
# original project, forwarded by deps.nim via --icproject.
|
||||
packSym = getPackage(graph, fileInfoIdx(graph.config, AbsoluteFile graph.config.icProject))
|
||||
let packSym = getPackage(graph, projectFile)
|
||||
graph.config.mainPackageId = packSym.getPackageId
|
||||
graph.importStack.add projectFile
|
||||
|
||||
@@ -571,32 +375,16 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
|
||||
elif graph.config.cmd == cmdM:
|
||||
# For cmdM: load system.nim from NIF first, then compile the main module
|
||||
connectPipelineCallbacks(graph)
|
||||
# Record the main module so the IC loader won't materialise duplicate stubs
|
||||
# for its own symbols when a dependency (e.g. system) re-exports them.
|
||||
setIcMainModule(projectFile)
|
||||
graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
|
||||
graph.config.libpath / RelativeFile"system.nim")
|
||||
when not defined(nimKochBootstrap):
|
||||
# Don't clobber an already-compiled system: nimsuggest's NimScript config
|
||||
# evaluation compiles `system` into this same graph before we get here.
|
||||
let precomp = moduleFromNifFile(graph, graph.config.m.systemFileIdx)
|
||||
graph.systemModule = precomp.module
|
||||
if graph.systemModule == nil:
|
||||
let precomp = moduleFromNifFile(graph, graph.config.m.systemFileIdx)
|
||||
graph.systemModule = precomp.module
|
||||
if graph.systemModule == nil:
|
||||
if graph.config.ideActive:
|
||||
# nimsuggest bootstrap: no system NIF yet — compile it from source
|
||||
# (the pass-close emits it), then continue with the main module.
|
||||
graph.compilePipelineSystemModule()
|
||||
else:
|
||||
let nifPath = toNifFilename(graph.config, graph.config.m.systemFileIdx)
|
||||
localError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for system module (expected: " & nifPath & ")")
|
||||
return
|
||||
# Apply system's (and its deps') load effects now: the main module is
|
||||
# compiled from source and never re-enters the moduleFromNifFile drain for
|
||||
# system, so without this its macro-cache / CT globals would wait until the
|
||||
# first NIF import is processed. See finalizeLoadedModules.
|
||||
finalizeLoadedModules(graph)
|
||||
let nifPath = toNifFilename(graph.config, graph.config.m.systemFileIdx)
|
||||
localError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for system module (expected: " & nifPath & ")")
|
||||
return
|
||||
discard graph.compilePipelineModule(projectFile, {sfMainModule})
|
||||
else:
|
||||
graph.compilePipelineSystemModule()
|
||||
|
||||
@@ -20,3 +20,7 @@ proc prepareConfigNotes*(graph: ModuleGraph; module: PSym) =
|
||||
else:
|
||||
if graph.config.mainPackageNotes == {}: graph.config.mainPackageNotes = graph.config.notes
|
||||
graph.config.notes = graph.config.foreignPackageNotes
|
||||
|
||||
proc moduleHasChanged*(graph: ModuleGraph; module: PSym): bool {.inline.} =
|
||||
result = true
|
||||
#module.id >= 0 or isDefined(graph.config, "nimBackendAssumesChange")
|
||||
|
||||
@@ -211,7 +211,7 @@ type
|
||||
cpuPowerpc64el, cpuSparc, cpuVm, cpuHppa, cpuIa64, cpuAmd64, cpuMips,
|
||||
cpuMipsel, cpuArm, cpuArm64, cpuJS, cpuNimVM, cpuAVR, cpuMSP430,
|
||||
cpuSparc64, cpuS390x, cpuMips64, cpuMips64el, cpuRiscV32, cpuRiscV64,
|
||||
cpuEsp, cpuWasm32, cpuE2k, cpuLoongArch64, cpuWasm64
|
||||
cpuEsp, cpuWasm32, cpuE2k, cpuLoongArch64
|
||||
|
||||
type
|
||||
TInfoCPU* = tuple[name: string, intSize: int, endian: Endianness,
|
||||
@@ -249,8 +249,7 @@ const
|
||||
(name: "esp", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32),
|
||||
(name: "wasm32", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32),
|
||||
(name: "e2k", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64),
|
||||
(name: "loongarch64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64),
|
||||
(name: "wasm64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)]
|
||||
(name: "loongarch64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)]
|
||||
|
||||
type
|
||||
Target* = object
|
||||
|
||||
@@ -11,10 +11,25 @@
|
||||
# This is needed for proper handling of forward declarations.
|
||||
|
||||
import
|
||||
ast, astalgo, msgs, semdata, types, lookups
|
||||
ast, astalgo, msgs, semdata, types, trees, lookups
|
||||
|
||||
import std/strutils
|
||||
|
||||
proc equalGenericParams(procA, procB: PNode): bool =
|
||||
if procA.len != procB.len: return false
|
||||
for i in 0..<procA.len:
|
||||
if procA[i].kind != nkSym:
|
||||
return false
|
||||
if procB[i].kind != nkSym:
|
||||
return false
|
||||
let a = procA[i].sym
|
||||
let b = procB[i].sym
|
||||
if a.name.id != b.name.id or
|
||||
not sameTypeOrNil(a.typ, b.typ, {ExactTypeDescValues}): return
|
||||
if a.ast != nil and b.ast != nil:
|
||||
if not exprStructuralEquivalent(a.ast, b.ast): return
|
||||
result = true
|
||||
|
||||
proc searchForProcAux(c: PContext, scope: PScope, fn: PSym): PSym =
|
||||
const flags = {ExactGenericParams, ExactTypeDescValues,
|
||||
ExactConstraints, IgnoreCC}
|
||||
@@ -44,3 +59,30 @@ proc searchForProc*(c: PContext, scope: PScope, fn: PSym): tuple[proto: PSym, co
|
||||
scope = scope.parent
|
||||
result.proto = searchForProcAux(c, scope, fn)
|
||||
result.comesFromShadowScope = true
|
||||
|
||||
when false:
|
||||
proc paramsFitBorrow(child, parent: PNode): bool =
|
||||
result = false
|
||||
if child.len == parent.len:
|
||||
for i in 1..<child.len:
|
||||
var m = child[i].sym
|
||||
var n = parent[i].sym
|
||||
assert((m.kind == skParam) and (n.kind == skParam))
|
||||
if not compareTypes(m.typ, n.typ, dcEqOrDistinctOf): return
|
||||
if not compareTypes(child[0].typ, parent[0].typ,
|
||||
dcEqOrDistinctOf): return
|
||||
result = true
|
||||
|
||||
proc searchForBorrowProc*(c: PContext, startScope: PScope, fn: PSym): PSym =
|
||||
# Searches for the fn in the symbol table. If the parameter lists are suitable
|
||||
# for borrowing the sym in the symbol table is returned, else nil.
|
||||
var it: TIdentIter = default(TIdentIter)
|
||||
for scope in walkScopes(startScope):
|
||||
result = initIdentIter(it, scope.symbols, fn.Name)
|
||||
while result != nil:
|
||||
# watchout! result must not be the same as fn!
|
||||
if (result.Kind == fn.kind) and (result.id != fn.id):
|
||||
if equalGenericParams(result.ast[genericParamsPos],
|
||||
fn.ast[genericParamsPos]):
|
||||
if paramsFitBorrow(fn.typ.n, result.typ.n): return
|
||||
result = NextIdentIter(it, scope.symbols)
|
||||
|
||||
@@ -537,6 +537,10 @@ proc putNL(g: var TSrcGen, indent: int) =
|
||||
g.lineLen = indent
|
||||
g.pendingWhitespace = -1
|
||||
|
||||
proc previousNL(g: TSrcGen): bool =
|
||||
result = g.pendingNL >= 0 or (g.tokens.len > 0 and
|
||||
g.tokens[^1].kind == tkSpaces)
|
||||
|
||||
proc putNL(g: var TSrcGen) =
|
||||
putNL(g, g.indent)
|
||||
|
||||
@@ -578,7 +582,6 @@ proc put(g: var TSrcGen, kind: TokType, s: string; sym: PSym = nil) =
|
||||
inc(g.lineLen, s.len)
|
||||
|
||||
proc putComment(g: var TSrcGen, s: string) =
|
||||
const SpecialWhitespace = {' ', '\t', '\r', '\n', '\0'}
|
||||
if s.len == 0: return
|
||||
var i = 0
|
||||
let hi = s.len - 1
|
||||
@@ -608,12 +611,12 @@ proc putComment(g: var TSrcGen, s: string) =
|
||||
# gets too long:
|
||||
# compute length of the following word:
|
||||
var j = i
|
||||
while j <= hi and s[j] notin SpecialWhitespace: inc(j)
|
||||
while j <= hi and s[j] > ' ': inc(j)
|
||||
if not isCode and (g.col + (j - i) > MaxLineLen):
|
||||
put(g, tkComment, com)
|
||||
optNL(g, ind)
|
||||
com = "## "
|
||||
while i <= hi and s[i] notin SpecialWhitespace:
|
||||
while i <= hi and s[i] > ' ':
|
||||
com.add(s[i])
|
||||
inc(i)
|
||||
put(g, tkComment, com)
|
||||
@@ -642,6 +645,28 @@ proc maxLineLength(s: string): int =
|
||||
inc(lineLen)
|
||||
inc(i)
|
||||
|
||||
proc putRawStr(g: var TSrcGen, kind: TokType, s: string) =
|
||||
var i = 0
|
||||
let hi = s.len - 1
|
||||
var str = ""
|
||||
while i <= hi:
|
||||
case s[i]
|
||||
of '\r':
|
||||
put(g, kind, str)
|
||||
str = ""
|
||||
inc(i)
|
||||
if i <= hi and s[i] == '\n': inc(i)
|
||||
optNL(g, 0)
|
||||
of '\n':
|
||||
put(g, kind, str)
|
||||
str = ""
|
||||
inc(i)
|
||||
optNL(g, 0)
|
||||
else:
|
||||
str.add(s[i])
|
||||
inc(i)
|
||||
put(g, kind, str)
|
||||
|
||||
proc containsNL(s: string): bool =
|
||||
for i in 0..<s.len:
|
||||
case s[i]
|
||||
@@ -1758,6 +1783,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
|
||||
gsub(g, n, 1)
|
||||
of nkInfix:
|
||||
if n.len < 3:
|
||||
var i = 0
|
||||
put(g, tkOpr, "Too few children for nkInfix")
|
||||
return
|
||||
let oldLineLen = g.lineLen # we cache this because lineLen gets updated below
|
||||
@@ -2049,6 +2075,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
|
||||
of nkPragma:
|
||||
if g.inPragma <= 0:
|
||||
inc g.inPragma
|
||||
#if not previousNL(g):
|
||||
put(g, tkSpaces, Space)
|
||||
put(g, tkCurlyDotLe, "{.")
|
||||
gcomma(g, n, emptyContext)
|
||||
|
||||
@@ -34,6 +34,14 @@ when defined(windows) and defined(bcc):
|
||||
#endif
|
||||
""".}
|
||||
|
||||
proc c_snprintf(s: cstring; n: uint; frmt: cstring): cint {.importc: "snprintf", header: "<stdio.h>", nodecl, varargs.}
|
||||
|
||||
|
||||
when not declared(signbit):
|
||||
proc c_signbit(x: SomeFloat): cint {.importc: "signbit", header: "<math.h>".}
|
||||
proc signbit*(x: SomeFloat): bool {.inline.} =
|
||||
result = c_signbit(x) != 0
|
||||
|
||||
import std/formatfloat
|
||||
|
||||
proc toStrMaxPrecision*(f: BiggestFloat | float32): string =
|
||||
|
||||
@@ -77,7 +77,7 @@ template semIdeForTemplateOrGeneric(c: PContext; n: PNode;
|
||||
# templates perform some quick check whether the cursor is actually in
|
||||
# the generic or template.
|
||||
when defined(nimsuggest):
|
||||
if c.config.ideActive and requiresCheck:
|
||||
if c.config.cmd == cmdIdeTools and requiresCheck:
|
||||
#if optIdeDebug in gGlobalOptions:
|
||||
# echo "passing to safeSemExpr: ", renderTree(n)
|
||||
discard safeSemExpr(c, n)
|
||||
@@ -89,18 +89,6 @@ proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode =
|
||||
changeType(c, x, formal, check=true)
|
||||
result = arg
|
||||
result = skipHiddenSubConv(result, c.graph, c.idgen)
|
||||
# Walk through nested statement-list/block expressions to find the innermost
|
||||
# value node. Empty containers (e.g. `@[]`) inside `nkStmtListExpr` wrappers
|
||||
# need their type resolved to match the formal type, otherwise the C codegen
|
||||
# cannot map `tyEmpty` to a concrete type (fixes #25945).
|
||||
var tail = result
|
||||
while tail.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkPragmaBlock} and tail.len > 0:
|
||||
tail = tail.lastSon
|
||||
|
||||
if tail.typ != nil and tail.typ.isEmptyContainer and
|
||||
formal.kind notin {tyUntyped, tyBuiltInTypeClass, tyAnything}:
|
||||
changeType(c, tail, formal, check=true)
|
||||
|
||||
# mark inserted converter as used:
|
||||
var a = result
|
||||
if a.kind == nkHiddenDeref: a = a[0]
|
||||
@@ -117,12 +105,9 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
|
||||
result.typ = formal
|
||||
elif arg.kind in nkSymChoices and formal.skipTypes(abstractInst).kind == tyEnum:
|
||||
# Pick the right 'sym' from the sym choice by looking at 'formal' type:
|
||||
# The choice candidates may be wrapped in `var`/`lent` when they come from
|
||||
# a loop-local view, but for enum disambiguation only the underlying enum
|
||||
# type matters.
|
||||
result = nil
|
||||
for ch in arg:
|
||||
if sameType(ch.typ.skipTypes({tyVar, tyLent}), formal):
|
||||
if sameType(ch.typ, formal):
|
||||
return ch
|
||||
typeMismatch(c.config, info, formal, arg.typ, arg)
|
||||
else:
|
||||
@@ -262,40 +247,12 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
|
||||
if result.kind notin {kind, skTemp}:
|
||||
localError(c.config, n.info, "cannot use symbol of kind '$1' as a '$2'" %
|
||||
[result.kind.toHumanStr, kind.toHumanStr])
|
||||
# bug #25693: a local declared inside a template/macro operand (recorded in
|
||||
# `shadowDiscardedDefs`) can be captured by a `{.dirty.}` template and
|
||||
# re-emitted as a definition more than once. The first emission keeps the
|
||||
# original symbol (so a leaked dirty-template name still resolves); every
|
||||
# later emission gets a fresh copy, so distinct emissions don't share one
|
||||
# symbol - which the destructor/liveness analysis would otherwise miscompile.
|
||||
# Unlike a plain redefinition check this is control-flow agnostic, so the
|
||||
# common "emit a `typed` body in several mutually-exclusive branches" pattern
|
||||
# keeps working. gensym'ed locals (and ones derived from a gensym name) are
|
||||
# excluded: the gensym machinery already keeps their names unique, and a
|
||||
# fresh copy would reuse the unique name and clash in the same scope.
|
||||
if kind in {skVar, skLet, skForVar} and
|
||||
{sfGenSym, sfWasGenSym} * result.flags == {} and
|
||||
result.id in c.shadowDiscardedDefs:
|
||||
if containsOrIncl(c.realizedDefs, result.id):
|
||||
let fresh = copySym(result, c.idgen)
|
||||
fresh.ast = result.ast
|
||||
put(c.p, result, fresh)
|
||||
c.hasSymRedefs = true
|
||||
result = fresh
|
||||
when false:
|
||||
if sfGenSym in result.flags and result.kind notin {skTemplate, skMacro, skParam}:
|
||||
# declarative context, so produce a fresh gensym:
|
||||
result = copySym(result)
|
||||
result.ast = n.sym.ast
|
||||
put(c.p, n.sym, result)
|
||||
if result.state == Sealed:
|
||||
# the symbol was loaded from another module's NIF cache (e.g. a param
|
||||
# symbol spliced out of an imported proc type by a `typed` macro) and is
|
||||
# therefore immutable; the caller re-owns it and assigns its type/flags,
|
||||
# so hand back a fresh, mutable copy owned by the current module instead.
|
||||
let fresh = copySym(result, c.idgen)
|
||||
fresh.ast = result.ast
|
||||
result = fresh
|
||||
# when there is a nested proc inside a template, semtmpl
|
||||
# will assign a wrong owner during the first pass over the
|
||||
# template; we must fix it here: see #909
|
||||
@@ -332,6 +289,7 @@ proc typeAllowedCheck(c: PContext; info: TLineInfo; typ: PType; kind: TSymKind;
|
||||
proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} =
|
||||
typeAllowedCheck(c, typ.n.info, typ, skProc)
|
||||
|
||||
proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym
|
||||
proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode
|
||||
proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode
|
||||
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
|
||||
@@ -583,12 +541,10 @@ const
|
||||
|
||||
proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
|
||||
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
|
||||
let info = getCallLineInfo(n)
|
||||
# the callee identifier's position is the usage site tooling expects (matches
|
||||
# `markUsed` below), not the whole-call `nOrig.info`.
|
||||
rememberExpansion(c, info, sym)
|
||||
rememberExpansion(c, nOrig.info, sym)
|
||||
pushInfoContext(c.config, nOrig.info, sym.detailedInfo)
|
||||
|
||||
let info = getCallLineInfo(n)
|
||||
markUsed(c, info, sym)
|
||||
onUse(info, sym)
|
||||
if sym == c.p.owner:
|
||||
@@ -895,7 +851,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
|
||||
result = hloStmt(c, result)
|
||||
if c.config.cmd == cmdInteractive and not isEmptyType(result.typ):
|
||||
result = buildEchoStmt(c, result)
|
||||
if c.config.ideActive:
|
||||
if c.config.cmd == cmdIdeTools:
|
||||
appendToModule(c.module, result)
|
||||
trackStmt(c, c.module, result, isTopLevel = true)
|
||||
if optMultiMethods notin c.config.globalOptions and
|
||||
@@ -932,7 +888,7 @@ proc semWithPContext*(c: PContext, n: PNode): PNode =
|
||||
result = nil
|
||||
else:
|
||||
result = newNodeI(nkEmpty, n.info)
|
||||
#if c.config.ideActive: findSuggest(c, n)
|
||||
#if c.config.cmd == cmdIdeTools: findSuggest(c, n)
|
||||
|
||||
proc reportUnusedModules(c: PContext) =
|
||||
if c.config.cmd == cmdM: return
|
||||
@@ -941,7 +897,7 @@ proc reportUnusedModules(c: PContext) =
|
||||
message(c.config, info, warnUnusedImportX, s.name.s)
|
||||
|
||||
proc closePContext*(graph: ModuleGraph; c: PContext, n: PNode): PNode =
|
||||
if c.config.ideActive and not c.suggestionsMade:
|
||||
if c.config.cmd == cmdIdeTools and not c.suggestionsMade:
|
||||
suggestSentinel(c)
|
||||
closeScope(c) # close module's scope
|
||||
rawCloseScope(c) # imported symbols; don't check for unused ones!
|
||||
|
||||
@@ -77,7 +77,7 @@ proc isAttachableRoutineTo(prc: PSym, arg: PType): bool =
|
||||
# has default value, parameter is not considered in type attachment
|
||||
continue
|
||||
let t = nominalRoot(prc.typ[i])
|
||||
if t != nil and t.bindingId == arg.bindingId:
|
||||
if t != nil and t.itemId == arg.itemId:
|
||||
# parameter `i` is a nominal type in this module
|
||||
# attachable if the nominal root `t` has the same id as `arg`
|
||||
return true
|
||||
@@ -90,14 +90,8 @@ proc addTypeBoundSymbols(graph: ModuleGraph, arg: PType, name: PIdent,
|
||||
# argument must be typed first, meaning arguments always
|
||||
# matching `untyped` are ignored
|
||||
let t = nominalRoot(arg)
|
||||
if t != nil and t.owner.kind == skModule and
|
||||
t.owner.position >= 0 and t.owner.position < graph.ifaces.len:
|
||||
# search module for routines attachable to `t`.
|
||||
# Under IC the nominal type may have been loaded from a NIF file, in which
|
||||
# case its owner module is a stub whose `position` (a NIF-suffix file index)
|
||||
# has no `ifaces` slot; such type-bound ops are reachable through normal
|
||||
# imports instead, so skip the direct module scan to avoid an out-of-range
|
||||
# access.
|
||||
if t != nil and t.owner.kind == skModule:
|
||||
# search module for routines attachable to `t`
|
||||
let module = t.owner
|
||||
var iter = default(ModuleIter)
|
||||
var s = initModuleIter(iter, graph, module, name)
|
||||
@@ -137,7 +131,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
|
||||
var sym = syms[0].s
|
||||
let name = sym.name
|
||||
var scope = syms[0].scope
|
||||
c.openShadowScope
|
||||
|
||||
if allowTypeBoundOps:
|
||||
for a in 1 ..< n.len:
|
||||
# for every already typed argument, add type bound ops
|
||||
@@ -166,13 +160,9 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
|
||||
addTypeBoundSymbols(c.graph, arg.typ, name, filter, symMarker, syms)
|
||||
|
||||
if z.state == csMatch:
|
||||
# Iterator preference is heuristic in iterator-admitting contexts.
|
||||
# The dedicated iterable path uses `iteratorPreference`, other
|
||||
# context use exact-match bump
|
||||
# little hack so that iterators are preferred over everything else:
|
||||
if sym.kind == skIterator:
|
||||
if efPreferIteratorForIterable in flags:
|
||||
inc(z.iteratorPreference)
|
||||
elif not (efWantIterator notin flags and efWantIterable in flags):
|
||||
if not (efWantIterator notin flags and efWantIterable in flags):
|
||||
inc(z.exactMatches, 200)
|
||||
else:
|
||||
dec(z.exactMatches, 200)
|
||||
@@ -224,10 +214,6 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
|
||||
scope = syms[nextSymIndex].scope
|
||||
inc(nextSymIndex)
|
||||
|
||||
if best.state == csMatch and best.calleeSym != nil and best.calleeSym.kind in {skTemplate, skMacro}:
|
||||
c.closeShadowScope
|
||||
else:
|
||||
c.mergeShadowScope
|
||||
|
||||
proc effectProblem(f, a: PType; result: var string; c: PContext) =
|
||||
if f.kind == tyProc and a.kind == tyProc:
|
||||
@@ -685,11 +671,11 @@ proc bracketNotFoundError(c: PContext; n: PNode; flags: TExprFlags) =
|
||||
# copied from semOverloadedCallAnalyzeEffects, might be overkill:
|
||||
const baseFilter = {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate}
|
||||
let filter =
|
||||
if flags*{efInTypeof, efWantIterator, efWantIterable, efPreferIteratorForIterable} != {}:
|
||||
if flags*{efInTypeof, efWantIterator, efWantIterable} != {}:
|
||||
baseFilter + {skIterator}
|
||||
else: baseFilter
|
||||
# this will add the errors:
|
||||
discard resolveOverloads(c, n, n, filter, flags, errors, true)
|
||||
var r = resolveOverloads(c, n, n, filter, flags, errors, true)
|
||||
if errors.len == 0:
|
||||
localError(c.config, n.info, "could not resolve: " & $n)
|
||||
else:
|
||||
@@ -732,15 +718,6 @@ proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode =
|
||||
result = paramTypesMatch(m, f, a, arg, nil)
|
||||
if m.genericConverter and result != nil:
|
||||
instGenericConvertersArg(c, result, m)
|
||||
when defined(icDbg):
|
||||
if result == nil and f != nil and a != nil and f.kind == tyEnum:
|
||||
echo "INDEXMISMATCH f=", typeToString(f), " itemId=", f.itemId,
|
||||
" bindingId=", f.bindingId, " mod=", toFullPath(c.config, f.itemId.module.FileIndex),
|
||||
" sym=", (if f.sym != nil: $f.sym.itemId else: "nil"), " state=", f.state
|
||||
let a2 = a.skipTypes({tyRange})
|
||||
echo " a=", typeToString(a), " itemId=", a2.itemId, " bindingId=", a2.bindingId,
|
||||
" mod=", toFullPath(c.config, a2.itemId.module.FileIndex),
|
||||
" sym=", (if a2.sym != nil: $a2.sym.itemId else: "nil"), " state=", a2.state
|
||||
|
||||
proc inferWithMetatype(c: PContext, formal: PType,
|
||||
arg: PNode, coerceDistincts = false): PNode =
|
||||
@@ -926,6 +903,15 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
|
||||
result.typ = finalCallee.typ.returnType
|
||||
updateDefaultParams(c, result)
|
||||
|
||||
proc canDeref(n: PNode): bool {.inline.} =
|
||||
result = n.len >= 2 and (let t = n[1].typ;
|
||||
t != nil and t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyPtr, tyRef})
|
||||
|
||||
proc tryDeref(n: PNode): PNode =
|
||||
result = newNodeI(nkHiddenDeref, n.info)
|
||||
result.typ = n.typ.skipTypes(abstractInst)[0]
|
||||
result.add n
|
||||
|
||||
proc semOverloadedCall(c: PContext, n, nOrig: PNode,
|
||||
filter: TSymKinds, flags: TExprFlags;
|
||||
expectedType: PType = nil): PNode =
|
||||
@@ -974,12 +960,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym, errors: var CandidateErr
|
||||
diagnostics: m.diagnostics))
|
||||
return nil
|
||||
var newInst = generateInstance(c, s, m.bindings, n.info)
|
||||
# `generateInstance` may return an instance REUSED from another module's NIF
|
||||
# `(offer …)` — its type is Sealed (immutable). Such an instance is already
|
||||
# fully resolved (`tfUnresolved` cleared at its original instantiation), so the
|
||||
# `excl` is a no-op; skip it rather than assert on a Sealed-type mutation.
|
||||
if newInst.typ.state != Sealed:
|
||||
newInst.typ.excl tfUnresolved
|
||||
newInst.typ.excl tfUnresolved
|
||||
let info = getCallLineInfo(n)
|
||||
markUsed(c, info, s, isGenericInstance = false)
|
||||
onUse(info, s, isGenericInstance = false)
|
||||
|
||||
@@ -54,18 +54,7 @@ type
|
||||
inst*: PInstantiation
|
||||
|
||||
TExprFlag* = enum
|
||||
efLValue,
|
||||
# The expression is used as an assignable location.
|
||||
efWantIterator,
|
||||
# Admit iterator candidates and prefer them during overload resolution.
|
||||
efWantIterable,
|
||||
# Admit iterator candidates for expressions that may feed iterable-style
|
||||
# chaining.
|
||||
efPreferIteratorForIterable,
|
||||
# Prefer iterator candidates for `iterable[T]` matching and wrap a
|
||||
# successful iterator call as `tyIterable`.
|
||||
efInTypeof,
|
||||
# The expression is being semchecked under `typeof`.
|
||||
efLValue, efWantIterator, efWantIterable, efInTypeof,
|
||||
efNeedStatic,
|
||||
# Use this in contexts where a static value is mandatory
|
||||
efPreferStatic,
|
||||
@@ -180,33 +169,12 @@ type
|
||||
sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
|
||||
inUncheckedAssignSection*: int
|
||||
importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id])
|
||||
forwardTypeUpdates*: seq[(PSym, PType, PNode)]
|
||||
# top-level owner, type, and type node for delayed retries inside a
|
||||
# type section due to containing forward types
|
||||
forwardFieldUpdates*: seq[(PType, PNode, PType)]
|
||||
# object/tuple field definitions whose default values mention forward
|
||||
# types and need delayed const checking
|
||||
forwardFlagUpdates*: seq[(PType, PType)]
|
||||
# (owner, son) pairs whose `propagateToOwner` ran on a not yet reified
|
||||
# forward type and has to be redone in the final pass
|
||||
staleTypeFlags*: IntSet
|
||||
# ids of the owners in `forwardFlagUpdates`; their flags are provisional
|
||||
# too, so reading them makes the reader provisional in turn
|
||||
forwardTypeUpdates*: seq[(PType, PNode)]
|
||||
# types that need to be updated in a type section
|
||||
# due to containing forward types, and their corresponding nodes
|
||||
inTypeofContext*: int
|
||||
|
||||
semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.}
|
||||
shadowDiscardedDefs*: IntSet
|
||||
# ids of local symbols that were declared inside a template/macro operand's
|
||||
# shadow scope and then discarded; re-emitting such a symbol as a
|
||||
# definition gives a fresh copy so distinct emissions don't share a symbol.
|
||||
# See bug #25693 and `rememberShadowDefs`.
|
||||
realizedDefs*: IntSet
|
||||
# ids from `shadowDiscardedDefs` already realized once; the first emission
|
||||
# keeps the original symbol (so leaked dirty-template names still resolve),
|
||||
# later emissions get a fresh copy.
|
||||
hasSymRedefs*: bool
|
||||
# set once a redefinition mapping has been installed; makes `getGenSym`
|
||||
# consult the proc-con mapping for non-gensym symbols too.
|
||||
|
||||
TBorrowState* = enum
|
||||
bsNone, bsReturnNotMatch, bsNoDistinct, bsGeneric, bsNotSupported, bsMatch
|
||||
@@ -299,10 +267,7 @@ proc get*(p: PProcCon; key: PSym): PSym =
|
||||
result = p.mapping.getOrDefault(key.itemId)
|
||||
|
||||
proc getGenSym*(c: PContext; s: PSym): PSym =
|
||||
# `c.hasSymRedefs` additionally routes ordinary (non-gensym) symbols through
|
||||
# the mapping so a re-emitted definition can redirect them to its fresh copy,
|
||||
# see bug #25693 and `newSymG`.
|
||||
if sfGenSym notin s.flags and not c.hasSymRedefs: return s
|
||||
if sfGenSym notin s.flags: return s
|
||||
var it = c.p
|
||||
while it != nil:
|
||||
result = get(it, s)
|
||||
@@ -312,7 +277,7 @@ proc getGenSym*(c: PContext; s: PSym): PSym =
|
||||
it = it.next
|
||||
result = s
|
||||
|
||||
proc considerGenSymsAux(c: PContext; n: PNode) =
|
||||
proc considerGenSyms*(c: PContext; n: PNode) =
|
||||
if n == nil:
|
||||
discard "can happen for nkFormalParams/nkArgList"
|
||||
elif n.kind == nkSym:
|
||||
@@ -321,16 +286,7 @@ proc considerGenSymsAux(c: PContext; n: PNode) =
|
||||
n.sym = s
|
||||
else:
|
||||
for i in 0..<n.safeLen:
|
||||
considerGenSymsAux(c, n[i])
|
||||
|
||||
proc considerGenSyms*(c: PContext; n: PNode) =
|
||||
var it = c.p
|
||||
while it != nil:
|
||||
if it.mappingExists:
|
||||
# Save a tree traversal when no mapping exists
|
||||
considerGenSymsAux(c, n)
|
||||
return
|
||||
it = it.next
|
||||
considerGenSyms(c, n[i])
|
||||
|
||||
proc newOptionEntry*(conf: ConfigRef): POptionEntry =
|
||||
result = POptionEntry(
|
||||
@@ -373,9 +329,6 @@ proc newContext*(graph: ModuleGraph; module: PSym): PContext =
|
||||
userPragmas: initStrTable(),
|
||||
generics: @[],
|
||||
unknownIdents: initIntSet(),
|
||||
shadowDiscardedDefs: initIntSet(),
|
||||
realizedDefs: initIntSet(),
|
||||
staleTypeFlags: initIntSet(),
|
||||
cache: graph.cache,
|
||||
graph: graph,
|
||||
signatures: initStrTable(),
|
||||
@@ -386,21 +339,11 @@ proc addIncludeFileDep*(c: PContext; f: FileIndex) =
|
||||
discard
|
||||
|
||||
proc addImportFileDep*(c: PContext; f: FileIndex) =
|
||||
# Under `nim m` (the IC frontend) record the REAL direct imports of the
|
||||
# current module as sem resolves them — including imports a macro generated
|
||||
# (e.g. chronicles' `parseStmt("import chronicles/textlines")`), which the
|
||||
# static dependency scanner never sees. `nim ic` writes this set as the
|
||||
# module's `.s.deps` sidecar and re-derives the build graph from it, so the
|
||||
# discovery is structured data instead of a build-failure side channel.
|
||||
if c.config.cmd == cmdM:
|
||||
let importer = c.module.position.FileIndex
|
||||
var deps = addr c.graph.importDeps.mgetOrPut(importer, @[])
|
||||
if f notin deps[]: deps[].add f
|
||||
discard
|
||||
|
||||
proc addPragmaComputation*(c: PContext; n: PNode) =
|
||||
# Also store whenever the semchecked module is serialized to NIF/BIF.
|
||||
if {optCompress, optGenBif} * c.config.globalOptions != {} or
|
||||
c.config.cmd == cmdM:
|
||||
# Also store for NIF-based IC (cmdM mode or optCompress)
|
||||
if optCompress in c.config.globalOptions or c.config.cmd == cmdM:
|
||||
addNifReplayAction(c.graph, c.module.position.int32, n)
|
||||
|
||||
proc inclSym(sq: var seq[PSym], s: PSym): bool =
|
||||
@@ -413,18 +356,6 @@ proc addConverter*(c: PContext, conv: PSym) =
|
||||
assert conv != nil
|
||||
if inclSym(c.converters, conv):
|
||||
add(c.graph.ifaces[c.module.position].converters, conv)
|
||||
# Record for IC: the loader rebuilds Iface.converters from the NIF's
|
||||
# (repconverter ...) entries (moduleFromNifFile). This must capture not only
|
||||
# converters DEFINED in this module (addConverterDef) but also ones IMPORTED
|
||||
# from another module here (importer.addUnnamedIt re-adds a re-exported
|
||||
# module's converters via this proc). Otherwise a loaded module's
|
||||
# re-exported converters were invisible to importers and implicit
|
||||
# conversions silently stopped matching at a consumer that reaches the
|
||||
# converter only through this module's re-export chain (e.g. faststreams'
|
||||
# `InputStreamHandle -> InputStream` via ssz_serialization, breaking
|
||||
# `SSZ.decode`/`encode`). `inclSym` guards against duplicate log entries.
|
||||
c.graph.opsLog.add LogEntry(kind: ConverterEntry, module: c.module.position,
|
||||
key: "", sym: conv)
|
||||
|
||||
proc addConverterDef*(c: PContext, conv: PSym) =
|
||||
addConverter(c, conv)
|
||||
@@ -432,13 +363,6 @@ proc addConverterDef*(c: PContext, conv: PSym) =
|
||||
proc addPureEnum*(c: PContext, e: PSym) =
|
||||
assert e != nil
|
||||
add(c.graph.ifaces[c.module.position].pureEnums, e)
|
||||
# record for IC: a NIF-loaded module rebuilds `Iface.pureEnums` from these log
|
||||
# entries (moduleFromNifFile); without it a loaded module's pure enums were
|
||||
# invisible to importers, so `importPureEnumFields` never offered their fields
|
||||
# and unqualified pure-enum values stopped resolving. (Same pattern as
|
||||
# `addConverterDef`.)
|
||||
c.graph.opsLog.add LogEntry(kind: PureEnumEntry, module: c.module.position,
|
||||
key: "", sym: e)
|
||||
|
||||
proc addPattern*(c: PContext, p: PSym) =
|
||||
assert p != nil
|
||||
@@ -685,15 +609,7 @@ proc rememberExpansion*(c: PContext; info: TLineInfo; expandedSym: PSym) =
|
||||
## ("find all usages of this template" would not work). We need special
|
||||
## logic to remember macro/template expansions. This is done here and
|
||||
## delegated to the "NIF" file mechanism.
|
||||
##
|
||||
## We only bother when a NIF file is actually going to be written (IC / `nim m`,
|
||||
## `--compress`, semantic BIF output, or a running suggestion engine); a plain
|
||||
## `nim c` throws the record away, so recording it would be pure overhead.
|
||||
if info.fileIndex == InvalidFileIdx: return
|
||||
if c.config.cmd == cmdM or
|
||||
{optCompress, optGenBif} * c.config.globalOptions != {} or
|
||||
c.config.ideActive:
|
||||
c.graph.nifExpansions.mgetOrPut(c.module.position.int32, @[]).add (expandedSym, info)
|
||||
discard "XXX To implement"
|
||||
|
||||
const
|
||||
errVarForOutParamNeededX = "for a 'var' type a variable needs to be passed; but '$1' is immutable"
|
||||
@@ -707,11 +623,6 @@ proc renderNotLValue*(n: PNode): string =
|
||||
elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2:
|
||||
result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")"
|
||||
|
||||
proc isSsoStringIndex*(conf: ConfigRef; n: PNode): bool =
|
||||
result = conf.usesSso() and n.kind == nkBracketExpr and n.len >= 1 and
|
||||
n[0].typ != nil and
|
||||
n[0].typ.skipTypes(abstractVar + abstractInst - {tyTypeDesc}).kind == tyString
|
||||
|
||||
proc isAssignable(c: PContext, n: PNode): TAssignableResult =
|
||||
result = parampatterns.isAssignable(c.p.owner, n)
|
||||
|
||||
@@ -819,7 +730,7 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
|
||||
case kind
|
||||
of attachedDestructor:
|
||||
result = n
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
|
||||
let t = n[1].typ.skipTypes(abstractVar)
|
||||
let op = getAttachedOp(c.graph, t, attachedDestructor)
|
||||
if op != nil:
|
||||
result[0] = newSymNode(op)
|
||||
@@ -831,13 +742,13 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
|
||||
result[1] = skipAddr(n[1])
|
||||
of attachedTrace:
|
||||
result = n
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
|
||||
let t = n[1].typ.skipTypes(abstractVar)
|
||||
let op = getAttachedOp(c.graph, t, attachedTrace)
|
||||
if op != nil:
|
||||
result[0] = newSymNode(op)
|
||||
of attachedDup:
|
||||
result = n
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
|
||||
let t = n[1].typ.skipTypes(abstractVar)
|
||||
let op = getAttachedOp(c.graph, t, attachedDup)
|
||||
if op != nil:
|
||||
result[0] = newSymNode(op)
|
||||
@@ -847,26 +758,18 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
|
||||
result.add boolLit
|
||||
of attachedWasMoved:
|
||||
result = n
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
|
||||
let t = n[1].typ.skipTypes(abstractVar)
|
||||
let op = getAttachedOp(c.graph, t, attachedWasMoved)
|
||||
if op != nil:
|
||||
result[0] = newSymNode(op)
|
||||
analyseIfAddressTakenInCall(c, result, false)
|
||||
of attachedSink:
|
||||
result = n
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
|
||||
let op = getAttachedOp(c.graph, t, kind)
|
||||
if op != nil:
|
||||
result[0] = newSymNode(op)
|
||||
result = c.semAsgnOpr(c, n, nkSinkAsgn)
|
||||
of attachedAsgn:
|
||||
result = n
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
|
||||
let op = getAttachedOp(c.graph, t, kind)
|
||||
if op != nil:
|
||||
result[0] = newSymNode(op)
|
||||
result = c.semAsgnOpr(c, n, nkAsgn)
|
||||
of attachedDeepCopy:
|
||||
result = n
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
|
||||
let t = n[1].typ.skipTypes(abstractVar)
|
||||
let op = getAttachedOp(c.graph, t, kind)
|
||||
if op != nil:
|
||||
result[0] = newSymNode(op)
|
||||
|
||||
@@ -22,18 +22,12 @@ const
|
||||
errNamedExprExpected = "named expression expected"
|
||||
errNamedExprNotAllowed = "named expression not allowed here"
|
||||
errFieldInitTwice = "field initialized twice: '$1'"
|
||||
errUndeclaredFieldX = "undeclared field: '$1'"
|
||||
|
||||
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
|
||||
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
|
||||
rememberExpansion(c, n.info, s)
|
||||
let info = getCallLineInfo(n)
|
||||
# `info` (the callee identifier's position, not the whole call node) is what
|
||||
# tooling wants to see as the usage site — matches `markUsed` below.
|
||||
rememberExpansion(c, info, s)
|
||||
# IC: this expands `s`'s body into the current module's sem, so the module
|
||||
# depends on that body — record a NeedsImpl (strong) edge to `s`'s module.
|
||||
# The iface cookie hashes only signatures now, so a template body edit moves
|
||||
# only the impl cookie, and just the modules that expanded it re-sem.
|
||||
recordIcImplDep(c.graph, s)
|
||||
markUsed(c, info, s)
|
||||
onUse(info, s)
|
||||
# Note: This is n.info on purpose. It prevents template from creating an info
|
||||
@@ -63,16 +57,6 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||
elif {efWantStmt, efAllowStmt} * flags != {}:
|
||||
result.typ = newTypeS(tyVoid, c)
|
||||
else:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icNoType] semOperand: ", renderTree(result, {renderNoComments}),
|
||||
" kind=", result.kind,
|
||||
(if result.kind in {nkCall, nkCommand} and result[0].kind == nkSym:
|
||||
" calleeTyp=" & (if result[0].sym.typ == nil: "NIL" else:
|
||||
$result[0].sym.typ.kind & " ret=" &
|
||||
(if result[0].sym.typ.returnType == nil: "NIL"
|
||||
else: $result[0].sym.typ.returnType.kind))
|
||||
else: "")
|
||||
echo getStackTrace()
|
||||
localError(c.config, n.info, errExprXHasNoType %
|
||||
renderTree(result, {renderNoComments}))
|
||||
result.typ = errorType(c)
|
||||
@@ -99,17 +83,6 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType
|
||||
if result.typ == nil and efInTypeof in flags:
|
||||
result.typ = c.voidType
|
||||
elif result.typ == nil or result.typ == c.enforceVoidContext:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icNoType] semExprWithType: ", renderTree(result, {renderNoComments}),
|
||||
" kind=", result.kind,
|
||||
(if result.kind in {nkCall, nkCommand} and result[0].kind == nkSym:
|
||||
" callee=" & result[0].sym.name.s &
|
||||
" calleeTyp=" & (if result[0].sym.typ == nil: "NIL" else:
|
||||
$result[0].sym.typ.kind & " ret=" &
|
||||
(if result[0].sym.typ.returnType == nil: "NIL"
|
||||
else: $result[0].sym.typ.returnType.kind))
|
||||
else: "")
|
||||
echo getStackTrace()
|
||||
localError(c.config, n.info, errExprXHasNoType %
|
||||
renderTree(result, {renderNoComments}))
|
||||
result.typ = errorType(c)
|
||||
@@ -133,9 +106,7 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType
|
||||
|
||||
proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||
result = semExprCheck(c, n, flags)
|
||||
if result.typ == nil and efInTypeof in flags:
|
||||
result.typ = c.voidType
|
||||
elif result.typ == nil:
|
||||
if result.typ == nil:
|
||||
localError(c.config, n.info, errExprXHasNoType %
|
||||
renderTree(result, {renderNoComments}))
|
||||
result.typ = errorType(c)
|
||||
@@ -226,29 +197,6 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
|
||||
# set symchoice node type back to None
|
||||
n.typ = newTypeS(tyNone, c)
|
||||
|
||||
proc resolveOpenSymDotRhs(c: PContext, n: PNode): PNode =
|
||||
## Resolves an `nkOpenSym` in the field position of a dot expression.
|
||||
## The dot handling (`builtinFieldAccess`, `dotTransformation`) matches on
|
||||
## the node kind of the RHS directly, so the wrapper cannot be left for
|
||||
## `semExpr` to unwrap; without this the captured symbol degrades to a
|
||||
## plain identifier that is then only looked up in the instantiation
|
||||
## context. Mirrors `semOpenSym`: a symbol injected during instantiation
|
||||
## under the current proc replaces the captured symbol, otherwise the
|
||||
## captured node is used.
|
||||
let inner = n[0]
|
||||
result = inner
|
||||
if inner.kind != nkSym: return
|
||||
let id = newIdentNode(inner.sym.name, n.info)
|
||||
c.isAmbiguous = false
|
||||
let s2 = qualifiedLookUp(c, id, {})
|
||||
if s2 != nil and not c.isAmbiguous and s2 != inner.sym:
|
||||
# only consider symbols defined under the current proc:
|
||||
var o = s2.owner
|
||||
while o != nil:
|
||||
if o == c.p.owner:
|
||||
return id
|
||||
o = o.owner
|
||||
|
||||
proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
|
||||
if n.kind == nkOpenSymChoice:
|
||||
result = semOpenSym(c, n, flags, expectedType,
|
||||
@@ -704,9 +652,6 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode =
|
||||
result = semExpr(c, result, flags = {efNoUndeclared})
|
||||
|
||||
proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
|
||||
template isViewTarget(t: PType): bool =
|
||||
t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyVar, tyLent}
|
||||
|
||||
case n.kind
|
||||
of nkCurly:
|
||||
for i in 0..<n.len:
|
||||
@@ -735,15 +680,12 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
|
||||
if f == nil:
|
||||
globalError(c.config, m.info, "unknown identifier: " & m.sym.name.s)
|
||||
return
|
||||
if not isViewTarget(f.typ):
|
||||
changeType(c, n[i][1], f.typ, check)
|
||||
changeType(c, n[i][1], f.typ, check)
|
||||
else:
|
||||
if not isViewTarget(tup[i]):
|
||||
changeType(c, n[i][1], tup[i], check)
|
||||
changeType(c, n[i][1], tup[i], check)
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
if not isViewTarget(tup[i]):
|
||||
changeType(c, n[i], tup[i], check)
|
||||
changeType(c, n[i], tup[i], check)
|
||||
when false:
|
||||
var m = n[i]
|
||||
var a = newNodeIT(nkExprColonExpr, m.info, newType[i])
|
||||
@@ -766,9 +708,19 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
|
||||
localError(c.config, n.info, "cannot convert '" & n.sym.name.s &
|
||||
"' to '" & typeNameAndDesc(newType) & "'")
|
||||
else: discard
|
||||
|
||||
n.typ = newType
|
||||
|
||||
proc arrayConstrType(c: PContext, n: PNode): PType =
|
||||
var typ = newTypeS(tyArray, c)
|
||||
rawAddSon(typ, nil) # index type
|
||||
if n.len == 0:
|
||||
rawAddSon(typ, newTypeS(tyEmpty, c)) # needs an empty basetype!
|
||||
else:
|
||||
var t = skipTypes(n[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
|
||||
addSonSkipIntLit(typ, t, c.idgen)
|
||||
typ.setIndexType makeRangeType(c, 0, n.len - 1, n.info)
|
||||
result = typ
|
||||
|
||||
proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode =
|
||||
result = newNodeI(nkBracket, n.info)
|
||||
# nkBracket nodes can also be produced by the VM as seq constant nodes
|
||||
@@ -1011,15 +963,12 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
|
||||
# echo "SUCCESS evaluated at compile time: ", call.renderTree
|
||||
|
||||
proc semStaticExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
||||
let oldErrorCount = c.config.errorCounter
|
||||
inc c.inStaticContext
|
||||
openScope(c)
|
||||
let a = semExprWithType(c, n, expectedType = expectedType)
|
||||
closeScope(c)
|
||||
dec c.inStaticContext
|
||||
if a.findUnresolvedStatic != nil or
|
||||
c.config.errorCounter != oldErrorCount:
|
||||
return a
|
||||
if a.findUnresolvedStatic != nil: return a
|
||||
result = evalStaticExpr(c.module, c.idgen, c.graph, a, c.p.owner)
|
||||
if result.isNil:
|
||||
localError(c.config, n.info, errCannotInterpretNodeX % renderTree(n))
|
||||
@@ -1030,7 +979,7 @@ proc semStaticExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
||||
|
||||
proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
|
||||
flags: TExprFlags; expectedType: PType = nil): PNode =
|
||||
if flags*{efInTypeof, efWantIterator, efWantIterable, efPreferIteratorForIterable} != {}:
|
||||
if flags*{efInTypeof, efWantIterator, efWantIterable} != {}:
|
||||
# consider: 'for x in pReturningArray()' --> we don't want the restriction
|
||||
# to 'skIterator' anymore; skIterator is preferred in sigmatch already
|
||||
# for typeof support.
|
||||
@@ -1057,8 +1006,7 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
|
||||
# See bug #2051:
|
||||
result[0] = newSymNode(errorSym(c, n))
|
||||
elif callee.kind == skIterator:
|
||||
if result.typ.kind != tyIterable and
|
||||
flags * {efWantIterable, efPreferIteratorForIterable} != {}:
|
||||
if efWantIterable in flags:
|
||||
let typ = newTypeS(tyIterable, c)
|
||||
rawAddSon(typ, result.typ)
|
||||
result.typ = typ
|
||||
@@ -1327,6 +1275,7 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent,
|
||||
else: illFormedAst(n, c.config)
|
||||
|
||||
const
|
||||
tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass}
|
||||
tyDotOpTransparent = {tyVar, tyLent, tyPtr, tyRef, tyOwned, tyAlias, tySink}
|
||||
|
||||
proc readTypeParameter(c: PContext, typ: PType,
|
||||
@@ -1448,11 +1397,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
|
||||
# not sure the symbol really ends up being used:
|
||||
# var len = 0 # but won't be called
|
||||
# genericThatUsesLen(x) # marked as taking a closure?
|
||||
# Lowered returns use resolved symbol nodes internally; warn only for
|
||||
# source-level references to the implicit result variable.
|
||||
if s.kind == skResult and
|
||||
(n.kind != nkSym or nfFromTemplate in n.flags) and
|
||||
hasWarn(c.config, warnResultUsed):
|
||||
if hasWarn(c.config, warnResultUsed):
|
||||
message(c.config, n.info, warnResultUsed)
|
||||
|
||||
of skGenericParam:
|
||||
@@ -1564,13 +1509,10 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
|
||||
# here at all!
|
||||
#if isSymChoice(n[1]): return
|
||||
when defined(nimsuggest):
|
||||
if c.config.ideActive:
|
||||
if c.config.cmd == cmdIdeTools:
|
||||
suggestExpr(c, n)
|
||||
if exactEquals(c.config.m.trackPos, n[1].info): suggestExprNoCheck(c, n)
|
||||
|
||||
if n[1].kind == nkOpenSym:
|
||||
n[1] = resolveOpenSymDotRhs(c, n[1])
|
||||
|
||||
var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule})
|
||||
if s != nil:
|
||||
if s.kind in OverloadableSyms:
|
||||
@@ -1583,7 +1525,7 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
|
||||
return
|
||||
|
||||
# extra flags since LHS may become a call operand:
|
||||
n[0] = semExprWithType(c, n[0], flags + {efDetermineType, efWantIterable, efAllowSymChoice})
|
||||
n[0] = semExprWithType(c, n[0], flags+{efDetermineType, efWantIterable, efAllowSymChoice})
|
||||
#restoreOldStyleType(n[0])
|
||||
var i = considerQuotedIdent(c, n[1], n)
|
||||
var ty = n[0].typ
|
||||
@@ -1898,22 +1840,6 @@ proc takeImplicitAddr(c: PContext, n: PNode; isLent: bool): PNode =
|
||||
n.typ = n.typ.elementType
|
||||
result.add(n)
|
||||
|
||||
proc markResultVarIsPtr(c: PContext, x: PNode) {.inline.} =
|
||||
## Set `tfVarIsPtr` on the (result) sym node's type. Under IC that type can be a
|
||||
## NIF-loaded (Sealed) and interned instance which must not be mutated in place
|
||||
## (it could corrupt other users of the shared type, and the assert forbids it):
|
||||
## give this result its own copy carrying the flag, exactly like a from-source
|
||||
## compile has a fresh result type here.
|
||||
if tfVarIsPtr in x.typ.flags: return
|
||||
if x.typ.state == Sealed:
|
||||
let fresh = copyType(x.typ, c.idgen, x.typ.owner)
|
||||
fresh.incl tfVarIsPtr
|
||||
x.typ = fresh
|
||||
if x.kind == nkSym and x.sym.state != Sealed:
|
||||
x.sym.typ = fresh
|
||||
else:
|
||||
x.typ.incl tfVarIsPtr
|
||||
|
||||
proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} =
|
||||
if le.kind == nkHiddenDeref:
|
||||
var x = le[0]
|
||||
@@ -1921,17 +1847,17 @@ proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} =
|
||||
if x.sym.kind == skResult and (x.typ.kind in {tyVar, tyLent} or classifyViewType(x.typ) != noView):
|
||||
n[0] = x # 'result[]' --> 'result'
|
||||
n[1] = takeImplicitAddr(c, ri, x.typ.kind == tyLent)
|
||||
markResultVarIsPtr(c, x)
|
||||
x.typ.incl tfVarIsPtr
|
||||
#echo x.info, " setting it for this type ", typeToString(x.typ), " ", n.info
|
||||
elif sfGlobal in x.sym.flags:
|
||||
markResultVarIsPtr(c, x)
|
||||
x.typ.incl tfVarIsPtr
|
||||
|
||||
proc borrowCheck(c: PContext, n, le, ri: PNode) =
|
||||
const
|
||||
PathKinds0 = {nkDotExpr, nkCheckedFieldExpr,
|
||||
nkBracketExpr, nkAddr, nkHiddenAddr,
|
||||
nkObjDownConv, nkObjUpConv}
|
||||
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv, nkCast}
|
||||
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv}
|
||||
|
||||
proc getRoot(n: PNode; followDeref: bool): PNode =
|
||||
result = n
|
||||
@@ -2151,8 +2077,6 @@ proc semReturn(c: PContext, n: PNode): PNode =
|
||||
# optimize away ``result = result``:
|
||||
if result[0][1].kind == nkSym and result[0][1].sym == c.p.resultSym:
|
||||
result[0] = c.graph.emptyNode
|
||||
elif c.p.resultSym != nil and hasWarn(c.config, warnResultUsed):
|
||||
message(c.config, n.info, warnResultUsed)
|
||||
else:
|
||||
localError(c.config, n.info, "'return' not allowed here")
|
||||
|
||||
@@ -2183,12 +2107,6 @@ proc semProcBody(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
||||
|
||||
if c.p.owner.kind notin {skMacro, skTemplate} and
|
||||
c.p.resultSym != nil and c.p.resultSym.typ.isMetaType:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icMetaRet] meta result type for ", c.p.owner.name.s, ": ",
|
||||
typeToString(c.p.resultSym.typ), " kind=", c.p.resultSym.typ.kind,
|
||||
" flags=", c.p.resultSym.typ.flags,
|
||||
" itemId=", c.p.resultSym.typ.itemId.module, ".", c.p.resultSym.typ.itemId.item,
|
||||
" state=", c.p.resultSym.typ.state
|
||||
if isEmptyType(result.typ):
|
||||
# we inferred a 'void' return type:
|
||||
c.p.resultSym.typ = errorType(c)
|
||||
@@ -2313,6 +2231,24 @@ proc semDeclared(c: PContext, n: PNode, onlyCurrentScope: bool): PNode =
|
||||
result.info = n.info
|
||||
result.typ = getSysType(c.graph, n.info, tyBool)
|
||||
|
||||
proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym =
|
||||
## The argument to the proc should be nkCall(...) or similar
|
||||
## Returns the macro/template symbol
|
||||
if isCallExpr(n):
|
||||
var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared})
|
||||
if expandedSym == nil:
|
||||
errorUndeclaredIdentifier(c, n.info, n[0].renderTree)
|
||||
return errorSym(c, n[0])
|
||||
|
||||
if expandedSym.kind notin {skMacro, skTemplate}:
|
||||
localError(c.config, n.info, "'$1' is not a macro or template" % expandedSym.name.s)
|
||||
return errorSym(c, n[0])
|
||||
|
||||
result = expandedSym
|
||||
else:
|
||||
localError(c.config, n.info, "'$1' is not a macro or template" % n.renderTree)
|
||||
result = errorSym(c, n)
|
||||
|
||||
proc expectString(c: PContext, n: PNode): string =
|
||||
var n = semConstExpr(c, n)
|
||||
if n.kind in nkStrKinds:
|
||||
@@ -2327,6 +2263,14 @@ proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym =
|
||||
proc semExpandToAst(c: PContext, n: PNode): PNode =
|
||||
let macroCall = n[1]
|
||||
|
||||
when false:
|
||||
let expandedSym = expectMacroOrTemplateCall(c, macroCall)
|
||||
if expandedSym.kind == skError: return n
|
||||
|
||||
macroCall[0] = newSymNode(expandedSym, macroCall.info)
|
||||
markUsed(c, n.info, expandedSym)
|
||||
onUse(n.info, expandedSym)
|
||||
|
||||
if isCallExpr(macroCall):
|
||||
for i in 1..<macroCall.len:
|
||||
#if macroCall[0].typ[i].kind != tyUntyped:
|
||||
@@ -2498,6 +2442,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||
let oldInStaticContext = c.inStaticContext
|
||||
let oldProcCon = c.p
|
||||
c.generics = @[]
|
||||
var err: string
|
||||
try:
|
||||
result = semExpr(c, n, flags)
|
||||
if result != nil and efNoSem2Check notin flags:
|
||||
@@ -2708,22 +2653,6 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags; expectedType: P
|
||||
else:
|
||||
result = semDirectOp(c, n, flags, expectedType)
|
||||
|
||||
proc semNimvmBranch(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||
let
|
||||
oldOptionStack = c.optionStack[0..^1]
|
||||
oldOptions = c.config.options
|
||||
oldNotes = c.config.notes
|
||||
oldWarningAsErrors = c.config.warningAsErrors
|
||||
oldFeatures = c.features
|
||||
try:
|
||||
result = semExpr(c, n, flags)
|
||||
finally:
|
||||
c.optionStack = oldOptionStack
|
||||
c.config.options = oldOptions
|
||||
c.config.notes = oldNotes
|
||||
c.config.warningAsErrors = oldWarningAsErrors
|
||||
c.features = oldFeatures
|
||||
|
||||
proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
|
||||
# If semCheck is set to false, ``when`` will return the verbatim AST of
|
||||
# the correct branch. Otherwise the AST will be passed through semStmt.
|
||||
@@ -2760,7 +2689,7 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
|
||||
checkSonsLen(it, 2, c.config)
|
||||
if whenNimvm:
|
||||
if semCheck:
|
||||
it[1] = semNimvmBranch(c, it[1], flags)
|
||||
it[1] = semExpr(c, it[1], flags)
|
||||
typ = commonType(c, typ, it[1].typ)
|
||||
result = n # when nimvm is not elimited until codegen
|
||||
elif c.inGenericContext > 0:
|
||||
@@ -2791,8 +2720,7 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
|
||||
discard
|
||||
elif result == nil or whenNimvm:
|
||||
if semCheck:
|
||||
it[0] = if whenNimvm: semNimvmBranch(c, it[0], flags)
|
||||
else: semExpr(c, it[0], flags)
|
||||
it[0] = semExpr(c, it[0], flags)
|
||||
typ = commonType(c, typ, it[0].typ)
|
||||
if typ != nil and typ.kind != tyUntyped:
|
||||
it[0] = fitNode(c, typ, it[0], it[0].info)
|
||||
@@ -3390,7 +3318,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
|
||||
c.config.expandNodeResult = $n
|
||||
suggestQuit()
|
||||
|
||||
if c.config.ideActive: suggestExpr(c, n)
|
||||
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
|
||||
if nfSem in n.flags: return
|
||||
case n.kind
|
||||
of nkIdent, nkAccQuoted:
|
||||
|
||||
@@ -19,7 +19,7 @@ import std/[strutils, math, strtabs]
|
||||
#from system/memory import nimCStrLen
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[assertions]
|
||||
import std/[assertions, formatfloat]
|
||||
|
||||
proc errorType*(g: ModuleGraph): PType =
|
||||
## creates a type representing an error state
|
||||
@@ -121,6 +121,21 @@ proc ordinalValToString*(a: PNode; g: ModuleGraph): string =
|
||||
else:
|
||||
result = $x
|
||||
|
||||
proc isFloatRange(t: PType): bool {.inline.} =
|
||||
result = t.kind == tyRange and t.elementType.kind in {tyFloat..tyFloat128}
|
||||
|
||||
proc isIntRange(t: PType): bool {.inline.} =
|
||||
result = t.kind == tyRange and t.elementType.kind in {
|
||||
tyInt..tyInt64, tyUInt8..tyUInt32}
|
||||
|
||||
proc pickIntRange(a, b: PType): PType =
|
||||
if isIntRange(a): result = a
|
||||
elif isIntRange(b): result = b
|
||||
else: result = a
|
||||
|
||||
proc isIntRangeOrLit(t: PType): bool =
|
||||
result = isIntRange(t) or isIntLit(t)
|
||||
|
||||
proc evalOp(m: TMagic, n, a, b, c: PNode; idgen: IdGenerator; g: ModuleGraph): PNode =
|
||||
# b and c may be nil
|
||||
result = nil
|
||||
@@ -377,6 +392,11 @@ proc rangeCheck(n: PNode, value: Int128; g: ModuleGraph) =
|
||||
localError(g.config, n.info, "cannot convert " & $value &
|
||||
" to " & typeToString(n.typ))
|
||||
|
||||
proc floatRangeCheck(n: PNode, value: BiggestFloat; g: ModuleGraph) =
|
||||
if value < firstFloat(n.typ) or value > lastFloat(n.typ):
|
||||
localError(g.config, n.info, "cannot convert " & $value &
|
||||
" to " & typeToString(n.typ))
|
||||
|
||||
proc foldConv(n, a: PNode; idgen: IdGenerator; g: ModuleGraph; check = false): PNode =
|
||||
let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc})
|
||||
let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc})
|
||||
@@ -456,12 +476,7 @@ proc foldArrayAccess(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNo
|
||||
#localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n)
|
||||
of nkBracket:
|
||||
idx -= toInt64(firstOrd(g.config, x.typ))
|
||||
if isDefaultBroadcastArray(x, g.config):
|
||||
# compact default array: any in-bounds index folds to the default element
|
||||
if idx >= 0 and idx < toInt64(lengthOrd(g.config, x.typ.skipTypes(abstractInst))):
|
||||
result = copyTree(x[0])
|
||||
else: result = nil
|
||||
elif idx >= 0 and idx < x.len: result = x[int(idx)]
|
||||
if idx >= 0 and idx < x.len: result = x[int(idx)]
|
||||
else:
|
||||
result = nil
|
||||
#localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n)
|
||||
@@ -595,21 +610,10 @@ proc getConstExpr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode
|
||||
var s = n.sym
|
||||
case s.kind
|
||||
of skEnumField:
|
||||
when defined(icDbg):
|
||||
if n.typ == nil:
|
||||
echo "ENUMFIELD niltyp sym=", s.name.s, " symtyp=",
|
||||
(if s.typ == nil: "nil" else: $s.typ.kind), " lazy=", nfLazyType in n.flags,
|
||||
" symstate=", s.state, " symid=", s.itemId
|
||||
result = newIntNodeT(toInt128(s.position), n, idgen, g)
|
||||
of skConst:
|
||||
case s.magic
|
||||
of mIsMainModule:
|
||||
# Under `nim m` (IC) `sfMainModule` is set on every module that is being
|
||||
# compiled (so it writes its own NIF), so it cannot answer `isMainModule`;
|
||||
# the IC build file marks the real entry point with `--isMainModule:on`.
|
||||
let isMain = if g.config.cmd == cmdM: g.config.isMainModule
|
||||
else: sfMainModule in m.flags
|
||||
result = newIntNodeT(toInt128(ord(isMain)), n, idgen, g)
|
||||
of mIsMainModule: result = newIntNodeT(toInt128(ord(sfMainModule in m.flags)), n, idgen, g)
|
||||
of mCompileDate: result = newStrNodeT(getDateStr(), n, g)
|
||||
of mCompileTime: result = newStrNodeT(getClockStr(), n, g)
|
||||
of mCpuEndian: result = newIntNodeT(toInt128(ord(CPU[g.config.target.targetCPU].endian)), n, idgen, g)
|
||||
|
||||
@@ -129,19 +129,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
|
||||
result.typ = nil
|
||||
onUse(n.info, s)
|
||||
of skParam:
|
||||
if s.typ != nil and s.typ.kind == tyStatic and s.typ.n != nil:
|
||||
# The enclosing routine gives this static parameter a concrete value.
|
||||
# Keep that value so the nested generic can fold it as a compile-time
|
||||
# expression instead of generating a runtime parameter reference.
|
||||
result = s.typ.n
|
||||
elif s.owner == c.p.owner:
|
||||
# Parameters of the routine currently being semchecked stay as local
|
||||
# identifiers
|
||||
result = n
|
||||
else:
|
||||
# Preserve captured outer parameters so nested generic procs can still
|
||||
# see them after the generic pre-pass.
|
||||
result = newSymNode(s, n.info)
|
||||
result = n
|
||||
onUse(n.info, s)
|
||||
of skType:
|
||||
if (s.typ != nil) and
|
||||
@@ -238,7 +226,7 @@ proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags,
|
||||
if s.kind == skType: # don't put types in sym choice
|
||||
var ambig = false
|
||||
if candidates.len > 1:
|
||||
discard searchInScopes(c, ident, ambig)
|
||||
let s2 = searchInScopes(c, ident, ambig)
|
||||
result = newDot(result, semGenericStmtSymbol(c, n, s, ctx, flags,
|
||||
isAmbiguous = ambig, fromDotExpr = true))
|
||||
else:
|
||||
@@ -278,7 +266,7 @@ proc semGenericStmt(c: PContext, n: PNode,
|
||||
when defined(nimsuggest):
|
||||
if withinTypeDesc in flags: inc c.inTypeContext
|
||||
|
||||
#if conf.ideActive: suggestStmt(c, n)
|
||||
#if conf.cmd == cmdIdeTools: suggestStmt(c, n)
|
||||
semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody)
|
||||
|
||||
case n.kind
|
||||
@@ -686,3 +674,4 @@ proc semConceptBody(c: PContext, n: PNode): PNode =
|
||||
)
|
||||
result = semGenericStmt(c, n, {withinConcept}, ctx)
|
||||
semIdeForTemplateOrGeneric(c, result, ctx.cursorInBody)
|
||||
|
||||
|
||||
@@ -93,37 +93,6 @@ proc genericCacheGet(g: ModuleGraph; genericSym: PSym, entry: TInstantiation;
|
||||
if (inst.compilesId == 0 or inst.compilesId == id) and sameInstantiation(entry, inst[]):
|
||||
return inst.sym
|
||||
|
||||
proc sameBindingSnapshot(pt: LayeredIdTable; inst: PInstantiation): bool =
|
||||
if inst.bindings.len == 0:
|
||||
return false
|
||||
const flags = {ExactTypeDescValues, ExactGcSafety, PickyCAliases}
|
||||
for binding in inst.bindings:
|
||||
let value = lookupById(pt, binding.key)
|
||||
if value == nil or
|
||||
(value != binding.value and
|
||||
not compareTypes(value, binding.value, flags = flags)):
|
||||
return false
|
||||
# Reject a binding that wasn't visible in the saved mapping. Duplicate keys
|
||||
# in parent layers are harmless because lookupById resolves the top layer.
|
||||
for key, _ in pt.pairs:
|
||||
var found = false
|
||||
for binding in inst.bindings:
|
||||
if key == binding.key:
|
||||
found = true
|
||||
break
|
||||
if not found: return false
|
||||
result = true
|
||||
|
||||
proc genericCacheGetFromBindings(g: ModuleGraph; genericSym: PSym,
|
||||
pt: LayeredIdTable; id: CompilesId;
|
||||
module: PSym): PSym =
|
||||
result = nil
|
||||
for inst in procInstCacheItems(g, genericSym):
|
||||
if inst.sym != nil and inst.sym.itemId.module == module.position and
|
||||
(inst.compilesId == 0 or inst.compilesId == id) and
|
||||
sameBindingSnapshot(pt, inst):
|
||||
return inst.sym
|
||||
|
||||
when false:
|
||||
proc `$`(x: PSym): string =
|
||||
result = x.name.s & " " & " id " & $x.id
|
||||
@@ -150,44 +119,11 @@ proc freshGenSyms(c: PContext; n: PNode, owner, orig: PSym, symMap: var SymMappi
|
||||
|
||||
proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind)
|
||||
|
||||
proc aliasLoadedTypedescParams(c: PContext, instantiated, orig: PSym): bool =
|
||||
## When the generic being instantiated had its body LOADED from a NIF (only
|
||||
## `nim m`/`nim nifc`, only for a generic owned by another module), that body
|
||||
## re-sems from plain identifiers — ast2nif serialises locals/params as idents,
|
||||
## not `nkSym`. A `T: typedesc[...]` param referenced as a type must then
|
||||
## resolve `T` to the bound type, but the instantiated skParam carries the
|
||||
## concrete type `instantiateProcType` typedesc-skipped it to, which an ident
|
||||
## lookup cannot use as a type name. Shadow each such param with an `skType`
|
||||
## alias of the same name in a fresh scope layer (the alias is exactly how Nim
|
||||
## models "this name denotes a type"). In-process bodies reach the param as
|
||||
## `nkSym` and never take this path, hence the command gate.
|
||||
##
|
||||
## Returns true iff a scope layer was opened; the caller must `closeScope`.
|
||||
if c.config.cmd notin {cmdM, cmdNifC} or orig == nil or
|
||||
orig.itemId.module == c.module.position or
|
||||
orig.typ == nil or orig.typ.n == nil:
|
||||
return false
|
||||
result = false
|
||||
let procParams = instantiated.typ.n
|
||||
for i in 1..<min(procParams.len, orig.typ.n.len):
|
||||
if orig.typ.n[i].kind != nkSym: continue
|
||||
let origParamTyp = orig.typ.n[i].sym.typ
|
||||
if origParamTyp != nil and origParamTyp.kind == tyTypeDesc and
|
||||
tfUnresolved in origParamTyp.flags:
|
||||
if not result:
|
||||
openScope(c)
|
||||
result = true
|
||||
let p = procParams[i].sym
|
||||
let alias = newSym(skType, p.name, c.idgen, instantiated, p.info)
|
||||
alias.typ = p.typ
|
||||
addDecl(c, alias)
|
||||
|
||||
proc instantiateBody(c: PContext, n, params: PNode, result, orig: PSym) =
|
||||
if n[bodyPos].kind != nkEmpty:
|
||||
let procParams = result.typ.n
|
||||
for i in 1..<procParams.len:
|
||||
addDecl(c, procParams[i].sym)
|
||||
let aliasLayer = aliasLoadedTypedescParams(c, result, orig)
|
||||
maybeAddResult(c, result, result.ast)
|
||||
|
||||
inc c.inGenericInst
|
||||
@@ -216,7 +152,6 @@ proc instantiateBody(c: PContext, n, params: PNode, result, orig: PSym) =
|
||||
excl(result, sfForward)
|
||||
trackProc(c, result, result.ast[bodyPos])
|
||||
dec c.inGenericInst
|
||||
if aliasLayer: closeScope(c)
|
||||
|
||||
proc fixupInstantiatedSymbols(c: PContext, s: PSym) =
|
||||
for i in 0..<c.generics.len:
|
||||
@@ -310,7 +245,7 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
let originalParams = result.n
|
||||
result.n = originalParams.shallowCopy
|
||||
for i in 1 ..< originalParams.len:
|
||||
var resulti = originalParams[i].sym.typ
|
||||
let resulti = originalParams[i].sym.typ
|
||||
# twrong_field_caching requires these 'resetIdTable' calls:
|
||||
if i > FirstParamAt:
|
||||
resetIdTable(cl.symMap)
|
||||
@@ -323,11 +258,6 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
let needsStaticSkipping = resulti.kind == tyFromExpr
|
||||
let needsTypeDescSkipping = resulti.kind == tyTypeDesc and tfUnresolved in resulti.flags
|
||||
if resulti.kind == tyFromExpr:
|
||||
if resulti.state == Sealed:
|
||||
# The generic was loaded from a NIF; do not brand the shared original.
|
||||
# A tyFromExpr is a placeholder that `replaceTypeVarsT` resolves away,
|
||||
# so a copy carries no identity that later comparisons could miss.
|
||||
resulti = copyType(resulti, c.idgen, resulti.owner)
|
||||
resulti.incl tfNonConstExpr
|
||||
var paramType = replaceTypeVarsT(cl, resulti)
|
||||
if needsStaticSkipping:
|
||||
@@ -346,12 +276,6 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
let param = copySym(oldParam, c.idgen)
|
||||
setOwner(param, prc)
|
||||
param.typ = paramType
|
||||
when defined(icDbgRefc):
|
||||
echo "[icInst] ", prc.name.s, " param ", oldParam.name.s,
|
||||
": ", typeToString(resulti), " (kind=", resulti.kind,
|
||||
" itemId=", resulti.itemId.module, ".", resulti.itemId.item,
|
||||
" flags=", resulti.flags, ") -> ", typeToString(paramType),
|
||||
" (kind=", paramType.kind, ")"
|
||||
|
||||
# The default value is instantiated and fitted against the final
|
||||
# concrete param type. We avoid calling `replaceTypeVarsN` on the
|
||||
@@ -359,9 +283,6 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
if oldParam.ast != nil:
|
||||
var def = oldParam.ast.copyTree
|
||||
if def.typ.kind == tyFromExpr:
|
||||
if def.typ.state == Sealed:
|
||||
# `copyTree` shares types; see the `resulti` comment above.
|
||||
def.typ = copyType(def.typ, c.idgen, def.typ.owner)
|
||||
def.typ.incl tfNonConstExpr
|
||||
if not isIntLit(def.typ):
|
||||
def = prepareNode(cl, def)
|
||||
@@ -407,10 +328,6 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
|
||||
eraseVoidParams(result)
|
||||
skipIntLiteralParams(result, c.idgen)
|
||||
|
||||
# The signature belongs to the INSTANCE, not to the generic it was copied
|
||||
# from: `instCopyType` above kept the generic's owner, and every parameter has
|
||||
# already been re-owned with `setOwner(param, prc)`.
|
||||
setOwner(result, prc)
|
||||
prc.typ = result
|
||||
popInfoContext(c.config)
|
||||
|
||||
@@ -457,18 +374,6 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
## parameters to their concrete types within the generic instance.
|
||||
# no need to instantiate generic templates/macros:
|
||||
internalAssert c.config, fn.kind notin {skMacro, skTemplate}
|
||||
# IC: instantiating `fn` consumes its generic body in the current module's
|
||||
# sem — record a NeedsImpl (strong) edge to `fn`'s module. The iface cookie
|
||||
# hashes only signatures now, so a generic body edit moves only the impl
|
||||
# cookie, and just the modules that instantiated it re-sem.
|
||||
recordIcImplDep(c.graph, fn)
|
||||
let canUseBindingCache = c.inGenericContext == 0 and c.matchedConcept == nil
|
||||
if canUseBindingCache:
|
||||
result = genericCacheGetFromBindings(c.graph, fn, pt, c.compilesContextId,
|
||||
c.module)
|
||||
if result != nil:
|
||||
if result.kind == skMethod: finishMethod(c, result)
|
||||
return
|
||||
# generates an instantiated proc
|
||||
if c.instCounter > 50:
|
||||
globalError(c.config, info, "generic instantiation too nested")
|
||||
@@ -478,6 +383,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
defer:
|
||||
dec c.instCounter
|
||||
c.inTypeofContext = currentTypeofContext
|
||||
# careful! we copy the whole AST including the possibly nil body!
|
||||
var n = copyTree(fn.ast)
|
||||
# NOTE: for access of private fields within generics from a different module
|
||||
# we set the friend module:
|
||||
let producer = getModule(fn)
|
||||
@@ -496,6 +403,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
setOwner(result, c.module)
|
||||
else:
|
||||
setOwner(result, fn)
|
||||
result.ast = n
|
||||
pushOwner(c, result)
|
||||
|
||||
# mixin scope:
|
||||
@@ -503,10 +411,11 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
fillMixinScope(c)
|
||||
|
||||
openScope(c)
|
||||
let gp = fn.ast[genericParamsPos]
|
||||
let gp = n[genericParamsPos]
|
||||
if gp.kind != nkGenericParams:
|
||||
# bug #22137
|
||||
globalError(c.config, info, "generic instantiation too nested")
|
||||
n[namePos] = newSymNode(result)
|
||||
pushInfoContext(c.config, info, fn.detailedInfo)
|
||||
var entry = TInstantiation.new
|
||||
entry.sym = result
|
||||
@@ -522,15 +431,6 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
entry.concreteTypes[i] = s.typ
|
||||
inc i
|
||||
entry.genericParamsCount = i
|
||||
if canUseBindingCache:
|
||||
for key, _ in pt.pairs:
|
||||
var seen = false
|
||||
for binding in entry.bindings:
|
||||
if binding.key == key:
|
||||
seen = true
|
||||
break
|
||||
if not seen:
|
||||
entry.bindings.add (key, lookupById(pt, key))
|
||||
c.matchedConcept = nil
|
||||
pushProcCon(c, result)
|
||||
instantiateProcType(c, pt, result, info)
|
||||
@@ -540,14 +440,9 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
#echo "INSTAN ", fn.name.s, " ", typeToString(result.typ), " ", entry.concreteTypes.len
|
||||
if tfTriggersCompileTime in result.typ.flags:
|
||||
incl(result, sfCompileTime)
|
||||
n[genericParamsPos] = c.graph.emptyNode
|
||||
var oldPrc = genericCacheGet(c.graph, fn, entry[], c.compilesContextId)
|
||||
if oldPrc == nil:
|
||||
# The signature has to be instantiated before the cache can be queried,
|
||||
# but cache hits don't need a private copy of the generic's full AST.
|
||||
var n = copyTree(fn.ast)
|
||||
result.ast = n
|
||||
n[namePos] = newSymNode(result)
|
||||
n[genericParamsPos] = c.graph.emptyNode
|
||||
# we MUST not add potentially wrong instantiations to the caching mechanism.
|
||||
# This means recursive instantiations behave differently when in
|
||||
# a ``compiles`` context but this is the lesser evil. See
|
||||
@@ -560,10 +455,6 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
|
||||
# This is needed for cyclic module dependencies where generic instances
|
||||
# may be created in one module but referenced from another.
|
||||
logGenericInstance(c.graph, result)
|
||||
# Under IC the instance's NIF name must be canonical across modules:
|
||||
# derive its `disamb` from the instantiation identity (generic +
|
||||
# concrete types) instead of the per-module counter.
|
||||
setInstanceDisamb(c.graph, result, fn, entry.concreteTypes)
|
||||
# bug #12985 bug #22913
|
||||
# TODO: use the context of the declaration of generic functions instead
|
||||
# TODO: consider fixing options as well
|
||||
|
||||
@@ -35,16 +35,23 @@ proc semAddr(c: PContext; n: PNode): PNode =
|
||||
let x = semExprWithType(c, n)
|
||||
if x.kind == nkSym:
|
||||
x.sym.flagsImpl.incl(sfAddrTaken)
|
||||
let aa = isAssignable(c, x)
|
||||
if aa notin {arLValue, arLocalLValue, arAddressableConst, arLentValue} and
|
||||
(aa != arDiscriminant or c.inUncheckedAssignSection <= 0):
|
||||
if isAssignable(c, x) notin {arLValue, arLocalLValue, arAddressableConst, arLentValue}:
|
||||
localError(c.config, n.info, errExprHasNoAddress)
|
||||
result.add x
|
||||
result.typ = makePtrType(c, x.typ.skipTypes({tySink}))
|
||||
|
||||
proc semTypeOf(c: PContext; n: PNode): PNode =
|
||||
let typExpr = semTypeOfImpl(c, n)
|
||||
var m = BiggestInt 1 # typeOfIter
|
||||
if n.len == 3:
|
||||
let mode = semConstExpr(c, n[2])
|
||||
if mode.kind != nkIntLit:
|
||||
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
|
||||
else:
|
||||
m = mode.intVal
|
||||
result = newNodeI(nkTypeOfExpr, n.info)
|
||||
inc c.inTypeofContext
|
||||
defer: dec c.inTypeofContext # compiles can raise an exception
|
||||
let typExpr = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {})
|
||||
result.add typExpr
|
||||
if typExpr.typ.kind == tyFromExpr:
|
||||
typExpr.typ.incl tfNonConstExpr
|
||||
@@ -225,7 +232,10 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
|
||||
of "stripGenericParams":
|
||||
result = uninstantiate(operand).toNode(traitCall.info)
|
||||
of "supportsCopyMem":
|
||||
result = newIntNodeT(toInt128(ord(supportsCopyMem(operand))), traitCall, c.idgen, c.graph)
|
||||
let t = operand.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
|
||||
let complexObj = containsGarbageCollectedRef(t) or
|
||||
hasDestructor(t)
|
||||
result = newIntNodeT(toInt128(ord(not complexObj)), traitCall, c.idgen, c.graph)
|
||||
of "canFormCycles":
|
||||
result = newIntNodeT(toInt128(ord(types.canFormAcycle(c.graph, operand))), traitCall, c.idgen, c.graph)
|
||||
of "hasDefaultValue":
|
||||
@@ -239,13 +249,10 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
|
||||
assert operand.kind == tyTuple, $operand.kind
|
||||
result = newIntNodeT(toInt128(operand.len), traitCall, c.idgen, c.graph)
|
||||
of "distinctBase":
|
||||
var arg = operand.skipTypes(skippedTypes)
|
||||
var arg = operand.skipTypes({tyGenericInst})
|
||||
let rec = semConstExpr(c, traitCall[2]).intVal != 0
|
||||
while true:
|
||||
let distinctArg = arg.skipTypes(skippedTypes + {tyGenericInst})
|
||||
if distinctArg.kind != tyDistinct:
|
||||
break
|
||||
arg = distinctArg.base.skipTypes(skippedTypes)
|
||||
while arg.kind == tyDistinct:
|
||||
arg = arg.base.skipTypes(skippedTypes + {tyGenericInst})
|
||||
if not rec: break
|
||||
result = getTypeDescNode(c, arg, operand.owner, traitCall.info)
|
||||
of "rangeBase":
|
||||
@@ -478,15 +485,6 @@ proc turnFinalizerIntoDestructor(c: PContext; orig: PSym; info: TLineInfo): PSym
|
||||
# proc signature:
|
||||
result.typ = newProcType(result.info, c.idgen, result)
|
||||
result.typ.addParam newParam
|
||||
# `transform` only rewrites the PARAMETER, so the copied AST still names `orig`
|
||||
# at `namePos`. Make the definition name itself, the invariant every other
|
||||
# routine AST keeps: the NIF writer re-derives a routine's serialized AST from
|
||||
# `ast[namePos].sym.ast` (ast2nif's `nkProcDef` branch), so a stale name node
|
||||
# made this proc serialize `orig`'s body — whose parameter belongs to `orig`.
|
||||
# Lambda lifting then saw the body's parameter as a variable captured from
|
||||
# another proc and aborted with "internal error: environment misses: x".
|
||||
if result.ast != nil and result.ast.safeLen > namePos:
|
||||
result.ast[namePos] = newSymNode(result, result.info)
|
||||
|
||||
proc semQuantifier(c: PContext; n: PNode): PNode =
|
||||
checkSonsLen(n, 2, c.config)
|
||||
@@ -618,9 +616,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
|
||||
of mAsgn:
|
||||
case n[0].sym.name.s
|
||||
of "=", "=copy":
|
||||
result = replaceHookMagic(c, n, attachedAsgn)
|
||||
result = semAsgnOpr(c, n, nkAsgn)
|
||||
of "=sink":
|
||||
result = replaceHookMagic(c, n, attachedSink)
|
||||
result = semAsgnOpr(c, n, nkSinkAsgn)
|
||||
else:
|
||||
result = semShallowCopy(c, n, flags)
|
||||
of mIsPartOf: result = semIsPartOf(c, n, flags)
|
||||
@@ -702,10 +700,5 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
|
||||
if n[1].kind in {nkStmtListExpr, nkBlockExpr,
|
||||
nkIfExpr, nkCaseStmt, nkTryStmt}:
|
||||
localError(c.config, n.info, "Nested expressions cannot be moved: '" & $n[1] & "'")
|
||||
of mMove:
|
||||
result = n
|
||||
if isCursor(n[1]):
|
||||
localError(c.config, n.info, errFailedMove,
|
||||
"cannot move cursor '" & $n[1] & "'; a cursor does not own its value")
|
||||
else:
|
||||
result = n
|
||||
|
||||
@@ -440,7 +440,7 @@ proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext =
|
||||
proc computeRequiresInit(c: PContext, t: PType): bool =
|
||||
assert t.kind == tyObject
|
||||
var constrCtx = initConstrContext(t, newNode(nkObjConstr))
|
||||
discard semConstructTypeAux(c, constrCtx, {efWantNoDefaults})
|
||||
let initResult = semConstructTypeAux(c, constrCtx, {efWantNoDefaults})
|
||||
constrCtx.missingFields.len > 0
|
||||
|
||||
proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
|
||||
@@ -450,7 +450,7 @@ proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
|
||||
assert objType != nil
|
||||
if objType.kind == tyObject:
|
||||
var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info))
|
||||
discard semConstructTypeAux(c, constrCtx, {efIgnoreDefaults})
|
||||
let initResult = semConstructTypeAux(c, constrCtx, {efIgnoreDefaults})
|
||||
if constrCtx.missingFields.len > 0:
|
||||
localError(c.config, info,
|
||||
"The $1 type doesn't have a default value. The following fields must be initialized: $2." % [typeToString(t), listSymbolNames(constrCtx.missingFields)])
|
||||
@@ -486,11 +486,6 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
|
||||
# we have to watch out, there are also 'owned proc' types that can be used
|
||||
# multiple times as long as they don't have closures.
|
||||
result.typ.incl tfHasOwned
|
||||
if t.kind == tyForward and efDetermineType in flags:
|
||||
# a forward object type does not error during determine-type analysis;
|
||||
# it now stays unresolved long enough for the existing delayed field-default pass to resolve it after the type section finishes.
|
||||
result.typ = t
|
||||
return result
|
||||
if t.kind != tyObject:
|
||||
return localErrorNode(c, result, if t.kind != tyGenericBody:
|
||||
"object constructor needs an object type".dup(addTypeNodeDeclaredLoc(c.config, t))
|
||||
|
||||
@@ -82,7 +82,6 @@ type
|
||||
guards: TModel # nested guards
|
||||
locked: seq[PNode] # locked locations
|
||||
gcUnsafe, isRecursive, isTopLevel, hasSideEffect, inEnforcedGcSafe: bool
|
||||
canRaiseDefect: bool # defects are deliberately omitted from `exc`
|
||||
isInnerProc: bool
|
||||
inEnforcedNoSideEffects: bool
|
||||
isArrayIndexing: bool
|
||||
@@ -94,7 +93,6 @@ type
|
||||
graph: ModuleGraph
|
||||
c: PContext
|
||||
escapingParams: IntSet
|
||||
inNimvmBranch: int
|
||||
PEffects = var TEffects
|
||||
|
||||
const
|
||||
@@ -109,7 +107,7 @@ proc getObjDepth(t: PType): (int, ItemId) =
|
||||
x = skipTypes(x, skipPtrs)
|
||||
if x.kind != tyObject:
|
||||
return (-3, default(ItemId))
|
||||
stack.add x.bindingId
|
||||
stack.add x.itemId
|
||||
x = x.baseClass
|
||||
inc(result[0])
|
||||
result[1] = stack[^2]
|
||||
@@ -142,11 +140,6 @@ proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo; explicit
|
||||
createTypeBoundOps(tracked.graph, tracked.c, realType.lastSon, info)
|
||||
|
||||
createTypeBoundOps(tracked.graph, tracked.c, typ, info, tracked.c.idgen)
|
||||
for kind in TTypeAttachedOp:
|
||||
let op = getAttachedOp(tracked.graph, typ, kind)
|
||||
if op != nil and sfNeverRaises notin op.flags:
|
||||
tracked.canRaiseDefect = true
|
||||
break
|
||||
if tracked.config.selectedGC == gcRefc or
|
||||
optSeqDestructors in tracked.config.globalOptions or
|
||||
tfHasAsgn in typ.flags:
|
||||
@@ -199,23 +192,6 @@ proc shouldWarnRangeConversion(conf: ConfigRef; info: TLineInfo; formalType, arg
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc conversionCanRaiseDefect(conf: ConfigRef; destType, sourceType: PType): bool =
|
||||
## Keep this in sync with the range checks introduced by `transformConv`.
|
||||
let
|
||||
dest = destType.skipTypes(abstractVarRange)
|
||||
source = sourceType.skipTypes(abstractVarRange)
|
||||
case dest.kind
|
||||
of tyInt..tyInt64, tyEnum, tyChar, tyUInt8..tyUInt32:
|
||||
if not source.isOrdinalType:
|
||||
result = dest.kind in tyInt..tyInt64
|
||||
else:
|
||||
result = firstOrd(conf, destType) > firstOrd(conf, sourceType) or
|
||||
lastOrd(conf, sourceType) > lastOrd(conf, destType)
|
||||
of tyFloat..tyFloat128:
|
||||
result = destType.skipTypes(abstractVar).kind == tyRange
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc lockLocations(a: PEffects; pragma: PNode) =
|
||||
if pragma.kind != nkExprColonExpr:
|
||||
localError(a.config, pragma.info, "locks pragma without argument")
|
||||
@@ -518,38 +494,9 @@ proc addRaiseEffect(a: PEffects, e, comesFrom: PNode) =
|
||||
if sameType(a.graph.excType(aa[i]), a.graph.excType(e)): return
|
||||
|
||||
if e.typ != nil:
|
||||
if isDefectException(e.typ):
|
||||
a.canRaiseDefect = true
|
||||
else:
|
||||
if not isDefectException(e.typ):
|
||||
throws(a.exc, e, comesFrom)
|
||||
|
||||
proc skipHiddenConv(n: PNode): PNode =
|
||||
result = n
|
||||
while true:
|
||||
case result.kind
|
||||
of nkHiddenStdConv, nkHiddenSubConv:
|
||||
result = result[1]
|
||||
else: break
|
||||
|
||||
proc addRaiseEffectsFromExpr(a: PEffects, e, comesFrom: PNode) =
|
||||
if e.isNil:
|
||||
return
|
||||
case e.kind
|
||||
of nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr:
|
||||
if e.len > 0:
|
||||
addRaiseEffectsFromExpr(a, e.lastSon.skipHiddenConv, comesFrom)
|
||||
of nkIfExpr, nkIfStmt:
|
||||
for branch in items(e):
|
||||
if branch.len > 0:
|
||||
addRaiseEffectsFromExpr(a, branch.lastSon.skipHiddenConv, comesFrom)
|
||||
of nkCaseStmt:
|
||||
for i in 1..<e.len:
|
||||
let branch = e[i]
|
||||
if branch.len > 0:
|
||||
addRaiseEffectsFromExpr(a, branch.lastSon.skipHiddenConv, comesFrom)
|
||||
else:
|
||||
addRaiseEffect(a, e, comesFrom)
|
||||
|
||||
proc addTag(a: PEffects, e, comesFrom: PNode) =
|
||||
var aa = a.tags
|
||||
for i in 0..<aa.len:
|
||||
@@ -862,10 +809,6 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; ar
|
||||
markSideEffect(tracked, a, n.info)
|
||||
let paramType = if formals != nil and argIndex < formals.signatureLen: formals[argIndex] else: nil
|
||||
if paramType != nil and paramType.kind in {tyVar}:
|
||||
let arg = n.skipAddr()
|
||||
if isSsoStringIndex(tracked.config, arg):
|
||||
localError(tracked.config, arg.info,
|
||||
"expression '$1' is immutable, not 'var'" % renderNotLValue(arg))
|
||||
invalidateFacts(tracked.guards, n)
|
||||
if n.kind == nkSym and isLocalSym(tracked, n.sym):
|
||||
makeVolatile(tracked, n.sym)
|
||||
@@ -1006,6 +949,7 @@ proc trackIf(tracked: PEffects, n: PNode) =
|
||||
|
||||
proc trackBlock(tracked: PEffects, n: PNode; typ: PType) =
|
||||
if n.kind in {nkStmtList, nkStmtListExpr}:
|
||||
let myBlock = tracked.currentBlock
|
||||
var oldState = -1
|
||||
for i in 0..<n.len:
|
||||
if hasSubnodeWith(n[i], nkBreakStmt):
|
||||
@@ -1028,6 +972,11 @@ proc trackBlock(tracked: PEffects, n: PNode; typ: PType) =
|
||||
else:
|
||||
track(tracked, n)
|
||||
|
||||
proc cstringCheck(tracked: PEffects; n: PNode) =
|
||||
if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]);
|
||||
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
|
||||
message(tracked.config, n.info, warnUnsafeCode, renderTree(n))
|
||||
|
||||
proc patchResult(c: PEffects; n: PNode) =
|
||||
if n.kind == nkSym and n.sym.kind == skResult:
|
||||
let fn = c.owner
|
||||
@@ -1134,85 +1083,9 @@ proc trackCall(tracked: PEffects; n: PNode) =
|
||||
markSideEffect(tracked, a, n.info)
|
||||
# p's effects are ours too:
|
||||
var a = n[0]
|
||||
if a.kind == nkSym:
|
||||
let s = a.sym
|
||||
case s.magic
|
||||
of mNone:
|
||||
if {sfNeverRaises, sfImportc, sfCompilerProc} * s.flags == {} and
|
||||
(sfSystemModule notin getModule(s).flags or
|
||||
sfSystemRaisesDefect in s.flags):
|
||||
tracked.canRaiseDefect = true
|
||||
of mUnaryMinusI..mAbsI, mAddI..mPred:
|
||||
if optOverflowCheck in tracked.currOptions:
|
||||
tracked.canRaiseDefect = true
|
||||
of mInc, mDec:
|
||||
let typ = n[1].typ.skipTypes({tyGenericInst, tyAlias, tySink,
|
||||
tyVar, tyLent, tyRange, tyDistinct})
|
||||
if optOverflowCheck in tracked.currOptions and
|
||||
typ.kind notin {tyUInt..tyUInt64}:
|
||||
tracked.canRaiseDefect = true
|
||||
of mDivU, mModU:
|
||||
tracked.canRaiseDefect = true
|
||||
of mAddF64..mDivF64:
|
||||
if {optNaNCheck, optInfCheck} * tracked.currOptions != {}:
|
||||
tracked.canRaiseDefect = true
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
tracked.canRaiseDefect = true
|
||||
#if canRaise(a):
|
||||
# echo "this can raise ", tracked.config $ n.info
|
||||
let op = a.typ
|
||||
# A routine whose body reaches a compile-time-only magic (`macros.error`,
|
||||
# `slurp`, `gorge`, `getAst`, …) can never be code-generated — the C/JS
|
||||
# backends reject those magics (ccgexprs `errXMustBeCompileTime`). Such a
|
||||
# routine is compile-time-only by construction; mark it `sfCompileTime` so it
|
||||
# is treated uniformly as such. Non-IC pruned it by demand-driven codegen, but
|
||||
# the per-module IC backend emits every owned routine (no DCE) and would
|
||||
# otherwise feed the magic to codegen. Mirrors the `tfTriggersCompileTime ->
|
||||
# sfCompileTime` path in `semProcAux`.
|
||||
#
|
||||
# GATE TO THE IC STAGES ONLY (`cmdM` sem + `cmdNifC` cg). The magic can reach a
|
||||
# runtime proc's body via an INLINED TEMPLATE (not a macro/template *owner*, so
|
||||
# the `insideMeta` walk below can't see it) — e.g. confutils' runtime
|
||||
# `addConfigFile`/json-serialization's `inputFile` expand a serialization
|
||||
# template that pastes a `getAst`/`quote` magic inline. Under plain `nim c` such
|
||||
# a proc still code-generates fine (the magic folds / is demand-pruned), so
|
||||
# marking it `sfCompileTime` there is a pure regression: "request to generate
|
||||
# code for .compileTime proc". Only the emit-everything IC backend needs the
|
||||
# mark, so restrict it to `{cmdM, cmdNifC}` (was `!= cmdNimscript`, which
|
||||
# wrongly swept in `cmdCompileToC`/JS/`cmdCheck`).
|
||||
if a.kind == nkSym and a.sym.magic in {mNLen..mNError, mSlurp..mQuoteAst} and
|
||||
tracked.owner != nil and tracked.owner.kind in routineKinds and
|
||||
tracked.config.cmd in {cmdM, cmdNifC} and tracked.inNimvmBranch == 0:
|
||||
# ...but NOT under `nim e`: nimscript has no codegen backend to protect, and
|
||||
# marking a routine `sfCompileTime` makes `semExpr` eagerly fold calls to it
|
||||
# at sem time (emConst), where module-level globals it reads have no VM slot
|
||||
# yet — distros' `detectOsWithAllCmd` reaches `gorge` and reads the plain
|
||||
# global `unameRes` → "cannot evaluate at compile time: unameRes". In the
|
||||
# normal nimscript run (emRepl) the module's var section runs first and the
|
||||
# slot exists, so the marking is both unnecessary and harmful here.
|
||||
#
|
||||
# ...and NOT if the routine is — or is nested inside — a macro/template:
|
||||
# those are VM-only (never code-generated), so the per-module IC backend has
|
||||
# nothing to protect there, while `sfCompileTime` on a macro-internal nested
|
||||
# closure breaks its captured-variable access in the VM ("cannot evaluate at
|
||||
# compile time: n" — `tests/macros/tmacros1`'s `innerProc` reading the
|
||||
# macro-local `n`). Walk the owner chain and bail on the first
|
||||
# skMacro/skTemplate. NB mark `tracked.owner` (the routine that directly
|
||||
# reaches the magic), NOT its outermost enclosing: a runtime proc may legally
|
||||
# nest a compile-time helper — `tests/generics/tunique_type`'s `[]` proc
|
||||
# contains a nested `buildResult` macro — and marking the proc would wrongly
|
||||
# make IT compile-time ("request to generate code for .compileTime proc: []").
|
||||
var encl = tracked.owner
|
||||
var insideMeta = false
|
||||
while encl != nil and encl.kind != skModule:
|
||||
if encl.kind in {skMacro, skTemplate}:
|
||||
insideMeta = true
|
||||
break
|
||||
encl = encl.skipGenericOwner
|
||||
if not insideMeta:
|
||||
incl(tracked.owner, sfCompileTime)
|
||||
if n.typ != nil:
|
||||
if tracked.owner.kind != skMacro and n.typ.skipTypes(abstractVar).kind != tyOpenArray:
|
||||
createTypeBoundOps(tracked, n.typ, n.info)
|
||||
@@ -1254,17 +1127,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
|
||||
else:
|
||||
if laxEffects notin tracked.c.config.legacyFeatures and a.kind == nkSym and
|
||||
a.sym.kind in routineKinds:
|
||||
# A hook reaching here has no effect list yet, i.e. it has not been
|
||||
# effect-tracked. Propagating from its (still unset) type flags would
|
||||
# spuriously mark the caller GC-unsafe/side-effecting: e.g. under
|
||||
# `nim ic` a concrete `=destroy` reached through a generic
|
||||
# instantiation is not analyzed before the instance body is tracked
|
||||
# here. Skip all such hooks (generalizes #25940, which special-cased
|
||||
# `=asgn`/`=sink`/`=dup`); once analyzed they carry an effect list and
|
||||
# take the branch below.
|
||||
let (isHook, _) = findHookKind(a.sym.name.s)
|
||||
if not isHook:
|
||||
propagateEffects(tracked, n, a.sym)
|
||||
propagateEffects(tracked, n, a.sym)
|
||||
else:
|
||||
mergeRaises(tracked, effectList[exceptionEffects], n)
|
||||
mergeTags(tracked, effectList[tagEffects], n)
|
||||
@@ -1301,7 +1164,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
|
||||
var (isHook, opKind) = findHookKind(a.sym.name.s)
|
||||
if isHook:
|
||||
# rebind type bounds operations after createTypeBoundOps call
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
|
||||
let t = n[1].typ.skipTypes({tyAlias, tyVar})
|
||||
if a.sym != getAttachedOp(tracked.graph, t, opKind):
|
||||
createTypeBoundOps(tracked, t, n.info, explicit = true)
|
||||
# replace builtin hooks with lifted ones
|
||||
@@ -1339,18 +1202,14 @@ type
|
||||
PragmaBlockContext = object
|
||||
oldLocked: int
|
||||
enforcedGcSafety, enforceNoSideEffects: bool
|
||||
oldInEnforcedGcSafe, oldInEnforcedNoSideEffects: bool
|
||||
oldExc, oldTags, oldForbids: int
|
||||
exc, tags, forbids: PNode
|
||||
excSource, tagsSource, forbidsSource: PNode
|
||||
|
||||
proc createBlockContext(tracked: PEffects): PragmaBlockContext =
|
||||
var oldForbidsLen = 0
|
||||
if tracked.forbids != nil: oldForbidsLen = tracked.forbids.len
|
||||
result = PragmaBlockContext(oldLocked: tracked.locked.len,
|
||||
enforcedGcSafety: false, enforceNoSideEffects: false,
|
||||
oldInEnforcedGcSafe: tracked.inEnforcedGcSafe,
|
||||
oldInEnforcedNoSideEffects: tracked.inEnforcedNoSideEffects,
|
||||
oldExc: tracked.exc.len, oldTags: tracked.tags.len,
|
||||
oldForbids: oldForbidsLen)
|
||||
|
||||
@@ -1359,27 +1218,25 @@ proc applyBlockContext(tracked: PEffects, bc: PragmaBlockContext) =
|
||||
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
|
||||
|
||||
proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
|
||||
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = bc.oldInEnforcedGcSafe
|
||||
if bc.enforceNoSideEffects:
|
||||
tracked.inEnforcedNoSideEffects = bc.oldInEnforcedNoSideEffects
|
||||
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = false
|
||||
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
|
||||
setLen(tracked.locked, bc.oldLocked)
|
||||
if bc.exc != nil:
|
||||
# beware that 'raises: []' is very different from not saying
|
||||
# anything about 'raises' in the 'cast' at all. Same applies for 'tags'.
|
||||
setLen(tracked.exc.sons, bc.oldExc)
|
||||
for e in bc.exc:
|
||||
addRaiseEffect(tracked, e, if bc.excSource != nil: bc.excSource else: e)
|
||||
addRaiseEffect(tracked, e, e)
|
||||
if bc.tags != nil:
|
||||
setLen(tracked.tags.sons, bc.oldTags)
|
||||
for t in bc.tags:
|
||||
addTag(tracked, t, if bc.tagsSource != nil: bc.tagsSource else: t)
|
||||
addTag(tracked, t, t)
|
||||
if bc.forbids != nil:
|
||||
setLen(tracked.forbids.sons, bc.oldForbids)
|
||||
for t in bc.forbids:
|
||||
addNotTag(tracked, t, if bc.forbidsSource != nil: bc.forbidsSource else: t)
|
||||
addNotTag(tracked, t, t)
|
||||
|
||||
proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext) =
|
||||
let pragma = castPragma[1]
|
||||
proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
|
||||
case whichPragma(pragma)
|
||||
of wGcSafe:
|
||||
bc.enforcedGcSafety = true
|
||||
@@ -1392,7 +1249,6 @@ proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext)
|
||||
else:
|
||||
bc.tags = newNodeI(nkArgList, pragma.info)
|
||||
bc.tags.add n
|
||||
bc.tagsSource = castPragma
|
||||
of wForbids:
|
||||
let n = pragma[1]
|
||||
if n.kind in {nkCurly, nkBracket}:
|
||||
@@ -1400,7 +1256,6 @@ proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext)
|
||||
else:
|
||||
bc.forbids = newNodeI(nkArgList, pragma.info)
|
||||
bc.forbids.add n
|
||||
bc.forbidsSource = castPragma
|
||||
of wRaises:
|
||||
let n = pragma[1]
|
||||
if n.kind in {nkCurly, nkBracket}:
|
||||
@@ -1408,7 +1263,6 @@ proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext)
|
||||
else:
|
||||
bc.exc = newNodeI(nkArgList, pragma.info)
|
||||
bc.exc.add n
|
||||
bc.excSource = castPragma
|
||||
of wUncheckedAssign:
|
||||
discard "handled in sempass1"
|
||||
else:
|
||||
@@ -1445,8 +1299,6 @@ proc allowCStringConv(n: PNode): bool =
|
||||
|
||||
proc track(tracked: PEffects, n: PNode) =
|
||||
case n.kind
|
||||
of nkTypeOfExpr:
|
||||
discard "typeof() never evaluates its operand; not a definite-assignment use"
|
||||
of nkSym:
|
||||
useVar(tracked, n)
|
||||
if n.sym.typ != nil and tfHasAsgn in n.sym.typ.flags:
|
||||
@@ -1460,11 +1312,10 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
else:
|
||||
track(tracked, n[0])
|
||||
of nkRaiseStmt:
|
||||
tracked.canRaiseDefect = true
|
||||
if n[0].kind != nkEmpty:
|
||||
n[0].info = n.info
|
||||
#throws(tracked.exc, n[0])
|
||||
addRaiseEffectsFromExpr(tracked, n[0], n)
|
||||
addRaiseEffect(tracked, n[0], n)
|
||||
for i in 0..<n.safeLen:
|
||||
track(tracked, n[i])
|
||||
createTypeBoundOps(tracked, n[0].typ, n.info)
|
||||
@@ -1489,8 +1340,6 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
for i in 0..<n.len: track(tracked, n[i])
|
||||
tracked.leftPartOfAsgn = oldLeftPartOfAsgn
|
||||
of nkCheckedFieldExpr:
|
||||
if optFieldCheck in tracked.currOptions:
|
||||
tracked.canRaiseDefect = true
|
||||
track(tracked, n[0])
|
||||
if tracked.config.hasWarn(warnProveField) or strictCaseObjects in tracked.c.features:
|
||||
checkFieldAccess(tracked.guards, n, tracked.config, strictCaseObjects in tracked.c.features)
|
||||
@@ -1505,6 +1354,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
dec tracked.leftPartOfAsgn
|
||||
addAsgnFact(tracked.guards, n[0], n[1])
|
||||
notNilCheck(tracked, n[1], n[0].typ)
|
||||
when false: cstringCheck(tracked, n)
|
||||
if tracked.owner.kind != skMacro and n[0].typ.kind notin {tyOpenArray, tyVarargs}:
|
||||
createTypeBoundOps(tracked, n[0].typ, n.info)
|
||||
if n[0].kind != nkSym or not isLocalSym(tracked, n[0].sym):
|
||||
@@ -1554,9 +1404,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
of nkCaseStmt: trackCase(tracked, n)
|
||||
of nkWhen: # This should be a "when nimvm" node.
|
||||
let oldState = tracked.init.len
|
||||
inc tracked.inNimvmBranch
|
||||
track(tracked, n[0][1])
|
||||
dec tracked.inNimvmBranch
|
||||
tracked.init.setLen(oldState)
|
||||
track(tracked, n[1][0])
|
||||
of nkIfStmt, nkIfExpr: trackIf(tracked, n)
|
||||
@@ -1668,7 +1516,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
of wNoSideEffect:
|
||||
bc.enforceNoSideEffects = true
|
||||
of wCast:
|
||||
castBlock(tracked, pragmaList[i], bc)
|
||||
castBlock(tracked, pragmaList[i][1], bc)
|
||||
else:
|
||||
discard
|
||||
applyBlockContext(tracked, bc)
|
||||
@@ -1689,9 +1537,6 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
if tracked.owner.kind != skMacro:
|
||||
createTypeBoundOps(tracked, n.typ, n.info)
|
||||
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
|
||||
if optRangeCheck in tracked.currOptions and
|
||||
conversionCanRaiseDefect(tracked.config, n.typ, n[1].typ):
|
||||
tracked.canRaiseDefect = true
|
||||
if n.kind in {nkHiddenStdConv, nkHiddenSubConv} and
|
||||
n.typ.skipTypes(abstractInst).kind == tyCstring and
|
||||
not allowCStringConv(n[1]):
|
||||
@@ -1703,11 +1548,10 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
message(tracked.config, n.info, warnPtrToCstringConv,
|
||||
$n[1].typ)
|
||||
|
||||
# Check for implicit range conversions. Compile-time constants are already
|
||||
# fully known here, so only non-constant values need the downsizing warning.
|
||||
# Check for implicit range conversions
|
||||
if n.kind == nkHiddenStdConv and (not tracked.isArrayIndexing) and
|
||||
shouldWarnRangeConversion(tracked.config, n.info, n.typ, n[1].typ) and
|
||||
getConstExpr(tracked.ownerModule, n[1], tracked.c.idgen, tracked.graph) == nil:
|
||||
n[1].kind notin {nkCharLit..nkUInt64Lit, nkFloatLit..nkFloat128Lit} and
|
||||
shouldWarnRangeConversion(tracked.config, n.info, n.typ, n[1].typ):
|
||||
message(tracked.config, n.info, warnImplicitRangeConversion,
|
||||
typeToString(n[1].typ) & " -> " & typeToString(n.typ))
|
||||
|
||||
@@ -1729,11 +1573,6 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
if optStaticBoundsCheck in tracked.currOptions:
|
||||
checkRange(tracked, n[1], n.typ)
|
||||
of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
|
||||
if n.kind in {nkObjUpConv, nkObjDownConv}:
|
||||
if optObjCheck in tracked.currOptions:
|
||||
tracked.canRaiseDefect = true
|
||||
elif optRangeCheck in tracked.currOptions:
|
||||
tracked.canRaiseDefect = true
|
||||
if n.len == 1:
|
||||
track(tracked, n[0])
|
||||
if tracked.owner.kind != skMacro:
|
||||
@@ -1748,8 +1587,6 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
if tracked.owner.kind != skMacro:
|
||||
createTypeBoundOps(tracked, n.typ, n.info)
|
||||
of nkBracketExpr:
|
||||
if optBoundsCheck in tracked.currOptions:
|
||||
tracked.canRaiseDefect = true
|
||||
if optStaticBoundsCheck in tracked.currOptions and n.len == 2:
|
||||
if n[0].typ != nil and skipTypes(n[0].typ, abstractVar).kind != tyTuple:
|
||||
checkBounds(tracked, n[0], n[1])
|
||||
@@ -1845,18 +1682,13 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode; s: PSym = nil) =
|
||||
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
||||
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
|
||||
|
||||
let forbidsSpec = effectSpec(n, wForbids)
|
||||
let tagsSpec = effectSpec(n, wTags)
|
||||
if not isNil(tagsSpec):
|
||||
effects[tagEffects] = tagsSpec
|
||||
elif not isNil(forbidsSpec):
|
||||
# `.forbids` without `.tags` still declares a known empty tag set.
|
||||
# Leaving this as nil would mean "unknown tags", which later widens
|
||||
# indirect calls to `RootEffect`.
|
||||
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
||||
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
||||
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
||||
|
||||
let forbidsSpec = effectSpec(n, wForbids)
|
||||
if not isNil(forbidsSpec):
|
||||
effects[forbiddenEffects] = forbidsSpec
|
||||
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
||||
@@ -1931,9 +1763,6 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||
|
||||
track(t, body)
|
||||
|
||||
if t.exc.len == 0 and not t.canRaiseDefect:
|
||||
s.incl sfNeverRaises
|
||||
|
||||
if s.kind != skMacro:
|
||||
let params = s.typ.n
|
||||
for i in 1..<params.len:
|
||||
@@ -1994,6 +1823,7 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||
patchResult(t, ensuresSpec)
|
||||
effects[ensuresEffects] = ensuresSpec
|
||||
|
||||
var mutationInfo = MutationInfo()
|
||||
if views in c.features:
|
||||
var partitions = computeGraphPartitions(s, body, g, {borrowChecking})
|
||||
checkBorrowedLocations(partitions, body, g.config)
|
||||
|
||||
@@ -17,14 +17,18 @@ const
|
||||
errInvalidControlFlowX = "invalid control flow: $1"
|
||||
errSelectorMustBeOfCertainTypes = "selector must be of an ordinal type, float or string"
|
||||
errExprCannotBeRaised = "only a 'ref object' can be raised"
|
||||
errBreakOnlyInLoop = "'break' only allowed in loop construct"
|
||||
errExceptionAlreadyHandled = "exception already handled"
|
||||
errYieldNotAllowedHere = "'yield' only allowed in an iterator"
|
||||
errYieldNotAllowedInTryStmt = "'yield' cannot be used within 'try' in a non-inlined iterator"
|
||||
errInvalidNumberOfYieldExpr = "invalid number of 'yield' expressions"
|
||||
errCannotReturnExpr = "current routine cannot return an expression"
|
||||
errGenericLambdaNotAllowed = "A nested proc can have generic parameters only when " &
|
||||
"it is used as an operand to another routine and the types " &
|
||||
"of the generic paramers can be inferred from the expected signature."
|
||||
errCannotInferTypeOfTheLiteral = "cannot infer the type of the $1"
|
||||
errCannotInferReturnType = "cannot infer the return type of '$1'"
|
||||
errCannotInferStaticParam = "cannot infer the value of the static param '$1'"
|
||||
errProcHasNoConcreteType = "'$1' doesn't have a concrete type, due to unspecified generic parameters."
|
||||
errLetNeedsInit = "'let' symbol requires an initialization"
|
||||
errThreadvarCannotInit = "a thread var cannot be initialized explicitly; this would only run for the main thread"
|
||||
@@ -527,7 +531,7 @@ proc semUsing(c: PContext; n: PNode): PNode =
|
||||
if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "using")
|
||||
for i in 0..<n.len:
|
||||
var a = n[i]
|
||||
if c.config.ideActive: suggestStmt(c, a)
|
||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
|
||||
checkMinSonsLen(a, 3, c.config)
|
||||
@@ -541,6 +545,7 @@ proc semUsing(c: PContext; n: PNode): PNode =
|
||||
strTableIncl(c.signatures, v)
|
||||
else:
|
||||
localError(c.config, a.info, "'using' section must have a type")
|
||||
var def: PNode
|
||||
if a[^1].kind != nkEmpty:
|
||||
localError(c.config, a.info, "'using' sections cannot contain assignments")
|
||||
|
||||
@@ -833,7 +838,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||
|
||||
for i in 0..<n.len:
|
||||
var a = n[i]
|
||||
if c.config.ideActive: suggestStmt(c, a)
|
||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config)
|
||||
checkMinSonsLen(a, 3, c.config)
|
||||
@@ -989,7 +994,7 @@ proc semConst(c: PContext, n: PNode): PNode =
|
||||
var b: PNode
|
||||
for i in 0..<n.len:
|
||||
var a = n[i]
|
||||
if c.config.ideActive: suggestStmt(c, a)
|
||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
||||
if a.kind == nkCommentStmt: continue
|
||||
if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config)
|
||||
checkMinSonsLen(a, 3, c.config)
|
||||
@@ -1091,12 +1096,7 @@ proc symForVar(c: PContext, n: PNode): PSym =
|
||||
proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
||||
result = n
|
||||
let iterBase = n[^2].typ
|
||||
let iterType =
|
||||
if iterBase.kind == tyIterable:
|
||||
iterBase.skipModifier
|
||||
else:
|
||||
skipTypes(iterBase, {tyAlias, tySink, tyOwned})
|
||||
var iter = skipTypes(iterType, {tyGenericInst})
|
||||
var iter = skipTypes(iterBase, {tyGenericInst, tyAlias, tySink, tyOwned})
|
||||
var iterAfterVarLent = iter.skipTypes({tyGenericInst, tyAlias, tyLent, tyVar})
|
||||
# n.len == 3 means that there is one for loop variable
|
||||
# and thus no tuple unpacking:
|
||||
@@ -1129,9 +1129,10 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
||||
else:
|
||||
var v = symForVar(c, n[0])
|
||||
if getCurrOwner(c).kind == skModule: incl(v, sfGlobal)
|
||||
# Use `iterType` here: it removes outer `tyIterable` / alias-like wrappers
|
||||
# from the loop source, but still preserves `tyGenericInst` for the loop var.
|
||||
v.typ = iterType
|
||||
# BUGFIX: don't use `iter` here as that would strip away
|
||||
# the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
|
||||
# for an example:
|
||||
v.typ = iterBase
|
||||
n[0] = newSymNode(v)
|
||||
if sfGenSym notin v.flags and not isDiscardUnderscore(v): addDecl(c, v)
|
||||
elif v.owner == nil: setOwner(v, getCurrOwner(c))
|
||||
@@ -1195,14 +1196,14 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
||||
c.p.breakInLoop = oldBreakInLoop
|
||||
dec(c.p.nestedLoopCounter)
|
||||
|
||||
proc implicitIterator(c: PContext, it: string, arg: PNode, flags: TExprFlags): PNode =
|
||||
proc implicitIterator(c: PContext, it: string, arg: PNode): PNode =
|
||||
result = newNodeI(nkCall, arg.info)
|
||||
result.add(newIdentNode(getIdent(c.cache, it), arg.info))
|
||||
if arg.typ != nil and arg.typ.kind in {tyVar, tyLent}:
|
||||
result.add newDeref(arg)
|
||||
else:
|
||||
result.add arg
|
||||
result = semExprNoDeref(c, result, flags + {efWantIterator})
|
||||
result = semExprNoDeref(c, result, {efWantIterator})
|
||||
|
||||
proc isTrivalStmtExpr(n: PNode): bool =
|
||||
for i in 0..<n.len-1:
|
||||
@@ -1288,8 +1289,7 @@ proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
||||
if result != nil: return result
|
||||
openScope(c)
|
||||
result = n
|
||||
let iteratorFlags = flags * {efPreferIteratorForIterable}
|
||||
n[^2] = semExprNoDeref(c, n[^2], iteratorFlags + {efWantIterator})
|
||||
n[^2] = semExprNoDeref(c, n[^2], {efWantIterator})
|
||||
var call = n[^2]
|
||||
|
||||
if call.kind == nkStmtListExpr and (isTrivalStmtExpr(call) or (call.lastSon.kind in nkCallKinds and call.lastSon[0].sym.kind == skIterator)):
|
||||
@@ -1309,16 +1309,14 @@ proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
||||
elif not isCallExpr or call[0].kind != nkSym or
|
||||
call[0].sym.kind != skIterator:
|
||||
if n.len == 3:
|
||||
n[^2] = implicitIterator(c, "items", n[^2], iteratorFlags)
|
||||
n[^2] = implicitIterator(c, "items", n[^2])
|
||||
elif n.len == 4:
|
||||
n[^2] = implicitIterator(c, "pairs", n[^2], iteratorFlags)
|
||||
n[^2] = implicitIterator(c, "pairs", n[^2])
|
||||
else:
|
||||
localError(c.config, n[^2].info, "iterator within for loop context expected")
|
||||
result = semForVars(c, n, flags)
|
||||
else:
|
||||
result = semForVars(c, n, flags)
|
||||
if n[^2].typ != nil and n[^2].typ.kind == tyIterable:
|
||||
n[^2].typ = n[^2].typ.skipModifier
|
||||
# propagate any enforced VoidContext:
|
||||
if n[^1].typ == c.enforceVoidContext:
|
||||
result.typ = c.enforceVoidContext
|
||||
@@ -1530,7 +1528,7 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) =
|
||||
while i < n.len: # n may grow due to type pragma macros
|
||||
var a = n[i]
|
||||
when defined(nimsuggest):
|
||||
if c.config.ideActive:
|
||||
if c.config.cmd == cmdIdeTools:
|
||||
inc c.inTypeContext
|
||||
suggestStmt(c, a)
|
||||
dec c.inTypeContext
|
||||
@@ -1803,53 +1801,15 @@ proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) =
|
||||
internalAssert c.config, false
|
||||
|
||||
proc typeSectionFinalPass(c: PContext, n: PNode) =
|
||||
# each top level type needs to be processed, each epoch should reify at least one
|
||||
var remainingOwners = initIntSet()
|
||||
for (owner, _, _) in c.forwardTypeUpdates:
|
||||
remainingOwners.incl owner.id
|
||||
|
||||
while c.forwardTypeUpdates.len > 0:
|
||||
let pending = move c.forwardTypeUpdates
|
||||
var madeProgress = false
|
||||
|
||||
for (owner, typ, typeNode) in pending:
|
||||
# types that need to be updated due to containing forward types
|
||||
# and their corresponding type nodes
|
||||
# for example generic invocations of forward types end up here
|
||||
var reified = semTypeNode(c, typeNode, nil)
|
||||
assert reified != nil
|
||||
assignType(typ, reified)
|
||||
typ.bindingId = reified.bindingId # same id
|
||||
if containsForwardType(typ):
|
||||
c.forwardTypeUpdates.add (owner, typ, typeNode)
|
||||
elif not remainingOwners.missingOrExcl(owner.id):
|
||||
madeProgress = true
|
||||
|
||||
if not madeProgress:
|
||||
# can't error here unfortunately
|
||||
break
|
||||
|
||||
for (owner, field, expectedType) in c.forwardFieldUpdates:
|
||||
semDelayedFieldDefault(c, owner, expectedType, field)
|
||||
c.forwardFieldUpdates = @[]
|
||||
|
||||
# a son that still was a `tyForward` could not propagate `tfHasAsgn` and
|
||||
# friends to its owner back then, see `rememberFlagUpdate`. Now that every
|
||||
# forward declaration has a body, redo those propagations. They are recorded
|
||||
# in declaration order rather than dependency order and an owner can itself
|
||||
# be the son of another pair, so repeat until nothing changes; this
|
||||
# terminates because flags are only ever added.
|
||||
if c.forwardFlagUpdates.len > 0:
|
||||
let updates = move c.forwardFlagUpdates
|
||||
c.staleTypeFlags = initIntSet()
|
||||
var changed = true
|
||||
while changed:
|
||||
changed = false
|
||||
for (owner, elem) in updates:
|
||||
let before = owner.flags
|
||||
propagateToOwner(owner, elem)
|
||||
if owner.flags != before: changed = true
|
||||
|
||||
for (typ, typeNode) in c.forwardTypeUpdates:
|
||||
# types that need to be updated due to containing forward types
|
||||
# and their corresponding type nodes
|
||||
# for example generic invocations of forward types end up here
|
||||
var reified = semTypeNode(c, typeNode, nil)
|
||||
assert reified != nil
|
||||
assignType(typ, reified)
|
||||
typ.itemId = reified.itemId # same id
|
||||
c.forwardTypeUpdates = @[]
|
||||
for i in 0..<n.len:
|
||||
var a = n[i]
|
||||
if a.kind == nkCommentStmt: continue
|
||||
@@ -2160,54 +2120,47 @@ proc checkedForDestructor(t: PType): bool =
|
||||
return true
|
||||
result = false
|
||||
|
||||
proc normalizeTypeHook(t: PType; markAsgn = false): PType =
|
||||
proc whereToBindTypeHook(c: PContext; t: PType): PType =
|
||||
result = t
|
||||
while true:
|
||||
if markAsgn:
|
||||
incl(result, tfHasAsgn)
|
||||
if result.kind == tyCompositeTypeClass and result.base.kind == tyGenericBody:
|
||||
result = result.base
|
||||
elif result.kind in {tyGenericBody, tyGenericInst}:
|
||||
result = result.skipModifier
|
||||
elif result.kind == tyGenericInvocation:
|
||||
result = result.genericHead
|
||||
else:
|
||||
break
|
||||
|
||||
proc whereToBindTypeHook(c: PContext; t: PType): PType =
|
||||
result = normalizeTypeHook(t)
|
||||
if result.kind in {tyGenericBody, tyGenericInst}: result = result.skipModifier
|
||||
elif result.kind == tyGenericInvocation: result = result[0]
|
||||
else: break
|
||||
if result.kind in {tyObject, tyDistinct, tySequence, tyString}:
|
||||
result = canonType(c, result)
|
||||
|
||||
proc bindHookToType(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp;
|
||||
typeToBind: PType): bool =
|
||||
var obj = typeToBind
|
||||
if obj.kind notin {tyObject, tyDistinct, tySequence, tyString}:
|
||||
return false
|
||||
obj = canonType(c, obj)
|
||||
let ao = getAttachedOp(c.graph, obj, op)
|
||||
if ao == s:
|
||||
discard "forward declared hook"
|
||||
elif ao.isNil and not checkedForDestructor(obj):
|
||||
setAttachedOp(c.graph, c.module.position, obj, op, s)
|
||||
else:
|
||||
prevDestructor(c, op, ao, obj, n.info)
|
||||
if obj.owner.getModule != s.getModule:
|
||||
localError(c.config, n.info, errGenerated,
|
||||
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
|
||||
result = true
|
||||
|
||||
proc bindDupHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
|
||||
let t = s.typ
|
||||
var noError = false
|
||||
let cond = t.len == 2 and t.returnType != nil
|
||||
|
||||
if cond:
|
||||
var obj = normalizeTypeHook(t.firstParamType, markAsgn = true)
|
||||
let res = normalizeTypeHook(t.returnType)
|
||||
var obj = t.firstParamType
|
||||
while true:
|
||||
incl(obj, tfHasAsgn)
|
||||
if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier
|
||||
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
|
||||
else: break
|
||||
|
||||
if sameType(obj, res):
|
||||
noError = bindHookToType(c, s, n, op, obj)
|
||||
var res = t.returnType
|
||||
while true:
|
||||
if res.kind in {tyGenericBody, tyGenericInst}: res = res.skipModifier
|
||||
elif res.kind == tyGenericInvocation: res = res.genericHead
|
||||
else: break
|
||||
|
||||
if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, res):
|
||||
obj = canonType(c, obj)
|
||||
let ao = getAttachedOp(c.graph, obj, op)
|
||||
if ao == s:
|
||||
discard "forward declared destructor"
|
||||
elif ao.isNil and not checkedForDestructor(obj):
|
||||
setAttachedOp(c.graph, c.module.position, obj, op, s)
|
||||
else:
|
||||
prevDestructor(c, op, ao, obj, n.info)
|
||||
noError = true
|
||||
if obj.owner.getModule != s.getModule:
|
||||
localError(c.config, n.info, errGenerated,
|
||||
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
|
||||
|
||||
if not noError and sfSystemModule notin s.owner.flags:
|
||||
localError(c.config, n.info, errGenerated,
|
||||
@@ -2237,8 +2190,25 @@ proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
|
||||
t.len >= 2 and t.returnType == nil
|
||||
|
||||
if cond:
|
||||
var obj = normalizeTypeHook(t.firstParamType.skipTypes({tyVar}), markAsgn = true)
|
||||
noError = bindHookToType(c, s, n, op, obj)
|
||||
var obj = t.firstParamType.skipTypes({tyVar})
|
||||
while true:
|
||||
incl(obj, tfHasAsgn)
|
||||
if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier
|
||||
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
|
||||
else: break
|
||||
if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
|
||||
obj = canonType(c, obj)
|
||||
let ao = getAttachedOp(c.graph, obj, op)
|
||||
if ao == s:
|
||||
discard "forward declared destructor"
|
||||
elif ao.isNil and not checkedForDestructor(obj):
|
||||
setAttachedOp(c.graph, c.module.position, obj, op, s)
|
||||
else:
|
||||
prevDestructor(c, op, ao, obj, n.info)
|
||||
noError = true
|
||||
if obj.owner.getModule != s.getModule:
|
||||
localError(c.config, n.info, errGenerated,
|
||||
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
|
||||
if not noError and sfSystemModule notin s.owner.flags:
|
||||
case op
|
||||
of attachedTrace:
|
||||
@@ -2305,12 +2275,35 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
|
||||
message(c.config, n.info, warnDeprecated, "Overriding `=` hook is deprecated; Override `=copy` hook instead")
|
||||
let t = s.typ
|
||||
if t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar:
|
||||
var obj = normalizeTypeHook(t.firstParamType.elementType, markAsgn = true)
|
||||
let objB = normalizeTypeHook(t[2])
|
||||
if sameType(obj, objB):
|
||||
var obj = t.firstParamType.elementType
|
||||
while true:
|
||||
incl(obj, tfHasAsgn)
|
||||
if obj.kind == tyGenericBody: obj = obj.skipModifier
|
||||
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
|
||||
else: break
|
||||
var objB = t[2]
|
||||
while true:
|
||||
if objB.kind == tyGenericBody: objB = objB.skipModifier
|
||||
elif objB.kind in {tyGenericInvocation, tyGenericInst}:
|
||||
objB = objB.genericHead
|
||||
else: break
|
||||
if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB):
|
||||
# attach these ops to the canonical tySequence
|
||||
obj = canonType(c, obj)
|
||||
#echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj)
|
||||
let k = if name == "=" or name == "=copy": attachedAsgn else: attachedSink
|
||||
if bindHookToType(c, s, n, k, obj): return
|
||||
let ao = getAttachedOp(c.graph, obj, k)
|
||||
if ao == s:
|
||||
discard "forward declared op"
|
||||
elif ao.isNil and not checkedForDestructor(obj):
|
||||
setAttachedOp(c.graph, c.module.position, obj, k, s)
|
||||
else:
|
||||
prevDestructor(c, k, ao, obj, n.info)
|
||||
if obj.owner.getModule != s.getModule:
|
||||
localError(c.config, n.info, errGenerated,
|
||||
"type bound operation `" & name & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
|
||||
|
||||
return
|
||||
if sfSystemModule notin s.owner.flags:
|
||||
localError(c.config, n.info, errGenerated,
|
||||
"signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)")
|
||||
@@ -2376,8 +2369,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
|
||||
if typ.kind != tyObject:
|
||||
localError(c.config, n.info, pragmaName & " must be either ptr to object or object type.")
|
||||
if sameOwners(typ.owner, s.owner) and sameOwners(c.module, s.owner):
|
||||
c.graph.memberProcsPerType.mgetOrPut(typ.bindingId, @[]).add s
|
||||
logCppMember(c.graph, s)
|
||||
c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s
|
||||
else:
|
||||
localError(c.config, n.info,
|
||||
pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope")
|
||||
@@ -2385,7 +2377,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
|
||||
localError(c.config, n.info, pragmaName & " procs are only supported in C++")
|
||||
else:
|
||||
var typ = s.typ.returnType
|
||||
if typ != nil and typ.kind == tyObject and typ.bindingId notin c.graph.initializersPerType:
|
||||
if typ != nil and typ.kind == tyObject and typ.itemId notin c.graph.initializersPerType:
|
||||
var initializerCall = newTree(nkCall, newSymNode(s))
|
||||
var isInitializer = n[paramsPos].len > 1
|
||||
for i in 1..<n[paramsPos].len:
|
||||
@@ -2399,8 +2391,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
|
||||
initializerCall.add val
|
||||
inc j
|
||||
if isInitializer:
|
||||
c.graph.initializersPerType[typ.bindingId] = initializerCall
|
||||
logCppMember(c.graph, s)
|
||||
c.graph.initializersPerType[typ.itemId] = initializerCall
|
||||
|
||||
proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
|
||||
if s.isGenericRoutine:
|
||||
@@ -2561,9 +2552,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
|
||||
if not hasProto:
|
||||
implicitPragmas(c, s, n.info, validPragmas)
|
||||
|
||||
if {sfError, sfExportc} * s.flags == {sfError, sfExportc}:
|
||||
localError(c.config, n.info, "{.error.} and {.exportc.} pragmas are incompatible")
|
||||
|
||||
if n[pragmasPos].kind != nkEmpty and sfBorrow notin s.flags:
|
||||
setEffectsForProcType(c.graph, s.typ, n[pragmasPos], s)
|
||||
s.typ.incl tfEffectSystemWorkaround
|
||||
@@ -2603,47 +2591,15 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
|
||||
addParams(c, proto.typ.n, proto.kind)
|
||||
proto.info = s.info # more accurate line information
|
||||
proto.options = s.options
|
||||
# `s` (the impl symbol) is discarded in favour of `proto`. It still carries
|
||||
# `s.ast == n` (set above) and stays reachable as the owner of body-local
|
||||
# symbols, so under IC it would be serialized as a SECOND, body-bearing
|
||||
# `proc` entry — a phantom duplicate of `proto`. The per-module backend then
|
||||
# codegens that phantom, whose `result` is owned by `proto` (addResult below
|
||||
# re-parents it), not by the phantom: lambdalifting's capture check
|
||||
# (`result.skipGenericOwner != owner`) then wrongly classifies `result` as a
|
||||
# captured outer variable → "'result' … cannot be captured". Drop the
|
||||
# discarded impl's body so it can never be emitted as a routine (same leak
|
||||
# class the `miscPos` adoption below guards against for generic params).
|
||||
let discardedImpl = s
|
||||
s = proto
|
||||
n[genericParamsPos] = proto.ast[genericParamsPos]
|
||||
n[paramsPos] = proto.ast[paramsPos]
|
||||
n[pragmasPos] = proto.ast[pragmasPos]
|
||||
# miscPos holds this definition's *original* generic-param node (kept for
|
||||
# error messages, see setGenericParamsMisc / issue #1713). For an impl that
|
||||
# resolves to a forward decl, that node was analysed under the now-discarded
|
||||
# impl symbol and its generic-param constraint types are owned by it. Adopt
|
||||
# the prototype's miscPos so the discarded impl sym is fully unreachable —
|
||||
# otherwise it leaks (via `proto.ast = n` below) as a type owner and gets
|
||||
# serialized as a phantom duplicate overload under IC.
|
||||
n[miscPos] = proto.ast[miscPos]
|
||||
if n[namePos].kind != nkSym: internalError(c.config, n.info, "semProcAux")
|
||||
n[namePos].sym = proto
|
||||
if importantComments(c.config) and proto.ast.comment.len > 0:
|
||||
n.comment = proto.ast.comment
|
||||
proto.ast = n # needed for code generation
|
||||
if discardedImpl != proto:
|
||||
discardedImpl.ast = nil
|
||||
# The impl symbol is discarded in favour of `proto`, but it stays `Complete`
|
||||
# in this module, so `ast2nif.shouldWriteSymDef` still serializes it. With
|
||||
# `sfExported` it would be written importable (`x` marker) and an importer
|
||||
# would load BOTH it and `proto` into the overload set: "ambiguous call;
|
||||
# both foo and foo" (identical signatures). Normally a discarded impl is a
|
||||
# gensym/transient that isn't reached this way, but a `{.async: (raises).}`
|
||||
# forward-decl + impl reconciles HERE with both syms exported. Strip the
|
||||
# export so the design's "forward declarations are never importable" holds —
|
||||
# the def still serializes (other refs may resolve to it) but is invisible
|
||||
# to importer overload resolution; `proto` carries the export.
|
||||
excl(discardedImpl, sfExported)
|
||||
popOwner(c)
|
||||
pushOwner(c, s)
|
||||
|
||||
@@ -2656,11 +2612,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
|
||||
elif s.name.s == "()" and callOperator notin c.features:
|
||||
localError(c.config, n.info, "the overloaded " & s.name.s &
|
||||
" operator has to be enabled with {.experimental: \"callOperator\".}")
|
||||
elif sfImportc notin s.flags and (s.name.s == ">" or s.name.s == ">=" or s.name.s == "!="):
|
||||
# ignore imported procs as these operators in backend language might have different semantics
|
||||
let op1 = if s.name.s == "!=": "==" elif s.name.s == ">": "<" else: "<="
|
||||
message(c.config, n.info, warnInvalidCmpOp, "define `" & op1 & "` instead of `" & s.name.s & "` to implement user defined comparison operator. " &
|
||||
"it allows you to use `" & s.name.s & "` automatically.")
|
||||
|
||||
if sfBorrow in s.flags and c.config.cmd notin cmdDocLike:
|
||||
result[bodyPos] = c.graph.emptyNode
|
||||
@@ -2874,8 +2825,7 @@ proc incMod(c: PContext, n: PNode, it: PNode, includeStmtResult, resolvedIncStmt
|
||||
proc evalInclude(c: PContext, n: PNode): PNode =
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
var resolvedIncStmt: PNode = nil
|
||||
if {optCompress, optGenBif} * c.config.globalOptions != {} or
|
||||
c.config.cmd == cmdM:
|
||||
if optCompress in c.config.globalOptions:
|
||||
# New resolve the include filenames to string literals that contain absolute paths,
|
||||
# nicer for IC:
|
||||
resolvedIncStmt = newNodeI(nkIncludeStmt, n.info)
|
||||
@@ -2956,15 +2906,13 @@ proc semPragmaBlock(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
||||
proc semStaticStmt(c: PContext, n: PNode): PNode =
|
||||
#echo "semStaticStmt"
|
||||
#writeStackTrace()
|
||||
let oldErrorCount = c.config.errorCounter
|
||||
inc c.inStaticContext
|
||||
openScope(c)
|
||||
let a = semStmt(c, n[0], {})
|
||||
closeScope(c)
|
||||
dec c.inStaticContext
|
||||
n[0] = a
|
||||
if c.config.errorCounter == oldErrorCount:
|
||||
evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
|
||||
evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
|
||||
when false:
|
||||
# for incremental replays, keep the AST as required for replays:
|
||||
result = n
|
||||
|
||||
@@ -581,7 +581,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
|
||||
result.add newIdentNode(getIdent(c.c.cache, "[]="), n.info)
|
||||
for i in 0..<a.len: result.add(a[i])
|
||||
result.add(b)
|
||||
discard semTemplBody(c, a[0])
|
||||
let a0 = semTemplBody(c, a[0])
|
||||
result = semTemplBodySons(c, result)
|
||||
of nkCurlyExpr:
|
||||
if a.typ == nil:
|
||||
|
||||
@@ -19,11 +19,13 @@ const
|
||||
errOverflowInEnumX = "The enum '$1' exceeds its maximum value ($2)"
|
||||
errOrdinalTypeExpected = "ordinal type expected; given: $1"
|
||||
errSetTooBig = "set is too large; use `std/sets` for ordinal types with more than 2^16 elements"
|
||||
errBaseTypeMustBeOrdinal = "base type of a set must be an ordinal"
|
||||
errInheritanceOnlyWithNonFinalObjects = "inheritance only works with non-final objects"
|
||||
errXExpectsOneTypeParam = "'$1' expects one type parameter"
|
||||
errArrayExpectsTwoTypeParams = "array expects two type parameters"
|
||||
errInvalidVisibilityX = "invalid visibility: '$1'"
|
||||
errXCannotBeAssignedTo = "'$1' cannot be assigned to"
|
||||
errIteratorNotAllowed = "iterators can only be defined at the module's top level"
|
||||
errXNeedsReturnType = "$1 needs a return type"
|
||||
errNoReturnTypeDeclared = "no return type declared"
|
||||
errTIsNotAConcreteType = "'$1' is not a concrete type"
|
||||
@@ -58,18 +60,6 @@ proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType =
|
||||
else:
|
||||
result = newTypeS(kind, c)
|
||||
|
||||
proc rememberFlagUpdate(c: PContext; owner, elem: PType) =
|
||||
## `propagateToOwner` just derived `owner`'s `tfHasAsgn` & friends from
|
||||
## `elem`, but inside a type section `elem` can still be an unreified
|
||||
## `tyForward` which has nothing to derive from yet -- and a type that read
|
||||
## such a type is provisional in turn. Remember the pair so
|
||||
## `typeSectionFinalPass` can redo the propagation once every forward
|
||||
## declaration has a body, the same way `forwardFieldUpdates` defers the
|
||||
## field defaults.
|
||||
if elem != nil and (elem.kind == tyForward or elem.id in c.staleTypeFlags):
|
||||
c.forwardFlagUpdates.add (owner, elem)
|
||||
c.staleTypeFlags.incl owner.id
|
||||
|
||||
proc newConstraint(c: PContext, k: TTypeKind): PType =
|
||||
result = newTypeS(tyBuiltInTypeClass, c)
|
||||
result.incl tfCheckedForDestructor
|
||||
@@ -229,13 +219,11 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = newOrPrevType(tySet, prev, c)
|
||||
if n.len == 2 and n[1].kind != nkEmpty:
|
||||
var base = semTypeNode(c, n[1], nil)
|
||||
if base.kind == tyTypeDesc: base = base.base # unwrap from type traits like distinctBase
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
rememberFlagUpdate(c, result, base)
|
||||
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
|
||||
if base.kind notin {tyGenericParam, tyGenericInvocation, tyFromExpr}:
|
||||
if base.kind notin {tyGenericParam, tyGenericInvocation}:
|
||||
if base.kind == tyForward:
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
|
||||
c.forwardTypeUpdates.add (base, n[1])
|
||||
elif not isOrdinalType(base, allowEnumWithHoles = true):
|
||||
localError(c.config, n.info, errOrdinalTypeExpected % typeToString(base, preferDesc))
|
||||
elif lengthOrd(c.config, base) > MaxSetElements:
|
||||
@@ -250,7 +238,6 @@ proc semContainerArg(c: PContext; n: PNode, kindStr: string; result: PType) =
|
||||
if base.kind == tyVoid:
|
||||
localError(c.config, n.info, errTIsNotAConcreteType % typeToString(base))
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
rememberFlagUpdate(c, result, base)
|
||||
else:
|
||||
localError(c.config, n.info, errXExpectsOneTypeParam % kindStr)
|
||||
addSonSkipIntLit(result, errorType(c), c.idgen)
|
||||
@@ -331,62 +318,6 @@ proc fitDefaultNode(c: PContext, n: var PNode, expectedType: PType) =
|
||||
typeAllowedCheck(c, n.info, n.typ, skConst, {taProcContextIsNotMacro, taIsDefaultField})
|
||||
dec c.inStaticContext
|
||||
|
||||
proc containsForwardTypeAux(t: PType; seen: var IntSet): bool
|
||||
|
||||
proc containsForwardTypeAux(n: PNode; seen: var IntSet): bool =
|
||||
result = false
|
||||
if n.isNil or n.kind in nkLiterals + {nkNilLit, nkEmpty, nkType}:
|
||||
return
|
||||
if containsForwardTypeAux(n.typ, seen) or
|
||||
(n.kind == nkSym and n.sym.typ != n.typ and containsForwardTypeAux(n.sym.typ, seen)):
|
||||
return true
|
||||
|
||||
for i in 0 ..< n.safeLen:
|
||||
if containsForwardTypeAux(n[i], seen):
|
||||
return true
|
||||
|
||||
proc containsForwardTypeAux(t: PType; seen: var IntSet): bool =
|
||||
result = false
|
||||
if t.isNil:
|
||||
return
|
||||
if t.kind == tyForward:
|
||||
return true
|
||||
|
||||
if not containsOrIncl(seen, t.id):
|
||||
if containsForwardTypeAux(t.n, seen):
|
||||
return true
|
||||
|
||||
for i in 0 ..< t.len:
|
||||
if containsForwardTypeAux(t[i], seen):
|
||||
return true
|
||||
|
||||
proc containsForwardType(arg: PNode): bool =
|
||||
var seen = initIntSet()
|
||||
containsForwardTypeAux(arg, seen)
|
||||
|
||||
proc containsForwardType(t: PType): bool =
|
||||
var seen = initIntSet()
|
||||
containsForwardTypeAux(t, seen)
|
||||
|
||||
proc semFieldDefault(c: PContext; owner, expectedType: PType; field: PNode): PType =
|
||||
result = expectedType
|
||||
field[^1] = semExprWithType(c, field[^1], {efDetermineType, efAllowSymChoice}, result)
|
||||
if result == nil:
|
||||
result = field[^1].typ
|
||||
|
||||
if c.inGenericContext == 0:
|
||||
if containsForwardType(field[^1]):
|
||||
c.forwardFieldUpdates.add (owner, field, result)
|
||||
else:
|
||||
fitDefaultNode(c, field[^1], result)
|
||||
result = field[^1].typ.skipIntLit(c.idgen)
|
||||
propagateToOwner(owner, result)
|
||||
|
||||
proc semDelayedFieldDefault(c: PContext; owner, expectedType: PType; field: PNode) =
|
||||
resetSemFlag(field[^1])
|
||||
fitDefaultNode(c, field[^1], expectedType)
|
||||
propagateToOwner(owner, field[^1].typ.skipIntLit(c.idgen))
|
||||
|
||||
proc isRecursiveType*(t: PType): bool =
|
||||
# handle simple recusive types before typeFinalPass
|
||||
var cycleDetector = initIntSet()
|
||||
@@ -399,7 +330,6 @@ proc addSonSkipIntLitChecked(c: PContext; father, son: PType; it: PNode, id: IdG
|
||||
localError(c.config, it.info, "illegal recursion in type '" & typeToString(s) & "'")
|
||||
else:
|
||||
propagateToOwner(father, s)
|
||||
rememberFlagUpdate(c, father, s)
|
||||
|
||||
proc semDistinct(c: PContext, n: PNode, prev: PType): PType =
|
||||
if n.len == 0: return newConstraint(c, tyDistinct)
|
||||
@@ -526,13 +456,7 @@ proc semArrayIndex(c: PContext, n: PNode): PType =
|
||||
if c.inGenericContext > 0: result.incl tfUnresolved
|
||||
else:
|
||||
result = e.typ.skipTypes({tyTypeDesc})
|
||||
if result.state != Sealed:
|
||||
# For a type loaded from the IC cache we skip the flag instead of
|
||||
# mutating (or copying) the type: tfImplicitStatic has no readers in
|
||||
# the compiler, and a copy would get a fresh itemId, breaking enum
|
||||
# identity (`sameEnumTypes` compares ids) — `arr[enumVal]` on an
|
||||
# `array[LoadedEnum, T]` would no longer typecheck.
|
||||
result.incl tfImplicitStatic
|
||||
result.incl tfImplicitStatic
|
||||
elif e.kind in (nkCallKinds + {nkBracketExpr}) and hasUnresolvedArgs(c, e):
|
||||
if not isOrdinalType(e.typ.skipTypes({tyStatic, tyAlias, tyGenericInst, tySink})):
|
||||
localError(c.config, n[1].info, errOrdinalTypeExpected % typeToString(e.typ, preferDesc))
|
||||
@@ -573,7 +497,6 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
|
||||
# index type:
|
||||
result = newOrPrevType(tyArray, prev, c, indx)
|
||||
addSonSkipIntLit(result, base, c.idgen)
|
||||
rememberFlagUpdate(c, result, base)
|
||||
else:
|
||||
localError(c.config, n.info, errArrayExpectsTwoTypeParams)
|
||||
result = newOrPrevType(tyError, prev, c)
|
||||
@@ -627,7 +550,13 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
|
||||
var hasDefaultField = a[^1].kind != nkEmpty
|
||||
if hasDefaultField:
|
||||
typ = if a[^2].kind != nkEmpty: semTypeNode(c, a[^2], nil) else: nil
|
||||
typ = semFieldDefault(c, result, typ, a)
|
||||
if c.inGenericContext > 0:
|
||||
a[^1] = semExprWithType(c, a[^1], {efDetermineType, efAllowSymChoice}, typ)
|
||||
if typ == nil:
|
||||
typ = a[^1].typ
|
||||
else:
|
||||
fitDefaultNode(c, a[^1], typ)
|
||||
typ = a[^1].typ.skipIntLit(c.idgen)
|
||||
elif a[^2].kind != nkEmpty:
|
||||
typ = semTypeNode(c, a[^2], nil)
|
||||
if c.graph.config.isDefined("nimPreviewRangeDefault") and typ.skipTypes(abstractInst).kind == tyRange:
|
||||
@@ -650,7 +579,6 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
|
||||
fSym.sym.ast.flags.incl nfSkipFieldChecking
|
||||
result.n.add fSym
|
||||
addSonSkipIntLit(result, typ, c.idgen)
|
||||
rememberFlagUpdate(c, result, typ)
|
||||
styleCheckDef(c, a[j].info, field)
|
||||
onDef(field.info, field)
|
||||
if result.n.len == 0: result.n = nil
|
||||
@@ -994,7 +922,14 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
|
||||
var hasDefaultField = n[^1].kind != nkEmpty
|
||||
if hasDefaultField:
|
||||
typ = if n[^2].kind != nkEmpty: semTypeNode(c, n[^2], nil) else: nil
|
||||
typ = semFieldDefault(c, rectype, typ, n)
|
||||
if c.inGenericContext > 0:
|
||||
n[^1] = semExprWithType(c, n[^1], {efDetermineType, efAllowSymChoice}, typ)
|
||||
if typ == nil:
|
||||
typ = n[^1].typ
|
||||
else:
|
||||
fitDefaultNode(c, n[^1], typ)
|
||||
typ = n[^1].typ.skipIntLit(c.idgen)
|
||||
propagateToOwner(rectype, typ)
|
||||
elif n[^2].kind == nkEmpty:
|
||||
localError(c.config, n.info, errTypeExpected)
|
||||
typ = errorType(c)
|
||||
@@ -1004,7 +939,6 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
|
||||
n[^1] = firstRange(c.config, typ)
|
||||
hasDefaultField = true
|
||||
propagateToOwner(rectype, typ)
|
||||
rememberFlagUpdate(c, rectype, typ)
|
||||
var fieldOwner = if c.inGenericContext > 0: c.getCurrOwner
|
||||
else: rectype.sym
|
||||
for i in 0..<n.len-2:
|
||||
@@ -1138,9 +1072,8 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
|
||||
if needsForwardUpdate:
|
||||
# if the inherited object is a forward type,
|
||||
# the entire object needs to be checked again
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) # we retry in the final pass
|
||||
c.forwardTypeUpdates.add (result, n) # we retry in the final pass
|
||||
rawAddSon(result, realBase)
|
||||
rememberFlagUpdate(c, result, realBase)
|
||||
if realBase == nil and tfInheritable in flags:
|
||||
result.incl tfInheritable
|
||||
if tfAcyclic in flags: result.incl tfAcyclic
|
||||
@@ -1379,7 +1312,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
|
||||
for i in 0..<paramType.len - 1:
|
||||
if paramType[i].kind == tyStatic:
|
||||
var staticCopy = copyType(paramType[i], c.idgen, paramType[i].owner)
|
||||
var staticCopy = paramType[i].exactReplica
|
||||
staticCopy.incl tfInferrableStatic
|
||||
result.rawAddSon staticCopy
|
||||
else:
|
||||
@@ -1787,7 +1720,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
for i in 1..<n.len:
|
||||
var elem = semGenericParamInInvocation(c, n[i])
|
||||
addToResult(elem, true)
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
|
||||
c.forwardTypeUpdates.add (result, n)
|
||||
return
|
||||
elif t.kind != tyGenericBody:
|
||||
# we likely got code of the form TypeA[TypeB] where TypeA is
|
||||
@@ -1840,14 +1773,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
localError(c.config, n.info, errCannotInstantiateX % s.name.s)
|
||||
result = newOrPrevType(tyError, prev, c)
|
||||
elif containsGenericInvocationWithForward(n[0]) or hasForwardTypeParam:
|
||||
# isConcrete == false means this generic type is not instanciated here because
|
||||
# it invoked with generic parameters.
|
||||
# Even if isConcrete == true, don't instanciate it now if there are
|
||||
# unresolved `tyForward` type params.
|
||||
# Such `tyForward` type params will be semchecked later and we can
|
||||
# instanciate this next time.
|
||||
# Some generic types like std/options.Option[T] need the kind of the
|
||||
# given type argument before their fields can be resolved.
|
||||
# isConcrete == false means this generic type is not instanciated here because it invoked with generic parameters.
|
||||
# Even if isConcrete == true, don't instanciate it now if there are any `tyForward` type params.
|
||||
# Such `tyForward` type params will be semchecked later and we can instanciate this next time.
|
||||
# Some generic types like std/options.Option[T] needs a type kinds of the given type argument.
|
||||
|
||||
# return `tyForward` instead of `tyGenericInvocation` because:
|
||||
# ```nim
|
||||
@@ -1863,7 +1792,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
else:
|
||||
assignType(result, newTypeS(tyForward, c))
|
||||
result.sym = s
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) #fixes 1500
|
||||
c.forwardTypeUpdates.add (result, n) #fixes 1500
|
||||
return
|
||||
else:
|
||||
result = instGenericContainer(c, n.info, result,
|
||||
@@ -1915,12 +1844,6 @@ proc semTypeExpr(c: PContext, n: PNode; prev: PType): PType =
|
||||
# by macros. Only macros can summon unnamed types
|
||||
# and cast spell upon AST. Here we need to give
|
||||
# it a name taken from left hand side's node
|
||||
if result.state == Sealed:
|
||||
# The unnamed type was loaded from a dependency's NIF and must not
|
||||
# be mutated in place; attach the name to a fresh copy instead.
|
||||
let orig = result
|
||||
result = copyType(orig, c.idgen, getCurrOwner(c))
|
||||
copyTypeProps(c.graph, c.idgen.module, result, orig)
|
||||
result.sym = prev.sym
|
||||
result.sym.typ = result
|
||||
else:
|
||||
@@ -1964,6 +1887,7 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
|
||||
return result
|
||||
|
||||
let
|
||||
pragmas = n[1]
|
||||
inherited = n[2]
|
||||
|
||||
var owner = getCurrOwner(c)
|
||||
@@ -2092,57 +2016,6 @@ proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType =
|
||||
result.rawAddSon(base)
|
||||
result.incl tfHasStatic
|
||||
|
||||
proc semTypeOfImpl(c: PContext; n: PNode): PNode =
|
||||
var m = BiggestInt 1 # typeOfIter
|
||||
var modifierMode = BiggestInt 0 # CompatibleTypeModifiers
|
||||
type
|
||||
TypeOfParams = enum
|
||||
topMode
|
||||
topModifier
|
||||
if n.len in 3 .. 4:
|
||||
for i in 2 ..< n.len:
|
||||
var argKind = topMode
|
||||
var arg: PNode = nil
|
||||
if n[i].kind == nkExprEqExpr and n[i][0].kind == nkIdent:
|
||||
# named param
|
||||
case n[i][0].ident.s
|
||||
of "mode": argKind = topMode
|
||||
of "modifierMode": argKind = topModifier
|
||||
else:
|
||||
localError(c.config, n.info, "typeof: got unknown parameter name")
|
||||
arg = n[i][1]
|
||||
else:
|
||||
if i == 2:
|
||||
argKind = topMode
|
||||
else:
|
||||
argKind = topModifier
|
||||
arg = n[i]
|
||||
case argKind
|
||||
of topMode:
|
||||
let mode = semConstExpr(c, arg)
|
||||
if mode.kind != nkIntLit:
|
||||
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
|
||||
else:
|
||||
m = mode.intVal
|
||||
of topModifier:
|
||||
let modMode = semConstExpr(c, arg)
|
||||
if modMode.kind != nkIntLit:
|
||||
localError(c.config, n.info, "typeof: cannot evaluate 'modifierMode' parameter at compile-time")
|
||||
else:
|
||||
modifierMode = modMode.intVal
|
||||
|
||||
inc c.inTypeofContext
|
||||
defer: dec c.inTypeofContext # compiles can raise an exception
|
||||
var typExpr = semExprNoDeref(c, n[1], if m == 1: {efInTypeof} else: {})
|
||||
if modifierMode == 0:
|
||||
# CompatibleTypeModifiers
|
||||
typExpr.typ = typExpr.typ.skipTypes({tyVar, tyLent})
|
||||
elif modifierMode == 1:
|
||||
# RemoveTypeModifiers
|
||||
typExpr.typ = typExpr.typ.skipTypes({tyVar, tyLent, tySink})
|
||||
|
||||
result = typExpr
|
||||
|
||||
proc semTypeOf(c: PContext; n: PNode; prev: PType): PType =
|
||||
openScope(c)
|
||||
inc c.inTypeofContext
|
||||
@@ -2163,7 +2036,16 @@ proc semTypeOf(c: PContext; n: PNode; prev: PType): PType =
|
||||
|
||||
proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType =
|
||||
openScope(c)
|
||||
let ex = semTypeOfImpl(c, n)
|
||||
var m = BiggestInt 1 # typeOfIter
|
||||
if n.len == 3:
|
||||
let mode = semConstExpr(c, n[2])
|
||||
if mode.kind != nkIntLit:
|
||||
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
|
||||
else:
|
||||
m = mode.intVal
|
||||
inc c.inTypeofContext
|
||||
defer: dec c.inTypeofContext # compiles can raise an exception
|
||||
let ex = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {})
|
||||
closeScope(c)
|
||||
result = ex.typ
|
||||
if result.kind == tyFromExpr:
|
||||
@@ -2207,7 +2089,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
|
||||
localError(c.config, n.info, errTypeExpected)
|
||||
return errorSym(c, n)
|
||||
result = result.typ.sym.copySym(c.idgen)
|
||||
result.typ = exactReplica(result.typ, c.idgen)
|
||||
result.typ = exactReplica(result.typ)
|
||||
result.typ.incl tfUnresolved
|
||||
|
||||
if result.kind == skGenericParam:
|
||||
@@ -2250,7 +2132,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = nil
|
||||
inc c.inTypeContext
|
||||
|
||||
if c.config.ideActive: suggestExpr(c, n)
|
||||
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
|
||||
case n.kind
|
||||
of nkEmpty: result = n.typ
|
||||
of nkTypeOfExpr:
|
||||
@@ -2352,9 +2234,6 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = semAnyRef(c, n, tyPtr, prev)
|
||||
elif op.id == ord(wRef):
|
||||
result = semAnyRef(c, n, tyRef, prev)
|
||||
elif op.id == ord(wStatic):
|
||||
checkSonsLen(n, 2, c.config)
|
||||
result = semStaticType(c, n[1], prev)
|
||||
elif op.id == ord(wType):
|
||||
checkSonsLen(n, 2, c.config)
|
||||
result = semTypeOf(c, n[1], prev)
|
||||
@@ -2452,7 +2331,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
else:
|
||||
result = typeExpr.typ.base
|
||||
if result.isMetaType and
|
||||
result.kind notin tyTypeClasses:
|
||||
result.kind != tyUserTypeClass:
|
||||
# the dot expression may refer to a concept type in
|
||||
# a different module. allow a normal alias then.
|
||||
let preprocessed = semGenericStmt(c, n)
|
||||
@@ -2481,7 +2360,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
# bugfix: keep the fresh id for aliases to integral types:
|
||||
if s.typ.kind notin {tyBool, tyChar, tyInt..tyInt64, tyFloat..tyFloat128,
|
||||
tyUInt..tyUInt64}:
|
||||
prev.bindingId = s.typ.bindingId
|
||||
prev.itemId = s.typ.itemId
|
||||
result = prev
|
||||
of nkSym:
|
||||
let s = getGenSym(c, n.sym)
|
||||
|
||||
@@ -180,6 +180,35 @@ proc prepareNode*(cl: var TReplTypeVars, n: PNode): PNode =
|
||||
for i in 0..<n.safeLen:
|
||||
result.add(prepareNode(cl, n[i]))
|
||||
|
||||
proc isTypeParam(n: PNode): bool =
|
||||
# XXX: generic params should use skGenericParam instead of skType
|
||||
return n.kind == nkSym and
|
||||
(n.sym.kind == skGenericParam or
|
||||
(n.sym.kind == skType and sfFromGeneric in n.sym.flags))
|
||||
|
||||
when false: # old workaround
|
||||
proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
|
||||
# This is needed for tuninstantiatedgenericcalls
|
||||
# It's possible that a generic param will be used in a proc call to a
|
||||
# typedesc accepting proc. After generic param substitution, such procs
|
||||
# should be optionally instantiated with the correct type. In order to
|
||||
# perform this instantiation, we need to re-run the generateInstance path
|
||||
# in the compiler, but it's quite complicated to do so at the moment so we
|
||||
# resort to a mild hack; the head symbol of the call is temporary reset and
|
||||
# overload resolution is executed again (which may trigger generateInstance).
|
||||
if n.kind in nkCallKinds and sfFromGeneric in n[0].sym.flags:
|
||||
var needsFixing = false
|
||||
for i in 1..<n.safeLen:
|
||||
if isTypeParam(n[i]): needsFixing = true
|
||||
if needsFixing:
|
||||
n[0] = newSymNode(n[0].sym.owner)
|
||||
return cl.c.semOverloadedCall(cl.c, n, n, {skProc, skFunc}, {})
|
||||
|
||||
for i in 0..<n.safeLen:
|
||||
n[i] = reResolveCallsWithTypedescParams(cl, n[i])
|
||||
|
||||
return n
|
||||
|
||||
proc replaceObjBranches(cl: TReplTypeVars, n: PNode): PNode =
|
||||
result = n
|
||||
case n.kind
|
||||
@@ -243,17 +272,10 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PT
|
||||
if n == nil: return
|
||||
result = copyNode(n)
|
||||
if n.typ != nil:
|
||||
var nodeTyp = n.typ
|
||||
if nodeTyp.kind == tyFromExpr:
|
||||
if n.typ.kind == tyFromExpr:
|
||||
# type of node should not be evaluated as a static value
|
||||
if nodeTyp.state == Sealed:
|
||||
# IC: do not brand the loaded shared original — a tyFromExpr is a
|
||||
# placeholder that `replaceTypeVarsT` resolves away, so the copy
|
||||
# carries no identity later comparisons could miss (mirrors
|
||||
# `instantiateProcType`)
|
||||
nodeTyp = copyType(nodeTyp, cl.c.idgen, nodeTyp.owner)
|
||||
nodeTyp.incl tfNonConstExpr
|
||||
result.typ = replaceTypeVarsT(cl, nodeTyp)
|
||||
n.typ.incl tfNonConstExpr
|
||||
result.typ = replaceTypeVarsT(cl, n.typ)
|
||||
checkMetaInvariants(cl, result.typ)
|
||||
case n.kind
|
||||
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||
@@ -265,22 +287,13 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PT
|
||||
replaceTypeVarsS(cl, n.sym, result.typ)
|
||||
else:
|
||||
replaceTypeVarsS(cl, n.sym, replaceTypeVarsT(cl, n.sym.typ))
|
||||
if result.sym.kind == skField and
|
||||
if result.sym.kind == skField and result.sym.ast != nil and
|
||||
(cl.owner == nil or result.sym.owner == cl.owner):
|
||||
if result.sym.ast != nil:
|
||||
# instantiate default value of object/tuple field
|
||||
var n = result.sym.ast
|
||||
cl.c.fitDefaultNode(cl.c, n, result.sym.typ)
|
||||
result.sym.ast = n
|
||||
result.sym.typ = n.typ.skipIntLit(cl.c.idgen)
|
||||
elif result.typ != nil:
|
||||
# The field SYM can be SHARED across the branches of an `nkRecWhen` (the
|
||||
# generic body reuses one `value` PSym, so it carries the LAST branch's
|
||||
# type), while the resolved field NODE carries the correct branch type.
|
||||
# Sync the sym to the node so the instantiated field's sym-type and
|
||||
# node-type agree (else a generic-object instance serializes a field
|
||||
# whose sym-type diverges from its node-type -> loader/computeSize crash).
|
||||
result.sym.typ = result.typ
|
||||
# instantiate default value of object/tuple field
|
||||
var n = result.sym.ast
|
||||
cl.c.fitDefaultNode(cl.c, n, result.sym.typ)
|
||||
result.sym.ast = n
|
||||
result.sym.typ = n.typ.skipIntLit(cl.c.idgen)
|
||||
# sym type can be nil if was gensym created by macro, see #24048
|
||||
if result.sym.typ != nil and result.sym.typ.kind == tyVoid:
|
||||
# don't add the 'void' field
|
||||
@@ -374,13 +387,6 @@ proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType =
|
||||
# don't bind `auto` return type to a previous binding of `auto`
|
||||
return nil
|
||||
result = cl.typeMap.lookup(t)
|
||||
when defined(icDbgRefc):
|
||||
if t.kind in {tyGenericParam, tyTypeDesc}:
|
||||
echo "[icBind] lookup ", t.kind, " ", typeToString(t), " itemId=", t.itemId.module, ".",
|
||||
t.itemId.item, " bindingId=", t.bindingId.module, ".", t.bindingId.item,
|
||||
" state=", t.state, " flags=", t.flags, " -> ",
|
||||
(if result != nil: typeToString(result) else: "MISS"),
|
||||
" allowMeta=", cl.allowMetaTypes
|
||||
if result == nil:
|
||||
if cl.allowMetaTypes or tfRetType in t.flags: return
|
||||
localError(cl.c.config, t.sym.info, "cannot instantiate: '" & typeToString(t) & "'")
|
||||
@@ -395,7 +401,7 @@ proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType =
|
||||
proc instCopyType*(cl: var TReplTypeVars, t: PType): PType =
|
||||
# XXX: relying on allowMetaTypes is a kludge
|
||||
if cl.allowMetaTypes:
|
||||
result = t.exactReplica(cl.c.idgen)
|
||||
result = t.exactReplica
|
||||
else:
|
||||
result = copyType(t, cl.c.idgen, t.owner)
|
||||
copyTypeProps(cl.c.graph, cl.c.idgen.module, result, t)
|
||||
@@ -423,7 +429,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
var header = t
|
||||
# search for some instantiation here:
|
||||
if cl.allowMetaTypes:
|
||||
result = getOrDefault(cl.localCache, t.bindingId)
|
||||
result = getOrDefault(cl.localCache, t.itemId)
|
||||
else:
|
||||
result = searchInstTypes(cl.c.graph, t)
|
||||
|
||||
@@ -440,13 +446,6 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
header[i] = x
|
||||
propagateToOwner(header, x)
|
||||
else:
|
||||
# Under IC `t` may be a loaded dep type (Sealed/immutable); mutating it
|
||||
# would assert, so propagate into a copy. For non-Sealed types keep
|
||||
# devel's in-place propagation: unconditionally copying here changes
|
||||
# `header != t` and with it the cached-instance lookup below, which
|
||||
# regressed non-IC generic instantiations (arraymancer: a cached
|
||||
# NimSeqV2 instance with stale flags was returned for a cast target).
|
||||
if header == t and t.state == Sealed: header = instCopyType(cl, t)
|
||||
propagateToOwner(header, x)
|
||||
|
||||
if header != t:
|
||||
@@ -460,11 +459,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
else:
|
||||
header = instCopyType(cl, t)
|
||||
|
||||
# The instantiating module owns the instance (and announces it as an offer):
|
||||
# the generic body's module (`t.genericHead.owner`) has no business owning a
|
||||
# type that references instantiation-site types — that is the IC parent->child
|
||||
# heap leak the write-barrier surfaces.
|
||||
result = newType(tyGenericInst, cl.c.idgen, cl.c.module, son = header.genericHead)
|
||||
result = newType(tyGenericInst, cl.c.idgen, t.genericHead.owner, son = header.genericHead)
|
||||
result.flags = header.flags
|
||||
# be careful not to propagate unnecessary flags here (don't use rawAddSon)
|
||||
# ugh need another pass for deeply recursive generic types (e.g. PActor)
|
||||
@@ -473,7 +468,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
if not cl.allowMetaTypes:
|
||||
cacheTypeInst(cl.c, result)
|
||||
else:
|
||||
cl.localCache[t.bindingId] = result
|
||||
cl.localCache[t.itemId] = result
|
||||
|
||||
let oldSkipTypedesc = cl.skipTypedesc
|
||||
cl.skipTypedesc = true
|
||||
@@ -502,14 +497,8 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
let bbody = last body
|
||||
var newbody = replaceTypeVarsT(cl, bbody, isInstValue = true)
|
||||
cl.skipTypedesc = oldSkipTypedesc
|
||||
let newbodyFlags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
|
||||
if newbody.state != Sealed:
|
||||
newbody.flags = newbodyFlags
|
||||
# else: `newbody` is a type loaded from a dep module (it can even be a
|
||||
# builtin like `int` when the generic's body is computed by a macro) and is
|
||||
# immutable under IC. Skip the in-place flag accumulation on the shared
|
||||
# type; the instance `result` still receives the flags below.
|
||||
result.flags = result.flags + newbodyFlags - tfInstClearedFlags
|
||||
newbody.flags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
|
||||
result.flags = result.flags + newbody.flags - tfInstClearedFlags
|
||||
|
||||
setToPreviousLayer(cl.typeMap)
|
||||
|
||||
@@ -529,11 +518,8 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
# generics *when the type is constructed*:
|
||||
cl.c.graph.setAttachedOp(cl.c.module.position, newbody, attachedDeepCopy,
|
||||
cl.c.instTypeBoundOp(cl.c, dc, result, cl.info, attachedDeepCopy, 1))
|
||||
if newbody.typeInst == nil and newbody.state != Sealed:
|
||||
if newbody.typeInst == nil:
|
||||
# doAssert newbody.typeInst == nil
|
||||
# An IC-loaded (Sealed) `newbody` keeps whatever `typeInst` its defining
|
||||
# module serialized; recording this process's first instantiation on the
|
||||
# shared type is not possible (and was always first-wins anyway).
|
||||
newbody.typeInst = result
|
||||
if tfRefsAnonObj in newbody.flags and newbody.kind != tyGenericInst:
|
||||
# can come here for tyGenericInst too, see tests/metatype/ttypeor.nim
|
||||
@@ -647,7 +633,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
# type
|
||||
# Vector[N: static[int]] = array[N, float64]
|
||||
# TwoVectors[Na, Nb: static[int]] = (Vector[Na], Vector[Nb])
|
||||
result = getOrDefault(cl.localCache, t.bindingId)
|
||||
result = getOrDefault(cl.localCache, t.itemId)
|
||||
if result != nil: return result
|
||||
inc cl.recursionLimit
|
||||
|
||||
@@ -739,7 +725,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
return
|
||||
bailout()
|
||||
result = instCopyType(cl, t)
|
||||
cl.localCache[t.bindingId] = result
|
||||
cl.localCache[t.itemId] = result
|
||||
for i in FirstGenericParamAt..<result.kidsLen:
|
||||
var r = result[i]
|
||||
if r != nil:
|
||||
@@ -755,7 +741,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
of tyGenericInst, tyUserTypeClassInst:
|
||||
bailout()
|
||||
result = instCopyType(cl, t)
|
||||
cl.localCache[t.bindingId] = result
|
||||
cl.localCache[t.itemId] = result
|
||||
for i in FirstGenericParamAt..<result.kidsLen:
|
||||
result[i] = replaceTypeVarsT(cl, result[i])
|
||||
propagateToOwner(result, result.last)
|
||||
@@ -770,7 +756,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
result = instCopyType(cl, t)
|
||||
result.size = -1 # needs to be recomputed
|
||||
#if not cl.allowMetaTypes:
|
||||
cl.localCache[t.bindingId] = result
|
||||
cl.localCache[t.itemId] = result
|
||||
let propagateInstValue = isInstValue and isRefPtrObject(t)
|
||||
|
||||
for i, resulti in result.ikids:
|
||||
@@ -818,21 +804,11 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
# trough replaceObjBranches in order to resolve any pending nkRecWhen nodes
|
||||
result = t
|
||||
|
||||
# Slow path, we have some work to do. CRUCIAL: only ever mutate a type that
|
||||
# is LOCAL to the module we are instantiating in (`itemId.module ==
|
||||
# idgen.module`). A type loaded from another module's NIF (foreign) already
|
||||
# had its object branches resolved when it was originally compiled; mutating
|
||||
# it in place here is an old→new heap write that re-homes the loaded type to
|
||||
# the instantiation site (its sym then looks owned by the consumer module and
|
||||
# loses its `info`, colliding C type names — the libp2p `Message` bug). The
|
||||
# prior `state != Sealed` guard was insufficient: a freshly-LOADED type is
|
||||
# `Complete`, not `Sealed` (`Sealed` only means "already re-written to a NIF").
|
||||
if t.kind == tyRef and t.hasElementType and t.elementType.kind == tyObject and
|
||||
t.elementType.n != nil and t.elementType.itemId.module == cl.c.idgen.module.int:
|
||||
# Slow path, we have some work to do
|
||||
if t.kind == tyRef and t.hasElementType and t.elementType.kind == tyObject and t.elementType.n != nil:
|
||||
discard replaceObjBranches(cl, t.elementType.n)
|
||||
|
||||
elif result.n != nil and t.kind == tyObject and result.state != Sealed and
|
||||
result.itemId.module == cl.c.idgen.module.int:
|
||||
elif result.n != nil and t.kind == tyObject:
|
||||
# Invalidate the type size as we may alter its structure
|
||||
result.size = -1
|
||||
result.n = replaceObjBranches(cl, result.n)
|
||||
@@ -884,10 +860,7 @@ proc recomputeFieldPositions*(t: PType; obj: PNode; currPosition: var int) =
|
||||
for i in 1..<obj.len:
|
||||
recomputeFieldPositions(nil, lastSon(obj[i]), currPosition)
|
||||
of nkSym:
|
||||
# A field loaded from the IC cache is already at its final position and must
|
||||
# not be mutated; only freshly instantiated fields need (re)positioning.
|
||||
if obj.sym.state != Sealed:
|
||||
obj.sym.position = currPosition
|
||||
obj.sym.position = currPosition
|
||||
inc currPosition
|
||||
else: discard "cannot happen"
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
## Computes hash values for routine (proc, method etc) signatures.
|
||||
|
||||
import ast, ropes, modulegraphs, options, msgs, pathutils
|
||||
from lineinfos import FileIndex
|
||||
from std/hashes import Hash
|
||||
import std/tables
|
||||
import types
|
||||
@@ -53,17 +52,7 @@ proc hashSym(c: var MD5Context, s: PSym) =
|
||||
c &= ":anon"
|
||||
else:
|
||||
var it = s
|
||||
when defined(icDbgHash):
|
||||
var ownerSteps = 0
|
||||
while it != nil:
|
||||
when defined(icDbgHash):
|
||||
inc ownerSteps
|
||||
if ownerSteps >= 1000 and ownerSteps <= 1030:
|
||||
echo "OWNERLOOP(hashSym) n=", ownerSteps, " sym=", it.name.s, " kind=", it.kind,
|
||||
" id=", it.itemId, " flags=", it.flags, " state=", it.state,
|
||||
" start=", s.name.s, " startId=", s.itemId
|
||||
elif ownerSteps == 1031:
|
||||
raiseAssert "owner-chain cycle detected, see OWNERLOOP dump above"
|
||||
c &= it.name.s
|
||||
c &= "."
|
||||
it = it.owner
|
||||
@@ -75,30 +64,8 @@ proc hashTypeSym(c: var MD5Context, s: PSym; conf: ConfigRef) =
|
||||
c &= ":anon"
|
||||
else:
|
||||
var it = s
|
||||
# The source file path disambiguates same-named object types from different
|
||||
# modules whose owner-chain names also coincide (e.g. libp2p kademlia/protobuf
|
||||
# `Message` vs rendezvous/protobuf `Message`, both modules named `protobuf`).
|
||||
# A type sym that reaches the backend as a `Complete` stub never individually
|
||||
# loaded carries `unknownLineInfo` (fileIndex -1), which `toFullPath` collapses
|
||||
# to the `???` placeholder — so the two would hash to ONE mangled C name and the
|
||||
# wrong struct gets emitted. Fall back to the sym's HOME module file (its
|
||||
# per-module NIF-suffix path, stable+unique) for the path. Only fires on a -1
|
||||
# fileIndex; non-IC type syms always have a real `info`, so the fast path is
|
||||
# taken and the hash is unchanged (koch boot byte-equal).
|
||||
let infoFi = s.info.fileIndex
|
||||
let pathFi = if infoFi.int32 >= 0'i32: infoFi else: s.itemId.module.int32.FileIndex
|
||||
c &= customPath(conf.toFullPath(pathFi))
|
||||
when defined(icDbgHash):
|
||||
var ownerSteps = 0
|
||||
c &= customPath(conf.toFullPath(s.info))
|
||||
while it != nil:
|
||||
when defined(icDbgHash):
|
||||
inc ownerSteps
|
||||
if ownerSteps >= 1000 and ownerSteps <= 1030:
|
||||
echo "OWNERLOOP n=", ownerSteps, " sym=", it.name.s, " kind=", it.kind,
|
||||
" id=", it.itemId, " flags=", it.flags, " state=", it.state,
|
||||
" start=", s.name.s, " startId=", s.itemId
|
||||
elif ownerSteps == 1031:
|
||||
raiseAssert "owner-chain cycle detected, see OWNERLOOP dump above"
|
||||
if sfFromGeneric in it.flags and it.kind in routineKinds and
|
||||
it.typ != nil:
|
||||
hashType c, it.typ, {CoProc}, conf
|
||||
@@ -135,44 +102,15 @@ proc hashTree(c: var MD5Context, n: PNode; flags: set[ConsiderFlag]; conf: Confi
|
||||
else:
|
||||
for i in 0..<n.len: hashTree(c, n[i], flags, conf)
|
||||
|
||||
when defined(icDbgHash):
|
||||
var hashDepth = 0
|
||||
var hashCalls = 0
|
||||
var hashMaxDepth = 0
|
||||
|
||||
proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) =
|
||||
if t == nil:
|
||||
c &= "\254"
|
||||
return
|
||||
when defined(icDbgHash):
|
||||
inc hashDepth
|
||||
inc hashCalls
|
||||
if hashDepth > hashMaxDepth: hashMaxDepth = hashDepth
|
||||
if hashCalls >= 500_000_000 and hashCalls <= 500_000_300:
|
||||
echo "HASHLOOP n=", hashCalls, " d=", hashDepth, " kind=", t.kind, " id=", t.itemId,
|
||||
" bindingId=", t.bindingId, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL"),
|
||||
" state=", t.state, " owner=", (if t.owner != nil: t.owner.name.s else: "NIL")
|
||||
elif hashCalls == 500_000_301:
|
||||
echo "HASHLOOP maxDepth=", hashMaxDepth
|
||||
raiseAssert "hashType runaway detected, see HASHLOOP dump above"
|
||||
defer:
|
||||
dec hashDepth
|
||||
|
||||
# Ensure type is fully loaded before hashing to avoid hash changing
|
||||
# as properties are accessed and trigger lazy loading.
|
||||
backendEnsureMutable(t)
|
||||
|
||||
# Bare type-class keywords used as a typedesc without arguments (e.g. `array`,
|
||||
# `range`, `distinct` passed to `signatureHash`) have no children, so the
|
||||
# structural branches below would index a non-existent `elementType`. Hash them
|
||||
# by kind (+ sym for an extra, stable distinction) — enough for a stable,
|
||||
# distinct identity. (`seq`/`openArray`/`tuple` already fall through the empty
|
||||
# `else` loop unharmed; this covers the branches that index `elementType`.)
|
||||
if t.kind in {tyArray, tyRange, tyDistinct} and not t.hasElementType:
|
||||
c &= char(t.kind)
|
||||
if t.sym != nil: c.hashSym(t.sym)
|
||||
return
|
||||
|
||||
case t.kind
|
||||
of tyGenericInvocation:
|
||||
for a in t.kids:
|
||||
@@ -203,17 +141,12 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
if CoConsiderOwned in flags:
|
||||
c &= char(t.kind)
|
||||
c.hashType t.skipModifier, flags, conf
|
||||
of tyBool, tyChar, tyPointer, tyCstring, tyInt..tyUInt64:
|
||||
# no canonicalization for builtin scalar-ish / pointer-like types, so
|
||||
# that e.g. ``pid_t`` or an imported ``pointer`` alias keep their
|
||||
# backend spelling instead of collapsing into the generic Nim builtin:
|
||||
of tyBool, tyChar, tyInt..tyUInt64:
|
||||
# no canonicalization for integral types, so that e.g. ``pid_t`` is
|
||||
# produced instead of ``NI``:
|
||||
c &= char(t.kind)
|
||||
if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}:
|
||||
# Aliases inherit the external name, but have a different symbol.
|
||||
if t.sym.loc.snippet != "":
|
||||
c &= t.sym.loc.snippet
|
||||
else:
|
||||
c.hashSym(t.sym)
|
||||
c.hashSym(t.sym)
|
||||
of tyObject, tyEnum:
|
||||
if t.typeInstImpl != nil:
|
||||
# prevent against infinite recursions here, see bug #8883:
|
||||
@@ -278,7 +211,6 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
c.hashTree(t.n, {}, conf)
|
||||
of tyTuple:
|
||||
c &= char(t.kind)
|
||||
c &= t.len
|
||||
if t.n != nil and CoType notin flags:
|
||||
for i in 0..<t.n.len:
|
||||
assert(t.n[i].kind == nkSym)
|
||||
@@ -316,29 +248,6 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
c.hashType(param.typ, flags, conf)
|
||||
c &= ','
|
||||
c.hashType(t.returnType, flags, conf)
|
||||
elif t.n != nil and t.n.kind == nkFormalParams:
|
||||
# Under IC a loaded proc type stores its parameters only in `n`; `sons`
|
||||
# holds just the return type. Hashing `t.signature` would silently drop
|
||||
# every parameter, collapsing distinct proc types onto one hash, so the
|
||||
# same logical type got different C struct names in different TUs
|
||||
# ("incompatible type for argument" on closure args). Hash the return
|
||||
# type first and then the parameter types from `n` — for from-source
|
||||
# types `n`'s param types equal `sons[1..]`, so non-IC hashes are
|
||||
# unchanged. (Same fix as typekeys' tyProc branch.)
|
||||
c.hashType(t.returnType, flags, conf)
|
||||
for i in 1..<t.n.len:
|
||||
let p = t.n[i]
|
||||
if p.kind == nkSym:
|
||||
backendEnsureMutable(p.sym)
|
||||
# The hidden closure env param: under IC, lambda lifting shares the
|
||||
# routine's AST params with `typ.n`, so the lifted `:envP` leaks into
|
||||
# the TYPE's params (from-source types never carry it). It is not part
|
||||
# of the type's identity — `genProcParams` skips it the same way.
|
||||
if t.callConv == ccClosure and p.sym.name.s == ":envP":
|
||||
continue
|
||||
c.hashType(p.sym.typ, flags, conf)
|
||||
else:
|
||||
c.hashType(p.typ, flags, conf)
|
||||
else:
|
||||
for a in t.signature: c.hashType(a, flags, conf)
|
||||
c &= char(t.callConv)
|
||||
@@ -354,21 +263,6 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
c &= char(t.kind)
|
||||
c.hashType(t.indexType, flags-{CoIgnoreRange}+{CoIgnoreRangeInArray}, conf)
|
||||
c.hashType(t.elementType, flags-{CoIgnoreRange}, conf)
|
||||
of tyBuiltInTypeClass:
|
||||
# A builtin type class (`object`, `tuple`, `proc`, `ref`, `seq`, ...) is
|
||||
# identified solely by the *kind* of its single placeholder son plus a few
|
||||
# flags/callConv (see `sameType`). That son is a fresh, field-less, sym-less
|
||||
# type, so the generic `else` below would recurse into it and hash its
|
||||
# process-local `t.id` — unstable across the NIF boundary. nim-serialization
|
||||
# keys auto-serialization on `signatureHash(object)`/`tuple`/... and missed
|
||||
# under IC because the registering and consuming modules minted different
|
||||
# placeholder ids. Hash the class identity that `sameType` actually compares.
|
||||
c &= char(t.kind)
|
||||
let elem = t.elementType
|
||||
c &= char(elem.kind)
|
||||
for f in eqTypeFlags * elem.flags: c &= char(ord(f))
|
||||
if elem.kind == tyProc and tfExplicitCallConv in elem.flags:
|
||||
c &= char(elem.callConv)
|
||||
else:
|
||||
c &= char(t.kind)
|
||||
for a in t.kids: c.hashType(a, flags, conf)
|
||||
@@ -552,3 +446,4 @@ proc idOrSig*(s: PSym, currentModule: string,
|
||||
if counter != 0:
|
||||
result.add "_" & rope(counter+1)
|
||||
sigCollisions.inc(sig)
|
||||
|
||||
|
||||
@@ -46,8 +46,7 @@ type
|
||||
|
||||
TCandidate* = object
|
||||
c*: PContext
|
||||
exactMatches*: int
|
||||
iteratorPreference*: int # prefer iterators in iterator-oriented contexts
|
||||
exactMatches*: int # also misused to prefer iters over procs
|
||||
genericMatches: int # also misused to prefer constraints
|
||||
subtypeMatches: int
|
||||
intConvMatches: int # conversions to int are not as expensive
|
||||
@@ -111,8 +110,7 @@ proc markOwnerModuleAsUsed*(c: PContext; s: PSym)
|
||||
proc initCandidateAux(ctx: PContext,
|
||||
callee: PType): TCandidate {.inline.} =
|
||||
result = TCandidate(c: ctx, exactMatches: 0, subtypeMatches: 0,
|
||||
iteratorPreference: 0, convMatches: 0, intConvMatches: 0,
|
||||
genericMatches: 0,
|
||||
convMatches: 0, intConvMatches: 0, genericMatches: 0,
|
||||
state: csEmpty, firstMismatch: MismatchInfo(),
|
||||
callee: callee, call: nil, baseTypeMatch: false,
|
||||
genericConverter: false, inheritancePenalty: -1
|
||||
@@ -135,11 +133,6 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} =
|
||||
writeStackTrace()
|
||||
if c.c.module.name.s == "temp3":
|
||||
echo "binding ", key, " -> ", val
|
||||
when defined(icDbgRefc):
|
||||
if key.kind in {tyGenericParam, tyTypeDesc}:
|
||||
echo "[icBind] put ", key.kind, " ", typeToString(key), " itemId=", key.itemId.module, ".",
|
||||
key.itemId.item, " bindingId=", key.bindingId.module, ".", key.bindingId.item,
|
||||
" state=", key.state, " -> ", typeToString(val)
|
||||
put(c.bindings, key, val.skipIntLit(c.c.idgen))
|
||||
|
||||
proc typeRel*(c: var TCandidate, f, aOrig: PType,
|
||||
@@ -182,6 +175,7 @@ proc matchGenericParams*(m: var TCandidate, binding: PNode, callee: PSym) =
|
||||
## state is set to `csMatch` if all generic params match, `csEmpty` if
|
||||
## implicit generic parameters are missing (matches but cannot instantiate),
|
||||
## `csNoMatch` if a constraint fails or param count doesn't match
|
||||
let c = m.c
|
||||
let typeParams = callee.ast[genericParamsPos]
|
||||
let paramCount = typeParams.len
|
||||
let bindingCount = binding.len-1
|
||||
@@ -399,7 +393,6 @@ proc complexDisambiguation(a, b: PType): int =
|
||||
proc writeMatches*(c: TCandidate) =
|
||||
echo "Candidate '", c.calleeSym.name.s, "' at ", c.c.config $ c.calleeSym.info
|
||||
echo " exact matches: ", c.exactMatches
|
||||
echo " iterator preference: ", c.iteratorPreference
|
||||
echo " generic matches: ", c.genericMatches
|
||||
echo " subtype matches: ", c.subtypeMatches
|
||||
echo " intconv matches: ", c.intConvMatches
|
||||
@@ -418,8 +411,6 @@ proc cmpInheritancePenalty(a, b: int): int =
|
||||
proc cmpCandidates*(a, b: TCandidate, isFormal=true): int =
|
||||
result = a.exactMatches - b.exactMatches
|
||||
if result != 0: return
|
||||
result = a.iteratorPreference - b.iteratorPreference
|
||||
if result != 0: return
|
||||
result = a.genericMatches - b.genericMatches
|
||||
if result != 0: return
|
||||
result = a.subtypeMatches - b.subtypeMatches
|
||||
@@ -648,7 +639,7 @@ type
|
||||
SkippedPtr = enum skippedNone, skippedRef, skippedPtr
|
||||
|
||||
proc skipToObject(t: PType; skipped: var SkippedPtr): PType =
|
||||
var r {.cursor.} = t
|
||||
var r = t
|
||||
# we're allowed to skip one level of ptr/ref:
|
||||
var ptrs = 0
|
||||
while r != nil:
|
||||
@@ -706,6 +697,8 @@ proc recordRel(c: var TCandidate, f, a: PType, flags: TTypeRelFlags): TTypeRelat
|
||||
result = isEqual
|
||||
elif sameTupleLengths(a, f):
|
||||
result = isEqual
|
||||
let firstField = if f.kind == tyTuple: 0
|
||||
else: 1
|
||||
for _, ff, aa in tupleTypePairs(f, a):
|
||||
var m = typeRel(c, ff, aa, flags)
|
||||
if m < isSubtype: return isNone
|
||||
@@ -786,19 +779,6 @@ proc procParamTypeRel(c: var TCandidate; f, a: PType): TTypeRelation =
|
||||
# if f is metatype.
|
||||
result = typeRel(c, f, a)
|
||||
|
||||
if result == isEqual and
|
||||
procParamTypeBackendAliases notin c.c.config.legacyFeatures:
|
||||
# Ensure types that are semantically equal also match at the backend level.
|
||||
# E.g. reject assigning proc(csize_t) to proc(uint) since these map to
|
||||
# different C types (size_t vs unsigned long long).
|
||||
let fCheck = concreteType(c, f)
|
||||
let aCheck = concreteType(c, a)
|
||||
# Note that `result` is equal; now check whether they have the same
|
||||
# backend type.
|
||||
if fCheck != nil and aCheck != nil and
|
||||
not sameBackendTypePickyAliases(fCheck, aCheck, {IgnoreFlags}):
|
||||
result = isNone
|
||||
|
||||
if result <= isSubrange or inconsistentVarTypes(f, a):
|
||||
result = isNone
|
||||
|
||||
@@ -913,14 +893,16 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
|
||||
case typ.kind
|
||||
of tyStatic:
|
||||
param = paramSym skConst
|
||||
param.typ = copyType(typ, m.c.idgen, typ.owner)
|
||||
param.typ = typ.exactReplica
|
||||
#copyType(typ, c.idgen, typ.owner)
|
||||
if typ.n == nil:
|
||||
param.typ.incl tfInferrableStatic
|
||||
else:
|
||||
param.ast = typ.n
|
||||
of tyFromExpr:
|
||||
param = paramSym skVar
|
||||
param.typ = copyType(typ, m.c.idgen, typ.owner)
|
||||
param.typ = typ.exactReplica
|
||||
#copyType(typ, c.idgen, typ.owner)
|
||||
else:
|
||||
param = paramSym skType
|
||||
param.typ = if typ.isMetaType:
|
||||
@@ -972,7 +954,8 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
|
||||
if ff.kind == tyUserTypeClassInst:
|
||||
result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff)
|
||||
else:
|
||||
result = copyType(ff, m.c.idgen, ff.owner)
|
||||
result = ff.exactReplica
|
||||
#copyType(ff, c.idgen, ff.owner)
|
||||
|
||||
result.n = checkedBody
|
||||
|
||||
@@ -987,6 +970,13 @@ proc shouldSkipDistinct(m: TCandidate; rules: PNode, callIdent: PIdent): bool =
|
||||
if considerQuotedIdent(m.c, r) == callIdent: return false
|
||||
return true
|
||||
|
||||
proc maybeSkipDistinct(m: TCandidate; t: PType, callee: PSym): PType =
|
||||
if t != nil and t.kind == tyDistinct and t.n != nil and
|
||||
shouldSkipDistinct(m, t.n, callee.name):
|
||||
result = t.base
|
||||
else:
|
||||
result = t
|
||||
|
||||
proc tryResolvingStaticExpr(c: var TCandidate, n: PNode,
|
||||
allowUnresolved = false,
|
||||
allowCalls = false,
|
||||
@@ -1160,14 +1150,8 @@ proc enterConceptMatch(c: var TCandidate; f,a: PType, flags: TTypeRelFlags): TTy
|
||||
if concpt.kind != tyConcept:
|
||||
container = concpt
|
||||
concpt = container.reduceToBase
|
||||
# considerPreviousT-like behavior
|
||||
let prev = lookup(c.bindings, concpt)
|
||||
if prev != nil:
|
||||
return typeRel(c, prev, a, flags)
|
||||
if trDontBind in flags:
|
||||
conceptFlags.incl mfDontBind
|
||||
if trBindGenericParam in flags:
|
||||
conceptFlags.incl mfBindGenericParam
|
||||
if trCheckGeneric in flags:
|
||||
conceptFlags.incl mfCheckGeneric
|
||||
let mres = concepts.conceptMatch(c.c, concpt, a, c.bindings, container, flags = conceptFlags)
|
||||
@@ -1231,28 +1215,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
|
||||
assert(aOrig != nil)
|
||||
|
||||
let useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
|
||||
not c.isNoCall and
|
||||
f.kind != tyTypeDesc and
|
||||
tfExplicit notin aOrig.flags and
|
||||
tfConceptMatchedTypeSym notin aOrig.flags
|
||||
var
|
||||
useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
|
||||
not c.isNoCall and
|
||||
f.kind != tyTypeDesc and
|
||||
tfExplicit notin aOrig.flags and
|
||||
tfConceptMatchedTypeSym notin aOrig.flags
|
||||
|
||||
template skipTypeCursor(it, kinds: untyped) =
|
||||
# `ast.last`, not a hand-inlined copy of it. What this replaces was `last`'s
|
||||
# body verbatim MINUS its `if state == Partial: loadType` line -- and that
|
||||
# line is the whole point: a NIF-loaded stub answers `kind` off its NIF name
|
||||
# while `sonsImpl` is still EMPTY, so `sonsImpl[^1]` raised IndexDefect.
|
||||
# nimbus-eth2 died on it in the very first `nim ic` pass, inside the `x is T`
|
||||
# under a chronos `{.async.}` iterator's `when`. The second call site below
|
||||
# is unguarded and runs on EVERY `typeRel`, so this is not a concept-only
|
||||
# corner: a probe counts 195 Partial `tyVar`/`tyLent` arrivals across one
|
||||
# nimbus frontend, each of which was an IndexDefect waiting for its turn.
|
||||
while it.kind in kinds:
|
||||
it = it.last
|
||||
|
||||
var aOrig {.cursor.} = aOrig
|
||||
if useTypeLoweringRuleInTypeClass:
|
||||
skipTypeCursor(aOrig, {tyTypeDesc})
|
||||
aOrig = if useTypeLoweringRuleInTypeClass:
|
||||
aOrig.skipTypes({tyTypeDesc})
|
||||
else:
|
||||
aOrig
|
||||
|
||||
if aOrig.kind == tyInferred:
|
||||
let prev = aOrig.previouslyInferred
|
||||
@@ -1289,14 +1262,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
template doBind: bool = trDontBind notin flags
|
||||
|
||||
# var, sink and static arguments match regular modifier-free types
|
||||
var a {.cursor.} = aOrig
|
||||
skipTypeCursor(a, {tyStatic, tyVar, tyLent, tySink})
|
||||
# Keep this expanded: an expression template materializes a PType temporary
|
||||
# here, adding an otherwise avoidable reference-counting pair.
|
||||
if a.kind == tyDistinct and a.n != nil and
|
||||
shouldSkipDistinct(c, a.n, c.calleeSym.name):
|
||||
a = a.base
|
||||
# XXX: Theoretically, distinct types could be skipped before we even
|
||||
var a = maybeSkipDistinct(c, aOrig.skipTypes({tyStatic, tyVar, tyLent, tySink}), c.calleeSym)
|
||||
# XXX: Theoretically, maybeSkipDistinct could be called before we even
|
||||
# start the param matching process. This could be done in `prepareOperand`
|
||||
# for example, but unfortunately `prepareOperand` is not called in certain
|
||||
# situation when nkDotExpr are rotated to nkDotCalls
|
||||
@@ -1774,21 +1741,6 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
let ff = last(f)
|
||||
if ff != nil:
|
||||
result = typeRel(c, ff, a, flags)
|
||||
if result == isNone and a.kind == tyGenericInst and trBindGenericParam in flags:
|
||||
var depth = -1
|
||||
# Generic-parameter constraints like `F: Future` can miss in `last(f)`
|
||||
# when the actual type inherits from a concrete generic instantiation.
|
||||
# Keep this fallback scoped to generic-parameter matching so typedesc
|
||||
# overloads such as `type Future[T]` still prefer more specific
|
||||
# descendants like `InternalRaisesFuture[T, E]`.
|
||||
if isGenericSubtype(c, a, f, depth, f) and depth > 0:
|
||||
var askip = skippedNone
|
||||
let aobj = a.skipToObject(askip)
|
||||
if aobj != nil and tfFinal notin aobj.flags:
|
||||
# Keep overload ranking consistent with other inheritance-based
|
||||
# matches: deeper descendants are slightly worse candidates.
|
||||
inc c.inheritancePenalty, depth + int(c.inheritancePenalty < 0)
|
||||
result = isGeneric
|
||||
of tyGenericInvocation:
|
||||
var x = a.skipGenericAlias
|
||||
if x.kind == tyGenericParam and x.len > 0:
|
||||
@@ -2093,18 +2045,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
result = typeRel(c, f.base, a, flags)
|
||||
else:
|
||||
result = isGeneric
|
||||
if result != isNone:
|
||||
if f.base.kind notin {tyNone, tyGenericParam} and
|
||||
aOrig.kind == tyStatic and aOrig.n != nil and aOrig.n.typ != nil and
|
||||
aOrig.n.typ.isEmptyContainer:
|
||||
# we need to infer the inner type for empty containers
|
||||
let literal = aOrig.n.copyTree
|
||||
literal.typ = f.base
|
||||
let staticArg = newTypeS(tyStatic, c.c, f.base)
|
||||
staticArg.n = literal
|
||||
put(c, f, staticArg)
|
||||
else:
|
||||
put(c, f, aOrig)
|
||||
if result != isNone: put(c, f, aOrig)
|
||||
elif aOrig.n != nil and aOrig.n.typ != nil:
|
||||
result = if f.base.kind != tyNone:
|
||||
typeRel(c, f.last, aOrig.n.typ, flags)
|
||||
@@ -2454,13 +2395,11 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
|
||||
argSemantized, argOrig: PNode): PNode =
|
||||
result = nil
|
||||
var
|
||||
fMaybeStatic = f.skipTypes({tyDistinct})
|
||||
arg = argSemantized
|
||||
a = a
|
||||
c = m.c
|
||||
let hasStatic = tfHasStatic in f.flags or
|
||||
(f.kind == tyDistinct and tfHasStatic in f.skipTypes({tyDistinct}).flags)
|
||||
if hasStatic:
|
||||
let fMaybeStatic = if f.kind == tyDistinct: f.skipTypes({tyDistinct}) else: f
|
||||
if tfHasStatic in fMaybeStatic.flags:
|
||||
# XXX: When implicit statics are the default
|
||||
# this will be done earlier - we just have to
|
||||
# make sure that static types enter here
|
||||
@@ -2516,10 +2455,6 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
|
||||
return arg
|
||||
elif f.kind == tyStatic and arg.typ.n != nil:
|
||||
return arg.typ.n
|
||||
elif f.kind == tyUntyped:
|
||||
# bug #25693: a different overload candidate may have sem-checked the
|
||||
# operand and left symbols behind; templates expect the pristine AST.
|
||||
return argOrig
|
||||
else:
|
||||
return argSemantized # argOrig
|
||||
|
||||
@@ -2694,7 +2629,7 @@ proc staticAwareTypeRel(m: var TCandidate, f: PType, arg: var PNode): TTypeRelat
|
||||
# The ast of the type does not point to the symbol.
|
||||
# Without this we will never resolve a `static proc` with overloads
|
||||
let copiedNode = copyNode(arg)
|
||||
copiedNode.typ = copyType(copiedNode.typ, m.c.idgen, copiedNode.typ.owner)
|
||||
copiedNode.typ = exactReplica(copiedNode.typ)
|
||||
copiedNode.typ.n = arg
|
||||
arg = copiedNode
|
||||
typeRel(m, f, arg.typ)
|
||||
@@ -2813,8 +2748,7 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode, newlyTyped: var bool):
|
||||
result = a
|
||||
elif a.typ.isNil:
|
||||
if formal.kind == tyIterable:
|
||||
let flags = {efDetermineType, efAllowStmt, efWantIterator, efWantIterable,
|
||||
efPreferIteratorForIterable}
|
||||
let flags = {efDetermineType, efAllowStmt, efWantIterator, efWantIterable}
|
||||
result = c.semOperand(c, a, flags)
|
||||
else:
|
||||
# XXX This is unsound! 'formal' can differ from overloaded routine to
|
||||
@@ -2831,20 +2765,6 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode, newlyTyped: var bool):
|
||||
considerGenSyms(c, result)
|
||||
if result.kind != nkHiddenDeref and result.typ.kind in {tyVar, tyLent} and c.matchedConcept == nil:
|
||||
result = newDeref(result)
|
||||
# Recovery for calls resolved too early as non-iterators.
|
||||
# TODO: retry only skIterator overloads instead of re-semming,
|
||||
# or preserve iterator-candidates info from the earlier semcheck.
|
||||
if formal.kind == tyIterable and result.typ.kind != tyIterable and
|
||||
a.kind in nkCallKinds and a[0].kind in {nkIdent, nkAccQuoted, nkSym, nkOpenSym}:
|
||||
let recheck = copyTree(a)
|
||||
recheck.typ = nil
|
||||
if recheck[0].kind == nkSym and recheck[0].sym != nil:
|
||||
recheck[0] = newIdentNode(recheck[0].sym.name, recheck[0].info)
|
||||
let flags = {efDetermineType, efAllowStmt, efNoUndeclared,
|
||||
efWantIterator, efWantIterable, efPreferIteratorForIterable}
|
||||
let fresh = c.semOperand(c, recheck, flags)
|
||||
if fresh.typ != nil and fresh.typ.kind == tyIterable:
|
||||
return fresh
|
||||
|
||||
proc prepareOperand(c: PContext; a: PNode, newlyTyped: var bool): PNode =
|
||||
if a.typ.isNil:
|
||||
@@ -2894,12 +2814,9 @@ proc findFirstArgBlock(m: var TCandidate, n: PNode): int =
|
||||
else: break
|
||||
|
||||
proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var IntSet) =
|
||||
|
||||
template noMatch() =
|
||||
if m.calleeSym != nil and m.calleeSym.kind notin {skTemplate, skMacro}:
|
||||
c.mergeShadowScope
|
||||
else:
|
||||
c.rememberShadowDefs
|
||||
c.closeShadowScope
|
||||
c.mergeShadowScope #merge so that we don't have to resem for later overloads
|
||||
m.state = csNoMatch
|
||||
m.firstMismatch.arg = a
|
||||
m.firstMismatch.formal = formal
|
||||
@@ -2955,10 +2872,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
|
||||
setSon(m.call, formal.position + 1, container)
|
||||
else:
|
||||
incrIndexType(container.typ)
|
||||
# bug #25693: like the scalar `tyUntyped` case in `paramTypesMatchAux`,
|
||||
# a previous overload candidate may have sem-checked the operand in
|
||||
# place; templates/macros expect the pristine AST, so use `nOrig`.
|
||||
container.add nOrig[a]
|
||||
container.add n[a]
|
||||
elif n[a].kind == nkExprEqExpr:
|
||||
# named param
|
||||
m.firstMismatch.kind = kUnknownNamedParam
|
||||
@@ -3057,8 +2971,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
|
||||
setSon(m.call, formal.position + 1, container)
|
||||
else:
|
||||
incrIndexType(container.typ)
|
||||
# bug #25693: see the leading isVarargsUntyped branch above.
|
||||
container.add nOrig[a]
|
||||
container.add n[a]
|
||||
else:
|
||||
m.baseTypeMatch = false
|
||||
m.typedescMatched = false
|
||||
@@ -3110,7 +3023,6 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
|
||||
if m.state == csMatch and not (m.calleeSym != nil and m.calleeSym.kind in {skTemplate, skMacro}):
|
||||
c.mergeShadowScope
|
||||
else:
|
||||
c.rememberShadowDefs
|
||||
c.closeShadowScope
|
||||
|
||||
inc a
|
||||
|
||||
@@ -394,10 +394,9 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
|
||||
accum.offset = 1
|
||||
computeObjectOffsetsFoldFunction(conf, typ.n, false, accum)
|
||||
let paddingAtEnd = int16(accum.finish())
|
||||
if (typ.sym != nil and
|
||||
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc} and
|
||||
tfCompleteStruct notin typ.flags) or
|
||||
tfIncompleteStruct in typ.flags:
|
||||
if typ.sym != nil and
|
||||
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc} and
|
||||
tfCompleteStruct notin typ.flags:
|
||||
typ.size = szUnknownSize
|
||||
typ.align = szUnknownSize
|
||||
typ.paddingAtEnd = szUnknownSize
|
||||
|
||||
@@ -97,7 +97,9 @@ iterator tokenize*(line: string): (int, string) =
|
||||
## normal JS code. This allows us to map mangled names back to Nim names.
|
||||
## Yields (column, name). Doesn't yield anything but identifiers.
|
||||
## See mangleName in compiler/jsgen.nim for how name mangling is done
|
||||
var col = 0
|
||||
var
|
||||
col = 0
|
||||
token = ""
|
||||
while col < line.len:
|
||||
var
|
||||
token: string = ""
|
||||
@@ -126,6 +128,7 @@ func parse*(source: string): SourceInfo =
|
||||
## So it can convert those into a series of mappings
|
||||
result = default(SourceInfo)
|
||||
var
|
||||
skipFirstLine = true
|
||||
currColumn = 0
|
||||
currLine = 0
|
||||
currFile = ""
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
## This module implements threadpool's ``spawn``.
|
||||
|
||||
import ast, types, idents, magicsys, msgs, options, modulegraphs,
|
||||
lowerings, liftdestructors, renderer, trees
|
||||
lowerings, liftdestructors, renderer
|
||||
from trees import getMagic, getRoot
|
||||
|
||||
proc callProc(a: PNode): PNode =
|
||||
result = newNodeI(nkCall, a.info)
|
||||
@@ -52,24 +53,6 @@ proc typeNeedsNoDeepCopy(t: PType): bool =
|
||||
if t.kind in {tyVar, tyLent, tySequence}: t = t.elementType
|
||||
result = not containsGarbageCollectedRef(t)
|
||||
|
||||
proc newSpawnMoveStmt(g: ModuleGraph; idgen: IdGenerator; le, ri: PNode): PNode =
|
||||
let op = getAttachedOp(g, ri.typ.skipTypes({tyGenericInst, tyAlias, tyVar, tySink}), attachedWasMoved)
|
||||
if op != nil and sfOverridden in op.flags:
|
||||
result = newNodeI(nkStmtList, le.info)
|
||||
result.add newFastAsgnStmt(le, ri)
|
||||
|
||||
let wasMovedCall = newNodeI(nkCall, ri.info)
|
||||
wasMovedCall.add newSymNode(op)
|
||||
|
||||
if op.typ != nil and op.typ.signatureLen > 1 and op.typ.firstParamType.kind != tyVar:
|
||||
wasMovedCall.add ri.skipAddr
|
||||
else:
|
||||
wasMovedCall.add makeAddr(ri.skipAddr, idgen)
|
||||
|
||||
result.add wasMovedCall
|
||||
else:
|
||||
result = newFastMoveStmt(g, le, ri)
|
||||
|
||||
proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator; owner: PSym; typ: PType;
|
||||
v: PNode; useShallowCopy=false): PSym =
|
||||
result = newSym(skTemp, getIdent(g.cache, genPrefix), idgen, owner, varSection.info,
|
||||
@@ -85,10 +68,10 @@ proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator;
|
||||
if varInit != nil:
|
||||
if g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}:
|
||||
# inject destructors pass will do its own analysis
|
||||
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
|
||||
varInit.add newFastMoveStmt(g, newSymNode(result), v)
|
||||
else:
|
||||
if useShallowCopy and typeNeedsNoDeepCopy(typ) or optTinyRtti in g.config.globalOptions:
|
||||
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
|
||||
varInit.add newFastMoveStmt(g, newSymNode(result), v)
|
||||
else:
|
||||
let deepCopyCall = newNodeI(nkCall, varInit.info, 3)
|
||||
deepCopyCall[0] = newSymNode(getSysMagic(g, varSection.info, "deepCopy", mDeepCopy))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user