mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-31 10:53:40 +00:00
Compare commits
9 Commits
pr_oi2
...
pr_nodecl_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a659dbff1b | ||
|
|
276840610b | ||
|
|
449511995f | ||
|
|
33263ef806 | ||
|
|
ce20f1b31a | ||
|
|
c1f99aa362 | ||
|
|
67d80c4971 | ||
|
|
237669a1b2 | ||
|
|
d39c76b8bd |
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -1,11 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
11
.github/workflows/bisects.yml
vendored
11
.github/workflows/bisects.yml
vendored
@@ -15,16 +15,9 @@ jobs:
|
||||
name: ${{ matrix.platform }}-bisects
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install OpenSSL (Windows)
|
||||
if: |
|
||||
runner.os == 'Windows'
|
||||
run: choco install openssl.light --version=1.1.1.0 # OpenSSL 3.x removed SSL_library_init
|
||||
shell: 'powershell'
|
||||
|
||||
# v2 wont work here, because uses "hardcoded" nim versions, action "dynamically" finds version with bug.
|
||||
- uses: jiro4989/setup-nim-action@v1
|
||||
- uses: jiro4989/setup-nim-action@v1
|
||||
with:
|
||||
nim-version: 'devel'
|
||||
|
||||
|
||||
6
.github/workflows/ci_docs.yml
vendored
6
.github/workflows/ci_docs.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
- target: windows
|
||||
os: windows-latest
|
||||
- target: osx
|
||||
os: macos-15
|
||||
os: macos-13
|
||||
|
||||
name: ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
if: |
|
||||
github.event_name == 'push' && github.ref == 'refs/heads/devel' &&
|
||||
matrix.target == 'linux'
|
||||
uses: crazy-max/ghaction-github-pages@v5
|
||||
uses: crazy-max/ghaction-github-pages@v4
|
||||
with:
|
||||
build_dir: doc/html
|
||||
env:
|
||||
|
||||
14
.github/workflows/ci_packages.yml
vendored
14
.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,14 +33,14 @@ jobs:
|
||||
NIM_TESTAMENT_BATCH: ${{ matrix.batch }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js'
|
||||
uses: actions/setup-node@v7
|
||||
- name: 'Install node.js 20.x'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: '20.x'
|
||||
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
|
||||
8
.github/workflows/ci_publish.yml
vendored
8
.github/workflows/ci_publish.yml
vendored
@@ -17,14 +17,14 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js'
|
||||
uses: actions/setup-node@v7
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: ''
|
||||
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
run: nim c -r -d:release ci/action.nim
|
||||
|
||||
- name: 'Comment'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
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@v10
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-pr-stale: 365
|
||||
days-before-pr-close: 30
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -68,7 +68,6 @@ testament.db
|
||||
/csources
|
||||
/csources_v1
|
||||
/csources_v2
|
||||
/csources_v3
|
||||
|
||||
/dist/
|
||||
# /lib/fusion # fusion is now unbundled; `git status` should reveal if it's there so users can act on it
|
||||
@@ -87,7 +86,6 @@ tweeter_test.db
|
||||
|
||||
/tests/megatest.nim
|
||||
/tests/ic/*_temp.nim
|
||||
/tests/ic/*_mm/
|
||||
/tests/navigator/*_temp.nim
|
||||
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ jobs:
|
||||
# # g++-multilib : Depends: gcc-multilib (>= 4:5.3.1-1ubuntu1) but it is not going to be installed
|
||||
# vmImage: 'ubuntu-18.04'
|
||||
# CPU: i386
|
||||
OSX_arm64:
|
||||
vmImage: 'macos-15'
|
||||
CPU: arm64
|
||||
OSX_arm64_cpp:
|
||||
vmImage: 'macos-15'
|
||||
CPU: arm64
|
||||
OSX_amd64:
|
||||
vmImage: 'macOS-13'
|
||||
CPU: amd64
|
||||
OSX_amd64_cpp:
|
||||
vmImage: 'macOS-13'
|
||||
CPU: amd64
|
||||
NIM_COMPILE_TO_CPP: true
|
||||
Windows_amd64_batch0_3:
|
||||
vmImage: 'windows-2025'
|
||||
|
||||
51
changelog.md
51
changelog.md
@@ -19,6 +19,8 @@ errors.
|
||||
|
||||
- With `-d:nimPreviewAsmSemSymbol`, backticked symbols are type checked in the `asm/emit` statements.
|
||||
|
||||
- `importc` no longer implies `nodecl` for imported types. Use `header` or `nodecl` pragmas so that it doesn't generate a declaration for the type symbol.
|
||||
|
||||
- The bare `except:` now panics on `Defect`. Use `except Exception:` or `except Defect:` to catch `Defect`. `--legacy:noPanicOnExcept` is provided for a transition period.
|
||||
|
||||
- With `-d:nimPreviewCStringComparisons`, comparsions (`<`, `>`, `<=`, `>=`) between cstrings switch from reference semantics to value semantics like `==` and `!=`.
|
||||
@@ -27,31 +29,11 @@ errors.
|
||||
|
||||
- With `-d:nimPreviewDuplicateModuleError`, importing two modules that share the same name becomes a compile-time error. This includes importing the same module more than once. Use `import foo as foo1` (or other aliases) to avoid collisions.
|
||||
|
||||
- Adds the switch `--mangle:nim|cpp`, which selects `nim` or `cpp` style name mangling when used with `debuginfo` on, defaults to `cpp`.
|
||||
|
||||
- The second parameter of `succ`, `pred`, `inc`, and `dec` in `system` now accepts `SomeInteger` (previously `Ordinal`).
|
||||
|
||||
- Bitshift operators (`shl`, `shr`, `ashr`) now apply bitmasking to the right operand in the C/C++/VM/JS backends.
|
||||
|
||||
- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`.
|
||||
|
||||
- Procedure compatibility also checks the backend representation of the
|
||||
parameter and result types, not just their source-level shape. Use
|
||||
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
|
||||
|
||||
## Standard library additions and changes
|
||||
|
||||
[//]: # "Additions:"
|
||||
|
||||
- Added `system.readRawDataStable`, a companion to `readRawData` that returns a
|
||||
raw `ptr UncheckedArray[char]` into a string's character data which stays valid
|
||||
across moves and copies of the string value. It is available under every string
|
||||
implementation (refc, ARC/ORC and `--strings:sso`) with the same signature, so
|
||||
code can pin an interior buffer pointer today and be ready for `--strings:sso`
|
||||
without `when declared` guards. Under `--strings:sso` it promotes a small inline
|
||||
string to its heap representation first; under the other implementations the data
|
||||
is already heap-resident, so it is equivalent to `readRawData`.
|
||||
|
||||
- `setutils.symmetricDifference` along with its operator version
|
||||
`` setutils.`-+-` `` and in-place version `setutils.toggle` have been added
|
||||
to more efficiently calculate the symmetric difference of bitsets.
|
||||
@@ -73,17 +55,6 @@ parameter and result types, not just their source-level shape. Use
|
||||
- `copyDirWithPermissions` to recursively preserve attributes
|
||||
|
||||
- `system.setLenUninit` now supports refc, JS and VM backends.
|
||||
- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth.
|
||||
|
||||
- `std/parseopt` now supports multiple parser modes via a `CliMode` enum.
|
||||
Modes include `Nim` (default, fully compatible) and two new experimental modes:
|
||||
`Lax` and `Gnu` for different option parsing behaviors.
|
||||
|
||||
- `std/symlinks.expandSymlink` now supports Windows symlinks and junctions with
|
||||
POSIX-like single-hop `readlink` semantics.
|
||||
- `std/nre2` is added to replace deprecated NRE.
|
||||
|
||||
- `system.typeof` adds a new parameter `modifierMode` to specify how type modifiers are handled.
|
||||
|
||||
[//]: # "Changes:"
|
||||
|
||||
@@ -91,14 +62,6 @@ parameter and result types, not just their source-level shape. Use
|
||||
- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function.
|
||||
- `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation.
|
||||
- `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`.
|
||||
- `std/re` and `std/nre` are deprecated as PCRE library is obsolete.
|
||||
Use https://github.com/nitely/nim-regex or `std/nre2`.
|
||||
See: https://github.com/nim-lang/Nim/issues/23668.
|
||||
- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style
|
||||
terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``)
|
||||
works without single-quoting.
|
||||
- `std/uri`: The `?` operator now appends query parameters to an existing query
|
||||
string instead of replacing it. Fixes [#19782](https://github.com/nim-lang/Nim/issues/19782).
|
||||
|
||||
## Language changes
|
||||
|
||||
@@ -139,17 +102,7 @@ parameter and result types, not just their source-level shape. Use
|
||||
|
||||
## Compiler changes
|
||||
|
||||
- Fixed a bug where `sizeof(T)` inside a `typedesc` template called from a generic type's
|
||||
`when` clause would error with "'sizeof' requires '.importc' types to be '.completeStruct'".
|
||||
The issue was that `hasValuelessStatics` in `semtypinst.nim` didn't recognize
|
||||
`tyTypeDesc(tyGenericParam)` as an unresolved generic parameter.
|
||||
|
||||
## Tool changes
|
||||
|
||||
- Added `--raw` flag when generating JSON docs to not render markup.
|
||||
- Added `--stdinfile` flag to name of the file used when running program from stdin (defaults to `stdinfile.nim`)
|
||||
- Added `--styleCheck:warning` flag to treat style check violations as warnings.
|
||||
|
||||
## Documentation changes
|
||||
|
||||
- Added documentation for the `completeStruct` pragma in the manual.
|
||||
|
||||
1900
compiler/ast.nim
1900
compiler/ast.nim
File diff suppressed because it is too large
Load Diff
3542
compiler/ast2nif.nim
3542
compiler/ast2nif.nim
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,8 @@ template mdbg*: bool {.deprecated.} =
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc lookupInRecord*(n: PNode, field: PIdent): PSym
|
||||
proc mustRehash*(length, counter: int): bool
|
||||
proc nextTry*(h, maxHash: Hash): Hash {.inline.}
|
||||
|
||||
# ------------- table[int, int] ---------------------------------------------
|
||||
const
|
||||
@@ -214,6 +216,10 @@ proc getNamedParamFromList*(list: PNode, ident: PIdent): PSym =
|
||||
proc hashNode(p: RootRef): Hash =
|
||||
result = hash(cast[pointer](p))
|
||||
|
||||
proc mustRehash(length, counter: int): bool =
|
||||
assert(length > counter)
|
||||
result = (length * 2 < counter * 3) or (length - counter < 4)
|
||||
|
||||
import std/tables
|
||||
|
||||
const backrefStyle = "\e[90m"
|
||||
@@ -478,6 +484,12 @@ proc debug(n: PNode; conf: ConfigRef) =
|
||||
this.value(n)
|
||||
echo($this.res)
|
||||
|
||||
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
|
||||
result = ((5 * h) + 1) and maxHash
|
||||
# For any initial h in range(maxHash), repeating that maxHash times
|
||||
# generates each int in range(maxHash) exactly once (see any text on
|
||||
# random-number generation for proof).
|
||||
|
||||
proc objectSetContains*(t: TObjectSet, obj: RootRef): bool =
|
||||
# returns true whether n is in t
|
||||
var h: Hash = hashNode(obj) and high(t.data) # start with real hash value
|
||||
@@ -525,6 +537,95 @@ proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool =
|
||||
inc(t.counter)
|
||||
result = false
|
||||
|
||||
proc strTableContains*(t: TStrTable, n: PSym): bool =
|
||||
var h: Hash = n.name.h and high(t.data) # start with real hash value
|
||||
while t.data[h] != nil:
|
||||
if (t.data[h] == n):
|
||||
return true
|
||||
h = nextTry(h, high(t.data))
|
||||
result = false
|
||||
|
||||
proc strTableRawInsert(data: var seq[PSym], n: PSym) =
|
||||
var h: Hash = n.name.h and high(data)
|
||||
while data[h] != nil:
|
||||
if data[h] == n:
|
||||
# allowed for 'export' feature:
|
||||
#InternalError(n.info, "StrTableRawInsert: " & n.name.s)
|
||||
return
|
||||
h = nextTry(h, high(data))
|
||||
assert(data[h] == nil)
|
||||
data[h] = n
|
||||
|
||||
proc symTabReplaceRaw(data: var seq[PSym], prevSym: PSym, newSym: PSym) =
|
||||
assert prevSym.name.h == newSym.name.h
|
||||
var h: Hash = prevSym.name.h and high(data)
|
||||
while data[h] != nil:
|
||||
if data[h] == prevSym:
|
||||
data[h] = newSym
|
||||
return
|
||||
h = nextTry(h, high(data))
|
||||
assert false
|
||||
|
||||
proc symTabReplace*(t: var TStrTable, prevSym: PSym, newSym: PSym) =
|
||||
symTabReplaceRaw(t.data, prevSym, newSym)
|
||||
|
||||
proc strTableEnlarge(t: var TStrTable) =
|
||||
var n: seq[PSym]
|
||||
newSeq(n, t.data.len * GrowthFactor)
|
||||
for i in 0..high(t.data):
|
||||
if t.data[i] != nil: strTableRawInsert(n, t.data[i])
|
||||
swap(t.data, n)
|
||||
|
||||
proc strTableAdd*(t: var TStrTable, n: PSym) =
|
||||
if mustRehash(t.data.len, t.counter): strTableEnlarge(t)
|
||||
strTableRawInsert(t.data, n)
|
||||
inc(t.counter)
|
||||
|
||||
proc strTableInclReportConflict*(t: var TStrTable, n: PSym;
|
||||
onConflictKeepOld = false): PSym =
|
||||
# if `t` has a conflicting symbol (same identifier as `n`), return it
|
||||
# otherwise return `nil`. Incl `n` to `t` unless `onConflictKeepOld = true`
|
||||
# and a conflict was found.
|
||||
assert n.name != nil
|
||||
var h: Hash = n.name.h and high(t.data)
|
||||
var replaceSlot = -1
|
||||
while true:
|
||||
var it = t.data[h]
|
||||
if it == nil: break
|
||||
# Semantic checking can happen multiple times thanks to templates
|
||||
# and overloading: (var x=@[]; x).mapIt(it).
|
||||
# So it is possible the very same sym is added multiple
|
||||
# times to the symbol table which we allow here with the 'it == n' check.
|
||||
if it.name.id == n.name.id:
|
||||
if it == n: return nil
|
||||
replaceSlot = h
|
||||
h = nextTry(h, high(t.data))
|
||||
if replaceSlot >= 0:
|
||||
result = t.data[replaceSlot] # found it
|
||||
if not onConflictKeepOld:
|
||||
t.data[replaceSlot] = n # overwrite it with newer definition!
|
||||
return result # but return the old one
|
||||
elif mustRehash(t.data.len, t.counter):
|
||||
strTableEnlarge(t)
|
||||
strTableRawInsert(t.data, n)
|
||||
else:
|
||||
assert(t.data[h] == nil)
|
||||
t.data[h] = n
|
||||
inc(t.counter)
|
||||
result = nil
|
||||
|
||||
proc strTableIncl*(t: var TStrTable, n: PSym;
|
||||
onConflictKeepOld = false): bool {.discardable.} =
|
||||
result = strTableInclReportConflict(t, n, onConflictKeepOld) != nil
|
||||
|
||||
proc strTableGet*(t: TStrTable, name: PIdent): PSym =
|
||||
var h: Hash = name.h and high(t.data)
|
||||
while true:
|
||||
result = t.data[h]
|
||||
if result == nil: break
|
||||
if result.name.id == name.id: break
|
||||
h = nextTry(h, high(t.data))
|
||||
|
||||
|
||||
type
|
||||
TIdentIter* = object # iterator over all syms with same identifier
|
||||
|
||||
1203
compiler/astdef.nim
1203
compiler/astdef.nim
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -69,7 +69,7 @@ proc copyHalf[Key, Val](h, result: Node[Key, Val]) =
|
||||
result.links[j] = h.links[Mhalf + j]
|
||||
else:
|
||||
for j in 0..<Mhalf:
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result.vals[j] = move h.vals[Mhalf + j]
|
||||
else:
|
||||
shallowCopy(result.vals[j], h.vals[Mhalf + j])
|
||||
@@ -92,7 +92,7 @@ proc insert[Key, Val](h: Node[Key, Val], key: Key, val: Val): Node[Key, Val] =
|
||||
if less(key, h.keys[j]): break
|
||||
inc j
|
||||
for i in countdown(h.entries, j+1):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
h.vals[i] = move h.vals[i-1]
|
||||
else:
|
||||
shallowCopy(h.vals[i], h.vals[i-1])
|
||||
|
||||
@@ -154,14 +154,14 @@ template addField(builder: var Builder, constr: var StructInitializer, name: str
|
||||
# no name, can just add value
|
||||
valueBody
|
||||
of siOrderedStruct:
|
||||
# positional init - name not used in output (empty allowed for anonymous unions)
|
||||
# no name, can just add value on C
|
||||
assert name.len != 0, "name has to be given for struct initializer field"
|
||||
valueBody
|
||||
of siNamedStruct:
|
||||
# designated init - empty name for anonymous unions (skips .name = prefix)
|
||||
if name.len != 0:
|
||||
builder.add(".")
|
||||
builder.add(name)
|
||||
builder.add(" = ")
|
||||
assert name.len != 0, "name has to be given for struct initializer field"
|
||||
builder.add(".")
|
||||
builder.add(name)
|
||||
builder.add(" = ")
|
||||
valueBody
|
||||
|
||||
proc finishStructInitializer(builder: var Builder, constr: StructInitializer) =
|
||||
|
||||
@@ -230,29 +230,20 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareF
|
||||
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))
|
||||
@@ -296,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)
|
||||
@@ -335,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)
|
||||
@@ -356,7 +331,7 @@ proc withTmpIfNeeded(p: BProc, a: TLoc, needsTmp: bool): TLoc =
|
||||
# Bug https://github.com/status-im/nimbus-eth2/issues/1549
|
||||
# Aliasing is preferred over stack overflows.
|
||||
# Also don't regress for non ARC-builds, too risky.
|
||||
if needsTmp and a.lode.typ != nil and p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and
|
||||
if needsTmp and a.lode.typ != nil and p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} and
|
||||
getSize(p.config, a.lode.typ) < 1024:
|
||||
result = getTemp(p, a.lode.typ, needsInit=false)
|
||||
genAssignment(p, result, a, {})
|
||||
@@ -369,8 +344,7 @@ proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc =
|
||||
|
||||
proc genArgStringToCString(p: BProc, n: PNode; result: var Builder; needsTmp: bool) {.inline.} =
|
||||
var a = initLocExpr(p, n[0])
|
||||
let tmp = withTmpIfNeeded(p, a, needsTmp)
|
||||
let ra = if p.config.usesSso(): byRefLoc(p, tmp) else: tmp.rdLoc
|
||||
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) =
|
||||
@@ -394,8 +368,8 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n
|
||||
# variable. Thus, we create a temporary pointer variable instead.
|
||||
let needsIndirect = mapType(p.config, n[0].typ, mapTypeChooser(n[0]) == skParam) != ctArray
|
||||
if needsIndirect:
|
||||
n.typ = n.typ.exactReplica(p.module.idgen)
|
||||
n.typ.incl tfVarIsPtr
|
||||
n.typ() = n.typ.exactReplica
|
||||
n.typ.flags.incl tfVarIsPtr
|
||||
a = initLocExprSingleUse(p, n)
|
||||
a = withTmpIfNeeded(p, a, needsTmp)
|
||||
if needsIndirect: a.flags.incl lfIndirect
|
||||
@@ -524,7 +498,7 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
else:
|
||||
cCall(p, params, e)
|
||||
cIfExpr(e,
|
||||
eCall,
|
||||
eCall,
|
||||
cCall(cCast(pTyp, p), params))
|
||||
|
||||
template callIter(rp, params: Snippet): Snippet =
|
||||
@@ -909,16 +883,6 @@ proc isInactiveDestructorCall(p: BProc, e: PNode): bool =
|
||||
proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
|
||||
if p.withinBlockLeaveActions > 0 and isInactiveDestructorCall(p, ri):
|
||||
return
|
||||
when defined(icDbgHash):
|
||||
if ri[0].typ == nil:
|
||||
echo "NILCALLEE kind=", ri[0].kind,
|
||||
" sym=", (if ri[0].kind == nkSym: ri[0].sym.name.s else: "-"),
|
||||
" symKind=", (if ri[0].kind == nkSym: $ri[0].sym.kind else: "-"),
|
||||
" flags=", (if ri[0].kind == nkSym: $ri[0].sym.flags else: "-"),
|
||||
" lazy=", nfLazyType in ri[0].flags,
|
||||
" inProc=", (if p.prc != nil: p.prc.name.s else: "NIL"),
|
||||
" module=", p.module.module.name.s
|
||||
raiseAssert "nil callee type, see NILCALLEE above"
|
||||
if ri[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
|
||||
genClosureCall(p, le, ri, d)
|
||||
elif ri[0].kind == nkSym and sfInfixCall in ri[0].sym.flags:
|
||||
|
||||
@@ -170,7 +170,7 @@ proc canMove(p: BProc, n: PNode; dest: TLoc): bool =
|
||||
template simpleAsgn(builder: var Builder, dest, src: TLoc) =
|
||||
let rd = rdLoc(dest)
|
||||
let rs = rdLoc(src)
|
||||
builder.addAssignment(rd, rs)
|
||||
builder.addAssignment(rd, rs)
|
||||
|
||||
proc genRefAssign(p: BProc, dest, src: TLoc) =
|
||||
if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
|
||||
@@ -216,8 +216,6 @@ proc genOptAsgnTuple(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
|
||||
flags
|
||||
let t = skipTypes(dest.t, abstractInst).getUniqueType()
|
||||
for i, t in t.ikids:
|
||||
# Do not produce code for void types
|
||||
if isEmptyType(t): continue
|
||||
let field = "Field$1" % [i.rope]
|
||||
genAssignment(p, optAsgnLoc(dest, t, field),
|
||||
optAsgnLoc(src, t, field), newflags)
|
||||
@@ -320,16 +318,12 @@ proc genOpenArrayConv(p: BProc; d: TLoc; a: TLoc; flags: TAssignmentFlags) =
|
||||
p.s(cpsStmts).addCallStmt(
|
||||
cgsymValue(p.module, "nimPrepareStrMutationV2"),
|
||||
bra)
|
||||
|
||||
let rd = d.rdLoc
|
||||
let ra = a.rdLoc
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
|
||||
cIfExpr(dataFieldAccessor(p, ra), dataField(p, ra), NimNil))
|
||||
let la = lenExpr(p, a)
|
||||
if p.config.usesSso():
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
|
||||
cCall(cgsymValue(p.module, "nimStrData"), bra))
|
||||
else:
|
||||
let ra = a.rdLoc
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
|
||||
cIfExpr(dataFieldAccessor(p, ra), dataField(p, ra), NimNil))
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "Field1", la)
|
||||
else:
|
||||
internalError(p.config, a.lode.info, "cannot handle " & $a.t.kind)
|
||||
@@ -422,7 +416,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
|
||||
else:
|
||||
simpleAsgn(p.s(cpsStmts), dest, src)
|
||||
of tyArray:
|
||||
if containsGarbageCollectedRef(dest.t) and p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcYrc, gcHooks}:
|
||||
if containsGarbageCollectedRef(dest.t) and p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcHooks}:
|
||||
genGenericAsgn(p, dest, src, flags)
|
||||
else:
|
||||
let rd = rdLoc(dest)
|
||||
@@ -681,7 +675,7 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
|
||||
if e[2].kind in {nkIntLit..nkInt64Lit}:
|
||||
needsOverflowCheck = e[2].intVal == -1
|
||||
if canBeZero:
|
||||
# remove extra paren from `==` op here to avoid Wparentheses-equality:
|
||||
# remove extra paren from `==` op here to avoid Wparentheses-equality:
|
||||
p.s(cpsStmts).addSingleIfStmt(removeSinglePar(cOp(Equal, rdLoc(b), cIntValue(0)))):
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseDivByZero"))
|
||||
raiseInstr(p, p.s(cpsStmts))
|
||||
@@ -702,7 +696,7 @@ proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
|
||||
let ra = rdLoc(a)
|
||||
if optOverflowCheck in p.options:
|
||||
let first = cIntLiteral(firstOrd(p.config, t))
|
||||
# remove extra paren from `==` op here to avoid Wparentheses-equality:
|
||||
# remove extra paren from `==` op here to avoid Wparentheses-equality:
|
||||
p.s(cpsStmts).addSingleIfStmt(removeSinglePar(cOp(Equal, ra, first))):
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseOverflow"))
|
||||
raiseInstr(p, p.s(cpsStmts))
|
||||
@@ -755,16 +749,16 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
|
||||
let t = getType()
|
||||
let at = cUintType(k)
|
||||
let bt = cUintType(s)
|
||||
res = cCast(t, cOp(Shr, at, cCast(at, ra), cOp(BitAnd, at, cCast(bt, rb), cIntLiteral(k - 1))))
|
||||
res = cCast(t, cOp(Shr, at, cCast(at, ra), cCast(bt, rb)))
|
||||
of mShlI:
|
||||
let t = getType()
|
||||
let at = cUintType(s)
|
||||
res = cCast(t, cOp(Shl, at, cCast(at, ra), cOp(BitAnd, at, cCast(at, rb), cIntLiteral(k - 1))))
|
||||
res = cCast(t, cOp(Shl, at, cCast(at, ra), cCast(at, rb)))
|
||||
of mAshrI:
|
||||
let t = getType()
|
||||
let at = cIntType(s)
|
||||
let bt = cUintType(s)
|
||||
res = cCast(t, cOp(Shr, at, cCast(at, ra), cOp(BitAnd, at, cCast(bt, rb), cIntLiteral(k - 1))))
|
||||
res = cCast(t, cOp(Shr, at, cCast(at, ra), cCast(bt, rb)))
|
||||
of mBitandI:
|
||||
let t = getType()
|
||||
res = cCast(t, cOp(BitAnd, t, ra, rb))
|
||||
@@ -925,10 +919,6 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc) =
|
||||
return
|
||||
else:
|
||||
a = initLocExprSingleUse(p, e[0])
|
||||
|
||||
# bug #23453 #25265
|
||||
if e.typ != nil and e.typ.skipTypes(abstractInst).kind == tyObject:
|
||||
discard getTypeDesc(p.module, e.typ)
|
||||
if d.k == locNone:
|
||||
# dest = *a; <-- We do not know that 'dest' is on the heap!
|
||||
# It is completely wrong to set 'd.storage' here, unless it's not yet
|
||||
@@ -962,8 +952,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc) =
|
||||
putIntoDest(p, d, e, cDeref(rdLoc(a)), a.storage)
|
||||
|
||||
proc cowBracket(p: BProc; n: PNode) =
|
||||
if n.kind == nkBracketExpr and optSeqDestructors in p.config.globalOptions and
|
||||
not p.config.usesSso():
|
||||
if n.kind == nkBracketExpr and optSeqDestructors in p.config.globalOptions:
|
||||
let strCandidate = n[0]
|
||||
if strCandidate.typ.skipTypes(abstractInst).kind == tyString:
|
||||
var a: TLoc = initLocExpr(p, strCandidate)
|
||||
@@ -989,9 +978,7 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) =
|
||||
# bug #19497
|
||||
d.lode = e
|
||||
else:
|
||||
let ssoStrSub = p.config.usesSso() and e[0].kind == nkBracketExpr and
|
||||
e[0][0].typ.skipTypes(abstractVar).kind == tyString
|
||||
var a: TLoc = initLocExpr(p, e[0], if ssoStrSub: {lfEnforceDeref, lfPrepareForMutation} else: {})
|
||||
var a: TLoc = initLocExpr(p, e[0])
|
||||
if e[0].kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv} and not ignoreConv(e[0]):
|
||||
# addr (conv x) introduces a temp because `conv x` is not a rvalue
|
||||
# transform addr ( conv ( x ) ) -> conv ( addr ( x ) )
|
||||
@@ -1015,7 +1002,7 @@ proc genTupleElem(p: BProc, e: PNode, d: var TLoc) =
|
||||
var
|
||||
i: int = 0
|
||||
var a: TLoc = initLocExpr(p, e[0])
|
||||
let tupType = a.t.skipTypes(abstractInst+{tyVar}+tyUserTypeClasses) # ref #25227
|
||||
let tupType = a.t.skipTypes(abstractInst+{tyVar})
|
||||
assert tupType.kind == tyTuple
|
||||
d.inheritLocation(a)
|
||||
discard getTypeDesc(p.module, a.t) # fill the record's fields.loc
|
||||
@@ -1071,7 +1058,7 @@ proc genRecordField(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc)
|
||||
|
||||
proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym, ty: PType) =
|
||||
proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym) =
|
||||
var test, u, v: TLoc
|
||||
for i in 1..<e.len:
|
||||
var it = e[i]
|
||||
@@ -1081,17 +1068,10 @@ proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym, ty: PType) =
|
||||
if op.magic == mNot: it = it[1]
|
||||
let disc = it[2].skipConv
|
||||
assert(disc.kind == nkSym)
|
||||
# Re-navigate the discriminant in the object type: under `nim ic` `disc.sym` is
|
||||
# a field-use stub whose `loc.snippet` is empty (the backend fills it on the
|
||||
# canonical reclist field, not on per-use leaves). Look up the canonical field
|
||||
# for the C member name; `disc`'s own node still supplies its type (TLoc.t).
|
||||
# Byte-neutral for non-IC, where re-navigation returns the same field.
|
||||
var rr = obj
|
||||
let dfield = lookupFieldAgain(p, ty, disc.sym, rr)
|
||||
test = initLoc(locNone, it, OnStack)
|
||||
u = initLocExpr(p, it[1])
|
||||
v = initLoc(locExpr, disc, OnUnknown)
|
||||
v.snippet = dotField(obj, dfield.loc.snippet)
|
||||
v.snippet = dotField(obj, disc.sym.loc.snippet)
|
||||
genInExprAux(p, it, u, v, test)
|
||||
var msg = ""
|
||||
if optDeclaredLocs in p.config.globalOptions:
|
||||
@@ -1101,7 +1081,7 @@ proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym, ty: PType) =
|
||||
# by encoding the file names separately from `file(line:col)`, essentially
|
||||
# passing around `TLineInfo` + the set of files in the project.
|
||||
msg.add toFileLineCol(p.config, e.info) & " "
|
||||
msg.add genFieldDefect(p.config, field.name.s, dfield)
|
||||
msg.add genFieldDefect(p.config, field.name.s, disc.sym)
|
||||
var strLitBuilder = newBuilder("")
|
||||
genStringLiteral(p.module, newStrNode(nkStrLit, msg), strLitBuilder)
|
||||
let strLit = extract(strLitBuilder)
|
||||
@@ -1165,7 +1145,7 @@ proc genCheckedRecordField(p: BProc, e: PNode, d: var TLoc) =
|
||||
if field.loc.snippet == "": fillObjectFields(p.module, ty)
|
||||
if field.loc.snippet == "":
|
||||
internalError(p.config, e.info, "genCheckedRecordField") # generate the checks:
|
||||
genFieldCheck(p, e, r, field, ty)
|
||||
genFieldCheck(p, e, r, field)
|
||||
r = dotField(r, field.loc.snippet)
|
||||
putIntoDest(p, d, e[0], r, a.storage)
|
||||
r.freeze
|
||||
@@ -1325,24 +1305,13 @@ proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) =
|
||||
if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}:
|
||||
a.snippet = cDeref(a.snippet)
|
||||
|
||||
if p.config.usesSso() and ty.kind == tyString:
|
||||
if lfPrepareForMutation in d.flags and ty.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions:
|
||||
let bra = byRefLoc(p, a)
|
||||
if lfPrepareForMutation in d.flags:
|
||||
# Use nimStrAtMutV3 to get a mutable reference (char*) to the element.
|
||||
# Only when mutation is requested: avoids calling nimPrepareStrMutationV2
|
||||
# on const string literals (which would SIGSEGV on write to read-only memory).
|
||||
putIntoDest(p, d, n,
|
||||
cDeref(cCall(cgsymValue(p.module, "nimStrAtMutV3"), bra, rcb)), a.storage)
|
||||
else:
|
||||
putIntoDest(p, d, n,
|
||||
cCall(cgsymValue(p.module, "nimStrAtV3"), bra, rcb), a.storage)
|
||||
else:
|
||||
if lfPrepareForMutation in d.flags and ty.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions:
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"), bra)
|
||||
let ra = rdLoc(a)
|
||||
putIntoDest(p, d, n, subscript(dataField(p, ra), rcb), a.storage)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
|
||||
bra)
|
||||
let ra = rdLoc(a)
|
||||
putIntoDest(p, d, n, subscript(dataField(p, ra), rcb), a.storage)
|
||||
|
||||
proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) =
|
||||
var ty = skipTypes(n[0].typ, abstractVarRange + tyUserTypeClasses)
|
||||
@@ -1614,51 +1583,6 @@ proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) =
|
||||
genAssignment(p, dest, b, {needToCopy})
|
||||
gcUsage(p.config, e)
|
||||
|
||||
proc genSeqElemAppendV2(p: BProc, e: PNode, d: var TLoc) =
|
||||
# s.add(x) with optSeqDestructors (arc/orc), inlined for direct slot construction:
|
||||
# NI oldLen = s.len;
|
||||
# if (s.p == NIM_NIL || (s.p->cap & ~NIM_STRLIT_FLAG) < oldLen + 1)
|
||||
# s.p = (PayloadType*)prepareSeqAddUninit(oldLen, s.p, 1, sizeof(T), alignof(T));
|
||||
# s.len = oldLen + 1;
|
||||
# s.p->data[oldLen] = x; // direct assignment, no function call overhead
|
||||
let seqtype = skipTypes(e[1].typ, abstractVarRange)
|
||||
var a = initLocExpr(p, e[1])
|
||||
let pt = getSeqPayloadType(p.module, seqtype)
|
||||
let pe = seqPayloadElem(p.module, seqtype)
|
||||
# Capture a stable pointer to the seq BEFORE evaluating the element (e[2]).
|
||||
# Evaluating e[2] may emit move semantics (eqwasMoved) that nil a variable
|
||||
# through which e[1]'s snippet is accessed (e.g. a closure env pointer).
|
||||
inc(p.labels)
|
||||
let seqPtrName = "T" & rope(p.labels) & "_"
|
||||
p.s(cpsLocals).addVar(kind = Local, name = seqPtrName,
|
||||
typ = ptrType(getTypeDesc(p.module, seqtype)))
|
||||
p.s(cpsStmts).addAssignment(seqPtrName, cAddr(rdLoc(a)))
|
||||
var b = initLocExpr(p, e[2])
|
||||
# All seq operations now go through the stable seqPtrName pointer.
|
||||
let ra = wrapPar(cDeref(seqPtrName))
|
||||
var tmpL = getIntTemp(p)
|
||||
p.s(cpsStmts).addAssignment(tmpL.snippet, dotField(ra, "len"))
|
||||
let pField = dotField(ra, "p")
|
||||
p.s(cpsStmts).addSingleIfStmt(
|
||||
cOp(Or,
|
||||
cOp(Equal, pField, NimNil),
|
||||
cOp(LessThan,
|
||||
cOp(BitAnd, NimInt, derefField(pField, "cap"), cOp(BitNot, NimInt, NimStrlitFlag)),
|
||||
cOp(Add, NimInt, tmpL.snippet, cIntValue(1))))):
|
||||
p.s(cpsStmts).addFieldAssignmentWithValue(ra, "p"):
|
||||
p.s(cpsStmts).addCast(ptrType(pt)):
|
||||
p.s(cpsStmts).addCall(cgsymValue(p.module, "prepareSeqAddUninit"),
|
||||
tmpL.snippet,
|
||||
pField,
|
||||
cIntValue(1),
|
||||
cSizeof(pe),
|
||||
cAlignof(pe))
|
||||
p.s(cpsStmts).addFieldAssignment(ra, "len",
|
||||
cOp(Add, NimInt, tmpL.snippet, cIntValue(1)))
|
||||
var dest = initLoc(locExpr, e[2], OnHeap)
|
||||
dest.snippet = subscript(dataField(p, ra), tmpL.snippet)
|
||||
genAssignment(p, dest, b, {})
|
||||
|
||||
proc genDefault(p: BProc; n: PNode; d: var TLoc) =
|
||||
if d.k == locNone: d = getTemp(p, n.typ, needsInit=true)
|
||||
else: resetLoc(p, d)
|
||||
@@ -1794,15 +1718,15 @@ proc genNewSeq(p: BProc, e: PNode) =
|
||||
let seqtype = skipTypes(e[1].typ, abstractVarRange)
|
||||
let ra = a.rdLoc
|
||||
let rb = b.rdLoc
|
||||
let et = getTypeDesc(p.module, seqtype.elementType)
|
||||
let pt = getSeqPayloadType(p.module, seqtype)
|
||||
let pe = seqPayloadElem(p.module, seqtype)
|
||||
p.s(cpsStmts).addFieldAssignment(ra, "len", rb)
|
||||
p.s(cpsStmts).addFieldAssignmentWithValue(ra, "p"):
|
||||
p.s(cpsStmts).addCast(ptrType(pt)):
|
||||
p.s(cpsStmts).addCall(cgsymValue(p.module, "newSeqPayload"),
|
||||
rb,
|
||||
cSizeof(pe),
|
||||
cAlignof(pe))
|
||||
cSizeof(et),
|
||||
cAlignof(et))
|
||||
else:
|
||||
let lenIsZero = e[2].kind == nkIntLit and e[2].intVal == 0
|
||||
genNewSeqAux(p, a, b.rdLoc, lenIsZero)
|
||||
@@ -1815,15 +1739,15 @@ proc genNewSeqOfCap(p: BProc; e: PNode; d: var TLoc) =
|
||||
if d.k == locNone: d = getTemp(p, e.typ, needsInit=false)
|
||||
let rd = d.rdLoc
|
||||
let ra = a.rdLoc
|
||||
let et = getTypeDesc(p.module, seqtype.elementType)
|
||||
let pt = getSeqPayloadType(p.module, seqtype)
|
||||
let pe = seqPayloadElem(p.module, seqtype)
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "len", cIntValue(0))
|
||||
p.s(cpsStmts).addFieldAssignmentWithValue(rd, "p"):
|
||||
p.s(cpsStmts).addCast(ptrType(pt)):
|
||||
p.s(cpsStmts).addCall(cgsymValue(p.module, "newSeqPayloadUninit"),
|
||||
ra,
|
||||
cSizeof(pe),
|
||||
cAlignof(pe))
|
||||
cSizeof(et),
|
||||
cAlignof(et))
|
||||
else:
|
||||
if d.k == locNone: d = getTemp(p, e.typ, needsInit=false) # bug #22560
|
||||
let ra = a.rdLoc
|
||||
@@ -1865,7 +1789,7 @@ proc genFieldObjConstr(p: BProc; ty: PType; useTemp, isRef: bool; nField, val, c
|
||||
if field.loc.snippet == "": fillObjectFields(p.module, ty)
|
||||
if field.loc.snippet == "": internalError(p.config, info, "genFieldObjConstr")
|
||||
if check != nil and optFieldCheck in p.options:
|
||||
genFieldCheck(p, check, r, field, ty)
|
||||
genFieldCheck(p, check, r, field)
|
||||
tmp2.snippet = dotField(tmp2.snippet, field.loc.snippet)
|
||||
if useTemp:
|
||||
tmp2.k = locTemp
|
||||
@@ -1896,24 +1820,15 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
|
||||
var t = e.typ.skipTypes(abstractInstOwned)
|
||||
let isRef = t.kind == tyRef
|
||||
|
||||
# check if we need to construct the object in a temporary.
|
||||
# A temp is needed when:
|
||||
# - the constructor produces a ref (isRef)
|
||||
# - the destination is not a writable location (d.k == locNone)
|
||||
# - the constructed type differs from the destination type (subtype
|
||||
# assignments need the genAssignment path for ObjectAssignmentDefect)
|
||||
# - the constructor's field values may alias the destination (isPartOf)
|
||||
# check if we need to construct the object in a temporary
|
||||
var useTemp =
|
||||
isRef or
|
||||
d.k == locNone or
|
||||
(d.t != nil and not sameBackendType(t, d.t.skipTypes(abstractInstOwned))) or
|
||||
(d.k notin {locTemp,locLocalVar,locGlobalVar,locParam,locField}) or
|
||||
(isPartOf(d.lode, e) != arNo)
|
||||
|
||||
var tmp: TLoc = default(TLoc)
|
||||
var r: Rope
|
||||
let needsZeroMem =
|
||||
nfAllFieldsSet notin e.flags or
|
||||
(optSeqDestructors notin p.config.globalOptions and containsGarbageCollectedRef(t))
|
||||
let needsZeroMem = p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc} or nfAllFieldsSet notin e.flags
|
||||
if useTemp:
|
||||
tmp = getTemp(p, t)
|
||||
r = rdLoc(tmp)
|
||||
@@ -1968,15 +1883,15 @@ proc genSeqConstr(p: BProc, n: PNode, d: var TLoc) =
|
||||
if optSeqDestructors in p.config.globalOptions:
|
||||
let seqtype = n.typ
|
||||
let rd = rdLoc dest[]
|
||||
let et = getTypeDesc(p.module, seqtype.elementType)
|
||||
let pt = getSeqPayloadType(p.module, seqtype)
|
||||
let pe = seqPayloadElem(p.module, seqtype)
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "len", lit)
|
||||
p.s(cpsStmts).addFieldAssignmentWithValue(rd, "p"):
|
||||
p.s(cpsStmts).addCast(ptrType(pt)):
|
||||
p.s(cpsStmts).addCall(cgsymValue(p.module, "newSeqPayload"),
|
||||
lit,
|
||||
cSizeof(pe),
|
||||
cAlignof(pe))
|
||||
cSizeof(et),
|
||||
cAlignof(et))
|
||||
else:
|
||||
# generate call to newSeq before adding the elements per hand:
|
||||
genNewSeqAux(p, dest[], lit, n.len == 0)
|
||||
@@ -1997,7 +1912,7 @@ proc genSeqConstr(p: BProc, n: PNode, d: var TLoc) =
|
||||
proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) =
|
||||
var elem, arr: TLoc
|
||||
if n[1].kind == nkBracket:
|
||||
n[1].typ = n.typ
|
||||
n[1].typ() = n.typ
|
||||
genSeqConstr(p, n[1], d)
|
||||
return
|
||||
if d.k == locNone:
|
||||
@@ -2009,15 +1924,15 @@ proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) =
|
||||
let seqtype = n.typ
|
||||
let rd = rdLoc d
|
||||
let valL = cIntValue(L)
|
||||
let et = getTypeDesc(p.module, seqtype.elementType)
|
||||
let pt = getSeqPayloadType(p.module, seqtype)
|
||||
let pe = seqPayloadElem(p.module, seqtype)
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "len", valL)
|
||||
p.s(cpsStmts).addFieldAssignmentWithValue(rd, "p"):
|
||||
p.s(cpsStmts).addCast(ptrType(pt)):
|
||||
p.s(cpsStmts).addCall(cgsymValue(p.module, "newSeqPayload"),
|
||||
valL,
|
||||
cSizeof(pe),
|
||||
cAlignof(pe))
|
||||
cSizeof(et),
|
||||
cAlignof(et))
|
||||
else:
|
||||
let lit = cIntLiteral(L)
|
||||
genNewSeqAux(p, d, lit, L == 0)
|
||||
@@ -2158,20 +2073,12 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
|
||||
let ra = rdLoc(a)
|
||||
putIntoDest(p, b, e, ra & cArgumentSeparator & ra & "Len_0", a.storage)
|
||||
of tyString, tySequence:
|
||||
let ra = rdLoc(a)
|
||||
let la = lenExpr(p, a)
|
||||
if p.config.usesSso() and
|
||||
skipTypes(a.t, abstractVarRange).kind == tyString:
|
||||
let bra = byRefLoc(p, a)
|
||||
putIntoDest(p, b, e,
|
||||
cCall(cgsymValue(p.module, "nimStrData"), bra) &
|
||||
cArgumentSeparator & la,
|
||||
a.storage)
|
||||
else:
|
||||
let ra = rdLoc(a)
|
||||
putIntoDest(p, b, e,
|
||||
cIfExpr(dataFieldAccessor(p, ra), dataField(p, ra), NimNil) &
|
||||
cArgumentSeparator & la,
|
||||
a.storage)
|
||||
putIntoDest(p, b, e,
|
||||
cIfExpr(dataFieldAccessor(p, ra), dataField(p, ra), NimNil) &
|
||||
cArgumentSeparator & la,
|
||||
a.storage)
|
||||
of tyArray:
|
||||
let ra = rdLoc(a)
|
||||
let la = cIntValue(lengthOrd(p.config, a.t))
|
||||
@@ -2752,9 +2659,9 @@ proc genConv(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) =
|
||||
var a: TLoc = initLocExpr(p, n[0])
|
||||
let arg = if p.config.usesSso(): byRefLoc(p, a) else: rdLoc(a)
|
||||
putIntoDest(p, d, n,
|
||||
cgCall(p, "nimToCStringConv", arg),
|
||||
cgCall(p, "nimToCStringConv", rdLoc(a)),
|
||||
# "($1 ? $1->data : (NCSTRING)\"\")" % [a.rdLoc],
|
||||
a.storage)
|
||||
|
||||
proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) =
|
||||
@@ -2825,38 +2732,45 @@ proc genWasMoved(p: BProc; n: PNode) =
|
||||
# [addrLoc(p.config, a), getTypeDesc(p.module, a.t)])
|
||||
|
||||
proc genMove(p: BProc; n: PNode; d: var TLoc) =
|
||||
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref})
|
||||
if n.len == 4:
|
||||
# generated by liftdestructors:
|
||||
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
|
||||
var src: TLoc = initLocExpr(p, n[2])
|
||||
let destVal = rdLoc(a)
|
||||
let srcVal = rdLoc(src)
|
||||
if p.config.usesSso() and
|
||||
n[1].typ.skipTypes(abstractVar).kind == tyString:
|
||||
# SmallString: destroy dst then struct-copy src; no .p field aliasing needed
|
||||
p.s(cpsStmts).addSingleIfStmt(
|
||||
cOp(NotEqual,
|
||||
dotField(destVal, "p"),
|
||||
dotField(srcVal, "p"))):
|
||||
genStmts(p, n[3])
|
||||
genAssignment(p, a, src, {})
|
||||
else:
|
||||
p.s(cpsStmts).addSingleIfStmt(
|
||||
cOp(NotEqual,
|
||||
dotField(destVal, "p"),
|
||||
dotField(srcVal, "p"))):
|
||||
genStmts(p, n[3])
|
||||
p.s(cpsStmts).addFieldAssignment(destVal, "len", dotField(srcVal, "len"))
|
||||
p.s(cpsStmts).addFieldAssignment(destVal, "p", dotField(srcVal, "p"))
|
||||
p.s(cpsStmts).addFieldAssignment(destVal, "len", dotField(srcVal, "len"))
|
||||
p.s(cpsStmts).addFieldAssignment(destVal, "p", dotField(srcVal, "p"))
|
||||
else:
|
||||
if d.k == locNone: d = getTemp(p, n.typ)
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
|
||||
genAssignment(p, d, a, {})
|
||||
var op = getAttachedOp(p.module.g.graph, n.typ, attachedWasMoved)
|
||||
if op == nil or sfOverridden notin op.flags:
|
||||
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
|
||||
genAssignment(p, d, a, {})
|
||||
if op == nil:
|
||||
resetLoc(p, a)
|
||||
else:
|
||||
n[1] = makeAddr(n[1], p.module.idgen)
|
||||
genCall(p, n, d)
|
||||
var b = initLocExpr(p, newSymNode(op))
|
||||
case skipTypes(a.t, abstractVar+{tyStatic}).kind
|
||||
of tyOpenArray, tyVarargs: # todo fixme generated `wasMoved` hooks for
|
||||
# openarrays, but it probably shouldn't?
|
||||
let ra = rdLoc(a)
|
||||
var s: string
|
||||
if reifiedOpenArray(a.lode):
|
||||
if a.t.kind in {tyVar, tyLent}:
|
||||
s = derefField(ra, "Field0") & cArgumentSeparator & derefField(ra, "Field1")
|
||||
else:
|
||||
s = dotField(ra, "Field0") & cArgumentSeparator & dotField(ra, "Field1")
|
||||
else:
|
||||
s = ra & cArgumentSeparator & ra & "Len_0"
|
||||
p.s(cpsStmts).addCallStmt(rdLoc(b), s)
|
||||
else:
|
||||
let val = if p.module.compileToCpp: rdLoc(a) else: byRefLoc(p, a)
|
||||
p.s(cpsStmts).addCallStmt(rdLoc(b), val)
|
||||
else:
|
||||
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
|
||||
genAssignment(p, d, a, {})
|
||||
resetLoc(p, a)
|
||||
|
||||
@@ -2867,19 +2781,15 @@ proc genDestroy(p: BProc; n: PNode) =
|
||||
case t.kind
|
||||
of tyString:
|
||||
var a: TLoc = initLocExpr(p, arg)
|
||||
if p.config.usesSso():
|
||||
# SmallString: delegate to nimDestroyStrV1 (rc-based, handles static strings)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimDestroyStrV1"), rdLoc(a))
|
||||
else:
|
||||
let ra = rdLoc(a)
|
||||
let rp = dotField(ra, "p")
|
||||
p.s(cpsStmts).addSingleIfStmt(
|
||||
cOp(And, rp,
|
||||
cOp(Not, cOp(BitAnd, NimInt,
|
||||
derefField(rp, "cap"),
|
||||
NimStrlitFlag)))):
|
||||
let fn = if optThreads in p.config.globalOptions: "deallocShared" else: "dealloc"
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, fn), rp)
|
||||
let ra = rdLoc(a)
|
||||
let rp = dotField(ra, "p")
|
||||
p.s(cpsStmts).addSingleIfStmt(
|
||||
cOp(And, rp,
|
||||
cOp(Not, cOp(BitAnd, NimInt,
|
||||
derefField(rp, "cap"),
|
||||
NimStrlitFlag)))):
|
||||
let fn = if optThreads in p.config.globalOptions: "deallocShared" else: "dealloc"
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, fn), rp)
|
||||
of tySequence:
|
||||
var a: TLoc = initLocExpr(p, arg)
|
||||
let ra = rdLoc(a)
|
||||
@@ -2921,7 +2831,7 @@ proc genSlice(p: BProc; e: PNode; d: var TLoc) =
|
||||
let (x, y) = genOpenArraySlice(p, e, e.typ, e.typ.elementType,
|
||||
prepareForMutation = e[1].kind == nkHiddenDeref and
|
||||
e[1].typ.skipTypes(abstractInst).kind == tyString and
|
||||
p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc})
|
||||
p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc})
|
||||
if d.k == locNone: d = getTemp(p, e.typ)
|
||||
let dest = rdLoc(d)
|
||||
p.s(cpsStmts).addFieldAssignment(dest, "Field0", x)
|
||||
@@ -2940,13 +2850,6 @@ proc genEnumToStr(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
|
||||
case op
|
||||
of mAsgn:
|
||||
let kind = if e[0].sym.name.s == "=sink": nkSinkAsgn else: nkAsgn
|
||||
let lhs = e[1].skipHiddenAddr
|
||||
let n = newTreeI(kind, e.info, lhs, e[2])
|
||||
n.typ = e.typ
|
||||
cow(p, e[2])
|
||||
genAsgn(p, n, fastAsgn = kind != nkAsgn)
|
||||
of mOr, mAnd: genAndOr(p, e, d, op)
|
||||
of mNot..mUnaryMinusF64: unaryArith(p, e, d, op)
|
||||
of mUnaryMinusI..mAbsI: unaryArithOverflow(p, e, d, op)
|
||||
@@ -2991,15 +2894,8 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
|
||||
of mAppendStrStr: genStrAppend(p, e, d)
|
||||
of mAppendSeqElem:
|
||||
if optSeqDestructors in p.config.globalOptions:
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
|
||||
# Inline growth + direct slot assignment: avoids the add() call overhead
|
||||
# and lets the C compiler see the construction expression at its final
|
||||
# destination, enabling in-place construction for nkObjConstr etc.
|
||||
# gcYrc is excluded because its add() acquires a striped reader lock.
|
||||
genSeqElemAppendV2(p, e, d)
|
||||
else:
|
||||
e[1] = makeAddr(e[1], p.module.idgen)
|
||||
genCall(p, e, d)
|
||||
e[1] = makeAddr(e[1], p.module.idgen)
|
||||
genCall(p, e, d)
|
||||
else:
|
||||
genSeqElemAppend(p, e, d)
|
||||
of mEqStr: genStrEquals(p, e, d)
|
||||
@@ -3139,16 +3035,10 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
|
||||
let n = semparallel.liftParallel(p.module.g.graph, p.module.idgen, p.module.module, e)
|
||||
expr(p, n, d)
|
||||
of mDeepCopy:
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and optEnableDeepCopy notin p.config.globalOptions:
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} and optEnableDeepCopy notin p.config.globalOptions:
|
||||
localError(p.config, e.info,
|
||||
"for --mm:arc|atomicArc|orc 'deepcopy' support has to be enabled with --deepcopy:on")
|
||||
|
||||
let typ = e[1].typ.skipTypes({tyVar, tyRef, tyGenericInst, tyTypeDesc,
|
||||
tyAlias, tyInferred, tySink, tyLent, tyOwned})
|
||||
if hasDisabledAsgn(p.module.g.graph, typ):
|
||||
localError(p.config, e.info,
|
||||
"'deepCopy' is not available for type <" & typeToString(typ) & ">")
|
||||
|
||||
let x = if e[1].kind in {nkAddr, nkHiddenAddr}: e[1][0] else: e[1]
|
||||
var a = initLocExpr(p, x)
|
||||
var b = initLocExpr(p, e[2])
|
||||
@@ -3261,8 +3151,6 @@ proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) =
|
||||
for i in 0..<n.len:
|
||||
var it = n[i]
|
||||
if it.kind == nkExprColonExpr: it = it[1]
|
||||
# Do not produce code for void types
|
||||
if it.typ != nil and isEmptyType(it.typ): continue
|
||||
rec = initLoc(locExpr, it, dest[].storage)
|
||||
rec.snippet = dotField(rdLoc(dest[]), "Field" & rope(i))
|
||||
rec.flags.incl(lfEnforceDeref)
|
||||
@@ -3379,11 +3267,7 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) =
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseObjectConversionError"))
|
||||
raiseInstr(p, p.s(cpsStmts))
|
||||
|
||||
# skip cast when types map to the same C type
|
||||
# this avoids invalid C code like `*(T*)&x` for types that can't have their address taken (e.g., WASM __externref_t)
|
||||
if getTypeDesc(p.module, n.typ) == getTypeDesc(p.module, n[0].typ):
|
||||
expr(p, n[0], d)
|
||||
elif n[0].typ.kind != tyObject:
|
||||
if n[0].typ.kind != tyObject:
|
||||
let destTyp = getTypeDesc(p.module, n.typ)
|
||||
let val = rdLoc(a)
|
||||
if n.isLValue:
|
||||
@@ -3429,7 +3313,7 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) =
|
||||
cCast(ptrType(destType),
|
||||
wrapPar(cAddr(wrapPar(val))))),
|
||||
a.storage)
|
||||
elif p.module.compileToCpp or isImportedType(src):
|
||||
elif p.module.compileToCpp:
|
||||
# C++ implicitly downcasts for us
|
||||
expr(p, arg, d)
|
||||
else:
|
||||
@@ -3475,9 +3359,8 @@ proc genConstSetup(p: BProc; sym: PSym): bool =
|
||||
useHeader(m, sym)
|
||||
if sym.loc.k == locNone:
|
||||
fillBackendName(p.module, sym)
|
||||
backendEnsureMutable sym
|
||||
fillLoc(sym.locImpl, locData, sym.astdef, OnStatic)
|
||||
if m.hcrOn: incl(sym, lfIndirect)
|
||||
fillLoc(sym.loc, locData, sym.astdef, OnStatic)
|
||||
if m.hcrOn: incl(sym.loc.flags, lfIndirect)
|
||||
result = lfNoDecl notin sym.loc.flags
|
||||
|
||||
proc genConstHeader(m, q: BModule; p: BProc, sym: PSym) =
|
||||
@@ -3511,23 +3394,7 @@ proc genConstDefinition(q: BModule; p: BProc; sym: PSym) =
|
||||
data.addDeclWithVisibility(Private):
|
||||
data.addVarWithInitializer(Local, actualConstName, typ = td):
|
||||
genBracedInit(q.initProc, sym.astdef, isConst = true, sym.typ, data)
|
||||
if q.config.cmd == cmdNifC:
|
||||
# Each `cg` process that demands this const emits its definition
|
||||
# (emit-everywhere). Always declare it first (the data analogue of a proc
|
||||
# prototype) so a TU whose copy the merge stage drops still has a valid
|
||||
# declaration; wrap the definition as a droppable `'d'` unit the merge
|
||||
# stage assigns to a single owner.
|
||||
let cname = stripCnifMarks(actualConstName)
|
||||
var decl = newBuilder("")
|
||||
decl.addDeclWithVisibility(Extern):
|
||||
decl.addVar(kind = Local, name = actualConstName, typ = td)
|
||||
q.s[cfsData].add(extract(decl))
|
||||
q.s[cfsData].add(cnifDefDirective(cname, "d", icNifName(q, sym)))
|
||||
q.s[cfsData].add(extract(data))
|
||||
q.s[cfsData].add(cnifEndDefs())
|
||||
q.icDataDefs.add (cname, icNifName(q, sym))
|
||||
else:
|
||||
q.s[cfsData].add(extract(data))
|
||||
q.s[cfsData].add(extract(data))
|
||||
if q.hcrOn:
|
||||
# generate the global pointer with the real name
|
||||
q.s[cfsVars].addVar(kind = Global, name = sym.loc.snippet,
|
||||
@@ -3563,7 +3430,7 @@ proc genConstDefinition(q: BModule; p: BProc; sym: PSym) =
|
||||
|
||||
proc genConstStmt(p: BProc, n: PNode) =
|
||||
# This code is only used in the new DCE implementation.
|
||||
assert delayedCodegen(p.module)
|
||||
assert useAliveDataFromDce in p.module.flags
|
||||
let m = p.module
|
||||
for it in n:
|
||||
if it[0].kind == nkSym:
|
||||
@@ -3581,7 +3448,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
var sym = n.sym
|
||||
case sym.kind
|
||||
of skMethod:
|
||||
if delayedCodegen(p.module) or {sfDispatcher, sfForward} * sym.flags != {}:
|
||||
if useAliveDataFromDce in p.module.flags or {sfDispatcher, sfForward} * sym.flags != {}:
|
||||
# we cannot produce code for the dispatcher yet:
|
||||
fillProcLoc(p.module, n)
|
||||
genProcPrototype(p.module, sym)
|
||||
@@ -3591,29 +3458,14 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
of skProc, skConverter, skIterator, skFunc:
|
||||
#if sym.kind == skIterator:
|
||||
# echo renderTree(sym.getBody, {renderIds})
|
||||
if p.config.cmd == cmdNifC and
|
||||
(isGenericRoutineStrict(sym) or sfCompileTime in sym.flags or
|
||||
(sym.kind == skIterator and sym.typ.callConv == ccInline)):
|
||||
# Under IC a module's top-level routine definitions are serialized as bare
|
||||
# symbol references that reappear in the loaded statement list. Uninstantiated
|
||||
# generic routines (incl. those with type-class params like `tuple`) and
|
||||
# `.compileTime` routines have no run-time code, so skip them here.
|
||||
# Inline iterators likewise have no standalone code — they are always inlined
|
||||
# at their for-loop call sites by the transformer (only closure iterators get
|
||||
# a standalone C function), so a bare serialized def reference is a no-op.
|
||||
return
|
||||
if sfCompileTime in sym.flags:
|
||||
localError(p.config, n.info, "request to generate code for .compileTime proc: " &
|
||||
sym.name.s)
|
||||
if delayedCodegen(p.module) and sym.typ.callConv != ccInline:
|
||||
if useAliveDataFromDce in p.module.flags and sym.typ.callConv != ccInline:
|
||||
fillProcLoc(p.module, n)
|
||||
genProcPrototype(p.module, sym)
|
||||
else:
|
||||
genProc(p.module, sym)
|
||||
# For cross-module inline procs with optCompress, ensure prototype is emitted
|
||||
if sym.typ.callConv == ccInline and optCompress in p.config.globalOptions and
|
||||
sym.itemId.module != p.module.module.position:
|
||||
genProcPrototype(p.module, sym)
|
||||
if sym.loc.snippet == "" or sym.loc.lode == nil:
|
||||
internalError(p.config, n.info, "expr: proc not init " & sym.name.s)
|
||||
putLocIntoDest(p, d, sym.loc)
|
||||
@@ -3622,13 +3474,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
var lit = newBuilder("")
|
||||
genLiteral(p, sym.astdef, sym.typ, lit)
|
||||
putIntoDest(p, d, n, extract(lit), OnStatic)
|
||||
elif optCompress in p.config.globalOptions:
|
||||
# With delayed codegen, we need to ensure the definition is generated
|
||||
# not just the extern header declaration
|
||||
requestConstImpl(p, sym)
|
||||
assert((sym.loc.snippet != "") and (sym.loc.t != nil))
|
||||
putLocIntoDest(p, d, sym.loc)
|
||||
elif delayedCodegen(p.module):
|
||||
elif useAliveDataFromDce in p.module.flags:
|
||||
genConstHeader(p.module, p.module, p, sym)
|
||||
assert((sym.loc.snippet != "") and (sym.loc.t != nil))
|
||||
putLocIntoDest(p, d, sym.loc)
|
||||
@@ -3676,11 +3522,6 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
# echo renderTree(p.prc.ast, {renderIds})
|
||||
internalError(p.config, n.info, "expr: param not init " & sym.name.s & "_" & $sym.id)
|
||||
putLocIntoDest(p, d, sym.loc)
|
||||
of skTemplate, skMacro:
|
||||
# Under IC a module's top-level template/macro definitions are serialized as
|
||||
# bare symbol references (only their interface matters), so they reappear in
|
||||
# the loaded statement list. They are compile-time only and produce no code.
|
||||
discard
|
||||
else: internalError(p.config, n.info, "expr(" & $sym.kind & "); unknown symbol")
|
||||
of nkNilLit:
|
||||
if not isEmptyType(n.typ):
|
||||
@@ -3765,7 +3606,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
of nkWhileStmt: genWhileStmt(p, n)
|
||||
of nkVarSection, nkLetSection: genVarStmt(p, n)
|
||||
of nkConstSection:
|
||||
if delayedCodegen(p.module):
|
||||
if useAliveDataFromDce in p.module.flags:
|
||||
genConstStmt(p, n)
|
||||
else: # enforce addressable consts for exportc
|
||||
let m = p.module
|
||||
@@ -3831,10 +3672,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef:
|
||||
if n[genericParamsPos].kind == nkEmpty:
|
||||
var prc = n[namePos].sym
|
||||
if optCompress in p.config.globalOptions:
|
||||
if prc.magic in generatedMagics:
|
||||
genProc(p.module, prc)
|
||||
elif delayedCodegen(p.module):
|
||||
if useAliveDataFromDce in p.module.flags:
|
||||
if p.module.alive.contains(prc.itemId.item) and
|
||||
prc.magic in generatedMagics:
|
||||
genProc(p.module, prc)
|
||||
@@ -3854,68 +3692,9 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
|
||||
inc p.splitDecls
|
||||
genGotoState(p, n)
|
||||
of nkBreakState: genBreakState(p, n, d)
|
||||
of nkMixinStmt, nkBindStmt, nkReplayAction: discard
|
||||
of nkMixinStmt, nkBindStmt: discard
|
||||
else: internalError(p.config, n.info, "expr(" & $n.kind & "); unknown node kind")
|
||||
|
||||
proc isOpaqueImportcType(t: PType): bool =
|
||||
# importc type without completeStruct that can't use aggregate init (e.g. C11 _Atomic)
|
||||
if t.sym != nil and sfImportc in t.sym.flags:
|
||||
if tfCompleteStruct notin t.flags:
|
||||
if tfIncompleteStruct in t.flags:
|
||||
return true
|
||||
if t.kind == tyObject and (t.n == nil or t.n.len == 0):
|
||||
return true
|
||||
return false
|
||||
|
||||
proc containsOpaqueImportcField(typ: PType): bool
|
||||
|
||||
proc containsOpaqueImportcFieldAux(t: PType; n: PNode): bool =
|
||||
if n == nil: return false
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
for child in n.sons:
|
||||
if containsOpaqueImportcFieldAux(t, child):
|
||||
return true
|
||||
of nkRecCase:
|
||||
if containsOpaqueImportcFieldAux(t, n[0]):
|
||||
return true
|
||||
for i in 1..<n.len:
|
||||
let branch = n[i]
|
||||
if branch.kind == nkOfBranch or branch.kind == nkElse:
|
||||
if containsOpaqueImportcFieldAux(t, branch.lastSon):
|
||||
return true
|
||||
of nkSym:
|
||||
if containsOpaqueImportcField(n.sym.typ):
|
||||
return true
|
||||
else:
|
||||
discard
|
||||
return false
|
||||
|
||||
proc containsOpaqueImportcField(typ: PType): bool =
|
||||
# Check if type contains opaque importc fields that need designated initializers
|
||||
if typ == nil: return false
|
||||
let t = skipTypes(typ, abstractRange+{tyOwned}-{tyTypeDesc})
|
||||
if isOpaqueImportcType(t):
|
||||
return true
|
||||
case t.kind
|
||||
of tyObject:
|
||||
if t.baseClass != nil:
|
||||
if containsOpaqueImportcField(t.baseClass):
|
||||
return true
|
||||
if containsOpaqueImportcFieldAux(t, t.n):
|
||||
return true
|
||||
of tyTuple:
|
||||
for i, a in t.ikids:
|
||||
if isEmptyType(a): continue
|
||||
if containsOpaqueImportcField(a):
|
||||
return true
|
||||
of tyArray:
|
||||
if containsOpaqueImportcField(t.elementType):
|
||||
return true
|
||||
else:
|
||||
discard
|
||||
return false
|
||||
|
||||
proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo; result: var Builder) =
|
||||
var t = skipTypes(typ, abstractRange+{tyOwned}-{tyTypeDesc})
|
||||
case t.kind
|
||||
@@ -3946,43 +3725,24 @@ proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo; result: var Builder)
|
||||
result.addField(closureInit, name = "ClE_0"):
|
||||
result.add(NimNil)
|
||||
of tyObject:
|
||||
# Use designated initializers when opaque importc fields present
|
||||
var objInit: StructInitializer
|
||||
let initKind = if containsOpaqueImportcField(t): siNamedStruct else: siOrderedStruct
|
||||
result.addStructInitializer(objInit, kind = initKind):
|
||||
result.addStructInitializer(objInit, kind = siOrderedStruct):
|
||||
getNullValueAuxT(p, t, t, t.n, nil, result, objInit, true, info)
|
||||
of tyTuple:
|
||||
# Use designated initializers when opaque importc fields present
|
||||
var tupleInit: StructInitializer
|
||||
let initKind = if containsOpaqueImportcField(t): siNamedStruct else: siOrderedStruct
|
||||
result.addStructInitializer(tupleInit, kind = initKind):
|
||||
if p.vccAndC and validTupleTypeFields(t) == 0:
|
||||
result.addStructInitializer(tupleInit, kind = siOrderedStruct):
|
||||
if p.vccAndC and t.isEmptyTupleType:
|
||||
result.addField(tupleInit, name = "dummy"):
|
||||
result.addIntValue(0)
|
||||
for i, a in t.ikids:
|
||||
# Do not produce code for void types
|
||||
if isEmptyType(a): continue
|
||||
let elemTyp = skipTypes(a, abstractRange+{tyOwned}-{tyTypeDesc})
|
||||
if not isOpaqueImportcType(elemTyp):
|
||||
result.addField(tupleInit, name = "Field" & $i):
|
||||
getDefaultValue(p, a, info, result)
|
||||
result.addField(tupleInit, name = "Field" & $i):
|
||||
getDefaultValue(p, a, info, result)
|
||||
of tyArray:
|
||||
let elemTyp = skipTypes(t.elementType, abstractRange+{tyOwned}-{tyTypeDesc})
|
||||
if isOpaqueImportcType(elemTyp):
|
||||
result.add "{0}"
|
||||
elif toInt(lengthOrd(p.config, t.indexType)) > broadcastArrayThreshold and
|
||||
elemTyp.kind in {tyInt..tyUInt64, tyBool, tyChar, tyFloat..tyFloat128,
|
||||
tyPtr, tyPointer, tyCstring}:
|
||||
# Large array of a scalar whose default is the zero representation: a single
|
||||
# C `{0}` zero-fills all `lengthOrd` slots instead of emitting that many
|
||||
# initializers (keeps huge SSZ-style zero buffers compact in the C output).
|
||||
result.add "{0}"
|
||||
else:
|
||||
var arrInit: StructInitializer
|
||||
result.addStructInitializer(arrInit, kind = siArray):
|
||||
for i in 0..<toInt(lengthOrd(p.config, t.indexType)):
|
||||
result.addField(arrInit, name = ""):
|
||||
getDefaultValue(p, t.elementType, info, result)
|
||||
var arrInit: StructInitializer
|
||||
result.addStructInitializer(arrInit, kind = siArray):
|
||||
for i in 0..<toInt(lengthOrd(p.config, t.indexType)):
|
||||
result.addField(arrInit, name = ""):
|
||||
getDefaultValue(p, t.elementType, info, result)
|
||||
#result = rope"{}"
|
||||
of tyOpenArray, tyVarargs:
|
||||
var openArrInit: StructInitializer
|
||||
@@ -4037,7 +3797,8 @@ proc getNullValueAux(p: BProc; t: PType; obj, constOrNil: PNode,
|
||||
var fieldName: string = ""
|
||||
if b.kind == nkRecList and not isEmptyCaseObjectBranch(b):
|
||||
fieldName = "_" & mangleRecFieldName(p.module, obj[0].sym) & "_" & $selectedBranch
|
||||
result.addField(init, name = ""): # anonymous union
|
||||
result.addField(init, name = "<anonymous union>"):
|
||||
# XXX figure out name for the union, see use of `addAnonUnion`
|
||||
var branchInit: StructInitializer
|
||||
result.addStructInitializer(branchInit, kind = siNamedStruct):
|
||||
result.addField(branchInit, name = fieldName):
|
||||
@@ -4046,7 +3807,8 @@ proc getNullValueAux(p: BProc; t: PType; obj, constOrNil: PNode,
|
||||
getNullValueAux(p, t, b, constOrNil, result, branchObjInit, isConst, info)
|
||||
elif b.kind == nkSym:
|
||||
fieldName = mangleRecFieldName(p.module, b.sym)
|
||||
result.addField(init, name = ""): # anonymous union
|
||||
result.addField(init, name = "<anonymous union>"):
|
||||
# XXX figure out name for the union, see use of `addAnonUnion`
|
||||
var branchInit: StructInitializer
|
||||
result.addStructInitializer(branchInit, kind = siNamedStruct):
|
||||
result.addField(branchInit, name = fieldName):
|
||||
@@ -4061,9 +3823,6 @@ proc getNullValueAux(p: BProc; t: PType; obj, constOrNil: PNode,
|
||||
|
||||
of nkSym:
|
||||
let field = obj.sym
|
||||
let fieldTyp = skipTypes(field.typ, abstractRange+{tyOwned}-{tyTypeDesc})
|
||||
if isOpaqueImportcType(fieldTyp):
|
||||
return # C zero-initializes omitted fields
|
||||
let sname = mangleRecFieldName(p.module, field)
|
||||
result.addField(init, name = sname):
|
||||
block fieldInit:
|
||||
@@ -4108,10 +3867,11 @@ proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: PNode,
|
||||
|
||||
proc genConstObjConstr(p: BProc; n: PNode; isConst: bool; result: var Builder) =
|
||||
let t = n.typ.skipTypes(abstractInstOwned)
|
||||
# Use designated initializers when opaque importc fields present
|
||||
#if not isObjLackingTypeField(t) and not p.module.compileToCpp:
|
||||
# result.addf("{$1}", [genTypeInfo(p.module, t)])
|
||||
# inc count
|
||||
var objInit: StructInitializer
|
||||
let initKind = if t.kind == tyObject and containsOpaqueImportcField(t): siNamedStruct else: siOrderedStruct
|
||||
result.addStructInitializer(objInit, kind = initKind):
|
||||
result.addStructInitializer(objInit, kind = siOrderedStruct):
|
||||
if t.kind == tyObject:
|
||||
getNullValueAuxT(p, t, t, t.n, n, result, objInit, isConst, n.info)
|
||||
|
||||
@@ -4143,8 +3903,6 @@ proc genConstTuple(p: BProc, n: PNode; isConst: bool; tup: PType; result: var Bu
|
||||
var it = n[i]
|
||||
if it.kind == nkExprColonExpr:
|
||||
it = it[1]
|
||||
# Do not produce code for void types
|
||||
if isEmptyType(tup[i]): continue
|
||||
result.addField(tupleInit, name = "Field" & $i):
|
||||
genBracedInit(p, it, isConst, tup[i], result)
|
||||
|
||||
@@ -4263,13 +4021,7 @@ proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; resul
|
||||
var d: TLoc = initLocExpr(p, n)
|
||||
result.add rdLoc(d)
|
||||
of tyArray, tyVarargs:
|
||||
if isDefaultBroadcastArray(n, p.config):
|
||||
# Compact zero/null-default array (see `isDefaultBroadcastArray`): the
|
||||
# whole thing is the null value of every slot, so a single C `{0}`
|
||||
# zero-fills all `lengthOrd` elements — no need to materialise them.
|
||||
result.add "{0}"
|
||||
else:
|
||||
genConstSimpleList(p, n, isConst, result)
|
||||
genConstSimpleList(p, n, isConst, result)
|
||||
of tyTuple:
|
||||
genConstTuple(p, n, isConst, typ, result)
|
||||
of tyOpenArray:
|
||||
@@ -4297,10 +4049,7 @@ proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; resul
|
||||
genConstObjConstr(p, n, isConst, result)
|
||||
of tyString, tyCstring:
|
||||
if optSeqDestructors in p.config.globalOptions and n.kind != nkNilLit and ty == tyString:
|
||||
if p.config.usesSso():
|
||||
genStringLiteralV3Const(p.module, n, isConst, result)
|
||||
else:
|
||||
genStringLiteralV2Const(p.module, n, isConst, result)
|
||||
genStringLiteralV2Const(p.module, n, isConst, result)
|
||||
else:
|
||||
var d: TLoc = initLocExpr(p, n)
|
||||
result.add rdLoc(d)
|
||||
|
||||
@@ -16,17 +16,16 @@
|
||||
## implementation.
|
||||
|
||||
template detectVersion(field, corename) =
|
||||
if m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
|
||||
result = 2
|
||||
else:
|
||||
result = 1
|
||||
if m.g.field == 0:
|
||||
let core = getCompilerProc(m.g.graph, corename)
|
||||
if core == nil or core.kind != skConst:
|
||||
m.g.field = 1
|
||||
else:
|
||||
m.g.field = toInt(ast.getInt(core.astdef))
|
||||
result = m.g.field
|
||||
|
||||
proc detectStrVersion(m: BModule): int =
|
||||
if m.g.config.usesSso() and
|
||||
m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
|
||||
result = 3
|
||||
else:
|
||||
detectVersion(strVersion, "nimStrVersion")
|
||||
detectVersion(strVersion, "nimStrVersion")
|
||||
|
||||
proc detectSeqVersion(m: BModule): int =
|
||||
detectVersion(seqVersion, "nimSeqVersion")
|
||||
@@ -132,192 +131,6 @@ proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Bu
|
||||
result.addField(strInit, name = "p"):
|
||||
result.add(cCast(ptrType("NimStrPayload"), cAddr(pureLit)))
|
||||
|
||||
proc ssoCharLit(ch: char): string =
|
||||
## Return a C char literal for ch, with proper escaping.
|
||||
const hexDigits = "0123456789abcdef"
|
||||
result = "'"
|
||||
case ch
|
||||
of '\'': result.add("\\'")
|
||||
of '\\': result.add("\\\\")
|
||||
of '\0': result.add("\\0")
|
||||
of '\n': result.add("\\n")
|
||||
of '\r': result.add("\\r")
|
||||
of '\t': result.add("\\t")
|
||||
elif ch.ord < 32 or ch.ord == 127:
|
||||
result.add("\\x")
|
||||
result.add(hexDigits[ch.ord shr 4])
|
||||
result.add(hexDigits[ch.ord and 0xf])
|
||||
else:
|
||||
result.add(ch)
|
||||
result.add('\'')
|
||||
|
||||
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;
|
||||
@@ -328,8 +141,6 @@ proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo;
|
||||
let tmp = getTempName(m)
|
||||
genStringLiteralDataOnlyV2(m, s, tmp, isConst)
|
||||
result.add tmp
|
||||
of 3:
|
||||
localError(m.config, info, "genStringLiteralDataOnly not supported for SmallString (nimsso)")
|
||||
else:
|
||||
localError(m.config, info, "cannot determine how to produce code for string literal")
|
||||
|
||||
@@ -340,6 +151,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")
|
||||
|
||||
@@ -126,26 +126,25 @@ proc genVarTuple(p: BProc, n: PNode) =
|
||||
let vn = n[i]
|
||||
let v = vn.sym
|
||||
if sfCompileTime in v.flags: continue
|
||||
backendEnsureMutable v
|
||||
if sfGlobal in v.flags:
|
||||
assignGlobalVar(p, vn, "")
|
||||
genObjectInit(p, cpsInit, v.typ, v.locImpl, constructObj)
|
||||
genObjectInit(p, cpsInit, v.typ, v.loc, constructObj)
|
||||
registerTraverseProc(p, v)
|
||||
else:
|
||||
assignLocalVar(p, vn)
|
||||
initLocalVar(p, v, immediateAsgn=isAssignedImmediately(p.config, n[^1]))
|
||||
var field = initLoc(locExpr, vn, tup.storage)
|
||||
let rtup = rdLoc(tup)
|
||||
let fieldName =
|
||||
let fieldName =
|
||||
if t.kind == tyTuple:
|
||||
"Field" & $i
|
||||
else:
|
||||
if t.n[i].kind != nkSym: internalError(p.config, n.info, "genVarTuple")
|
||||
mangleRecFieldName(p.module, t.n[i].sym)
|
||||
field.snippet = dotField(rtup, fieldName)
|
||||
putLocIntoDest(p, v.locImpl, field)
|
||||
putLocIntoDest(p, v.loc, field)
|
||||
if forHcr or isGlobalInBlock:
|
||||
hcrGlobals.add((loc: v.locImpl, tp: CNil))
|
||||
hcrGlobals.add((loc: v.loc, tp: CNil))
|
||||
|
||||
if forHcr:
|
||||
# end the block where the tuple gets initialized
|
||||
@@ -226,11 +225,11 @@ proc genState(p: BProc, n: PNode) =
|
||||
elif n0.kind == nkStrLit:
|
||||
p.s(cpsStmts).addLabel(n0.strVal)
|
||||
|
||||
proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int, isReturnStmt = false) =
|
||||
proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
|
||||
# Called by return and break stmts.
|
||||
# Deals with issues faced when jumping out of try/except/finally stmts.
|
||||
|
||||
var stack = newSeq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]](0)
|
||||
var stack = newSeq[tuple[fin: PNode, inExcept: bool, label: Natural]](0)
|
||||
|
||||
inc p.withinBlockLeaveActions
|
||||
for i in 1..howManyTrys:
|
||||
@@ -258,7 +257,7 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int, isReturnStmt
|
||||
|
||||
# Pop exceptions that was handled by the
|
||||
# except-blocks we are in
|
||||
if noSafePoints notin p.flags and not (isReturnStmt and isClosureIterator(p.prc.typ)):
|
||||
if noSafePoints notin p.flags:
|
||||
for i in countdown(howManyExcepts-1, 0):
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popCurrentException"))
|
||||
|
||||
@@ -341,9 +340,9 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): Snippet =
|
||||
call[i][0]
|
||||
else:
|
||||
call[i]
|
||||
if not param.typ.isCompileTimeOnly and (param.kind != nkBracketExpr or param.typ.kind in
|
||||
if param.kind != nkBracketExpr or param.typ.kind in
|
||||
{tyRef, tyPtr, tyUncheckedArray, tyArray, tyOpenArray,
|
||||
tyVarargs, tySequence, tyString, tyCstring, tyTuple}):
|
||||
tyVarargs, tySequence, tyString, tyCstring, tyTuple}:
|
||||
let tempLoc = initLocExprSingleUse(p, param)
|
||||
didGenTemp = didGenTemp or tempLoc.k == locTemp
|
||||
genOtherArg(p, call, i, typ, res, argBuilder)
|
||||
@@ -366,7 +365,6 @@ proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) =
|
||||
if v.flags * {sfImportc, sfExportc} == {sfImportc} and
|
||||
value.kind == nkEmpty and
|
||||
v.loc.flags * {lfHeader, lfNoDecl} != {}:
|
||||
# IC XXX: this is bad, we should set v.loc regardless here
|
||||
return
|
||||
if sfPure in v.flags:
|
||||
# v.owner.kind != skModule:
|
||||
@@ -462,8 +460,7 @@ proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) =
|
||||
if value.kind != nkEmpty and valueAsRope.len == 0:
|
||||
genLineDir(targetProc, vn)
|
||||
if not isCppCtorCall:
|
||||
backendEnsureMutable v
|
||||
loadInto(targetProc, vn, value, v.locImpl)
|
||||
loadInto(targetProc, vn, value, v.loc)
|
||||
if forHcr:
|
||||
endBlockWith(targetProc):
|
||||
finishBranch(p.s(cpsStmts), hcrInit)
|
||||
@@ -490,17 +487,14 @@ proc genClosureVar(p: BProc, a: PNode) =
|
||||
constructLoc(p, v)
|
||||
|
||||
proc genVarStmt(p: BProc, n: PNode) =
|
||||
for it in n:
|
||||
case it.kind
|
||||
of nkCommentStmt: discard
|
||||
of nkIdentDefs:
|
||||
for it in n.sons:
|
||||
if it.kind == nkCommentStmt: continue
|
||||
if it.kind == nkIdentDefs:
|
||||
# can be a lifted var nowadays ...
|
||||
if it[0].kind == nkSym:
|
||||
genSingleVar(p, it)
|
||||
else:
|
||||
genClosureVar(p, it)
|
||||
of nkSym:
|
||||
genSingleVar(p, it.sym, newSymNode(it.sym), it.sym.astdef)
|
||||
else:
|
||||
genVarTuple(p, it)
|
||||
|
||||
@@ -560,8 +554,7 @@ proc genReturnStmt(p: BProc, t: PNode) =
|
||||
if (t[0].kind != nkEmpty): genStmts(p, t[0])
|
||||
blockLeaveActions(p,
|
||||
howManyTrys = p.nestedTryStmts.len,
|
||||
howManyExcepts = p.inExceptBlockLen,
|
||||
isReturnStmt = true)
|
||||
howManyExcepts = p.inExceptBlockLen)
|
||||
if (p.finallySafePoints.len > 0) and noSafePoints notin p.flags:
|
||||
# If we're in a finally block, and we came here by exception
|
||||
# consume it before we return.
|
||||
@@ -743,10 +736,8 @@ proc genBlock(p: BProc, n: PNode, d: var TLoc) =
|
||||
# named block?
|
||||
assert(n[0].kind == nkSym)
|
||||
var sym = n[0].sym
|
||||
backendEnsureMutable sym
|
||||
sym.locImpl.k = locOther
|
||||
sym.positionImpl = p.breakIdx+1
|
||||
# ^ IC: review this
|
||||
sym.loc.k = locOther
|
||||
sym.position = p.breakIdx+1
|
||||
expr(p, n[1], d)
|
||||
endSimpleBlock(p, scope)
|
||||
|
||||
@@ -836,26 +827,12 @@ proc raiseExitCleanup(p: BProc, destroy: string) =
|
||||
p.s(cpsStmts).addGoto("LA" & $p.nestedTryStmts[^1].label & "_")
|
||||
|
||||
proc finallyActions(p: BProc) =
|
||||
if p.config.exc != excGoto:
|
||||
# Walk past compiler-injected `nkHiddenTryStmt` wrappers (e.g. ARC's
|
||||
# destructor try/finally that wraps `except T as e:` bodies) to reach
|
||||
# the user's actual try. We must NOT walk past a real user try whose
|
||||
# body we are currently in, because a raise from there will be caught
|
||||
# by that try's own except branches rather than escaping outward.
|
||||
#
|
||||
# If after skipping wrappers the next entry is a user try in its
|
||||
# except branch (inExcept=true), inline its finally body before the
|
||||
# raise propagates — without this, the C++ sibling-catch rule would
|
||||
# cause the user's catch(...)/finally pair to be bypassed and the
|
||||
# finally would be silently dropped.
|
||||
for i in countdown(p.nestedTryStmts.high, 0):
|
||||
if p.nestedTryStmts[i].isHidden:
|
||||
continue
|
||||
if p.nestedTryStmts[i].inExcept:
|
||||
let finallyBlock = p.nestedTryStmts[i].fin
|
||||
if finallyBlock != nil:
|
||||
genSimpleBlock(p, finallyBlock[0])
|
||||
return
|
||||
if p.config.exc != excGoto and p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept:
|
||||
# if the current try stmt have a finally block,
|
||||
# we must execute it before reraising
|
||||
let finallyBlock = p.nestedTryStmts[^1].fin
|
||||
if finallyBlock != nil:
|
||||
genSimpleBlock(p, finallyBlock[0])
|
||||
|
||||
proc raiseInstr(p: BProc; result: var Builder) =
|
||||
if p.config.exc == excGoto:
|
||||
@@ -1179,7 +1156,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
throw;
|
||||
}
|
||||
} catch(...) {
|
||||
// C++ exception occurred, not under Nim's control.
|
||||
// C++ exception occured, not under Nim's control.
|
||||
}
|
||||
{
|
||||
/* finally: */
|
||||
@@ -1199,7 +1176,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
lineCg(p, cpsLocals, "std::exception_ptr T$1_;$n", [etmp])
|
||||
|
||||
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
||||
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
|
||||
p.nestedTryStmts.add((fin, false, 0.Natural))
|
||||
|
||||
if t.kind == nkHiddenTryStmt:
|
||||
lineCg(p, cpsStmts, "try {$n", [])
|
||||
@@ -1237,7 +1214,6 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
else:
|
||||
scope = initScope(p.s(cpsStmts))
|
||||
# we handled the error:
|
||||
linefmt(p, cpsStmts, "T$1_ = nullptr;$n", [etmp])
|
||||
expr(p, t[i][0], d)
|
||||
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
|
||||
endBlockWith(p):
|
||||
@@ -1274,8 +1250,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
initElifBranch(p.s(cpsStmts), ifStmt, orExpr)
|
||||
if exvar != nil:
|
||||
fillLocalName(p, exvar.sym)
|
||||
backendEnsureMutable exvar.sym
|
||||
fillLoc(exvar.sym.locImpl, locTemp, exvar, OnStack)
|
||||
fillLoc(exvar.sym.loc, locTemp, exvar, OnStack)
|
||||
linefmt(p, cpsStmts, "$1 $2 = T$3_;$n", [getTypeDesc(p.module, exvar.sym.typ),
|
||||
rdLoc(exvar.sym.loc), rope(etmp+1)])
|
||||
# we handled the error:
|
||||
@@ -1323,8 +1298,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
if isImportedException(typeNode.typ, p.config):
|
||||
let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
|
||||
fillLocalName(p, exvar.sym)
|
||||
backendEnsureMutable exvar.sym
|
||||
fillLoc(exvar.sym.locImpl, locTemp, exvar, OnStack)
|
||||
fillLoc(exvar.sym.loc, locTemp, exvar, OnStack)
|
||||
startBlockWith(p):
|
||||
lineCg(p, cpsStmts, "catch ($1& $2) {$n", [getTypeDesc(p.module, typeNode.typ), rdLoc(exvar.sym.loc)])
|
||||
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
|
||||
@@ -1386,7 +1360,7 @@ proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) =
|
||||
genLineDir(p, t)
|
||||
cgsym(p.module, "popCurrentExceptionEx")
|
||||
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
||||
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
|
||||
p.nestedTryStmts.add((fin, false, 0.Natural))
|
||||
startBlockWith(p):
|
||||
p.s(cpsStmts).add("try {\n")
|
||||
expr(p, t[0], d)
|
||||
@@ -1415,8 +1389,7 @@ proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) =
|
||||
if t[i][j].isInfixAs():
|
||||
let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
|
||||
fillLocalName(p, exvar.sym)
|
||||
backendEnsureMutable exvar.sym
|
||||
fillLoc(exvar.sym.locImpl, locTemp, exvar, OnUnknown)
|
||||
fillLoc(exvar.sym.loc, locTemp, exvar, OnUnknown)
|
||||
startBlockWith(p):
|
||||
lineCg(p, cpsStmts, "catch ($1& $2) {$n", [getTypeDesc(p.module, t[i][j][1].typ), rdLoc(exvar.sym.loc)])
|
||||
else:
|
||||
@@ -1465,7 +1438,7 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) =
|
||||
let lab = p.labels
|
||||
let hasExcept = t[1].kind == nkExceptBranch
|
||||
if hasExcept: inc p.withinTryWithExcept
|
||||
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, Natural lab))
|
||||
p.nestedTryStmts.add((fin, false, Natural lab))
|
||||
|
||||
p.flags.incl nimErrorFlagAccessed
|
||||
|
||||
@@ -1671,7 +1644,7 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
|
||||
initElifBranch(p.s(cpsStmts), nonQuirkyIf, removeSinglePar(
|
||||
cOp(Equal, dotField(safePoint, "status"), cIntValue(0))))
|
||||
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
||||
p.nestedTryStmts.add((fin, quirkyExceptions, t.kind == nkHiddenTryStmt, 0.Natural))
|
||||
p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural))
|
||||
expr(p, t[0], d)
|
||||
var quirkyIf = default(IfBuilder)
|
||||
var quirkyScope = default(ScopeBuilder)
|
||||
@@ -1955,15 +1928,6 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) =
|
||||
elif optFieldCheck in p.options and isDiscriminantField(e[0]):
|
||||
genLineDir(p, e)
|
||||
asgnFieldDiscriminant(p, e)
|
||||
elif p.config.usesSso() and e[0].kind == nkBracketExpr and
|
||||
e[0][0].typ.skipTypes(abstractVar).kind == tyString:
|
||||
# nimsso: s[i] = c → nimStrPutV3(&s, i, c) (handles COW internally)
|
||||
genLineDir(p, e)
|
||||
var base = initLocExpr(p, e[0][0])
|
||||
var idx = initLocExpr(p, e[0][1])
|
||||
var rhs = initLocExpr(p, e[1])
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimStrPutV3"),
|
||||
byRefLoc(p, base), rdLoc(idx), rdCharLoc(rhs))
|
||||
else:
|
||||
let le = e[0]
|
||||
let ri = e[1]
|
||||
@@ -1986,9 +1950,4 @@ proc genStmts(p: BProc, t: PNode) =
|
||||
if isPush: pushInfoContext(p.config, t.info)
|
||||
expr(p, t, a)
|
||||
if isPush: popInfoContext(p.config)
|
||||
# A bare `nkSym` statement is how IC serializes a definition that lives inside a
|
||||
# top-level block (e.g. a nested `proc`/`var`): codegen emits the definition and
|
||||
# leaves the symbol's own location in `a` (e.g. `locProc`), which is discarded
|
||||
# here, so the value-sanity check below does not apply to it.
|
||||
internalAssert p.config, t.kind == nkSym or
|
||||
a.k in {locNone, locTemp, locLocalVar, locExpr}
|
||||
internalAssert p.config, a.k in {locNone, locTemp, locLocalVar, locExpr}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
# ------------------------- Name Mangling --------------------------------
|
||||
|
||||
import sighashes, std/strscans
|
||||
import sighashes, modulegraphs, std/strscans
|
||||
import ../dist/checksums/src/checksums/md5
|
||||
import std/sequtils
|
||||
|
||||
@@ -72,67 +72,19 @@ 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
|
||||
if not m.compileToCpp and s.kind in routineKinds and optCDebug in m.g.config.globalOptions 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.loc.snippet = result
|
||||
|
||||
proc fillParamName(m: BModule; s: PSym) =
|
||||
if s.loc.snippet == "":
|
||||
@@ -155,8 +107,7 @@ proc fillParamName(m: BModule; s: PSym) =
|
||||
# and a function called in main or proxy uses `socket` as a parameter name.
|
||||
# That would lead to either needing to reload `proxy` or to overwrite the
|
||||
# executable file for the main module, which is running (or both!) -> error.
|
||||
backendEnsureMutable s
|
||||
s.locImpl.snippet = res.rope
|
||||
s.loc.snippet = res.rope
|
||||
|
||||
proc fillLocalName(p: BProc; s: PSym) =
|
||||
assert s.kind in skLocalVars+{skTemp}
|
||||
@@ -171,8 +122,7 @@ proc fillLocalName(p: BProc; s: PSym) =
|
||||
elif s.kind != skResult:
|
||||
result.add "_" & rope(counter+1)
|
||||
p.sigConflicts.inc(key)
|
||||
backendEnsureMutable s
|
||||
s.locImpl.snippet = result
|
||||
s.loc.snippet = result
|
||||
|
||||
proc scopeMangledParam(p: BProc; param: PSym) =
|
||||
## parameter generation only takes BModule, not a BProc, so we have to
|
||||
@@ -206,9 +156,8 @@ proc getTypeName(m: BModule; typ: PType; sig: SigHash): Rope =
|
||||
break
|
||||
let typ = if typ.kind in {tyAlias, tySink, tyOwned}: typ.elementType else: typ
|
||||
if typ.loc.snippet == "":
|
||||
backendEnsureMutable typ
|
||||
typ.typeName(typ.locImpl.snippet)
|
||||
typ.locImpl.snippet.add $sig
|
||||
typ.typeName(typ.loc.snippet)
|
||||
typ.loc.snippet.add $sig
|
||||
else:
|
||||
when defined(debugSigHashes):
|
||||
# check consistency:
|
||||
@@ -289,6 +238,9 @@ proc mapReturnType(conf: ConfigRef; typ: PType): TCTypeKind =
|
||||
proc isImportedType(t: PType): bool =
|
||||
result = t.sym != nil and sfImportc in t.sym.flags
|
||||
|
||||
proc isNoDeclType(t: PType): bool =
|
||||
result = t.sym != nil and {lfNoDecl, lfHeader} * t.sym.loc.flags != {}
|
||||
|
||||
proc isImportedCppType(t: PType): bool =
|
||||
let x = t.skipTypes(irrelevantForBackend)
|
||||
result = (t.sym != nil and sfInfixCall in t.sym.flags) or
|
||||
@@ -298,7 +250,6 @@ proc isOrHasImportedCppType(typ: PType): bool =
|
||||
searchTypeFor(typ.skipTypes({tyRef}), isImportedCppType)
|
||||
|
||||
proc hasNoInit(t: PType): bool =
|
||||
let t = skipTypes(t, {tyGenericInst})
|
||||
result = t.sym != nil and sfNoInit in t.sym.flags
|
||||
|
||||
proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDescKind): Rope
|
||||
@@ -324,7 +275,7 @@ proc isInvalidReturnType(conf: ConfigRef; typ: PType, isProc = true): bool =
|
||||
of ctStruct:
|
||||
let t = skipTypes(rettype, typedescInst)
|
||||
if rettype.isImportedCppType or t.isImportedCppType or
|
||||
(typ.callConv == ccCDecl and conf.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}):
|
||||
(typ.callConv == ccCDecl and conf.selectedGC in {gcArc, gcAtomicArc, gcOrc}):
|
||||
# prevents nrvo for cdecl procs; # bug #23401
|
||||
result = false
|
||||
else:
|
||||
@@ -341,12 +292,7 @@ proc cacheGetType(tab: TypeCache; sig: SigHash): Rope =
|
||||
result = tab.getOrDefault(sig)
|
||||
|
||||
proc addAbiCheck(m: BModule; t: PType, name: Rope) =
|
||||
if isDefined(m.config, "checkAbi") and (let size = getSize(m.config, t); size != szUnknownSize) and
|
||||
not (t.kind == tyObject and searchTypeFor(t, proc (t: PType): bool {.nimcall.} = t.kind == tyUncheckedArray)):
|
||||
# `UncheckedArray`, not `ptr UncheckedArray` type field in object types is a flexible array.
|
||||
# `sizeof` in C and Nim doesn't always return the same value for object types containing it.
|
||||
# making `getSize` in Nim always returns the same value as `sizeof` in C from flexible arrays seems hard.
|
||||
# See `SEQ_DECL_SIZE` in lib/nimbase.h
|
||||
if isDefined(m.config, "checkAbi") and (let size = getSize(m.config, t); size != szUnknownSize):
|
||||
var msg = "backend & Nim disagree on size for: "
|
||||
msg.addTypeHeader(m.config, t)
|
||||
var msg2 = ""
|
||||
@@ -356,13 +302,12 @@ proc addAbiCheck(m: BModule; t: PType, name: Rope) =
|
||||
|
||||
|
||||
proc fillResult(conf: ConfigRef; param: PNode, proctype: PType) =
|
||||
backendEnsureMutable param.sym
|
||||
fillLoc(param.sym.locImpl, locParam, param, "Result",
|
||||
fillLoc(param.sym.loc, locParam, param, "Result",
|
||||
OnStack)
|
||||
let t = param.sym.typ
|
||||
if mapReturnType(conf, t) != ctArray and isInvalidReturnType(conf, proctype):
|
||||
incl(param.sym.locImpl.flags, lfIndirect)
|
||||
param.sym.locImpl.storage = OnUnknown
|
||||
incl(param.sym.loc.flags, lfIndirect)
|
||||
param.sym.loc.storage = OnUnknown
|
||||
|
||||
proc typeNameOrLiteral(m: BModule; t: PType, literal: string): Rope =
|
||||
if t.sym != nil and sfImportc in t.sym.flags and t.sym.magic == mNone:
|
||||
@@ -386,10 +331,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 +361,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
|
||||
@@ -458,7 +393,7 @@ proc getTypeForward(m: BModule; typ: PType; sig: SigHash): Rope =
|
||||
of tySequence, tyTuple, tyObject:
|
||||
result = getTypeName(m, typ, sig)
|
||||
m.forwTypeCache[sig] = result
|
||||
if not isImportedType(concrete):
|
||||
if not isNoDeclType(concrete):
|
||||
addForwardStructFormat(m, structOrUnion(typ), result)
|
||||
else:
|
||||
pushType(m, concrete)
|
||||
@@ -510,14 +445,6 @@ 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)
|
||||
@@ -599,15 +526,14 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
|
||||
var types, names, args: seq[string] = @[]
|
||||
if not isCtor:
|
||||
var this = t.n[1].sym
|
||||
backendEnsureMutable this
|
||||
fillParamName(m, this)
|
||||
fillLoc(this.locImpl, locParam, t.n[1],
|
||||
fillLoc(this.loc, locParam, t.n[1],
|
||||
this.paramStorageLoc)
|
||||
if this.typ.kind == tyPtr:
|
||||
this.locImpl.snippet = "this"
|
||||
this.loc.snippet = "this"
|
||||
else:
|
||||
this.locImpl.snippet = "(*this)"
|
||||
names.add this.locImpl.snippet
|
||||
this.loc.snippet = "(*this)"
|
||||
names.add this.loc.snippet
|
||||
types.add getTypeDescWeak(m, this.typ, check, dkParam)
|
||||
|
||||
let firstParam = if isCtor: 1 else: 2
|
||||
@@ -621,14 +547,13 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
|
||||
else:
|
||||
descKind = dkRefParam
|
||||
var typ, name: string
|
||||
backendEnsureMutable param
|
||||
fillParamName(m, param)
|
||||
fillLoc(param.locImpl, locParam, t.n[i],
|
||||
fillLoc(param.loc, locParam, t.n[i],
|
||||
param.paramStorageLoc)
|
||||
if ccgIntroducedPtr(m.config, param, t.returnType) and descKind == dkParam:
|
||||
typ = getTypeDescWeak(m, param.typ, check, descKind) & "*"
|
||||
incl(param.locImpl.flags, lfIndirect)
|
||||
param.locImpl.storage = OnUnknown
|
||||
incl(param.loc.flags, lfIndirect)
|
||||
param.loc.storage = OnUnknown
|
||||
elif weakDep:
|
||||
typ = getTypeDescWeak(m, param.typ, check, descKind)
|
||||
else:
|
||||
@@ -636,7 +561,7 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params
|
||||
if sfNoalias in param.flags:
|
||||
typ.add("NIM_NOALIAS ")
|
||||
|
||||
name = param.locImpl.snippet
|
||||
name = param.loc.snippet
|
||||
types.add typ
|
||||
names.add name
|
||||
if sfCodegenDecl notin param.flags:
|
||||
@@ -671,18 +596,6 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
|
||||
for i in 1..<t.n.len:
|
||||
if t.n[i].kind != nkSym: internalError(m.config, t.n.info, "genProcParams")
|
||||
var param = t.n[i].sym
|
||||
# The hidden closure environment param (`:envP`) is not a real C parameter:
|
||||
# the environment is passed via the trailing `ClE_0` (added below) and
|
||||
# `closureSetup` materialises `:envP` as a local cast of it. In a from-source
|
||||
# build `:envP` only lives in the routine's AST params, never in the proc
|
||||
# *type's* `n`, so it never reaches here. Under IC `closureParams` re-shares
|
||||
# the AST param node with `typ.n`, so the lifted `:envP` leaks into `t.n`;
|
||||
# emitting it would produce a bogus extra parameter that collides with the
|
||||
# `closureSetup` local (the "redeclared as different kind of symbol" / env
|
||||
# pointer-type mismatch). We still must fill its name/loc (later passes such
|
||||
# as `assignParam` and `closureSetup` reference it), but it is omitted from
|
||||
# the C signature to match the from-source ABI.
|
||||
let isClosureEnv = t.callConv == ccClosure and param.name.s == ":envP"
|
||||
var descKind = dkParam
|
||||
if m.config.backend == backendCpp and optByRef in param.options:
|
||||
if param.typ.kind == tyGenericInst:
|
||||
@@ -690,16 +603,14 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
|
||||
else:
|
||||
descKind = dkRefParam
|
||||
if isCompileTimeOnly(param.typ): continue
|
||||
backendEnsureMutable param
|
||||
fillParamName(m, param)
|
||||
fillLoc(param.locImpl, locParam, t.n[i],
|
||||
fillLoc(param.loc, 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))
|
||||
incl(param.locImpl.flags, lfIndirect)
|
||||
param.locImpl.storage = OnUnknown
|
||||
incl(param.loc.flags, lfIndirect)
|
||||
param.loc.storage = OnUnknown
|
||||
elif weakDep:
|
||||
typ = (getTypeDescWeak(m, param.typ, check, descKind))
|
||||
else:
|
||||
@@ -711,9 +622,9 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder,
|
||||
var j = 0
|
||||
while arr.kind in {tyOpenArray, tyVarargs}:
|
||||
# this fixes the 'sort' bug:
|
||||
if param.typ.kind in {tyVar, tyLent}: param.locImpl.storage = OnUnknown
|
||||
if param.typ.kind in {tyVar, tyLent}: param.loc.storage = OnUnknown
|
||||
# need to pass hidden parameter:
|
||||
params.addParam(paramBuilder, name = param.locImpl.snippet & "Len_" & $j, typ = NimInt)
|
||||
params.addParam(paramBuilder, name = param.loc.snippet & "Len_" & $j, typ = NimInt)
|
||||
inc(j)
|
||||
arr = arr[0].skipTypes({tySink})
|
||||
if t.returnType != nil and isInvalidReturnType(m.config, t):
|
||||
@@ -798,13 +709,12 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
|
||||
if field.typ.kind == tyVoid: return
|
||||
#assert(field.ast == nil)
|
||||
let sname = mangleRecFieldName(m, field)
|
||||
backendEnsureMutable field
|
||||
fillLoc(field.locImpl, locField, n, unionPrefix & sname, OnUnknown)
|
||||
fillLoc(field.loc, locField, n, unionPrefix & sname, OnUnknown)
|
||||
# for importcpp'ed objects, we only need to set field.loc, but don't
|
||||
# have to recurse via 'getTypeDescAux'. And not doing so prevents problems
|
||||
# with heavily templatized C++ code:
|
||||
if not isImportedCppType(rectype):
|
||||
let fieldType = field.loc.t.skipTypes(abstractInst)
|
||||
let fieldType = field.loc.lode.typ.skipTypes(abstractInst)
|
||||
var typ: Rope = ""
|
||||
var isFlexArray = false
|
||||
var initializer = ""
|
||||
@@ -819,11 +729,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)
|
||||
@@ -882,8 +788,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))
|
||||
@@ -933,54 +837,6 @@ proc getOpenArrayDesc(m: BModule; t: PType, check: var IntSet; kind: TypeDescKin
|
||||
m.s[cfsTypes].addField(name = "Field0", typ = ptrType(elemType))
|
||||
m.s[cfsTypes].addField(name = "Field1", typ = NimInt)
|
||||
|
||||
proc importedCppObject(m: BModule; t, tt: PType; check: var IntSet; kind: TypeDescKind; sig: SigHash; result: var Rope) =
|
||||
let cppNameAsRope = getTypeName(m, t, sig)
|
||||
let cppName = $cppNameAsRope
|
||||
var i = 0
|
||||
var chunkStart = 0
|
||||
|
||||
template addResultType(ty: untyped) =
|
||||
if ty == nil or ty.kind == tyVoid:
|
||||
result.add(CVoid)
|
||||
elif ty.kind == tyStatic:
|
||||
internalAssert m.config, ty.n != nil
|
||||
result.add ty.n.renderTree
|
||||
else:
|
||||
result.add getTypeDescAux(m, ty, check, kind)
|
||||
|
||||
while i < cppName.len:
|
||||
if cppName[i] == '\'':
|
||||
var chunkEnd = i-1
|
||||
var idx, stars: int = 0
|
||||
if scanCppGenericSlot(cppName, i, idx, stars):
|
||||
result.add cppName.substr(chunkStart, chunkEnd)
|
||||
chunkStart = i
|
||||
|
||||
let typeInSlot = resolveStarsInCppType(tt, idx + 1, stars)
|
||||
addResultType(typeInSlot)
|
||||
else:
|
||||
inc i
|
||||
|
||||
if chunkStart != 0:
|
||||
result.add cppName.substr(chunkStart)
|
||||
else:
|
||||
result = cppNameAsRope & "<"
|
||||
for needsComma, a in tt.genericInstParams:
|
||||
if needsComma: result.add(" COMMA ")
|
||||
addResultType(a)
|
||||
result.add("> ")
|
||||
# always call for sideeffects:
|
||||
assert t.kind != tyTuple
|
||||
discard getRecordDesc(m, t, result, check)
|
||||
# The resulting type will include commas and these won't play well
|
||||
# with the C macros for defining procs such as N_NIMCALL. We must
|
||||
# create a typedef for the type and use it in the proc signature:
|
||||
let typedefName = "TY" & $sig
|
||||
m.s[cfsTypes].addTypedef(name = typedefName):
|
||||
m.s[cfsTypes].add(result)
|
||||
m.typeCache[sig] = typedefName
|
||||
result = typedefName
|
||||
|
||||
proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDescKind): Rope =
|
||||
# returns only the type's name
|
||||
var t = origTyp.skipTypes(irrelevantForBackend-{tyOwned})
|
||||
@@ -996,7 +852,7 @@ proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDes
|
||||
|
||||
# tyDistinct matters if it is an importc type
|
||||
result = getTypePre(m, origTyp.skipTypes(irrelevantForBackend-{tyOwned, tyDistinct}), sig)
|
||||
defer:
|
||||
defer: # defer is the simplest in this case
|
||||
if isImportedType(t) and not m.typeABICache.containsOrIncl(sig):
|
||||
addAbiCheck(m, t, result)
|
||||
|
||||
@@ -1130,7 +986,7 @@ proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDes
|
||||
m.s[cfsTypes].addArrayTypedef(name = result, len = 1):
|
||||
m.s[cfsTypes].add(et)
|
||||
of tyArray:
|
||||
var n = toInt64(lengthOrd(m.config, t))
|
||||
var n: BiggestInt = toInt64(lengthOrd(m.config, t))
|
||||
if n <= 0: n = 1 # make an array of at least one element
|
||||
result = getTypeName(m, origTyp, sig)
|
||||
m.typeCache[sig] = result
|
||||
@@ -1141,22 +997,66 @@ proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDes
|
||||
of tyObject, tyTuple:
|
||||
let tt = origTyp.skipTypes({tyDistinct})
|
||||
if isImportedCppType(t) and tt.kind == tyGenericInst:
|
||||
importedCppObject(m, t, tt, check, kind, sig, result)
|
||||
let cppNameAsRope = getTypeName(m, t, sig)
|
||||
let cppName = $cppNameAsRope
|
||||
var i = 0
|
||||
var chunkStart = 0
|
||||
|
||||
template addResultType(ty: untyped) =
|
||||
if ty == nil or ty.kind == tyVoid:
|
||||
result.add(CVoid)
|
||||
elif ty.kind == tyStatic:
|
||||
internalAssert m.config, ty.n != nil
|
||||
result.add ty.n.renderTree
|
||||
else:
|
||||
result.add getTypeDescAux(m, ty, check, kind)
|
||||
|
||||
while i < cppName.len:
|
||||
if cppName[i] == '\'':
|
||||
var chunkEnd = i-1
|
||||
var idx, stars: int = 0
|
||||
if scanCppGenericSlot(cppName, i, idx, stars):
|
||||
result.add cppName.substr(chunkStart, chunkEnd)
|
||||
chunkStart = i
|
||||
|
||||
let typeInSlot = resolveStarsInCppType(tt, idx + 1, stars)
|
||||
addResultType(typeInSlot)
|
||||
else:
|
||||
inc i
|
||||
|
||||
if chunkStart != 0:
|
||||
result.add cppName.substr(chunkStart)
|
||||
else:
|
||||
result = cppNameAsRope & "<"
|
||||
for needsComma, a in tt.genericInstParams:
|
||||
if needsComma: result.add(" COMMA ")
|
||||
addResultType(a)
|
||||
result.add("> ")
|
||||
# always call for sideeffects:
|
||||
assert t.kind != tyTuple
|
||||
discard getRecordDesc(m, t, result, check)
|
||||
# The resulting type will include commas and these won't play well
|
||||
# with the C macros for defining procs such as N_NIMCALL. We must
|
||||
# create a typedef for the type and use it in the proc signature:
|
||||
let typedefName = "TY" & $sig
|
||||
m.s[cfsTypes].addTypedef(name = typedefName):
|
||||
m.s[cfsTypes].add(result)
|
||||
m.typeCache[sig] = typedefName
|
||||
result = typedefName
|
||||
else:
|
||||
result = cacheGetType(m.forwTypeCache, sig)
|
||||
if result == "":
|
||||
result = getTypeName(m, origTyp, sig)
|
||||
m.forwTypeCache[sig] = result
|
||||
if not isImportedType(t):
|
||||
if not isNoDeclType(t):
|
||||
addForwardStructFormat(m, structOrUnion(t), result)
|
||||
assert m.forwTypeCache[sig] == result
|
||||
m.typeCache[sig] = result # always call for sideeffects:
|
||||
if not incompleteType(t):
|
||||
let recdesc = if t.kind != tyTuple: getRecordDesc(m, t, result, check)
|
||||
else: getTupleDesc(m, t, result, check)
|
||||
if not isImportedType(t):
|
||||
if not isImportedType(t) and not isNoDeclType(t):
|
||||
m.s[cfsTypes].add(recdesc)
|
||||
addAbiCheck(m, t, result)
|
||||
elif tfIncompleteStruct notin t.flags:
|
||||
discard # addAbiCheck(m, t, result) # already handled elsewhere
|
||||
of tySet:
|
||||
@@ -1178,11 +1078,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 +1116,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 +1157,7 @@ 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, prc.ast[namePos], OnUnknown)
|
||||
fillLoc(prc.loc, locProc, prc.ast[namePos], OnUnknown)
|
||||
var memberOp = "#." #only virtual
|
||||
var typ: PType
|
||||
if isCtor:
|
||||
@@ -1299,7 +1189,7 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
|
||||
superCall = ""
|
||||
else:
|
||||
if not isCtor:
|
||||
prc.locImpl.snippet = "$1$2(@)" % [memberOp, name]
|
||||
prc.loc.snippet = "$1$2(@)" % [memberOp, name]
|
||||
elif superCall != "":
|
||||
superCall = " : " & superCall
|
||||
|
||||
@@ -1314,15 +1204,14 @@ proc genProcHeader(m: BModule; prc: PSym; result: var Builder; visibility: var D
|
||||
# using static is needed for inline procs
|
||||
var check = initIntSet()
|
||||
fillBackendName(m, prc)
|
||||
backendEnsureMutable prc
|
||||
fillLoc(prc.locImpl, locProc, prc.ast[namePos], OnUnknown)
|
||||
fillLoc(prc.loc, locProc, prc.ast[namePos], OnUnknown)
|
||||
var rettype: Snippet = ""
|
||||
var desc = newBuilder("")
|
||||
genProcParams(m, prc.typ, rettype, desc, check, true, false)
|
||||
let params = extract(desc)
|
||||
# handle the 2 options for hotcodereloading codegen - function pointer
|
||||
# (instead of forward declaration) or header for function body with "_actual" postfix
|
||||
var name = prc.locImpl.snippet
|
||||
var name = prc.loc.snippet
|
||||
if not asPtr and isReloadable(m, prc):
|
||||
name.add("_actual")
|
||||
# careful here! don't access ``prc.ast`` as that could reload large parts of
|
||||
@@ -1339,9 +1228,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:
|
||||
@@ -1419,24 +1306,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) =
|
||||
@@ -1529,25 +1400,8 @@ 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)
|
||||
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)
|
||||
@@ -1597,41 +1451,30 @@ proc genObjectInfo(m: BModule; typ, origType: PType, name: Rope; info: TLineInfo
|
||||
var t = typ.baseClass
|
||||
while t != nil:
|
||||
t = t.skipTypes(skipPtrs)
|
||||
t.incl tfObjHasKids
|
||||
t.flags.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))
|
||||
|
||||
@@ -1741,13 +1584,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
|
||||
@@ -1809,18 +1647,8 @@ proc generateRttiDestructor(g: ModuleGraph; typ: PType; owner: PSym; kind: TType
|
||||
n[bodyPos] = body
|
||||
result.ast = n
|
||||
|
||||
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)
|
||||
incl result.flags, sfFromGeneric
|
||||
incl result.flags, sfGeneratedOp
|
||||
|
||||
proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp; result: var Builder) =
|
||||
let theProc = getAttachedOp(m.g.graph, t, op)
|
||||
@@ -1846,7 +1674,7 @@ proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp; result:
|
||||
echo "ayclic but has this =trace ", t, " ", theProc.ast
|
||||
else:
|
||||
when false:
|
||||
if op == attachedTrace and m.config.selectedGC in {gcOrc, gcYrc} and
|
||||
if op == attachedTrace and m.config.selectedGC == gcOrc and
|
||||
containsGarbageCollectedRef(t):
|
||||
# unfortunately this check is wrong for an object type that only contains
|
||||
# .cursor fields like 'Node' inside 'cycleleak'.
|
||||
@@ -1896,8 +1724,6 @@ proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLin
|
||||
cgsym(m, "TNimTypeV2")
|
||||
m.s[cfsStrData].addDeclWithVisibility(Private):
|
||||
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
|
||||
if m.config.cmd == cmdNifC:
|
||||
m.icDataDefs.add (name, icNifName(m, origType))
|
||||
|
||||
var flags = 0
|
||||
if not canFormAcycle(m.g.graph, t): flags = flags or 1
|
||||
@@ -1960,15 +1786,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
|
||||
@@ -2029,22 +1848,11 @@ 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)
|
||||
|
||||
proc myModuleOpenForCodegen(m: BModule; idx: FileIndex): bool {.inline.} =
|
||||
if moduleOpenForCodegen(m.g.graph, idx):
|
||||
result = idx.int < m.g.mods.len and m.g.mods[idx.int] != nil
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
let origType = t
|
||||
# distinct types can have their own destructors
|
||||
@@ -2073,15 +1881,9 @@ proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
m.typeInfoMarkerV2[sig] = result
|
||||
|
||||
let owner = t.skipTypes(typedescPtrs).itemId.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):
|
||||
if owner != m.module.position and moduleOpenForCodegen(m.g.graph, FileIndex owner):
|
||||
# make sure the type info is created in the owner module
|
||||
discard genTypeInfoV2(m.g.mods[owner], origType, info)
|
||||
discard genTypeInfoV2(m.g.modules[owner], origType, info)
|
||||
# reference the type info as extern here
|
||||
cgsym(m, "TNimTypeV2")
|
||||
declareNimType(m, "TNimTypeV2", result, owner)
|
||||
@@ -2146,10 +1948,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)
|
||||
@@ -2160,34 +1958,17 @@ 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).itemId.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"
|
||||
if owner != m.module.position and moduleOpenForCodegen(m.g.graph, FileIndex owner):
|
||||
# make sure the type info is created in the owner module
|
||||
discard genTypeInfoV1(m.g.mods[owner], origType, info)
|
||||
discard genTypeInfoV1(m.g.modules[owner], origType, info)
|
||||
# reference the type info as extern here
|
||||
cgsym(m, "TNimType")
|
||||
cgsym(m, "TNimNode")
|
||||
@@ -2196,9 +1977,8 @@ 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)
|
||||
rememberEmittedTypeInfo(m.g.graph, FileIndex(owner), $result)
|
||||
|
||||
case t.kind
|
||||
of tyEmpty, tyVoid: result = cIntValue(0)
|
||||
@@ -2265,39 +2045,17 @@ proc genTypeInfo*(config: ConfigRef, m: BModule; t: PType; info: TLineInfo): Rop
|
||||
else:
|
||||
result = genTypeInfoV1(m, t, info)
|
||||
|
||||
proc retrieveSym(n: PNode): PSym =
|
||||
case n.kind
|
||||
of nkPostfix: result = retrieveSym(n[1])
|
||||
of nkPragmaExpr, nkTypeDef: result = retrieveSym(n[0])
|
||||
of nkSym: result = n.sym
|
||||
else: result = nil
|
||||
|
||||
proc genTypeSection(m: BModule, n: PNode) =
|
||||
var intSet = initIntSet()
|
||||
let compress = optCompress in m.config.globalOptions
|
||||
for typedef in n:
|
||||
let s = retrieveSym(typedef)
|
||||
if s != nil and ({sfExportc, sfCompilerProc} * s.flags == {sfExportc} or compress) and s.typ != nil and
|
||||
not containsGenericType(s.typ) and
|
||||
s.typ.kind notin {tyVoid, tyNot, tyAnything, tyOr, tyAnd, tyUntyped, tyTyped, tyNone, tyNil, tySink}:
|
||||
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.itemId in m.g.graph.initializersPerType:
|
||||
let call = m.g.graph.initializersPerType[typ.itemId]
|
||||
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 & ")"
|
||||
for i in 0..<n.len:
|
||||
if len(n[i]) == 0: continue
|
||||
if n[i][0].kind != nkPragmaExpr: continue
|
||||
for p in 0..<n[i][0].len:
|
||||
if (n[i][0][p].kind notin {nkSym, nkPostfix}): continue
|
||||
var s = n[i][0][p]
|
||||
if s.kind == nkPostfix:
|
||||
s = n[i][0][p][1]
|
||||
if {sfExportc, sfCompilerProc} * s.sym.flags == {sfExportc}:
|
||||
discard getTypeDescAux(m, s.typ, intSet, descKindFromSymKind(s.sym.kind))
|
||||
if m.g.generatedHeader != nil:
|
||||
discard getTypeDescAux(m.g.generatedHeader, s.typ, intSet, descKindFromSymKind(s.sym.kind))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -112,34 +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
|
||||
if s.itemId.isBackendMinted:
|
||||
result.add "_c"
|
||||
if (s.disamb and HookDisambBit) != 0'i32:
|
||||
# A backend-minted sym whose `disamb` is content-derived (setHookDisamb gave
|
||||
# it HookDisambBit) — e.g. the `rttiDestroy` wrapper. Its `itemId.item` is a
|
||||
# PER-PROCESS backend counter, so using it makes the C name diverge across
|
||||
# the emit-everywhere processes: the type's RTTI table (emit-everywhere,
|
||||
# merge-deduped) ends up referencing one process's `_c<item>` while the
|
||||
# wrapper is defined with another's -> undefined at link (`rttiDestroy_c23`).
|
||||
# The content-derived disamb is stable across processes, so use it.
|
||||
result.add $s.disamb
|
||||
else:
|
||||
result.add $s.itemId.item
|
||||
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
|
||||
|
||||
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
|
||||
@@ -120,9 +117,9 @@ type
|
||||
BModuleList* = ref object of RootObj
|
||||
mainModProcs*, mainModInit*, otherModsInit*, mainDatInit*: Builder
|
||||
mapping*: Rope # the generated mapping file (if requested)
|
||||
mods*: seq[BModule] # list of all compiled modules
|
||||
modules*: seq[BModule] # list of all compiled modules
|
||||
modulesClosed*: seq[BModule] # list of the same compiled modules, but in the order they were closed
|
||||
forwardedProcs*: seq[PSym] # procs that did not yet have a body
|
||||
forwardedProcs*: seq[PSym] # proc:s that did not yet have a body
|
||||
generatedHeader*: BModule
|
||||
typeInfoMarker*: TypeCacheWithOwner
|
||||
typeInfoMarkerV2*: TypeCacheWithOwner
|
||||
@@ -158,13 +155,6 @@ type
|
||||
forwTypeCache*: TypeCache # cache for forward declarations of types
|
||||
declaredThings*: IntSet # things we have declared in this .c file
|
||||
declaredProtos*: IntSet # prototypes we have declared in this .c file
|
||||
emittedContentDefs*: HashSet[string]
|
||||
# cmdNifC per-module backend: content-addressed C names (generic
|
||||
# instances and synthesized hooks) whose body this TU already emitted.
|
||||
# Distinct symbols (minted in different source modules) can share one
|
||||
# `_i<disamb>` name; `declaredThings` keys on symbol id and lets the
|
||||
# second one through, so we dedup the body by name here instead.
|
||||
queue*: seq[PSym] # queue of procs to generate
|
||||
alive*: IntSet # symbol IDs of alive data as computed by `dce.nim`
|
||||
headerFiles*: seq[string] # needed headers to include
|
||||
typeInfoMarker*: TypeCache # needed for generating type information
|
||||
@@ -182,26 +172,12 @@ type
|
||||
extensionLoaders*: array['0'..'9', Builder] # special procs for the
|
||||
# OpenGL wrapper
|
||||
sigConflicts*: CountTable[SigHash]
|
||||
icImplMods*: IntSet # module ids whose routine BODIES this TU
|
||||
# embeds (redirected defs, shared instances,
|
||||
# hooks); recorded as the artifact's cdeps so
|
||||
# the reuse gate can check their impl cookies
|
||||
icDataDefs*: seq[tuple[cname, nifname: string]]
|
||||
# C names of data definitions (consts, globals,
|
||||
# RTTI) this TU embeds plus their NIF symbol
|
||||
# names (empty for RTTI, which has no symbol);
|
||||
# recorded in the cnif artifact so a later run
|
||||
# can reuse the TU and re-demand definitions
|
||||
# that cached TUs still reference
|
||||
g*: BModuleList
|
||||
|
||||
template config*(m: BModule): ConfigRef = m.g.config
|
||||
template config*(p: BProc): ConfigRef = p.module.g.config
|
||||
template vccAndC*(p: BProc): bool = p.module.config.cCompiler == ccVcc and p.module.config.backend == backendC
|
||||
|
||||
proc delayedCodegen*(m: BModule): bool {.inline.} =
|
||||
useAliveDataFromDce in m.flags or m.config.globalOptions.contains(optCompress)
|
||||
|
||||
proc includeHeader*(this: BModule; header: string) =
|
||||
if not this.headerFiles.contains header:
|
||||
this.headerFiles.add header
|
||||
|
||||
@@ -55,7 +55,7 @@ proc methodCall*(n: PNode; conf: ConfigRef): PNode =
|
||||
# replace ordinary method by dispatcher method:
|
||||
let disp = getDispatcher(result[0].sym)
|
||||
if disp != nil:
|
||||
result[0].typ = disp.typ
|
||||
result[0].typ() = disp.typ
|
||||
result[0].sym = disp
|
||||
# change the arguments to up/downcasts to fit the dispatcher's parameters:
|
||||
for i in 1..<result.len:
|
||||
@@ -123,8 +123,8 @@ proc attachDispatcher(s: PSym, dispatcher: PNode) =
|
||||
|
||||
proc createDispatcher(s: PSym; g: ModuleGraph; idgen: IdGenerator): PSym =
|
||||
var disp = copySym(s, idgen)
|
||||
incl(disp, sfDispatcher)
|
||||
excl(disp, sfExported)
|
||||
incl(disp.flags, sfDispatcher)
|
||||
excl(disp.flags, sfExported)
|
||||
let old = disp.typ
|
||||
disp.typ = copyType(disp.typ, idgen, disp.typ.owner)
|
||||
copyTypeProps(g, idgen.module, disp.typ, old)
|
||||
@@ -133,7 +133,7 @@ proc createDispatcher(s: PSym; g: ModuleGraph; idgen: IdGenerator): PSym =
|
||||
if disp.typ.callConv == ccInline: disp.typ.callConv = ccNimCall
|
||||
disp.ast = copyTree(s.ast)
|
||||
disp.ast[bodyPos] = newNodeI(nkEmpty, s.info)
|
||||
disp.locImpl.snippet = ""
|
||||
disp.loc.snippet = ""
|
||||
if s.typ.returnType != nil:
|
||||
if disp.ast.len > resultPos:
|
||||
disp.ast[resultPos].sym = copySym(s.ast[resultPos].sym, idgen)
|
||||
@@ -160,17 +160,7 @@ proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
|
||||
proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
var witness: PSym = nil
|
||||
if s.typ.firstParamType.owner.getModule != s.getModule and vtables in g.config.features and not
|
||||
g.config.isDefined("nimInternalNonVtablesTesting") and sfFromGeneric notin s.flags:
|
||||
# `sfFromGeneric` excepted: this is the same-module restriction for vtable
|
||||
# slot placement, and it must be judged on the GENERIC method, not on an
|
||||
# instance. The generic `method skip[T](x: Input[T])` never reaches here
|
||||
# (`semMethodPrototype` registers generic methods via `addMethodToGeneric`,
|
||||
# bypassing `methodDef`); only its instance `skip[string]` does, and that
|
||||
# instance's first-param type `Input[string]` is owned by whichever module
|
||||
# first instantiated it (`tparsecombnum`, which `import parsecomb`s and uses
|
||||
# it), NOT by `Input[T]`'s defining module — so the comparison spuriously
|
||||
# fails for a method that is perfectly legal at the generic level. (Concrete
|
||||
# methods, `sfFromGeneric notin flags`, are still checked.)
|
||||
g.config.isDefined("nimInternalNonVtablesTesting"):
|
||||
localError(g.config, s.info, errGenerated, "method `" & s.name.s &
|
||||
"` can be defined only in the same module with its type (" & s.typ.firstParamType.typeToString() & ")")
|
||||
if sfImportc in s.flags:
|
||||
@@ -190,7 +180,6 @@ 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:
|
||||
@@ -202,7 +191,6 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
else:
|
||||
g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).itemId)
|
||||
g.methods.add((methods: @[s], dispatcher: createDispatcher(s, g, idgen)))
|
||||
logMethodDef(g, s)
|
||||
#echo "adding ", s.info
|
||||
if witness != nil:
|
||||
localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s &
|
||||
|
||||
@@ -64,8 +64,7 @@
|
||||
# the target state is `except` block. For all states in `except` block
|
||||
# the target state is `finally` block. For all other states there is no
|
||||
# target state (0, as the first state can never be except nor finally).
|
||||
# - env var :curExc is created, where "current" exception within the iterator is stored,
|
||||
# also finallies use it to decide their exit logic
|
||||
# - env var :curExcLevel is created, finallies use it to decide their exit logic
|
||||
# - if there are finallies, env var :finallyPath is created. It contains exit state labels
|
||||
# for every finally level, and is changed in runtime in try, except, break, and return
|
||||
# nodes to control finally exit behavior.
|
||||
@@ -112,6 +111,7 @@
|
||||
# :state = 2 # And we continue to our finally
|
||||
# break :stateLoop
|
||||
# of 1: # Except
|
||||
# inc(:curExcLevel, -1) # Exception is caught
|
||||
# yield 1
|
||||
# :tmpResult = 3 # Return
|
||||
# :finalyPath[LEVEL] = 0 # Configure finally path.
|
||||
@@ -123,7 +123,7 @@
|
||||
# of 2: # Finally
|
||||
# yield 2
|
||||
# if :finallyPath[LEVEL] == 0: # This node is created by `newEndFinallyNode`
|
||||
# if :curExc == nil:
|
||||
# if :curExcLevel == 0:
|
||||
# :state = -1
|
||||
# return result = :tmpResult
|
||||
# else:
|
||||
@@ -139,7 +139,8 @@
|
||||
|
||||
import
|
||||
ast, msgs, idents,
|
||||
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos, trees
|
||||
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos,
|
||||
options
|
||||
|
||||
import std/tables
|
||||
|
||||
@@ -164,10 +165,7 @@ type
|
||||
fn: PSym
|
||||
tmpResultSym: PSym # Used when we return, but finally has to interfere
|
||||
finallyPathSym: PSym
|
||||
curExcSym: PSym # Current exception
|
||||
externExcSym: PSym # Extern exception: what would getCurrentException() return outside of closure iter
|
||||
|
||||
enclosingPragmas: seq[PNode] # stack of pragma blocks wrapping stmtlist
|
||||
curExcLevelSym: PSym # Current exception level (because exceptions are stacked)
|
||||
|
||||
states: seq[State] # The resulting states. Label is int literal.
|
||||
finallyPathStack: seq[FinallyTarget] # Stack of split blocks, whiles and finallies
|
||||
@@ -180,9 +178,6 @@ type
|
||||
varStates: Table[ItemId, int] # Used to detect if local variable belongs to multiple states
|
||||
finallyPathLen: PNode # int literal
|
||||
|
||||
nullifyCurExc: PNode # Empty node, if no yields in tries
|
||||
restoreExternExc: PNode # Empty node, id no yields in tries
|
||||
|
||||
const
|
||||
nkSkip = {nkEmpty..nkNilLit, nkTemplateDef, nkTypeSection, nkStaticStmt,
|
||||
nkCommentStmt, nkMixinStmt, nkBindStmt, nkTypeOfExpr} + procDefs
|
||||
@@ -201,7 +196,7 @@ proc newStateAssgn(ctx: var Ctx, toValue: PNode): PNode =
|
||||
proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym =
|
||||
result = newSym(skVar, getIdent(ctx.g.cache, name), ctx.idgen, ctx.fn, ctx.fn.info)
|
||||
result.typ = typ
|
||||
result.flagsImpl.incl sfNoInit
|
||||
result.flags.incl sfNoInit
|
||||
assert(not typ.isNil, "Env var needs a type")
|
||||
|
||||
let envParam = getEnvParam(ctx.fn)
|
||||
@@ -247,15 +242,13 @@ proc newFinallyPathAssign(ctx: var Ctx, level: int, label: PNode, info: TLineInf
|
||||
let fp = newFinallyPathAccess(ctx, level, info)
|
||||
result = newTree(nkAsgn, fp, label)
|
||||
|
||||
proc newCurExcAccess(ctx: var Ctx): PNode =
|
||||
if ctx.curExcSym.isNil:
|
||||
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
|
||||
ctx.curExcSym = ctx.newEnvVar(":curExc", getCurExc.typ)
|
||||
ctx.newEnvVarAccess(ctx.curExcSym)
|
||||
proc newCurExcLevelAccess(ctx: var Ctx): PNode =
|
||||
if ctx.curExcLevelSym.isNil:
|
||||
ctx.curExcLevelSym = ctx.newEnvVar(":curExcLevel", ctx.g.getSysType(ctx.fn.info, tyInt16))
|
||||
ctx.newEnvVarAccess(ctx.curExcLevelSym)
|
||||
|
||||
proc newStateLabel(ctx: Ctx): PNode =
|
||||
result = nkIntLit.newIntNode(0)
|
||||
result.typ = getSysType(ctx.g, TLineInfo(), tyInt16)
|
||||
ctx.g.newIntLit(TLineInfo(), 0)
|
||||
|
||||
proc newState(ctx: var Ctx, n: PNode, inlinable: bool, label: PNode): PNode =
|
||||
# Creates a new state, adds it to the context
|
||||
@@ -291,15 +284,6 @@ proc newTempVar(ctx: var Ctx, typ: PType, parent: PNode, initialValue: PNode = n
|
||||
assert(not typ.isNil, "Temp var needs a type")
|
||||
parent.add(ctx.newTempVarDef(result, initialValue))
|
||||
|
||||
proc newExternExcAccess(ctx: var Ctx): PNode =
|
||||
if ctx.externExcSym == nil:
|
||||
ctx.externExcSym = newSym(skVar, getIdent(ctx.g.cache, ":externExc"), ctx.idgen, ctx.fn, ctx.fn.info)
|
||||
ctx.externExcSym.typ = ctx.curExcSym.typ
|
||||
newSymNode(ctx.externExcSym, ctx.fn.info)
|
||||
|
||||
proc newRestoreExternException(ctx: var Ctx): PNode =
|
||||
ctx.g.callCodegenProc("closureIterSetExc", ctx.fn.info, ctx.newExternExcAccess())
|
||||
|
||||
proc hasYields(n: PNode): bool =
|
||||
# TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt.
|
||||
case n.kind
|
||||
@@ -314,13 +298,21 @@ proc hasYields(n: PNode): bool =
|
||||
result = true
|
||||
break
|
||||
|
||||
proc newNullifyCurExc(ctx: var Ctx, info: TLineInfo): PNode =
|
||||
# :curExc = nil
|
||||
let curExc = ctx.newCurExcAccess()
|
||||
proc newNullifyCurExcLevel(ctx: var Ctx, info: TLineInfo, decrement = false): PNode =
|
||||
# :curEcx = 0
|
||||
let curExc = ctx.newCurExcLevelAccess()
|
||||
curExc.info = info
|
||||
let nilnode = newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil))
|
||||
let nilnode = ctx.g.newIntLit(info, 0)
|
||||
result = newTree(nkAsgn, curExc, nilnode)
|
||||
|
||||
proc newChangeCurExcLevel(ctx: var Ctx, info: TLineInfo, by: int): PNode =
|
||||
# inc(:curEcxLevel, by)
|
||||
let curExc = ctx.newCurExcLevelAccess()
|
||||
curExc.info = info
|
||||
result = newTreeIT(nkCall, info, ctx.g.getSysType(info, tyVoid),
|
||||
newSymNode(ctx.g.getSysMagic(info, "inc", mInc)), curExc,
|
||||
ctx.g.newIntLit(info, by))
|
||||
|
||||
proc newOr(g: ModuleGraph, a, b: PNode): PNode {.inline.} =
|
||||
result = newTreeIT(nkCall, a.info, g.getSysType(a.info, tyBool),
|
||||
newSymNode(g.getSysMagic(a.info, "or", mOr)), a, b)
|
||||
@@ -336,14 +328,9 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
||||
var cond: PNode = nil
|
||||
for i in 0..<c.len - 1:
|
||||
assert(c[i].kind == nkType)
|
||||
# Use the :curExc env field (set by the wrapper before entering the
|
||||
# except landing state) instead of calling getCurrentException():
|
||||
# injectdestructors does not process the args of this raw generic
|
||||
# `of` magic call, so an owning getCurrentException() temp would
|
||||
# never be destroyed and the caught exception would leak (#23615).
|
||||
let nextCond = newTreeIT(nkCall, c.info, ctx.g.getSysType(c.info, tyBool),
|
||||
newSymNode(g.getSysMagic(c.info, "of", mOf)),
|
||||
ctx.newCurExcAccess(),
|
||||
g.callCodegenProc("getCurrentException"),
|
||||
c[i])
|
||||
|
||||
cond = if cond.isNil: nextCond
|
||||
@@ -357,7 +344,7 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
||||
else:
|
||||
ifBranch = newNodeI(nkElse, c.info)
|
||||
|
||||
ifBranch.add(c[^1])
|
||||
ifBranch.add(newTreeI(nkStmtList, c.info, ctx.newChangeCurExcLevel(c.info, -1), c[^1]))
|
||||
ifStmt.add(ifBranch)
|
||||
|
||||
if ifStmt.len != 0:
|
||||
@@ -365,10 +352,9 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
||||
else:
|
||||
result = ctx.g.emptyNode
|
||||
|
||||
proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode): PNode =
|
||||
proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode) =
|
||||
# We should adjust finallyPath to gotoOut if exception is handled
|
||||
# if there is no finally node next to this except, gotoOut must be nil
|
||||
result = n
|
||||
if n.kind == nkStmtList:
|
||||
if n[0].kind == nkIfStmt and n[0][^1].kind != nkElse:
|
||||
# Not all cases are covered, which means exception is not handled
|
||||
@@ -391,7 +377,6 @@ proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode): PNode =
|
||||
# raised one.
|
||||
n.add newTree(nkCall,
|
||||
newSymNode(ctx.g.getCompilerProc("popCurrentException")))
|
||||
n.add ctx.newNullifyCurExc(n.info)
|
||||
if gotoOut != nil:
|
||||
# We have a finally node following this except block, and exception is handled
|
||||
# Configure its path to continue normally
|
||||
@@ -465,7 +450,7 @@ proc newNotCall(g: ModuleGraph; e: PNode): PNode =
|
||||
|
||||
proc boolLit(g: ModuleGraph; info: TLineInfo; value: bool): PNode =
|
||||
result = newIntLit(g, info, ord value)
|
||||
result.typ = getSysType(g, info, tyBool)
|
||||
result.typ() = getSysType(g, info, tyBool)
|
||||
|
||||
proc captureVar(c: var Ctx, s: PSym) =
|
||||
if c.varStates.getOrDefault(s.itemId) != localRequiresLifting:
|
||||
@@ -600,7 +585,10 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
let branch = n[i]
|
||||
case branch.kind
|
||||
of nkExceptBranch:
|
||||
branch[^1] = ctx.convertExprBodyToAsgn(branch[^1], tmp)
|
||||
if branch[0].kind == nkType:
|
||||
branch[1] = ctx.convertExprBodyToAsgn(branch[1], tmp)
|
||||
else:
|
||||
branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp)
|
||||
of nkFinally:
|
||||
discard
|
||||
else:
|
||||
@@ -732,7 +720,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
n[0] = ex
|
||||
result.add(n)
|
||||
|
||||
of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv, nkObjUpConv,
|
||||
of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv,
|
||||
nkDerefExpr, nkHiddenDeref:
|
||||
var ns = false
|
||||
for i in ord(n.kind == nkCast)..<n.len:
|
||||
@@ -823,7 +811,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||
let (st, ex) = exprToStmtList(n[1])
|
||||
n.transitionSonsKind(nkBlockStmt)
|
||||
n.typ = nil
|
||||
n.typ() = nil
|
||||
n[1] = st
|
||||
result.add(n)
|
||||
result.add(ex)
|
||||
@@ -835,7 +823,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
|
||||
# Generate the following code:
|
||||
# if :finallyPath[FINALLY_LEVEL] == 0:
|
||||
# if :curExc == nil:
|
||||
# if :curExcLevel == 0:
|
||||
# :state = -1
|
||||
# return result = :tmpResult
|
||||
# else:
|
||||
@@ -849,9 +837,9 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
|
||||
|
||||
let excNilCmp = newTreeIT(nkCall,
|
||||
info, ctx.g.getSysType(info, tyBool),
|
||||
newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info),
|
||||
ctx.newCurExcAccess(),
|
||||
newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil)))
|
||||
newSymNode(ctx.g.getSysMagic(info, "==", mEqI), info),
|
||||
ctx.newCurExcLevelAccess(),
|
||||
ctx.g.newIntLit(info, 0))
|
||||
|
||||
let retStmt =
|
||||
block:
|
||||
@@ -868,7 +856,7 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
|
||||
retStmt.flags.incl(nfNoRewrite)
|
||||
|
||||
let ifBody = newTree(nkIfStmt,
|
||||
newTree(nkElifBranch, excNilCmp, newTree(nkStmtList, ctx.newRestoreExternException(), retStmt)),
|
||||
newTree(nkElifBranch, excNilCmp, retStmt),
|
||||
newTree(nkElse,
|
||||
newTree(nkStmtList,
|
||||
newTreeI(nkRaiseStmt, info, ctx.g.emptyNode))))
|
||||
@@ -923,15 +911,14 @@ proc transformBreakStmt(ctx: var Ctx, n: PNode): PNode =
|
||||
result = n
|
||||
|
||||
proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode =
|
||||
# "Returning" involves jumping along all the current finally path.
|
||||
# "Returning" involves jumping along all the cureent finally path.
|
||||
# The last finally should exit to state 0 which is a special case for last exit
|
||||
# (either return or propagating exception to the caller).
|
||||
# It is eccounted for in newEndFinallyNode.
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
|
||||
# Returns prevent exception propagation
|
||||
result.add(ctx.nullifyCurExc)
|
||||
|
||||
result.add(ctx.newNullifyCurExcLevel(n.info))
|
||||
|
||||
var finallyChain = newSeq[PNode]()
|
||||
|
||||
@@ -955,7 +942,6 @@ proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode =
|
||||
result.add(ctx.newJumpAlongFinallyChain(finallyChain, n.info))
|
||||
else:
|
||||
# There are no (split) finallies on the path, so we can return right away
|
||||
result.add(ctx.restoreExternExc)
|
||||
result.add(n)
|
||||
|
||||
proc transformBreaksAndReturns(ctx: var Ctx, n: PNode): PNode =
|
||||
@@ -966,7 +952,7 @@ proc transformBreaksAndReturns(ctx: var Ctx, n: PNode): PNode =
|
||||
# of nkContinueStmt: # By this point all relevant continues should be
|
||||
# lowered to breaks in transf.nim.
|
||||
of nkReturnStmt:
|
||||
if nfNoRewrite notin n.flags:
|
||||
if ctx.curFinallyLevel > 0 and nfNoRewrite notin n.flags:
|
||||
result = ctx.transformReturnStmt(n)
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
@@ -990,14 +976,9 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
for j in i + 1..<n.len:
|
||||
s.add(n[j])
|
||||
|
||||
var body = s
|
||||
for pragma in ctx.enclosingPragmas:
|
||||
body = newTreeI(nkPragmaBlock, n[i + 1].info,
|
||||
pragma[0].copyTree, body)
|
||||
|
||||
n.sons.setLen(i + 1)
|
||||
discard ctx.newState(body, true, label)
|
||||
if ctx.transformClosureIteratorBody(body, gotoOut) != body:
|
||||
discard ctx.newState(s, true, label)
|
||||
if ctx.transformClosureIteratorBody(s, gotoOut) != s:
|
||||
internalError(ctx.g.config, "transformClosureIteratorBody != s")
|
||||
break
|
||||
else:
|
||||
@@ -1005,7 +986,6 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
|
||||
of nkYieldStmt:
|
||||
result = addGotoOut(result, gotoOut)
|
||||
result = newTree(nkStmtList, ctx.restoreExternExc, result)
|
||||
|
||||
of nkElse, nkElseExpr:
|
||||
result[0] = addGotoOut(result[0], gotoOut)
|
||||
@@ -1075,7 +1055,7 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
result.add(tryLabel)
|
||||
var tryBody = toStmtList(n[0])
|
||||
|
||||
var exceptBody = ctx.collectExceptState(n)
|
||||
let exceptBody = ctx.collectExceptState(n)
|
||||
var finallyBody = ctx.getFinallyNode(n)
|
||||
var exceptLabel, finallyLabel = ctx.g.emptyNode
|
||||
|
||||
@@ -1114,7 +1094,8 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
inc ctx.curFinallyLevel
|
||||
ctx.finallyPathStack.add(FinallyTarget(n: n[^1], label: finallyLabel))
|
||||
|
||||
tryBody = ctx.transformClosureIteratorBody(tryBody, tryOut)
|
||||
if ctx.transformClosureIteratorBody(tryBody, tryOut) != tryBody:
|
||||
internalError(ctx.g.config, "transformClosureIteratorBody != tryBody")
|
||||
|
||||
if exceptBody.kind != nkEmpty:
|
||||
ctx.curExcLandingState = if finallyBody.kind != nkEmpty: finallyLabel
|
||||
@@ -1122,9 +1103,10 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
discard ctx.newState(exceptBody, false, exceptLabel)
|
||||
|
||||
let normalOut = if finallyBody.kind != nkEmpty: gotoOut else: nil
|
||||
exceptBody = ctx.addElseToExcept(exceptBody, normalOut)
|
||||
ctx.addElseToExcept(exceptBody, normalOut)
|
||||
# echo "EXCEPT: ", renderTree(exceptBody)
|
||||
exceptBody = ctx.transformClosureIteratorBody(exceptBody, tryOut)
|
||||
if ctx.transformClosureIteratorBody(exceptBody, tryOut) != exceptBody:
|
||||
internalError(ctx.g.config, "transformClosureIteratorBody != exceptBody")
|
||||
|
||||
ctx.curExcLandingState = oldExcLandingState
|
||||
|
||||
@@ -1132,17 +1114,10 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
|
||||
discard ctx.finallyPathStack.pop()
|
||||
discard ctx.newState(finallyBody, false, finallyLabel)
|
||||
let finallyExit = newTree(nkGotoState, ctx.newFinallyPathAccess(ctx.curFinallyLevel - 1, finallyBody.info))
|
||||
finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit)
|
||||
if ctx.transformClosureIteratorBody(finallyBody, finallyExit) != finallyBody:
|
||||
internalError(ctx.g.config, "transformClosureIteratorBody != finallyBody")
|
||||
dec ctx.curFinallyLevel
|
||||
|
||||
of nkPragmaBlock:
|
||||
# Propagate the pragma blocks so that blocks like {.cast(uncheckedAssign).}
|
||||
# remain effective
|
||||
ctx.enclosingPragmas.add(n)
|
||||
n[1] = ctx.transformClosureIteratorBody(n[1], gotoOut)
|
||||
discard ctx.enclosingPragmas.pop()
|
||||
result = n
|
||||
|
||||
of nkGotoState, nkForStmt:
|
||||
internalError(ctx.g.config, "closure iter " & $n.kind)
|
||||
|
||||
@@ -1226,6 +1201,34 @@ proc createExceptionTable(ctx: var Ctx): PNode {.inline.} =
|
||||
for i in 0 .. ctx.states.high:
|
||||
result.add(ctx.states[i].excLandingState)
|
||||
|
||||
proc newExceptBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} =
|
||||
# Generates code:
|
||||
# :state = exceptionTable[:state]
|
||||
# if :state == 0:
|
||||
# raise
|
||||
result = newNodeI(nkStmtList, info)
|
||||
|
||||
let intTyp = ctx.g.getSysType(info, tyInt)
|
||||
let boolTyp = ctx.g.getSysType(info, tyBool)
|
||||
|
||||
# :state = exceptionTable[:state]
|
||||
result.add ctx.newStateAssgn(
|
||||
newTreeIT(nkBracketExpr, info, intTyp,
|
||||
ctx.createExceptionTable(),
|
||||
ctx.newStateAccess()))
|
||||
|
||||
# if :state == 0: raise
|
||||
block:
|
||||
let cond = newTreeIT(nkCall, info, boolTyp,
|
||||
ctx.g.getSysMagic(info, "==", mEqI).newSymNode(),
|
||||
ctx.newStateAccess(),
|
||||
newIntTypeNode(0, intTyp))
|
||||
|
||||
let raiseStmt = newTree(nkRaiseStmt, ctx.g.emptyNode)
|
||||
let ifBranch = newTree(nkElifBranch, cond, raiseStmt)
|
||||
let ifStmt = newTree(nkIfStmt, ifBranch)
|
||||
result.add(ifStmt)
|
||||
|
||||
proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
||||
# Generates code:
|
||||
# var :tmp = nil
|
||||
@@ -1233,45 +1236,24 @@ proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
||||
# body
|
||||
# except:
|
||||
# :state = exceptionTable[:state]
|
||||
# :curExc = getCurrentException()
|
||||
# if :state == 0:
|
||||
# closureIterSetExc(:externExc)
|
||||
# raise
|
||||
# if :state == 0:
|
||||
# raise
|
||||
# :tmp = getCurrentException()
|
||||
#
|
||||
# pushCurrentException(:curExc)
|
||||
# pushCurrentException(:tmp)
|
||||
|
||||
let info = ctx.fn.info
|
||||
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
|
||||
let exceptBody = newTreeI(nkStmtList, info,
|
||||
ctx.newStateAssgn(
|
||||
newTreeIT(nkBracketExpr, info, ctx.g.getSysType(info, tyInt),
|
||||
ctx.createExceptionTable(),
|
||||
ctx.newStateAccess())),
|
||||
newTreeI(nkFastAsgn, info, ctx.newCurExcAccess(), getCurExc))
|
||||
let tryBody = newTree(nkStmtList, n)
|
||||
let exceptBody = ctx.newExceptBody(ctx.fn.info)
|
||||
let exceptBranch = newTree(nkExceptBranch, exceptBody)
|
||||
|
||||
result = newTree(nkStmtList)
|
||||
result.add newTree(nkTryStmt,
|
||||
newTree(nkStmtList, n),
|
||||
newTree(nkExceptBranch, exceptBody))
|
||||
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
|
||||
let tempExc = ctx.newTempVar(getCurExc.typ, result)
|
||||
result.add newTree(nkTryStmt, tryBody, exceptBranch)
|
||||
exceptBody.add ctx.newTempVarAsgn(tempExc, getCurExc)
|
||||
|
||||
# if :state == 0:
|
||||
# closureIterSetExc(:externExc)
|
||||
# raise
|
||||
block:
|
||||
let boolTyp = ctx.g.getSysType(info, tyBool)
|
||||
let intTyp = ctx.g.getSysType(info, tyInt)
|
||||
let cond = newTreeIT(nkCall, info, boolTyp,
|
||||
ctx.g.getSysMagic(info, "==", mEqI).newSymNode(),
|
||||
ctx.newStateAccess(),
|
||||
newIntTypeNode(0, intTyp))
|
||||
|
||||
let raiseStmt = newTree(nkRaiseStmt, ctx.newCurExcAccess())
|
||||
let ifBody = newTree(nkStmtList, ctx.newRestoreExternException(), raiseStmt)
|
||||
let ifBranch = newTree(nkElifBranch, cond, ifBody)
|
||||
let ifStmt = newTree(nkIfStmt, ifBranch)
|
||||
result.add(ifStmt)
|
||||
|
||||
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newCurExcAccess())
|
||||
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newTempVarAccess(tempExc))
|
||||
result.add ctx.newChangeCurExcLevel(n.info, 1)
|
||||
|
||||
proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
|
||||
# while true:
|
||||
@@ -1294,19 +1276,6 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
|
||||
blockStmt.add(blockBody)
|
||||
loopBody.add(blockStmt)
|
||||
|
||||
if ctx.hasExceptions:
|
||||
# Since we have yields in tries, we must switch current exception
|
||||
# between the iter and "outer world"
|
||||
# var :externExc = getCurrentException()
|
||||
# closureIterSetExc(:curExc)
|
||||
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
|
||||
discard ctx.newExternExcAccess()
|
||||
let setCurExc = ctx.g.callCodegenProc("closureIterSetExc", n.info, ctx.newCurExcAccess())
|
||||
result = newTreeI(nkStmtList, n.info,
|
||||
ctx.newTempVarDef(ctx.externExcSym, getCurExc),
|
||||
setCurExc,
|
||||
result)
|
||||
|
||||
proc countStateOccurences(ctx: var Ctx, n: PNode, stateOccurences: var openArray[int]) =
|
||||
## Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
|
||||
## For every such node increment stateOccurences[stateIdx]
|
||||
@@ -1324,15 +1293,16 @@ proc countStateOccurences(ctx: var Ctx, n: PNode, stateOccurences: var openArray
|
||||
|
||||
proc replaceDeletedStates(ctx: var Ctx, n: PNode): PNode =
|
||||
result = n
|
||||
if n.kind == nkIntLit:
|
||||
let idx = n.intVal
|
||||
if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == n and ctx.states[idx].deletable:
|
||||
let gt = ctx.replaceDeletedStates(skipStmtList(ctx.states[idx].body))
|
||||
assert(gt.kind == nkGotoState)
|
||||
result = gt[0]
|
||||
else:
|
||||
for i in 0 ..< n.safeLen:
|
||||
n[i] = ctx.replaceDeletedStates(n[i])
|
||||
for i in 0 ..< n.safeLen:
|
||||
let c = n[i]
|
||||
if c.kind == nkIntLit:
|
||||
let idx = c.intVal
|
||||
if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == c and ctx.states[idx].deletable:
|
||||
let gt = ctx.replaceDeletedStates(skipStmtList(ctx.states[idx].body))
|
||||
assert(gt.kind == nkGotoState)
|
||||
n[i] = gt[0]
|
||||
else:
|
||||
n[i] = ctx.replaceDeletedStates(c)
|
||||
|
||||
proc replaceInlinedStates(ctx: var Ctx, n: PNode): PNode =
|
||||
## Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
|
||||
@@ -1363,7 +1333,6 @@ proc optimizeStates(ctx: var Ctx) =
|
||||
# Replace deletable state labels to labels of respective non-empty states
|
||||
for i in 0 .. ctx.states.high:
|
||||
ctx.states[i].body = ctx.replaceDeletedStates(ctx.states[i].body)
|
||||
ctx.states[i].excLandingState = ctx.replaceDeletedStates(ctx.states[i].excLandingState)
|
||||
|
||||
# Remove deletable states
|
||||
var i = 0
|
||||
@@ -1408,34 +1377,18 @@ proc optimizeStates(ctx: var Ctx) =
|
||||
for i in 0 .. ctx.states.high:
|
||||
ctx.states[i].label.intVal = i
|
||||
|
||||
proc detectCapturedSym(c: var Ctx, s: PSym, stateIdx: int) =
|
||||
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym:
|
||||
let vs = c.varStates.getOrDefault(s.itemId, localNotSeen)
|
||||
if vs == localNotSeen: # First seing this variable
|
||||
c.varStates[s.itemId] = stateIdx
|
||||
elif vs == localRequiresLifting:
|
||||
discard # Sym already marked
|
||||
elif vs != stateIdx:
|
||||
c.captureVar(s)
|
||||
|
||||
proc isClosureIterLocal(c: Ctx, s: PSym): bool =
|
||||
s.kind in {skResult, skVar, skLet, skForVar, skTemp} and
|
||||
sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym
|
||||
|
||||
proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) =
|
||||
case n.kind
|
||||
of nkSym:
|
||||
let s = n.sym
|
||||
detectCapturedSym(c, s, stateIdx)
|
||||
of nkAddr, nkHiddenAddr:
|
||||
let s = getRoot(n)
|
||||
if s != nil and isClosureIterLocal(c, s):
|
||||
detectCapturedSym(c, s, stateIdx)
|
||||
# bug #25596; lifetime extension for `addr`-taken locals as
|
||||
# we claim ARC/ORC do destruction based on scopes, not on last-usages.
|
||||
c.captureVar(s)
|
||||
for i in 0 ..< n.safeLen:
|
||||
detectCapturedVars(c, n[i], stateIdx)
|
||||
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn:
|
||||
let vs = c.varStates.getOrDefault(s.itemId, localNotSeen)
|
||||
if vs == localNotSeen: # First seing this variable
|
||||
c.varStates[s.itemId] = stateIdx
|
||||
elif vs == localRequiresLifting:
|
||||
discard # Sym already marked
|
||||
elif vs != stateIdx:
|
||||
c.captureVar(s)
|
||||
of nkReturnStmt:
|
||||
if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}:
|
||||
# we have a `result = result` expression produced by the closure
|
||||
@@ -1501,16 +1454,10 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
|
||||
|
||||
ctx.curExcLandingState = ctx.newStateLabel()
|
||||
ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), idgen, fn, fn.info)
|
||||
|
||||
|
||||
ctx.nullifyCurExc = newTree(nkStmtList)
|
||||
ctx.restoreExternExc = newTree(nkStmtList)
|
||||
|
||||
var n = n.toStmtList
|
||||
# echo "transformed into ", n
|
||||
|
||||
discard ctx.newState(n, false, nil)
|
||||
|
||||
let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1))
|
||||
|
||||
var ns = false
|
||||
@@ -1523,10 +1470,6 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
|
||||
# Splitting transformation
|
||||
discard ctx.transformClosureIteratorBody(n, gotoOut)
|
||||
|
||||
if ctx.hasExceptions:
|
||||
ctx.nullifyCurExc.add(ctx.newNullifyCurExc(fn.info))
|
||||
ctx.restoreExternExc.add(ctx.newRestoreExternException())
|
||||
|
||||
# Assign state label indexes
|
||||
for i in 0 .. ctx.states.high:
|
||||
ctx.states[i].label.intVal = i
|
||||
@@ -1544,9 +1487,7 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
|
||||
let body = ctx.transformStateAssignments(s.body)
|
||||
caseDispatcher.add newTreeI(nkOfBranch, body.info, s.label, body)
|
||||
|
||||
caseDispatcher.add newTreeI(nkElse, n.info,
|
||||
newTree(nkStmtList, ctx.restoreExternExc,
|
||||
newTreeI(nkReturnStmt, n.info, g.emptyNode)))
|
||||
caseDispatcher.add newTreeI(nkElse, n.info, newTreeI(nkReturnStmt, n.info, g.emptyNode))
|
||||
|
||||
result = wrapIntoStateLoop(ctx, caseDispatcher)
|
||||
result = liftLocals(ctx, result)
|
||||
|
||||
@@ -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,726 +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* = "4"
|
||||
## Artifact format version, stored in the meta head. Artifacts written
|
||||
## by an older compiler lack the NIF names and the cref group the
|
||||
## def-retention check needs (v2), the cdeps group the fine-grained
|
||||
## reuse gate needs (v3), or the type NIF names and cnif-marked extern
|
||||
## RTTI references the typeinfo flavor of the def-retention check
|
||||
## needs (v4); `readCnifHeads` reports them as invalid so their TUs
|
||||
## simply regenerate once.
|
||||
|
||||
proc cnifDefDirective*(name, flags, nifName: string): string =
|
||||
CnifDefStart & name & CnifDefSep & flags & CnifDefSep & nifName & CnifDefEnd
|
||||
|
||||
proc cnifEndDefs*(): string =
|
||||
CnifDefStart & CnifDefEnd
|
||||
|
||||
proc writeCnifArtifact*(code: string; outfile: string;
|
||||
initRequired = false; datInitRequired = false;
|
||||
dataDefs: openArray[tuple[cname, nifname: string]] = [];
|
||||
semmedNif = ""; moduleBase = "";
|
||||
implDeps: openArray[string] = []) =
|
||||
## Splits the marked module text into the `.c.nif` artifact.
|
||||
## The artifact starts with a `(meta <flags> "semmedNif" "moduleBase"
|
||||
## "version")` head — whether the module has an init/datInit proc
|
||||
## ('i'/'d'), which semmed NIF it was generated from and the module's
|
||||
## mangled base name (what `registerModuleToMain` and the reuse decision
|
||||
## need when the TU is reused in a later run, possibly without the module
|
||||
## ever being loaded again) — a `(cdata (SymbolDef StrLit)*)` group naming
|
||||
## the data definitions (consts, globals, RTTI) the TU embeds together
|
||||
## with their NIF names, a `(cref Ident*)` group naming every C name
|
||||
## the TU references but does not define itself (what the def-retention
|
||||
## check consults when some *other* TU regenerates), and a
|
||||
## `(cdeps Ident*)` group naming the modules whose routine *bodies* this
|
||||
## TU embeds (redirected defs, shared instances, hooks): the fine-grained
|
||||
## reuse gate checks their `.impl.nif` cookies on top of the direct
|
||||
## imports' `.iface.nif` cookies.
|
||||
# pre-pass: every marked name is a use, every definition directive (and
|
||||
# every data def) is a definition; external references = uses - defs
|
||||
var uses = initHashSet[string]()
|
||||
var defs = initHashSet[string]()
|
||||
block prePass:
|
||||
var i = 0
|
||||
while i < code.len:
|
||||
case code[i]
|
||||
of CnifSymStart:
|
||||
inc i
|
||||
var name = ""
|
||||
while i < code.len and code[i] != CnifSymEnd:
|
||||
name.add code[i]
|
||||
inc i
|
||||
inc i
|
||||
uses.incl name
|
||||
of CnifDefStart:
|
||||
inc i
|
||||
var payload = ""
|
||||
while i < code.len and code[i] != CnifDefEnd:
|
||||
payload.add code[i]
|
||||
inc i
|
||||
inc i
|
||||
let sep = find(payload, CnifDefSep)
|
||||
if sep > 0: defs.incl payload[0..<sep]
|
||||
elif payload.len > 0: defs.incl payload
|
||||
else:
|
||||
inc i
|
||||
for d in dataDefs: defs.incl d.cname
|
||||
var crefs: seq[string] = @[]
|
||||
for u in uses:
|
||||
if u notin defs: crefs.add u
|
||||
sort crefs
|
||||
|
||||
var b = nifbuilder.open(outfile)
|
||||
b.withTree "stmts":
|
||||
b.withTree "meta":
|
||||
var metaFlags = ""
|
||||
if initRequired: metaFlags.add 'i'
|
||||
if datInitRequired: metaFlags.add 'd'
|
||||
if metaFlags.len > 0: b.addIdent metaFlags
|
||||
else: b.addEmpty
|
||||
b.addStrLit semmedNif
|
||||
b.addStrLit moduleBase
|
||||
b.addStrLit CnifVersion
|
||||
b.withTree "cdata":
|
||||
for d in dataDefs:
|
||||
b.addSymbolDef d.cname
|
||||
b.addStrLit d.nifname
|
||||
b.withTree "cref":
|
||||
for r in crefs:
|
||||
b.addIdent r
|
||||
b.withTree "cdeps":
|
||||
for s in implDeps:
|
||||
b.addIdent s
|
||||
var raw = ""
|
||||
var inDef = false
|
||||
template flushRaw() =
|
||||
if raw.len > 0:
|
||||
b.addStrLit raw
|
||||
raw.setLen 0
|
||||
var i = 0
|
||||
while i < code.len:
|
||||
case code[i]
|
||||
of CnifSymStart:
|
||||
flushRaw()
|
||||
inc i
|
||||
var name = ""
|
||||
while i < code.len and code[i] != CnifSymEnd:
|
||||
name.add code[i]
|
||||
inc i
|
||||
inc i # skip CnifSymEnd
|
||||
b.addSymbol name, ""
|
||||
of CnifDefStart:
|
||||
flushRaw()
|
||||
inc i
|
||||
var payload = ""
|
||||
while i < code.len and code[i] != CnifDefEnd:
|
||||
payload.add code[i]
|
||||
inc i
|
||||
inc i # skip CnifDefEnd
|
||||
if inDef:
|
||||
b.endTree()
|
||||
inDef = false
|
||||
if payload.len > 0:
|
||||
let sep = find(payload, CnifDefSep)
|
||||
let name = if sep >= 0: payload[0..<sep] else: payload
|
||||
var flags = if sep >= 0: payload[sep+1..^1] else: ""
|
||||
var nifName = ""
|
||||
let sep2 = find(flags, CnifDefSep)
|
||||
if sep2 >= 0:
|
||||
nifName = flags[sep2+1..^1]
|
||||
flags = flags[0..<sep2]
|
||||
b.addTree "cdef"
|
||||
b.addSymbolDef name
|
||||
if flags.len > 0: b.addIdent flags
|
||||
else: b.addEmpty
|
||||
b.addStrLit nifName
|
||||
inDef = true
|
||||
else:
|
||||
raw.add code[i]
|
||||
inc i
|
||||
flushRaw()
|
||||
if inDef:
|
||||
b.endTree()
|
||||
b.close()
|
||||
|
||||
proc renderMarkedC*(code: string; live: HashSet[string]; dropped: var int): string =
|
||||
## Renders the final C text from the marked module text: symbol marks are
|
||||
## removed (keeping the names — a later merge step substitutes them here),
|
||||
## and definitions whose name is not in `live` are dropped entirely. Each
|
||||
## definition is self-delimiting (genProcAux emits an end directive right
|
||||
## after the proc's text), so text written by other emitters is never part
|
||||
## of a definition's span and survives unconditionally.
|
||||
result = newStringOfCap(code.len)
|
||||
var i = 0
|
||||
while i < code.len:
|
||||
case code[i]
|
||||
of CnifSymStart, CnifSymEnd:
|
||||
inc i
|
||||
of CnifDefStart:
|
||||
var payload = ""
|
||||
inc i
|
||||
while i < code.len and code[i] != CnifDefEnd:
|
||||
payload.add code[i]
|
||||
inc i
|
||||
inc i # skip CnifDefEnd
|
||||
if payload.len > 0:
|
||||
let sep = find(payload, CnifDefSep)
|
||||
let name = if sep >= 0: payload[0..<sep] else: payload
|
||||
if name notin live:
|
||||
inc dropped
|
||||
# drop the definition's text: everything up to its end directive
|
||||
while i < code.len and code[i] != CnifDefStart: inc i
|
||||
else:
|
||||
result.add code[i]
|
||||
inc i
|
||||
|
||||
# ---- Liveness over the artifact -------------------------------------------
|
||||
|
||||
proc symOrIdentName(c: Cursor): string {.inline.} =
|
||||
if c.kind == Ident: strVal(c) else: symName(c)
|
||||
|
||||
type
|
||||
CnifHeads* = object
|
||||
## The cheap-to-parse part of an artifact that a later run needs in
|
||||
## order to reuse the TU without regenerating it.
|
||||
valid*: bool ## file parsed, carries the meta head and has
|
||||
## the current format version
|
||||
initRequired*: bool
|
||||
datInitRequired*: bool
|
||||
semmedNif*: string ## the semmed NIF this TU was generated from
|
||||
moduleBase*: string ## the module's mangled base name
|
||||
cdefs*: seq[tuple[cname, nifname: string]] ## the proc definitions
|
||||
cdata*: seq[tuple[cname, nifname: string]] ## the data definitions
|
||||
crefs*: seq[string] ## C names referenced but not defined here
|
||||
cdeps*: seq[string] ## module suffixes whose routine bodies this
|
||||
## TU embeds (impl-cookie gated on reuse)
|
||||
|
||||
proc readCnifHeads*(f: string): CnifHeads =
|
||||
## Reads `(meta ...)`, `(cdata ...)`, `(cref ...)` and the `(cdef ...)`
|
||||
## head names from an artifact. Artifacts written by an older compiler
|
||||
## (no meta head or a different format version) report `valid=false`.
|
||||
result = CnifHeads()
|
||||
if not fileExists(f): return
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let stmtsTag = tags.registerTag("stmts")
|
||||
let cdefTag = tags.registerTag("cdef")
|
||||
let cdataTag = tags.registerTag("cdata")
|
||||
let crefTag = tags.registerTag("cref")
|
||||
let cdepsTag = tags.registerTag("cdeps")
|
||||
let metaTag = tags.registerTag("meta")
|
||||
var buf = parseFromFile(f, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
endRead(c)
|
||||
return
|
||||
var version = ""
|
||||
var sawMeta = false
|
||||
c.loopInto:
|
||||
if c.kind == TagLit:
|
||||
if c.cursorTagId == metaTag:
|
||||
sawMeta = true
|
||||
var strIdx = 0
|
||||
c.loopInto:
|
||||
if c.kind == Ident:
|
||||
for ch in strVal(c):
|
||||
if ch == 'i': result.initRequired = true
|
||||
elif ch == 'd': result.datInitRequired = true
|
||||
inc c
|
||||
elif c.kind == StrLit:
|
||||
if strIdx == 0: result.semmedNif = strVal(c)
|
||||
elif strIdx == 1: result.moduleBase = strVal(c)
|
||||
elif strIdx == 2: version = strVal(c)
|
||||
inc strIdx
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == cdataTag:
|
||||
c.loopInto:
|
||||
if c.kind == SymbolDef:
|
||||
result.cdata.add (symName(c), "")
|
||||
inc c
|
||||
elif c.kind == StrLit:
|
||||
if result.cdata.len > 0:
|
||||
result.cdata[^1].nifname = strVal(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == crefTag:
|
||||
c.loopInto:
|
||||
if c.kind in {Ident, Symbol, SymbolDef}:
|
||||
result.crefs.add symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == cdepsTag:
|
||||
c.loopInto:
|
||||
if c.kind in {Ident, Symbol, SymbolDef}:
|
||||
result.cdeps.add symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.cursorTagId == cdefTag:
|
||||
# fixed head: SymbolDef, flags (Ident or empty), NIF name StrLit;
|
||||
# everything after that is the definition's body text
|
||||
var state = 0
|
||||
c.loopInto:
|
||||
if c.kind == SymbolDef:
|
||||
result.cdefs.add (symName(c), "")
|
||||
state = 1
|
||||
inc c
|
||||
elif state == 1: # the flags field
|
||||
state = 2
|
||||
skip c
|
||||
elif state == 2: # the NIF name
|
||||
if c.kind == StrLit and result.cdefs.len > 0:
|
||||
result.cdefs[^1].nifname = strVal(c)
|
||||
state = 3
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
result.valid = sawMeta and version == CnifVersion
|
||||
|
||||
type
|
||||
CnifLiveness* = object
|
||||
defs*: int ## proc definitions emitted across all modules
|
||||
liveDefs*: int ## of those, reachable from the roots
|
||||
live*: HashSet[string] ## live C names
|
||||
broken*: bool
|
||||
|
||||
proc computeLiveFromCArtifacts*(files: openArray[string]): CnifLiveness =
|
||||
## dce1-style mark&sweep over the C-shaped artifacts: a `(cdef ...)`
|
||||
## group is a definition (flags 'x'/'c'/'m' — exportc, compilerproc,
|
||||
## method/dispatcher — make it a root), names at the top level (data,
|
||||
## globals, init code) are roots, names inside a group are its uses.
|
||||
## Because the artifact is *fully lowered* output, no conservative
|
||||
## modelling is needed: every call the C code contains is a token here.
|
||||
##
|
||||
## NB: mangled C names contain no dots, so NIF's text reader classifies
|
||||
## them as `Ident` rather than `Symbol`; the dialect therefore treats
|
||||
## Ident tokens as name uses. Inside a `(cdef ...)` the flags ident is
|
||||
## the one immediately following the SymbolDef; everything after is a use.
|
||||
result = CnifLiveness(live: initHashSet[string]())
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let stmtsTag = tags.registerTag("stmts")
|
||||
let cdefTag = tags.registerTag("cdef")
|
||||
let cdataTag = tags.registerTag("cdata")
|
||||
let crefTag = tags.registerTag("cref")
|
||||
let cdepsTag = tags.registerTag("cdeps")
|
||||
let metaTag = tags.registerTag("meta")
|
||||
var uses = initTable[string, HashSet[string]]()
|
||||
var roots = initHashSet[string]()
|
||||
var defs = initHashSet[string]()
|
||||
for f in files:
|
||||
if not fileExists(f):
|
||||
result.broken = true
|
||||
return
|
||||
var buf = parseFromFile(f, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
result.broken = true
|
||||
endRead(c)
|
||||
return
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of Symbol, Ident:
|
||||
roots.incl symOrIdentName(c)
|
||||
inc c
|
||||
of TagLit:
|
||||
if c.cursorTagId == metaTag or c.cursorTagId == cdataTag or
|
||||
c.cursorTagId == crefTag or c.cursorTagId == cdepsTag:
|
||||
# bookkeeping for TU reuse, irrelevant for liveness
|
||||
skip c
|
||||
elif c.cursorTagId == cdefTag:
|
||||
var owner = ""
|
||||
var flagsSeen = false
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of SymbolDef:
|
||||
owner = symName(c)
|
||||
defs.incl owner
|
||||
flagsSeen = false
|
||||
inc c
|
||||
of Symbol, Ident:
|
||||
let name = symOrIdentName(c)
|
||||
if not flagsSeen:
|
||||
# the flags field right after the SymbolDef
|
||||
flagsSeen = true
|
||||
for ch in name:
|
||||
# 'd' marks a data definition (const/RTTI): never DCE'd, so it
|
||||
# is a root whose body keeps its referenced procs live
|
||||
if ch in {'x', 'c', 'm', 'd'}:
|
||||
roots.incl owner
|
||||
break
|
||||
else:
|
||||
uses.mgetOrPut(owner, initHashSet[string]()).incl name
|
||||
inc c
|
||||
of DotToken:
|
||||
flagsSeen = true # empty flags field
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
c.loopInto:
|
||||
if c.kind in {Symbol, Ident}:
|
||||
roots.incl symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
# mark & sweep
|
||||
var work = newSeqOfCap[string](roots.len)
|
||||
for r in roots: work.add r
|
||||
while work.len > 0:
|
||||
let s = work.pop()
|
||||
if not result.live.containsOrIncl(s):
|
||||
if uses.hasKey(s):
|
||||
for dep in uses[s]:
|
||||
if dep notin result.live:
|
||||
work.add dep
|
||||
result.defs = defs.len
|
||||
for d in defs:
|
||||
if d in result.live: inc result.liveDefs
|
||||
|
||||
# ---- The merge stage: liveness + owner assignment -------------------------
|
||||
|
||||
type
|
||||
MergeDecision* = object
|
||||
## What the per-module backend's `merge` stage computes from every
|
||||
## module's `.c.nif` and what its `emit` stage consumes to render the
|
||||
## final `.c` of one module.
|
||||
live*: HashSet[string] ## globally reachable C names (dead cdefs
|
||||
## are dropped from every module)
|
||||
owners*: Table[string, string] ## for each `'u'`-flagged (unique,
|
||||
## externally-linked) definition, the single
|
||||
## artifact base name allowed to embed its
|
||||
## body; every other module prototypes it
|
||||
broken*: bool ## an artifact was missing or unparsable —
|
||||
## the caller should fall back / regenerate
|
||||
defs*, liveDefs*: int
|
||||
|
||||
proc computeMergeDecision*(files: openArray[string]): MergeDecision =
|
||||
## One pass over every `.c.nif`: the same mark&sweep as
|
||||
## `computeLiveFromCArtifacts` plus, per definition, owner assignment.
|
||||
##
|
||||
## Each `cg` process emits the body of every definition it demands
|
||||
## (emit-everywhere), so the same externally-linked definition appears in
|
||||
## several artifacts. A `'u'` flag on the `(cdef ...)` marks those that need
|
||||
## exactly one owner, assigned here across processes: the owner is the
|
||||
## lexicographically smallest artifact that emits it — a pure function of the
|
||||
## claimant set, hence stable across rebuilds. Definitions without `'u'`
|
||||
## (inline procs, dispatchers) are `static`/main-only and emitted into every
|
||||
## using TU, so they get no owner entry and are never deduplicated.
|
||||
result = MergeDecision(live: initHashSet[string](),
|
||||
owners: initTable[string, string]())
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let stmtsTag = tags.registerTag("stmts")
|
||||
let cdefTag = tags.registerTag("cdef")
|
||||
let cdataTag = tags.registerTag("cdata")
|
||||
let crefTag = tags.registerTag("cref")
|
||||
let cdepsTag = tags.registerTag("cdeps")
|
||||
let metaTag = tags.registerTag("meta")
|
||||
var uses = initTable[string, HashSet[string]]()
|
||||
var roots = initHashSet[string]()
|
||||
var defs = initHashSet[string]()
|
||||
for f in files:
|
||||
if not fileExists(f):
|
||||
result.broken = true
|
||||
return
|
||||
let owner = extractFilename(f)
|
||||
var buf = parseFromFile(f, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
result.broken = true
|
||||
endRead(c)
|
||||
return
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of Symbol, Ident:
|
||||
roots.incl symOrIdentName(c)
|
||||
inc c
|
||||
of TagLit:
|
||||
if c.cursorTagId == metaTag or c.cursorTagId == cdataTag or
|
||||
c.cursorTagId == crefTag or c.cursorTagId == cdepsTag:
|
||||
skip c
|
||||
elif c.cursorTagId == cdefTag:
|
||||
var ownerName = ""
|
||||
var flagsSeen = false
|
||||
var needsOwner = false
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of SymbolDef:
|
||||
ownerName = symName(c)
|
||||
defs.incl ownerName
|
||||
flagsSeen = false
|
||||
inc c
|
||||
of Symbol, Ident:
|
||||
let name = symOrIdentName(c)
|
||||
if not flagsSeen:
|
||||
flagsSeen = true
|
||||
for ch in name:
|
||||
if ch in {'x', 'c', 'm'}: roots.incl ownerName
|
||||
# 'u' = unique proc (DCE'd), 'd' = data (never DCE'd, hence a
|
||||
# root); both need a single owner across the emit-everywhere
|
||||
# processes
|
||||
elif ch == 'u': needsOwner = true
|
||||
elif ch == 'd':
|
||||
needsOwner = true
|
||||
roots.incl ownerName
|
||||
else:
|
||||
uses.mgetOrPut(ownerName, initHashSet[string]()).incl name
|
||||
inc c
|
||||
of DotToken:
|
||||
flagsSeen = true # empty flags field
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
if needsOwner and ownerName.len > 0:
|
||||
# smallest claimant wins; ties impossible (one entry per name)
|
||||
let prev = result.owners.getOrDefault(ownerName, "")
|
||||
if prev.len == 0 or owner < prev:
|
||||
result.owners[ownerName] = owner
|
||||
else:
|
||||
c.loopInto:
|
||||
if c.kind in {Symbol, Ident}:
|
||||
roots.incl symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
var work = newSeqOfCap[string](roots.len)
|
||||
for r in roots: work.add r
|
||||
while work.len > 0:
|
||||
let s = work.pop()
|
||||
if not result.live.containsOrIncl(s):
|
||||
if uses.hasKey(s):
|
||||
for dep in uses[s]:
|
||||
if dep notin result.live:
|
||||
work.add dep
|
||||
result.defs = defs.len
|
||||
for d in defs:
|
||||
if d in result.live: inc result.liveDefs
|
||||
|
||||
const MergeDecisionFile* = "ic.backend.merge.nif"
|
||||
## Fixed name of the merge stage's output in the nimcache, read by `emit`.
|
||||
|
||||
proc writeMergeDecision*(outfile: string; d: MergeDecision) =
|
||||
## Serializes the merge decision: `(merge (live Symbol*) (owners (own
|
||||
## Symbol StrLit)*))`. C names are mangled (no dots) so they serialize as
|
||||
## symbols; owner artifact base names go in string literals.
|
||||
var live: seq[string] = @[]
|
||||
for n in d.live: live.add n
|
||||
sort live
|
||||
var keys: seq[string] = @[]
|
||||
for k in d.owners.keys: keys.add k
|
||||
sort keys
|
||||
var b = nifbuilder.open(outfile)
|
||||
b.withTree "merge":
|
||||
b.withTree "live":
|
||||
for n in live: b.addSymbol n, ""
|
||||
b.withTree "owners":
|
||||
for k in keys:
|
||||
b.withTree "own":
|
||||
b.addSymbol k, ""
|
||||
b.addStrLit d.owners[k]
|
||||
b.close()
|
||||
|
||||
proc readMergeDecision*(f: string): MergeDecision =
|
||||
## Reads back a `writeMergeDecision` file; `broken=true` if absent/unparsable.
|
||||
result = MergeDecision(live: initHashSet[string](),
|
||||
owners: initTable[string, string]())
|
||||
if not fileExists(f):
|
||||
result.broken = true
|
||||
return
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let mergeTag = tags.registerTag("merge")
|
||||
let liveTag = tags.registerTag("live")
|
||||
let ownersTag = tags.registerTag("owners")
|
||||
let ownTag = tags.registerTag("own")
|
||||
var buf = parseFromFile(f, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != mergeTag:
|
||||
result.broken = true
|
||||
endRead(c)
|
||||
return
|
||||
c.loopInto:
|
||||
if c.kind == TagLit and c.cursorTagId == liveTag:
|
||||
c.loopInto:
|
||||
if c.kind in {Symbol, Ident}:
|
||||
result.live.incl symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
elif c.kind == TagLit and c.cursorTagId == ownersTag:
|
||||
c.loopInto:
|
||||
if c.kind == TagLit and c.cursorTagId == ownTag:
|
||||
var key = ""
|
||||
c.loopInto:
|
||||
if c.kind in {Symbol, Ident}:
|
||||
key = symOrIdentName(c)
|
||||
inc c
|
||||
elif c.kind == StrLit:
|
||||
if key.len > 0: result.owners[key] = strVal(c)
|
||||
inc c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
else:
|
||||
skip c
|
||||
endRead(c)
|
||||
|
||||
proc renderCFromArtifact*(artifact: string; d: MergeDecision; ownerId: string;
|
||||
dropped: var int): string =
|
||||
## The per-module backend's `emit` stage: render one module's final `.c` from
|
||||
## its `.c.nif` and the merge decision. String literals are emitted verbatim,
|
||||
## symbols by name; a `(cdef ...)` body is dropped when the name is dead, or
|
||||
## when it is a `'u'` unique definition this module does not own. The body's
|
||||
## prototype lives in the surrounding raw text (cgen emits a forward
|
||||
## declaration for every *used* proc, independent of where the body lands), so
|
||||
## a dropped body still leaves a valid declaration — no synthesis needed. The
|
||||
## head groups (meta/cdata/cref/cdeps) carry no C text.
|
||||
result = ""
|
||||
if not fileExists(artifact): return
|
||||
var pool = newPool()
|
||||
var tags = newTagPool()
|
||||
let stmtsTag = tags.registerTag("stmts")
|
||||
let cdefTag = tags.registerTag("cdef")
|
||||
var buf = parseFromFile(artifact, 1000, pool, tags)
|
||||
var c = beginRead(buf)
|
||||
if c.kind != TagLit or c.cursorTagId != stmtsTag:
|
||||
endRead(c)
|
||||
return
|
||||
c.loopInto:
|
||||
case c.kind
|
||||
of StrLit:
|
||||
result.add strVal(c)
|
||||
inc c
|
||||
of Symbol, Ident:
|
||||
result.add symOrIdentName(c)
|
||||
inc c
|
||||
of TagLit:
|
||||
if c.cursorTagId == cdefTag:
|
||||
# fixed head: SymbolDef, flags (Ident or empty), nifname StrLit; the
|
||||
# rest is the definition's body text. `state` counts past the head.
|
||||
var name = ""
|
||||
var isUnique = false
|
||||
var isData = false
|
||||
var keep = true
|
||||
var state = 0
|
||||
c.loopInto:
|
||||
if state == 0 and c.kind == SymbolDef:
|
||||
name = symName(c)
|
||||
state = 1
|
||||
inc c
|
||||
elif state == 1: # the flags field (one token: Ident/Symbol or empty)
|
||||
if c.kind in {Ident, Symbol}:
|
||||
for ch in symOrIdentName(c):
|
||||
if ch == 'u': isUnique = true
|
||||
elif ch == 'd': isData = true
|
||||
state = 2
|
||||
inc c
|
||||
elif state == 2: # the NIF name (one StrLit) — decide keep here
|
||||
let owned = d.owners.getOrDefault(name, ownerId) == ownerId
|
||||
keep =
|
||||
if isData: owned # data: kept by its owner only
|
||||
elif isUnique: (name in d.live) and owned
|
||||
else: name in d.live # inline/dispatcher: per-TU
|
||||
if not keep: inc dropped
|
||||
state = 3
|
||||
inc c
|
||||
else: # body tokens
|
||||
if keep:
|
||||
if c.kind == StrLit: result.add strVal(c)
|
||||
elif c.kind in {Symbol, Ident}: result.add symOrIdentName(c)
|
||||
inc c
|
||||
else:
|
||||
# head groups (meta/cdata/cref/cdeps) carry no C text
|
||||
skip c
|
||||
else:
|
||||
inc c
|
||||
endRead(c)
|
||||
@@ -24,7 +24,7 @@ bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep")
|
||||
bootSwitch(usedGoGC, defined(gogc), "--gc:go")
|
||||
bootSwitch(usedNoGC, defined(nogc), "--gc:none")
|
||||
|
||||
import std/[setutils, sets, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
|
||||
import std/[setutils, os, strutils, parseutils, parseopt, sequtils, strtabs, enumutils]
|
||||
import
|
||||
msgs, options, nversion, condsyms, extccomp, platform,
|
||||
wordrecg, nimblecmd, lineinfos, pathutils
|
||||
@@ -118,7 +118,7 @@ const
|
||||
errInvalidCmdLineOption = "invalid command line option: '$1'"
|
||||
errOnOrOffExpectedButXFound = "'on' or 'off' expected, but '$1' found"
|
||||
errOnOffOrListExpectedButXFound = "'on', 'off' or 'list' expected, but '$1' found"
|
||||
errOffHintsError = "'off', 'hint', 'warning', 'error' or 'usages' expected, but '$1' found"
|
||||
errOffHintsError = "'off', 'hint', 'error' or 'usages' expected, but '$1' found"
|
||||
|
||||
proc invalidCmdLineOption(conf: ConfigRef; pass: TCmdLinePass, switch: string, info: TLineInfo) =
|
||||
if switch == " ": localError(conf, info, errInvalidCmdLineOption % "-")
|
||||
@@ -245,12 +245,11 @@ proc processCompile(conf: ConfigRef; filename: string) =
|
||||
extccomp.addExternalFileToCompile(conf, found)
|
||||
|
||||
const
|
||||
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'yrc', 'atomicArc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
|
||||
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'atomicArc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
|
||||
errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found"
|
||||
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
|
||||
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
|
||||
errInvalidFeatureButXFound = Feature.toSeq.map(proc(val:Feature): string = "'$1'" % $val).join(", ") & " expected, but '$1' found"
|
||||
errDefaultOrSsoExpectedButXFound = "'default' or 'sso' expected, but '$1' found"
|
||||
|
||||
template warningOptionNoop(switch: string) =
|
||||
warningDeprecated(conf, info, "'$#' is deprecated, now a noop" % switch)
|
||||
@@ -267,7 +266,6 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
of "markandsweep": result = conf.selectedGC == gcMarkAndSweep
|
||||
of "destructors", "arc": result = conf.selectedGC == gcArc
|
||||
of "orc": result = conf.selectedGC == gcOrc
|
||||
of "yrc": result = conf.selectedGC == gcYrc
|
||||
of "hooks": result = conf.selectedGC == gcHooks
|
||||
of "go": result = conf.selectedGC == gcGo
|
||||
of "none": result = conf.selectedGC == gcNone
|
||||
@@ -307,13 +305,6 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errInvalidExceptionSystem % arg)
|
||||
of "strings":
|
||||
case arg.normalize
|
||||
of "default": result = conf.selectedStrings == stringDefault
|
||||
of "sso": result = conf.selectedStrings == stringSso
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
|
||||
of "experimental":
|
||||
try:
|
||||
result = conf.features.contains parseEnum[Feature](arg)
|
||||
@@ -373,7 +364,6 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
|
||||
result = false
|
||||
of "panics": result = contains(conf.globalOptions, optPanics)
|
||||
of "jsbigint64": result = contains(conf.globalOptions, optJsBigInt64)
|
||||
of "mangle": result = contains(conf.globalOptions, optItaniumMangle)
|
||||
else:
|
||||
result = false
|
||||
invalidCmdLineOption(conf, passCmd1, switch, info)
|
||||
@@ -503,13 +493,10 @@ proc parseCommand*(command: string): Command =
|
||||
of "gendepend": cmdGendepend
|
||||
of "dump": cmdDump
|
||||
of "parse": cmdParse
|
||||
of "rod": cmdRod
|
||||
of "secret": cmdInteractive
|
||||
of "nop", "help": cmdNop
|
||||
of "jsonscript": cmdJsonscript
|
||||
of "nifc": cmdNifC # generate C from NIF files
|
||||
of "ic": cmdIc # generate .build.nif for nifmake
|
||||
of "icconfig": cmdIcConfig # produce the precompiled config artifact
|
||||
of "track": cmdTrack # IDE goto-def / find-usages over `nim ic`'s NIF output
|
||||
else: cmdUnknown
|
||||
|
||||
proc setCmd*(conf: ConfigRef, cmd: Command) =
|
||||
@@ -522,11 +509,6 @@ proc setCmd*(conf: ConfigRef, cmd: Command) =
|
||||
of cmdCompileToOC: conf.backend = backendObjc
|
||||
of cmdCompileToJS: conf.backend = backendJs
|
||||
of cmdCompileToNif: conf.backend = backendNif
|
||||
of cmdNifC:
|
||||
conf.backend = backendC # NIF to C compilation
|
||||
of cmdM:
|
||||
# cmdM requires optCompress for proper IC handling (include files, etc.)
|
||||
conf.globalOptions.incl optCompress
|
||||
else: discard
|
||||
|
||||
proc setCommandEarly*(conf: ConfigRef, command: string) =
|
||||
@@ -581,7 +563,6 @@ proc unregisterArcOrc*(conf: ConfigRef) =
|
||||
undefSymbol(conf.symbols, "gcdestructors")
|
||||
undefSymbol(conf.symbols, "gcarc")
|
||||
undefSymbol(conf.symbols, "gcorc")
|
||||
undefSymbol(conf.symbols, "gcyrc")
|
||||
undefSymbol(conf.symbols, "gcatomicarc")
|
||||
undefSymbol(conf.symbols, "nimSeqsV2")
|
||||
undefSymbol(conf.symbols, "nimV2")
|
||||
@@ -615,10 +596,6 @@ proc processMemoryManagementOption(switch, arg: string, pass: TCmdLinePass,
|
||||
conf.selectedGC = gcOrc
|
||||
defineSymbol(conf.symbols, "gcorc")
|
||||
registerArcOrc(pass, conf)
|
||||
of "yrc":
|
||||
conf.selectedGC = gcYrc
|
||||
defineSymbol(conf.symbols, "gcyrc")
|
||||
registerArcOrc(pass, conf)
|
||||
of "atomicarc":
|
||||
conf.selectedGC = gcAtomicArc
|
||||
defineSymbol(conf.symbols, "gcatomicarc")
|
||||
@@ -655,18 +632,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf: ConfigRef) =
|
||||
var key = ""
|
||||
var val = ""
|
||||
# Record config-file switches so the `nim ic` driver can serialise them into a
|
||||
# precompiled-config artifact and have its per-module child processes replay
|
||||
# them instead of re-parsing the `nim.cfg` chain (and re-running `config.nims`
|
||||
# in the VM) on every invocation. Only `passPP` (config-file) switches are
|
||||
# captured; command-line switches are forwarded by the build graph as usual.
|
||||
# Path-search switches are skipped: their net effect already lives in the
|
||||
# resolved `searchPaths` the driver forwards as `--path`, and replaying their
|
||||
# raw (often relative-to-config-dir) arguments here would misresolve.
|
||||
if pass == passPP and switch.normalize notin
|
||||
["path", "p", "nimblepath", "lazypath", "excludepath",
|
||||
"nonimblepath", "clearnimblepath", "nimcache"]:
|
||||
conf.icConfigSwitches.add (switch, arg)
|
||||
case switch.normalize
|
||||
of "eval":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
@@ -719,14 +684,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf.outDir = processPath(conf, arg, info, notRelativeToProj=true)
|
||||
of "usenimcache":
|
||||
processOnOffSwitchG(conf, {optUseNimcache}, arg, pass, info)
|
||||
of "ideimports":
|
||||
# nimsuggest: where the import closure comes from. IC is opt-in.
|
||||
# nif|on load unchanged imports from precompiled NIF (cmdM)
|
||||
# source|off (default) recompile the whole closure from source (cmdCheck)
|
||||
case arg.normalize
|
||||
of "nif", "on", "": conf.ideImportsFromNif = true
|
||||
of "source", "off": conf.ideImportsFromNif = false
|
||||
else: localError(conf, info, "'--ideImports' expects 'nif' or 'source', got: '$1'" % arg)
|
||||
of "docseesrcurl":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.docSeeSrcUrl = arg
|
||||
@@ -780,17 +737,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "mm":
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "strings":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
case arg.normalize
|
||||
of "default":
|
||||
conf.selectedStrings = stringDefault
|
||||
of "sso":
|
||||
conf.selectedStrings = stringSso
|
||||
defineSymbol(conf.symbols, "nimsso")
|
||||
else:
|
||||
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
|
||||
of "warnings", "w":
|
||||
if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
|
||||
of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
|
||||
@@ -816,18 +762,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
conf.globalOptions.excl optCDebug
|
||||
else:
|
||||
localError(conf, info, "expected native|gdb|on|off but found " & arg)
|
||||
of "mangle":
|
||||
case arg.normalize
|
||||
of "nim":
|
||||
conf.globalOptions.excl optItaniumMangle
|
||||
of "cpp":
|
||||
conf.globalOptions.incl optItaniumMangle
|
||||
else:
|
||||
localError(conf, info, "expected nim|cpp but found " & arg)
|
||||
of "compress":
|
||||
conf.globalOptions.incl optCompress
|
||||
of "genbif":
|
||||
processOnOffSwitchG(conf, {optGenBif}, arg, pass, info)
|
||||
of "g": # alias for --debugger:native
|
||||
conf.globalOptions.incl optCDebug
|
||||
conf.options.incl optLineDir
|
||||
@@ -947,65 +881,14 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
else: localError(conf, info, errOnOrOffExpectedButXFound % arg)
|
||||
of "noimportdoc":
|
||||
processOnOffSwitchG(conf, {optNoImportdoc}, arg, pass, info)
|
||||
of "ismainmodule":
|
||||
# `nim m` (IC) only: marks the single module being checked as the program's
|
||||
# real entry point so that `isMainModule` and `when isMainModule:` resolve
|
||||
# correctly even though every module is compiled with `sfMainModule` set.
|
||||
conf.isMainModule = switchOn(arg)
|
||||
of "icgroup":
|
||||
# `nim m` only: register a module that belongs to the current strongly-
|
||||
# connected import group, so it is compiled from source (not loaded from a
|
||||
# precompiled NIF) and gets its own NIF written. `deps.nim` emits one
|
||||
# `--icGroup:<path>` per member of a dependency cycle. The argument is an
|
||||
# absolute .nim path produced by the dependency scanner.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icGroup.incl(canonicalizePath(conf, AbsoluteFile arg).string)
|
||||
of "icproject":
|
||||
# `nim m`/`nim nifc` only: the ORIGINAL project file (see options.icProject)
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icProject = canonicalizePath(conf, AbsoluteFile arg).string
|
||||
of "icpreparsedconfig":
|
||||
# `nim m`/`nim nifc` only: path of the precompiled-config artifact (see
|
||||
# options.icPreparsedConfig). Read in `passCmd1`, before `loadConfigs`, so
|
||||
# config loading can replay it instead of re-parsing the `nim.cfg` chain.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.icPreparsedConfig = arg
|
||||
of "icconfigout":
|
||||
# `nim icconfig` only: where to write the precompiled config artifact (see
|
||||
# options.icConfigOut). The `nim ic` driver spawns the producer with this.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
conf.icConfigOut = arg
|
||||
of "icbackendstage":
|
||||
# `nim nifc` only: per-module backend stage, one of cg|merge|emit (see
|
||||
# options.icBackendStage). Empty (switch unused) keeps the whole-program
|
||||
# backend. Emitted by `deps.nim`'s backend build file.
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icBackendStage = arg
|
||||
of "icbackendmodule":
|
||||
# `nim nifc` only: the NIF module suffix the cg/emit stage operates on (see
|
||||
# options.icBackendModule).
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
conf.icBackendModule = arg
|
||||
of "import":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
let m = findModule(conf, arg, toFullPath(conf, info)).string
|
||||
if m.len == 0:
|
||||
localError(conf, info, "Cannot resolve filename: " & arg)
|
||||
else:
|
||||
conf.implicitImports.add(if arg.startsWith(stdPrefix): arg else: m)
|
||||
conf.implicitImports.add findModule(conf, arg, toFullPath(conf, info)).string
|
||||
of "include":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
let m = findModule(conf, arg, toFullPath(conf, info)).string
|
||||
if m.len == 0:
|
||||
localError(conf, info, "Cannot resolve filename: " & arg)
|
||||
else:
|
||||
conf.implicitIncludes.add m
|
||||
conf.implicitIncludes.add findModule(conf, arg, toFullPath(conf, info)).string
|
||||
of "listcmd":
|
||||
processOnOffSwitchG(conf, {optListCmd}, arg, pass, info)
|
||||
of "asm":
|
||||
@@ -1037,7 +920,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
var value: int = 10_000_000
|
||||
discard parseSaturatedNatural(arg, value)
|
||||
if value <= 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero")
|
||||
if not value > 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero")
|
||||
conf.maxLoopIterationsVM = value
|
||||
of "maxcalldepthvm":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
@@ -1091,8 +974,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
# xxx maybe also ic, since not in help?
|
||||
if pass in {passCmd2, passPP}:
|
||||
case arg.normalize
|
||||
of "on": conf.ic = true
|
||||
of "legacy": conf.symbolFiles = v2Sf
|
||||
of "on": conf.symbolFiles = v2Sf
|
||||
of "off": conf.symbolFiles = disabledSf
|
||||
of "writeonly": conf.symbolFiles = writeOnlySf
|
||||
of "readonly": conf.symbolFiles = readOnlySf
|
||||
@@ -1200,9 +1082,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "shownonexports":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
showNonExportedFields(conf)
|
||||
of "raw":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
docRawOutput(conf)
|
||||
of "exceptions":
|
||||
case arg.normalize
|
||||
of "cpp": conf.exc = excCpp
|
||||
@@ -1234,10 +1113,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
of "stylecheck":
|
||||
case arg.normalize
|
||||
of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError, optStyleWarning}
|
||||
of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} - {optStyleError, optStyleWarning}
|
||||
of "warning": conf.globalOptions = conf.globalOptions + {optStyleWarning} - {optStyleHint, optStyleError}
|
||||
of "error": conf.globalOptions = conf.globalOptions + {optStyleError} - {optStyleHint, optStyleWarning}
|
||||
of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError}
|
||||
of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} - {optStyleError}
|
||||
of "error": conf.globalOptions = conf.globalOptions + {optStyleError}
|
||||
of "usages": conf.globalOptions.incl optStyleUsages
|
||||
else: localError(conf, info, errOffHintsError % arg)
|
||||
of "showallmismatches":
|
||||
@@ -1327,16 +1205,8 @@ proc processArgument*(pass: TCmdLinePass; p: OptParser;
|
||||
# support UNIX style filenames everywhere for portable build scripts:
|
||||
if config.projectName.len == 0:
|
||||
config.projectName = unixToNativePath(p.key)
|
||||
if config.cmd == cmdTrack:
|
||||
# `nim track PROJ --def:...`: unlike a normal command (where everything
|
||||
# after the project file is passed to the compiled program), `track`
|
||||
# accepts its IDE-query switches AFTER the project — the natural,
|
||||
# nimsuggest-like invocation form. So don't swallow the rest of the line
|
||||
# into `arguments`; keep parsing the remaining tokens as switches.
|
||||
result = false
|
||||
else:
|
||||
config.arguments = cmdLineRest(p)
|
||||
result = true
|
||||
config.arguments = cmdLineRest(p)
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
inc argsCount
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
## for details. Note this is a first implementation and only the "Concept matching"
|
||||
## section has been implemented.
|
||||
|
||||
import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
|
||||
import ast, astalgo, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
|
||||
|
||||
import std/sets
|
||||
import std/intsets
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
@@ -29,7 +29,7 @@ proc declareSelf(c: PContext; info: TLineInfo) =
|
||||
let ow = getCurrOwner(c)
|
||||
let s = newSym(skType, getIdent(c.cache, "Self"), c.idgen, ow, info)
|
||||
s.typ = newType(tyTypeDesc, c.idgen, ow)
|
||||
s.typ.incl {tfUnresolved, tfPacked}
|
||||
s.typ.flags.incl {tfUnresolved, tfPacked}
|
||||
s.typ.add newType(tyEmpty, c.idgen, ow)
|
||||
addDecl(c, s, info)
|
||||
|
||||
@@ -73,20 +73,18 @@ type
|
||||
MatchFlags* = enum
|
||||
mfDontBind # Do not bind generic parameters
|
||||
mfCheckGeneric # formal <- formal comparison as opposed to formal <- operand
|
||||
|
||||
ConceptTypePair = tuple[conceptId, typeId: ItemId]
|
||||
## Pair of (concept type id, implementation type id) used for cycle detection
|
||||
|
||||
|
||||
MatchCon = object ## Context we pass around during concept matching.
|
||||
bindings: LayeredIdTable
|
||||
marker: HashSet[ConceptTypePair] ## Tracks (concept, type) pairs being checked to detect cycles.
|
||||
marker: IntSet ## Some protection against wild runaway recursions.
|
||||
potentialImplementation: PType ## the concrete type that might match the concept we try to match.
|
||||
magic: TMagic ## mArrGet and mArrPut is wrong in system.nim and
|
||||
## cannot be fixed that easily.
|
||||
## Thus we special case it here.
|
||||
concpt: PType ## current concept being evaluated
|
||||
depthCount = 0
|
||||
flags: set[MatchFlags]
|
||||
|
||||
|
||||
MatchKind = enum
|
||||
mkNoMatch, mkSubset, mkSame
|
||||
|
||||
@@ -139,7 +137,7 @@ proc bindParam(c: PContext, m: var MatchCon; key, v: PType): bool {. discardable
|
||||
# check previously bound value
|
||||
if not matchType(c, old, value, m):
|
||||
return false
|
||||
elif key.hasElementType and not key.elementType.isNil and key.elementType.kind != tyNone:
|
||||
elif key.hasElementType and key.elementType.kind != tyNone:
|
||||
# check constaint
|
||||
if matchType(c, unrollGenericParam(key), value, m) == false:
|
||||
return false
|
||||
@@ -190,48 +188,32 @@ iterator traverseTyOr(t: PType): PType {. closure .}=
|
||||
proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon): bool =
|
||||
assert not(potentialImpl.reduceToBase.kind == tyConcept)
|
||||
let concpt = f.reduceToBase
|
||||
|
||||
# Handle self-referential concepts: when a concept references itself in its body
|
||||
# (e.g., `A = concept; proc test(x: Self, y: A)`), the inner type A has n=nil.
|
||||
# We detect this by checking if the concept has the same symbol name as the
|
||||
# one we're currently matching and has no body (n=nil).
|
||||
if concpt.n.isNil:
|
||||
if concpt.sym != nil and m.concpt.sym != nil and
|
||||
concpt.sym == m.concpt.sym:
|
||||
# Self-reference: check if potentialImpl matches what we're already checking
|
||||
return potentialImpl.id == m.potentialImplementation.id
|
||||
# Concept without body that's not a self-reference - cannot match
|
||||
return false
|
||||
|
||||
# Cycle detection: track (concept, type) pairs to prevent infinite recursion.
|
||||
# Returns true on cycle (coinductive semantics) to support co-dependent concepts.
|
||||
let pair: ConceptTypePair = (concpt.itemId, potentialImpl.itemId)
|
||||
if pair in m.marker:
|
||||
if m.depthCount > 0:
|
||||
# concepts that are more then 2 levels deep are treated like
|
||||
# tyAnything to stop dependencies from getting out of control
|
||||
return true
|
||||
m.marker.incl pair
|
||||
|
||||
var efPot = potentialImpl
|
||||
if potentialImpl.isSelf:
|
||||
if m.concpt.n == concpt.n:
|
||||
m.marker.excl pair
|
||||
return true
|
||||
efPot = m.potentialImplementation
|
||||
|
||||
|
||||
var oldBindings = m.bindings
|
||||
m.bindings = newTypeMapLayer(m.bindings)
|
||||
let oldPotentialImplementation = m.potentialImplementation
|
||||
m.potentialImplementation = efPot
|
||||
let oldConcept = m.concpt
|
||||
m.concpt = concpt
|
||||
|
||||
|
||||
var invocation: PType = nil
|
||||
if f.kind in {tyGenericInvocation, tyGenericInst}:
|
||||
invocation = f
|
||||
inc m.depthCount
|
||||
result = processConcept(c, concpt, invocation, oldBindings, m)
|
||||
dec m.depthCount
|
||||
m.potentialImplementation = oldPotentialImplementation
|
||||
m.concpt = oldConcept
|
||||
m.bindings = oldBindings
|
||||
m.marker.excl pair
|
||||
|
||||
proc cmpConceptDefs(c: PContext, fn, an: PNode, m: var MatchCon): bool=
|
||||
if fn.kind != an.kind:
|
||||
@@ -281,22 +263,6 @@ proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind =
|
||||
return mkNoMatch
|
||||
return mkSubset
|
||||
|
||||
proc isObjectSubtype(f, a: PType): bool =
|
||||
var t = a
|
||||
result = false
|
||||
while t != nil:
|
||||
t = t.baseClass
|
||||
if t == nil:
|
||||
break
|
||||
t = t.skipTypes({tyPtr,tyRef})
|
||||
if t == nil:
|
||||
break
|
||||
if t.kind != tyObject:
|
||||
break
|
||||
if sameObjectTypes(f, t):
|
||||
result = true
|
||||
break
|
||||
|
||||
proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
|
||||
## The heart of the concept matching process. 'f' is the formal parameter of some
|
||||
## routine inside the concept that we're looking for. 'a' is the formal parameter
|
||||
@@ -361,8 +327,6 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
|
||||
result = a.base.sym == f.sym
|
||||
else:
|
||||
result = sameType(f, a)
|
||||
if not result and f.kind == tyObject and a.kind == tyObject:
|
||||
result = isObjectSubtype(f, a)
|
||||
of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid:
|
||||
result = a.skipTypes(ignorableForArgType).kind == f.kind
|
||||
of tyBool, tyChar, tyInt..tyUInt64:
|
||||
@@ -394,14 +358,6 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
|
||||
if not matchType(c, f[i], ea[i], m):
|
||||
result = false
|
||||
break
|
||||
elif f.kind == tyGenericInvocation:
|
||||
# bind potential generic constraints into body
|
||||
let body = f.base
|
||||
for i in 1 ..< len(f):
|
||||
bindParam(c,m,body[i-1], f[i])
|
||||
result = matchType(c, body, a, m)
|
||||
else: # tyGenericInst
|
||||
result = matchType(c, f.last, a, m)
|
||||
of tyOrdinal:
|
||||
result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam
|
||||
of tyStatic:
|
||||
@@ -628,7 +584,7 @@ proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable
|
||||
## `C[S, T]` parent type that we look for. We need this because we need to store bindings
|
||||
## for 'S' and 'T' inside 'bindings' on a successful match. It is very important that
|
||||
## we do not add any bindings at all on an unsuccessful match!
|
||||
var m = MatchCon(bindings: bindings, potentialImplementation: arg, concpt: concpt, flags: flags, marker: initHashSet[ConceptTypePair]())
|
||||
var m = MatchCon(bindings: bindings, potentialImplementation: arg, concpt: concpt, flags: flags)
|
||||
if arg.isConcept:
|
||||
result = conceptsMatch(c, concpt.reduceToBase, arg.reduceToBase, m) >= mkSubset
|
||||
elif arg.acceptsAllTypes:
|
||||
|
||||
@@ -175,5 +175,3 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimHasSetLengthSeqUninitMagic")
|
||||
defineSymbol("nimHasPreviewDuplicateModuleError")
|
||||
|
||||
defineSymbol("nimHasImplicitRangeConversion")
|
||||
|
||||
|
||||
1434
compiler/deps.nim
1434
compiler/deps.nim
File diff suppressed because it is too large
Load Diff
@@ -483,7 +483,7 @@ proc constructCfg*(s: PSym; body: PNode; root: PSym): ControlFlowGraph =
|
||||
gen(c, body)
|
||||
if root.kind == skResult:
|
||||
genImplicitReturn(c)
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result = c.code # will move
|
||||
else:
|
||||
shallowCopy(result, c.code)
|
||||
|
||||
@@ -19,7 +19,7 @@ import
|
||||
wordrecg, syntaxes, renderer, lexer,
|
||||
packages/docutils/[rst, rstidx, rstgen, dochelpers],
|
||||
trees, types,
|
||||
typesrenderer, lineinfos,
|
||||
typesrenderer, astalgo, lineinfos,
|
||||
pathutils, nimpaths, renderverbatim, packages
|
||||
import packages/docutils/rstast except FileIndex, TLineInfo
|
||||
|
||||
@@ -148,7 +148,7 @@ proc cmpDecimalsIgnoreCase(a, b: string): int =
|
||||
limitB = iB
|
||||
while limitA < aLen and isDigit(a[limitA]): inc limitA
|
||||
while limitB < bLen and isDigit(b[limitB]): inc limitB
|
||||
var pos = max(limitA-iA, limitB-iB)
|
||||
var pos = max(limitA-iA, limitB-iA)
|
||||
while pos > 0:
|
||||
if limitA-pos < iA: # digit in `a` is 0 effectively
|
||||
result = ord('0') - ord(b[limitB-pos])
|
||||
@@ -433,9 +433,6 @@ proc getVarIdx(varnames: openArray[string], id: string): int =
|
||||
|
||||
proc genComment(d: PDoc, n: PNode): PRstNode =
|
||||
if n.comment.len > 0:
|
||||
if optDocRaw in d.conf.globalOptions:
|
||||
return newRstLeaf(n.comment)
|
||||
|
||||
d.sharedState.currFileIdx = addRstFileIndex(d, n.info)
|
||||
try:
|
||||
result = parseRst(n.comment,
|
||||
@@ -540,11 +537,10 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var string;
|
||||
elif s != nil and s.kind in {skType, skVar, skLet, skConst} and
|
||||
sfExported in s.flags and s.owner != nil and
|
||||
belongsToProjectPackage(d.conf, s.owner) and d.target == outHtml:
|
||||
let href = (if d.module == s.owner: ""
|
||||
else: externalDep(d, s.owner).changeFileExt("html")
|
||||
) & "#" & literal
|
||||
result.addf "<a href=\"$1\"><span class=\"Identifier\">$2</span></a>",
|
||||
[href, escLit]
|
||||
let external = externalDep(d, s.owner)
|
||||
result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
|
||||
[changeFileExt(external, "html"), literal,
|
||||
escLit]
|
||||
else:
|
||||
dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
|
||||
"\\spanIdentifier{$1}", [escLit])
|
||||
@@ -1180,12 +1176,8 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind, nonExports = false):
|
||||
"col": %n.info.col}
|
||||
)
|
||||
if comm != nil:
|
||||
if optDocRaw in d.conf.globalOptions:
|
||||
result.json["description"] = %comm.text
|
||||
else:
|
||||
result.rst = comm
|
||||
result.rstField = "description"
|
||||
|
||||
result.rst = comm
|
||||
result.rstField = "description"
|
||||
if r.buf.len > 0:
|
||||
result.json["code"] = %r.buf
|
||||
if k in routineKinds:
|
||||
@@ -1328,7 +1320,7 @@ proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, id
|
||||
if t.startsWith("ref "): t = substr(t, 4)
|
||||
effects[i] = newIdentNode(getIdent(cache, t), n.info)
|
||||
# set the type so that the following analysis doesn't screw up:
|
||||
effects[i].typ = real[i].typ
|
||||
effects[i].typ() = real[i].typ
|
||||
|
||||
result = newTreeI(nkExprColonExpr, n.info,
|
||||
newIdentNode(getIdent(cache, $effectType), n.info), effects)
|
||||
@@ -1426,7 +1418,7 @@ proc generateDoc*(d: PDoc, n, orig: PNode, config: ConfigRef, docFlags: DocFlags
|
||||
of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept"
|
||||
of nkFromStmt, nkImportExceptStmt: traceDeps(d, n[0])
|
||||
of nkCallKinds:
|
||||
var comm = default(ItemPre)
|
||||
var comm: ItemPre = default(ItemPre)
|
||||
getAllRunnableExamples(d, n, comm)
|
||||
if comm.len != 0: d.modDescPre.add(comm)
|
||||
else: discard
|
||||
|
||||
@@ -47,8 +47,8 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
|
||||
n[bodyPos] = body
|
||||
n[resultPos] = newSymNode(res)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}
|
||||
setHookDisamb(g, result, "$enumtostr", t)
|
||||
incl result.flags, sfFromGeneric
|
||||
incl result.flags, sfNeverRaises
|
||||
|
||||
proc searchObjCaseImpl(obj: PNode; field: PSym): PNode =
|
||||
case obj.kind
|
||||
@@ -110,4 +110,5 @@ proc genCaseObjDiscMapping*(t: PType; field: PSym; info: TLineInfo; g: ModuleGra
|
||||
n[bodyPos] = body
|
||||
n[resultPos] = newSymNode(res)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}
|
||||
incl result.flags, sfFromGeneric
|
||||
incl result.flags, sfNeverRaises
|
||||
|
||||
@@ -275,7 +275,7 @@ proc unpackObject(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
# the nkPar node:
|
||||
if n.isNil:
|
||||
result = newNode(nkTupleConstr)
|
||||
result.typ = typ
|
||||
result.typ() = typ
|
||||
if typ.n.isNil:
|
||||
internalError(conf, "cannot unpack unnamed tuple")
|
||||
unpackObjectAdd(conf, x, typ.n, result)
|
||||
@@ -298,7 +298,7 @@ proc unpackObject(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
proc unpackArray(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
if n.isNil:
|
||||
result = newNode(nkBracket)
|
||||
result.typ = typ
|
||||
result.typ() = typ
|
||||
newSeq(result.sons, lengthOrd(conf, typ).toInt)
|
||||
else:
|
||||
result = n
|
||||
@@ -319,7 +319,7 @@ proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
template aw(k, v, field: untyped): untyped =
|
||||
if n.isNil:
|
||||
result = newNode(k)
|
||||
result.typ = typ
|
||||
result.typ() = typ
|
||||
else:
|
||||
# check we have the right field:
|
||||
result = n
|
||||
@@ -333,12 +333,12 @@ proc unpack(conf: ConfigRef, x: pointer, typ: PType, n: PNode): PNode =
|
||||
template setNil() =
|
||||
if n.isNil:
|
||||
result = newNode(nkNilLit)
|
||||
result.typ = typ
|
||||
result.typ() = typ
|
||||
else:
|
||||
reset n[]
|
||||
result = n
|
||||
result[] = TNode(kind: nkNilLit)
|
||||
result.typ = typ
|
||||
result.typ() = typ
|
||||
|
||||
template awi(kind, v: untyped): untyped = aw(kind, v, intVal)
|
||||
template awf(kind, v: untyped): untyped = aw(kind, v, floatVal)
|
||||
@@ -427,7 +427,7 @@ proc fficast*(conf: ConfigRef, x: PNode, destTyp: PType): PNode =
|
||||
# cast through a pointer needs a new inner object:
|
||||
let y = if x.kind == nkRefTy: newNodeI(nkRefTy, x.info, 1)
|
||||
else: x.copyTree
|
||||
y.typ = x.typ
|
||||
y.typ() = x.typ
|
||||
result = unpack(conf, a, destTyp, y)
|
||||
dealloc a
|
||||
|
||||
@@ -481,7 +481,7 @@ proc callForeignFunction*(conf: ConfigRef, fn: PNode, fntyp: PType,
|
||||
if aTyp.isNil:
|
||||
internalAssert conf, i+1 < fntyp.len
|
||||
aTyp = fntyp[i+1]
|
||||
args[i+start].typ = aTyp
|
||||
args[i+start].typ() = aTyp
|
||||
sig[i] = mapType(conf, aTyp)
|
||||
if sig[i].isNil: globalError(conf, info, "cannot map FFI type")
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode =
|
||||
d.add newSymNode(sym, info)
|
||||
result.add d
|
||||
result.add res
|
||||
result.typ = res.typ
|
||||
result.typ() = res.typ
|
||||
|
||||
proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
|
||||
conf: ConfigRef;
|
||||
|
||||
@@ -46,7 +46,7 @@ proc isLocation(n: PNode): bool = not n.isValue
|
||||
|
||||
proc isLet(n: PNode): bool =
|
||||
if n.kind == nkSym:
|
||||
if n.sym.kind in {skLet, skConst, skTemp, skForVar}: # guard immutable variables
|
||||
if n.sym.kind in {skLet, skTemp, skForVar}:
|
||||
result = true
|
||||
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
|
||||
abstractInst).kind notin {tyVar}:
|
||||
@@ -1104,7 +1104,7 @@ proc settype(n: PNode): PType =
|
||||
|
||||
proc buildOf(it, loc: PNode; o: Operators): PNode =
|
||||
var s = newNodeI(nkCurly, it.info, it.len-1)
|
||||
s.typ = settype(loc)
|
||||
s.typ() = settype(loc)
|
||||
for i in 0..<it.len-1: s[i] = it[i]
|
||||
result = newNodeI(nkCall, it.info, 3)
|
||||
result[0] = newSymNode(o.opContains)
|
||||
@@ -1170,7 +1170,7 @@ proc buildProperFieldCheck(access, check: PNode; o: Operators): PNode =
|
||||
# set field name to discriminator field name
|
||||
a[1] = check[2]
|
||||
# set discriminator field type: important for `neg`
|
||||
a.typ = check[2].typ
|
||||
a.typ() = check[2].typ
|
||||
result[2] = a
|
||||
# 'access.kind != nkDotExpr' can happen for object constructors
|
||||
# which we don't check yet
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# This include implements the high level optimization pass.
|
||||
# included from sem.nim
|
||||
|
||||
proc hlo(c: PContext, n: PNode, loopDetector: int): PNode
|
||||
proc hlo(c: PContext, n: PNode): PNode
|
||||
|
||||
proc evalPattern(c: PContext, n, orig: PNode): PNode =
|
||||
internalAssert c.config, n.kind == nkCall and n[0].kind == nkSym
|
||||
@@ -61,11 +61,10 @@ proc applyPatterns(c: PContext, n: PNode): PNode =
|
||||
# activate this pattern again:
|
||||
c.patterns[i] = pattern
|
||||
|
||||
proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
|
||||
proc hlo(c: PContext, n: PNode): PNode =
|
||||
inc(c.hloLoopDetector)
|
||||
# simply stop and do not perform any further transformations:
|
||||
if loopDetector > 300:
|
||||
message(c.config, n.info, warnUser, "term rewrite macro instantiation too nested")
|
||||
return n
|
||||
if c.hloLoopDetector > 300: return n
|
||||
case n.kind
|
||||
of nkMacroDef, nkTemplateDef, procDefs:
|
||||
# already processed (special cases in semstmts.nim)
|
||||
@@ -81,7 +80,7 @@ proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
|
||||
# no optimization applied, try subtrees:
|
||||
for i in 0..<result.safeLen:
|
||||
let a = result[i]
|
||||
let h = hlo(c, a, loopDetector)
|
||||
let h = hlo(c, a)
|
||||
if h != a: result[i] = h
|
||||
else:
|
||||
# perform type checking, so that the replacement still fits:
|
||||
@@ -91,15 +90,17 @@ proc hlo(c: PContext, n: PNode, loopDetector: int): PNode =
|
||||
result = fitNode(c, n.typ, result, n.info)
|
||||
# optimization has been applied so check again:
|
||||
result = commonOptimizations(c.graph, c.idgen, c.module, result)
|
||||
result = hlo(c, result, loopDetector + 1)
|
||||
result = hlo(c, result)
|
||||
result = commonOptimizations(c.graph, c.idgen, c.module, result)
|
||||
|
||||
proc hloBody(c: PContext, n: PNode): PNode =
|
||||
# fast exit:
|
||||
if c.patterns.len == 0 or optTrMacros notin c.config.options: return n
|
||||
result = hlo(c, n, 0)
|
||||
c.hloLoopDetector = 0
|
||||
result = hlo(c, n)
|
||||
|
||||
proc hloStmt(c: PContext, n: PNode): PNode =
|
||||
# fast exit:
|
||||
if c.patterns.len == 0 or optTrMacros notin c.config.options: return n
|
||||
result = hlo(c, n, 0)
|
||||
c.hloLoopDetector = 0
|
||||
result = hlo(c, n)
|
||||
|
||||
178
compiler/ic/bitabs.nim
Normal file
178
compiler/ic/bitabs.nim
Normal file
@@ -0,0 +1,178 @@
|
||||
## A BiTable is a table that can be seen as an optimized pair
|
||||
## of `(Table[LitId, Val], Table[Val, LitId])`.
|
||||
|
||||
import std/hashes
|
||||
import rodfiles
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
LitId* = distinct uint32
|
||||
|
||||
BiTable*[T] = object
|
||||
vals: seq[T] # indexed by LitId
|
||||
keys: seq[LitId] # indexed by hash(val)
|
||||
|
||||
proc initBiTable*[T](): BiTable[T] = BiTable[T](vals: @[], keys: @[])
|
||||
|
||||
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
|
||||
result = (h + 1) and maxHash
|
||||
|
||||
template maxHash(t): untyped = high(t.keys)
|
||||
template isFilled(x: LitId): bool = x.uint32 > 0'u32
|
||||
|
||||
proc `$`*(x: LitId): string {.borrow.}
|
||||
proc `<`*(x, y: LitId): bool {.borrow.}
|
||||
proc `<=`*(x, y: LitId): bool {.borrow.}
|
||||
proc `==`*(x, y: LitId): bool {.borrow.}
|
||||
proc hash*(x: LitId): Hash {.borrow.}
|
||||
|
||||
|
||||
proc len*[T](t: BiTable[T]): int = t.vals.len
|
||||
|
||||
proc mustRehash(length, counter: int): bool {.inline.} =
|
||||
assert(length > counter)
|
||||
result = (length * 2 < counter * 3) or (length - counter < 4)
|
||||
|
||||
const
|
||||
idStart = 1
|
||||
|
||||
template idToIdx(x: LitId): int = x.int - idStart
|
||||
|
||||
proc hasLitId*[T](t: BiTable[T]; x: LitId): bool =
|
||||
let idx = idToIdx(x)
|
||||
result = idx >= 0 and idx < t.vals.len
|
||||
|
||||
proc enlarge[T](t: var BiTable[T]) =
|
||||
var n: seq[LitId]
|
||||
newSeq(n, len(t.keys) * 2)
|
||||
swap(t.keys, n)
|
||||
for i in 0..high(n):
|
||||
let eh = n[i]
|
||||
if isFilled(eh):
|
||||
var j = hash(t.vals[idToIdx eh]) and maxHash(t)
|
||||
while isFilled(t.keys[j]):
|
||||
j = nextTry(j, maxHash(t))
|
||||
t.keys[j] = move n[i]
|
||||
|
||||
proc getKeyId*[T](t: BiTable[T]; v: T): LitId =
|
||||
let origH = hash(v)
|
||||
var h = origH and maxHash(t)
|
||||
if t.keys.len != 0:
|
||||
while true:
|
||||
let litId = t.keys[h]
|
||||
if not isFilled(litId): break
|
||||
if t.vals[idToIdx t.keys[h]] == v: return litId
|
||||
h = nextTry(h, maxHash(t))
|
||||
return LitId(0)
|
||||
|
||||
proc getOrIncl*[T](t: var BiTable[T]; v: T): LitId =
|
||||
let origH = hash(v)
|
||||
var h = origH and maxHash(t)
|
||||
if t.keys.len != 0:
|
||||
while true:
|
||||
let litId = t.keys[h]
|
||||
if not isFilled(litId): break
|
||||
if t.vals[idToIdx t.keys[h]] == v: return litId
|
||||
h = nextTry(h, maxHash(t))
|
||||
# not found, we need to insert it:
|
||||
if mustRehash(t.keys.len, t.vals.len):
|
||||
enlarge(t)
|
||||
# recompute where to insert:
|
||||
h = origH and maxHash(t)
|
||||
while true:
|
||||
let litId = t.keys[h]
|
||||
if not isFilled(litId): break
|
||||
h = nextTry(h, maxHash(t))
|
||||
else:
|
||||
setLen(t.keys, 16)
|
||||
h = origH and maxHash(t)
|
||||
|
||||
result = LitId(t.vals.len + idStart)
|
||||
t.keys[h] = result
|
||||
t.vals.add v
|
||||
|
||||
|
||||
proc `[]`*[T](t: var BiTable[T]; litId: LitId): var T {.inline.} =
|
||||
let idx = idToIdx litId
|
||||
assert idx < t.vals.len
|
||||
result = t.vals[idx]
|
||||
|
||||
proc `[]`*[T](t: BiTable[T]; litId: LitId): lent T {.inline.} =
|
||||
let idx = idToIdx litId
|
||||
assert idx < t.vals.len
|
||||
result = t.vals[idx]
|
||||
|
||||
proc hash*[T](t: BiTable[T]): Hash =
|
||||
## as the keys are hashes of the values, we simply use them instead
|
||||
var h: Hash = 0
|
||||
for i, n in pairs t.keys:
|
||||
h = h !& hash((i, n))
|
||||
result = !$h
|
||||
|
||||
proc store*[T](f: var RodFile; t: BiTable[T]) =
|
||||
storeSeq(f, t.vals)
|
||||
storeSeq(f, t.keys)
|
||||
|
||||
proc load*[T](f: var RodFile; t: var BiTable[T]) =
|
||||
loadSeq(f, t.vals)
|
||||
loadSeq(f, t.keys)
|
||||
|
||||
proc sizeOnDisc*(t: BiTable[string]): int =
|
||||
result = 4
|
||||
for x in t.vals:
|
||||
result += x.len + 4
|
||||
result += t.keys.len * sizeof(LitId)
|
||||
|
||||
when isMainModule:
|
||||
|
||||
var t: BiTable[string]
|
||||
|
||||
echo getOrIncl(t, "hello")
|
||||
|
||||
echo getOrIncl(t, "hello")
|
||||
echo getOrIncl(t, "hello3")
|
||||
echo getOrIncl(t, "hello4")
|
||||
echo getOrIncl(t, "helloasfasdfdsa")
|
||||
echo getOrIncl(t, "hello")
|
||||
echo getKeyId(t, "hello")
|
||||
echo getKeyId(t, "none")
|
||||
|
||||
for i in 0 ..< 100_000:
|
||||
discard t.getOrIncl($i & "___" & $i)
|
||||
|
||||
for i in 0 ..< 100_000:
|
||||
assert t.getOrIncl($i & "___" & $i).idToIdx == i + 4
|
||||
echo "begin"
|
||||
echo t.vals.len
|
||||
|
||||
echo t.vals[0]
|
||||
echo t.vals[1004]
|
||||
|
||||
echo "middle"
|
||||
|
||||
var tf: BiTable[float]
|
||||
|
||||
discard tf.getOrIncl(0.4)
|
||||
discard tf.getOrIncl(16.4)
|
||||
discard tf.getOrIncl(32.4)
|
||||
echo getKeyId(tf, 32.4)
|
||||
|
||||
var f2 = open("testblah.bin", fmWrite)
|
||||
echo store(f2, tf)
|
||||
f2.close
|
||||
|
||||
var f1 = open("testblah.bin", fmRead)
|
||||
|
||||
var t2: BiTable[float]
|
||||
|
||||
echo f1.load(t2)
|
||||
echo t2.vals.len
|
||||
|
||||
echo getKeyId(t2, 32.4)
|
||||
|
||||
echo "end"
|
||||
|
||||
|
||||
f1.close
|
||||
180
compiler/ic/cbackend.nim
Normal file
180
compiler/ic/cbackend.nim
Normal file
@@ -0,0 +1,180 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## New entry point into our C/C++ code generator. Ideally
|
||||
## somebody would rewrite the old backend (which is 8000 lines of crufty Nim code)
|
||||
## to work on packed trees directly and produce the C code as an AST which can
|
||||
## then be rendered to text in a very simple manner. Unfortunately nobody wrote
|
||||
## this code. So instead we wrap the existing cgen.nim and its friends so that
|
||||
## we call directly into the existing code generation logic but avoiding the
|
||||
## naive, outdated `passes` design. Thus you will see some
|
||||
## `useAliveDataFromDce in flags` checks in the old code -- the old code is
|
||||
## also doing cross-module dependency tracking and DCE that we don't need
|
||||
## anymore. DCE is now done as prepass over the entire packed module graph.
|
||||
|
||||
import std/[packedsets, algorithm, tables]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".."/[ast, options, lineinfos, modulegraphs, cgendata, cgen,
|
||||
pathutils, extccomp, msgs, modulepaths]
|
||||
|
||||
import packed_ast, ic, dce, rodfiles
|
||||
|
||||
proc unpackTree(g: ModuleGraph; thisModule: int;
|
||||
tree: PackedTree; n: NodePos): PNode =
|
||||
var decoder = initPackedDecoder(g.config, g.cache)
|
||||
result = loadNodes(decoder, g.packed, thisModule, tree, n)
|
||||
|
||||
proc setupBackendModule(g: ModuleGraph; m: var LoadedModule) =
|
||||
if g.backend == nil:
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
assert g.backend != nil
|
||||
var bmod = cgen.newModule(BModuleList(g.backend), m.module, g.config)
|
||||
bmod.idgen = idgenFromLoadedModule(m)
|
||||
|
||||
proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var AliveSyms) =
|
||||
var bmod = BModuleList(g.backend).modules[m.module.position]
|
||||
assert bmod != nil
|
||||
bmod.flags.incl useAliveDataFromDce
|
||||
bmod.alive = move alive[m.module.position]
|
||||
|
||||
for p in allNodes(m.fromDisk.topLevel):
|
||||
let n = unpackTree(g, m.module.position, m.fromDisk.topLevel, p)
|
||||
cgen.genTopLevelStmt(bmod, n)
|
||||
|
||||
finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
|
||||
for disp in getDispatchers(g):
|
||||
genProcAux(bmod, disp)
|
||||
m.fromDisk.backendFlags = cgen.whichInitProcs(bmod)
|
||||
|
||||
proc replayTypeInfo(g: ModuleGraph; m: var LoadedModule; origin: FileIndex) =
|
||||
for x in mitems(m.fromDisk.emittedTypeInfo):
|
||||
#echo "found type ", x, " for file ", int(origin)
|
||||
g.emittedTypeInfo[x] = origin
|
||||
|
||||
proc addFileToLink(config: ConfigRef; m: PSym) =
|
||||
let filename = AbsoluteFile toFullPath(config, m.position.FileIndex)
|
||||
let ext =
|
||||
if config.backend == backendCpp: ".nim.cpp"
|
||||
elif config.backend == backendObjc: ".nim.m"
|
||||
else: ".nim.c"
|
||||
let cfile = changeFileExt(completeCfilePath(config,
|
||||
mangleModuleName(config, filename).AbsoluteFile), ext)
|
||||
let objFile = completeCfilePath(config, toObjFile(config, cfile))
|
||||
if fileExists(objFile):
|
||||
var cf = Cfile(nimname: m.name.s, cname: cfile,
|
||||
obj: objFile,
|
||||
flags: {CfileFlag.Cached})
|
||||
addFileToCompile(config, cf)
|
||||
|
||||
when defined(debugDce):
|
||||
import os, std/packedsets
|
||||
|
||||
proc storeAliveSymsImpl(asymFile: AbsoluteFile; s: seq[int32]) =
|
||||
var f = rodfiles.create(asymFile.string)
|
||||
f.storeHeader()
|
||||
f.storeSection aliveSymsSection
|
||||
f.storeSeq(s)
|
||||
close f
|
||||
|
||||
template prepare {.dirty.} =
|
||||
let asymFile = toRodFile(config, AbsoluteFile toFullPath(config, position.FileIndex), ".alivesyms")
|
||||
var s = newSeqOfCap[int32](alive[position].len)
|
||||
for a in items(alive[position]): s.add int32(a)
|
||||
sort(s)
|
||||
|
||||
proc storeAliveSyms(config: ConfigRef; position: int; alive: AliveSyms) =
|
||||
prepare()
|
||||
storeAliveSymsImpl(asymFile, s)
|
||||
|
||||
proc aliveSymsChanged(config: ConfigRef; position: int; alive: AliveSyms): bool =
|
||||
prepare()
|
||||
var f2 = rodfiles.open(asymFile.string)
|
||||
f2.loadHeader()
|
||||
f2.loadSection aliveSymsSection
|
||||
var oldData: seq[int32] = @[]
|
||||
f2.loadSeq(oldData)
|
||||
f2.close
|
||||
if f2.err == ok and oldData == s:
|
||||
result = false
|
||||
else:
|
||||
when defined(debugDce):
|
||||
let oldAsSet = toPackedSet[int32](oldData)
|
||||
let newAsSet = toPackedSet[int32](s)
|
||||
echo "set of live symbols changed ", asymFile.changeFileExt("rod"), " ", position, " ", f2.err
|
||||
echo "in old but not in new ", oldAsSet.difference(newAsSet), " number of entries in old ", oldAsSet.len
|
||||
echo "in new but not in old ", newAsSet.difference(oldAsSet), " number of entries in new ", newAsSet.len
|
||||
#if execShellCmd(getAppFilename() & " rod " & quoteShell(asymFile.changeFileExt("rod"))) != 0:
|
||||
# echo "command failed"
|
||||
result = true
|
||||
storeAliveSymsImpl(asymFile, s)
|
||||
|
||||
proc genPackedModule(g: ModuleGraph, i: int; alive: var AliveSyms) =
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading, stored:
|
||||
assert false
|
||||
of storing, outdated:
|
||||
storeAliveSyms(g.config, g.packed[i].module.position, alive)
|
||||
generateCodeForModule(g, g.packed[i], alive)
|
||||
closeRodFile(g, g.packed[i].module)
|
||||
of loaded:
|
||||
if g.packed[i].loadedButAliveSetChanged:
|
||||
generateCodeForModule(g, g.packed[i], alive)
|
||||
else:
|
||||
addFileToLink(g.config, g.packed[i].module)
|
||||
replayTypeInfo(g, g.packed[i], FileIndex(i))
|
||||
|
||||
if g.backend == nil:
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
registerInitProcs(BModuleList(g.backend), g.packed[i].module, g.packed[i].fromDisk.backendFlags)
|
||||
|
||||
proc generateCode*(g: ModuleGraph) =
|
||||
## The single entry point, generate C(++) code for the entire
|
||||
## Nim program aka `ModuleGraph`.
|
||||
resetForBackend(g)
|
||||
var alive = computeAliveSyms(g.packed, g.config)
|
||||
|
||||
when false:
|
||||
for i in 0..<len(g.packed):
|
||||
echo i, " is of status ", g.packed[i].status, " ", toFullPath(g.config, FileIndex(i))
|
||||
|
||||
# First pass: Setup all the backend modules for all the modules that have
|
||||
# changed:
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading, stored:
|
||||
assert false
|
||||
of storing, outdated:
|
||||
setupBackendModule(g, g.packed[i])
|
||||
of loaded:
|
||||
# Even though this module didn't change, DCE might trigger a change.
|
||||
# Consider this case: Module A uses symbol S from B and B does not use
|
||||
# S itself. A is then edited not to use S either. Thus we have to
|
||||
# recompile B in order to remove S from the final result.
|
||||
if aliveSymsChanged(g.config, g.packed[i].module.position, alive):
|
||||
g.packed[i].loadedButAliveSetChanged = true
|
||||
setupBackendModule(g, g.packed[i])
|
||||
|
||||
# Second pass: Code generation.
|
||||
let mainModuleIdx = g.config.projectMainIdx2.int
|
||||
# We need to generate the main module last, because only then
|
||||
# all init procs have been registered:
|
||||
for i in 0..<len(g.packed):
|
||||
if i != mainModuleIdx:
|
||||
genPackedModule(g, i, alive)
|
||||
if mainModuleIdx >= 0:
|
||||
genPackedModule(g, mainModuleIdx, alive)
|
||||
169
compiler/ic/dce.nim
Normal file
169
compiler/ic/dce.nim
Normal file
@@ -0,0 +1,169 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Dead code elimination (=DCE) for IC.
|
||||
|
||||
import std/[intsets, tables]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, options, lineinfos, types]
|
||||
|
||||
import packed_ast, ic, bitabs
|
||||
|
||||
type
|
||||
AliveSyms* = seq[IntSet]
|
||||
AliveContext* = object ## Purpose is to fill the 'alive' field.
|
||||
stack: seq[(int, TOptions, NodePos)] ## A stack for marking symbols as alive.
|
||||
decoder: PackedDecoder ## We need a PackedDecoder for module ID address translations.
|
||||
thisModule: int ## The module we're currently analysing for DCE.
|
||||
alive: AliveSyms ## The final result of our computation.
|
||||
options: TOptions
|
||||
compilerProcs: Table[string, (int, int32)]
|
||||
|
||||
proc isExportedToC(c: var AliveContext; g: PackedModuleGraph; symId: int32): bool =
|
||||
## "Exported to C" procs are special (these are marked with '.exportc') because these
|
||||
## must not be optimized away!
|
||||
let symPtr = unsafeAddr g[c.thisModule].fromDisk.syms[symId]
|
||||
let flags = symPtr.flags
|
||||
# due to a bug/limitation in the lambda lifting, unused inner procs
|
||||
# are not transformed correctly; issue (#411). However, the whole purpose here
|
||||
# is to eliminate unused procs. So there is no special logic required for this case.
|
||||
if sfCompileTime notin flags:
|
||||
if ({sfExportc, sfCompilerProc} * flags != {}) or
|
||||
(symPtr.kind == skMethod):
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
# XXX: This used to be a condition to:
|
||||
# (sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
|
||||
if sfCompilerProc in flags:
|
||||
c.compilerProcs[g[c.thisModule].fromDisk.strings[symPtr.name]] = (c.thisModule, symId)
|
||||
else:
|
||||
result = false
|
||||
|
||||
template isNotGeneric(n: NodePos): bool = ithSon(tree, n, genericParamsPos).kind == nkEmpty
|
||||
|
||||
proc followLater(c: var AliveContext; g: PackedModuleGraph; module: int; item: int32) =
|
||||
## Marks a symbol 'item' as used and later in 'followNow' the symbol's body will
|
||||
## be analysed.
|
||||
if not c.alive[module].containsOrIncl(item):
|
||||
var body = g[module].fromDisk.syms[item].ast
|
||||
if body != emptyNodeId:
|
||||
let opt = g[module].fromDisk.syms[item].options
|
||||
if g[module].fromDisk.syms[item].kind in routineKinds:
|
||||
body = NodeId ithSon(g[module].fromDisk.bodies, NodePos body, bodyPos)
|
||||
c.stack.add((module, opt, NodePos(body)))
|
||||
|
||||
when false:
|
||||
let nid = g[module].fromDisk.syms[item].name
|
||||
if nid != LitId(0):
|
||||
let name = g[module].fromDisk.strings[nid]
|
||||
if name in ["nimFrame", "callDepthLimitReached"]:
|
||||
echo "I was called! ", name, " body exists: ", body != emptyNodeId, " ", module, " ", item
|
||||
|
||||
proc requestCompilerProc(c: var AliveContext; g: PackedModuleGraph; name: string) =
|
||||
let (module, item) = c.compilerProcs[name]
|
||||
followLater(c, g, module, item)
|
||||
|
||||
proc loadTypeKind(t: PackedItemId; c: AliveContext; g: PackedModuleGraph; toSkip: set[TTypeKind]): TTypeKind =
|
||||
template kind(t: ItemId): TTypeKind = g[t.module].fromDisk.types[t.item].kind
|
||||
|
||||
var t2 = translateId(t, g, c.thisModule, c.decoder.config)
|
||||
result = t2.kind
|
||||
while result in toSkip:
|
||||
t2 = translateId(g[t2.module].fromDisk.types[t2.item].types[^1], g, t2.module, c.decoder.config)
|
||||
result = t2.kind
|
||||
|
||||
proc rangeCheckAnalysis(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
|
||||
## Replicates the logic of `ccgexprs.genRangeChck`.
|
||||
## XXX Refactor so that the duplicated logic is avoided. However, for now it's not clear
|
||||
## the approach has enough merit.
|
||||
var dest = loadTypeKind(n.typ, c, g, abstractVar)
|
||||
if optRangeCheck notin c.options or dest in {tyUInt..tyUInt64}:
|
||||
discard "no need to generate a check because it was disabled"
|
||||
else:
|
||||
let n0t = loadTypeKind(n.firstSon.typ, c, g, {})
|
||||
if n0t in {tyUInt, tyUInt64}:
|
||||
c.requestCompilerProc(g, "raiseRangeErrorNoArgs")
|
||||
else:
|
||||
let raiser =
|
||||
case loadTypeKind(n.typ, c, g, abstractVarRange)
|
||||
of tyUInt..tyUInt64, tyChar: "raiseRangeErrorU"
|
||||
of tyFloat..tyFloat128: "raiseRangeErrorF"
|
||||
else: "raiseRangeErrorI"
|
||||
c.requestCompilerProc(g, raiser)
|
||||
|
||||
proc aliveCode(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
|
||||
## Marks the symbols we encounter when we traverse the AST at `tree[n]` as alive, unless
|
||||
## it is purely in a declarative context (type section etc.).
|
||||
case n.kind
|
||||
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||
discard "ignore non-sym atoms"
|
||||
of nkSym:
|
||||
# This symbol is alive and everything its body references.
|
||||
followLater(c, g, c.thisModule, tree[n].soperand)
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, n)
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
let m = n1.litId
|
||||
let item = tree[n2].soperand
|
||||
let otherModule = toFileIndexCached(c.decoder, g, c.thisModule, m).int
|
||||
followLater(c, g, otherModule, item)
|
||||
of nkMacroDef, nkTemplateDef, nkTypeSection, nkTypeOfExpr,
|
||||
nkCommentStmt, nkIncludeStmt,
|
||||
nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
|
||||
nkFromStmt, nkStaticStmt:
|
||||
discard
|
||||
of nkVarSection, nkLetSection, nkConstSection:
|
||||
# XXX ignore the defining local variable name?
|
||||
for son in sonsReadonly(tree, n):
|
||||
aliveCode(c, g, tree, son)
|
||||
of nkChckRangeF, nkChckRange64, nkChckRange:
|
||||
rangeCheckAnalysis(c, g, tree, n)
|
||||
of nkProcDef, nkConverterDef, nkMethodDef, nkFuncDef, nkIteratorDef:
|
||||
if n.firstSon.kind == nkSym and isNotGeneric(n):
|
||||
let item = tree[n.firstSon].soperand
|
||||
if isExportedToC(c, g, item):
|
||||
# This symbol is alive and everything its body references.
|
||||
followLater(c, g, c.thisModule, item)
|
||||
else:
|
||||
for son in sonsReadonly(tree, n):
|
||||
aliveCode(c, g, tree, son)
|
||||
|
||||
proc followNow(c: var AliveContext; g: PackedModuleGraph) =
|
||||
## Mark all entries in the stack. Marking can add more entries
|
||||
## to the stack but eventually we have looked at every alive symbol.
|
||||
while c.stack.len > 0:
|
||||
let (modId, opt, ast) = c.stack.pop()
|
||||
c.thisModule = modId
|
||||
c.options = opt
|
||||
aliveCode(c, g, g[modId].fromDisk.bodies, ast)
|
||||
|
||||
proc computeAliveSyms*(g: PackedModuleGraph; conf: ConfigRef): AliveSyms =
|
||||
## Entry point for our DCE algorithm.
|
||||
var c = AliveContext(stack: @[], decoder: PackedDecoder(config: conf),
|
||||
thisModule: -1, alive: newSeq[IntSet](g.len),
|
||||
options: conf.options)
|
||||
for i in countdown(len(g)-1, 0):
|
||||
if g[i].status != undefined:
|
||||
c.thisModule = i
|
||||
for p in allNodes(g[i].fromDisk.topLevel):
|
||||
aliveCode(c, g, g[i].fromDisk.topLevel, p)
|
||||
|
||||
followNow(c, g)
|
||||
result = move(c.alive)
|
||||
|
||||
proc isAlive*(a: AliveSyms; module: int, item: int32): bool =
|
||||
## Backends use this to query if a symbol is `alive` which means
|
||||
## we need to produce (C/C++/etc) code for it.
|
||||
result = a[module].contains(item)
|
||||
|
||||
56
compiler/ic/design.rst
Normal file
56
compiler/ic/design.rst
Normal file
@@ -0,0 +1,56 @@
|
||||
====================================
|
||||
Incremental Recompilations
|
||||
====================================
|
||||
|
||||
We split the Nim compiler into a frontend and a backend.
|
||||
The frontend produces a set of `.rod` files. Every `.nim` module
|
||||
produces its own `.rod` file.
|
||||
|
||||
- The IR must be a faithful representation of the AST in memory.
|
||||
- The backend can do its own caching but doesn't have to. In the
|
||||
current implementation the backend also caches its results.
|
||||
|
||||
Advantage of the "set of files" vs the previous global database:
|
||||
- By construction, we either read from the `.rod` file or from the
|
||||
`.nim` file, there can be no inconsistency. There can also be no
|
||||
partial updates.
|
||||
- No dependency to external packages (SQLite). SQLite simply is too
|
||||
slow and the old way of serialization was too slow too. We use a
|
||||
format designed for Nim and expect to base further tools on this
|
||||
file format.
|
||||
|
||||
References to external modules must be (moduleId, symId) pairs.
|
||||
The symbol IDs are module specific. This way no global ID increment
|
||||
mechanism needs to be implemented that we could get wrong. ModuleIds
|
||||
are rod-file specific too.
|
||||
|
||||
|
||||
|
||||
Global state
|
||||
------------
|
||||
|
||||
There is no global state.
|
||||
|
||||
Rod File Format
|
||||
---------------
|
||||
|
||||
It's a simple binary file format. `rodfiles.nim` contains some details.
|
||||
|
||||
|
||||
Backend
|
||||
-------
|
||||
|
||||
Nim programmers have to come to enjoy whole-program dead code elimination,
|
||||
by default. Since this is a "whole program" optimization, it does break
|
||||
modularity. However, thanks to the packed AST representation we can perform
|
||||
this global analysis without having to unpack anything. This is basically
|
||||
a mark&sweep GC algorithm:
|
||||
|
||||
- Start with the top level statements. Every symbol that is referenced
|
||||
from a top level statement is not "dead" and needs to be compiled by
|
||||
the backend.
|
||||
- Every symbol referenced from a referenced symbol also has to be
|
||||
compiled.
|
||||
|
||||
Caching logic: Only if the set of alive symbols is different from the
|
||||
last run, the module has to be regenerated.
|
||||
File diff suppressed because it is too large
Load Diff
1343
compiler/ic/ic.nim
Normal file
1343
compiler/ic/ic.nim
Normal file
File diff suppressed because it is too large
Load Diff
84
compiler/ic/iclineinfos.nim
Normal file
84
compiler/ic/iclineinfos.nim
Normal file
@@ -0,0 +1,84 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2024 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# For the line information we use 32 bits. They are used as follows:
|
||||
# Bit 0 (AsideBit): If we have inline line information or not. If not, the
|
||||
# remaining 31 bits are used as an index into a seq[(LitId, int, int)].
|
||||
#
|
||||
# We use 10 bits for the "file ID", this means a program can consist of as much
|
||||
# as 1024 different files. (If it uses more files than that, the overflow bit
|
||||
# would be set.)
|
||||
# This means we have 21 bits left to encode the (line, col) pair. We use 7 bits for the column
|
||||
# so 128 is the limit and 14 bits for the line number.
|
||||
# The packed representation supports files with up to 16384 lines.
|
||||
# Keep in mind that whenever any limit is reached the AsideBit is set and the real line
|
||||
# information is kept in a side channel.
|
||||
|
||||
import std / assertions
|
||||
|
||||
const
|
||||
AsideBit = 1
|
||||
FileBits = 10
|
||||
LineBits = 14
|
||||
ColBits = 7
|
||||
FileMax = (1 shl FileBits) - 1
|
||||
LineMax = (1 shl LineBits) - 1
|
||||
ColMax = (1 shl ColBits) - 1
|
||||
|
||||
static:
|
||||
assert AsideBit + FileBits + LineBits + ColBits == 32
|
||||
|
||||
import .. / ic / [bitabs, rodfiles] # for LitId
|
||||
|
||||
type
|
||||
PackedLineInfo* = distinct uint32
|
||||
|
||||
LineInfoManager* = object
|
||||
aside: seq[(LitId, int32, int32)]
|
||||
|
||||
const
|
||||
NoLineInfo* = PackedLineInfo(0'u32)
|
||||
|
||||
proc pack*(m: var LineInfoManager; file: LitId; line, col: int32): PackedLineInfo =
|
||||
if file.uint32 <= FileMax.uint32 and line <= LineMax and col <= ColMax:
|
||||
let col = if col < 0'i32: 0'u32 else: col.uint32
|
||||
let line = if line < 0'i32: 0'u32 else: line.uint32
|
||||
# use inline representation:
|
||||
result = PackedLineInfo((file.uint32 shl 1'u32) or (line shl uint32(AsideBit + FileBits)) or
|
||||
(col shl uint32(AsideBit + FileBits + LineBits)))
|
||||
else:
|
||||
result = PackedLineInfo((m.aside.len shl 1) or AsideBit)
|
||||
m.aside.add (file, line, col)
|
||||
|
||||
proc unpack*(m: LineInfoManager; i: PackedLineInfo): (LitId, int32, int32) =
|
||||
let i = i.uint32
|
||||
if (i and 1'u32) == 0'u32:
|
||||
# inline representation:
|
||||
result = (LitId((i shr 1'u32) and FileMax.uint32),
|
||||
int32((i shr uint32(AsideBit + FileBits)) and LineMax.uint32),
|
||||
int32((i shr uint32(AsideBit + FileBits + LineBits)) and ColMax.uint32))
|
||||
else:
|
||||
result = m.aside[int(i shr 1'u32)]
|
||||
|
||||
proc getFileId*(m: LineInfoManager; i: PackedLineInfo): LitId =
|
||||
result = unpack(m, i)[0]
|
||||
|
||||
proc store*(r: var RodFile; m: LineInfoManager) = storeSeq(r, m.aside)
|
||||
proc load*(r: var RodFile; m: var LineInfoManager) = loadSeq(r, m.aside)
|
||||
|
||||
when isMainModule:
|
||||
var m = LineInfoManager(aside: @[])
|
||||
for i in 0'i32..<16388'i32:
|
||||
for col in 0'i32..<100'i32:
|
||||
let packed = pack(m, LitId(1023), i, col)
|
||||
let u = unpack(m, packed)
|
||||
assert u[0] == LitId(1023)
|
||||
assert u[1] == i
|
||||
assert u[2] == col
|
||||
echo m.aside.len
|
||||
155
compiler/ic/integrity.nim
Normal file
155
compiler/ic/integrity.nim
Normal file
@@ -0,0 +1,155 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Integrity checking for a set of .rod files.
|
||||
## The set must cover a complete Nim project.
|
||||
|
||||
import std/[sets, tables]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, modulegraphs]
|
||||
import packed_ast, bitabs, ic
|
||||
|
||||
type
|
||||
CheckedContext = object
|
||||
g: ModuleGraph
|
||||
thisModule: int32
|
||||
checkedSyms: HashSet[ItemId]
|
||||
checkedTypes: HashSet[ItemId]
|
||||
|
||||
proc checkType(c: var CheckedContext; typeId: PackedItemId)
|
||||
proc checkForeignSym(c: var CheckedContext; symId: PackedItemId)
|
||||
proc checkNode(c: var CheckedContext; tree: PackedTree; n: NodePos)
|
||||
|
||||
proc checkTypeObj(c: var CheckedContext; typ: PackedType) =
|
||||
for child in typ.types:
|
||||
checkType(c, child)
|
||||
if typ.n != emptyNodeId:
|
||||
checkNode(c, c.g.packed[c.thisModule].fromDisk.bodies, NodePos typ.n)
|
||||
if typ.sym != nilItemId:
|
||||
checkForeignSym(c, typ.sym)
|
||||
if typ.owner != nilItemId:
|
||||
checkForeignSym(c, typ.owner)
|
||||
checkType(c, typ.typeInst)
|
||||
|
||||
proc checkType(c: var CheckedContext; typeId: PackedItemId) =
|
||||
if typeId == nilItemId: return
|
||||
let itemId = translateId(typeId, c.g.packed, c.thisModule, c.g.config)
|
||||
if not c.checkedTypes.containsOrIncl(itemId):
|
||||
let oldThisModule = c.thisModule
|
||||
c.thisModule = itemId.module
|
||||
checkTypeObj c, c.g.packed[itemId.module].fromDisk.types[itemId.item]
|
||||
c.thisModule = oldThisModule
|
||||
|
||||
proc checkSym(c: var CheckedContext; s: PackedSym) =
|
||||
if s.name != LitId(0):
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId s.name
|
||||
checkType c, s.typ
|
||||
if s.ast != emptyNodeId:
|
||||
checkNode(c, c.g.packed[c.thisModule].fromDisk.bodies, NodePos s.ast)
|
||||
if s.owner != nilItemId:
|
||||
checkForeignSym(c, s.owner)
|
||||
|
||||
proc checkLocalSym(c: var CheckedContext; item: int32) =
|
||||
let itemId = ItemId(module: c.thisModule, item: item)
|
||||
if not c.checkedSyms.containsOrIncl(itemId):
|
||||
checkSym c, c.g.packed[c.thisModule].fromDisk.syms[item]
|
||||
|
||||
proc checkForeignSym(c: var CheckedContext; symId: PackedItemId) =
|
||||
let itemId = translateId(symId, c.g.packed, c.thisModule, c.g.config)
|
||||
if not c.checkedSyms.containsOrIncl(itemId):
|
||||
let oldThisModule = c.thisModule
|
||||
c.thisModule = itemId.module
|
||||
checkSym c, c.g.packed[itemId.module].fromDisk.syms[itemId.item]
|
||||
c.thisModule = oldThisModule
|
||||
|
||||
proc checkNode(c: var CheckedContext; tree: PackedTree; n: NodePos) =
|
||||
let t = findType(tree, n)
|
||||
if t != nilItemId:
|
||||
checkType(c, t)
|
||||
case n.kind
|
||||
of nkEmpty, nkNilLit, nkType, nkNilRodNode:
|
||||
discard
|
||||
of nkIdent:
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId n.litId
|
||||
of nkSym:
|
||||
checkLocalSym(c, tree[n].soperand)
|
||||
of directIntLit:
|
||||
discard
|
||||
of externIntLit, nkFloatLit..nkFloat128Lit:
|
||||
assert c.g.packed[c.thisModule].fromDisk.numbers.hasLitId n.litId
|
||||
of nkStrLit..nkTripleStrLit:
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId n.litId
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, n)
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
checkForeignSym(c, PackedItemId(module: n1.litId, item: tree[n2].soperand))
|
||||
else:
|
||||
for n0 in sonsReadonly(tree, n):
|
||||
checkNode(c, tree, n0)
|
||||
|
||||
proc checkTree(c: var CheckedContext; t: PackedTree) =
|
||||
for p in allNodes(t): checkNode(c, t, p)
|
||||
|
||||
proc checkLocalSymIds(c: var CheckedContext; m: PackedModule; symIds: seq[int32]) =
|
||||
for symId in symIds:
|
||||
assert symId >= 0 and symId < m.syms.len, $symId & " " & $m.syms.len
|
||||
|
||||
proc checkModule(c: var CheckedContext; m: PackedModule) =
|
||||
# We check that:
|
||||
# - Every symbol references existing types and symbols.
|
||||
# - Every tree node references existing types and symbols.
|
||||
for _, v in pairs(m.syms):
|
||||
checkLocalSym c, v.id
|
||||
|
||||
checkTree c, m.toReplay
|
||||
checkTree c, m.topLevel
|
||||
|
||||
for e in m.exports:
|
||||
#assert e[1] >= 0 and e[1] < m.syms.len
|
||||
assert e[0] == m.syms[e[1]].name
|
||||
|
||||
for e in m.compilerProcs:
|
||||
#assert e[1] >= 0 and e[1] < m.syms.len
|
||||
assert e[0] == m.syms[e[1]].name
|
||||
|
||||
checkLocalSymIds c, m, m.converters
|
||||
checkLocalSymIds c, m, m.methods
|
||||
checkLocalSymIds c, m, m.trmacros
|
||||
checkLocalSymIds c, m, m.pureEnums
|
||||
#[
|
||||
To do: Check all these fields:
|
||||
|
||||
reexports*: seq[(LitId, PackedItemId)]
|
||||
macroUsages*: seq[(PackedItemId, PackedLineInfo)]
|
||||
|
||||
typeInstCache*: seq[(PackedItemId, PackedItemId)]
|
||||
procInstCache*: seq[PackedInstantiation]
|
||||
attachedOps*: seq[(TTypeAttachedOp, PackedItemId, PackedItemId)]
|
||||
methodsPerGenericType*: seq[(PackedItemId, int, PackedItemId)]
|
||||
enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
|
||||
methodsPerType*: seq[(PackedItemId, PackedItemId)]
|
||||
dispatchers*: seq[PackedItemId]
|
||||
]#
|
||||
|
||||
proc checkIntegrity*(g: ModuleGraph) =
|
||||
var c = CheckedContext(g: g)
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading:
|
||||
assert false, "cannot check integrity: Module still loading"
|
||||
of stored, storing, outdated, loaded:
|
||||
c.thisModule = int32 i
|
||||
checkModule(c, g.packed[i].fromDisk)
|
||||
183
compiler/ic/navigator.nim
Normal file
183
compiler/ic/navigator.nim
Normal file
@@ -0,0 +1,183 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Supports the "nim check --ic:on --defusages:FILE,LINE,COL"
|
||||
## IDE-like features. It uses the set of .rod files to accomplish
|
||||
## its task. The set must cover a complete Nim project.
|
||||
|
||||
import std/[sets, tables]
|
||||
|
||||
from std/os import nil
|
||||
from std/private/miscdollars import toLocation
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, modulegraphs, msgs, options]
|
||||
import iclineinfos
|
||||
import packed_ast, bitabs, ic
|
||||
|
||||
type
|
||||
UnpackedLineInfo = object
|
||||
file: LitId
|
||||
line, col: int
|
||||
NavContext = object
|
||||
g: ModuleGraph
|
||||
thisModule: int32
|
||||
trackPos: UnpackedLineInfo
|
||||
alreadyEmitted: HashSet[string]
|
||||
outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
|
||||
|
||||
proc isTracked(man: LineInfoManager; current: PackedLineInfo, trackPos: UnpackedLineInfo, tokenLen: int): bool =
|
||||
let (currentFile, currentLine, currentCol) = man.unpack(current)
|
||||
if currentFile == trackPos.file and currentLine == trackPos.line:
|
||||
let col = trackPos.col
|
||||
if col >= currentCol and col < currentCol+tokenLen:
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
|
||||
result = s.name != LitId(0) and
|
||||
isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[c.thisModule].fromDisk.strings[s.name].len)
|
||||
|
||||
proc searchForeignSym(c: var NavContext; s: ItemId; info: PackedLineInfo): bool =
|
||||
let name = c.g.packed[s.module].fromDisk.syms[s.item].name
|
||||
result = name != LitId(0) and
|
||||
isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[s.module].fromDisk.strings[name].len)
|
||||
|
||||
const
|
||||
EmptyItemId = ItemId(module: -1'i32, item: -1'i32)
|
||||
|
||||
proc search(c: var NavContext; tree: PackedTree): ItemId =
|
||||
# We use the linear representation here directly:
|
||||
for i in 0..<len(tree):
|
||||
let i = NodePos(i)
|
||||
case tree[i].kind
|
||||
of nkSym:
|
||||
let item = tree[i].soperand
|
||||
if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.syms[item], tree[i].info):
|
||||
return ItemId(module: c.thisModule, item: item)
|
||||
of nkModuleRef:
|
||||
let (currentFile, currentLine, currentCol) = c.g.packed[c.thisModule].fromDisk.man.unpack(tree[i].info)
|
||||
if currentLine == c.trackPos.line and currentFile == c.trackPos.file:
|
||||
let (n1, n2) = sons2(tree, i)
|
||||
assert n1.kind == nkInt32Lit
|
||||
assert n2.kind == nkInt32Lit
|
||||
let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
|
||||
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
|
||||
if searchForeignSym(c, itemId, tree[i].info):
|
||||
return itemId
|
||||
else: discard
|
||||
return EmptyItemId
|
||||
|
||||
proc isDecl(tree: PackedTree; n: NodePos): bool =
|
||||
# XXX This is not correct yet.
|
||||
const declarativeNodes = procDefs + {nkMacroDef, nkTemplateDef,
|
||||
nkLetSection, nkVarSection, nkUsingStmt, nkConstSection, nkTypeSection,
|
||||
nkIdentDefs, nkEnumTy, nkVarTuple}
|
||||
result = n.int >= 0 and tree[n].kind in declarativeNodes
|
||||
|
||||
proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
|
||||
let (fileId, line, col) = unpack(c.g.packed[c.thisModule].fromDisk.man, info)
|
||||
var m = ""
|
||||
var file = c.g.packed[c.thisModule].fromDisk.strings[fileId]
|
||||
if c.outputSep == ' ':
|
||||
file = os.extractFilename file
|
||||
toLocation(m, file, line, col + ColOffset)
|
||||
if not c.alreadyEmitted.containsOrIncl(m):
|
||||
msgWriteln c.g.config, (if isDecl: "def" else: "usage") & c.outputSep & m
|
||||
|
||||
proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
|
||||
for i in 0..<len(tree):
|
||||
let i = NodePos(i)
|
||||
case tree[i].kind
|
||||
of nkSym:
|
||||
let item = tree[i].soperand
|
||||
if sym.item == item and sym.module == c.thisModule:
|
||||
usage(c, tree[i].info, isDecl(tree, parent(i)))
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, i)
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
|
||||
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
|
||||
if itemId.item == sym.item and sym.module == itemId.module:
|
||||
usage(c, tree[i].info, isDecl(tree, parent(i)))
|
||||
else: discard
|
||||
|
||||
proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
|
||||
for i in 0..<len(g.packed):
|
||||
for k in 1..high(g.packed[i].fromDisk.includes):
|
||||
# we start from 1 because the first "include" file is
|
||||
# the module's filename.
|
||||
if os.cmpPaths(g.packed[i].fromDisk.strings[g.packed[i].fromDisk.includes[k][0]], fullPath) == 0:
|
||||
return i
|
||||
return -1
|
||||
|
||||
proc nav(g: ModuleGraph) =
|
||||
# translate the track position to a packed position:
|
||||
let unpacked = g.config.m.trackPos
|
||||
var mid = unpacked.fileIndex.int
|
||||
|
||||
let fullPath = toFullPath(g.config, unpacked.fileIndex)
|
||||
|
||||
if g.packed[mid].status == undefined:
|
||||
# check if 'mid' is an include file of some other module:
|
||||
mid = searchForIncludeFile(g, fullPath)
|
||||
|
||||
if mid < 0:
|
||||
localError(g.config, unpacked, "unknown file name: " & fullPath)
|
||||
return
|
||||
|
||||
let fileId = g.packed[mid].fromDisk.strings.getKeyId(fullPath)
|
||||
|
||||
if fileId == LitId(0):
|
||||
internalError(g.config, unpacked, "cannot find a valid file ID")
|
||||
return
|
||||
|
||||
var c = NavContext(
|
||||
g: g,
|
||||
thisModule: int32 mid,
|
||||
trackPos: UnpackedLineInfo(line: unpacked.line.int, col: unpacked.col.int, file: fileId),
|
||||
outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
|
||||
)
|
||||
var symId = search(c, g.packed[mid].fromDisk.topLevel)
|
||||
if symId == EmptyItemId:
|
||||
symId = search(c, g.packed[mid].fromDisk.bodies)
|
||||
|
||||
if symId == EmptyItemId:
|
||||
localError(g.config, unpacked, "no symbol at this position")
|
||||
return
|
||||
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading:
|
||||
assert false, "cannot check integrity: Module still loading"
|
||||
of stored, storing, outdated, loaded:
|
||||
c.thisModule = int32 i
|
||||
list(c, g.packed[i].fromDisk.topLevel, symId)
|
||||
list(c, g.packed[i].fromDisk.bodies, symId)
|
||||
|
||||
proc navDefinition*(g: ModuleGraph) = nav(g)
|
||||
proc navUsages*(g: ModuleGraph) = nav(g)
|
||||
proc navDefusages*(g: ModuleGraph) = nav(g)
|
||||
|
||||
proc writeRodFiles*(g: ModuleGraph) =
|
||||
for i in 0..<len(g.packed):
|
||||
case g.packed[i].status
|
||||
of undefined, loading, stored, loaded:
|
||||
discard "nothing to do"
|
||||
of storing, outdated:
|
||||
closeRodFile(g, g.packed[i].module)
|
||||
367
compiler/ic/packed_ast.nim
Normal file
367
compiler/ic/packed_ast.nim
Normal file
@@ -0,0 +1,367 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2020 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Packed AST representation, mostly based on a seq of nodes.
|
||||
## For IC support. Far future: Rewrite the compiler passes to
|
||||
## use this representation directly in all the transformations,
|
||||
## it is superior.
|
||||
|
||||
import std/[hashes, tables, strtabs]
|
||||
import bitabs, rodfiles
|
||||
import ".." / [ast, options]
|
||||
|
||||
import iclineinfos
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
SymId* = distinct int32
|
||||
ModuleId* = distinct int32
|
||||
NodePos* = distinct int
|
||||
|
||||
NodeId* = distinct int32
|
||||
|
||||
PackedItemId* = object
|
||||
module*: LitId # 0 if it's this module
|
||||
item*: int32 # same as the in-memory representation
|
||||
|
||||
const
|
||||
nilItemId* = PackedItemId(module: LitId(0), item: 0.int32)
|
||||
|
||||
const
|
||||
emptyNodeId* = NodeId(-1)
|
||||
|
||||
type
|
||||
PackedLib* = object
|
||||
kind*: TLibKind
|
||||
generated*: bool
|
||||
isOverridden*: bool
|
||||
name*: LitId
|
||||
path*: NodeId
|
||||
|
||||
PackedSym* = object
|
||||
id*: int32
|
||||
kind*: TSymKind
|
||||
name*: LitId
|
||||
typ*: PackedItemId
|
||||
flags*: TSymFlags
|
||||
magic*: TMagic
|
||||
info*: PackedLineInfo
|
||||
ast*: NodeId
|
||||
owner*: PackedItemId
|
||||
guard*: PackedItemId
|
||||
bitsize*: int
|
||||
alignment*: int # for alignment
|
||||
options*: TOptions
|
||||
position*: int
|
||||
offset*: int32
|
||||
disamb*: int32
|
||||
externalName*: LitId # instead of TLoc
|
||||
locFlags*: TLocFlags
|
||||
annex*: PackedLib
|
||||
when hasFFI:
|
||||
cname*: LitId
|
||||
constraint*: NodeId
|
||||
instantiatedFrom*: PackedItemId
|
||||
|
||||
PackedType* = object
|
||||
id*: int32
|
||||
kind*: TTypeKind
|
||||
callConv*: TCallingConvention
|
||||
#nodekind*: TNodeKind
|
||||
flags*: TTypeFlags
|
||||
types*: seq[PackedItemId]
|
||||
n*: NodeId
|
||||
#nodeflags*: TNodeFlags
|
||||
sym*: PackedItemId
|
||||
owner*: PackedItemId
|
||||
size*: BiggestInt
|
||||
align*: int16
|
||||
paddingAtEnd*: int16
|
||||
# not serialized: loc*: TLoc because it is backend-specific
|
||||
typeInst*: PackedItemId
|
||||
nonUniqueId*: int32
|
||||
|
||||
PackedNode* = object # 8 bytes
|
||||
x: uint32
|
||||
info*: PackedLineInfo
|
||||
|
||||
PackedTree* = object ## usually represents a full Nim module
|
||||
nodes: seq[PackedNode]
|
||||
withFlags: seq[(int32, TNodeFlags)]
|
||||
withTypes: seq[(int32, PackedItemId)]
|
||||
|
||||
PackedInstantiation* = object
|
||||
key*, sym*: PackedItemId
|
||||
concreteTypes*: seq[PackedItemId]
|
||||
|
||||
const
|
||||
NodeKindBits = 8'u32
|
||||
NodeKindMask = (1'u32 shl NodeKindBits) - 1'u32
|
||||
|
||||
template kind*(n: PackedNode): TNodeKind = TNodeKind(n.x and NodeKindMask)
|
||||
template uoperand*(n: PackedNode): uint32 = (n.x shr NodeKindBits)
|
||||
template soperand*(n: PackedNode): int32 = int32(uoperand(n))
|
||||
|
||||
template toX(k: TNodeKind; operand: uint32): uint32 =
|
||||
uint32(k) or (operand shl NodeKindBits)
|
||||
|
||||
template toX(k: TNodeKind; operand: LitId): uint32 =
|
||||
uint32(k) or (operand.uint32 shl NodeKindBits)
|
||||
|
||||
template typeId*(n: PackedNode): PackedItemId = n.typ
|
||||
|
||||
proc `==`*(a, b: SymId): bool {.borrow.}
|
||||
proc hash*(a: SymId): Hash {.borrow.}
|
||||
|
||||
proc `==`*(a, b: NodePos): bool {.borrow.}
|
||||
#proc `==`*(a, b: PackedItemId): bool {.borrow.}
|
||||
proc `==`*(a, b: NodeId): bool {.borrow.}
|
||||
|
||||
proc newTreeFrom*(old: PackedTree): PackedTree =
|
||||
result = PackedTree(nodes: @[])
|
||||
when false: result.sh = old.sh
|
||||
|
||||
proc addIdent*(tree: var PackedTree; s: LitId; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(x: toX(nkIdent, uint32(s)), info: info)
|
||||
|
||||
proc addSym*(tree: var PackedTree; s: int32; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(x: toX(nkSym, cast[uint32](s)), info: info)
|
||||
|
||||
proc addSymDef*(tree: var PackedTree; s: SymId; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(x: toX(nkSym, cast[uint32](s)), info: info)
|
||||
|
||||
proc isAtom*(tree: PackedTree; pos: int): bool {.inline.} = tree.nodes[pos].kind <= nkNilLit
|
||||
|
||||
type
|
||||
PatchPos = distinct int
|
||||
|
||||
proc addNode*(t: var PackedTree; kind: TNodeKind; operand: int32;
|
||||
typeId: PackedItemId = nilItemId; info: PackedLineInfo;
|
||||
flags: TNodeFlags = {}) =
|
||||
t.nodes.add PackedNode(x: toX(kind, cast[uint32](operand)), info: info)
|
||||
if flags != {}:
|
||||
t.withFlags.add (t.nodes.len.int32 - 1, flags)
|
||||
if typeId != nilItemId:
|
||||
t.withTypes.add (t.nodes.len.int32 - 1, typeId)
|
||||
|
||||
proc prepare*(tree: var PackedTree; kind: TNodeKind; flags: TNodeFlags; typeId: PackedItemId; info: PackedLineInfo): PatchPos =
|
||||
result = PatchPos tree.nodes.len
|
||||
tree.addNode(kind = kind, flags = flags, operand = 0, info = info, typeId = typeId)
|
||||
|
||||
proc prepare*(dest: var PackedTree; source: PackedTree; sourcePos: NodePos): PatchPos =
|
||||
result = PatchPos dest.nodes.len
|
||||
dest.nodes.add source.nodes[sourcePos.int]
|
||||
|
||||
proc patch*(tree: var PackedTree; pos: PatchPos) =
|
||||
let pos = pos.int
|
||||
let k = tree.nodes[pos].kind
|
||||
assert k > nkNilLit
|
||||
let distance = int32(tree.nodes.len - pos)
|
||||
assert distance > 0
|
||||
tree.nodes[pos].x = toX(k, cast[uint32](distance))
|
||||
|
||||
proc len*(tree: PackedTree): int {.inline.} = tree.nodes.len
|
||||
|
||||
proc `[]`*(tree: PackedTree; i: NodePos): lent PackedNode {.inline.} =
|
||||
tree.nodes[i.int]
|
||||
|
||||
template rawSpan(n: PackedNode): int = int(uoperand(n))
|
||||
|
||||
proc nextChild(tree: PackedTree; pos: var int) {.inline.} =
|
||||
if tree.nodes[pos].kind > nkNilLit:
|
||||
assert tree.nodes[pos].uoperand > 0
|
||||
inc pos, tree.nodes[pos].rawSpan
|
||||
else:
|
||||
inc pos
|
||||
|
||||
iterator sonsReadonly*(tree: PackedTree; n: NodePos): NodePos =
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
while pos < last:
|
||||
yield NodePos pos
|
||||
nextChild tree, pos
|
||||
|
||||
iterator sons*(dest: var PackedTree; tree: PackedTree; n: NodePos): NodePos =
|
||||
let patchPos = prepare(dest, tree, n)
|
||||
for x in sonsReadonly(tree, n): yield x
|
||||
patch dest, patchPos
|
||||
|
||||
iterator isons*(dest: var PackedTree; tree: PackedTree;
|
||||
n: NodePos): (int, NodePos) =
|
||||
var i = 0
|
||||
for ch0 in sons(dest, tree, n):
|
||||
yield (i, ch0)
|
||||
inc i
|
||||
|
||||
iterator sonsFrom1*(tree: PackedTree; n: NodePos): NodePos =
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
if pos < last:
|
||||
nextChild tree, pos
|
||||
while pos < last:
|
||||
yield NodePos pos
|
||||
nextChild tree, pos
|
||||
|
||||
iterator sonsWithoutLast2*(tree: PackedTree; n: NodePos): NodePos =
|
||||
var count = 0
|
||||
for child in sonsReadonly(tree, n):
|
||||
inc count
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
while pos < last and count > 2:
|
||||
yield NodePos pos
|
||||
dec count
|
||||
nextChild tree, pos
|
||||
|
||||
proc parentImpl(tree: PackedTree; n: NodePos): NodePos =
|
||||
# finding the parent of a node is rather easy:
|
||||
var pos = n.int - 1
|
||||
while pos >= 0 and (isAtom(tree, pos) or (pos + tree.nodes[pos].rawSpan - 1 < n.int)):
|
||||
dec pos
|
||||
#assert pos >= 0, "node has no parent"
|
||||
result = NodePos(pos)
|
||||
|
||||
template parent*(n: NodePos): NodePos = parentImpl(tree, n)
|
||||
|
||||
proc hasXsons*(tree: PackedTree; n: NodePos; x: int): bool =
|
||||
var count = 0
|
||||
if tree.nodes[n.int].kind > nkNilLit:
|
||||
for child in sonsReadonly(tree, n): inc count
|
||||
result = count == x
|
||||
|
||||
proc hasAtLeastXsons*(tree: PackedTree; n: NodePos; x: int): bool =
|
||||
if tree.nodes[n.int].kind > nkNilLit:
|
||||
var count = 0
|
||||
for child in sonsReadonly(tree, n):
|
||||
inc count
|
||||
if count >= x: return true
|
||||
return false
|
||||
|
||||
proc firstSon*(tree: PackedTree; n: NodePos): NodePos {.inline.} =
|
||||
NodePos(n.int+1)
|
||||
proc kind*(tree: PackedTree; n: NodePos): TNodeKind {.inline.} =
|
||||
tree.nodes[n.int].kind
|
||||
proc litId*(tree: PackedTree; n: NodePos): LitId {.inline.} =
|
||||
LitId tree.nodes[n.int].uoperand
|
||||
proc info*(tree: PackedTree; n: NodePos): PackedLineInfo {.inline.} =
|
||||
tree.nodes[n.int].info
|
||||
|
||||
proc findType*(tree: PackedTree; n: NodePos): PackedItemId =
|
||||
for x in tree.withTypes:
|
||||
if x[0] == int32(n): return x[1]
|
||||
if x[0] > int32(n): return nilItemId
|
||||
return nilItemId
|
||||
|
||||
proc findFlags*(tree: PackedTree; n: NodePos): TNodeFlags =
|
||||
for x in tree.withFlags:
|
||||
if x[0] == int32(n): return x[1]
|
||||
if x[0] > int32(n): return {}
|
||||
return {}
|
||||
|
||||
template typ*(n: NodePos): PackedItemId =
|
||||
tree.findType(n)
|
||||
template flags*(n: NodePos): TNodeFlags =
|
||||
tree.findFlags(n)
|
||||
|
||||
template uoperand*(n: NodePos): uint32 =
|
||||
tree.nodes[n.int].uoperand
|
||||
|
||||
proc span*(tree: PackedTree; pos: int): int {.inline.} =
|
||||
if isAtom(tree, pos): 1 else: tree.nodes[pos].rawSpan
|
||||
|
||||
proc sons2*(tree: PackedTree; n: NodePos): (NodePos, NodePos) =
|
||||
assert(not isAtom(tree, n.int))
|
||||
let a = n.int+1
|
||||
let b = a + span(tree, a)
|
||||
result = (NodePos a, NodePos b)
|
||||
|
||||
proc sons3*(tree: PackedTree; n: NodePos): (NodePos, NodePos, NodePos) =
|
||||
assert(not isAtom(tree, n.int))
|
||||
let a = n.int+1
|
||||
let b = a + span(tree, a)
|
||||
let c = b + span(tree, b)
|
||||
result = (NodePos a, NodePos b, NodePos c)
|
||||
|
||||
proc ithSon*(tree: PackedTree; n: NodePos; i: int): NodePos =
|
||||
result = default(NodePos)
|
||||
if tree.nodes[n.int].kind > nkNilLit:
|
||||
var count = 0
|
||||
for child in sonsReadonly(tree, n):
|
||||
if count == i: return child
|
||||
inc count
|
||||
assert false, "node has no i-th child"
|
||||
|
||||
when false:
|
||||
proc `@`*(tree: PackedTree; lit: LitId): lent string {.inline.} =
|
||||
tree.sh.strings[lit]
|
||||
|
||||
template kind*(n: NodePos): TNodeKind = tree.nodes[n.int].kind
|
||||
template info*(n: NodePos): PackedLineInfo = tree.nodes[n.int].info
|
||||
template litId*(n: NodePos): LitId = LitId tree.nodes[n.int].uoperand
|
||||
|
||||
template symId*(n: NodePos): SymId = SymId tree.nodes[n.int].soperand
|
||||
|
||||
proc firstSon*(n: NodePos): NodePos {.inline.} = NodePos(n.int+1)
|
||||
|
||||
const
|
||||
externIntLit* = {nkCharLit,
|
||||
nkIntLit,
|
||||
nkInt8Lit,
|
||||
nkInt16Lit,
|
||||
nkInt32Lit,
|
||||
nkInt64Lit,
|
||||
nkUIntLit,
|
||||
nkUInt8Lit,
|
||||
nkUInt16Lit,
|
||||
nkUInt32Lit,
|
||||
nkUInt64Lit}
|
||||
|
||||
externSIntLit* = {nkIntLit, nkInt8Lit, nkInt16Lit, nkInt32Lit, nkInt64Lit}
|
||||
externUIntLit* = {nkUIntLit, nkUInt8Lit, nkUInt16Lit, nkUInt32Lit, nkUInt64Lit}
|
||||
directIntLit* = nkNone
|
||||
|
||||
template copyInto*(dest, n, body) =
|
||||
let patchPos = prepare(dest, tree, n)
|
||||
body
|
||||
patch dest, patchPos
|
||||
|
||||
template copyIntoKind*(dest, kind, info, body) =
|
||||
let patchPos = prepare(dest, kind, info)
|
||||
body
|
||||
patch dest, patchPos
|
||||
|
||||
proc getNodeId*(tree: PackedTree): NodeId {.inline.} = NodeId tree.nodes.len
|
||||
|
||||
iterator allNodes*(tree: PackedTree): NodePos =
|
||||
var p = 0
|
||||
while p < tree.len:
|
||||
yield NodePos(p)
|
||||
let s = span(tree, p)
|
||||
inc p, s
|
||||
|
||||
proc toPackedItemId*(item: int32): PackedItemId {.inline.} =
|
||||
PackedItemId(module: LitId(0), item: item)
|
||||
|
||||
proc load*(f: var RodFile; t: var PackedTree) =
|
||||
loadSeq f, t.nodes
|
||||
loadSeq f, t.withFlags
|
||||
loadSeq f, t.withTypes
|
||||
|
||||
proc store*(f: var RodFile; t: PackedTree) =
|
||||
storeSeq f, t.nodes
|
||||
storeSeq f, t.withFlags
|
||||
storeSeq f, t.withTypes
|
||||
@@ -19,11 +19,10 @@ import std/tables
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
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.
|
||||
import packed_ast, ic, bitabs
|
||||
|
||||
proc replayStateChanges*(module: PSym; g: ModuleGraph) =
|
||||
let list = module.ast
|
||||
assert list != nil
|
||||
assert list.kind == nkStmtList
|
||||
for n in list:
|
||||
@@ -67,9 +66,8 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
|
||||
g.cacheTables[destKey] = initBTree[string, PNode]()
|
||||
if not contains(g.cacheTables[destKey], key):
|
||||
g.cacheTables[destKey].add(key, val)
|
||||
# else: the same key was already replayed. Under IC the import closure is
|
||||
# replayed (direct module + transitive deps), so the same registration can
|
||||
# legitimately be reached twice; re-applying it is a no-op, not an error.
|
||||
else:
|
||||
internalError(g.config, n.info, "key already exists: " & key)
|
||||
of "incl":
|
||||
let destKey = n[1].strVal
|
||||
let val = n[2]
|
||||
@@ -91,36 +89,83 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph; list: PNode) =
|
||||
else:
|
||||
internalAssert g.config, false
|
||||
|
||||
proc replayBackendActions*(g: ModuleGraph; module: PSym; list: PNode) =
|
||||
## Applies the backend-relevant replay actions (C compile/link directives)
|
||||
## found in a NIF-loaded module's top-level statement list. The `nifc`
|
||||
## backend loads modules without going through sem's `replayStateChanges`,
|
||||
## so e.g. math's `{.passL: "-lm".}` was lost and the final link failed
|
||||
## with undefined references. VM cache actions are deliberately NOT
|
||||
## replayed here — codegen does not run macros.
|
||||
if list == nil: return
|
||||
for n in list:
|
||||
if n.kind == nkReplayAction and n.len >= 2 and
|
||||
n[0].kind == nkStrLit and n[1].kind == nkStrLit:
|
||||
case n[0].strVal
|
||||
of "compile":
|
||||
if n.len == 4 and n[2].kind == nkStrLit:
|
||||
let cname = AbsoluteFile n[1].strVal
|
||||
var cf = Cfile(nimname: splitFile(cname).name, cname: cname,
|
||||
obj: AbsoluteFile n[2].strVal,
|
||||
flags: {CfileFlag.External},
|
||||
customArgs: n[3].strVal)
|
||||
extccomp.addExternalFileToCompile(g.config, cf)
|
||||
of "link":
|
||||
extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal)
|
||||
of "passl":
|
||||
extccomp.addLinkOption(g.config, n[1].strVal)
|
||||
of "passc":
|
||||
extccomp.addCompileOption(g.config, n[1].strVal)
|
||||
of "localpassc":
|
||||
extccomp.addLocalCompileOption(g.config, n[1].strVal,
|
||||
toFullPathConsiderDirty(g.config, module.info.fileIndex))
|
||||
of "cppdefine":
|
||||
options.cppDefine(g.config, n[1].strVal)
|
||||
else:
|
||||
discard
|
||||
proc replayBackendProcs*(g: ModuleGraph; module: int) =
|
||||
for it in mitems(g.packed[module].fromDisk.attachedOps):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let op = it[1]
|
||||
let tmp = translateId(it[2], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[2])
|
||||
g.attachedOps[op][key] = LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.enumToStringProcs):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let tmp = translateId(it[1], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[1])
|
||||
g.enumToStringProcs[key] = LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methodsPerType):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let tmp = translateId(it[1], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[1])
|
||||
g.methodsPerType.mgetOrPut(key, @[]).add LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.dispatchers):
|
||||
let tmp = translateId(it, g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it)
|
||||
g.dispatchers.add LazySym(id: symId, sym: nil)
|
||||
|
||||
proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
|
||||
## We remember the generic instantiations a module performed
|
||||
## in order to to avoid the code bloat that generic code tends
|
||||
## to imply. This is cheaper than deduplication of identical
|
||||
## generic instantiations. However, deduplication is more
|
||||
## powerful and general and I hope to implement it soon too
|
||||
## (famous last words).
|
||||
assert g.packed[module].status == loaded
|
||||
for it in g.packed[module].fromDisk.typeInstCache:
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
g.typeInstCache.mgetOrPut(key, @[]).add LazyType(id: FullId(module: module, packed: it[1]), typ: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.procInstCache):
|
||||
let key = translateId(it.key, g.packed, module, g.config)
|
||||
let sym = translateId(it.sym, g.packed, module, g.config)
|
||||
var concreteTypes = newSeq[FullId](it.concreteTypes.len)
|
||||
for i in 0..high(it.concreteTypes):
|
||||
let tmp = translateId(it.concreteTypes[i], g.packed, module, g.config)
|
||||
concreteTypes[i] = FullId(module: tmp.module, packed: it.concreteTypes[i])
|
||||
|
||||
g.procInstCache.mgetOrPut(key, @[]).add LazyInstantiation(
|
||||
module: module, sym: FullId(module: sym.module, packed: it.sym),
|
||||
concreteTypes: concreteTypes, inst: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methodsPerGenericType):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let col = it[1]
|
||||
let tmp = translateId(it[2], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[2])
|
||||
g.methodsPerGenericType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
|
||||
|
||||
replayBackendProcs(g, module)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methods):
|
||||
let sym = loadSymFromId(g.config, g.cache, g.packed, module,
|
||||
PackedItemId(module: LitId(0), item: it))
|
||||
methodDef(g, g.idgen, sym)
|
||||
|
||||
when false:
|
||||
# not used anymore:
|
||||
for it in mitems(g.packed[module].fromDisk.compilerProcs):
|
||||
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it[1]))
|
||||
g.lazyCompilerprocs[g.packed[module].fromDisk.sh.strings[it[0]]] = symId
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.converters):
|
||||
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
|
||||
g.ifaces[module].converters.add LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.trmacros):
|
||||
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
|
||||
g.ifaces[module].patterns.add LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.pureEnums):
|
||||
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
|
||||
g.ifaces[module].pureEnums.add LazySym(id: symId, sym: nil)
|
||||
|
||||
283
compiler/ic/rodfiles.nim
Normal file
283
compiler/ic/rodfiles.nim
Normal file
@@ -0,0 +1,283 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2020 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Low level binary format used by the compiler to store and load various AST
|
||||
## and related data.
|
||||
##
|
||||
## NB: this is incredibly low level and if you're interested in how the
|
||||
## compiler works and less a storage format, you're probably looking for
|
||||
## the `ic` or `packed_ast` modules to understand the logical format.
|
||||
|
||||
from std/typetraits import supportsCopyMem
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions]
|
||||
|
||||
import std / tables
|
||||
|
||||
## Overview
|
||||
## ========
|
||||
## `RodFile` represents a Rod File (versioned binary format), and the
|
||||
## associated data for common interactions such as IO and error tracking
|
||||
## (`RodFileError`). The file format broken up into sections (`RodSection`)
|
||||
## and preceded by a header (see: `cookie`). The precise layout, section
|
||||
## ordering and data following the section are determined by the user. See
|
||||
## `ic.loadRodFile`.
|
||||
##
|
||||
## A basic but "wrong" example of the lifecycle:
|
||||
## ---------------------------------------------
|
||||
## 1. `create` or `open` - create a new one or open an existing
|
||||
## 2. `storeHeader` - header info
|
||||
## 3. `storePrim` or `storeSeq` - save your stuff
|
||||
## 4. `close` - and we're done
|
||||
##
|
||||
## Now read the bits below to understand what's missing.
|
||||
##
|
||||
## ### Issues with the Example
|
||||
## Missing Sections:
|
||||
## This is a low level API, so headers and sections need to be stored and
|
||||
## loaded by the user, see `storeHeader` & `loadHeader` and `storeSection` &
|
||||
## `loadSection`, respectively.
|
||||
##
|
||||
## No Error Handling:
|
||||
## The API is centered around IO and prone to error, each operation checks or
|
||||
## sets the `RodFile.err` field. A user of this API needs to handle these
|
||||
## appropriately.
|
||||
##
|
||||
## API Notes
|
||||
## =========
|
||||
##
|
||||
## Valid inputs for Rod files
|
||||
## --------------------------
|
||||
## ASTs, hopes, dreams, and anything as long as it and any children it may have
|
||||
## support `copyMem`. This means anything that is not a pointer and that does not contain a pointer. At a glance these are:
|
||||
## * string
|
||||
## * objects & tuples (fields are recursed)
|
||||
## * sequences AKA `seq[T]`
|
||||
##
|
||||
## Note on error handling style
|
||||
## ----------------------------
|
||||
## A flag based approach is used where operations no-op in case of a
|
||||
## preexisting error and set the flag if they encounter one.
|
||||
##
|
||||
## Misc
|
||||
## ----
|
||||
## * 'Prim' is short for 'primitive', as in a non-sequence type
|
||||
|
||||
type
|
||||
RodSection* = enum
|
||||
versionSection
|
||||
configSection
|
||||
stringsSection
|
||||
checkSumsSection
|
||||
depsSection
|
||||
numbersSection
|
||||
exportsSection
|
||||
hiddenSection
|
||||
reexportsSection
|
||||
compilerProcsSection
|
||||
trmacrosSection
|
||||
convertersSection
|
||||
methodsSection
|
||||
pureEnumsSection
|
||||
toReplaySection
|
||||
topLevelSection
|
||||
bodiesSection
|
||||
symsSection
|
||||
typesSection
|
||||
typeInstCacheSection
|
||||
procInstCacheSection
|
||||
attachedOpsSection
|
||||
methodsPerGenericTypeSection
|
||||
enumToStringProcsSection
|
||||
methodsPerTypeSection
|
||||
dispatchersSection
|
||||
typeInfoSection # required by the backend
|
||||
backendFlagsSection
|
||||
aliveSymsSection # beware, this is stored in a `.alivesyms` file.
|
||||
sideChannelSection
|
||||
namespaceSection
|
||||
symnamesSection
|
||||
|
||||
RodFileError* = enum
|
||||
ok, tooBig, cannotOpen, ioFailure, wrongHeader, wrongSection, configMismatch,
|
||||
includeFileChanged
|
||||
|
||||
RodFile* = object
|
||||
f*: File
|
||||
currentSection*: RodSection # for error checking
|
||||
err*: RodFileError # little experiment to see if this works
|
||||
# better than exceptions.
|
||||
|
||||
const
|
||||
RodVersion = 2
|
||||
defaultCookie = [byte(0), byte('R'), byte('O'), byte('D'),
|
||||
byte(sizeof(int)*8), byte(system.cpuEndian), byte(0), byte(RodVersion)]
|
||||
|
||||
proc setError(f: var RodFile; err: RodFileError) {.inline.} =
|
||||
f.err = err
|
||||
#raise newException(IOError, "IO error")
|
||||
|
||||
proc storePrim*(f: var RodFile; s: string) =
|
||||
## Stores a string.
|
||||
## The len is prefixed to allow for later retreival.
|
||||
if f.err != ok: return
|
||||
if s.len >= high(int32):
|
||||
setError f, tooBig
|
||||
return
|
||||
var lenPrefix = int32(s.len)
|
||||
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
if s.len != 0:
|
||||
if writeBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
|
||||
setError f, ioFailure
|
||||
|
||||
proc storePrim*[T](f: var RodFile; x: T) =
|
||||
## Stores a non-sequence/string `T`.
|
||||
## If `T` doesn't support `copyMem` and is an object or tuple then the fields
|
||||
## are written -- the user from context will need to know which `T` to load.
|
||||
if f.err != ok: return
|
||||
when supportsCopyMem(T):
|
||||
if writeBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
|
||||
setError f, ioFailure
|
||||
elif T is tuple:
|
||||
for y in fields(x):
|
||||
storePrim(f, y)
|
||||
elif T is object:
|
||||
for y in fields(x):
|
||||
when y is seq:
|
||||
storeSeq(f, y)
|
||||
else:
|
||||
storePrim(f, y)
|
||||
else:
|
||||
{.error: "unsupported type for 'storePrim'".}
|
||||
|
||||
proc storeSeq*[T](f: var RodFile; s: seq[T]) =
|
||||
## Stores a sequence of `T`s, with the len as a prefix for later retrieval.
|
||||
if f.err != ok: return
|
||||
if s.len >= high(int32):
|
||||
setError f, tooBig
|
||||
return
|
||||
var lenPrefix = int32(s.len)
|
||||
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
for i in 0..<s.len:
|
||||
storePrim(f, s[i])
|
||||
|
||||
proc storeOrderedTable*[K, T](f: var RodFile; s: OrderedTable[K, T]) =
|
||||
if f.err != ok: return
|
||||
if s.len >= high(int32):
|
||||
setError f, tooBig
|
||||
return
|
||||
var lenPrefix = int32(s.len)
|
||||
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
for _, v in s:
|
||||
storePrim(f, v)
|
||||
|
||||
proc loadPrim*(f: var RodFile; s: var string) =
|
||||
## Read a string, the length was stored as a prefix
|
||||
if f.err != ok: return
|
||||
var lenPrefix = int32(0)
|
||||
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
s = newString(lenPrefix)
|
||||
if lenPrefix > 0:
|
||||
if readBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
|
||||
setError f, ioFailure
|
||||
|
||||
proc loadPrim*[T](f: var RodFile; x: var T) =
|
||||
## Load a non-sequence/string `T`.
|
||||
if f.err != ok: return
|
||||
when supportsCopyMem(T):
|
||||
if readBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
|
||||
setError f, ioFailure
|
||||
elif T is tuple:
|
||||
for y in fields(x):
|
||||
loadPrim(f, y)
|
||||
elif T is object:
|
||||
for y in fields(x):
|
||||
when y is seq:
|
||||
loadSeq(f, y)
|
||||
else:
|
||||
loadPrim(f, y)
|
||||
else:
|
||||
{.error: "unsupported type for 'loadPrim'".}
|
||||
|
||||
proc loadSeq*[T](f: var RodFile; s: var seq[T]) =
|
||||
## `T` must be compatible with `copyMem`, see `loadPrim`
|
||||
if f.err != ok: return
|
||||
var lenPrefix = int32(0)
|
||||
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
s = newSeq[T](lenPrefix)
|
||||
for i in 0..<lenPrefix:
|
||||
loadPrim(f, s[i])
|
||||
|
||||
proc loadOrderedTable*[K, T](f: var RodFile; s: var OrderedTable[K, T]) =
|
||||
## `T` must be compatible with `copyMem`, see `loadPrim`
|
||||
if f.err != ok: return
|
||||
var lenPrefix = int32(0)
|
||||
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
s = initOrderedTable[K, T](lenPrefix)
|
||||
for i in 0..<lenPrefix:
|
||||
var x = default T
|
||||
loadPrim(f, x)
|
||||
s[x.id] = x
|
||||
|
||||
proc storeHeader*(f: var RodFile; cookie = defaultCookie) =
|
||||
## stores the header which is described by `cookie`.
|
||||
if f.err != ok: return
|
||||
if f.f.writeBytes(cookie, 0, cookie.len) != cookie.len:
|
||||
setError f, ioFailure
|
||||
|
||||
proc loadHeader*(f: var RodFile; cookie = defaultCookie) =
|
||||
## Loads the header which is described by `cookie`.
|
||||
if f.err != ok: return
|
||||
var thisCookie: array[cookie.len, byte] = default(array[cookie.len, byte])
|
||||
if f.f.readBytes(thisCookie, 0, thisCookie.len) != thisCookie.len:
|
||||
setError f, ioFailure
|
||||
elif thisCookie != cookie:
|
||||
setError f, wrongHeader
|
||||
|
||||
proc storeSection*(f: var RodFile; s: RodSection) =
|
||||
## update `currentSection` and writes the bytes value of s.
|
||||
if f.err != ok: return
|
||||
assert f.currentSection < s
|
||||
f.currentSection = s
|
||||
storePrim(f, s)
|
||||
|
||||
proc loadSection*(f: var RodFile; expected: RodSection) =
|
||||
## read the bytes value of s, sets and error if the section is incorrect.
|
||||
if f.err != ok: return
|
||||
var s: RodSection = default(RodSection)
|
||||
loadPrim(f, s)
|
||||
if expected != s and f.err == ok:
|
||||
setError f, wrongSection
|
||||
|
||||
proc create*(filename: string): RodFile =
|
||||
## create the file and open it for writing
|
||||
result = default(RodFile)
|
||||
if not open(result.f, filename, fmWrite):
|
||||
setError result, cannotOpen
|
||||
|
||||
proc close*(f: var RodFile) = close(f.f)
|
||||
|
||||
proc open*(filename: string): RodFile =
|
||||
## open the file for reading
|
||||
result = default(RodFile)
|
||||
if not open(result.f, filename, fmRead):
|
||||
setError result, cannotOpen
|
||||
@@ -1,292 +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]
|
||||
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]
|
||||
var rest: seq[string] = @[]
|
||||
var droppedCmd = false
|
||||
for a in commandLineParams():
|
||||
if a.len == 0: continue
|
||||
if a[0] == '-':
|
||||
pargs.add a
|
||||
elif not droppedCmd:
|
||||
droppedCmd = true # drop the original command token (`ic`/`track`)
|
||||
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,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)
|
||||
@@ -10,10 +10,10 @@
|
||||
## This module implements the symbol importing mechanism.
|
||||
|
||||
import
|
||||
ast, msgs, options, idents, lookups,
|
||||
ast, astalgo, msgs, options, idents, lookups,
|
||||
semdata, modulepaths, sigmatch, lineinfos,
|
||||
modulegraphs, wordrecg
|
||||
from std/strutils import `%`, startsWith, replace
|
||||
from std/strutils import `%`, startsWith
|
||||
from std/sequtils import addUnique
|
||||
import std/[sets, tables, intsets]
|
||||
|
||||
@@ -108,8 +108,8 @@ proc rawImportSymbol(c: PContext, s, origin: PSym; importSet: var IntSet) =
|
||||
else:
|
||||
importPureEnumField(c, e)
|
||||
else:
|
||||
if s.kind == skConverter: addConverter(c, s)
|
||||
if hasPattern(s): addPattern(c, s)
|
||||
if s.kind == skConverter: addConverter(c, LazySym(sym: s))
|
||||
if hasPattern(s): addPattern(c, LazySym(sym: s))
|
||||
if s.owner != origin:
|
||||
c.exportIndirections.incl((origin.id, s.id))
|
||||
|
||||
@@ -190,19 +190,22 @@ proc addImport(c: PContext; im: sink ImportedModule) =
|
||||
template addUnnamedIt(c: PContext, fromMod: PSym; filter: untyped) {.dirty.} =
|
||||
for it in mitems c.graph.ifaces[fromMod.position].converters:
|
||||
if filter:
|
||||
if sfExported in it.flags:
|
||||
loadPackedSym(c.graph, it)
|
||||
if sfExported in it.sym.flags:
|
||||
addConverter(c, it)
|
||||
for it in mitems c.graph.ifaces[fromMod.position].patterns:
|
||||
if filter:
|
||||
if sfExported in it.flags:
|
||||
loadPackedSym(c.graph, it)
|
||||
if sfExported in it.sym.flags:
|
||||
addPattern(c, it)
|
||||
for it in mitems c.graph.ifaces[fromMod.position].pureEnums:
|
||||
if filter:
|
||||
importPureEnumFields(c, it, it.typ)
|
||||
loadPackedSym(c.graph, it)
|
||||
importPureEnumFields(c, it.sym, it.sym.typ)
|
||||
|
||||
proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) =
|
||||
c.addImport ImportedModule(m: fromMod, mode: importExcept, exceptSet: exceptSet)
|
||||
addUnnamedIt(c, fromMod, it.name.id notin exceptSet)
|
||||
addUnnamedIt(c, fromMod, it.sym.name.id notin exceptSet)
|
||||
|
||||
proc importAllSymbols*(c: PContext, fromMod: PSym) =
|
||||
c.addImport ImportedModule(m: fromMod, mode: importAll)
|
||||
@@ -242,8 +245,7 @@ proc importModuleAs(c: PContext; n: PNode, realModule: PSym, importHidden, track
|
||||
# avoids modifying `realModule`, see D20201209T194412 for `import {.all.}`
|
||||
result = createModuleAliasImpl(realModule.name)
|
||||
if importHidden:
|
||||
ensureMutable result
|
||||
result.optionsImpl.incl optImportHidden
|
||||
result.options.incl optImportHidden
|
||||
let moduleIdent = if n.kind in {nkInfix, nkImportAs}: n[^1] else: n
|
||||
result.info = moduleIdent.info
|
||||
if trackUnusedImport:
|
||||
@@ -289,8 +291,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
|
||||
c.recursiveDep = err
|
||||
|
||||
let trackUnusedImport = warnUnusedImportX in c.config.notes
|
||||
var realModule: PSym
|
||||
discard pushOptionEntry(c)
|
||||
let realModule = c.graph.importModuleCallback(c.graph, c.module, f)
|
||||
realModule = c.graph.importModuleCallback(c.graph, c.module, f)
|
||||
result = importModuleAs(c, n, realModule, transf.importHidden, trackUnusedImport)
|
||||
popOptionEntry(c)
|
||||
|
||||
@@ -304,9 +307,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
|
||||
var prefix = ""
|
||||
if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; "
|
||||
message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated")
|
||||
let moduleNameNorm = getModuleName(c.config, n).replace("\\", "/")
|
||||
if belongsToStdlib(c.graph, result) and not startsWith(moduleNameNorm, stdPrefix) and
|
||||
not startsWith(moduleNameNorm, "system/") and not startsWith(moduleNameNorm, "packages/"):
|
||||
let moduleName = getModuleName(c.config, n)
|
||||
if belongsToStdlib(c.graph, result) and not startsWith(moduleName, stdPrefix) and
|
||||
not startsWith(moduleName, "system/") and not startsWith(moduleName, "packages/"):
|
||||
message(c.config, n.info, warnStdPrefix, realModule.name.s)
|
||||
|
||||
proc suggestMod(n: PNode; s: PSym) =
|
||||
|
||||
@@ -24,7 +24,7 @@ import std/[strtabs, tables, strutils, intsets]
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
from trees import exprStructuralEquivalent, getRoot, isCursor, whichPragma, getPotentialWrites
|
||||
from trees import exprStructuralEquivalent, getRoot, whichPragma, getPotentialWrites
|
||||
|
||||
type
|
||||
Con = object
|
||||
@@ -69,14 +69,12 @@ proc hasDestructor(c: Con; t: PType): bool {.inline.} =
|
||||
result = ast.hasDestructor(t)
|
||||
when toDebug.len > 0:
|
||||
# for more effective debugging
|
||||
if not result and c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if not result and c.graph.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
assert(not containsGarbageCollectedRef(t))
|
||||
|
||||
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo; needsInit: bool): PNode =
|
||||
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo): PNode =
|
||||
let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.idgen, c.owner, info)
|
||||
sym.typ = typ
|
||||
if not needsInit:
|
||||
sym.incl sfNoInit
|
||||
s.vars.add(sym)
|
||||
result = newSymNode(sym)
|
||||
|
||||
@@ -167,7 +165,7 @@ proc isLastReadImpl(n: PNode; c: var Con; scope: var Scope): bool =
|
||||
|
||||
template hasDestructorOrAsgn(c: var Con, typ: PType): bool =
|
||||
# bug #23354; an object type could have a non-trivial assignements when it is passed to a sink parameter
|
||||
hasDestructor(c, typ) or (c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc} and
|
||||
hasDestructor(c, typ) or (c.graph.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
||||
typ.kind == tyObject and not isTrivial(getAttachedOp(c.graph, typ, attachedAsgn)))
|
||||
|
||||
proc isLastRead(n: PNode; c: var Con; s: var Scope): bool =
|
||||
@@ -180,6 +178,17 @@ 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
|
||||
@@ -234,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] &
|
||||
@@ -305,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:
|
||||
@@ -332,21 +329,21 @@ proc isCriticalLink(dest: PNode): bool {.inline.} =
|
||||
result = dest.kind != nkSym
|
||||
|
||||
proc finishCopy(c: var Con; result, dest: PNode; flags: set[MoveOrCopyFlag]; isFromSink: bool) =
|
||||
if c.graph.config.selectedGC in {gcOrc, gcYrc} and IsExplicitSink notin flags:
|
||||
if c.graph.config.selectedGC == gcOrc and IsExplicitSink notin flags:
|
||||
# add cyclic flag, but not to sink calls, which IsExplicitSink generates
|
||||
let t = dest.typ.skipTypes(tyUserTypeClasses + {tyGenericInst, tyAlias, tySink, tyDistinct})
|
||||
if cyclicType(c.graph, t):
|
||||
result.add boolLit(c.graph, result.info, isFromSink or isCriticalLink(dest))
|
||||
|
||||
proc genMarkCyclic(c: var Con; result, dest: PNode) =
|
||||
if c.graph.config.selectedGC in {gcOrc, gcYrc}:
|
||||
if c.graph.config.selectedGC == gcOrc:
|
||||
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
|
||||
if cyclicType(c.graph, t):
|
||||
if t.kind == tyRef:
|
||||
result.add callCodegenProc(c.graph, "nimMarkCyclic", dest.info, dest)
|
||||
else:
|
||||
let xenv = genBuiltin(c.graph, c.idgen, mAccessEnv, "accessEnv", dest)
|
||||
xenv.typ = getSysType(c.graph, dest.info, tyPointer)
|
||||
xenv.typ() = getSysType(c.graph, dest.info, tyPointer)
|
||||
result.add callCodegenProc(c.graph, "nimMarkCyclic", dest.info, xenv)
|
||||
|
||||
proc genCopyNoCheck(c: var Con; dest, ri: PNode; a: TTypeAttachedOp): PNode =
|
||||
@@ -374,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))
|
||||
@@ -422,7 +419,7 @@ proc genWasMoved(c: var Con, n: PNode): PNode =
|
||||
proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
|
||||
result = newNodeI(nkCall, info)
|
||||
result.add(newSymNode(createMagic(c.graph, c.idgen, "default", mDefault)))
|
||||
result.typ = t
|
||||
result.typ() = t
|
||||
|
||||
proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
|
||||
# generate: (let tmp = v; reset(v); tmp)
|
||||
@@ -460,50 +457,49 @@ proc isCapturedVar(n: PNode): bool =
|
||||
else: result = false
|
||||
|
||||
proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
|
||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||
let nTyp = n.typ.skipTypes(tyUserTypeClasses)
|
||||
if not hasDestructorOrAsgn(c, nTyp):
|
||||
# Non-managed (plain-old-data) type: no ownership transfer is needed.
|
||||
# Return the expression directly — no temp required.
|
||||
if c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
let tmp = c.getTemp(s, nTyp, n.info)
|
||||
if hasDestructorOrAsgn(c, nTyp):
|
||||
let typ = nTyp.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||
let op = getAttachedOp(c.graph, typ, attachedDup)
|
||||
if op != nil and tfHasOwned notin typ.flags:
|
||||
if sfError in op.flags:
|
||||
c.checkForErrorPragma(nTyp, n, "=dup")
|
||||
else:
|
||||
let copyOp = getAttachedOp(c.graph, typ, attachedAsgn)
|
||||
if copyOp != nil and sfError in copyOp.flags and
|
||||
sfOverridden notin op.flags:
|
||||
c.checkForErrorPragma(nTyp, n, "=dup", inferredFromCopy = true)
|
||||
|
||||
let src = p(n, c, s, normal)
|
||||
var newCall = newTreeIT(nkCall, src.info, src.typ,
|
||||
newSymNode(op),
|
||||
src)
|
||||
c.finishCopy(newCall, n, {}, isFromSink = true)
|
||||
result.add newTreeI(nkFastAsgn,
|
||||
src.info, tmp,
|
||||
newCall
|
||||
)
|
||||
else:
|
||||
result.add c.genWasMoved(tmp)
|
||||
var m = c.genCopy(tmp, n, {})
|
||||
m.add p(n, c, s, normal)
|
||||
c.finishCopy(m, n, {}, isFromSink = true)
|
||||
result.add m
|
||||
if isLValue(n) and not isCapturedVar(n) and nTyp.skipTypes(abstractInst).kind != tyRef and c.inSpawn == 0:
|
||||
message(c.graph.config, n.info, hintPerformance,
|
||||
("passing '$1' to a sink parameter introduces an implicit copy; " &
|
||||
"if possible, rearrange your program's control flow to prevent it") % $n)
|
||||
if c.inEnsureMove > 0:
|
||||
localError(c.graph.config, n.info, errFailedMove,
|
||||
("cannot move '$1', passing '$1' to a sink parameter introduces an implicit copy") % $n)
|
||||
else:
|
||||
if c.graph.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
assert(not containsManagedMemory(nTyp))
|
||||
if nTyp.skipTypes(abstractInst).kind in {tyOpenArray, tyVarargs}:
|
||||
localError(c.graph.config, n.info, "cannot create an implicit openArray copy to be passed to a sink parameter")
|
||||
return p(n, c, s, normal)
|
||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||
let tmp = c.getTemp(s, nTyp, n.info, needsInit = false)
|
||||
let typ = nTyp.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||
let op = getAttachedOp(c.graph, typ, attachedDup)
|
||||
if op != nil and tfHasOwned notin typ.flags:
|
||||
if sfError in op.flags:
|
||||
c.checkForErrorPragma(nTyp, n, "=dup")
|
||||
else:
|
||||
let copyOp = getAttachedOp(c.graph, typ, attachedAsgn)
|
||||
if copyOp != nil and sfError in copyOp.flags and
|
||||
sfOverridden notin op.flags:
|
||||
c.checkForErrorPragma(nTyp, n, "=dup", inferredFromCopy = true)
|
||||
|
||||
let src = p(n, c, s, normal)
|
||||
var newCall = newTreeIT(nkCall, src.info, src.typ,
|
||||
newSymNode(op),
|
||||
src)
|
||||
c.finishCopy(newCall, n, {}, isFromSink = true)
|
||||
result.add newTreeI(nkFastAsgn,
|
||||
src.info, tmp,
|
||||
newCall
|
||||
)
|
||||
else:
|
||||
result.add c.genWasMoved(tmp)
|
||||
var m = c.genCopy(tmp, n, {})
|
||||
m.add p(n, c, s, normal)
|
||||
c.finishCopy(m, n, {}, isFromSink = true)
|
||||
result.add m
|
||||
if isLValue(n) and not isCapturedVar(n) and nTyp.skipTypes(abstractInst).kind != tyRef and c.inSpawn == 0:
|
||||
message(c.graph.config, n.info, hintPerformance,
|
||||
("passing '$1' to a sink parameter introduces an implicit copy; " &
|
||||
"if possible, rearrange your program's control flow to prevent it") % $n)
|
||||
if c.inEnsureMove > 0:
|
||||
localError(c.graph.config, n.info, errFailedMove,
|
||||
("cannot move '$1', passing '$1' to a sink parameter introduces an implicit copy") % $n)
|
||||
result.add newTree(nkAsgn, tmp, p(n, c, s, normal))
|
||||
# Since we know somebody will take over the produced copy, there is
|
||||
# no need to destroy it.
|
||||
result.add tmp
|
||||
@@ -534,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)
|
||||
@@ -613,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}:
|
||||
@@ -774,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)
|
||||
@@ -804,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}:
|
||||
@@ -846,9 +825,9 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
|
||||
n[1].typ.skipTypes(abstractInst-{tyOwned}).kind == tyOwned:
|
||||
# allow conversions from owned to unowned via this little hack:
|
||||
let nTyp = n[1].typ
|
||||
n[1].typ = n.typ
|
||||
n[1].typ() = n.typ
|
||||
result[1] = p(n[1], c, s, sinkArg)
|
||||
result[1].typ = nTyp
|
||||
result[1].typ() = nTyp
|
||||
else:
|
||||
result[1] = p(n[1], c, s, sinkArg)
|
||||
elif n.kind in {nkObjDownConv, nkObjUpConv}:
|
||||
@@ -947,7 +926,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
|
||||
|
||||
if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
|
||||
result[0] = copyTree(n[0])
|
||||
if c.graph.config.selectedGC in {gcHooks, gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
if c.graph.config.selectedGC in {gcHooks, gcArc, gcAtomicArc, gcOrc}:
|
||||
let destroyOld = c.genDestroy(result[1])
|
||||
result = newTree(nkStmtList, destroyOld, result)
|
||||
else:
|
||||
@@ -985,14 +964,14 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
|
||||
s.locals.add v.sym
|
||||
pVarTopLevel(v, c, s, result)
|
||||
if ri.kind != nkEmpty:
|
||||
let isGlobalPragma = v.kind == nkSym and
|
||||
let isGlobalPragma = v.kind == nkSym and
|
||||
{sfPure, sfGlobal} <= v.sym.flags and
|
||||
isInProc
|
||||
|
||||
let value = moveOrCopy(v, ri, c, s, if v.kind == nkSym: {IsDecl} else: {})
|
||||
if isGlobalPragma:
|
||||
c.graph.procGlobals.add newTree(nkFastAsgn, v, ri)
|
||||
c.graph.procGlobals.add value
|
||||
else:
|
||||
let value = moveOrCopy(v, ri, c, s, if v.kind == nkSym: {IsDecl} else: {})
|
||||
result.add value
|
||||
elif ri.kind == nkEmpty and c.inLoop > 0:
|
||||
let skipInit = v.kind == nkDotExpr and # Closure var
|
||||
@@ -1022,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)
|
||||
@@ -1062,9 +1036,9 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
|
||||
n[1].typ.skipTypes(abstractInst-{tyOwned}).kind == tyOwned:
|
||||
# allow conversions from owned to unowned via this little hack:
|
||||
let nTyp = n[1].typ
|
||||
n[1].typ = n.typ
|
||||
n[1].typ() = n.typ
|
||||
result[1] = p(n[1], c, s, mode)
|
||||
result[1].typ = nTyp
|
||||
result[1].typ() = nTyp
|
||||
else:
|
||||
result[1] = p(n[1], c, s, mode)
|
||||
|
||||
@@ -1180,8 +1154,8 @@ proc ownsData(c: var Con; s: var Scope; orig: PNode; flags: set[MoveOrCopyFlag])
|
||||
break
|
||||
if n.kind in nkCallKinds and n.typ != nil and hasDestructor(c, n.typ):
|
||||
result = newNodeIT(nkStmtListExpr, orig.info, orig.typ)
|
||||
let tmp = c.getTemp(s, n.typ, n.info, needsInit = true)
|
||||
tmp.sym.flagsImpl.incl sfSingleUsedTemp
|
||||
let tmp = c.getTemp(s, n.typ, n.info)
|
||||
tmp.sym.flags.incl sfSingleUsedTemp
|
||||
result.add newTree(nkFastAsgn, tmp, copyTree(n))
|
||||
s.final.add c.genDestroy(tmp)
|
||||
n[] = tmp[]
|
||||
@@ -1356,7 +1330,7 @@ proc addSinkCopy(c: var Con; s: var Scope; sinkParams: seq[PSym]; n: PNode): PNo
|
||||
for param in sinkParams:
|
||||
if param.id in mutatedSet:
|
||||
let newSym = newSym(skTemp, getIdent(c.graph.cache, "sinkCopy"), c.idgen, param.owner, n.info)
|
||||
newSym.flagsImpl.incl sfFromGeneric
|
||||
newSym.flags.incl sfFromGeneric
|
||||
newSym.typ = param.typ.elementType
|
||||
mapping[param.id] = newSym
|
||||
let v = newNodeI(nkVarSection, n.info)
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
|
||||
proc copySymdef(n: PNode; locals: var Table[int, PSym]; idgen: IdGenerator; owner: PSym): PNode =
|
||||
case n.kind
|
||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||
result = n
|
||||
of nkSym:
|
||||
let oldSym = n.sym
|
||||
let newSym = copySym(oldSym, idgen)
|
||||
setOwner(newSym, owner)
|
||||
locals[oldSym.id] = newSym
|
||||
result = newSymNode(newSym, oldSym.info)
|
||||
else:
|
||||
result = shallowCopy(n)
|
||||
for i in 0..<n.len:
|
||||
result[i] = copySymdef(n[i], locals, idgen, owner)
|
||||
|
||||
proc copyInlineProcBody(n: PNode; locals: var Table[int, PSym]; idgen: IdGenerator; owner: PSym): PNode =
|
||||
case n.kind
|
||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||
result = n
|
||||
of nkSym:
|
||||
let sym = locals.getOrDefault(n.sym.id)
|
||||
if sym != nil:
|
||||
result = newSymNode(sym, n.info)
|
||||
else:
|
||||
result = n
|
||||
of nkLetSection, nkVarSection:
|
||||
result = shallowCopy(n)
|
||||
for i in 0..<n.len:
|
||||
let it = n[i]
|
||||
if it.kind == nkCommentStmt:
|
||||
result[i] = it
|
||||
elif it.kind in {nkIdentDefs, nkConstDef}:
|
||||
result[i] = shallowCopy(it)
|
||||
for j in 0..<it.len-2:
|
||||
result[i][j] = copySymdef(it[j], locals, idgen, owner)
|
||||
for j in it.len-2..<it.len:
|
||||
result[i][j] = copyInlineProcBody(it[j], locals, idgen, owner)
|
||||
else:
|
||||
assert it.kind == nkVarTuple
|
||||
result[i] = shallowCopy(it)
|
||||
for j in 0..<it.len-2:
|
||||
assert it[j].kind == nkSym
|
||||
let oldSym = it[j].sym
|
||||
let newSym = copySym(oldSym, idgen)
|
||||
setOwner(newSym, owner)
|
||||
locals[oldSym.id] = newSym
|
||||
result[i][j] = newSymNode(newSym, oldSym.info)
|
||||
for j in it.len-2..<it.len:
|
||||
result[i][j] = copyInlineProcBody(it[j], locals, idgen, owner)
|
||||
|
||||
of nkForStmt, nkParForStmt:
|
||||
result = shallowCopy(n)
|
||||
for i in 0..<n.len-2:
|
||||
assert n[i].kind == nkSym
|
||||
let oldSym = n[i].sym
|
||||
let newSym = copySym(oldSym, idgen)
|
||||
setOwner(newSym, owner)
|
||||
locals[oldSym.id] = newSym
|
||||
result[i] = newSymNode(newSym, oldSym.info)
|
||||
result[n.len-2] = copyInlineProcBody(n[n.len-2], locals, idgen, owner)
|
||||
result[n.len-1] = copyInlineProcBody(n[n.len-1], locals, idgen, owner)
|
||||
of routineDefs, nkTypeSection, nkTypeOfExpr, nkMixinStmt, nkBindStmt, nkConstSection:
|
||||
result = n
|
||||
else:
|
||||
result = shallowCopy(n)
|
||||
for i in 0..<n.len:
|
||||
result[i] = copyInlineProcBody(n[i], locals, idgen, owner)
|
||||
|
||||
proc copyParams(n: PNode; locals: var Table[int, PSym]; idgen: IdGenerator; owner: PSym): PNode =
|
||||
result = shallowCopy(n)
|
||||
result[0] = n[0] # return type
|
||||
for i in 1..<n.len:
|
||||
let it = n[i]
|
||||
assert it.kind == nkIdentDefs
|
||||
result[i] = shallowCopy(it)
|
||||
for j in 0..<it.len-2:
|
||||
assert it[j].kind == nkSym
|
||||
let oldSym = it[j].sym
|
||||
let newSym = copySym(oldSym, idgen)
|
||||
setOwner(newSym, owner)
|
||||
locals[oldSym.id] = newSym
|
||||
result[i][j] = newSymNode(newSym, oldSym.info)
|
||||
owner.typ.addParam newSym
|
||||
for j in it.len-2..<it.len:
|
||||
result[i][j] = copyInlineProcBody(it[j], locals, idgen, owner)
|
||||
|
||||
proc copyInlineProc(prc: PSym; idgen: IdGenerator): PSym =
|
||||
result = copySym(prc, idgen)
|
||||
var locals = initTable[int, PSym]()
|
||||
|
||||
var a = shallowCopy(prc.ast)
|
||||
if resultPos < prc.ast.len and prc.ast[resultPos].kind == nkSym:
|
||||
let oldRes = prc.ast[resultPos].sym
|
||||
let newRes = copySym(oldRes, idgen)
|
||||
setOwner(newRes, result)
|
||||
locals[oldRes.id] = newRes
|
||||
a[resultPos] = newSymNode(newRes, oldRes.info)
|
||||
|
||||
result.typ = copyType(prc.typ, idgen, result)
|
||||
result.typ.n = newNodeI(prc.typ.n.kind, prc.typ.n.info)
|
||||
if prc.typ.n.len > 0:
|
||||
result.typ.n.add copyNode(prc.typ.n[0])
|
||||
for i in 1..<prc.typ.n.len:
|
||||
let it = prc.typ.n[i]
|
||||
assert it.kind == nkSym
|
||||
let oldSym = it.sym
|
||||
let newSym = copySym(oldSym, idgen)
|
||||
setOwner(newSym, result)
|
||||
locals[oldSym.id] = newSym
|
||||
result.typ.addParam newSym
|
||||
|
||||
for i in 0..<prc.ast.len:
|
||||
if i == paramsPos:
|
||||
a[i] = copyTree(prc.ast[i])
|
||||
elif i == resultPos and prc.ast[i].kind == nkSym:
|
||||
discard "handled above"
|
||||
else:
|
||||
a[i] = copyInlineProcBody(prc.ast[i], locals, idgen, result)
|
||||
result.ast = a
|
||||
|
||||
#echo "Produced: ", renderTree(result.ast, {renderIds})
|
||||
@@ -460,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
|
||||
|
||||
@@ -277,8 +277,7 @@ proc mangleName(m: BModule, s: PSym): Rope =
|
||||
else:
|
||||
result.add("_")
|
||||
result.add(rope(s.id))
|
||||
ensureMutable s
|
||||
s.locImpl.snippet = result
|
||||
s.loc.snippet = result
|
||||
|
||||
proc escapeJSString(s: string): string =
|
||||
result = newStringOfCap(s.len + s.len shr 2)
|
||||
@@ -729,47 +728,44 @@ proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
|
||||
of mShrI:
|
||||
let typ = n[1].typ.skipTypes(abstractVarRange)
|
||||
if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
|
||||
applyFormat("BigInt.asIntN(64, BigInt.asUintN(64, $1) >> (BigInt($2) & 63n))")
|
||||
applyFormat("BigInt.asIntN(64, BigInt.asUintN(64, $1) >> BigInt($2))")
|
||||
elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
|
||||
applyFormat("($1 >> (BigInt($2) & 63n))")
|
||||
applyFormat("($1 >> BigInt($2))")
|
||||
else:
|
||||
let bitmask = typ.size * 8 - 1
|
||||
if typ.kind in {tyInt..tyInt32}:
|
||||
let trimmerU = unsignedTrimmer(typ.size)
|
||||
let trimmerS = signedTrimmer(typ.size)
|
||||
r.res = "((($1 $2) >>> ($3 & $5)) $4)" % [xLoc, trimmerU, yLoc, trimmerS, $bitmask]
|
||||
r.res = "((($1 $2) >>> $3) $4)" % [xLoc, trimmerU, yLoc, trimmerS]
|
||||
else:
|
||||
r.res = "($1 >>> ($2 & $3))" % [xLoc, yLoc, $bitmask]
|
||||
applyFormat("($1 >>> $2)")
|
||||
of mShlI:
|
||||
let typ = n[1].typ.skipTypes(abstractVarRange)
|
||||
if typ.size == 8:
|
||||
if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
|
||||
applyFormat("BigInt.asIntN(64, $1 << (BigInt($2) & 63n))")
|
||||
applyFormat("BigInt.asIntN(64, $1 << BigInt($2))")
|
||||
elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
|
||||
applyFormat("BigInt.asUintN(64, $1 << (BigInt($2) & 63n))")
|
||||
applyFormat("BigInt.asUintN(64, $1 << BigInt($2))")
|
||||
else:
|
||||
applyFormat("($1 * Math.pow(2, ($2 & 63)))")
|
||||
applyFormat("($1 * Math.pow(2, $2))")
|
||||
else:
|
||||
let bitmask = typ.size * 8 - 1
|
||||
if typ.kind in {tyUInt..tyUInt32}:
|
||||
let trimmer = unsignedTrimmer(typ.size)
|
||||
r.res = "(($1 << ($2 & $4)) $3)" % [xLoc, yLoc, trimmer, $bitmask]
|
||||
r.res = "(($1 << $2) $3)" % [xLoc, yLoc, trimmer]
|
||||
else:
|
||||
let trimmer = signedTrimmer(typ.size)
|
||||
r.res = "(($1 << ($2 & $4)) $3)" % [xLoc, yLoc, trimmer, $bitmask]
|
||||
r.res = "(($1 << $2) $3)" % [xLoc, yLoc, trimmer]
|
||||
of mAshrI:
|
||||
let typ = n[1].typ.skipTypes(abstractVarRange)
|
||||
if typ.size == 8:
|
||||
if optJsBigInt64 in p.config.globalOptions:
|
||||
applyFormat("($1 >> (BigInt($2) & 63n))")
|
||||
applyFormat("($1 >> BigInt($2))")
|
||||
else:
|
||||
applyFormat("Math.floor($1 / Math.pow(2, ($2 & 63)))")
|
||||
applyFormat("Math.floor($1 / Math.pow(2, $2))")
|
||||
else:
|
||||
let bitmask = typ.size * 8 - 1
|
||||
if typ.kind in {tyUInt..tyUInt32}:
|
||||
r.res = "($1 >>> ($2 & $3)))" % [xLoc, yLoc, $bitmask]
|
||||
applyFormat("($1 >>> $2)")
|
||||
else:
|
||||
r.res = "($1 >> ($2 & $3))" % [xLoc, yLoc, $bitmask]
|
||||
applyFormat("($1 >> $2)")
|
||||
of mBitandI: bitwiseExpr("&")
|
||||
of mBitorI: bitwiseExpr("|")
|
||||
of mBitxorI: bitwiseExpr("^")
|
||||
@@ -1006,8 +1002,7 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
|
||||
# If some branch requires a local alias introduce it here. This is needed
|
||||
# since JS cannot do ``catch x as y``.
|
||||
if excAlias != nil:
|
||||
ensureMutable excAlias.sym
|
||||
excAlias.sym.locImpl.snippet = mangleName(p.module, excAlias.sym)
|
||||
excAlias.sym.loc.snippet = mangleName(p.module, excAlias.sym)
|
||||
lineF(p, "var $1 = lastJSError;$n", excAlias.sym.loc.snippet)
|
||||
gen(p, n[i][^1], a)
|
||||
moveInto(p, a, r)
|
||||
@@ -1140,8 +1135,7 @@ proc genBlock(p: PProc, n: PNode, r: var TCompRes) =
|
||||
# named block?
|
||||
if (n[0].kind != nkSym): internalError(p.config, n.info, "genBlock")
|
||||
var sym = n[0].sym
|
||||
ensureMutable sym
|
||||
sym.locImpl.k = locOther
|
||||
sym.loc.k = locOther
|
||||
sym.position = idx+1
|
||||
let labl = p.unique
|
||||
lineF(p, "Label$1: {$n", [labl.rope])
|
||||
@@ -1240,8 +1234,7 @@ proc generateHeader(p: PProc, prc: PSym): Rope =
|
||||
# to keep it simple
|
||||
let env = prc.ast[paramsPos].lastSon
|
||||
assert env.kind == nkSym, "env is missing"
|
||||
ensureMutable env.sym
|
||||
env.sym.locImpl.snippet = "this"
|
||||
env.sym.loc.snippet = "this"
|
||||
|
||||
for i in 1..<typ.n.len:
|
||||
assert(typ.n[i].kind == nkSym)
|
||||
@@ -1298,16 +1291,14 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
|
||||
xtyp = etySeq
|
||||
case xtyp
|
||||
of etySeq:
|
||||
if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or
|
||||
(x.kind == nkSym and sfCursor in x.sym.flags):
|
||||
if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
|
||||
lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
|
||||
else:
|
||||
useMagic(p, "nimCopy")
|
||||
lineF(p, "$1 = nimCopy(null, $2, $3);$n",
|
||||
[a.rdLoc, b.res, genTypeInfo(p, y.typ)])
|
||||
of etyObject:
|
||||
if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or
|
||||
(x.kind == nkSym and sfCursor in x.sym.flags):
|
||||
if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
|
||||
lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
|
||||
else:
|
||||
useMagic(p, "nimCopy")
|
||||
@@ -1380,15 +1371,12 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
|
||||
r.typ = etyBaseIndex
|
||||
let b = if n.kind == nkHiddenAddr: n[0] else: n
|
||||
gen(p, b[0], a)
|
||||
if skipTypes(b[0].typ, abstractVarRange + tyTypeClasses).kind == tyTuple:
|
||||
# ref #25227 about `+ tyTypeClasses`
|
||||
if skipTypes(b[0].typ, abstractVarRange).kind == tyTuple:
|
||||
r.res = makeJSString("Field" & $getFieldPosition(p, b[1]))
|
||||
else:
|
||||
if b[1].kind != nkSym: internalError(p.config, b[1].info, "genFieldAddr")
|
||||
var f = b[1].sym
|
||||
if f.loc.snippet == "":
|
||||
ensureMutable f
|
||||
f.locImpl.snippet = mangleName(p.module, f)
|
||||
if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f)
|
||||
r.res = makeJSString($f.loc.snippet)
|
||||
internalAssert p.config, a.typ != etyBaseIndex
|
||||
r.address = a.res
|
||||
@@ -1416,9 +1404,7 @@ proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) =
|
||||
else:
|
||||
if n[1].kind != nkSym: internalError(p.config, n[1].info, "genFieldAccess")
|
||||
var f = n[1].sym
|
||||
if f.loc.snippet == "":
|
||||
ensureMutable f
|
||||
f.locImpl.snippet = mangleName(p.module, f)
|
||||
if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f)
|
||||
r.res = "$1.$2" % [r.res, f.loc.snippet]
|
||||
mkTemp(1)
|
||||
r.kind = resExpr
|
||||
@@ -1439,15 +1425,11 @@ proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) =
|
||||
# Field symbol
|
||||
var field = accessExpr[1].sym
|
||||
internalAssert p.config, field.kind == skField
|
||||
if field.loc.snippet == "":
|
||||
ensureMutable field
|
||||
field.locImpl.snippet = mangleName(p.module, field)
|
||||
if field.loc.snippet == "": field.loc.snippet = mangleName(p.module, field)
|
||||
# Discriminant symbol
|
||||
let disc = checkExpr[2].sym
|
||||
internalAssert p.config, disc.kind == skField
|
||||
if disc.loc.snippet == "":
|
||||
ensureMutable disc
|
||||
disc.locImpl.snippet = mangleName(p.module, disc)
|
||||
if disc.loc.snippet == "": disc.loc.snippet = mangleName(p.module, disc)
|
||||
|
||||
var setx: TCompRes = default(TCompRes)
|
||||
gen(p, checkExpr[1], setx)
|
||||
@@ -1546,7 +1528,7 @@ proc genSymAddr(p: PProc, n: PNode, typ: PType, r: var TCompRes) =
|
||||
r.res = s.loc.snippet
|
||||
r.address = ""
|
||||
r.typ = etyNone
|
||||
of skVar, skLet, skResult, skTemp, skForVar:
|
||||
of skVar, skLet, skResult:
|
||||
r.kind = resExpr
|
||||
let jsType = mapType(p):
|
||||
if typ.isNil:
|
||||
@@ -1604,11 +1586,7 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
|
||||
of nkObjDownConv:
|
||||
gen(p, n[0], r)
|
||||
of nkHiddenDeref, nkDerefExpr:
|
||||
if n.kind in {nkAddr, nkHiddenAddr}:
|
||||
# addr ( deref ( x )) --> x
|
||||
gen(p, n[0][0], r)
|
||||
else:
|
||||
gen(p, n[0], r)
|
||||
gen(p, n[0], r)
|
||||
of nkHiddenAddr:
|
||||
gen(p, n[0], r)
|
||||
of nkConv:
|
||||
@@ -1859,9 +1837,7 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
|
||||
proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) =
|
||||
# don't call '$' here for efficiency:
|
||||
let f = n[0].sym
|
||||
if f.loc.snippet == "":
|
||||
ensureMutable f
|
||||
f.locImpl.snippet = mangleName(p.module, f)
|
||||
if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f)
|
||||
if sfInfixCall in f.flags:
|
||||
let pat = $n[0].sym.loc.snippet
|
||||
internalAssert p.config, pat.len > 0
|
||||
@@ -2020,12 +1996,8 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope =
|
||||
if indirect: result = "[$1]" % [result]
|
||||
of tyTuple:
|
||||
result = rope("{")
|
||||
var first = true
|
||||
for i in 0..<t.len:
|
||||
# Do not produce code for void types
|
||||
if isEmptyType(t[i]): continue
|
||||
if not first: result.add(", ")
|
||||
first = false
|
||||
if i > 0: result.add(", ")
|
||||
result.addf("Field$1: $2", [i.rope,
|
||||
createVar(p, t[i], false)])
|
||||
result.add("}")
|
||||
@@ -2094,8 +2066,7 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
|
||||
gen(p, n, a)
|
||||
case mapType(p, v.typ)
|
||||
of etyObject, etySeq:
|
||||
if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n) or
|
||||
sfCursor in v.flags:
|
||||
if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n):
|
||||
s = a.res
|
||||
else:
|
||||
useMagic(p, "nimCopy")
|
||||
@@ -2358,8 +2329,8 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
|
||||
r.res = "if (null != $1) { if (null == $2) $2 = $3; else $2 += $3; }" %
|
||||
[b, lhs.rdLoc, tmp]
|
||||
else:
|
||||
useMagic(p, "nimAddStrStr")
|
||||
r.res = "nimAddStrStr($1, $2);" % [lhs.rdLoc, rhs.rdLoc]
|
||||
let (a, tmp) = maybeMakeTemp(p, n[1], lhs)
|
||||
r.res = "$1.push.apply($3, $2);" % [a, rhs.rdLoc, tmp]
|
||||
r.kind = resExpr
|
||||
of mAppendSeqElem:
|
||||
var x, y: TCompRes = default(TCompRes)
|
||||
@@ -2602,9 +2573,7 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
|
||||
let val = it[1]
|
||||
gen(p, val, a)
|
||||
var f = it[0].sym
|
||||
if f.loc.snippet == "":
|
||||
ensureMutable f
|
||||
f.locImpl.snippet = mangleName(p.module, f)
|
||||
if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f)
|
||||
fieldIDs.incl(lookupFieldAgain(n.typ.skipTypes({tyDistinct}), f).id)
|
||||
|
||||
let typ = val.typ.skipTypes(abstractInst)
|
||||
@@ -2801,11 +2770,6 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
|
||||
var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, {})
|
||||
if sfInjectDestructors in prc.flags:
|
||||
transformedBody = injectDestructorCalls(p.module.graph, p.module.idgen, prc, transformedBody)
|
||||
else:
|
||||
# JS has a GC, so the destructor pass is off; but the cursor (alias) analysis
|
||||
# is independent of ownership and always memory-safe on a traced target.
|
||||
# Running it lets last-use `var b = a` aliases skip the deep `nimCopy`.
|
||||
computeCursors(prc, transformedBody, p.module.graph)
|
||||
|
||||
p.nested: genStmt(p, transformedBody)
|
||||
|
||||
@@ -2911,8 +2875,6 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) =
|
||||
elif dest.kind in tyFloat..tyFloat64:
|
||||
if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
|
||||
r.res = "Number($1)" % [r.res]
|
||||
elif dest.kind == tyChar and (fromInt or fromUint):
|
||||
r.res = "($1 & 255)" % [r.res]
|
||||
elif (src.kind == tyPtr and mapType(p, src) == etyObject) and dest.kind == tyPointer:
|
||||
r.address = r.res
|
||||
r.res = "null"
|
||||
|
||||
@@ -150,7 +150,6 @@ template isIterator*(owner: PSym): bool =
|
||||
|
||||
proc createEnvObj(g: ModuleGraph; idgen: IdGenerator; owner: PSym; info: TLineInfo): PType =
|
||||
result = createObj(g, idgen, owner, info, final=false)
|
||||
result.incl tfFinal
|
||||
if owner.isIterator:
|
||||
rawAddField(result, createStateField(g, owner, idgen))
|
||||
|
||||
@@ -161,24 +160,12 @@ proc getClosureIterResult*(g: ModuleGraph; iter: PSym; idgen: IdGenerator): PSym
|
||||
# XXX a bit hacky:
|
||||
result = newSym(skResult, getIdent(g.cache, ":result"), idgen, iter, iter.info, {})
|
||||
result.typ = iter.typ.returnType
|
||||
incl(result.flagsImpl, sfUsed)
|
||||
incl(result.flags, sfUsed)
|
||||
iter.ast.add newSymNode(result)
|
||||
|
||||
proc closureParams(routine: PSym): PNode =
|
||||
## The formal parameters node lambda lifting reads and extends. In a
|
||||
## from-source compilation `routine.ast[paramsPos]` and `routine.typ.n` are the
|
||||
## very same node (see the `typ.n.len` based position math below). Under IC the
|
||||
## loaded proc AST omits the parameters (they are kept only in `typ.n`), so
|
||||
## restore the shared node here.
|
||||
result = routine.ast[paramsPos]
|
||||
if (result == nil or result.kind == nkEmpty) and routine.typ != nil and
|
||||
routine.typ.n != nil and routine.ast.len > paramsPos:
|
||||
result = routine.typ.n
|
||||
routine.ast[paramsPos] = result
|
||||
|
||||
proc addHiddenParam*(routine: PSym, param: PSym) =
|
||||
proc addHiddenParam(routine: PSym, param: PSym) =
|
||||
assert param.kind == skParam
|
||||
var params = closureParams(routine)
|
||||
var params = routine.ast[paramsPos]
|
||||
# -1 is correct here as param.position is 0 based but we have at position 0
|
||||
# some nkEffect node:
|
||||
param.position = routine.typ.n.len-1
|
||||
@@ -189,8 +176,7 @@ proc addHiddenParam*(routine: PSym, param: PSym) =
|
||||
|
||||
proc getEnvParam*(routine: PSym): PSym =
|
||||
if routine.ast.isNil: return nil
|
||||
let params = closureParams(routine)
|
||||
if params == nil or params.len == 0: return nil
|
||||
let params = routine.ast[paramsPos]
|
||||
let hidden = lastSon(params)
|
||||
if hidden.kind == nkSym and hidden.sym.kind == skParam and hidden.sym.name.s == paramName:
|
||||
result = hidden.sym
|
||||
@@ -229,10 +215,6 @@ proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode =
|
||||
result[0] = le
|
||||
result[1] = ri
|
||||
|
||||
proc markInjectDestructors(s: PSym) {.inline.} =
|
||||
backendEnsureMutable s
|
||||
s.flagsImpl.incl sfInjectDestructors
|
||||
|
||||
proc makeClosure*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; env: PNode; info: TLineInfo): PNode =
|
||||
result = newNodeIT(nkClosure, info, prc.typ)
|
||||
result.add(newSymNode(prc))
|
||||
@@ -245,7 +227,7 @@ proc makeClosure*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; env: PNode; inf
|
||||
#if isClosureIterator(result.typ):
|
||||
createTypeBoundOps(g, nil, result.typ, info, idgen)
|
||||
if tfHasAsgn in result.typ.flags or optSeqDestructors in g.config.globalOptions:
|
||||
markInjectDestructors(prc)
|
||||
prc.flags.incl sfInjectDestructors
|
||||
|
||||
template liftingHarmful(conf: ConfigRef; owner: PSym): bool =
|
||||
## lambda lifting can be harmful for JS-like code generators.
|
||||
@@ -257,7 +239,7 @@ proc createTypeBoundOpsLL(g: ModuleGraph; refType: PType; info: TLineInfo; idgen
|
||||
createTypeBoundOps(g, nil, refType.elementType, info, idgen)
|
||||
createTypeBoundOps(g, nil, refType, info, idgen)
|
||||
if tfHasAsgn in refType.flags or optSeqDestructors in g.config.globalOptions:
|
||||
markInjectDestructors(owner)
|
||||
owner.flags.incl sfInjectDestructors
|
||||
|
||||
proc genCreateEnv(env: PNode): PNode =
|
||||
var c = newNodeIT(nkObjConstr, env.info, env.typ)
|
||||
@@ -307,27 +289,7 @@ proc markAsClosure(g: ModuleGraph; owner: PSym; n: PNode) =
|
||||
elif not (owner.typ.isClosure or owner.isNimcall and not owner.isExplicitCallConv or isEnv):
|
||||
localError(g.config, n.info, "illegal capture '$1' because '$2' has the calling convention: <$3>" %
|
||||
[s.name.s, owner.name.s, $owner.typ.callConv])
|
||||
unsealForTransform(owner.typ)
|
||||
incl(owner.typ, tfCapturesEnv)
|
||||
# A closure proc type that captures an env owns a REF to it: copying the closure
|
||||
# value must incref the env and destroying it must decref. That is exactly what
|
||||
# `tfHasAsgn` signals to `injectDestructorCalls` (so a closure assignment becomes
|
||||
# `=copy`, not a raw field store).
|
||||
#
|
||||
# Set it HERE (closure-type creation) so the flag is DETERMINISTIC and serializes
|
||||
# with the type — but ONLY under `nim ic`. The per-module `lower` stage is a
|
||||
# separate process that lowers routines in index order; if a consumer (e.g.
|
||||
# `workNimAsyncContinue`) was lowered before the closure type's ops were lifted,
|
||||
# its env store emitted a RAW assign with no incref → freed env → async
|
||||
# "yielded `nil`". A normal single-process `nim c` build does NOT need this —
|
||||
# `createTypeBoundOps` sets the flag lazily, in lift order, before it matters
|
||||
# (the old `liftdestructors ~1498` "XXX Breaks IC!" side effect) — and setting it
|
||||
# eagerly there REGRESSES codegen: a `=destroy` hook gets generated against the
|
||||
# bare `void(*)(void)` proc representation but is then called with closure structs
|
||||
# (`eqdestroy__u2__stdZtypedthreads` type mismatch — broke megatest). So gate on
|
||||
# `cmdNifC`; normal builds keep the lazy (devel) behavior.
|
||||
if g.config.cmd == cmdNifC:
|
||||
incl(owner.typ, tfHasAsgn)
|
||||
incl(owner.typ.flags, tfCapturesEnv)
|
||||
if not isEnv:
|
||||
owner.typ.callConv = ccClosure
|
||||
|
||||
@@ -373,7 +335,7 @@ proc asOwnedRef(c: var DetectionPass; t: PType): PType =
|
||||
if optOwnedRefs in c.graph.config.globalOptions:
|
||||
assert t.kind == tyRef
|
||||
result = newType(tyOwned, c.idgen, t.owner)
|
||||
result.incl tfHasOwned
|
||||
result.flags.incl tfHasOwned
|
||||
result.rawAddSon t
|
||||
else:
|
||||
result = t
|
||||
@@ -445,29 +407,17 @@ Consider:
|
||||
proc isTypeOf(n: PNode): bool =
|
||||
n.kind == nkSym and n.sym.magic in {mTypeOf, mType}
|
||||
|
||||
proc isEnvTypeForRoutine(envTyp: PType; routine: PSym): bool =
|
||||
## True if `envTyp` is (maybe wrapped) env object type owned by `routine`, as
|
||||
## created by `getEnvTypeForOwner` / `createEnvObj`.
|
||||
let obj = envTyp.skipTypes({tyOwned, tyRef, tyPtr})
|
||||
result = obj.kind == tyObject and obj.owner.id == routine.id
|
||||
|
||||
proc addClosureParam(c: var DetectionPass; fn: PSym; info: TLineInfo) =
|
||||
var cp = getEnvParam(fn)
|
||||
let owner = if fn.kind == skIterator: fn else: fn.skipGenericOwner
|
||||
let t = c.getEnvTypeForOwner(owner, info)
|
||||
if cp == nil:
|
||||
cp = newSym(skParam, getIdent(c.graph.cache, paramName), c.idgen, fn, fn.info)
|
||||
incl(cp.flagsImpl, sfFromGeneric)
|
||||
incl(cp.flags, sfFromGeneric)
|
||||
cp.typ = t
|
||||
addHiddenParam(fn, cp)
|
||||
elif cp.typ != t and fn.kind != skIterator:
|
||||
# Nested `liftLambdas` uses a fresh `DetectionPass`, so `getEnvTypeForOwner`
|
||||
# can allocate another PType for the same logical env; the hidden param from
|
||||
# the inner pass is authoritative (bug #21242).
|
||||
if isEnvTypeForRoutine(cp.typ, owner) and isEnvTypeForRoutine(t, owner):
|
||||
c.ownerToType[owner.id] = cp.typ
|
||||
else:
|
||||
localError(c.graph.config, fn.info, "internal error: inconsistent environment type")
|
||||
localError(c.graph.config, fn.info, "internal error: inconsistent environment type")
|
||||
#echo "adding closure to ", fn.name.s
|
||||
|
||||
proc iterEnvHasUpField(g: ModuleGraph, iter: PSym): bool =
|
||||
@@ -659,7 +609,7 @@ proc rawClosureCreation(owner: PSym;
|
||||
let unowned = c.unownedEnvVars[owner.id]
|
||||
assert unowned != nil
|
||||
let env2 = copyTree(env)
|
||||
env2.typ = unowned.typ
|
||||
env2.typ() = unowned.typ
|
||||
result.add newAsgnStmt(unowned, env2, env.info)
|
||||
createTypeBoundOpsLL(d.graph, unowned.typ, env.info, d.idgen, owner)
|
||||
|
||||
@@ -673,7 +623,7 @@ proc rawClosureCreation(owner: PSym;
|
||||
if owner.kind != skMacro:
|
||||
createTypeBoundOps(d.graph, nil, fieldAccess.typ, env.info, d.idgen)
|
||||
if tfHasAsgn in fieldAccess.typ.flags or optSeqDestructors in d.graph.config.globalOptions:
|
||||
markInjectDestructors(owner)
|
||||
owner.flags.incl sfInjectDestructors
|
||||
|
||||
let upField = lookupInRecord(env.typ.skipTypes({tyOwned, tyRef, tyPtr}).n, getIdent(d.graph.cache, upName))
|
||||
if upField != nil:
|
||||
@@ -715,7 +665,7 @@ proc closureCreationForIter(owner: PSym, iter: PNode;
|
||||
result = newNodeIT(nkStmtListExpr, iter.info, iter.sym.typ)
|
||||
let iterOwner = iter.sym.skipGenericOwner
|
||||
var v = newSym(skVar, getIdent(d.graph.cache, envName), d.idgen, iterOwner, iter.info)
|
||||
incl(v.flagsImpl, sfShadowed)
|
||||
incl(v.flags, sfShadowed)
|
||||
v.typ = asOwnedRef(d, getHiddenParam(d.graph, iter.sym).typ)
|
||||
var vnode: PNode
|
||||
if iterOwner.isIterator:
|
||||
@@ -836,7 +786,7 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: var DetectionPass;
|
||||
let oldInContainer = c.inContainer
|
||||
c.inContainer = 0
|
||||
let m = newSymNode(n[namePos].sym)
|
||||
m.typ = n.typ
|
||||
m.typ() = n.typ
|
||||
result = liftCapturedVars(m, owner, d, c)
|
||||
c.inContainer = oldInContainer
|
||||
of nkHiddenStdConv:
|
||||
@@ -1024,20 +974,6 @@ proc liftForLoop*(g: ModuleGraph; body: PNode; idgen: IdGenerator; owner: PSym):
|
||||
for i in 0..<op.len-1:
|
||||
result.add op[i]
|
||||
|
||||
elif op.kind != nkSym: # might have side effects
|
||||
# bug #25046
|
||||
# create a temp for the closure
|
||||
# var :closureTemp
|
||||
# :closureTemp = ...
|
||||
let tempSym = newSym(skLet, getIdent(g.cache, ":closureTemp"), idgen, owner, body.info)
|
||||
tempSym.typ = call[0].typ
|
||||
let temp = newSymNode(tempSym)
|
||||
var v = newNodeI(nkVarSection, body.info)
|
||||
addVar(v, temp)
|
||||
result.add(v)
|
||||
result.add newAsgnStmt(temp, call[0], body.info)
|
||||
call[0] = temp
|
||||
|
||||
var loopBody = newNodeI(nkStmtList, body.info, 3)
|
||||
var whileLoop = newNodeI(nkWhileStmt, body.info, 2)
|
||||
whileLoop[0] = newIntTypeNode(1, getSysType(g, body.info, tyBool))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import std/[tables]
|
||||
import ast, astalgo
|
||||
|
||||
type
|
||||
@@ -46,11 +47,12 @@ proc setToPreviousLayer*(pt: var LayeredIdTable) {.inline.} =
|
||||
when useRef:
|
||||
pt = pt.nextLayer
|
||||
else:
|
||||
# Must read nextLayer into a temp before destroying pt:
|
||||
# `pt = pt.nextLayer[]` would call eqcopy(&pt, &(*pt.nextLayer)) which
|
||||
# decrements pt.nextLayer's rc (freeing it) before reading pt.nextLayer.nextLayer.
|
||||
let tmp = pt.nextLayer[]
|
||||
pt = tmp
|
||||
when defined(gcDestructors):
|
||||
pt = pt.nextLayer[]
|
||||
else:
|
||||
# workaround refc
|
||||
let tmp = pt.nextLayer[]
|
||||
pt = tmp
|
||||
|
||||
iterator pairs*(pt: LayeredIdTable): (ItemId, PType) =
|
||||
var tm = pt
|
||||
|
||||
@@ -316,28 +316,6 @@ proc getNumber(L: var Lexer, result: var Token) =
|
||||
L.bufpos = msgPos
|
||||
lexMessage(L, msgKind, msg % t.literal)
|
||||
|
||||
proc checkBitWidth(L: var Lexer, base: NumericalBase, tokType: TokType,
|
||||
numDigits: int, startpos: int) =
|
||||
# Check bit width for non-base-10 literals
|
||||
# Warn if the digit count exceeds what can fit in the target type
|
||||
let bitsPerDigit = case base
|
||||
of base2: 1
|
||||
of base8: 3
|
||||
of base16: 4
|
||||
else: raiseAssert "unreachable"
|
||||
let bitWidth = case tokType
|
||||
of tkInt8Lit, tkUInt8Lit: 8
|
||||
of tkInt16Lit, tkUInt16Lit: 16
|
||||
of tkInt32Lit, tkUInt32Lit: 32
|
||||
of tkInt64Lit, tkUIntLit, tkIntLit, tkUInt64Lit: 64
|
||||
else: raiseAssert "unreachable"
|
||||
# Maximum digits = ceil(bitWidth / bitsPerDigit) = (bitWidth + bitsPerDigit - 1) div bitsPerDigit
|
||||
let maxDigits = (bitWidth + bitsPerDigit - 1) div bitsPerDigit
|
||||
if numDigits > maxDigits:
|
||||
lexMessageLitNum(L,
|
||||
"number has " & $numDigits & " digits but type only supports " &
|
||||
$maxDigits & " digits: '$1'", startpos, warnLongLiterals)
|
||||
|
||||
var
|
||||
xi: BiggestInt
|
||||
isBase10 = true
|
||||
@@ -513,11 +491,6 @@ proc getNumber(L: var Lexer, result: var Token) =
|
||||
setNumber result.fNumber, (cast[ptr float64](addr(xi)))[]
|
||||
else: internalError(L.config, getLineInfo(L), "getNumber")
|
||||
|
||||
# Check bit width for non-base-10 literals
|
||||
# Warn if the digit count exceeds what can fit in the target type
|
||||
if result.base != base10 and result.tokType in {tkIntLit..tkUInt64Lit} and numDigits > 0:
|
||||
checkBitWidth(L, result.base, result.tokType, numDigits, startpos)
|
||||
|
||||
# Bounds checks. Non decimal literals are allowed to overflow the range of
|
||||
# the datatype as long as their pattern don't overflow _bitwise_, hence
|
||||
# below checks of signed sizes against uint*.high is deliberate:
|
||||
@@ -923,7 +896,7 @@ proc getSymbol(L: var Lexer, tok: var Token) =
|
||||
tok.tokType = tkSymbol
|
||||
else:
|
||||
tok.tokType = TokType(tok.ident.id + ord(tkSymbol))
|
||||
if suspicious and {optStyleHint, optStyleError, optStyleWarning} * L.config.globalOptions != {}:
|
||||
if suspicious and {optStyleHint, optStyleError} * L.config.globalOptions != {}:
|
||||
lintReport(L.config, getLineInfo(L), tok.ident.s.normalize, tok.ident.s)
|
||||
L.bufpos = pos
|
||||
|
||||
@@ -1349,7 +1322,7 @@ proc rawGetTok*(L: var Lexer, tok: var Token) =
|
||||
lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
|
||||
of '-':
|
||||
if L.buf[L.bufpos+1] in {'0'..'9'} and
|
||||
(L.bufpos == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
|
||||
(L.bufpos-1 == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
|
||||
# x)-23 # binary minus
|
||||
# ,-23 # unary minus
|
||||
# \n-78 # unary minus? Yes.
|
||||
|
||||
@@ -49,7 +49,7 @@ proc at(a, i: PNode, elemType: PType): PNode =
|
||||
result = newNodeI(nkBracketExpr, a.info, 2)
|
||||
result[0] = a
|
||||
result[1] = i
|
||||
result.typ = elemType
|
||||
result.typ() = elemType
|
||||
|
||||
proc destructorOverridden(g: ModuleGraph; t: PType): bool =
|
||||
let op = getAttachedOp(g, t, attachedDestructor)
|
||||
@@ -68,7 +68,7 @@ proc dotField(x: PNode, f: PSym): PNode =
|
||||
else:
|
||||
result[0] = x
|
||||
result[1] = newSymNode(f, x.info)
|
||||
result.typ = f.typ
|
||||
result.typ() = f.typ
|
||||
|
||||
proc newAsgnStmt(le, ri: PNode): PNode =
|
||||
result = newNodeI(nkAsgn, le.info, 2)
|
||||
@@ -88,34 +88,24 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
body.add newAsgnStmt(x, y)
|
||||
elif c.kind == attachedDestructor and c.addMemReset:
|
||||
let call = genBuiltin(c, mDefault, "default", x)
|
||||
call.typ = t
|
||||
call.typ() = t
|
||||
body.add newAsgnStmt(x, call)
|
||||
elif c.kind == attachedWasMoved:
|
||||
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
|
||||
proc genAddr(c: var TLiftCtx; x: PNode): PNode =
|
||||
# These synthesized addresses are always passed to codegen procs that expect a
|
||||
# genuine pointer (nimAsgnYrc, nimSinkYrc, destructors, ...). `addr(deref x)`
|
||||
# collapses to `x` only when `x` is a real pointer; on the C++ backend a `var`
|
||||
# parameter is a C++ reference, so we must keep the `nkHiddenAddr` to actually
|
||||
# take its address (`&dest`) instead of passing the reference's value. Likewise
|
||||
# `tfVarIsPtr` keeps the C++ backend from lowering the synthesized address back
|
||||
# to a reference and dropping the `&` (e.g. a closure's `tyPointer` env). See
|
||||
# #26026 CI (yrc + cpp).
|
||||
if x.kind == nkHiddenDeref and c.g.config.backend != backendCpp:
|
||||
if x.kind == nkHiddenDeref:
|
||||
checkSonsLen(x, 1, c.g.config)
|
||||
result = x[0]
|
||||
else:
|
||||
let addrTyp = makeVarType(x.typ.owner, x.typ, c.idgen)
|
||||
addrTyp.incl tfVarIsPtr
|
||||
result = newNodeIT(nkHiddenAddr, x.info, addrTyp)
|
||||
result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ, c.idgen))
|
||||
result.add x
|
||||
|
||||
proc genWhileLoop(c: var TLiftCtx; i, dest: PNode): PNode =
|
||||
result = newNodeI(nkWhileStmt, c.info, 2)
|
||||
let cmp = genBuiltin(c, mLtI, "<", i)
|
||||
cmp.add genLen(c.g, dest)
|
||||
cmp.typ = getSysType(c.g, c.info, tyBool)
|
||||
cmp.typ() = getSysType(c.g, c.info, tyBool)
|
||||
result[0] = cmp
|
||||
result[1] = newNodeI(nkStmtList, c.info)
|
||||
|
||||
@@ -137,10 +127,10 @@ proc genContainerOf(c: var TLiftCtx; objType: PType, field, x: PSym): PNode =
|
||||
dotExpr.add newSymNode(field)
|
||||
|
||||
let offsetOf = genBuiltin(c, mOffsetOf, "offsetof", dotExpr)
|
||||
offsetOf.typ = intType
|
||||
offsetOf.typ() = intType
|
||||
|
||||
let minusExpr = genBuiltin(c, mSubI, "-", castExpr1)
|
||||
minusExpr.typ = intType
|
||||
minusExpr.typ() = intType
|
||||
minusExpr.add offsetOf
|
||||
|
||||
let objPtr = makePtrType(objType.owner, objType, c.idgen)
|
||||
@@ -173,7 +163,7 @@ proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode; enforceDefaultOp: bool,
|
||||
if c.filterDiscriminator != nil: return
|
||||
let f = n.sym
|
||||
let b = if c.kind == attachedTrace: y else: y.dotField(f)
|
||||
if (sfCursor in f.flags and c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc, gcHooks}) or
|
||||
if (sfCursor in f.flags and c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcHooks}) or
|
||||
enforceDefaultOp:
|
||||
defaultOp(c, f.typ, body, x.dotField(f), b)
|
||||
else:
|
||||
@@ -290,11 +280,11 @@ proc fillBodyObjT(c: var TLiftCtx; t: PType, body, x, y: PNode) =
|
||||
# because the wasMoved(dest) call would zero out src, if dest aliases src.
|
||||
var cond = newTree(nkCall, newSymNode(c.g.getSysMagic(c.info, "==", mEqRef)),
|
||||
newTreeIT(nkAddr, c.info, makePtrType(c.fn, x.typ, c.idgen), x), newTreeIT(nkAddr, c.info, makePtrType(c.fn, y.typ, c.idgen), y))
|
||||
cond.typ = getSysType(c.g, x.info, tyBool)
|
||||
cond.typ() = getSysType(c.g, x.info, tyBool)
|
||||
body.add genIf(c, cond, newTreeI(nkReturnStmt, c.info, newNodeI(nkEmpty, c.info)))
|
||||
var temp = newSym(skTemp, getIdent(c.g.cache, lowerings.genPrefix), c.idgen, c.fn, c.info)
|
||||
temp.typ = x.typ
|
||||
incl(temp, sfFromGeneric)
|
||||
incl(temp.flags, sfFromGeneric)
|
||||
var v = newNodeI(nkVarSection, c.info)
|
||||
let blob = newSymNode(temp)
|
||||
v.addVar(blob, x)
|
||||
@@ -322,7 +312,7 @@ proc fillBodyObjT(c: var TLiftCtx; t: PType, body, x, y: PNode) =
|
||||
|
||||
proc boolLit*(g: ModuleGraph; info: TLineInfo; value: bool): PNode =
|
||||
result = newIntLit(g, info, ord value)
|
||||
result.typ = getSysType(g, info, tyBool)
|
||||
result.typ() = getSysType(g, info, tyBool)
|
||||
|
||||
proc getCycleParam(c: TLiftCtx): PNode =
|
||||
assert c.kind in {attachedAsgn, attachedDup}
|
||||
@@ -390,10 +380,6 @@ proc requiresDestructor(c: TLiftCtx; t: PType): bool {.inline.} =
|
||||
proc instantiateGeneric(c: var TLiftCtx; op: PSym; t, typeInst: PType): PSym =
|
||||
if c.c != nil and typeInst != nil:
|
||||
result = c.c.instTypeBoundOp(c.c, op, typeInst, c.info, attachedAsgn, 1)
|
||||
elif typeInst != nil and getAttachedOp(c.g, typeInst, c.kind) != nil:
|
||||
# c.c == nil in lambdalifting
|
||||
# hooks are already insted
|
||||
result = getAttachedOp(c.g, typeInst, c.kind)
|
||||
else:
|
||||
localError(c.g.config, c.info,
|
||||
"cannot generate destructor for generic type: " & typeToString(t))
|
||||
@@ -407,8 +393,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
|
||||
if op != nil and op != c.fn and
|
||||
(sfOverridden in op.flags or destructorOverridden):
|
||||
if sfError in op.flags:
|
||||
ensureMutable c.fn
|
||||
incl c.fn.flagsImpl, sfError
|
||||
incl c.fn.flags, sfError
|
||||
#else:
|
||||
# markUsed(c.g.config, c.info, op, c.g.usageSym)
|
||||
onUse(c.info, op)
|
||||
@@ -434,8 +419,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
|
||||
if op == nil:
|
||||
op = produceSym(c.g, c.c, t, c.kind, c.info, c.idgen)
|
||||
if sfError in op.flags:
|
||||
ensureMutable c.fn
|
||||
incl c.fn.flagsImpl, sfError
|
||||
incl c.fn.flags, sfError
|
||||
#else:
|
||||
# markUsed(c.g.config, c.info, op, c.g.usageSym)
|
||||
onUse(c.info, op)
|
||||
@@ -551,7 +535,7 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
|
||||
proc declareCounter(c: var TLiftCtx; body: PNode; first: BiggestInt): PNode =
|
||||
var temp = newSym(skTemp, getIdent(c.g.cache, lowerings.genPrefix), c.idgen, c.fn, c.info)
|
||||
temp.typ = getSysType(c.g, body.info, tyInt)
|
||||
incl(temp.flagsImpl, sfFromGeneric)
|
||||
incl(temp.flags, sfFromGeneric)
|
||||
|
||||
var v = newNodeI(nkVarSection, c.info)
|
||||
result = newSymNode(temp)
|
||||
@@ -561,29 +545,13 @@ proc declareCounter(c: var TLiftCtx; body: PNode; first: BiggestInt): PNode =
|
||||
proc declareTempOf(c: var TLiftCtx; body: PNode; value: PNode): PNode =
|
||||
var temp = newSym(skTemp, getIdent(c.g.cache, lowerings.genPrefix), c.idgen, c.fn, c.info)
|
||||
temp.typ = value.typ
|
||||
incl(temp.flagsImpl, sfFromGeneric)
|
||||
incl(temp.flags, sfFromGeneric)
|
||||
|
||||
var v = newNodeI(nkVarSection, c.info)
|
||||
result = newSymNode(temp)
|
||||
v.addVar(result, value)
|
||||
body.add v
|
||||
|
||||
proc considerInferDupFromCopy(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
|
||||
## For `=dup`, if no explicit hook exists, try to infer from `=copy` hook
|
||||
## to maintain backward compatibility. Returns true if inference was applied.
|
||||
if c.kind == attachedDup:
|
||||
var op2 = getAttachedOp(c.g, t, attachedAsgn)
|
||||
if op2 != nil and sfOverridden in op2.flags:
|
||||
#markUsed(c.g.config, c.info, op, c.g.usageSym)
|
||||
onUse(c.info, op2)
|
||||
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
body.add newHookCall(c, op2, x, y)
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc addIncStmt(c: var TLiftCtx; body, i: PNode) =
|
||||
let incCall = genBuiltin(c, mInc, "inc", i)
|
||||
incCall.add lowerings.newIntLit(c.g, c.info, 1)
|
||||
@@ -593,21 +561,19 @@ proc newSeqCall(c: var TLiftCtx; x, y: PNode): PNode =
|
||||
# don't call genAddr(c, x) here:
|
||||
result = genBuiltin(c, mNewSeq, "newSeq", x)
|
||||
let lenCall = genBuiltin(c, mLengthSeq, "len", y)
|
||||
lenCall.typ = getSysType(c.g, x.info, tyInt)
|
||||
lenCall.typ() = getSysType(c.g, x.info, tyInt)
|
||||
result.add lenCall
|
||||
|
||||
proc setLenStrCall(c: var TLiftCtx; x, y: PNode): PNode =
|
||||
let lenCall = genBuiltin(c, mLengthStr, "len", y)
|
||||
lenCall.typ = getSysType(c.g, x.info, tyInt)
|
||||
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; noinit = false): PNode =
|
||||
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode): PNode =
|
||||
let lenCall = genBuiltin(c, mLengthSeq, "len", y)
|
||||
lenCall.typ = getSysType(c.g, x.info, tyInt)
|
||||
let name = if noinit: "setLenUninit" else: "setLen"
|
||||
let magic = if noinit: mSetLengthSeqUninit else: mSetLengthSeq
|
||||
var op = getSysMagic(c.g, x.info, name, magic)
|
||||
lenCall.typ() = getSysType(c.g, x.info, tyInt)
|
||||
var op = getSysMagic(c.g, x.info, "setLen", mSetLengthSeq)
|
||||
op = instantiateGeneric(c, op, t, t)
|
||||
result = newTree(nkCall, newSymNode(op, x.info), x, lenCall)
|
||||
|
||||
@@ -629,38 +595,14 @@ proc checkSelfAssignment(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
newTreeIT(nkAddr, c.info, makePtrType(c.fn, x.typ, c.idgen), x),
|
||||
newTreeIT(nkAddr, c.info, makePtrType(c.fn, y.typ, c.idgen), y)
|
||||
)
|
||||
cond.typ = getSysType(c.g, c.info, tyBool)
|
||||
cond.typ() = getSysType(c.g, c.info, tyBool)
|
||||
body.add genIf(c, cond, newTreeI(nkReturnStmt, c.info, newNodeI(nkEmpty, c.info)))
|
||||
|
||||
proc genBulkCopySeq(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
## Generates a call to nimCopySeqPayload for bulk memcpy of seq data.
|
||||
let elemType = t.elementType
|
||||
let sym = magicsys.getCompilerProc(c.g, "nimCopySeqPayload")
|
||||
if sym == nil:
|
||||
localError(c.g.config, c.info, "system module needs: nimCopySeqPayload")
|
||||
return
|
||||
var sizeOf = genBuiltin(c, mSizeOf, "sizeof", newNodeIT(nkType, c.info, elemType))
|
||||
sizeOf.typ = getSysType(c.g, c.info, tyInt)
|
||||
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
|
||||
alignOf.typ = getSysType(c.g, c.info, tyInt)
|
||||
let call = newNodeI(nkCall, c.info)
|
||||
call.add newSymNode(sym)
|
||||
call.add newTreeIT(nkAddr, c.info, makePtrType(c.fn, x.typ, c.idgen), x)
|
||||
call.add newTreeIT(nkAddr, c.info, makePtrType(c.fn, y.typ, c.idgen), y)
|
||||
call.add sizeOf
|
||||
call.add alignOf
|
||||
call.typ = sym.typ.returnType
|
||||
body.add call
|
||||
|
||||
proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
case c.kind
|
||||
of attachedDup:
|
||||
let bulkCopy = supportsCopyMem(t.elementType)
|
||||
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
|
||||
if bulkCopy:
|
||||
genBulkCopySeq(c, t, body, x, y)
|
||||
else:
|
||||
forallElements(c, t, body, x, y)
|
||||
body.add setLenSeqCall(c, t, x, y)
|
||||
forallElements(c, t, body, x, y)
|
||||
of attachedAsgn, attachedDeepCopy:
|
||||
# we generate:
|
||||
# if x.p == y.p:
|
||||
@@ -669,14 +611,9 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
# var i = 0
|
||||
# while i < y.len: dest[i] = y[i]; inc(i)
|
||||
# This is usually more efficient than a destroy/create pair.
|
||||
# For trivially copyable types, use bulk copyMem instead of element loop.
|
||||
checkSelfAssignment(c, t, body, x, y)
|
||||
let bulkCopy = supportsCopyMem(t.elementType)
|
||||
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
|
||||
if bulkCopy:
|
||||
genBulkCopySeq(c, t, body, x, y)
|
||||
else:
|
||||
forallElements(c, t, body, x, y)
|
||||
body.add setLenSeqCall(c, t, x, y)
|
||||
forallElements(c, t, body, x, y)
|
||||
of attachedSink:
|
||||
let moveCall = genBuiltin(c, mMove, "move", x)
|
||||
moveCall.add y
|
||||
@@ -721,11 +658,6 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
doAssert t.asink != nil
|
||||
body.add newHookCall(c, t.asink, x, y)
|
||||
of attachedDestructor:
|
||||
when defined(icDbg):
|
||||
if t.destructor == nil:
|
||||
echo "MISSING destructor: ", typeToString(t), " kind=", t.kind,
|
||||
" itemId=", t.itemId, " uniqueId=", t.uniqueId, " state=", t.state,
|
||||
" owner=", (if t.owner != nil: t.owner.name.s else: "nil")
|
||||
doAssert t.destructor != nil
|
||||
body.add destructorCall(c, t.destructor, x)
|
||||
of attachedTrace:
|
||||
@@ -747,18 +679,11 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedAsgn, attachedDeepCopy, attachedDup:
|
||||
body.add callCodegenProc(c.g, "nimAsgnStrV2", c.info, genAddr(c, x), y)
|
||||
of attachedSink:
|
||||
if c.g.config.usesSso():
|
||||
# SmallString: destroy old dst, then bit-copy src (no rc increment — this is a move).
|
||||
# No .p aliasing check needed; rc-based destroy handles COW sharing correctly.
|
||||
doAssert t.destructor != nil
|
||||
body.add destructorCall(c, t.destructor, x)
|
||||
body.add newAsgnStmt(x, y)
|
||||
else:
|
||||
let moveCall = genBuiltin(c, mMove, "move", x)
|
||||
moveCall.add y
|
||||
doAssert t.destructor != nil
|
||||
moveCall.add destructorCall(c, t.destructor, x)
|
||||
body.add moveCall
|
||||
let moveCall = genBuiltin(c, mMove, "move", x)
|
||||
moveCall.add y
|
||||
doAssert t.destructor != nil
|
||||
moveCall.add destructorCall(c, t.destructor, x)
|
||||
body.add moveCall
|
||||
of attachedDestructor:
|
||||
body.add genBuiltin(c, mDestroy, "destroy", x)
|
||||
of attachedTrace:
|
||||
@@ -790,59 +715,28 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
dest[] = source
|
||||
decRef tmp
|
||||
|
||||
For YRC the write barrier is more complicated still and must be:
|
||||
|
||||
let tmp = dest
|
||||
# assignment must come first so that the collector sees the most-recent graph:
|
||||
atomic: dest[] = source
|
||||
# Then teach the cycle collector about the changes edge (these use locks, see yrc.nim):
|
||||
incRef source
|
||||
decRef tmp
|
||||
|
||||
This is implemented as a single runtime call (nimAsgnYrc / nimSinkYrc).
|
||||
]#
|
||||
var actions = newNodeI(nkStmtList, c.info)
|
||||
let elemType = t.elementType
|
||||
|
||||
createTypeBoundOps(c.g, c.c, elemType, c.info, c.idgen)
|
||||
let isCyclic = c.g.config.selectedGC == gcOrc and types.canFormAcycle(c.g, elemType)
|
||||
|
||||
# YRC uses dedicated runtime procs for the entire write barrier:
|
||||
if c.g.config.selectedGC == gcYrc:
|
||||
let desc =
|
||||
if isFinal(elemType):
|
||||
let ti = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
|
||||
ti.typ = getSysType(c.g, c.info, tyPointer)
|
||||
ti
|
||||
else:
|
||||
newNodeIT(nkNilLit, c.info, getSysType(c.g, c.info, tyPointer))
|
||||
case c.kind
|
||||
of attachedAsgn, attachedDup:
|
||||
body.add callCodegenProc(c.g, "nimAsgnYrc", c.info, genAddr(c, x), y, desc)
|
||||
return
|
||||
of attachedSink:
|
||||
body.add callCodegenProc(c.g, "nimSinkYrc", c.info, genAddr(c, x), y, desc)
|
||||
return
|
||||
else: discard # fall through for destructor, trace, wasMoved
|
||||
|
||||
let isCyclic = c.g.config.selectedGC in {gcOrc, gcYrc} and types.canFormAcycle(c.g, elemType)
|
||||
|
||||
let isInheritableAcyclicRef = c.g.config.selectedGC in {gcOrc, gcYrc} and
|
||||
let isInheritableAcyclicRef = c.g.config.selectedGC == gcOrc and
|
||||
(not isPureObject(elemType)) and
|
||||
tfAcyclic in skipTypes(elemType, abstractInst+{tyOwned}-{tyTypeDesc}).flags
|
||||
# dynamic Acyclic refs need to use dyn decRef
|
||||
|
||||
let useStatic = isFinal(elemType)
|
||||
|
||||
let tmp =
|
||||
if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}:
|
||||
declareTempOf(c, body, x)
|
||||
else:
|
||||
x
|
||||
|
||||
if useStatic:
|
||||
if isFinal(elemType):
|
||||
addDestructorCall(c, elemType, actions, genDeref(tmp, nkDerefExpr))
|
||||
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
|
||||
alignOf.typ = getSysType(c.g, c.info, tyInt)
|
||||
alignOf.typ() = getSysType(c.g, c.info, tyInt)
|
||||
actions.add callCodegenProc(c.g, "nimRawDispose", c.info, tmp, alignOf)
|
||||
else:
|
||||
addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(tmp, nkDerefExpr))
|
||||
@@ -850,9 +744,9 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
|
||||
var cond: PNode
|
||||
if isCyclic:
|
||||
if useStatic:
|
||||
if isFinal(elemType):
|
||||
let typInfo = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
|
||||
typInfo.typ = getSysType(c.g, c.info, tyPointer)
|
||||
typInfo.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicStatic", c.info, tmp, typInfo)
|
||||
else:
|
||||
cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicDyn", c.info, tmp)
|
||||
@@ -860,7 +754,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
cond = callCodegenProc(c.g, "nimDecRefIsLastDyn", c.info, x)
|
||||
else:
|
||||
cond = callCodegenProc(c.g, "nimDecRefIsLast", c.info, x)
|
||||
cond.typ = getSysType(c.g, x.info, tyBool)
|
||||
cond.typ() = getSysType(c.g, x.info, tyBool)
|
||||
|
||||
case c.kind
|
||||
of attachedSink:
|
||||
@@ -885,9 +779,9 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedDeepCopy: assert(false, "cannot happen")
|
||||
of attachedTrace:
|
||||
if isCyclic:
|
||||
if useStatic:
|
||||
if isFinal(elemType):
|
||||
let typInfo = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
|
||||
typInfo.typ = getSysType(c.g, c.info, tyPointer)
|
||||
typInfo.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
body.add callCodegenProc(c.g, "nimTraceRef", c.info, genAddrOf(x, c.idgen), typInfo, y)
|
||||
else:
|
||||
# If the ref is polymorphic we have to account for this
|
||||
@@ -908,28 +802,9 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
## Closures are really like refs except they always use a virtual destructor
|
||||
## and we need to do the refcounting only on the ref field which we call 'xenv':
|
||||
let xenv = genBuiltin(c, mAccessEnv, "accessEnv", x)
|
||||
xenv.typ = getSysType(c.g, c.info, tyPointer)
|
||||
xenv.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
|
||||
# Closures are (fnPtr, env) pairs. nimAsgnYrc/nimSinkYrc handle the env pointer
|
||||
# (atomic store + buffered inc/dec). We also need newAsgnStmt to copy the fnPtr.
|
||||
if c.g.config.selectedGC == gcYrc:
|
||||
let nilDesc = newNodeIT(nkNilLit, c.info, getSysType(c.g, c.info, tyPointer))
|
||||
let yenv = genBuiltin(c, mAccessEnv, "accessEnv", y)
|
||||
yenv.typ = getSysType(c.g, c.info, tyPointer)
|
||||
case c.kind
|
||||
of attachedAsgn, attachedDup:
|
||||
# nimAsgnYrc: save old env, atomic store new env, inc new env, dec old env
|
||||
body.add callCodegenProc(c.g, "nimAsgnYrc", c.info, genAddr(c, xenv), yenv, nilDesc)
|
||||
# Raw struct copy to also update the function pointer (env write is redundant but benign)
|
||||
body.add newAsgnStmt(x, y)
|
||||
return
|
||||
of attachedSink:
|
||||
body.add callCodegenProc(c.g, "nimSinkYrc", c.info, genAddr(c, xenv), yenv, nilDesc)
|
||||
body.add newAsgnStmt(x, y)
|
||||
return
|
||||
else: discard # fall through for destructor, trace, wasMoved
|
||||
|
||||
let isCyclic = c.g.config.selectedGC in {gcOrc, gcYrc}
|
||||
let isCyclic = c.g.config.selectedGC == gcOrc
|
||||
let tmp =
|
||||
if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}:
|
||||
declareTempOf(c, body, xenv)
|
||||
@@ -943,7 +818,7 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
if isCyclic: "nimDecRefIsLastCyclicDyn"
|
||||
else: "nimDecRefIsLast"
|
||||
let cond = callCodegenProc(c.g, decRefProc, c.info, tmp)
|
||||
cond.typ = getSysType(c.g, x.info, tyBool)
|
||||
cond.typ() = getSysType(c.g, x.info, tyBool)
|
||||
|
||||
case c.kind
|
||||
of attachedSink:
|
||||
@@ -955,18 +830,19 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
body.add newAsgnStmt(x, y)
|
||||
of attachedAsgn:
|
||||
let yenv = genBuiltin(c, mAccessEnv, "accessEnv", y)
|
||||
yenv.typ = getSysType(c.g, c.info, tyPointer)
|
||||
yenv.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
if isCyclic:
|
||||
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRefCyclic", c.info, yenv, getCycleParam(c)))
|
||||
body.add newAsgnStmt(x, y)
|
||||
body.add genIf(c, cond, actions)
|
||||
else:
|
||||
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv))
|
||||
|
||||
body.add genIf(c, cond, actions)
|
||||
body.add newAsgnStmt(x, y)
|
||||
of attachedDup:
|
||||
let yenv = genBuiltin(c, mAccessEnv, "accessEnv", y)
|
||||
yenv.typ = getSysType(c.g, c.info, tyPointer)
|
||||
yenv.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
if isCyclic:
|
||||
body.add newAsgnStmt(x, y)
|
||||
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRefCyclic", c.info, yenv, getCycleParam(c)))
|
||||
@@ -1018,7 +894,7 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
if isFinal(elemType):
|
||||
addDestructorCall(c, elemType, actions, genDeref(x, nkDerefExpr))
|
||||
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
|
||||
alignOf.typ = getSysType(c.g, c.info, tyInt)
|
||||
alignOf.typ() = getSysType(c.g, c.info, tyInt)
|
||||
actions.add callCodegenProc(c.g, "nimRawDispose", c.info, x, alignOf)
|
||||
else:
|
||||
addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(x, nkDerefExpr))
|
||||
@@ -1041,14 +917,14 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
# a big problem is that we don't know the environment's type here, so we
|
||||
# have to go through some indirection; we delegate this to the codegen:
|
||||
let call = newNodeI(nkCall, c.info, 2)
|
||||
call.typ = t
|
||||
call.typ() = t
|
||||
call[0] = newSymNode(createMagic(c.g, c.idgen, "deepCopy", mDeepCopy))
|
||||
call[1] = y
|
||||
body.add newAsgnStmt(x, call)
|
||||
elif (optOwnedRefs in c.g.config.globalOptions and
|
||||
optRefCheck in c.g.config.options) or c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
optRefCheck in c.g.config.options) or c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
|
||||
let xx = genBuiltin(c, mAccessEnv, "accessEnv", x)
|
||||
xx.typ = getSysType(c.g, c.info, tyPointer)
|
||||
xx.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
case c.kind
|
||||
of attachedSink:
|
||||
# we 'nil' y out afterwards so we *need* to take over its reference
|
||||
@@ -1057,13 +933,13 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
body.add newAsgnStmt(x, y)
|
||||
of attachedAsgn:
|
||||
let yy = genBuiltin(c, mAccessEnv, "accessEnv", y)
|
||||
yy.typ = getSysType(c.g, c.info, tyPointer)
|
||||
yy.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
body.add genIf(c, yy, callCodegenProc(c.g, "nimIncRef", c.info, yy))
|
||||
body.add genIf(c, xx, callCodegenProc(c.g, "nimDecWeakRef", c.info, xx))
|
||||
body.add newAsgnStmt(x, y)
|
||||
of attachedDup:
|
||||
let yy = genBuiltin(c, mAccessEnv, "accessEnv", y)
|
||||
yy.typ = getSysType(c.g, c.info, tyPointer)
|
||||
yy.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
body.add newAsgnStmt(x, y)
|
||||
body.add genIf(c, yy, callCodegenProc(c.g, "nimIncRef", c.info, yy))
|
||||
of attachedDestructor:
|
||||
@@ -1078,7 +954,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
|
||||
proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
let xx = genBuiltin(c, mAccessEnv, "accessEnv", x)
|
||||
xx.typ = getSysType(c.g, c.info, tyPointer)
|
||||
xx.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
var actions = newNodeI(nkStmtList, c.info)
|
||||
#discard addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(xx))
|
||||
actions.add callCodegenProc(c.g, "nimDestroyAndDispose", c.info, xx)
|
||||
@@ -1097,20 +973,11 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
case t.kind
|
||||
of tyNone, tyEmpty, tyVoid: discard
|
||||
of tyUncheckedArray:
|
||||
# An UncheckedArray has no known length, so it cannot be copied, moved or
|
||||
# destroyed as a value: it only ever lives behind a pointer and its bytes
|
||||
# are managed manually (element ops for seqs/strings go through the
|
||||
# seq/string hooks, which know the length). Emitting `x = y` for it (as the
|
||||
# pointer-like group below does) produces an assignment of an unsized array,
|
||||
# which the C backend cannot lower (genAssignment: tyUncheckedArray). So all
|
||||
# value hooks for it are no-ops.
|
||||
discard
|
||||
of tyPointer, tySet, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCstring,
|
||||
tyPtr, tyVar, tyLent:
|
||||
tyPtr, tyUncheckedArray, tyVar, tyLent:
|
||||
defaultOp(c, t, body, x, y)
|
||||
of tyRef:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
atomicRefOp(c, t, body, x, y)
|
||||
elif (optOwnedRefs in c.g.config.globalOptions and
|
||||
optRefCheck in c.g.config.options):
|
||||
@@ -1119,7 +986,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
defaultOp(c, t, body, x, y)
|
||||
of tyProc:
|
||||
if t.callConv == ccClosure:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
atomicClosureOp(c, t, body, x, y)
|
||||
else:
|
||||
closureOp(c, t, body, x, y)
|
||||
@@ -1177,15 +1044,20 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
else:
|
||||
fillBodyObjT(c, t, body, x, y)
|
||||
elif tfUnion in t.flags: # bug #25236
|
||||
defaultOp(c, t, body, x, y)
|
||||
else:
|
||||
if not considerInferDupFromCopy(c, t, body, x, y):
|
||||
if c.kind == attachedDup:
|
||||
var op2 = getAttachedOp(c.g, t, attachedAsgn)
|
||||
if op2 != nil and sfOverridden in op2.flags:
|
||||
#markUsed(c.g.config, c.info, op, c.g.usageSym)
|
||||
onUse(c.info, op2)
|
||||
body.add newHookCall(c, t.assignment, x, y)
|
||||
else:
|
||||
fillBodyObjT(c, t, body, x, y)
|
||||
else:
|
||||
fillBodyObjT(c, t, body, x, y)
|
||||
of tyDistinct:
|
||||
if not considerUserDefinedOp(c, t, body, x, y):
|
||||
if not considerInferDupFromCopy(c, t, body, x, y):
|
||||
fillBody(c, t.elementType, body, x, y)
|
||||
fillBody(c, t.elementType, body, x, y)
|
||||
of tyTuple:
|
||||
fillBodyTup(c, t, body, x, y)
|
||||
of tyVarargs, tyOpenArray:
|
||||
@@ -1232,7 +1104,7 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
|
||||
|
||||
result.typ.addParam src
|
||||
|
||||
if g.config.selectedGC in {gcOrc, gcYrc} and
|
||||
if g.config.selectedGC == gcOrc and
|
||||
cyclicType(g, typ.skipTypes(abstractInst)):
|
||||
let cycleParam = newSym(skParam, getIdent(g.cache, "cyclic"),
|
||||
idgen, result, info)
|
||||
@@ -1246,8 +1118,8 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
|
||||
n[bodyPos] = newNodeI(nkStmtList, info)
|
||||
n[resultPos] = newSymNode(res)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, {sfFromGeneric, sfGeneratedOp}
|
||||
setHookDisamb(g, result, AttachedOpToStr[kind], typ)
|
||||
incl result.flags, sfFromGeneric
|
||||
incl result.flags, sfGeneratedOp
|
||||
|
||||
proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp;
|
||||
info: TLineInfo; idgen: IdGenerator; isDiscriminant = false): PSym =
|
||||
@@ -1260,7 +1132,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
|
||||
let src = newSym(skParam, getIdent(g.cache, if kind == attachedTrace: "env" else: "src"),
|
||||
idgen, result, info)
|
||||
|
||||
if kind == attachedDestructor and g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc} and
|
||||
if kind == attachedDestructor and g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
||||
((g.config.isDefined("nimPreviewNonVarDestructor") and not isDiscriminant) or (typ.kind in {tyRef, tyString, tySequence})):
|
||||
dest.typ = typ
|
||||
else:
|
||||
@@ -1276,7 +1148,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
|
||||
if kind notin {attachedDestructor, attachedWasMoved}:
|
||||
result.typ.addParam src
|
||||
|
||||
if kind == attachedAsgn and g.config.selectedGC in {gcOrc, gcYrc} and
|
||||
if kind == attachedAsgn and g.config.selectedGC == gcOrc and
|
||||
cyclicType(g, typ.skipTypes(abstractInst)):
|
||||
let cycleParam = newSym(skParam, getIdent(g.cache, "cyclic"),
|
||||
idgen, result, info)
|
||||
@@ -1289,37 +1161,23 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
|
||||
n[paramsPos] = result.typ.n
|
||||
n[bodyPos] = newNodeI(nkStmtList, info)
|
||||
result.ast = n
|
||||
incl result.flagsImpl, sfFromGeneric
|
||||
incl result.flagsImpl, sfGeneratedOp
|
||||
incl result.flags, sfFromGeneric
|
||||
incl result.flags, sfGeneratedOp
|
||||
if kind == attachedWasMoved:
|
||||
incl result.flagsImpl, sfNoSideEffect
|
||||
incl result.typ, tfNoSideEffect
|
||||
if not isDiscriminant:
|
||||
# discriminant destructors derive their body from the enclosing object
|
||||
# AND the selected field; their key is set at the call site
|
||||
setHookDisamb(g, result, AttachedOpToStr[kind], typ)
|
||||
incl result.flags, sfNoSideEffect
|
||||
incl result.typ.flags, tfNoSideEffect
|
||||
|
||||
proc genTypeFieldCopy(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
let xx = genBuiltin(c, mAccessTypeField, "accessTypeField", x)
|
||||
let yy = genBuiltin(c, mAccessTypeField, "accessTypeField", y)
|
||||
xx.typ = getSysType(c.g, c.info, tyPointer)
|
||||
yy.typ = xx.typ
|
||||
xx.typ() = getSysType(c.g, c.info, tyPointer)
|
||||
yy.typ() = xx.typ
|
||||
body.add newAsgnStmt(xx, yy)
|
||||
|
||||
proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
|
||||
info: TLineInfo; idgen: IdGenerator): PSym =
|
||||
if typ.kind == tyDistinct:
|
||||
# For =dup, if the distinct type has a user-defined =copy, don't delegate
|
||||
# to the base type. Instead fall through to the normal produceSym logic
|
||||
# so that fillBody -> considerInferDupFromCopy can synthesize =dup from =copy.
|
||||
if kind == attachedDup:
|
||||
let copyOp = getAttachedOp(g, typ, attachedAsgn)
|
||||
if copyOp != nil and sfOverridden in copyOp.flags:
|
||||
discard "fall through to normal produceSym logic"
|
||||
else:
|
||||
return produceSymDistinctType(g, c, typ, kind, info, idgen)
|
||||
else:
|
||||
return produceSymDistinctType(g, c, typ, kind, info, idgen)
|
||||
return produceSymDistinctType(g, c, typ, kind, info, idgen)
|
||||
|
||||
result = getAttachedOp(g, typ, kind)
|
||||
if result == nil:
|
||||
@@ -1340,48 +1198,35 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
|
||||
|
||||
if kind == attachedSink and destructorOverridden(g, typ):
|
||||
## compiler can use a combination of `=destroy` and memCopy for sink op
|
||||
ensureMutable dest
|
||||
dest.flagsImpl.incl sfCursor
|
||||
dest.flags.incl sfCursor
|
||||
let op = getAttachedOp(g, typ, attachedDestructor)
|
||||
result.ast[bodyPos].add newOpCall(a, op, if op.typ.firstParamType.kind == tyVar: d[0] else: d)
|
||||
result.ast[bodyPos].add newAsgnStmt(d, src)
|
||||
else:
|
||||
var tk: TTypeKind
|
||||
var skipped: PType = nil
|
||||
if g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcHooks, gcAtomicArc}:
|
||||
skipped = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink})
|
||||
tk = skipped.kind
|
||||
if g.config.selectedGC in {gcArc, gcOrc, gcHooks, gcAtomicArc}:
|
||||
tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind
|
||||
else:
|
||||
tk = tyNone # no special casing for strings and seqs
|
||||
case tk
|
||||
of tySequence:
|
||||
let needsYrcLock = g.config.selectedGC == gcYrc and
|
||||
kind in {attachedDestructor, attachedSink, attachedAsgn, attachedDeepCopy, attachedDup} and
|
||||
types.canFormAcycle(g, skipped.elementType)
|
||||
# YRC: topology-changing seq ops must hold the mutator (read) lock
|
||||
if needsYrcLock:
|
||||
result.ast[bodyPos].add callCodegenProc(g, "acquireMutatorLock", info)
|
||||
fillSeqOp(a, typ, result.ast[bodyPos], d, src)
|
||||
if needsYrcLock:
|
||||
result.ast[bodyPos].add callCodegenProc(g, "releaseMutatorLock", info)
|
||||
of tyString:
|
||||
fillStrOp(a, typ, result.ast[bodyPos], d, src)
|
||||
else:
|
||||
fillBody(a, typ, result.ast[bodyPos], d, src)
|
||||
if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not isObjLackingTypeField(skipped):
|
||||
if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not isObjLackingTypeField(typ):
|
||||
# bug #19205: Do not forget to also copy the hidden type field:
|
||||
genTypeFieldCopy(a, typ, result.ast[bodyPos], d, src)
|
||||
|
||||
if not a.canRaise:
|
||||
ensureMutable result
|
||||
incl result.flagsImpl, sfNeverRaises
|
||||
incl result.flags, sfNeverRaises
|
||||
result.ast[pragmasPos] = newNodeI(nkPragma, info)
|
||||
result.ast[pragmasPos].add newTree(nkExprColonExpr,
|
||||
newIdentNode(g.cache.getIdent("raises"), info), newNodeI(nkBracket, info))
|
||||
|
||||
if kind == attachedDestructor:
|
||||
ensureMutable result
|
||||
incl result.optionsImpl, optQuirky
|
||||
incl result.options, optQuirky
|
||||
completePartialOp(g, idgen.module, typ, kind, result)
|
||||
|
||||
|
||||
@@ -1390,7 +1235,6 @@ proc produceDestructorForDiscriminator*(g: ModuleGraph; typ: PType; field: PSym,
|
||||
assert(typ.skipTypes({tyAlias, tyGenericInst}).kind == tyObject)
|
||||
# discrimantor assignments needs pointers to destroy fields; alas, we cannot use non-var destructor here
|
||||
result = symPrototype(g, field.typ, typ.owner, attachedDestructor, info, idgen, isDiscriminant = true)
|
||||
setHookDisamb(g, result, "=destroy¦" & field.name.s & "¦" & $field.position, typ)
|
||||
var a = TLiftCtx(info: info, g: g, kind: attachedDestructor, asgnForType: typ, idgen: idgen,
|
||||
fn: result)
|
||||
a.asgnForType = typ
|
||||
@@ -1407,9 +1251,7 @@ proc produceDestructorForDiscriminator*(g: ModuleGraph; typ: PType; field: PSym,
|
||||
result.ast[bodyPos].add v
|
||||
let placeHolder = newNodeIT(nkSym, info, getSysType(g, info, tyPointer))
|
||||
fillBody(a, typ, result.ast[bodyPos], d, placeHolder)
|
||||
if not a.canRaise:
|
||||
ensureMutable result
|
||||
incl result.flagsImpl, sfNeverRaises
|
||||
if not a.canRaise: incl result.flags, sfNeverRaises
|
||||
|
||||
|
||||
template liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) =
|
||||
@@ -1453,13 +1295,11 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
|
||||
## to ensure we lift assignment, destructors and moves properly.
|
||||
## The later 'injectdestructors' pass depends on it.
|
||||
if orig == nil or {tfCheckedForDestructor, tfHasMeta} * orig.flags != {}: return
|
||||
# IC: review this solution again later
|
||||
incl orig.flagsImpl, tfCheckedForDestructor
|
||||
incl orig.flags, tfCheckedForDestructor
|
||||
# for user defined generic destructors:
|
||||
let origRoot = genericRoot(orig)
|
||||
if origRoot != nil:
|
||||
# IC: review this solution again later
|
||||
incl origRoot.flagsImpl, tfGenericHasDestructor
|
||||
incl origRoot.flags, tfGenericHasDestructor
|
||||
|
||||
let skipped = orig.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||
if isEmptyContainer(skipped) or skipped.kind == tyStatic: return
|
||||
@@ -1479,13 +1319,13 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
|
||||
|
||||
# we do not generate '=trace' procs if we
|
||||
# have the cycle detection disabled, saves code size.
|
||||
let lastAttached = if g.config.selectedGC in {gcOrc, gcYrc}: attachedTrace
|
||||
let lastAttached = if g.config.selectedGC == gcOrc: attachedTrace
|
||||
else: attachedSink
|
||||
|
||||
# bug #15122: We need to produce all prototypes before entering the
|
||||
# mind boggling recursion. Hacks like these imply we should rewrite
|
||||
# this module.
|
||||
var generics = default(array[attachedWasMoved..attachedTrace, bool])
|
||||
var generics: array[attachedWasMoved..attachedTrace, bool] = default(array[attachedWasMoved..attachedTrace, bool])
|
||||
for k in attachedWasMoved..lastAttached:
|
||||
generics[k] = getAttachedOp(g, canon, k) != nil
|
||||
if not generics[k]:
|
||||
@@ -1504,6 +1344,5 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
|
||||
if not isTrivial(getAttachedOp(g, orig, attachedDestructor)):
|
||||
#or not isTrivial(orig.assignment) or
|
||||
# not isTrivial(orig.sink):
|
||||
# IC: review this solution again later
|
||||
orig.flagsImpl.incl tfHasAsgn
|
||||
orig.flags.incl tfHasAsgn
|
||||
# ^ XXX Breaks IC!
|
||||
|
||||
@@ -32,12 +32,12 @@ proc interestingVar(s: PSym): bool {.inline.} =
|
||||
proc lookupOrAdd(c: var Ctx; s: PSym; info: TLineInfo): PNode =
|
||||
let field = addUniqueField(c.objType, s, c.cache, c.idgen)
|
||||
var deref = newNodeI(nkHiddenDeref, info)
|
||||
deref.typ = c.objType
|
||||
deref.typ() = c.objType
|
||||
deref.add(newSymNode(c.partialParam, info))
|
||||
result = newNodeI(nkDotExpr, info)
|
||||
result.add(deref)
|
||||
result.add(newSymNode(field))
|
||||
result.typ = field.typ
|
||||
result.typ() = field.typ
|
||||
|
||||
proc liftLocals(n: PNode; i: int; c: var Ctx) =
|
||||
let it = n[i]
|
||||
|
||||
@@ -93,14 +93,10 @@ type
|
||||
warnBareExcept = "BareExcept",
|
||||
warnImplicitDefaultValue = "ImplicitDefaultValue",
|
||||
warnIgnoredSymbolInjection = "IgnoredSymbolInjection",
|
||||
warnStdPrefix = "StdPrefix",
|
||||
warnUnknownNotes = "UnknownNotes",
|
||||
warnLongLiterals = "LongLiterals",
|
||||
warnStdPrefix = "StdPrefix"
|
||||
warnUnknownNotes = "UnknownNotes"
|
||||
warnUser = "User",
|
||||
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
|
||||
warnImplicitRangeConversion = "ImplicitRangeConversion",
|
||||
warnSystemRangeConversion = "SystemRangeConversion",
|
||||
warnInvalidCmpOp = "InvalidCmpOp",
|
||||
# hints
|
||||
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
||||
hintCC = "CC",
|
||||
@@ -206,12 +202,8 @@ const
|
||||
warnIgnoredSymbolInjection: "$1",
|
||||
warnStdPrefix: "$1 needs the 'std' prefix",
|
||||
warnUnknownNotes: "$1",
|
||||
warnLongLiterals: "$1",
|
||||
warnUser: "$1",
|
||||
warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable",
|
||||
warnImplicitRangeConversion: "implicit range conversion $1",
|
||||
warnSystemRangeConversion: "implicit range conversion $1",
|
||||
warnInvalidCmpOp: "$1",
|
||||
hintSuccess: "operation successful: $#",
|
||||
# keep in sync with `testament.isSuccess`
|
||||
hintSuccessX: "$build\n$loc lines; ${sec}s; $mem; proj: $project; out: $output",
|
||||
@@ -266,9 +258,9 @@ type
|
||||
|
||||
proc computeNotesVerbosity(): array[0..3, TNoteKinds] =
|
||||
result = default(array[0..3, TNoteKinds])
|
||||
result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix, warnSystemRangeConversion}
|
||||
result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix}
|
||||
result[2] = result[3] - {hintStackTrace, hintExtendedContext, hintDeclaredLoc, hintProcessingStmt}
|
||||
result[1] = result[2] - {warnImplicitRangeConversion, warnProveField, warnProveIndex,
|
||||
result[1] = result[2] - {warnProveField, warnProveIndex,
|
||||
warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd,
|
||||
hintSource, hintGlobalVar, hintGCStats, hintMsgOrigin, hintPerformance}
|
||||
result[0] = result[1] - {hintSuccessX, hintSuccess, hintConf,
|
||||
@@ -281,10 +273,6 @@ const
|
||||
errFloatToString* = "cannot convert '$1' to '$2'"
|
||||
|
||||
type
|
||||
FileInfoKind* = enum
|
||||
fikSource, ## A real source file path
|
||||
fikNifModule ## A NIF module suffix (not a real path)
|
||||
|
||||
TFileInfo* = object
|
||||
fullPath*: AbsoluteFile # This is a canonical full filesystem path
|
||||
projPath*: RelativeFile # This is relative to the project's root
|
||||
@@ -303,7 +291,6 @@ type
|
||||
# for 'nimsuggest'
|
||||
hash*: string # the checksum of the file
|
||||
dirty*: bool # for 'nimpretty' like tooling
|
||||
kind*: FileInfoKind # distinguishes real files from NIF suffixes
|
||||
when defined(nimpretty):
|
||||
fullContent*: string
|
||||
FileIndex* = distinct int32
|
||||
|
||||
@@ -95,7 +95,7 @@ proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
|
||||
template styleCheckDef*(ctx: PContext; info: TLineInfo; sym: PSym; k: TSymKind) =
|
||||
## Check symbol definitions adhere to NEP1 style rules.
|
||||
if optStyleCheck in ctx.config.options and # ignore if styleChecks are off
|
||||
{optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # check only if hint/error/warning is enabled
|
||||
{optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # check only if hint/error is enabled
|
||||
hintName in ctx.config.notes and # ignore if name checks are not requested
|
||||
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)) and # ignore foreign packages
|
||||
optStyleUsages notin ctx.config.globalOptions and # ignore if requested to only check name usage
|
||||
@@ -136,7 +136,7 @@ proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) =
|
||||
|
||||
template styleCheckUse*(ctx: PContext; info: TLineInfo; sym: PSym) =
|
||||
## Check symbol uses match their definition's style.
|
||||
if {optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
|
||||
if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
|
||||
hintName in ctx.config.notes and # ignore if name checks are not requested
|
||||
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)) and # ignore foreign packages
|
||||
sym.kind != skTemp and # ignore temporary variables created by the compiler
|
||||
@@ -152,7 +152,7 @@ proc checkPragmaUseImpl(conf: ConfigRef; info: TLineInfo; w: TSpecialWord; pragm
|
||||
template checkPragmaUse*(ctx: PContext; info: TLineInfo; w: TSpecialWord; pragmaName: string, sym: PSym) =
|
||||
## Check builtin pragma uses match their definition's style.
|
||||
## Note: This only applies to builtin pragmas, not user pragmas.
|
||||
if {optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
|
||||
if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
|
||||
hintName in ctx.config.notes and # ignore if name checks are not requested
|
||||
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)): # ignore foreign packages
|
||||
checkPragmaUseImpl(ctx.config, info, w, pragmaName)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -311,7 +311,7 @@ proc errorSym*(c: PContext, ident: PIdent, info: TLineInfo): PSym =
|
||||
## creates an error symbol to avoid cascading errors (for IDE support)
|
||||
result = newSym(skError, ident, c.idgen, getCurrOwner(c), info, {})
|
||||
result.typ = errorType(c)
|
||||
incl(result.flagsImpl, sfDiscardable)
|
||||
incl(result.flags, sfDiscardable)
|
||||
# pretend it's from the top level scope to prevent cascading errors:
|
||||
if c.config.cmd != cmdInteractive and c.compilesContextId == 0:
|
||||
c.moduleScope.addSym(result)
|
||||
@@ -378,9 +378,6 @@ proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string;
|
||||
conflictsWith: TLineInfo, note = errGenerated) =
|
||||
## Emit a redefinition error if in non-interactive mode
|
||||
if c.config.cmd != cmdInteractive:
|
||||
when defined(icDbgRefc):
|
||||
echo "[icRedef] ", s
|
||||
echo getStackTrace()
|
||||
localError(c.config, info, note,
|
||||
"redefinition of '$1'; previous declaration here: $2" %
|
||||
[s, c.config $ conflictsWith])
|
||||
@@ -415,6 +412,8 @@ proc addDecl*(c: PContext, sym: PSym) {.inline.} =
|
||||
proc addPrelimDecl*(c: PContext, sym: PSym) =
|
||||
discard c.currentScope.addUniqueSym(sym)
|
||||
|
||||
from ic / ic import addHidden
|
||||
|
||||
proc addInterfaceDeclAux(c: PContext, sym: PSym) =
|
||||
## adds symbol to the module for either private or public access.
|
||||
if sfExported in sym.flags:
|
||||
@@ -423,6 +422,8 @@ proc addInterfaceDeclAux(c: PContext, sym: PSym) =
|
||||
else: internalError(c.config, sym.info, "addInterfaceDeclAux")
|
||||
elif sym.kind in ExportableSymKinds and c.module != nil and isTopLevelInsideDeclaration(c, sym):
|
||||
strTableAdd(semtabAll(c.graph, c.module), sym)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addHidden(c.encoder, c.packedRepr, sym)
|
||||
|
||||
proc addInterfaceDeclAt*(c: PContext, scope: PScope, sym: PSym) =
|
||||
## adds a symbol on the scope and the interface if appropriate
|
||||
@@ -462,15 +463,6 @@ proc openShadowScope*(c: PContext) =
|
||||
symbols: initStrTable(),
|
||||
depthLevel: c.scopeDepth)
|
||||
|
||||
proc rememberShadowDefs*(c: PContext) =
|
||||
## bug #25693: a template/macro operand's local definitions are sem-checked in
|
||||
## a shadow scope that is then discarded. Record those definitions so that a
|
||||
## later re-emission (e.g. a captured `typed` fragment expanded more than once)
|
||||
## can be detected as a redefinition rather than silently miscompiled.
|
||||
for s in c.currentScope.symbols:
|
||||
if s.kind in {skVar, skLet, skForVar} and {sfGenSym, sfWasGenSym} * s.flags == {}:
|
||||
c.shadowDiscardedDefs.incl s.id
|
||||
|
||||
proc closeShadowScope*(c: PContext) =
|
||||
## closes the shadow scope, but doesn't merge any of the symbols
|
||||
## Does not check for unused symbols or missing forward decls since a macro
|
||||
|
||||
@@ -82,7 +82,7 @@ proc lowerTupleUnpacking*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: P
|
||||
var temp = newSym(skTemp, getIdent(g.cache, genPrefix), idgen,
|
||||
owner, value.info, g.config.options)
|
||||
temp.typ = skipTypes(value.typ, abstractInst)
|
||||
incl(temp.flagsImpl, sfFromGeneric)
|
||||
incl(temp.flags, sfFromGeneric)
|
||||
tempAsNode = newSymNode(temp)
|
||||
|
||||
var v = newNodeI(nkVarSection, value.info)
|
||||
@@ -103,7 +103,7 @@ proc evalOnce*(g: ModuleGraph; value: PNode; idgen: IdGenerator; owner: PSym): P
|
||||
var temp = newSym(skTemp, getIdent(g.cache, genPrefix), idgen,
|
||||
owner, value.info, g.config.options)
|
||||
temp.typ = skipTypes(value.typ, abstractInst)
|
||||
incl(temp.flagsImpl, sfFromGeneric)
|
||||
incl(temp.flags, sfFromGeneric)
|
||||
|
||||
var v = newNodeI(nkLetSection, value.info)
|
||||
let tempAsNode = newSymNode(temp)
|
||||
@@ -127,8 +127,8 @@ proc lowerSwap*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PNod
|
||||
# note: cannot use 'skTemp' here cause we really need the copy for the VM :-(
|
||||
var temp = newSym(skVar, getIdent(g.cache, genPrefix), idgen, owner, n.info, owner.options)
|
||||
temp.typ = n[1].typ
|
||||
incl(temp.flagsImpl, sfFromGeneric)
|
||||
incl(temp.flagsImpl, sfGenSym)
|
||||
incl(temp.flags, sfFromGeneric)
|
||||
incl(temp.flags, sfGenSym)
|
||||
|
||||
var v = newNodeI(nkVarSection, n.info)
|
||||
let tempAsNode = newSymNode(temp)
|
||||
@@ -147,13 +147,13 @@ proc createObj*(g: ModuleGraph; idgen: IdGenerator; owner: PSym, info: TLineInfo
|
||||
result = newType(tyObject, idgen, owner)
|
||||
if final:
|
||||
rawAddSon(result, nil)
|
||||
incl result, tfFinal
|
||||
incl result.flags, tfFinal
|
||||
else:
|
||||
rawAddSon(result, getCompilerProc(g, "RootObj").typ)
|
||||
result.n = newNodeI(nkRecList, info)
|
||||
let s = newSym(skType, getIdent(g.cache, "Env_" & toFilename(g.config, info) & "_" & $owner.name.s),
|
||||
idgen, owner, info, owner.options)
|
||||
incl s.flagsImpl, sfAnon
|
||||
incl s.flags, sfAnon
|
||||
s.typ = result
|
||||
result.sym = s
|
||||
|
||||
@@ -174,12 +174,12 @@ proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
|
||||
# returns a[].field as a node
|
||||
assert field.kind == skField
|
||||
var deref = newNodeI(nkHiddenDeref, info)
|
||||
deref.typ = a.typ.skipTypes(abstractInst)[0]
|
||||
deref.typ() = a.typ.skipTypes(abstractInst)[0]
|
||||
deref.add a
|
||||
result = newNodeI(nkDotExpr, info)
|
||||
result.add deref
|
||||
result.add newSymNode(field)
|
||||
result.typ = field.typ
|
||||
result.typ() = field.typ
|
||||
|
||||
proc rawDirectAccess*(obj, field: PSym): PNode =
|
||||
# returns a.field as a node
|
||||
@@ -187,7 +187,7 @@ proc rawDirectAccess*(obj, field: PSym): PNode =
|
||||
result = newNodeI(nkDotExpr, field.info)
|
||||
result.add newSymNode(obj)
|
||||
result.add newSymNode(field)
|
||||
result.typ = field.typ
|
||||
result.typ() = field.typ
|
||||
|
||||
proc lookupInRecord(n: PNode, id: ItemId): PSym =
|
||||
result = nil
|
||||
@@ -207,70 +207,15 @@ proc lookupInRecord(n: PNode, id: ItemId): PSym =
|
||||
if result != nil: return
|
||||
else: discard
|
||||
of nkSym:
|
||||
if matchesDerivedFieldId(n.sym.itemId, id): result = n.sym
|
||||
else: discard
|
||||
|
||||
proc lookupCapturedField(n: PNode, s: PSym): PSym =
|
||||
## Find an env field that `addField` would have produced for the captured
|
||||
## local `s`. Used as a fallback when the derived-itemId match fails because
|
||||
## `s` is a macro-generated gensym whose process-local id diverges from the
|
||||
## loaded env field's (see `addField`). `addField` always names a field
|
||||
## `s.name & $field.position`, so that pair uniquely identifies the field for a
|
||||
## local of this name without relying on the (unstable) item id.
|
||||
result = nil
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
for i in 0..<n.len:
|
||||
result = lookupCapturedField(n[i], s)
|
||||
if result != nil: return
|
||||
of nkRecCase:
|
||||
if n[0].kind != nkSym: return
|
||||
result = lookupCapturedField(n[0], s)
|
||||
if result != nil: return
|
||||
for i in 1..<n.len:
|
||||
case n[i].kind
|
||||
of nkOfBranch, nkElse:
|
||||
result = lookupCapturedField(lastSon(n[i]), s)
|
||||
if result != nil: return
|
||||
else: discard
|
||||
of nkSym:
|
||||
if n.sym.kind == skField and n.sym.name.s == s.name.s & $n.sym.position:
|
||||
result = n.sym
|
||||
if n.sym.itemId.module == id.module and n.sym.itemId.item == -abs(id.item): result = n.sym
|
||||
else: discard
|
||||
|
||||
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
|
||||
# Idempotent w.r.t. the captured symbol (mirrors `addUniqueField`): re-lifting
|
||||
# a LOADED routine re-derives its transformed body (never serialized under IC)
|
||||
# and re-captures the same locals, but the env object loaded from the NIF
|
||||
# already carries their fields. Re-adding would duplicate the field and, worse,
|
||||
# mutate a Sealed loaded type via `propagateToOwner` (the `t.state != Sealed`
|
||||
# crash). Return the existing field instead.
|
||||
let existing = lookupInRecord(obj.n, s.itemId)
|
||||
if existing != nil:
|
||||
return existing
|
||||
# Re-lifting a LOADED routine during a VM transform (its transformed body is
|
||||
# re-derived per process, never serialized) re-captures the same locals, but
|
||||
# for a macro-generated gensym (e.g. libp2p `p2pProtocolBackendImpl`'s
|
||||
# `msgVar`) its process-local id diverges from the one baked into the loaded
|
||||
# env field, so the id match above misses. Reuse the existing same-named field
|
||||
# rather than appending a divergent duplicate, which keeps the re-derived
|
||||
# closure consistent (else a stale `:env` access reaches `cannotEval`).
|
||||
# Confined to a loaded (Sealed) env: in a freshly built env ids are consistent,
|
||||
# and two distinct same-named captures legitimately get distinct fields there.
|
||||
if obj.state == Sealed:
|
||||
let byName = lookupCapturedField(obj.n, s)
|
||||
if byName != nil:
|
||||
return byName
|
||||
# Genuinely new field. Under IC the env may be a loaded Sealed type whose
|
||||
# transform-time mutation is process-local (the body is discarded after the
|
||||
# macro runs), so downgrade it to mutable instead of crashing on
|
||||
# `t.state != Sealed` (mirrors `markAsClosure`).
|
||||
unsealForTransform(obj)
|
||||
# because of 'gensym' support, we have to mangle the name with its ID.
|
||||
# This is hacky but the clean solution is much more complex than it looks.
|
||||
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len),
|
||||
idgen, s.owner, s.info, s.options)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
field.itemId = ItemId(module: s.itemId.module, item: -s.itemId.item)
|
||||
let t = skipIntLit(s.typ, idgen)
|
||||
field.typ = t
|
||||
if s.kind in {skLet, skVar, skField, skForVar}:
|
||||
@@ -290,7 +235,7 @@ proc addUniqueField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator)
|
||||
if result == nil:
|
||||
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), idgen,
|
||||
s.owner, s.info, s.options)
|
||||
field.itemId = derivedFieldId(s.itemId)
|
||||
field.itemId = ItemId(module: s.itemId.module, item: -s.itemId.item)
|
||||
let t = skipIntLit(s.typ, idgen)
|
||||
field.typ = t
|
||||
assert t.kind != tyTyped
|
||||
@@ -305,12 +250,12 @@ proc newDotExpr*(obj, b: PSym): PNode =
|
||||
assert field != nil, b.name.s
|
||||
result.add newSymNode(obj)
|
||||
result.add newSymNode(field)
|
||||
result.typ = field.typ
|
||||
result.typ() = field.typ
|
||||
|
||||
proc indirectAccess*(a: PNode, b: ItemId, info: TLineInfo): PNode =
|
||||
# returns a[].b as a node
|
||||
var deref = newNodeI(nkHiddenDeref, info)
|
||||
deref.typ = a.typ.skipTypes(abstractInst).elementType
|
||||
deref.typ() = a.typ.skipTypes(abstractInst).elementType
|
||||
var t = deref.typ.skipTypes(abstractInst)
|
||||
var field: PSym
|
||||
while true:
|
||||
@@ -328,12 +273,12 @@ proc indirectAccess*(a: PNode, b: ItemId, info: TLineInfo): PNode =
|
||||
result = newNodeI(nkDotExpr, info)
|
||||
result.add deref
|
||||
result.add newSymNode(field)
|
||||
result.typ = field.typ
|
||||
result.typ() = field.typ
|
||||
|
||||
proc indirectAccess*(a: PNode, b: string, info: TLineInfo; cache: IdentCache): PNode =
|
||||
# returns a[].b as a node
|
||||
var deref = newNodeI(nkHiddenDeref, info)
|
||||
deref.typ = a.typ.skipTypes(abstractInst).elementType
|
||||
deref.typ() = a.typ.skipTypes(abstractInst).elementType
|
||||
var t = deref.typ.skipTypes(abstractInst)
|
||||
var field: PSym
|
||||
let bb = getIdent(cache, b)
|
||||
@@ -352,7 +297,7 @@ proc indirectAccess*(a: PNode, b: string, info: TLineInfo; cache: IdentCache): P
|
||||
result = newNodeI(nkDotExpr, info)
|
||||
result.add deref
|
||||
result.add newSymNode(field)
|
||||
result.typ = field.typ
|
||||
result.typ() = field.typ
|
||||
|
||||
proc getFieldFromObj*(t: PType; v: PSym): PSym =
|
||||
assert v.kind != skField
|
||||
@@ -361,16 +306,6 @@ proc getFieldFromObj*(t: PType; v: PSym): PSym =
|
||||
assert t.kind == tyObject
|
||||
result = lookupInRecord(t.n, v.itemId)
|
||||
if result != nil: break
|
||||
# A LOADED (Sealed) env object carries fields baked by the producer process;
|
||||
# re-lifting a NIF-loaded routine in a consumer (e.g. a macro VM-evaluating an
|
||||
# imported `p2pProtocolBackendImpl`) re-captures the same local under a
|
||||
# divergent process-local id, so the derived-itemId match misses. Fall back to
|
||||
# the name+position identity `addField` uses — SYMMETRIC with `addField`'s
|
||||
# Sealed by-name reuse — so the access resolves the field `addField` produced
|
||||
# instead of failing with `not part of closure object type`.
|
||||
if t.state == Sealed:
|
||||
result = lookupCapturedField(t.n, v)
|
||||
if result != nil: break
|
||||
t = t.baseClass
|
||||
if t == nil: break
|
||||
t = t.skipTypes(skipPtrs)
|
||||
@@ -385,7 +320,7 @@ proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode =
|
||||
proc genAddrOf*(n: PNode; idgen: IdGenerator; typeKind = tyPtr): PNode =
|
||||
result = newNodeI(nkAddr, n.info, 1)
|
||||
result[0] = n
|
||||
result.typ = newType(typeKind, idgen, n.typ.owner)
|
||||
result.typ() = newType(typeKind, idgen, n.typ.owner)
|
||||
result.typ.rawAddSon(n.typ)
|
||||
|
||||
proc genDeref*(n: PNode; k = nkHiddenDeref): PNode =
|
||||
@@ -409,18 +344,18 @@ proc callCodegenProc*(g: ModuleGraph; name: string;
|
||||
if optionalArgs != nil:
|
||||
for i in 1..<optionalArgs.len-2:
|
||||
result.add optionalArgs[i]
|
||||
result.typ = sym.typ.returnType
|
||||
result.typ() = sym.typ.returnType
|
||||
|
||||
proc newIntLit*(g: ModuleGraph; info: TLineInfo; value: BiggestInt): PNode =
|
||||
result = nkIntLit.newIntNode(value)
|
||||
result.typ = getSysType(g, info, tyInt)
|
||||
result.typ() = getSysType(g, info, tyInt)
|
||||
|
||||
proc genHigh*(g: ModuleGraph; n: PNode): PNode =
|
||||
if skipTypes(n.typ, abstractVar).kind == tyArray:
|
||||
result = newIntLit(g, n.info, toInt64(lastOrd(g.config, skipTypes(n.typ, abstractVar))))
|
||||
else:
|
||||
result = newNodeI(nkCall, n.info, 2)
|
||||
result.typ = getSysType(g, n.info, tyInt)
|
||||
result.typ() = getSysType(g, n.info, tyInt)
|
||||
result[0] = newSymNode(getSysMagic(g, n.info, "high", mHigh))
|
||||
result[1] = n
|
||||
|
||||
@@ -429,7 +364,7 @@ proc genLen*(g: ModuleGraph; n: PNode): PNode =
|
||||
result = newIntLit(g, n.info, toInt64(lastOrd(g.config, skipTypes(n.typ, abstractVar)) + 1))
|
||||
else:
|
||||
result = newNodeI(nkCall, n.info, 2)
|
||||
result.typ = getSysType(g, n.info, tyInt)
|
||||
result.typ() = getSysType(g, n.info, tyInt)
|
||||
result[0] = newSymNode(getSysMagic(g, n.info, "len", mLengthSeq))
|
||||
result[1] = n
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
# Built-in types and compilerprocs are registered here.
|
||||
|
||||
import
|
||||
ast, msgs, platform, idents,
|
||||
modulegraphs, lineinfos, types
|
||||
ast, astalgo, msgs, platform, idents,
|
||||
modulegraphs, lineinfos
|
||||
|
||||
export createMagic
|
||||
|
||||
@@ -134,7 +134,7 @@ proc getNimScriptSymbol*(g: ModuleGraph; name: string): PSym =
|
||||
proc resetNimScriptSymbols*(g: ModuleGraph) = g.exposed = initStrTable()
|
||||
|
||||
proc getMagicEqSymForType*(g: ModuleGraph; t: PType; info: TLineInfo): PSym =
|
||||
case t.skipTypes(abstractRange).kind
|
||||
case t.kind
|
||||
of tyInt, tyInt8, tyInt16, tyInt32, tyInt64,
|
||||
tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64:
|
||||
result = getSysMagic(g, info, "==", mEqI)
|
||||
@@ -166,4 +166,4 @@ proc makeAddr*(n: PNode; idgen: IdGenerator): PNode =
|
||||
result = n
|
||||
else:
|
||||
result = newTree(nkHiddenAddr, n)
|
||||
result.typ = makePtrType(n.typ.skipTypes({tySink}), idgen)
|
||||
result.typ() = makePtrType(n.typ.skipTypes({tySink}), idgen)
|
||||
|
||||
@@ -26,15 +26,11 @@ import
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions]
|
||||
|
||||
import ic / [cbackend, integrity, navigator, ic]
|
||||
|
||||
import ../dist/checksums/src/checksums/sha1
|
||||
|
||||
import pipelines
|
||||
from icconfig import produceIcConfig
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
import nifbackend
|
||||
import deps
|
||||
import idetools
|
||||
|
||||
when not defined(leanCompiler):
|
||||
import docgen
|
||||
@@ -99,6 +95,14 @@ proc commandCheck(graph: ModuleGraph) =
|
||||
setPipeLinePass(graph, SemPass)
|
||||
compilePipelineProject(graph)
|
||||
|
||||
if conf.symbolFiles != disabledSf:
|
||||
case conf.ideCmd
|
||||
of ideDef: navDefinition(graph)
|
||||
of ideUse: navUsages(graph)
|
||||
of ideDus: navDefusages(graph)
|
||||
else: discard
|
||||
writeRodFiles(graph)
|
||||
|
||||
when not defined(leanCompiler):
|
||||
proc commandDoc2(graph: ModuleGraph; ext: string) =
|
||||
handleDocOutputOptions graph.config
|
||||
@@ -129,22 +133,6 @@ proc commandCompileToNif(graph: ModuleGraph) =
|
||||
setPipeLinePass(graph, NifgenPass)
|
||||
compilePipelineProject(graph)
|
||||
|
||||
proc commandNifC(graph: ModuleGraph) =
|
||||
## Generate C code from precompiled NIF files.
|
||||
## This is the new IC approach: compile modules to NIF first with `nim m`,
|
||||
## then generate C code from the entry.nif file with whole-program DCE.
|
||||
when not defined(nimKochBootstrap):
|
||||
let conf = graph.config
|
||||
extccomp.initVars(conf)
|
||||
|
||||
if not extccomp.ccHasSaneOverflow(conf):
|
||||
conf.symbols.defineSymbol("nimEmulateOverflowChecks")
|
||||
|
||||
# Use the NIF backend to generate C code
|
||||
nifbackend.generateCode(graph, conf.projectMainIdx)
|
||||
else:
|
||||
rawMessage(graph.config, errGenerated, "NIF backend not available during bootstrap build")
|
||||
|
||||
proc commandCompileToC(graph: ModuleGraph) =
|
||||
let conf = graph.config
|
||||
extccomp.initVars(conf)
|
||||
@@ -165,7 +153,15 @@ proc commandCompileToC(graph: ModuleGraph) =
|
||||
compilePipelineProject(graph)
|
||||
if graph.config.errorCounter > 0:
|
||||
return # issue #9933
|
||||
cgenWriteModules(graph.backend, conf)
|
||||
if conf.symbolFiles == disabledSf:
|
||||
cgenWriteModules(graph.backend, conf)
|
||||
else:
|
||||
if isDefined(conf, "nimIcIntegrityChecks"):
|
||||
checkIntegrity(graph)
|
||||
generateCode(graph)
|
||||
# graph.backend can be nil under IC when nothing changed at all:
|
||||
if graph.backend != nil:
|
||||
cgenWriteModules(graph.backend, conf)
|
||||
if conf.cmd != cmdTcc and graph.backend != nil:
|
||||
extccomp.callCCompiler(conf)
|
||||
# for now we do not support writing out a .json file with the build instructions when HCR is on
|
||||
@@ -204,7 +200,7 @@ proc commandInteractive(graph: ModuleGraph) =
|
||||
discard graph.compilePipelineModule(fileInfoIdx(graph.config, graph.config.projectFull), {})
|
||||
else:
|
||||
var m = graph.makeStdinModule()
|
||||
incl(m, sfMainModule)
|
||||
incl(m.flags, sfMainModule)
|
||||
var idgen = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0)
|
||||
let s = llStreamOpenStdIn(onPrompt = proc() = flushDot(graph.config))
|
||||
discard processPipelineModule(graph, m, idgen, s)
|
||||
@@ -225,6 +221,10 @@ proc commandScan(cache: IdentCache, config: ConfigRef) =
|
||||
else:
|
||||
rawMessage(config, errGenerated, "cannot open file: " & f.string)
|
||||
|
||||
proc commandView(graph: ModuleGraph) =
|
||||
let f = toAbsolute(mainCommandArg(graph.config), AbsoluteDir getCurrentDir()).addFileExt(RodExt)
|
||||
rodViewer(f, graph.config, graph.cache)
|
||||
|
||||
const
|
||||
PrintRopeCacheStats = false
|
||||
|
||||
@@ -322,6 +322,8 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
case conf.cmd
|
||||
of cmdBackends:
|
||||
compileToBackend()
|
||||
when BenchIC:
|
||||
echoTimes graph.packed
|
||||
of cmdTcc:
|
||||
when hasTinyCBackend:
|
||||
extccomp.setCC(conf, "tcc", unknownLineInfo)
|
||||
@@ -417,55 +419,17 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
for it in conf.searchPaths: msgWriteln(conf, it.string)
|
||||
of cmdCheck:
|
||||
commandCheck(graph)
|
||||
of cmdTrack:
|
||||
# `nim track --def:/--usages:/--track:` — IDE goto-definition / find-usages.
|
||||
# Runs `nim ic`'s incremental frontend (nifler + per-module `nim m`, so only
|
||||
# changed modules recompile and each writes a faithful, VM-executed `.s.bif`
|
||||
# — covering stdlib too), then scans those NIF files (idetools.runIdeQuery).
|
||||
# Shares the `nim ic` nimcache dir, so a prior `nim ic` build is reused.
|
||||
setUseIc(true)
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
when not defined(nimKochBootstrap):
|
||||
commandIc(conf, frontendOnly = true)
|
||||
runIdeQuery(conf)
|
||||
else:
|
||||
rawMessage(conf, errGenerated, "nim track not available in bootstrap build")
|
||||
of cmdM:
|
||||
# cmdM uses NIF files, not ROD files
|
||||
graph.config.symbolFiles = disabledSf
|
||||
setUseIc(true)
|
||||
# vtable dispatch needs a whole-program vtable layout, which the
|
||||
# per-module compilation model cannot provide (yet); methods dispatch
|
||||
# through the classic if-chain dispatchers instead
|
||||
excl conf.features, Feature.vtables
|
||||
graph.config.symbolFiles = v2Sf
|
||||
setUseIc(graph.config.symbolFiles != disabledSf)
|
||||
commandCheck(graph)
|
||||
of cmdNifC:
|
||||
setUseIc(true)
|
||||
excl conf.features, Feature.vtables
|
||||
# Generate C code from NIF files
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
commandNifC(graph)
|
||||
of cmdIc:
|
||||
# Generate .build.nif for nifmake
|
||||
setUseIc(true)
|
||||
wantMainModule(conf)
|
||||
# Resolve the output binary path (honoring `--out`) up front, like cmdNifC:
|
||||
# the backend build file derives the link target from `conf.absOutFile`.
|
||||
setOutFile(conf)
|
||||
when not defined(nimKochBootstrap):
|
||||
commandIc(conf)
|
||||
else:
|
||||
rawMessage(conf, errGenerated, "nim deps not available in bootstrap build")
|
||||
of cmdIcConfig:
|
||||
# Produce the precompiled config artifact for `nim ic` (config already
|
||||
# parsed by the normal pipeline); a separate process spawned by the driver.
|
||||
wantMainModule(conf)
|
||||
produceIcConfig(conf)
|
||||
of cmdParse:
|
||||
wantMainModule(conf)
|
||||
discard parseFile(conf.projectMainIdx, cache, conf)
|
||||
of cmdRod:
|
||||
wantMainModule(conf)
|
||||
commandView(graph)
|
||||
#msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!")
|
||||
of cmdInteractive: commandInteractive(graph)
|
||||
of cmdNimscript:
|
||||
if conf.projectIsCmd or conf.projectIsStdin: discard
|
||||
@@ -476,17 +440,10 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
of cmdJsonscript:
|
||||
setOutFile(graph.config)
|
||||
commandJsonScript(graph)
|
||||
of cmdUnknown, cmdNone:
|
||||
of cmdUnknown, cmdNone, cmdIdeTools:
|
||||
rawMessage(conf, errGenerated, "invalid command: " & conf.command)
|
||||
|
||||
if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop, cmdM} and
|
||||
not (conf.cmd == cmdNifC and conf.icBackendStage.len > 0):
|
||||
# The IC build runs hundreds of internal per-module child processes — the
|
||||
# frontend `nim m` (cmdM) and the per-module backend stages (cg/emit/merge/
|
||||
# link). Each would print a `[SuccessX]` summary that is pure noise (and
|
||||
# misleading: `out: unknownOutput`, or `out: <the whole compiler>` for a
|
||||
# step that only wrote one `.c.nif`/`.c`). The driving `nim ic` (and koch)
|
||||
# reports the real result.
|
||||
if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop}:
|
||||
if optProfileVM in conf.globalOptions:
|
||||
echo conf.dump(conf.vmProfileData)
|
||||
genSuccessX(conf)
|
||||
|
||||
@@ -53,39 +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). Most such symbols never cross a process boundary (nifc
|
||||
# lifts, emits and compiles them in one run), so the per-module-unique
|
||||
# item id is a safe and deterministic discriminator; the `_c` marker keeps
|
||||
# the namespace disjoint from `_u<disamb>`.
|
||||
result = "_c"
|
||||
if (s.disamb and HookDisambBit) != 0'i32:
|
||||
# EXCEPTION: a backend-minted sym whose `disamb` is content-derived
|
||||
# (setHookDisamb gave it HookDisambBit) — e.g. the `rttiDestroy` wrapper —
|
||||
# DOES cross process boundaries: its C name is baked into the type's RTTI
|
||||
# table, which is emit-everywhere and merge-deduped, so one process's
|
||||
# `_c<item>` (a per-process backend counter) ends up referenced while the
|
||||
# wrapper is defined with another's → undefined at link (`rttiDestroy_c23`).
|
||||
# The content-derived disamb is stable across processes; use it.
|
||||
result.addInt s.disamb
|
||||
else:
|
||||
result.addInt s.itemId.item
|
||||
else:
|
||||
result = "_u"
|
||||
# Use `disamb` rather than `itemId.item`: under incremental compilation a
|
||||
# symbol loaded from a NIF file gets a fresh, load-order-dependent `itemId.item`
|
||||
# (from the per-module symbol counter), which is neither stable across the
|
||||
# processes that compile vs. use a module nor guaranteed distinct from another
|
||||
# loaded symbol's. `disamb` is assigned deterministically per (module, name)
|
||||
# and, together with the already-prepended mangled name, yields a unique and
|
||||
# stable C identifier.
|
||||
result.addInt s.disamb
|
||||
result.add "__"
|
||||
result = "__"
|
||||
result.add graph.ifaces[s.itemId.module].uniqueName
|
||||
result.add "_u"
|
||||
result.addInt s.itemId.item # s.disamb #
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -109,11 +109,9 @@ proc mangleModuleName*(conf: ConfigRef; path: AbsoluteFile): string =
|
||||
of FromSearchPath: "@p"
|
||||
of FromNimblePath: "@n"
|
||||
|
||||
# Note: We encode ".." specially as "@d" to avoid issues with changeFileExt
|
||||
# which would misinterpret ".." as "name.ext" and strip the second part.
|
||||
prefix & best.multiReplace(
|
||||
{"..": "@d", $os.DirSep: "@s", $os.AltSep: "@s", "#": "@h", "@": "@@", ":": "@c"})
|
||||
{$os.DirSep: "@s", $os.AltSep: "@s", "#": "@h", "@": "@@", ":": "@c"})
|
||||
|
||||
proc demangleModuleName*(path: string): string =
|
||||
## Demangle a relative module path.
|
||||
result = path.multiReplace({"@@": "@", "@d": "..", "@h": "#", "@s": "/", "@m": "", "@p": "", "@n": "", "@c": ":"})
|
||||
result = path.multiReplace({"@@": "@", "@h": "#", "@s": "/", "@m": "", "@p": "", "@n": "", "@c": ":"})
|
||||
|
||||
@@ -32,9 +32,9 @@ proc newModule*(graph: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
let filename = AbsoluteFile toFullPath(graph.config, fileIdx)
|
||||
# We cannot call ``newSym`` here, because we have to circumvent the ID
|
||||
# mechanism, which we do in order to assign each module a persistent ID.
|
||||
result = PSym(kindImpl: skModule, itemId: itemId(int32(fileIdx), 0'i32),
|
||||
result = PSym(kind: skModule, itemId: ItemId(module: int32(fileIdx), item: 0'i32),
|
||||
name: getModuleIdent(graph, filename),
|
||||
infoImpl: newLineInfo(fileIdx, 1, 1))
|
||||
info: newLineInfo(fileIdx, 1, 1))
|
||||
if not isNimIdentifier(result.name.s):
|
||||
rawMessage(graph.config, errGenerated, "invalid module name: '" & result.name.s &
|
||||
"'; a module name must be a valid Nim identifier.")
|
||||
|
||||
@@ -30,6 +30,7 @@ proc toLowerAscii(a: var string) {.inline.} =
|
||||
|
||||
proc flushDot*(conf: ConfigRef) =
|
||||
## safe to call multiple times
|
||||
# xxx one edge case not yet handled is when `printf` is called at CT with `compiletimeFFI`.
|
||||
let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr
|
||||
let stdOrrKind = toStdOrrKind(stdOrr)
|
||||
if stdOrrKind in conf.lastMsgWasDot:
|
||||
@@ -51,7 +52,7 @@ proc makeCString*(s: string): Rope =
|
||||
result = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
|
||||
result.add("\"")
|
||||
for i in 0..<s.len:
|
||||
# line wrapping of string literals in cgen'd code was a bad idea, e.g. causes: bug #16265
|
||||
# line wrapping of string litterals in cgen'd code was a bad idea, e.g. causes: bug #16265
|
||||
# It also makes reading c sources or grepping harder, for zero benefit.
|
||||
# const MaxLineLength = 64
|
||||
# if (i + 1) mod MaxLineLength == 0:
|
||||
@@ -59,12 +60,12 @@ proc makeCString*(s: string): Rope =
|
||||
toCChar(s[i], result)
|
||||
result.add('\"')
|
||||
|
||||
proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile; kind = fikSource): TFileInfo =
|
||||
proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo =
|
||||
result = TFileInfo(fullPath: fullPath, projPath: projPath,
|
||||
shortName: fullPath.extractFilename,
|
||||
quotedFullName: fullPath.string.makeCString,
|
||||
lines: @[],
|
||||
kind: kind)
|
||||
lines: @[]
|
||||
)
|
||||
result.quotedName = result.shortName.makeCString
|
||||
when defined(nimpretty):
|
||||
if not result.fullPath.isEmpty:
|
||||
@@ -125,42 +126,12 @@ proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
|
||||
var dummy: bool = false
|
||||
result = fileInfoIdx(conf, filename, dummy)
|
||||
|
||||
proc expandOrPseudo(filename: string): AbsoluteFile =
|
||||
# `expandFilename` raises OSError when the path does not exist on disk. That is
|
||||
# fine for a real source path, but a macro can legitimately set a node's
|
||||
# line-info file to a name that has no file behind it — e.g. the `???` sentinel
|
||||
# produced by `toFilename` for a NIF-loaded node whose `fileIndex` is unknown
|
||||
# (FileIndex(-1)). Falling back to the raw name lets the `AbsoluteFile` overload
|
||||
# register it as a pseudo-path (like `command line`/`stdin`) instead of crashing
|
||||
# the whole `nim m` child with an unhandled OSError.
|
||||
try:
|
||||
result = AbsoluteFile expandFilename(filename)
|
||||
except OSError:
|
||||
result = AbsoluteFile filename
|
||||
|
||||
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile; isKnownFile: var bool): FileIndex =
|
||||
fileInfoIdx(conf, expandOrPseudo(filename.string), isKnownFile)
|
||||
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), isKnownFile)
|
||||
|
||||
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile): FileIndex =
|
||||
var dummy: bool = false
|
||||
fileInfoIdx(conf, expandOrPseudo(filename.string), dummy)
|
||||
|
||||
proc registerNifSuffix*(conf: ConfigRef; suffix: string; isKnownFile: var bool): FileIndex =
|
||||
result = conf.m.filenameToIndexTbl.getOrDefault(suffix, InvalidFileIdx)
|
||||
if result == InvalidFileIdx:
|
||||
isKnownFile = false
|
||||
result = conf.m.fileInfos.len.FileIndex
|
||||
conf.m.fileInfos.add(newFileInfo(AbsoluteFile suffix, RelativeFile suffix, fikNifModule))
|
||||
conf.m.filenameToIndexTbl[suffix] = result
|
||||
else:
|
||||
isKnownFile = true
|
||||
|
||||
proc fileInfoKind*(conf: ConfigRef; fileIdx: FileIndex): FileInfoKind =
|
||||
## Returns the kind of a FileIndex (source file or NIF module suffix).
|
||||
if fileIdx.int >= 0 and fileIdx.int < conf.m.fileInfos.len:
|
||||
result = conf.m.fileInfos[fileIdx.int].kind
|
||||
else:
|
||||
result = fikSource # Default to source for unknown indices
|
||||
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), dummy)
|
||||
|
||||
proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
|
||||
result = TLineInfo(fileIndex: fileInfoIdx)
|
||||
@@ -253,7 +224,7 @@ proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile)
|
||||
|
||||
proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
|
||||
assert fileIdx.int32 >= 0
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
conf.m.fileInfos[fileIdx.int32].hash = hash
|
||||
else:
|
||||
shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
|
||||
@@ -261,7 +232,7 @@ proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
|
||||
|
||||
proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
|
||||
assert fileIdx.int32 >= 0
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result = conf.m.fileInfos[fileIdx.int32].hash
|
||||
else:
|
||||
shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
|
||||
@@ -351,7 +322,7 @@ proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
|
||||
|
||||
## This is used for 'nim dump' etc. where we don't have nimsuggest
|
||||
## support.
|
||||
#if conf.ideActive and optCDebug notin gGlobalOptions: return
|
||||
#if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
|
||||
let sep = if msgNoUnitSep notin flags: conf.unitSep else: ""
|
||||
if not isNil(conf.writelnHook) and msgSkipHook notin flags:
|
||||
conf.writelnHook(s & sep)
|
||||
@@ -457,8 +428,8 @@ To create a stacktrace, rerun compilation with './koch temp $1 <file>', see $2 f
|
||||
|
||||
proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string, ignoreMsg: bool) =
|
||||
if msg in fatalMsgs:
|
||||
if conf.ideActive: log(s)
|
||||
if not conf.ideActive or msg != errFatal:
|
||||
if conf.cmd == cmdIdeTools: log(s)
|
||||
if conf.cmd != cmdIdeTools or msg != errFatal:
|
||||
quit(conf, msg)
|
||||
if msg >= errMin and msg <= errMax or
|
||||
(msg in warnMin..hintMax and msg in conf.warningAsErrors and not ignoreMsg):
|
||||
@@ -472,7 +443,7 @@ proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string,
|
||||
raiseRecoverableError(s)
|
||||
else:
|
||||
quit(conf, msg)
|
||||
elif eh == doAbort and not conf.ideActive:
|
||||
elif eh == doAbort and conf.cmd != cmdIdeTools:
|
||||
quit(conf, msg)
|
||||
elif eh == doRaise:
|
||||
raiseRecoverableError(s)
|
||||
@@ -503,7 +474,7 @@ proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
|
||||
info = context.info
|
||||
|
||||
proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
|
||||
msg >= errGenerated and conf.ideActive and optIdeDebug notin conf.globalOptions
|
||||
msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
|
||||
|
||||
proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
|
||||
conf.m.fileInfos[fileIdx.int32].lines.add line
|
||||
@@ -524,9 +495,6 @@ proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
|
||||
## 1-based index (matches editor line numbers); 1st line is for i.line = 1
|
||||
## last valid line is `numLines` inclusive
|
||||
if i.fileIndex.int32 < 0: return ""
|
||||
# line 0 means "unknown": nodes synthesized from an IC-loaded template or
|
||||
# macro body carry no source position.
|
||||
if i.line.int < 1: return ""
|
||||
let num = numLines(conf, i.fileIndex)
|
||||
# can happen if the error points to EOF:
|
||||
if i.line.int > num: return ""
|
||||
@@ -661,7 +629,7 @@ proc warningDeprecated*(conf: ConfigRef, info: TLineInfo = gCmdLineInfo, msg = "
|
||||
message(conf, info, warnDeprecated, msg)
|
||||
|
||||
proc internalErrorImpl(conf: ConfigRef; info: TLineInfo, errMsg: string, info2: InstantiationInfo) =
|
||||
if (conf.ideActive or conf.cmd == cmdCheck) and conf.structuredErrorHook.isNil: return
|
||||
if conf.cmd in {cmdIdeTools, cmdCheck} and conf.structuredErrorHook.isNil: return
|
||||
writeContext(conf, info)
|
||||
liMessage(conf, info, errInternal, errMsg, doAbort, info2)
|
||||
|
||||
@@ -680,9 +648,7 @@ template internalAssert*(conf: ConfigRef, e: bool) =
|
||||
|
||||
template lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string, extraMsg = "") =
|
||||
let m = "'$1' should be: '$2'$3" % [got, beau, extraMsg]
|
||||
let msg = if optStyleError in conf.globalOptions: errGenerated
|
||||
elif optStyleWarning in conf.globalOptions: warnUser
|
||||
else: hintName
|
||||
let msg = if optStyleError in conf.globalOptions: errGenerated else: hintName
|
||||
liMessage(conf, info, msg, m, doNothing, instLoc())
|
||||
|
||||
proc quotedFilename*(conf: ConfigRef; fi: FileIndex): Rope =
|
||||
|
||||
@@ -1,884 +0,0 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2025 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## NIF-based C/C++ code generator backend.
|
||||
##
|
||||
## This module implements C code generation from precompiled NIF files.
|
||||
## It traverses the module dependency graph starting from the main module
|
||||
## and generates C code for all reachable modules.
|
||||
##
|
||||
## Usage:
|
||||
## 1. Compile modules to NIF: nim m mymodule.nim
|
||||
## 2. Generate C from NIF: nim nifc myproject.nim
|
||||
|
||||
import std/[intsets, tables, sets, os, algorithm, syncio, times, strutils]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ast, options, lineinfos, modulegraphs, cgendata, cgen,
|
||||
pathutils, extccomp, msgs, modulepaths, idents, types, ast2nif, typekeys,
|
||||
cnif, icmodnames
|
||||
from cgmeth import generateIfMethodDispatchers
|
||||
from transf import transformBody
|
||||
from injectdestructors import injectDestructorCalls
|
||||
import ic / replayer
|
||||
|
||||
proc systemNifSuffix(conf: ConfigRef): string =
|
||||
## The system module's NIF suffix, derived from `system.nim`'s path EXACTLY as
|
||||
## the frontend derives it (deps.nim's `toPair` on `libpath/system.nim`), so the
|
||||
## backend loads the very `.s.bif` the frontend wrote. It must NOT be a constant:
|
||||
## `moduleSuffix` (icmodnames) now hashes the absolute path, so the system suffix
|
||||
## is install-dependent (was hardcoded `sysma2dyk`, valid only for the old
|
||||
## relative-path scheme where `system.nim` always relativized to `system.nim`).
|
||||
moduleSuffix((conf.libpath / RelativeFile"system.nim").string,
|
||||
cast[seq[string]](conf.searchPaths))
|
||||
|
||||
proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex;
|
||||
nifFiles: var seq[string];
|
||||
depFlags: set[LoadFlag] = {LoadFullAst}): seq[PrecompiledModule] =
|
||||
## Traverse the module dependency graph using a stack.
|
||||
## Returns all modules that need code generation, in dependency order.
|
||||
##
|
||||
## The main module is always loaded with its full AST (it is the codegen
|
||||
## target). `depFlags` governs the rest: the whole-program backend needs every
|
||||
## module's full AST (it generates code for all of them), but a per-module
|
||||
## stage codegens only one target, so it loads the others interface-only
|
||||
## (`depFlags = {}`) — the interface, hooks, methods and the `(replay ...)`
|
||||
## directives are loaded regardless of `LoadFullAst`, and demanded bodies are
|
||||
## fetched lazily from the kept-open stream, so the per-module proc-body ASTs
|
||||
## (the bulk of the memory) are never materialized for non-targets.
|
||||
# The main module is loaded by its SOURCE FileIndex, but its serialized
|
||||
# symbols carry the module's NIF suffix. Pre-alias the suffix to the source
|
||||
# index so that `registerNifSuffix` does not allocate a second FileIndex for
|
||||
# the same module, which would split its codegen across two C translation
|
||||
# units (top-level globals in one, procs in the other → undeclared symbols).
|
||||
g.config.m.filenameToIndexTbl[cachedModuleSuffix(g.config, mainFileIdx)] = mainFileIdx
|
||||
let mainModule = moduleFromNifFile(g, mainFileIdx, {LoadFullAst})
|
||||
nifFiles.add toNifFilename(g.config, mainFileIdx)
|
||||
|
||||
var stack: seq[ModuleSuffix] = @[]
|
||||
result = @[]
|
||||
|
||||
if mainModule.module != nil:
|
||||
incl mainModule.module.flagsImpl, sfMainModule
|
||||
for dep in mainModule.deps:
|
||||
stack.add dep
|
||||
|
||||
var visited = initHashSet[string]()
|
||||
|
||||
while stack.len > 0:
|
||||
let suffix = stack.pop()
|
||||
|
||||
if not visited.containsOrIncl(suffix.string):
|
||||
var isKnownFile = false
|
||||
let fileIdx = g.config.registerNifSuffix(suffix.string, isKnownFile)
|
||||
let precomp = moduleFromNifFile(g, fileIdx, depFlags)
|
||||
if precomp.module != nil:
|
||||
result.add precomp
|
||||
nifFiles.add toNifFilename(g.config, fileIdx)
|
||||
for dep in precomp.deps:
|
||||
if not visited.contains(dep.string):
|
||||
stack.add dep
|
||||
else:
|
||||
assert false, "Recompiling module is not implemented."
|
||||
|
||||
if mainModule.module != nil:
|
||||
result.add mainModule
|
||||
|
||||
proc setupNifBackendModule(g: ModuleGraph; module: PSym): BModule =
|
||||
## Set up a BModule for code generation from a NIF module.
|
||||
if g.backend == nil:
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
result = cgen.newModule(BModuleList(g.backend), module, g.config, idGeneratorForBackend(module))
|
||||
|
||||
proc isMetaIter(t: PType, closure: RootRef): bool =
|
||||
# openArray/varargs hooks are sem bookkeeping: no real flow ever demands
|
||||
# them, and generating one pollutes the TU's type cache with a struct
|
||||
# descriptor for what must remain a (ptr, len) parameter expansion
|
||||
t.kind in tyMetaTypes + {tyTyped, tyUntyped, tyNone, tyVarargs, tyOpenArray}
|
||||
|
||||
proc finishModule(g: ModuleGraph; bmod: BModule) =
|
||||
# Finalize the module (this adds it to modulesClosed)
|
||||
# Create an empty stmt list as the init body - genInitCode in writeModule will set it up properly
|
||||
let initStmt = newNode(nkStmtList)
|
||||
finalCodegenActions(g, bmod, initStmt)
|
||||
|
||||
# NB: the method dispatchers are emitted in `emitMethodDispatchers`,
|
||||
# between the module loop and this finish loop: their bodies demand the
|
||||
# method definitions, which can in turn demand definitions from modules
|
||||
# the backend never loaded — and a TU demand-created during the LAST
|
||||
# finishModule call would miss `modulesClosed` and never be written.
|
||||
|
||||
proc emitMethodDispatchers(g: ModuleGraph) =
|
||||
## Synthesizes the method dispatcher bodies from the replayed dispatch
|
||||
## buckets (`registerLoadedMethod`) and emits their definitions into the
|
||||
## main TU. Main is regenerated on every run, so a dispatcher — whose
|
||||
## body enumerates the whole program's method set — can never go stale
|
||||
## inside a cached TU; cross-TU callers prototype it (see genProcLvl3).
|
||||
let bl = BModuleList(g.backend)
|
||||
var mainMod: BModule = nil
|
||||
for m in bl.mods:
|
||||
if m != nil and m.module != nil and sfMainModule in m.module.flags:
|
||||
mainMod = m
|
||||
break
|
||||
if mainMod == nil: return
|
||||
generateIfMethodDispatchers(g, mainMod.idgen)
|
||||
for disp in getDispatchers(g):
|
||||
if not containsOrIncl(mainMod.declaredThings, disp.id):
|
||||
genProcLvl3(mainMod, disp)
|
||||
|
||||
proc signatureHasMetaType(t: PType; depth: int = 0): bool =
|
||||
## Whether a routine signature mentions a compile-time/meta element type
|
||||
## (`typed`/`untyped` — e.g. `echo`'s `varargs[typed]` — typedesc, static,
|
||||
## generic param). Such routines are expanded at their call sites and never
|
||||
## emitted standalone, so the per-module owned-routine seeding must skip them
|
||||
## (`getTypeDescAux(tyTyped)` otherwise). `tfHasMeta` alone misses the varargs
|
||||
## element case, hence the explicit scan.
|
||||
result = false
|
||||
if t == nil or depth > 8: return false
|
||||
if t.kind == tyGenericBody:
|
||||
# The uninstantiated template carried as a `tyGenericInst`'s first child
|
||||
# always mentions its `tyGenericParam` placeholders, but the instance
|
||||
# itself is fully concrete (e.g. `var CountTable[SigHash]`). Descending
|
||||
# here would wrongly flag every routine with a generic-instance parameter
|
||||
# as meta and drop it from the owned-routine seeding -> undefined symbols
|
||||
# at link (its only definer never emits it).
|
||||
return false
|
||||
if t.kind == tyStatic:
|
||||
# A RESOLVED static value (the `256` in `MDigest[256]`, the `N` in
|
||||
# `HashList[T, N]`, …) is carried as a `tyStatic` node inside the otherwise
|
||||
# fully-concrete `tyGenericInst`, but it is NOT meta: the routine is a normal
|
||||
# runtime routine the owner must emit. Only an UNRESOLVED `static T` parameter
|
||||
# (no bound value, `t.n == nil`) is meta. Without this, every routine whose
|
||||
# signature touches a `static`-parameterized generic instance (the bulk of
|
||||
# the SSZ/`MDigest` API) is dropped from the owned-routine seeding and ends up
|
||||
# an undefined reference at link (mirrors the tyGenericBody case above).
|
||||
return t.n == nil
|
||||
if t.kind in {tyTyped, tyUntyped, tyTypeDesc, tyGenericParam,
|
||||
tyAnything, tyFromExpr, tyError}:
|
||||
return true
|
||||
for k in t.kids:
|
||||
if signatureHasMetaType(k, depth + 1): return true
|
||||
|
||||
proc ownsRuntimeRoutine(s: PSym; modPos: int): bool =
|
||||
## A concrete, non-generic, runtime routine with a real body, OWNED by the
|
||||
## module at `modPos`. Shared by the `cg` stage's owned-routine seeding (so a
|
||||
## routine called only from other modules is still emitted by somebody) and
|
||||
## the `lower` stage's owned-routine enumeration, so both stages see exactly
|
||||
## the same set. The exclusions:
|
||||
## - nested/closure procs (owner is a proc, not a module): emitted via their
|
||||
## enclosing routine's lambda-lifting, never standalone;
|
||||
## - generic instances (`sfFromGeneric`): emitted by demand, deduped by merge;
|
||||
## - `importc`/`compileTime`/`error`/forward sentinels and meta signatures:
|
||||
## not real codegen targets.
|
||||
## - method DISPATCHERS (`sfDispatcher`): their bodies are (re)synthesized into
|
||||
## the main TU by `emitMethodDispatchers`/`generateIfMethodDispatchers`, never
|
||||
## per module. A dispatcher is a `copySym` clone of the method that shares the
|
||||
## method's body sub-tree (incl. its closure iterator); transforming it here
|
||||
## would lambda-lift that SHARED iterator a SECOND time under a different owner
|
||||
## identity, baking a conflicting `up` field → "up references do not agree"
|
||||
## (the divergence is impossible in non-IC, where the dispatcher body is empty
|
||||
## at lift time). So a dispatcher is never an owned runtime routine.
|
||||
## A `{.closure.}` iterator IS a standalone runtime routine (unlike an inline
|
||||
## iterator, which is expanded at each call site) and must be emitted by its
|
||||
## owner — else a cross-module `for` over it links to nothing.
|
||||
##
|
||||
## Generic INSTANCES (`sfFromGeneric`) are NEVER an owned runtime routine — not
|
||||
## in `cg` and not in the `lower` stage. They are demanded by the backend's
|
||||
## emit-everywhere path and deduped by `merge` (content C name); the frontend
|
||||
## materialises them through the `(offer)` mechanism. The `lower` stage must
|
||||
## not transform an instance: a not-fully-concrete instance (a closure factory
|
||||
## over a `static` param, or a `$`/`=` op instance whose body resolves only at
|
||||
## its further-specialised use sites) still carries unresolved overload choices
|
||||
## and crashes `transformBody` (empty-`namePos` lambda, nil-typed const-fold).
|
||||
s.itemId.module == modPos and
|
||||
(s.kind in {skProc, skFunc, skConverter, skMethod} or
|
||||
(s.kind == skIterator and s.typ != nil and s.typ.callConv == ccClosure)) and
|
||||
s.skipGenericOwner != nil and s.skipGenericOwner.kind == skModule and
|
||||
s.magic == mNone and
|
||||
sfFromGeneric notin s.flags and
|
||||
sfDispatcher notin s.flags and
|
||||
{sfForward, sfImportc, sfCompileTime, sfError} * s.flags == {} and
|
||||
s.typ != nil and not signatureHasMetaType(s.typ) and
|
||||
s.ast != nil and s.ast.safeLen > bodyPos and
|
||||
s.ast[genericParamsPos].kind == nkEmpty
|
||||
# NOTE: an `nkEmpty` body is NOT a disqualifier. A concrete, owned, non-
|
||||
# forward/-importc/-magic routine whose body folds to nothing is still a real
|
||||
# definition the owner must emit (`void f(void){}`), exactly as whole-program
|
||||
# cgen does — else a cross-module caller links to nothing. This bites e.g.
|
||||
# Nimbus' `extras.incInternalErrors`, a plain `proc` whose sole statement is a
|
||||
# metrics-counter `.inc()` that the `metrics` library expands to a no-op when
|
||||
# the importing tool (ncli) builds with `-u:metrics`; the body is then a bare
|
||||
# `nkEmpty`, but `state_transition_epoch` still calls it. Forward declarations
|
||||
# (the other empty-body case) carry `sfForward` and are excluded above.
|
||||
|
||||
proc generateCodeForModule(g: ModuleGraph; precomp: PrecompiledModule) =
|
||||
## Generate C code for a single module.
|
||||
let moduleId = precomp.module.position
|
||||
var bmod = BModuleList(g.backend).mods[moduleId]
|
||||
if bmod == nil:
|
||||
bmod = setupNifBackendModule(g, precomp.module)
|
||||
|
||||
# Apply the module's recorded C compile/link directives (passl/passc/...)
|
||||
# before generating code: the link step needs them (e.g. math's -lm).
|
||||
replayBackendActions(g, precomp.module, precomp.topLevel)
|
||||
|
||||
# Generate code for the module's top-level statements
|
||||
if precomp.topLevel != nil:
|
||||
cgen.genTopLevelStmt(bmod, precomp.topLevel)
|
||||
|
||||
# Per-module backend: emit the bodies of the routines this module OWNS, not
|
||||
# only the ones its top-level happens to demand. Procs are serialized as lazy
|
||||
# `(sd ...)` defs (never as `nkProcDef` statements), so `genTopLevelStmt` never
|
||||
# reaches them; a routine called only from *other* modules would otherwise be
|
||||
# emitted by nobody, because every module now merely prototypes its foreign
|
||||
# callees instead of funnelling their bodies (see `cgen.emitsBodyInThisModule`).
|
||||
# The merge stage's DCE drops whatever turns out globally dead.
|
||||
if g.config.cmd == cmdNifC and g.config.icBackendStage == "cg":
|
||||
let modPos = precomp.module.position
|
||||
for s in moduleSymbolStubs(ast.program, FileIndex modPos):
|
||||
if ownsRuntimeRoutine(s, modPos):
|
||||
requestProcDef(bmod, s)
|
||||
|
||||
proc loadBackendModules(g: ModuleGraph; mainFileIdx: FileIndex):
|
||||
tuple[modules: seq[PrecompiledModule], precompSys: PrecompiledModule,
|
||||
nifFiles: seq[string]] =
|
||||
## Shared by the per-module `cg` and `emit` stages: load system + the main
|
||||
## module's whole import closure and set up a `BModule` for each, so every
|
||||
## type/symbol resolves and `getCFile` yields the same path both stages use.
|
||||
## The main module is loaded by its source index (its NIF suffix is aliased to
|
||||
## it in `loadModuleDependencies`), so it gets exactly one `BModule`.
|
||||
##
|
||||
## Only the main module — the codegen target of the stages that use this — is
|
||||
## loaded with its full AST; every other module is loaded interface-only so
|
||||
## the whole program's proc bodies are not materialized into this process (that
|
||||
## was ~1.8 GB for the compiler's main `cg`). The `link` stage codegens nothing
|
||||
## and only needs each module's `(replay ...)` directives, which load anyway.
|
||||
resetForBackend(g)
|
||||
var isKnownFile = false
|
||||
let systemFileIdx = registerNifSuffix(g.config, systemNifSuffix(g.config), isKnownFile)
|
||||
g.config.m.systemFileIdx = systemFileIdx
|
||||
var precompSys = moduleFromNifFile(g, systemFileIdx, {AlwaysLoadInterface})
|
||||
g.systemModule = precompSys.module
|
||||
if precompSys.module != nil:
|
||||
# The precompiled-load path does not restore `sfSystemModule` (mirror of the
|
||||
# `sfMainModule` re-add above). `registerReusedModuleToMain` keys on it to put
|
||||
# the system module's init right after its datInit AND to emit
|
||||
# `initStackBottomWith` into `mainDatInit` — so that the main thread's stack
|
||||
# bottom is set before any module's init runs. Without the flag the system
|
||||
# init is mis-routed into the regular `otherModsInit` bucket and
|
||||
# `initStackBottomWith` is never registered, so a GC cycle during a module's
|
||||
# init (under refc) scans the stack with a nil bottom and crashes.
|
||||
incl precompSys.module.flagsImpl, sfSystemModule
|
||||
var nifFiles: seq[string] = @[toNifFilename(g.config, systemFileIdx)]
|
||||
var modules = loadModuleDependencies(g, mainFileIdx, nifFiles, depFlags = {})
|
||||
# loadModuleDependencies traverses the project's import closure and stops at
|
||||
# system. The whole-program backend then demand-loads system's own closure
|
||||
# (locks, allocators, threads, …) during codegen; the per-module backend
|
||||
# instead makes every one of those a first-class cg/emit target, so load that
|
||||
# closure here too — otherwise `findTargetModule` cannot resolve their suffix.
|
||||
block:
|
||||
var visited = initHashSet[string]()
|
||||
visited.incl systemNifSuffix(g.config)
|
||||
for m in modules:
|
||||
visited.incl cachedModuleSuffix(g.config, FileIndex m.module.position)
|
||||
var stack: seq[ModuleSuffix] = @[]
|
||||
if precompSys.module != nil:
|
||||
for dep in precompSys.deps: stack.add dep
|
||||
while stack.len > 0:
|
||||
let suffix = stack.pop()
|
||||
if not visited.containsOrIncl(suffix.string):
|
||||
var isKnown = false
|
||||
let fileIdx = registerNifSuffix(g.config, suffix.string, isKnown)
|
||||
let precomp = moduleFromNifFile(g, fileIdx, {})
|
||||
if precomp.module != nil:
|
||||
modules.add precomp
|
||||
nifFiles.add toNifFilename(g.config, fileIdx)
|
||||
for dep in precomp.deps: stack.add dep
|
||||
flushMethodReplays(g)
|
||||
for m in modules:
|
||||
discard setupNifBackendModule(g, m.module)
|
||||
if precompSys.module != nil:
|
||||
discard setupNifBackendModule(g, precompSys.module)
|
||||
result = (modules, precompSys, nifFiles)
|
||||
|
||||
proc loadDepClosure(g: ModuleGraph; targetSuffix: string):
|
||||
tuple[modules: seq[PrecompiledModule], precompSys: PrecompiledModule,
|
||||
target: PrecompiledModule] =
|
||||
## Per-module `cg`/`emit` for a NON-main target: load system + the target
|
||||
## module + the target's transitive import closure ONLY — not the whole
|
||||
## program. This is the "process the one file it is passed" model (à la
|
||||
## Nimony's `hexer c file.nif`): the foreign symbols the target's codegen
|
||||
## demands are loaded lazily by `ast2nif.moduleId`, which opens any referenced
|
||||
## module's NIF index on first touch, so a body in a not-loaded module still
|
||||
## resolves. The closure is loaded as full `BModule`s only so that the
|
||||
## incidental `g.mods[pos]` accesses during codegen resolve; system's own
|
||||
## internal closure (allocators, locks, …) is included because a target's
|
||||
## emit-everywhere codegen can demand those without importing them directly.
|
||||
##
|
||||
## The whole program is no longer loaded in this process, which is what bounds
|
||||
## per-process memory under nifmake's parallel fan-out (the main module's `cg`,
|
||||
## which still loads everything for NimMain's init list and the method
|
||||
## dispatchers, runs essentially alone since every other `.c.nif` precedes it).
|
||||
resetForBackend(g)
|
||||
var isKnownFile = false
|
||||
let systemFileIdx = registerNifSuffix(g.config, systemNifSuffix(g.config), isKnownFile)
|
||||
g.config.m.systemFileIdx = systemFileIdx
|
||||
let precompSys = moduleFromNifFile(g, systemFileIdx, {AlwaysLoadInterface})
|
||||
g.systemModule = precompSys.module
|
||||
|
||||
var modules: seq[PrecompiledModule] = @[]
|
||||
var visited = initHashSet[string]()
|
||||
visited.incl systemNifSuffix(g.config)
|
||||
|
||||
# Only the target is codegen'd, so only it needs its full AST; the closure is
|
||||
# loaded interface-only (demanded bodies come lazily from the kept-open
|
||||
# streams), which is what keeps a per-module process light under parallel fan-out.
|
||||
var isKnown = false
|
||||
let targetIdx = registerNifSuffix(g.config, targetSuffix, isKnown)
|
||||
let target = moduleFromNifFile(g, targetIdx, {LoadFullAst})
|
||||
visited.incl targetSuffix
|
||||
|
||||
var stack: seq[ModuleSuffix] = @[]
|
||||
if target.module != nil:
|
||||
modules.add target
|
||||
for dep in target.deps: stack.add dep
|
||||
if precompSys.module != nil:
|
||||
for dep in precompSys.deps: stack.add dep
|
||||
while stack.len > 0:
|
||||
let suffix = stack.pop()
|
||||
if not visited.containsOrIncl(suffix.string):
|
||||
var isKnown2 = false
|
||||
let fileIdx = registerNifSuffix(g.config, suffix.string, isKnown2)
|
||||
let precomp = moduleFromNifFile(g, fileIdx, {})
|
||||
if precomp.module != nil:
|
||||
modules.add precomp
|
||||
for dep in precomp.deps: stack.add dep
|
||||
flushMethodReplays(g)
|
||||
for m in modules:
|
||||
discard setupNifBackendModule(g, m.module)
|
||||
if precompSys.module != nil:
|
||||
discard setupNifBackendModule(g, precompSys.module)
|
||||
result = (modules, precompSys, target)
|
||||
|
||||
proc findTargetModule(g: ModuleGraph; modules: seq[PrecompiledModule];
|
||||
precompSys: PrecompiledModule; suffix: string): PrecompiledModule =
|
||||
## The loaded module whose NIF suffix is `suffix` (the `--icBackendModule`
|
||||
## value), or a nil module if none matches.
|
||||
result = PrecompiledModule(module: nil)
|
||||
for m in modules:
|
||||
if cachedModuleSuffix(g.config, FileIndex m.module.position) == suffix:
|
||||
return m
|
||||
if precompSys.module != nil and
|
||||
cachedModuleSuffix(g.config, FileIndex precompSys.module.position) == suffix:
|
||||
return precompSys
|
||||
|
||||
proc setNestedClosureBodies(g: ModuleGraph; idgen: IdGenerator; n: PNode;
|
||||
owner: PSym; seen: var IntSet) =
|
||||
## A closure routine nested in `owner` (the `:anonymous` proc lambda-lifting
|
||||
## minted, plus any deeper nesting) gets its captured-var→env rewrite produced
|
||||
## as part of the OWNER's `transformBody`. The nested proc is a module-indexed
|
||||
## sym whose `.s.nif` sdef carries its PRE-lift body, so without help the whole
|
||||
## module re-serializer would write that pre-lift body and cg would lose the
|
||||
## capture mapping (it accesses `x` directly instead of `ClE_0->x0`). Walk the
|
||||
## owner's transformed body and cache each nested closure's transformed body on
|
||||
## its sym so `writeSymDef` serializes the lifted body into the routine's
|
||||
## 2-way-body slot.
|
||||
if n == nil: return
|
||||
if n.kind == nkSym:
|
||||
let s = n.sym
|
||||
if s != nil and s.kind in routineKinds and s != owner and
|
||||
s.skipGenericOwner != nil and s.skipGenericOwner.kind != skModule and
|
||||
not seen.containsOrIncl(s.id):
|
||||
# Covers ALL nested routines, not only ccClosure ones. A NIMCALL nested proc
|
||||
# the async transform mints (e.g. workNimAsyncContinue) already has its
|
||||
# lifted body set by the OWNER's transformBody, but it is NOT in the owned
|
||||
# loop (owner is a proc, not the module). Without injecting it HERE it is
|
||||
# serialized transform-only; cg loads it (wasLoaded) and skips injection, so
|
||||
# a closure-env store stays a raw field assign with no incref -> the env is
|
||||
# freed before the async callback runs -> "yielded nil". `seen` (shared
|
||||
# across the owned loop) injects each routine exactly once.
|
||||
if s.ast != nil and getBody(g, s).kind != nkEmpty:
|
||||
# Only ccClosure routines are safe to `transformBody` standalone here; a
|
||||
# nimcall nested proc already has its lifted body from the owner's lift,
|
||||
# and transforming an arbitrary nested routine with no cached body crashes
|
||||
# (not in a standalone-transformable state).
|
||||
let weTransformed = s.transformedBody == nil and
|
||||
s.typ != nil and s.typ.callConv == ccClosure
|
||||
if weTransformed:
|
||||
s.transformedBody = transformBody(g, idgen, s, {})
|
||||
if s.transformedBody != nil:
|
||||
# Inject destructors so cg loads a fully-lowered body and never rebuilds
|
||||
# (mirrors non-IC, which injects every nested proc separately). The
|
||||
# importer `n2` skField collision this used to trigger is fixed at the
|
||||
# NIF-naming layer (toNifSymName gives derived env fields a unique
|
||||
# disamb), so injecting ccClosure nested procs here is safe.
|
||||
if sfInjectDestructors in s.flags:
|
||||
s.transformedBody = injectDestructorCalls(g, idgen, s, s.transformedBody)
|
||||
setNestedClosureBodies(g, idgen, s.transformedBody, s, seen)
|
||||
else:
|
||||
for i in 0 ..< n.safeLen:
|
||||
setNestedClosureBodies(g, idgen, n[i], owner, seen)
|
||||
|
||||
proc reownFromTwin(n: PNode; twin, s: PSym) =
|
||||
## Re-own to `s` every entity the frontend attributed to `s`'s forward-decl
|
||||
## `twin` (found via the result's owner). lambda-lifting compares owners by
|
||||
## reference, so a twin-owned `result` is rejected as `illegalCapture`
|
||||
## ("'result' ... cannot be captured") and, once that is fixed, twin-owned
|
||||
## locals go missing from `s`'s env ("environment misses: ..."). Both are
|
||||
## pervasive on chronos `{.async.}` methods. Re-owning to `s` matches the
|
||||
## single-sym non-IC case. `twin` is ONE specific sym, so only THIS routine's
|
||||
## result-twin-owned entities match — re-owning entities of OTHER same-name
|
||||
## twins proved too blunt (it disrupts env construction and reintroduces the
|
||||
## very capture errors it should fix). `n.sym != s` guards self-ownership.
|
||||
if n == nil: return
|
||||
if n.kind == nkSym and n.sym != nil and n.sym != s and n.sym.owner == twin:
|
||||
setOwner(n.sym, s)
|
||||
for i in 0 ..< n.safeLen:
|
||||
reownFromTwin(n[i], twin, s)
|
||||
|
||||
proc generateLowerStage(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Per-module backend lowering (`--icBackendStage:lower --icBackendModule:<suffix>`):
|
||||
## enumerate the routines this module OWNS and write them to `<module>.t.nif`.
|
||||
## Eventually this transforms each owned routine once, in the owner's id space,
|
||||
## so `cg` reads the result instead of re-deriving it (re-derivation per
|
||||
## parallel `cg` process is the root of the closure-`:env` identity drift).
|
||||
## Runs per module in parallel on the shallow backend dep-graph — NOT folded
|
||||
## into the dense, mostly-serial sem stage.
|
||||
##
|
||||
## gate `newSymNode`'s lazy-type marking to the backend (see astdef) — the
|
||||
## transform builds sym nodes off not-yet-typed stubs, exactly as the `cg`
|
||||
## stage does.
|
||||
nifcBackendActive = true
|
||||
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
|
||||
let targetIsMain = g.config.icBackendModule.len == 0 or
|
||||
g.config.icBackendModule == mainSuffix
|
||||
var modules: seq[PrecompiledModule]
|
||||
var precompSys: PrecompiledModule
|
||||
var target: PrecompiledModule
|
||||
if targetIsMain:
|
||||
var nifFiles: seq[string]
|
||||
(modules, precompSys, nifFiles) = loadBackendModules(g, mainFileIdx)
|
||||
if modules.len == 0:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
|
||||
return
|
||||
target = findTargetModule(g, modules, precompSys, g.config.icBackendModule)
|
||||
else:
|
||||
(modules, precompSys, target) = loadDepClosure(g, g.config.icBackendModule)
|
||||
if target.module == nil:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module lowering: module not found for suffix: " & g.config.icBackendModule)
|
||||
return
|
||||
let modPos = target.module.position
|
||||
let tb = BModuleList(g.backend).mods[modPos]
|
||||
if tb == nil:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module lowering: no backend module for suffix: " & g.config.icBackendModule)
|
||||
return
|
||||
# Transform every owned routine ONCE in this single process's id space and
|
||||
# re-serialize the ENTIRE module as a proper indexed NIF (`writeLoweredModule`)
|
||||
# with the transformed bodies baked into the routine `(sd)` entries. `cg` loads
|
||||
# it through the normal module loader, so nested procs (incl. async state
|
||||
# machines) arrive as real defs with their lifted bodies — no re-derivation.
|
||||
# This single-writer-per-owner is what keeps closure-`:env` identity stable
|
||||
# across the parallel `cg` processes (re-derivation per process was the root of
|
||||
# the `:env` identity drift). `transformBody` with flags {} mirrors the cg call
|
||||
# (cgen.nim); `injectDestructorCalls` is NOT run here — it stays in `cg` on the
|
||||
# loaded body.
|
||||
#
|
||||
# `transformBody`/lambda-lifting LIFTS the closure env's type-bound ops
|
||||
# (`=destroy` etc.) into `g.opsLog`; snapshot its length so we serialize exactly
|
||||
# the ops THIS stage created (not those loaded from `.s.nif`).
|
||||
let opsLogStart = g.opsLog.len
|
||||
# Shared across the owned loop so a nested routine reachable from more than one
|
||||
# owner is transformed + destructor-injected EXACTLY once (double injection
|
||||
# would emit two `=destroy`/`=copy` runs).
|
||||
var seenNested = initIntSet()
|
||||
for s in moduleSymbolStubs(ast.program, FileIndex modPos):
|
||||
if ownsRuntimeRoutine(s, modPos):
|
||||
# REUSE path (`icReuseSemLowering` ON): a routine already transformed during
|
||||
# sem (CT eval / macro / VM transform) carries its lowered body in the
|
||||
# `.s.nif` slot (loaded into `transformedBody`) — don't re-transform it.
|
||||
# Default OFF: the slot is never loaded (see loadSymFromCursor), so
|
||||
# `transformedBody` is nil here and we always re-derive below. See
|
||||
# doc/ic_backend_simplify.md §6a/§6b.
|
||||
if icReuseSemLowering(g.config) and s.transformedBody != nil: continue
|
||||
# A routine serialized as a forward-decl + impl pair (writeSymDef's
|
||||
# "separate forward declaration and implementation") loads as TWO syms; the
|
||||
# impl `s` we transform here can carry body entities (`result`, locals,
|
||||
# nested routines) owned by its fwd-decl TWIN, not by `s`. lambda-lifting
|
||||
# compares owners by reference → `illegalCapture` rejects a twin-owned
|
||||
# `result` and the lifting pass can't find twin-owned locals in `s`'s env.
|
||||
# Pervasive on chronos `{.async.}` methods. Re-own them to `s`, matching the
|
||||
# single-sym non-IC case. Backend-only, so frontend effect/exception
|
||||
# inference is untouched.
|
||||
if s.ast != nil and s.ast.len > resultPos and
|
||||
s.ast[resultPos].kind == nkSym and s.ast[resultPos].sym.owner != s:
|
||||
reownFromTwin(s.ast, s.ast[resultPos].sym.owner, s)
|
||||
# Retain the transformed body on the sym so `writeSymDef` serializes it in
|
||||
# the routine's `(sd)` 2-way-body slot.
|
||||
s.transformedBody = transformBody(g, tb.idgen, s, {})
|
||||
# Run the destructor injection HERE so the `.t.bif` body is FULLY lowered:
|
||||
# `injectDestructorCalls` is demand-driven (it decides where destructors go
|
||||
# by move analysis) and LIFTS the type-bound ops it needs (e.g. a nested
|
||||
# closure env's `=destroy`) into `g.opsLog` — which the `hooks` collection
|
||||
# below then serializes. Done in `cg` instead, those ops were lifted per-cg
|
||||
# process, owned by nobody, and emitted as a prototype-only → undefined at
|
||||
# link (the `eqdestroy__c<n>` gap). cg must NOT re-inject a loaded body
|
||||
# (see genProcLvl3's `wasLoaded` gate) so this stays the single injection.
|
||||
if sfInjectDestructors in s.flags:
|
||||
s.transformedBody = injectDestructorCalls(g, tb.idgen, s, s.transformedBody)
|
||||
# Cache the lifted+injected body on nested ccClosure routines too, so a
|
||||
# module-indexed nested closure serializes its lifted (capture-rewritten,
|
||||
# destructor-injected) body.
|
||||
setNestedClosureBodies(g, tb.idgen, s.transformedBody, s, seenNested)
|
||||
# Collect the hooks this stage lifted, and transform each hook ROUTINE's body
|
||||
# too (it is itself lowered into NIFC). The hooks' `(sd)` + transformed body go
|
||||
# into the `.t.nif`; `cg` re-attaches them so `injectDestructorCalls` resolves
|
||||
# the loaded env's `=destroy`. Iterate to a fixpoint: a hook body can lift
|
||||
# further hooks (a field's `=destroy`).
|
||||
var hooks: seq[LogEntry] = @[]
|
||||
var i = opsLogStart
|
||||
while i < g.opsLog.len:
|
||||
let e = g.opsLog[i]
|
||||
if e.kind == HookEntry and e.sym != nil and e.sym.kind in routineKinds and
|
||||
e.sym.transformedBody == nil:
|
||||
hooks.add e
|
||||
# Transform the hook routine's body and cache it on the sym so `writeSymDef`
|
||||
# serializes it in the hook's `(sd)` transformed-body slot (`transformBody
|
||||
# {}` returns the body but does not cache it). Inject the hook's own
|
||||
# destructors here too (it can destroy fields/temporaries) so cg loads a
|
||||
# fully-lowered hook and never re-injects.
|
||||
e.sym.transformedBody = transformBody(g, tb.idgen, e.sym, {})
|
||||
if sfInjectDestructors in e.sym.flags:
|
||||
e.sym.transformedBody = injectDestructorCalls(g, tb.idgen, e.sym, e.sym.transformedBody)
|
||||
inc i
|
||||
# Re-serialize the whole module to its suffix-based `.t.nif` (the path
|
||||
# `toNifFilename` resolves for the cg/emit stages). `writeLoweredModule` seals
|
||||
# routines itself.
|
||||
let suffix = cachedModuleSuffix(g.config, FileIndex modPos)
|
||||
let wholeArtifact = toGeneratedFile(g.config, AbsoluteFile(suffix), ".t.bif").string
|
||||
writeLoweredModule(ast.program, g.config, target, hooks, wholeArtifact)
|
||||
if isDefined(g.config, "icDceCheck"):
|
||||
stderr.writeLine "[icLower] " & extractFilename(wholeArtifact) & " " &
|
||||
$hooks.len & " hooks"
|
||||
|
||||
proc visitDep(suffix: string;
|
||||
suffixToMod: Table[string, PrecompiledModule];
|
||||
visited: var HashSet[string]; bl: BModuleList;
|
||||
ordered: var seq[BModule]) =
|
||||
## Post-order DFS over a module's import closure used to reconstruct the
|
||||
## dependency (init) order: a dependency's init must be registered before its
|
||||
## importer's. Appends each reachable non-main module's `BModule` to `ordered`.
|
||||
if visited.containsOrIncl(suffix): return
|
||||
let pm = suffixToMod.getOrDefault(suffix)
|
||||
if pm.module == nil: return
|
||||
for dep in pm.deps: # dependencies first (post-order)
|
||||
visitDep(dep.string, suffixToMod, visited, bl, ordered)
|
||||
if sfMainModule notin pm.module.flags:
|
||||
let bm = bl.mods[pm.module.position]
|
||||
if bm != nil: ordered.add bm
|
||||
|
||||
proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Per-module backend codegen (`--icBackendStage:cg --icBackendModule:<suffix>`):
|
||||
## generate C for the single module named by `icBackendModule` and write only
|
||||
## its `.c.nif` artifact (no merge, no `.c` render, no cc/link — those are
|
||||
## separate nifmake rules).
|
||||
##
|
||||
## `findPendingModule` routes every demand into the target (emit-everywhere).
|
||||
##
|
||||
## A NON-main target loads only its own import closure (`loadDepClosure`); the
|
||||
## whole program is no longer pulled into every parallel `cg` process. The main
|
||||
## module still loads everything (`loadBackendModules`) because NimMain's init
|
||||
## list and the method dispatchers are whole-program; its `cg` runs essentially
|
||||
## alone (every other `.c.nif` precedes it), so it does not contend for memory.
|
||||
# gate `newSymNode`'s lazy-type marking to this stage only (see astdef)
|
||||
nifcBackendActive = true
|
||||
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
|
||||
let targetIsMain = g.config.icBackendModule.len == 0 or
|
||||
g.config.icBackendModule == mainSuffix
|
||||
var modules: seq[PrecompiledModule]
|
||||
var precompSys: PrecompiledModule
|
||||
var target: PrecompiledModule
|
||||
if targetIsMain:
|
||||
var nifFiles: seq[string]
|
||||
(modules, precompSys, nifFiles) = loadBackendModules(g, mainFileIdx)
|
||||
if modules.len == 0:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
|
||||
return
|
||||
# No whole-program DCE here: each module emits the routines it owns and the
|
||||
# MERGE stage recomputes the one program-wide live set across all `.c.nif`s.
|
||||
# Running a whole-program liveness pass over all ~260 NIFs in the main `cg`
|
||||
# would cost ~900 MB for a result the merge stage throws away.
|
||||
target = findTargetModule(g, modules, precompSys, g.config.icBackendModule)
|
||||
else:
|
||||
# No whole-program load, hence no whole-program DCE: the target emits its
|
||||
# full demanded closure and the merge stage drops what is globally dead.
|
||||
(modules, precompSys, target) = loadDepClosure(g, g.config.icBackendModule)
|
||||
if target.module == nil:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module codegen: module not found for suffix: " & g.config.icBackendModule)
|
||||
return
|
||||
|
||||
# The `lower` stage already wrote each module's transformed bodies + lifted
|
||||
# hooks into its `.t.nif`, which the loaders above read directly (toNifFilename
|
||||
# resolves the `.t.nif`); transformed bodies arrive via loadSymFromCursor and
|
||||
# lifted hooks via moduleFromNifFile's registerLoadedHooks. Nothing to apply.
|
||||
generateCodeForModule(g, target)
|
||||
let bl = BModuleList(g.backend)
|
||||
# The main module also owns the whole-program method dispatchers + NimMain.
|
||||
if sfMainModule in target.module.flags:
|
||||
emitMethodDispatchers(g)
|
||||
# NimMain (generated when the main module is finished) must call every other
|
||||
# module's init/datInit. Those translation units are produced by their own
|
||||
# `cg` processes, so the calls are registered here from each `.c.nif` meta
|
||||
# head — which is why the main module's `cg` runs last, after every other
|
||||
# `.c.nif` exists. Modules without init code (no `.c.nif`) register nothing.
|
||||
#
|
||||
# The registration order IS the runtime init order, and it must be the
|
||||
# DEPENDENCY (post-order) order: an imported module's init has to run before
|
||||
# its importer's. The whole-program backend gets this for free — it iterates
|
||||
# `modulesClosed`, built in module-FINISH order (a post-order DFS over
|
||||
# imports). Iterating `bl.mods` by position is WRONG: an importer gets a
|
||||
# LOWER position than the modules it imports (its file is registered before
|
||||
# its `import` statements are processed), so position order runs importers
|
||||
# before their dependencies. That left chronicles' `topics_registry` — whose
|
||||
# init sets `mainThreadId` — running AFTER a module that calls `registerTopic`
|
||||
# from its own init, tripping the `getThreadId() == mainThreadId` assert at
|
||||
# startup. So reconstruct the post-order DFS over the import closure here.
|
||||
#
|
||||
# NOTE: this is deliberately a SEPARATE traversal rather than reusing the
|
||||
# module LOAD order — the per-module backend's C emit is sensitive to load
|
||||
# order (it determines the main TU's header composition), so the loader must
|
||||
# keep its existing order and the init order is derived independently here.
|
||||
var suffixToMod = initTable[string, PrecompiledModule]()
|
||||
for pm in modules:
|
||||
if pm.module != nil:
|
||||
suffixToMod[cachedModuleSuffix(g.config, FileIndex pm.module.position)] = pm
|
||||
if precompSys.module != nil:
|
||||
suffixToMod[cachedModuleSuffix(g.config, FileIndex precompSys.module.position)] = precompSys
|
||||
var visited = initHashSet[string]()
|
||||
var ordered: seq[BModule] = @[]
|
||||
# System (and its include/import closure) must initialize FIRST: its init
|
||||
# runs `initGC()` (top-level code in `threadimpl`, included into system),
|
||||
# and every other module's init may allocate — an allocation before the GC
|
||||
# heap is set up triggers a collection over an uninitialized region and
|
||||
# crashes (e.g. nim-metrics' `newRegistry` in its init). System is the
|
||||
# IMPLICIT universal import and appears in no module's explicit `deps`, so a
|
||||
# DFS rooted at main never reaches it; seed the traversal from system first.
|
||||
if precompSys.module != nil:
|
||||
visitDep(cachedModuleSuffix(g.config, FileIndex precompSys.module.position),
|
||||
suffixToMod, visited, bl, ordered)
|
||||
# Then order the whole import closure rooted at the main module; main itself
|
||||
# is excluded above (its init body becomes NimMain).
|
||||
for pm in modules:
|
||||
if pm.module != nil and sfMainModule in pm.module.flags:
|
||||
visitDep(cachedModuleSuffix(g.config, FileIndex pm.module.position),
|
||||
suffixToMod, visited, bl, ordered)
|
||||
# Defensive: any loaded module not reachable from main's import closure
|
||||
# (demand-loaded system internals) keeps its init registered, appended last
|
||||
# — nothing imports it, so its relative order does not matter.
|
||||
for m in bl.mods:
|
||||
if m != nil and sfMainModule notin m.module.flags:
|
||||
let suffix = cachedModuleSuffix(g.config, FileIndex m.module.position)
|
||||
if not visited.containsOrIncl(suffix):
|
||||
ordered.add m
|
||||
for m in ordered:
|
||||
let heads = readCnifHeads(getCFile(m).string & ".nif")
|
||||
registerReusedModuleToMain(bl, m, heads.initRequired, heads.datInitRequired)
|
||||
let tb = bl.mods[target.module.position]
|
||||
if tb != nil:
|
||||
finishModule(g, tb)
|
||||
|
||||
# Writes only the target's `.c.nif` (every other loaded module's TU is empty,
|
||||
# so `cgenWriteModules` emits no artifact for it). cc/link are NOT run here.
|
||||
cgenWriteModules(g.backend, g.config)
|
||||
|
||||
# Always leave a `.c.nif` for the target, even when the module has no code
|
||||
# (a leaf library whose procs all emit into their users): the per-module
|
||||
# nifmake graph declares one `.c.nif` output per `cg` rule, so a missing one
|
||||
# would re-fire the rule forever. An empty artifact renders to an empty `.c`.
|
||||
if tb != nil:
|
||||
let artifact = getCFile(tb).string & ".nif"
|
||||
if not fileExists(artifact):
|
||||
writeCnifArtifact("", artifact,
|
||||
semmedNif = toNifFilename(g.config, FileIndex target.module.position),
|
||||
moduleBase = $getSomeNameForModule(tb))
|
||||
|
||||
proc generateMergeStage(g: ModuleGraph) =
|
||||
## Per-module backend merge (`--icBackendStage:merge`): a pure artifact
|
||||
## operation, no module graph loaded. Reads every `.c.nif` the `cg` stages
|
||||
## wrote, computes the global live set and — for each `'u'`-flagged unique
|
||||
## definition that several `cg` processes emitted (emit-everywhere) — the one
|
||||
## artifact allowed to embed its body, and writes the decision the `emit`
|
||||
## stages consume — the cross-process replacement for what used to be
|
||||
## in-process first-claimant/DCE coordination.
|
||||
let nimcache = getNimcacheDir(g.config).string
|
||||
var files: seq[string] = @[]
|
||||
for artifact in walkFiles(nimcache / "*.c.nif"):
|
||||
files.add artifact
|
||||
sort files
|
||||
let decision = computeMergeDecision(files)
|
||||
if decision.broken:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module backend merge: a .c.nif artifact is missing or unparsable")
|
||||
return
|
||||
writeMergeDecision(nimcache / MergeDecisionFile, decision)
|
||||
if isDefined(g.config, "icDceCheck"):
|
||||
stderr.writeLine "[icMerge] artifacts: " & $files.len &
|
||||
" live: " & $decision.live.len & " defs: " & $decision.defs &
|
||||
" liveDefs: " & $decision.liveDefs & " owned: " & $decision.owners.len
|
||||
|
||||
proc generateEmitStage(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Per-module backend emit (`--icBackendStage:emit --icBackendModule:<suffix>`):
|
||||
## render the target module's final `.c` from its `.c.nif` and the merge
|
||||
## decision. Loads the target the same way `cg` does so `getCFile` returns the
|
||||
## identical path `cg` wrote to (the main module's source-vs-suffix aliasing in
|
||||
## particular); no codegen runs. A non-main target loads only its own closure
|
||||
## (`loadDepClosure`) so emit, like `cg`, stays bounded under parallel fan-out.
|
||||
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
|
||||
let targetIsMain = g.config.icBackendModule.len == 0 or
|
||||
g.config.icBackendModule == mainSuffix
|
||||
# emit renders a module's final `.c` PURELY from its own `.c.nif` and the merge
|
||||
# decision (see `renderCFromArtifact` — text filtering, no AST is touched). It
|
||||
# used to load the target's whole transitive import closure as BModules solely
|
||||
# to reach `getCFile(bmod)` for the output path. Under the fire-all-every-edit
|
||||
# merge barrier (every `emit` re-fires whenever `merge` bumps the decision's
|
||||
# mtime — deliberate insurance so a decision change re-renders all `.c`
|
||||
# consistently) that per-process `loadDepClosure` was the bulk of a warm
|
||||
# rebuild's cost: 240 processes each re-parsing a module closure only to filter
|
||||
# a handful of `.c.nif`s whose bytes are usually unchanged. Derive the `.c`
|
||||
# path directly instead — the SAME pure computation `deps.nim.backendCFile`
|
||||
# uses to DECLARE this stage's output (`getCFile` == that formula) — so an emit
|
||||
# process loads nothing and the fire-all costs process-startup, not a graph load.
|
||||
let cfilename =
|
||||
if targetIsMain: AbsoluteFile toFullPath(g.config, mainFileIdx)
|
||||
else: AbsoluteFile g.config.icBackendModule
|
||||
let cfile = changeFileExt(completeCfilePath(g.config,
|
||||
mangleModuleName(g.config, cfilename).AbsoluteFile), ".nim.c").string
|
||||
let artifact = cfile & ".nif"
|
||||
if not fileExists(artifact):
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module emit: missing .c.nif artifact for suffix: " & g.config.icBackendModule)
|
||||
return
|
||||
let decision = readMergeDecision(getNimcacheDir(g.config).string / MergeDecisionFile)
|
||||
if decision.broken:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"per-module emit: missing or unparsable merge decision " & MergeDecisionFile)
|
||||
return
|
||||
var dropped = 0
|
||||
let code = renderCFromArtifact(artifact, decision, extractFilename(artifact), dropped)
|
||||
# Write the `.c` content-stably. `merge` re-runs on any edit and bumps the
|
||||
# decision file's mtime, so nifmake re-fires every `emit` (the filter is cheap);
|
||||
# but the FILTERED output is usually byte-identical for modules unaffected by
|
||||
# the edit. Rewriting it unconditionally would bump every `.c`'s mtime and make
|
||||
# `callCCompiler` recompile every `.o`. Writing only on a real change preserves
|
||||
# the mtime, so the C compiler recompiles exactly the modules whose `.c` changed
|
||||
# — the same DCE model as Nimony's. Safe here (unlike a content-stable merge
|
||||
# decision): a `.c` is a per-module LEAF consumed only by the C compiler's own
|
||||
# up-to-date check, not a shared prerequisite in nifmake's mtime ordering.
|
||||
if not fileExists(cfile) or readFile(cfile) != code:
|
||||
writeFile(cfile, code)
|
||||
if isDefined(g.config, "icDceCheck"):
|
||||
stderr.writeLine "[icEmit] " & extractFilename(cfile) & " dropped " &
|
||||
$dropped & " bodies (" & $code.len & " bytes)"
|
||||
|
||||
proc generateLinkStage(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Per-module backend link (`--icBackendStage:link`): the `emit` stages have
|
||||
## written every module's `.c`; register them and run the C compiler + linker
|
||||
## once via `extccomp.callCCompiler` (which parallelizes the per-file cc and
|
||||
## skips up-to-date objects itself). No codegen runs — the graph is loaded only
|
||||
## so `getCFile` yields each module's emitted `.c` path.
|
||||
let (modules, precompSys, _) = loadBackendModules(g, mainFileIdx)
|
||||
if modules.len == 0:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
|
||||
return
|
||||
# The per-module `cg` processes each collect their module's C compile/link
|
||||
# directives (`{.passL: "-lm".}` etc.) via `replayBackendActions`, but those
|
||||
# live in the cg process and never reach this separate link process. Re-collect
|
||||
# every loaded module's directives here so the final `callCCompiler` sees them
|
||||
# (without this, math's `-lm` is lost → undefined `floor`/`pow`/… at link).
|
||||
for m in modules:
|
||||
replayBackendActions(g, m.module, m.topLevel)
|
||||
if precompSys.module != nil:
|
||||
replayBackendActions(g, precompSys.module, precompSys.topLevel)
|
||||
let bl = BModuleList(g.backend)
|
||||
var addedCFiles = initHashSet[string]()
|
||||
for m in bl.mods:
|
||||
if m != nil:
|
||||
let cfile = getCFile(m)
|
||||
# Only modules that are their own cg/emit target produced a `.c`; the rest
|
||||
# (extra members of system's closure that no build rule targets) had their
|
||||
# code emit-everywhere'd into the targets, so they have no file to compile.
|
||||
if not fileExists(cfile.string): continue
|
||||
addedCFiles.incl extractFilename(cfile.string)
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
obj: completeCfilePath(g.config, toObjFile(g.config, cfile)),
|
||||
flags: {})
|
||||
# `addExternalFileToCompile` (not `addFileToCompile`) gates each `.c` on its
|
||||
# SHA1 footprint: an unchanged `.c` keeps its `.o` and is flagged Cached, so
|
||||
# `callCCompiler` skips its compile but still links the existing object. This
|
||||
# is what makes a localized edit recompile only the handful of `.c`s the
|
||||
# `emit` stage actually rewrote, instead of every object every time — the
|
||||
# final piece of per-module backend incrementality after the merge barrier.
|
||||
addExternalFileToCompile(g.config, cf)
|
||||
# deps.nim's static scanner can keep a CONDITIONALLY-imported module as a build
|
||||
# node (e.g. `net`'s `when defineSsl: import openssl`, or a `when defined(os)`
|
||||
# import) that the NIF-`deps` walk above never reaches because the condition is
|
||||
# off. Such a node still emitted a `.c`, and it can OWN a live generic instance
|
||||
# that a REACHABLE module reuses (openssl owns `toHex[uint8]`, reused by
|
||||
# `strutils.escape`) — so its body must be at link or that reference is
|
||||
# undefined. Link every emitted `.c` the merge decision says OWNS a LIVE symbol;
|
||||
# a node that owns nothing live (a Windows-only winsock node on Linux) is
|
||||
# correctly skipped.
|
||||
block:
|
||||
let nimcache = getNimcacheDir(g.config).string
|
||||
let decision = readMergeDecision(nimcache / MergeDecisionFile)
|
||||
if not decision.broken:
|
||||
var liveOwners = initHashSet[string]()
|
||||
for cname, owner in decision.owners:
|
||||
if owner.endsWith(".c.nif") and cname in decision.live:
|
||||
liveOwners.incl owner
|
||||
for owner in liveOwners:
|
||||
let cbase = owner[0 ..< owner.len - ".nif".len] # "@m….nim.c.nif" -> ".c"
|
||||
if addedCFiles.containsOrIncl(cbase): continue
|
||||
let cfile = AbsoluteFile(nimcache / cbase)
|
||||
if not fileExists(cfile.string): continue
|
||||
var cf = Cfile(nimname: cbase, cname: cfile,
|
||||
obj: completeCfilePath(g.config, toObjFile(g.config, cfile)),
|
||||
flags: {})
|
||||
addExternalFileToCompile(g.config, cf)
|
||||
if g.config.cmd != cmdTcc:
|
||||
extccomp.callCCompiler(g.config)
|
||||
|
||||
proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
## Main entry point for NIF-based C code generation.
|
||||
## Traverses the module dependency graph and generates C code.
|
||||
if g.config.icBackendStage == "lower":
|
||||
generateLowerStage(g, mainFileIdx)
|
||||
return
|
||||
elif g.config.icBackendStage == "cg":
|
||||
generateCgStage(g, mainFileIdx)
|
||||
return
|
||||
elif g.config.icBackendStage == "merge":
|
||||
generateMergeStage(g)
|
||||
return
|
||||
elif g.config.icBackendStage == "emit":
|
||||
generateEmitStage(g, mainFileIdx)
|
||||
return
|
||||
elif g.config.icBackendStage == "link":
|
||||
generateLinkStage(g, mainFileIdx)
|
||||
return
|
||||
else:
|
||||
rawMessage(g.config, errGenerated,
|
||||
"the per-module NIF backend requires --icBackendStage:lower|cg|merge|emit|link")
|
||||
@@ -16,7 +16,7 @@ import
|
||||
|
||||
import "../dist/nimony/src/lib" / nifbuilder
|
||||
import "../dist/nimony/src/models" / nifler_tags
|
||||
import icmodnames
|
||||
import "../dist/nimony/src/gear2" / modnames
|
||||
|
||||
## This was copied from Nifler's bridge.nim. However, this code will evolve
|
||||
## in a different direction as it needs to translate the semchecked AST which
|
||||
@@ -983,7 +983,7 @@ proc genericParamToNif(n: PNode; parent: PNode; c: var TranslationContext) =
|
||||
toNif n, parent, c
|
||||
|
||||
proc addExternName(sym: PSym; c: var TranslationContext) =
|
||||
if sym.loc.snippet != "":
|
||||
if sym.loc.snippet != nil:
|
||||
c.b.addStrLit sym.loc.snippet
|
||||
else:
|
||||
c.b.addStrLit sym.name.s
|
||||
|
||||
@@ -183,6 +183,12 @@ func `<`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
func `<=`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
a.int16 <= b.int16
|
||||
|
||||
func `>`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
a.int16 > b.int16
|
||||
|
||||
func `>=`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
a.int16 >= b.int16
|
||||
|
||||
func `==`*(a: ExprIndex, b: ExprIndex): bool =
|
||||
a.int16 == b.int16
|
||||
|
||||
@@ -913,7 +919,7 @@ proc infix(ctx: NilCheckerContext, l: PNode, r: PNode, magic: TMagic): PNode =
|
||||
newSymNode(op, r.info),
|
||||
l,
|
||||
r)
|
||||
result.typ = newType(tyBool, ctx.idgen, nil)
|
||||
result.typ() = newType(tyBool, ctx.idgen, nil)
|
||||
|
||||
proc prefixNot(ctx: NilCheckerContext, node: PNode): PNode =
|
||||
var cache = newIdentCache()
|
||||
@@ -923,7 +929,7 @@ proc prefixNot(ctx: NilCheckerContext, node: PNode): PNode =
|
||||
result = nkPrefix.newTree(
|
||||
newSymNode(op, node.info),
|
||||
node)
|
||||
result.typ = newType(tyBool, ctx.idgen, nil)
|
||||
result.typ() = newType(tyBool, ctx.idgen, nil)
|
||||
|
||||
proc infixEq(ctx: NilCheckerContext, l: PNode, r: PNode): PNode =
|
||||
infix(ctx, l, r, mEqRef)
|
||||
|
||||
@@ -12,12 +12,7 @@ define:nimPreviewNonVarDestructor
|
||||
define:nimPreviewCheckedClose
|
||||
define:nimPreviewAsmSemSymbol
|
||||
define:nimPreviewCStringComparisons
|
||||
#define:nimPreviewDuplicateModuleError
|
||||
# Incompatible with Nimony's compat2.nim for now
|
||||
# NOTE: `-d:virtualParRi` (jump-encoded ParLe + elided ParRi) is NOT yet enabled:
|
||||
# the IC writer assembles buffers by raw token splicing (`dest.add content[i]`),
|
||||
# which does not seal scopes the way `addParRi` does, so sealed `(stmts)` get
|
||||
# jump=0 and serialize empty. Enabling it needs writer buffer-sealing work first.
|
||||
define:nimPreviewDuplicateModuleError
|
||||
|
||||
threads:off
|
||||
|
||||
@@ -70,7 +65,3 @@ define:useStdoutAsStdmsg
|
||||
@if nimHasVtables:
|
||||
experimental:vtables
|
||||
@end
|
||||
|
||||
@if nimHasImplicitRangeConversion:
|
||||
warning[ImplicitRangeConversion]:off
|
||||
@end
|
||||
|
||||
@@ -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}:
|
||||
ensureIcConfig(conf)
|
||||
|
||||
var graph = newModuleGraph(cache, conf)
|
||||
if not self.loadConfigsAndProcessCmdLine(cache, conf, graph):
|
||||
return
|
||||
@@ -134,14 +118,9 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
if conf.selectedGC == gcUnselected:
|
||||
if conf.backend in {backendC, backendCpp, backendObjc} or
|
||||
(conf.cmd in cmdDocLike and conf.backend != backendJs) or
|
||||
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM, cmdTrack}:
|
||||
conf.cmd == cmdGendepend:
|
||||
initOrcDefines(conf)
|
||||
|
||||
if conf.selectedStrings == stringSso and
|
||||
conf.selectedGC notin {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
rawMessage(conf, errGenerated,
|
||||
"--strings:sso requires --mm:arc, --mm:orc, --mm:yrc, or --mm:atomicArc")
|
||||
|
||||
mainCommand(graph)
|
||||
if conf.hasHint(hintGCStats): echo(GC_getStatistics())
|
||||
#echo(GC_getStatistics())
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import
|
||||
llstream, commands, msgs, lexer, ast,
|
||||
options, idents, wordrecg, lineinfos, pathutils, scriptconfig, icconfig
|
||||
options, idents, wordrecg, lineinfos, pathutils, scriptconfig
|
||||
|
||||
import std/[os, strutils, strtabs]
|
||||
|
||||
@@ -207,6 +207,7 @@ proc parseAssignment(L: var Lexer, tok: var Token;
|
||||
checkSymbol(L, tok)
|
||||
val.add($tok)
|
||||
confTok(L, tok, config, condStack)
|
||||
config.currentConfigDir = parentDir(filename.string)
|
||||
if percent:
|
||||
processSwitch(s, strtabs.`%`(val, config.configVars,
|
||||
{useEnvironment, useEmpty}), passPP, info, config)
|
||||
@@ -246,20 +247,8 @@ proc getSystemConfigPath*(conf: ConfigRef; filename: RelativeFile): AbsoluteFile
|
||||
|
||||
proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef; idgen: IdGenerator) =
|
||||
setDefaultLibpath(conf)
|
||||
# The `nim ic` driver and its `nim m`/`nim nifc` children replay the precompiled
|
||||
# config (produced once by a separate `nim icconfig` process — see
|
||||
# `icconfig.ensureIcConfig`, which sets `icPreparsedConfig` for the driver
|
||||
# before this runs; the children get it as a forwarded `--icPreparsedConfig`
|
||||
# argument) instead of re-reading the `nim.cfg` chain and re-running
|
||||
# `config.nims` in the VM. A missing/format-incompatible artifact returns false:
|
||||
# fall through to a normal parse (this is also the path the `nim icconfig`
|
||||
# producer itself takes, since it runs with no `icPreparsedConfig`).
|
||||
if conf.icPreparsedConfig.len > 0 and applyIcConfig(conf, conf.icPreparsedConfig):
|
||||
return
|
||||
template readConfigFile(path) =
|
||||
let configPath = path
|
||||
conf.currentConfigDir = configPath.splitFile.dir.string
|
||||
setConfigVar(conf, "selfDir", conf.currentConfigDir)
|
||||
if readConfigFile(configPath, cache, conf):
|
||||
conf.configFiles.add(configPath)
|
||||
|
||||
@@ -316,7 +305,7 @@ proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef; idgen:
|
||||
if conf.cmd == cmdNimscript:
|
||||
showHintConf()
|
||||
conf.configFiles.setLen 0
|
||||
if not conf.ideActive and conf.cmd notin {cmdCheck, cmdDump}:
|
||||
if conf.cmd notin {cmdIdeTools, cmdCheck, cmdDump}:
|
||||
if conf.cmd == cmdNimscript:
|
||||
runNimScriptIfExists(conf.projectFull, isMain = true)
|
||||
else:
|
||||
|
||||
@@ -128,7 +128,7 @@ proc createInterpreter*(scriptName: string;
|
||||
if conf.libpath.isEmpty: conf.libpath = AbsoluteDir p
|
||||
|
||||
var m = graph.makeModule(scriptName)
|
||||
incl(m, sfMainModule)
|
||||
incl(m.flags, sfMainModule)
|
||||
var idgen = idGeneratorFromModule(m)
|
||||
var vm = newCtx(m, cache, graph, idgen)
|
||||
vm.mode = emRepl
|
||||
@@ -168,7 +168,7 @@ proc runRepl*(r: TLLRepl;
|
||||
if supportNimscript: defineSymbol(conf.symbols, "nimconfig")
|
||||
when hasFFI: defineSymbol(graph.config.symbols, "nimffi")
|
||||
var m = graph.makeStdinModule()
|
||||
incl(m, sfMainModule)
|
||||
incl(m.flags, sfMainModule)
|
||||
var idgen = idGeneratorFromModule(m)
|
||||
|
||||
if supportNimscript: graph.vm = setupVM(m, cache, "stdin", graph, idgen)
|
||||
|
||||
@@ -84,7 +84,7 @@ proc toTreeSet*(conf: ConfigRef; s: TBitSet, settype: PType, info: TLineInfo): P
|
||||
elemType = settype[0]
|
||||
first = firstOrd(conf, elemType).toInt64
|
||||
result = newNodeI(nkCurly, info)
|
||||
result.typ = settype
|
||||
result.typ() = settype
|
||||
result.info = info
|
||||
e = 0
|
||||
while e < s.len * ElemSize:
|
||||
@@ -101,7 +101,7 @@ proc toTreeSet*(conf: ConfigRef; s: TBitSet, settype: PType, info: TLineInfo): P
|
||||
result.add aa
|
||||
else:
|
||||
n = newNodeI(nkRange, info)
|
||||
n.typ = elemType
|
||||
n.typ() = elemType
|
||||
n.add aa
|
||||
let bb = newIntTypeNode(b + first, elemType)
|
||||
bb.info = info
|
||||
|
||||
@@ -25,36 +25,10 @@ const
|
||||
useEffectSystem* = true
|
||||
useWriteTracking* = false
|
||||
hasFFI* = defined(nimHasLibFFI)
|
||||
copyrightYear* = "2026"
|
||||
copyrightYear* = "2025"
|
||||
|
||||
nimEnableCovariance* = defined(nimEnableCovariance)
|
||||
|
||||
icFormatVersion* = "30"
|
||||
## Version of the IC cache format (the sem-NIF module layout written by
|
||||
## ast2nif.nim plus the iface/impl/edges side files). Bump it whenever
|
||||
## that layout changes: `commandIc` wipes a nimcache whose `ic.version`
|
||||
## stamp differs, instead of letting a newer reader mis-parse records
|
||||
## written by an older compiler (nifmake's rebuild check is mtime-only
|
||||
## and knows nothing about format changes).
|
||||
## v2: iface cookie hashes routine SIGNATURES only (no inline-semantics
|
||||
## body folding); body access now records a NeedsImpl edge instead. A v1
|
||||
## cache mixes body-sensitive and body-insensitive cookies, so it must be
|
||||
## wiped rather than warm-rebuilt.
|
||||
## v3: added the `.s.deps` sidecar (real post-sem imports) and switched the
|
||||
## macro-generated-import discovery from `icmissing.txt` to it.
|
||||
## v4: backend C-name scheme change — the module suffix is now the trailing
|
||||
## token (`name_u<disamb>__<suffix>`, was `name__<suffix>_u<disamb>`), so
|
||||
## cached `.c.nif` artifacts hold incompatible names and must be wiped.
|
||||
## v5: data definitions (consts, RTTI) are now wrapped in droppable `'d'`
|
||||
## cdef directives with an always-present extern declaration, so the
|
||||
## per-module merge stage can assign them a single owner; old `.c.nif`
|
||||
## artifacts lack the wrappers.
|
||||
## v6: `signatureHash`/`hashType` of a builtin type class (`object`, `tuple`,
|
||||
## `proc`, ...) no longer mixes in the placeholder son's process-local type
|
||||
## id, so its hash is stable across the NIF boundary (was breaking
|
||||
## nim-serialization's auto-serialization lookup under IC). The sem-NIF
|
||||
## macrocache entries and baked generic-instance bodies hold the old hashes.
|
||||
|
||||
type # please make sure we have under 32 options
|
||||
# (improves code efficiency a lot!)
|
||||
TOption* = enum # **keep binary compatible**
|
||||
@@ -94,7 +68,6 @@ type # please make sure we have under 32 options
|
||||
optUseNimcache, # save artifacts (including binary) in $nimcache
|
||||
optStyleHint, # check that the names adhere to NEP-1
|
||||
optStyleError, # enforce that the names adhere to NEP-1
|
||||
optStyleWarning, # emit style checks as warnings
|
||||
optStyleUsages, # only enforce consistent **usages** of the symbol
|
||||
optSkipSystemConfigFile, # skip the system's cfg/nims config file
|
||||
optSkipProjConfigFile, # skip the project's cfg/nims config file
|
||||
@@ -137,11 +110,6 @@ type # please make sure we have under 32 options
|
||||
optEnableDeepCopy # ORC specific: enable 'deepcopy' for all types.
|
||||
optShowNonExportedFields # for documentation: show fields that are not exported
|
||||
optJsBigInt64 # use bigints for 64-bit integers in JS
|
||||
optDocRaw # for documentation: Don't render markdown for JSON output
|
||||
optItaniumMangle # mangling follows the Itanium spec
|
||||
optCompress # turn on AST compression by converting it to NIF
|
||||
optGenBif # generate semantic BIF alongside ordinary code generation
|
||||
optWithinConfigSystem # we still compile within the configuration system
|
||||
|
||||
TGlobalOptions* = set[TGlobalOption]
|
||||
|
||||
@@ -184,6 +152,8 @@ type
|
||||
cmdCheck # semantic checking for whole project
|
||||
cmdM # only compile a single
|
||||
cmdParse # parse a single file (for debugging)
|
||||
cmdRod # .rod to some text representation (for debugging)
|
||||
cmdIdeTools # ide tools (e.g. nimsuggest)
|
||||
cmdNimscript # evaluate nimscript
|
||||
cmdDoc0
|
||||
cmdDoc # convert .nim doc comments to HTML
|
||||
@@ -203,10 +173,6 @@ type
|
||||
cmdJsonscript # compile a .json build file
|
||||
# old unused: cmdInterpret, cmdDef: def feature (find definition for IDEs)
|
||||
cmdCompileToNif
|
||||
cmdNifC # generate C code from NIF files
|
||||
cmdIc # generate .build.nif for nifmake
|
||||
cmdIcConfig # `nim ic`'s precompiled-config producer (writes ic_config.cfg.nif)
|
||||
cmdTrack # `nim track --def/--usages`: IC frontend build + NIF scan for IDE queries
|
||||
|
||||
const
|
||||
cmdBackends* = {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
|
||||
@@ -223,7 +189,6 @@ type
|
||||
gcRegions = "regions"
|
||||
gcArc = "arc"
|
||||
gcOrc = "orc"
|
||||
gcYrc = "yrc" # thread-safe ORC (concurrent cycle collector)
|
||||
gcAtomicArc = "atomicArc"
|
||||
gcMarkAndSweep = "markAndSweep"
|
||||
gcHooks = "hooks"
|
||||
@@ -287,14 +252,6 @@ type
|
||||
## Old transformation for closures in JS backend
|
||||
noPanicOnExcept
|
||||
## don't panic on bare except
|
||||
procParamTypeBackendAliases
|
||||
## Keep the old proc type compatibility rules that ignore backend
|
||||
## c type aliases.
|
||||
injectedSymbolRedefinition
|
||||
## Allow a template to inject a symbol *definition* that is then emitted
|
||||
## more than once (e.g. a `typed` argument captured by a `{.dirty.}`
|
||||
## template and re-emitted). This is a redefinition and rejected by
|
||||
## default; enabling this restores the old, unsound behavior. See #25693.
|
||||
|
||||
SymbolFilesOption* = enum
|
||||
disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest
|
||||
@@ -303,10 +260,6 @@ type
|
||||
ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccBcc, ccVcc,
|
||||
ccTcc, ccEnv, ccIcl, ccIcc, ccClangCl, ccHipcc, ccNvcc
|
||||
|
||||
StringsMode* = enum
|
||||
stringDefault = "default"
|
||||
stringSso = "sso"
|
||||
|
||||
ExceptionSystem* = enum
|
||||
excNone, # no exception system selected yet
|
||||
excSetjmp, # setjmp based exception handling
|
||||
@@ -401,67 +354,16 @@ type
|
||||
evalMacroCounter*: int
|
||||
exitcode*: int8
|
||||
cmd*: Command # raw command parsed as enum
|
||||
ideActive*: bool # serving IDE tooling (nimsuggest): collect suggestions and
|
||||
# keep going after errors. Decoupled from `cmd` so the IDE
|
||||
# server can run under any compilation mode (cmdCheck, cmdM).
|
||||
ideImportsFromNif*: bool # nimsuggest: load the unchanged import closure from
|
||||
# precompiled NIF (run under cmdM) instead of recompiling it
|
||||
# from source (cmdCheck). IC is opt-in: default off (cmdCheck);
|
||||
# `--ideImports:nif` opts in.
|
||||
cmdInput*: string # input command
|
||||
projectIsCmd*: bool # whether we're compiling from a command input
|
||||
implicitCmd*: bool # whether some flag triggered an implicit `command`
|
||||
selectedGC*: TGCMode # the selected GC (+)
|
||||
exc*: ExceptionSystem
|
||||
selectedStrings*: StringsMode
|
||||
hintProcessingDots*: bool # true for dots, false for filenames
|
||||
verbosity*: int # how verbose the compiler is
|
||||
numberOfProcessors*: int # number of processors
|
||||
lastCmdTime*: float # when caas is enabled, we measure each command
|
||||
symbolFiles*: SymbolFilesOption
|
||||
ic*: bool # whether ic is enabled
|
||||
icGroup*: HashSet[string] # under `nim m`: absolute paths of the modules in
|
||||
# this strongly-connected import group. They are all
|
||||
# compiled from source in one process (so mutual
|
||||
# recursion resolves in-memory) and each gets its NIF
|
||||
# written, instead of being loaded from a precompiled
|
||||
# NIF. See `compiler/deps.nim` (SCC grouping).
|
||||
icProject*: string # under `nim m`/`nim nifc`: absolute path of the
|
||||
# ORIGINAL project file. The child's own project file
|
||||
# is the module being compiled, which would make that
|
||||
# module's package the "main package" and unfilter
|
||||
# foreign-package diagnostics; the real project
|
||||
# restores whole-program filtering semantics.
|
||||
icPreparsedConfig*: string # under the `nim ic` driver and its `nim m`/`nim nifc`
|
||||
# children: path of the precompiled config artifact.
|
||||
# When set, `loadConfigs` replays the recorded
|
||||
# config-file switches from it instead of re-reading
|
||||
# the `nim.cfg` chain and re-running `config.nims`
|
||||
# (which the VM makes expensive) per process. The
|
||||
# artifact itself is produced by a separate
|
||||
# `nim icconfig` process (see `cmdIcConfig`).
|
||||
icConfigOut*: string # under `nim icconfig`: the path to write the
|
||||
# precompiled config artifact to (set via `--o`).
|
||||
icConfigSwitches*: seq[tuple[switch, arg: string]]
|
||||
# the config-file (`passPP`) switches applied while
|
||||
# loading config, in order. Recorded by every nim
|
||||
# process; only the `ic` driver serialises them.
|
||||
# Path-search switches are excluded — the driver
|
||||
# forwards the resolved `searchPaths` as `--path`.
|
||||
icBackendStage*: string # under `nim nifc`: which stage of the per-module
|
||||
# backend this invocation runs — "cg" (codegen one
|
||||
# module to its `.c.nif`), "merge" (global liveness
|
||||
# + owner assignment across all `.c.nif`), "emit"
|
||||
# (render one module's `.c` from its `.c.nif` + the
|
||||
# merge decision), "link" (cc + link every emitted
|
||||
# `.c`). Empty = whole-program backend (load all,
|
||||
# codegen+DCE+cc+link in one process). The stages
|
||||
# are wired as nifmake rules by `deps.nim`'s backend
|
||||
# build file. See `compiler/nifbackend.nim`.
|
||||
icBackendModule*: string # under `nim nifc` with icBackendStage in {cg,emit}:
|
||||
# the NIF module suffix this invocation codegens or
|
||||
# emits. The other modules are loaded only so types
|
||||
# resolve; their definitions are referenced extern.
|
||||
spellSuggestMax*: int # max number of spelling suggestions for typos
|
||||
|
||||
cppDefines*: HashSet[string] # (*)
|
||||
@@ -508,12 +410,6 @@ type
|
||||
lastMsgWasDot*: set[StdOrrKind] # the last compiler message was a single '.'
|
||||
projectMainIdx*: FileIndex # the canonical path id of the main module
|
||||
projectMainIdx2*: FileIndex # consider merging with projectMainIdx
|
||||
isMainModule*: bool # `nim m`/IC only: whether the single module being
|
||||
# semantically checked is the program's real entry point.
|
||||
# Under IC every module is compiled via `nim m` (which sets
|
||||
# `sfMainModule` so the module writes its own NIF), so
|
||||
# `sfMainModule` can no longer answer `isMainModule`. The IC
|
||||
# build file passes `--isMainModule:on` for the root module.
|
||||
command*: string # the main command (e.g. cc, check, scan, etc)
|
||||
commandArgs*: seq[string] # any arguments after the main command
|
||||
commandLine*: string
|
||||
@@ -612,7 +508,7 @@ const
|
||||
optHints, optStackTrace, optLineTrace, # consider adding `optStackTraceMsgs`
|
||||
optTrMacros, optStyleCheck, optCursorInference}
|
||||
DefaultGlobalOptions* = {optThreadAnalysis, optExcessiveStackTrace,
|
||||
optJsBigInt64, optItaniumMangle}
|
||||
optJsBigInt64}
|
||||
|
||||
proc getSrcTimestamp(): DateTime =
|
||||
try:
|
||||
@@ -670,7 +566,6 @@ proc newConfigRef*(): ConfigRef =
|
||||
arcToExpand: newStringTable(modeStyleInsensitive),
|
||||
m: initMsgConfig(),
|
||||
cppDefines: initHashSet[string](),
|
||||
icGroup: initHashSet[string](),
|
||||
headerFile: "", features: {}, legacyFeatures: {},
|
||||
configVars: newStringTable(modeStyleInsensitive),
|
||||
symbols: newStringTable(modeStyleInsensitive),
|
||||
@@ -693,7 +588,6 @@ proc newConfigRef*(): ConfigRef =
|
||||
command: "", # the main command (e.g. cc, check, scan, etc)
|
||||
commandArgs: @[], # any arguments after the main command
|
||||
commandLine: "",
|
||||
ideImportsFromNif: false, # IC opt-in; see `--ideImports`
|
||||
implicitImports: @[], # modules that are to be implicitly imported
|
||||
implicitIncludes: @[], # modules that are to be implicitly included
|
||||
docSeeSrcUrl: "",
|
||||
@@ -747,7 +641,6 @@ proc isDefined*(conf: ConfigRef; symbol: string): bool =
|
||||
of "x86": result = conf.target.targetCPU == cpuI386
|
||||
of "itanium": result = conf.target.targetCPU == cpuIa64
|
||||
of "x8664": result = conf.target.targetCPU == cpuAmd64
|
||||
of "wasm": result = conf.target.targetCPU in {cpuWasm32, cpuWasm64}
|
||||
of "posix", "unix":
|
||||
result = conf.target.targetOS in {osLinux, osMorphos, osSkyos, osIrix, osPalmos,
|
||||
osQnx, osAtari, osAix,
|
||||
@@ -795,20 +688,8 @@ template quitOrRaise*(conf: ConfigRef, msg = "") =
|
||||
else:
|
||||
quit(msg) # quits with QuitFailure
|
||||
|
||||
proc icReuseSemLowering*(conf: ConfigRef): bool {.inline.} =
|
||||
## When ON, the per-module `lower` backend stage REUSES the VM/CT lowering that
|
||||
## sem cached in the `.s.nif` 2-way-body slot (the non-IC single-lowering
|
||||
## semantics) instead of re-deriving the transform. Default OFF: the backend
|
||||
## re-derives every body from the pristine semchecked body (simpler; allowed by
|
||||
## the 2026-06-27 spec that VM-requested frontend transforms need not influence
|
||||
## the backend). The switch exists so caching can be restored if a target (e.g.
|
||||
## Nimbus) depends on the cached lowering being reused, not re-derived. See
|
||||
## doc/ic_backend_simplify.md §6b.
|
||||
isDefined(conf, "icReuseSemLowering")
|
||||
|
||||
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.ideActive or conf.cmd in cmdDocLike
|
||||
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in cmdDocLike + {cmdIdeTools}
|
||||
proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc
|
||||
proc usesSso*(conf: ConfigRef): bool {.inline.} = conf.selectedStrings == stringSso
|
||||
|
||||
template compilationCachePresent*(conf: ConfigRef): untyped =
|
||||
false
|
||||
@@ -932,8 +813,7 @@ proc getOsCacheDir(): string =
|
||||
|
||||
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"
|
||||
|
||||
@@ -1160,9 +1040,6 @@ proc isDynlibOverride*(conf: ConfigRef; lib: string): bool =
|
||||
proc showNonExportedFields*(conf: ConfigRef) =
|
||||
incl(conf.globalOptions, optShowNonExportedFields)
|
||||
|
||||
proc docRawOutput*(conf: ConfigRef) =
|
||||
incl(conf.globalOptions, optDocRaw)
|
||||
|
||||
proc expandDone*(conf: ConfigRef): bool =
|
||||
result = conf.ideCmd == ideExpand and conf.expandLevels == 0 and conf.expandProgress
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ proc getPackage*(conf: ConfigRef; cache: IdentCache; fileIdx: FileIndex): PSym =
|
||||
pkgIdent = getIdent(cache, pkgName)
|
||||
newSym(skPackage, pkgIdent, idGeneratorForPackage(int32(fileIdx)), nil, info)
|
||||
|
||||
proc getPackageSymbol*(sym: PSym): PSym =
|
||||
func getPackageSymbol*(sym: PSym): PSym =
|
||||
## Return the owning package symbol.
|
||||
assert sym != nil
|
||||
result = sym
|
||||
@@ -41,18 +41,18 @@ proc getPackageSymbol*(sym: PSym): PSym =
|
||||
result = result.owner
|
||||
assert result != nil, repr(sym.info)
|
||||
|
||||
proc getPackageId*(sym: PSym): int =
|
||||
func getPackageId*(sym: PSym): int =
|
||||
## Return the owning package ID.
|
||||
sym.getPackageSymbol.id
|
||||
|
||||
proc belongsToProjectPackage*(conf: ConfigRef, sym: PSym): bool =
|
||||
func belongsToProjectPackage*(conf: ConfigRef, sym: PSym): bool =
|
||||
## Return whether the symbol belongs to the project's package.
|
||||
##
|
||||
## See Also:
|
||||
## * `modulegraphs.belongsToStdlib`
|
||||
conf.mainPackageId == sym.getPackageId
|
||||
|
||||
proc belongsToProjectPackageMaybeNil*(conf: ConfigRef, sym: PSym): bool =
|
||||
func belongsToProjectPackageMaybeNil*(conf: ConfigRef, sym: PSym): bool =
|
||||
## Return whether the symbol belongs to the project's package.
|
||||
## Returns `false` if `sym` is nil.
|
||||
##
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -148,6 +148,11 @@ proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
|
||||
closeParser(p)
|
||||
if s.kind != llsStdIn: break
|
||||
closePasses(graph, a)
|
||||
if graph.config.backend notin {backendC, backendCpp, backendObjc}:
|
||||
# We only write rod files here if no C-like backend is active.
|
||||
# The C-like backends have been patched to support the IC mechanism.
|
||||
# They are responsible for closing the rod files. See `cbackend.nim`.
|
||||
closeRodFile(graph, module)
|
||||
result = true
|
||||
|
||||
proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags, fromModule: PSym = nil): PSym =
|
||||
@@ -163,12 +168,24 @@ proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags, fr
|
||||
elif graph.config.projectIsCmd: s = llStreamOpen(graph.config.cmdInput)
|
||||
discard processModule(graph, result, idGeneratorFromModule(result), s)
|
||||
if result == nil:
|
||||
result = newModule(graph, fileIdx)
|
||||
result.incl flags
|
||||
registerModule(graph, result)
|
||||
processModuleAux("import")
|
||||
var cachedModules: seq[FileIndex] = @[]
|
||||
result = moduleFromRodFile(graph, fileIdx, cachedModules)
|
||||
let filename = AbsoluteFile toFullPath(graph.config, fileIdx)
|
||||
if result == nil:
|
||||
result = newModule(graph, fileIdx)
|
||||
result.flags.incl flags
|
||||
registerModule(graph, result)
|
||||
processModuleAux("import")
|
||||
else:
|
||||
if sfSystemModule in flags:
|
||||
graph.systemModule = result
|
||||
partialInitModule(result, graph, fileIdx, filename)
|
||||
for m in cachedModules:
|
||||
registerModuleById(graph, m)
|
||||
replayStateChanges(graph.packed.pm[m.int].module, graph)
|
||||
replayGenericCacheInformation(graph, m.int)
|
||||
elif graph.isDirty(result):
|
||||
result.excl sfDirty
|
||||
result.flags.excl sfDirty
|
||||
# reset module fields:
|
||||
initStrTables(graph, result)
|
||||
result.ast = nil
|
||||
|
||||
@@ -3,11 +3,6 @@ import sem, cgen, modulegraphs, ast, llstream, parser, msgs,
|
||||
packages, syntaxes, depends, vm, pragmas, idents, lookups, wordrecg,
|
||||
liftdestructors, nifgen
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
import vmdef
|
||||
import ast2nif
|
||||
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
|
||||
|
||||
import pipelineutils
|
||||
|
||||
import ../dist/checksums/src/checksums/sha1
|
||||
@@ -15,7 +10,7 @@ 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
|
||||
|
||||
@@ -40,12 +35,7 @@ proc processPipeline(graph: ModuleGraph; semNode: PNode; bModule: PPassContext):
|
||||
of GenDependPass:
|
||||
result = addDotDependency(bModule, semNode)
|
||||
of SemPass:
|
||||
# Return the semantic node for cmdM (NIF generation needs it)
|
||||
# For regular check, we don't need the result
|
||||
if graph.config.cmd == cmdM:
|
||||
result = semNode
|
||||
else:
|
||||
result = graph.emptyNode
|
||||
result = graph.emptyNode
|
||||
of Docgen2Pass, Docgen2TexPass:
|
||||
when not defined(leanCompiler):
|
||||
result = processNode(bModule, semNode)
|
||||
@@ -62,8 +52,7 @@ proc processPipeline(graph: ModuleGraph; semNode: PNode; bModule: PPassContext):
|
||||
raiseAssert "use setPipeLinePass to set a proper PipelinePass"
|
||||
|
||||
proc processImplicitImports*(graph: ModuleGraph; implicits: seq[string], nodeKind: TNodeKind,
|
||||
m: PSym, ctx: PContext, bModule: PPassContext, idgen: IdGenerator;
|
||||
topLevelStmts: PNode) =
|
||||
m: PSym, ctx: PContext, bModule: PPassContext, idgen: IdGenerator) =
|
||||
# XXX fixme this should actually be relative to the config file!
|
||||
let relativeTo = toFullPath(graph.config, m.info)
|
||||
for module in items(implicits):
|
||||
@@ -75,13 +64,8 @@ proc processImplicitImports*(graph: ModuleGraph; implicits: seq[string], nodeKin
|
||||
importStmt.add str
|
||||
message(graph.config, importStmt.info, hintProcessingStmt, $idgen[])
|
||||
let semNode = semWithPContext(ctx, importStmt)
|
||||
if semNode == nil:
|
||||
if semNode == nil or processPipeline(graph, semNode, bModule) == nil:
|
||||
break
|
||||
let top = processPipeline(graph, semNode, bModule)
|
||||
if top == nil:
|
||||
break
|
||||
if topLevelStmts != nil:
|
||||
topLevelStmts.add top
|
||||
|
||||
proc prePass*(c: PContext; n: PNode) =
|
||||
for son in n:
|
||||
@@ -103,7 +87,7 @@ proc prePass*(c: PContext; n: PNode) =
|
||||
let feature = parseEnum[Feature](name.strVal)
|
||||
if feature == codeReordering:
|
||||
c.features.incl feature
|
||||
c.module.incl sfReorder
|
||||
c.module.flags.incl sfReorder
|
||||
except ValueError:
|
||||
discard
|
||||
else:
|
||||
@@ -166,12 +150,6 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
else:
|
||||
s = stream
|
||||
graph.interactive = stream.kind == llsStdIn
|
||||
var topLevelStmts =
|
||||
if {optCompress, optGenBif} * graph.config.globalOptions != {} or
|
||||
graph.config.cmd == cmdM:
|
||||
newNodeI(nkStmtList, module.info)
|
||||
else:
|
||||
nil
|
||||
while true:
|
||||
syntaxes.openParser(p, fileIdx, s, graph.cache, graph.config)
|
||||
|
||||
@@ -181,8 +159,8 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
# in ROD files. I think we should enable this feature only
|
||||
# for the interactive mode.
|
||||
if module.name.s != "nimscriptapi":
|
||||
processImplicitImports graph, graph.config.implicitImports, nkImportStmt, module, ctx, bModule, idgen, topLevelStmts
|
||||
processImplicitImports graph, graph.config.implicitIncludes, nkIncludeStmt, module, ctx, bModule, idgen, topLevelStmts
|
||||
processImplicitImports graph, graph.config.implicitImports, nkImportStmt, module, ctx, bModule, idgen
|
||||
processImplicitImports graph, graph.config.implicitIncludes, nkIncludeStmt, module, ctx, bModule, idgen
|
||||
|
||||
checkFirstLineIndentation(p)
|
||||
block processCode:
|
||||
@@ -203,9 +181,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
if graph.pipelinePass != EvalPass:
|
||||
message(graph.config, sl.info, hintProcessingStmt, $idgen[])
|
||||
var semNode = semWithPContext(ctx, sl)
|
||||
let top = processPipeline(graph, semNode, bModule)
|
||||
if top != nil and topLevelStmts != nil:
|
||||
topLevelStmts.add top
|
||||
discard processPipeline(graph, semNode, bModule)
|
||||
|
||||
closeParser(p)
|
||||
if s.kind != llsStdIn: break
|
||||
@@ -222,7 +198,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
if retTyp != nil:
|
||||
# TODO: properly semcheck the code of dispatcher?
|
||||
createTypeBoundOps(graph, ctx, retTyp, disp.ast.info, idgen)
|
||||
genProcLvl3(m, disp)
|
||||
genProcAux(m, disp)
|
||||
discard closePContext(graph, ctx, nil)
|
||||
of JSgenPass:
|
||||
when not defined(leanCompiler):
|
||||
@@ -242,173 +218,19 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
of NonePass:
|
||||
raiseAssert "use setPipeLinePass to set a proper PipelinePass"
|
||||
|
||||
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.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)))
|
||||
if shouldWriteNif and not graph.config.isDefined("nimscript"):
|
||||
topLevelStmts.add finalNode
|
||||
# Collect replay actions from both pragma computations and VM state diff
|
||||
var replayActions: seq[PNode] = @[]
|
||||
# Get pragma-recorded replay actions (compile, link, passC, passL, etc.)
|
||||
if graph.nifReplayActions.hasKey(module.position.int32):
|
||||
replayActions.add graph.nifReplayActions[module.position.int32]
|
||||
# Also get VM state diff (macro cache operations)
|
||||
if graph.vm != nil:
|
||||
for (m, n) in PCtx(graph.vm).vmstateDiff:
|
||||
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.uniqueId.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)
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog,
|
||||
replayActions, implDeps, reexportedModuleSyms(graph, module),
|
||||
genericOffers, typeOffers, resolvedImportDeps, firstUnusedId,
|
||||
expansions)
|
||||
# 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)
|
||||
|
||||
if graph.config.backend notin {backendC, backendCpp, backendObjc}:
|
||||
# We only write rod files here if no C-like backend is active.
|
||||
# The C-like backends have been patched to support the IC mechanism.
|
||||
# They are responsible for closing the rod files. See `cbackend.nim`.
|
||||
closeRodFile(graph, module)
|
||||
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:
|
||||
@@ -416,60 +238,8 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
elif graph.config.projectIsCmd: s = llStreamOpen(graph.config.cmdInput)
|
||||
discard processPipelineModule(graph, result, idGeneratorFromModule(result), s)
|
||||
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.
|
||||
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):
|
||||
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
|
||||
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)
|
||||
return result # Return early, don't process from source
|
||||
var cachedModules: seq[FileIndex] = @[]
|
||||
result = moduleFromRodFile(graph, fileIdx, cachedModules)
|
||||
let path = toFullPath(graph.config, fileIdx)
|
||||
let filename = AbsoluteFile path
|
||||
# it could be a stdinfile/cmdfile
|
||||
@@ -477,19 +247,26 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
graph.cachedFiles[path] = $secureHashFile(path)
|
||||
if result == nil:
|
||||
result = newModule(graph, fileIdx)
|
||||
result.incl flags
|
||||
result.flags.incl flags
|
||||
registerModule(graph, result)
|
||||
processModuleAux("import")
|
||||
else:
|
||||
if sfSystemModule in flags:
|
||||
graph.systemModule = result
|
||||
if sfMainModule in flags and graph.config.cmd == cmdM:
|
||||
result.incl flags
|
||||
result.flags.incl flags
|
||||
registerModule(graph, result)
|
||||
processModuleAux("import")
|
||||
partialInitModule(result, graph, fileIdx, filename)
|
||||
for m in cachedModules:
|
||||
registerModuleById(graph, m)
|
||||
if sfMainModule in flags and graph.config.cmd == cmdM:
|
||||
discard
|
||||
else:
|
||||
replayStateChanges(graph.packed.pm[m.int].module, graph)
|
||||
replayGenericCacheInformation(graph, m.int)
|
||||
elif graph.isDirty(result):
|
||||
result.excl sfDirty
|
||||
result.flags.excl sfDirty
|
||||
# reset module fields:
|
||||
initStrTables(graph, result)
|
||||
result.ast = nil
|
||||
@@ -517,12 +294,10 @@ proc connectPipelineCallbacks*(graph: ModuleGraph) =
|
||||
|
||||
proc compilePipelineSystemModule*(graph: ModuleGraph) =
|
||||
if graph.systemModule == nil:
|
||||
graph.withinSystem = true
|
||||
connectPipelineCallbacks(graph)
|
||||
graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
|
||||
graph.config.libpath / RelativeFile"system.nim")
|
||||
discard graph.compilePipelineModule(graph.config.m.systemFileIdx, {sfSystemModule})
|
||||
graph.withinSystem = false
|
||||
|
||||
proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx) =
|
||||
connectPipelineCallbacks(graph)
|
||||
@@ -534,51 +309,12 @@ 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
|
||||
|
||||
if projectFile == systemFileIdx:
|
||||
graph.withinSystem = true
|
||||
discard graph.compilePipelineModule(projectFile, {sfMainModule, sfSystemModule})
|
||||
graph.withinSystem = false
|
||||
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.
|
||||
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)
|
||||
discard graph.compilePipelineModule(projectFile, {sfMainModule})
|
||||
else:
|
||||
graph.compilePipelineSystemModule()
|
||||
discard graph.compilePipelineModule(projectFile, {sfMainModule})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import std/intsets
|
||||
import ast, options, lineinfos, pathutils, msgs, modulegraphs, packages
|
||||
|
||||
proc skipCodegen*(config: ConfigRef; n: PNode): bool {.inline.} =
|
||||
@@ -24,3 +23,4 @@ proc prepareConfigNotes*(graph: ModuleGraph; module: PSym) =
|
||||
|
||||
proc moduleHasChanged*(graph: ModuleGraph; module: PSym): bool {.inline.} =
|
||||
result = true
|
||||
#module.id >= 0 or isDefined(graph.config, "nimBackendAssumesChange")
|
||||
|
||||
@@ -211,7 +211,7 @@ type
|
||||
cpuPowerpc64el, cpuSparc, cpuVm, cpuHppa, cpuIa64, cpuAmd64, cpuMips,
|
||||
cpuMipsel, cpuArm, cpuArm64, cpuJS, cpuNimVM, cpuAVR, cpuMSP430,
|
||||
cpuSparc64, cpuS390x, cpuMips64, cpuMips64el, cpuRiscV32, cpuRiscV64,
|
||||
cpuEsp, cpuWasm32, cpuE2k, cpuLoongArch64, 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
|
||||
|
||||
@@ -33,7 +33,7 @@ proc iterToProcImpl*(c: PContext, n: PNode): PNode =
|
||||
|
||||
let prc = newSym(skProc, n[3].ident, c.idgen, iter.sym.owner, iter.sym.info)
|
||||
prc.typ = copyType(iter.sym.typ, c.idgen, prc)
|
||||
excl prc.typ, tfCapturesEnv
|
||||
excl prc.typ.flags, tfCapturesEnv
|
||||
prc.typ.n.add newSymNode(getEnvParam(iter.sym))
|
||||
prc.typ.rawAddSon t
|
||||
let orig = iter.sym.ast
|
||||
|
||||
@@ -21,6 +21,8 @@ import std/[os, math, strutils]
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
from ic / ic import addCompilerProc
|
||||
|
||||
const
|
||||
FirstCallConv* = wNimcall
|
||||
LastCallConv* = wNoconv
|
||||
@@ -146,7 +148,7 @@ proc pragmaEnsures(c: PContext, n: PNode) =
|
||||
if o.kind in routineKinds and o.typ != nil and o.typ.returnType != nil:
|
||||
var s = newSym(skResult, getIdent(c.cache, "result"), c.idgen, o, n.info)
|
||||
s.typ = o.typ.returnType
|
||||
incl(s.flagsImpl, sfUsed)
|
||||
incl(s.flags, sfUsed)
|
||||
addDecl(c, s)
|
||||
n[1] = c.semExpr(c, n[1])
|
||||
closeScope(c)
|
||||
@@ -154,12 +156,12 @@ proc pragmaEnsures(c: PContext, n: PNode) =
|
||||
proc setExternName(c: PContext; s: PSym, extname: string, info: TLineInfo) =
|
||||
# special cases to improve performance:
|
||||
if extname == "$1":
|
||||
s.setSnippet(rope(s.name.s))
|
||||
s.loc.snippet = rope(s.name.s)
|
||||
elif '$' notin extname:
|
||||
s.setSnippet(rope(extname))
|
||||
s.loc.snippet = rope(extname)
|
||||
else:
|
||||
try:
|
||||
s.setSnippet(rope(extname % s.name.s))
|
||||
s.loc.snippet = rope(extname % s.name.s)
|
||||
except ValueError:
|
||||
localError(c.config, info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)")
|
||||
when hasFFI:
|
||||
@@ -168,36 +170,36 @@ proc setExternName(c: PContext; s: PSym, extname: string, info: TLineInfo) =
|
||||
|
||||
proc makeExternImport(c: PContext; s: PSym, extname: string, info: TLineInfo) =
|
||||
setExternName(c, s, extname, info)
|
||||
s.incl(sfImportc)
|
||||
s.excl(sfForward)
|
||||
incl(s.flags, sfImportc)
|
||||
excl(s.flags, sfForward)
|
||||
|
||||
proc makeExternExport(c: PContext; s: PSym, extname: string, info: TLineInfo) =
|
||||
setExternName(c, s, extname, info)
|
||||
s.incl(sfExportc)
|
||||
incl(s.flags, sfExportc)
|
||||
|
||||
proc processImportCompilerProc(c: PContext; s: PSym, extname: string, info: TLineInfo) =
|
||||
setExternName(c, s, extname, info)
|
||||
s.incl(sfImportc)
|
||||
s.excl(sfForward)
|
||||
incl(s.locImpl.flags, lfImportCompilerProc)
|
||||
incl(s.flags, sfImportc)
|
||||
excl(s.flags, sfForward)
|
||||
incl(s.loc.flags, lfImportCompilerProc)
|
||||
|
||||
proc processImportCpp(c: PContext; s: PSym, extname: string, info: TLineInfo) =
|
||||
setExternName(c, s, extname, info)
|
||||
s.incl(sfImportc)
|
||||
incl(s.flagsImpl, sfInfixCall)
|
||||
excl(s.flagsImpl, sfForward)
|
||||
incl(s.flags, sfImportc)
|
||||
incl(s.flags, sfInfixCall)
|
||||
excl(s.flags, sfForward)
|
||||
if c.config.backend == backendC:
|
||||
let m = s.getModule()
|
||||
incl(m.flagsImpl, sfCompileToCpp)
|
||||
incl(m.flags, sfCompileToCpp)
|
||||
incl c.config.globalOptions, optMixedMode
|
||||
|
||||
proc processImportObjC(c: PContext; s: PSym, extname: string, info: TLineInfo) =
|
||||
setExternName(c, s, extname, info)
|
||||
s.incl(sfImportc)
|
||||
incl(s.flagsImpl, sfNamedParamCall)
|
||||
excl(s.flagsImpl, sfForward)
|
||||
incl(s.flags, sfImportc)
|
||||
incl(s.flags, sfNamedParamCall)
|
||||
excl(s.flags, sfForward)
|
||||
let m = s.getModule()
|
||||
m.incl(sfCompileToObjc)
|
||||
incl(m.flags, sfCompileToObjc)
|
||||
|
||||
proc newEmptyStrNode(c: PContext; n: PNode, strVal: string = ""): PNode {.noinline.} =
|
||||
result = newNodeIT(nkStrLit, n.info, getSysType(c.graph, n.info, tyString))
|
||||
@@ -237,14 +239,14 @@ proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string =
|
||||
proc processVirtual(c: PContext, n: PNode, s: PSym, flag: TSymFlag) =
|
||||
s.constraint = newEmptyStrNode(c, n, getOptionalStr(c, n, "$1"))
|
||||
s.constraint.strVal = s.constraint.strVal % s.name.s
|
||||
s.flagsImpl.incl {flag, sfInfixCall, sfExportc, sfMangleCpp}
|
||||
s.flags.incl {flag, sfInfixCall, sfExportc, sfMangleCpp}
|
||||
|
||||
s.typ.callConv = ccMember
|
||||
incl c.config.globalOptions, optMixedMode
|
||||
|
||||
proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) =
|
||||
sym.constraint = getStrLitNode(c, n)
|
||||
sym.flagsImpl.incl sfCodegenDecl
|
||||
sym.flags.incl sfCodegenDecl
|
||||
|
||||
proc processMagic(c: PContext, n: PNode, s: PSym) =
|
||||
#if sfSystemModule notin c.module.flags:
|
||||
@@ -280,10 +282,10 @@ proc onOff(c: PContext, n: PNode, op: TOptions, resOptions: var TOptions) =
|
||||
|
||||
proc pragmaNoForward*(c: PContext, n: PNode; flag=sfNoForward) =
|
||||
if isTurnedOn(c, n):
|
||||
incl(c.module.flagsImpl, flag)
|
||||
incl(c.module.flags, flag)
|
||||
c.features.incl codeReordering
|
||||
else:
|
||||
excl(c.module.flagsImpl, flag)
|
||||
excl(c.module.flags, flag)
|
||||
# c.features.excl codeReordering
|
||||
|
||||
# deprecated as of 0.18.1
|
||||
@@ -355,9 +357,9 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) =
|
||||
var lib = getLib(c, libDynamic, expectDynlibNode(c, n))
|
||||
if not lib.isOverridden:
|
||||
addToLib(lib, sym)
|
||||
sym.incl(lfDynamicLib)
|
||||
incl(sym.loc.flags, lfDynamicLib)
|
||||
else:
|
||||
sym.incl(lfExportLib)
|
||||
incl(sym.loc.flags, lfExportLib)
|
||||
# since we'll be loading the dynlib symbols dynamically, we must use
|
||||
# a calling convention that doesn't introduce custom name mangling
|
||||
# cdecl is the default - the user can override this explicitly
|
||||
@@ -433,7 +435,7 @@ proc processExperimental(c: PContext; n: PNode) =
|
||||
if not isTopLevel(c):
|
||||
localError(c.config, n.info,
|
||||
"Code reordering experimental pragma only valid at toplevel")
|
||||
c.module.flagsImpl.incl sfReorder
|
||||
c.module.flags.incl sfReorder
|
||||
except ValueError:
|
||||
localError(c.config, n[1].info, "unknown experimental feature")
|
||||
else:
|
||||
@@ -567,7 +569,7 @@ proc processCompile(c: PContext, n: PNode) =
|
||||
n[i] = c.semConstExpr(c, n[i])
|
||||
case n[i].kind
|
||||
of nkStrLit, nkRStrLit, nkTripleStrLit:
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result = n[i].strVal
|
||||
else:
|
||||
shallowCopy(result, n[i].strVal)
|
||||
@@ -634,7 +636,7 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
|
||||
var e = searchInScopes(con, getIdent(con.cache, sub), amb)
|
||||
# XXX what to do here if 'amb' is true?
|
||||
if e != nil:
|
||||
incl(e.flagsImpl, sfUsed)
|
||||
incl(e.flags, sfUsed)
|
||||
if isDefined(con.config, "nimPreviewAsmSemSymbol"):
|
||||
result.add con.semExprWithType(con, newSymNode(e), {efTypeAllowed})
|
||||
else:
|
||||
@@ -723,12 +725,12 @@ proc processPragma(c: PContext, n: PNode, i: int) =
|
||||
proc pragmaRaisesOrTags(c: PContext, n: PNode) =
|
||||
proc processExc(c: PContext, x: PNode) =
|
||||
if c.hasUnresolvedArgs(c, x):
|
||||
x.typ = makeTypeFromExpr(c, x)
|
||||
x.typ() = makeTypeFromExpr(c, x)
|
||||
else:
|
||||
var t = skipTypes(c.semTypeNode(c, x, nil), skipPtrs)
|
||||
if t.kind notin {tyObject, tyOr}:
|
||||
localError(c.config, x.info, errGenerated, "invalid type for raises/tags list")
|
||||
x.typ = t
|
||||
x.typ() = t
|
||||
|
||||
if n.kind in nkPragmaCallKinds and n.len == 2:
|
||||
let it = n[1]
|
||||
@@ -755,21 +757,23 @@ proc typeBorrow(c: PContext; sym: PSym, n: PNode) =
|
||||
let it = n[1]
|
||||
if it.kind != nkAccQuoted:
|
||||
localError(c.config, n.info, "a type can only borrow `.` for now")
|
||||
incl(sym.typ, tfBorrowDot)
|
||||
incl(sym.typ.flags, tfBorrowDot)
|
||||
|
||||
proc markCompilerProc(c: PContext; s: PSym) =
|
||||
# minor hack ahead: FlowVar is the only generic .compilerproc type which
|
||||
# should not have an external name set:
|
||||
if s.kind != skType or s.name.s != "FlowVar":
|
||||
makeExternExport(c, s, "$1", s.info)
|
||||
incl(s, sfCompilerProc)
|
||||
incl(s.flagsImpl, sfUsed)
|
||||
incl(s.flags, sfCompilerProc)
|
||||
incl(s.flags, sfUsed)
|
||||
registerCompilerProc(c.graph, s)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addCompilerProc(c.encoder, c.packedRepr, s)
|
||||
|
||||
proc deprecatedStmt(c: PContext; outerPragma: PNode) =
|
||||
let pragma = outerPragma[1]
|
||||
if pragma.kind in {nkStrLit..nkTripleStrLit}:
|
||||
incl(c.module, sfDeprecated)
|
||||
incl(c.module.flags, sfDeprecated)
|
||||
c.module.constraint = getStrLitNode(c, outerPragma)
|
||||
return
|
||||
if pragma.kind != nkBracket:
|
||||
@@ -838,7 +842,7 @@ proc processEffectsOf(c: PContext, n: PNode; owner: PSym) =
|
||||
let r = c.semExpr(c, n)
|
||||
if r.kind == nkSym and r.sym.kind == skParam:
|
||||
if r.sym.owner == owner:
|
||||
incl r.sym, sfEffectsDelayed
|
||||
incl r.sym.flags, sfEffectsDelayed
|
||||
else:
|
||||
localError(c.config, n.info, errGenerated, "parameter cannot be declared as .effectsOf")
|
||||
else:
|
||||
@@ -903,8 +907,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
if c.config.backend != backendCpp:
|
||||
localError(c.config, it.info, "exportcpp requires `cpp` backend, got: " & $c.config.backend)
|
||||
else:
|
||||
incl(sym, sfMangleCpp)
|
||||
incl(sym.flagsImpl, sfUsed) # avoid wrong hints
|
||||
incl(sym.flags, sfMangleCpp)
|
||||
incl(sym.flags, sfUsed) # avoid wrong hints
|
||||
of wImportc:
|
||||
let name = getOptionalStr(c, it, "$1")
|
||||
cppDefine(c.config, name)
|
||||
@@ -917,24 +921,24 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
processImportCompilerProc(c, sym, name, it.info)
|
||||
of wExtern: setExternName(c, sym, expectStrLit(c, it), it.info)
|
||||
of wDirty:
|
||||
if sym.kind == skTemplate: incl(sym, sfDirty)
|
||||
if sym.kind == skTemplate: incl(sym.flags, sfDirty)
|
||||
else: invalidPragma(c, it)
|
||||
of wRedefine:
|
||||
if sym.kind == skTemplate: incl(sym, sfTemplateRedefinition)
|
||||
if sym.kind == skTemplate: incl(sym.flags, sfTemplateRedefinition)
|
||||
else: invalidPragma(c, it)
|
||||
of wCallsite:
|
||||
if sym.kind == skTemplate: incl(sym, sfCallsite)
|
||||
if sym.kind == skTemplate: incl(sym.flags, sfCallsite)
|
||||
else: invalidPragma(c, it)
|
||||
of wImportCpp:
|
||||
processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info)
|
||||
of wCppNonPod:
|
||||
incl(sym, sfCppNonPod)
|
||||
incl(sym.flags, sfCppNonPod)
|
||||
of wImportJs:
|
||||
if c.config.backend != backendJs:
|
||||
localError(c.config, it.info, "`importjs` pragma requires the JavaScript target")
|
||||
let name = getOptionalStr(c, it, "$1")
|
||||
incl(sym, sfImportc)
|
||||
incl(sym.flagsImpl, sfInfixCall)
|
||||
incl(sym.flags, sfImportc)
|
||||
incl(sym.flags, sfInfixCall)
|
||||
if sym.kind in skProcKinds and {'(', '#', '@'} notin name:
|
||||
localError(c.config, n.info, "`importjs` for routines requires a pattern")
|
||||
setExternName(c, sym, name, it.info)
|
||||
@@ -964,29 +968,29 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
localError(c.config, it.info, "power of two expected")
|
||||
of wNodecl:
|
||||
noVal(c, it)
|
||||
sym.incl(lfNoDecl)
|
||||
incl(sym.loc.flags, lfNoDecl)
|
||||
of wPure, wAsmNoStackFrame:
|
||||
noVal(c, it)
|
||||
if sym != nil:
|
||||
if k == wPure and sym.kind in routineKinds: invalidPragma(c, it)
|
||||
else: incl(sym, sfPure)
|
||||
else: incl(sym.flags, sfPure)
|
||||
of wVolatile:
|
||||
noVal(c, it)
|
||||
incl(sym, sfVolatile)
|
||||
incl(sym.flags, sfVolatile)
|
||||
of wCursor:
|
||||
noVal(c, it)
|
||||
incl(sym, sfCursor)
|
||||
incl(sym.flags, sfCursor)
|
||||
of wRegister:
|
||||
noVal(c, it)
|
||||
incl(sym, sfRegister)
|
||||
incl(sym.flags, sfRegister)
|
||||
of wNoalias:
|
||||
noVal(c, it)
|
||||
incl(sym, sfNoalias)
|
||||
incl(sym.flags, sfNoalias)
|
||||
of wEffectsOf:
|
||||
processEffectsOf(c, it, sym)
|
||||
of wThreadVar:
|
||||
noVal(c, it)
|
||||
incl(sym, {sfThread, sfGlobal})
|
||||
incl(sym.flags, {sfThread, sfGlobal})
|
||||
of wDeadCodeElimUnused:
|
||||
warningDeprecated(c.config, n.info, "'{.deadcodeelim: on.}' is deprecated, now a noop") # deprecated, dead code elim always on
|
||||
of wNoForward: pragmaNoForward(c, it)
|
||||
@@ -996,50 +1000,51 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
noVal(c, it)
|
||||
if comesFromPush:
|
||||
if sym.kind in {skProc, skFunc}:
|
||||
incl(sym, sfCompileTime)
|
||||
incl(sym.flags, sfCompileTime)
|
||||
else:
|
||||
incl(sym, sfCompileTime)
|
||||
incl(sym.flags, sfCompileTime)
|
||||
#incl(sym.loc.flags, lfNoDecl)
|
||||
of wGlobal:
|
||||
noVal(c, it)
|
||||
incl(sym, {sfGlobal, sfPure})
|
||||
incl(sym.flags, sfGlobal)
|
||||
incl(sym.flags, sfPure)
|
||||
of wConstructor:
|
||||
incl(sym, sfConstructor)
|
||||
incl(sym.flags, sfConstructor)
|
||||
if sfImportc notin sym.flags:
|
||||
sym.constraint = newEmptyStrNode(c, it, getOptionalStr(c, it, ""))
|
||||
sym.constraint.strVal = sym.constraint.strVal
|
||||
sym.flagsImpl.incl {sfExportc, sfMangleCpp}
|
||||
sym.flags.incl {sfExportc, sfMangleCpp}
|
||||
sym.typ.callConv = ccNoConvention
|
||||
of wHeader:
|
||||
var lib = getLib(c, libHeader, getStrLitNode(c, it))
|
||||
addToLib(lib, sym)
|
||||
incl(sym, sfImportc)
|
||||
incl(sym.locImpl.flags, lfHeader)
|
||||
incl(sym.locImpl.flags, lfNoDecl)
|
||||
incl(sym.flags, sfImportc)
|
||||
incl(sym.loc.flags, lfHeader)
|
||||
incl(sym.loc.flags, lfNoDecl)
|
||||
# implies nodecl, because otherwise header would not make sense
|
||||
if sym.locImpl.snippet == "": sym.locImpl.snippet = rope(sym.name.s)
|
||||
if sym.loc.snippet == "": sym.loc.snippet = rope(sym.name.s)
|
||||
of wNoSideEffect:
|
||||
noVal(c, it)
|
||||
if sym != nil:
|
||||
incl(sym, sfNoSideEffect)
|
||||
if sym.typ != nil: incl(sym.typ, tfNoSideEffect)
|
||||
incl(sym.flags, sfNoSideEffect)
|
||||
if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect)
|
||||
of wSideEffect:
|
||||
noVal(c, it)
|
||||
incl(sym, sfSideEffect)
|
||||
incl(sym.flags, sfSideEffect)
|
||||
of wNoreturn:
|
||||
noVal(c, it)
|
||||
# Disable the 'noreturn' annotation when in the "Quirky Exceptions" mode!
|
||||
if c.config.exc != excQuirky:
|
||||
incl(sym, sfNoReturn)
|
||||
incl(sym.flags, sfNoReturn)
|
||||
if sym.typ.returnType != nil:
|
||||
localError(c.config, sym.ast[paramsPos][0].info,
|
||||
".noreturn with return type not allowed")
|
||||
of wNoDestroy:
|
||||
noVal(c, it)
|
||||
incl(sym, sfGeneratedOp)
|
||||
incl(sym.flags, sfGeneratedOp)
|
||||
of wNosinks:
|
||||
noVal(c, it)
|
||||
incl(sym, sfWasForwarded)
|
||||
incl(sym.flags, sfWasForwarded)
|
||||
of wDynlib:
|
||||
processDynLib(c, it, sym)
|
||||
of wCompilerProc, wCore:
|
||||
@@ -1048,79 +1053,79 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
recordPragma(c, it, "cppdefine", sym.name.s)
|
||||
if sfFromGeneric notin sym.flags: markCompilerProc(c, sym)
|
||||
of wNonReloadable:
|
||||
sym.incl sfNonReloadable
|
||||
sym.flags.incl sfNonReloadable
|
||||
of wProcVar:
|
||||
# old procvar annotation, no longer needed
|
||||
noVal(c, it)
|
||||
of wExplain:
|
||||
sym.incl sfExplain
|
||||
sym.flags.incl sfExplain
|
||||
of wDeprecated:
|
||||
if sym != nil and sym.kind in routineKinds + {skType, skVar, skLet, skConst}:
|
||||
if it.kind in nkPragmaCallKinds: discard getStrLitNode(c, it)
|
||||
incl(sym, sfDeprecated)
|
||||
incl(sym.flags, sfDeprecated)
|
||||
elif sym != nil and sym.kind != skModule:
|
||||
# We don't support the extra annotation field
|
||||
if it.kind in nkPragmaCallKinds:
|
||||
localError(c.config, it.info, "annotation to deprecated not supported here")
|
||||
incl(sym, sfDeprecated)
|
||||
incl(sym.flags, sfDeprecated)
|
||||
# At this point we're quite sure this is a statement and applies to the
|
||||
# whole module
|
||||
elif it.kind in nkPragmaCallKinds: deprecatedStmt(c, it)
|
||||
else: incl(c.module, sfDeprecated)
|
||||
else: incl(c.module.flags, sfDeprecated)
|
||||
of wVarargs:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfVarargs)
|
||||
else: incl(sym.typ.flags, tfVarargs)
|
||||
of wBorrow:
|
||||
if sym.kind == skType:
|
||||
typeBorrow(c, sym, it)
|
||||
else:
|
||||
noVal(c, it)
|
||||
incl(sym, sfBorrow)
|
||||
incl(sym.flags, sfBorrow)
|
||||
of wFinal:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfFinal)
|
||||
else: incl(sym.typ.flags, tfFinal)
|
||||
of wInheritable:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil or tfFinal in sym.typ.flags: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfInheritable)
|
||||
else: incl(sym.typ.flags, tfInheritable)
|
||||
of wPackage:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym, sfForward)
|
||||
else: incl(sym.flags, sfForward)
|
||||
of wAcyclic:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfAcyclic)
|
||||
else: incl(sym.typ.flags, tfAcyclic)
|
||||
of wShallow:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfShallow)
|
||||
else: incl(sym.typ.flags, tfShallow)
|
||||
of wThread:
|
||||
noVal(c, it)
|
||||
incl(sym, sfThread)
|
||||
incl(sym.flags, sfThread)
|
||||
if sym.typ != nil:
|
||||
incl(sym.typ, tfThread)
|
||||
incl(sym.typ.flags, tfThread)
|
||||
if sym.typ.callConv == ccClosure: sym.typ.callConv = ccNimCall
|
||||
of wSendable:
|
||||
noVal(c, it)
|
||||
if sym != nil and sym.typ != nil:
|
||||
incl(sym.typ, tfSendable)
|
||||
incl(sym.typ.flags, tfSendable)
|
||||
else:
|
||||
invalidPragma(c, it)
|
||||
of wGcSafe:
|
||||
noVal(c, it)
|
||||
if sym != nil:
|
||||
if sym.kind != skType: incl(sym, sfThread)
|
||||
if sym.typ != nil: incl(sym.typ, tfGcSafe)
|
||||
if sym.kind != skType: incl(sym.flags, sfThread)
|
||||
if sym.typ != nil: incl(sym.typ.flags, tfGcSafe)
|
||||
else: invalidPragma(c, it)
|
||||
else:
|
||||
discard "no checking if used as a code block"
|
||||
of wPacked:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfPacked)
|
||||
else: incl(sym.typ.flags, tfPacked)
|
||||
of wHint:
|
||||
let s = expectStrLit(c, it)
|
||||
recordPragma(c, it, "hint", s)
|
||||
@@ -1136,8 +1141,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
# distinguish properly between
|
||||
# ``proc p() {.error}`` and ``proc p() = {.error: "msg".}``
|
||||
if it.kind in nkPragmaCallKinds: discard getStrLitNode(c, it)
|
||||
incl(sym, sfError)
|
||||
excl(sym, sfForward)
|
||||
incl(sym.flags, sfError)
|
||||
excl(sym.flags, sfForward)
|
||||
else:
|
||||
let s = expectStrLit(c, it)
|
||||
recordPragma(c, it, "error", s)
|
||||
@@ -1147,18 +1152,18 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
of wUndef: processUndef(c, it)
|
||||
of wCompile:
|
||||
let m = sym.getModule()
|
||||
incl(m.flagsImpl, sfUsed)
|
||||
incl(m.flags, sfUsed)
|
||||
processCompile(c, it)
|
||||
of wLink: processLink(c, it)
|
||||
of wPassl:
|
||||
let m = sym.getModule()
|
||||
incl(m.flagsImpl, sfUsed)
|
||||
incl(m.flags, sfUsed)
|
||||
let s = expectStrLit(c, it)
|
||||
extccomp.addLinkOption(c.config, s)
|
||||
recordPragma(c, it, "passl", s)
|
||||
of wPassc:
|
||||
let m = sym.getModule()
|
||||
incl(m.flagsImpl, sfUsed)
|
||||
incl(m.flags, sfUsed)
|
||||
let s = expectStrLit(c, it)
|
||||
extccomp.addCompileOption(c.config, s)
|
||||
recordPragma(c, it, "passc", s)
|
||||
@@ -1176,16 +1181,16 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
result = true
|
||||
of wPragma:
|
||||
if not sym.isNil and sym.kind == skTemplate:
|
||||
sym.incl sfCustomPragma
|
||||
sym.flags.incl sfCustomPragma
|
||||
else:
|
||||
processPragma(c, n, i)
|
||||
result = true
|
||||
of wDiscardable:
|
||||
noVal(c, it)
|
||||
if sym != nil: incl(sym, sfDiscardable)
|
||||
if sym != nil: incl(sym.flags, sfDiscardable)
|
||||
of wNoInit:
|
||||
noVal(c, it)
|
||||
if sym != nil: incl(sym, sfNoInit)
|
||||
if sym != nil: incl(sym.flags, sfNoInit)
|
||||
of wCodegenDecl: processCodegenDecl(c, it, sym)
|
||||
of wChecks, wObjChecks, wFieldChecks, wRangeChecks, wBoundChecks,
|
||||
wOverflowChecks, wNilChecks, wAssertions, wWarnings, wHints,
|
||||
@@ -1195,8 +1200,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
processOption(c, it, c.config.options)
|
||||
of wStackTrace, wLineTrace:
|
||||
if sym.kind in {skProc, skMethod, skConverter}:
|
||||
ensureMutable sym
|
||||
processOption(c, it, sym.optionsImpl)
|
||||
processOption(c, it, sym.options)
|
||||
else:
|
||||
processOption(c, it, c.config.options)
|
||||
of FirstCallConv..LastCallConv:
|
||||
@@ -1204,7 +1208,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else:
|
||||
sym.typ.callConv = wordToCallConv(k)
|
||||
sym.typ.incl tfExplicitCallConv
|
||||
sym.typ.flags.incl tfExplicitCallConv
|
||||
of wEmit: pragmaEmit(c, it)
|
||||
of wUnroll: pragmaUnroll(c, it)
|
||||
of wLinearScanEnd, wComputedGoto: noVal(c, it)
|
||||
@@ -1214,11 +1218,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
of wIncompleteStruct:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfIncompleteStruct)
|
||||
else: incl(sym.typ.flags, tfIncompleteStruct)
|
||||
of wCompleteStruct:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfCompleteStruct)
|
||||
else: incl(sym.typ.flags, tfCompleteStruct)
|
||||
of wUnchecked:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil or sym.typ.kind notin {tyArray, tyUncheckedArray}:
|
||||
@@ -1231,35 +1235,34 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
else:
|
||||
noVal(c, it)
|
||||
if sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfUnion)
|
||||
else: incl(sym.typ.flags, tfUnion)
|
||||
of wRequiresInit:
|
||||
noVal(c, it)
|
||||
if sym.kind == skField:
|
||||
sym.incl sfRequiresInit
|
||||
sym.flags.incl sfRequiresInit
|
||||
elif sym.typ != nil:
|
||||
incl(sym.typ, tfNeedsFullInit)
|
||||
incl(sym.typ.flags, tfNeedsFullInit)
|
||||
else:
|
||||
invalidPragma(c, it)
|
||||
of wByRef:
|
||||
noVal(c, it)
|
||||
if sym != nil and sym.kind == skParam:
|
||||
ensureMutable sym
|
||||
sym.optionsImpl.incl optByRef
|
||||
sym.options.incl optByRef
|
||||
elif sym == nil or sym.typ == nil:
|
||||
processOption(c, it, c.config.options)
|
||||
else:
|
||||
incl(sym.typ, tfByRef)
|
||||
incl(sym.typ.flags, tfByRef)
|
||||
of wByCopy:
|
||||
noVal(c, it)
|
||||
if sym.kind == skParam:
|
||||
incl(sym, sfByCopy)
|
||||
incl(sym.flags, sfByCopy)
|
||||
elif sym.kind != skType or sym.typ == nil: invalidPragma(c, it)
|
||||
else: incl(sym.typ, tfByCopy)
|
||||
else: incl(sym.typ.flags, tfByCopy)
|
||||
of wPartial:
|
||||
noVal(c, it)
|
||||
if sym.kind != skType or sym.typ == nil: invalidPragma(c, it)
|
||||
else:
|
||||
incl(sym.typ, tfPartial)
|
||||
incl(sym.typ.flags, tfPartial)
|
||||
of wInject, wGensym:
|
||||
# We check for errors, but do nothing with these pragmas otherwise
|
||||
# as they are handled directly in 'evalTemplate'.
|
||||
@@ -1287,7 +1290,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
if sym == nil or sym.kind notin {skVar, skLet}:
|
||||
invalidPragma(c, it)
|
||||
else:
|
||||
sym.incl sfGoto
|
||||
sym.flags.incl sfGoto
|
||||
of wExportNims:
|
||||
if sym == nil: invalidPragma(c, it)
|
||||
else: magicsys.registerNimScriptSymbol(c.graph, sym)
|
||||
@@ -1302,7 +1305,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
noVal(c, it)
|
||||
of wBase:
|
||||
noVal(c, it)
|
||||
sym.incl sfBase
|
||||
sym.flags.incl sfBase
|
||||
of wIntDefine:
|
||||
processDefineConst(c, n, sym, mIntDefine)
|
||||
of wStrDefine:
|
||||
@@ -1312,22 +1315,21 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
of wUsed:
|
||||
noVal(c, it)
|
||||
if sym == nil: invalidPragma(c, it)
|
||||
else: sym.incl sfUsed
|
||||
else: sym.flags.incl sfUsed
|
||||
of wLiftLocals:
|
||||
sym.incl(sfForceLift)
|
||||
sym.flags.incl(sfForceLift)
|
||||
of wRequires, wInvariant, wAssume, wAssert:
|
||||
pragmaProposition(c, it)
|
||||
of wEnsures:
|
||||
pragmaEnsures(c, it)
|
||||
of wEnforceNoRaises:
|
||||
sym.incl sfNeverRaises
|
||||
sym.flags.incl sfNeverRaises
|
||||
of wQuirky:
|
||||
sym.incl sfNeverRaises
|
||||
sym.flags.incl sfNeverRaises
|
||||
if sym.kind in {skProc, skMethod, skConverter, skFunc, skIterator}:
|
||||
ensureMutable sym
|
||||
sym.optionsImpl.incl optQuirky
|
||||
sym.options.incl optQuirky
|
||||
of wSystemRaisesDefect:
|
||||
sym.incl sfSystemRaisesDefect
|
||||
sym.flags.incl sfSystemRaisesDefect
|
||||
of wVirtual:
|
||||
processVirtual(c, it, sym, sfVirtual)
|
||||
of wMember:
|
||||
@@ -1384,9 +1386,9 @@ proc implicitPragmas*(c: PContext, sym: PSym, info: TLineInfo,
|
||||
var lib = c.optionStack[^1].dynlib
|
||||
if {lfDynamicLib, lfHeader} * sym.loc.flags == {} and
|
||||
sfImportc in sym.flags and lib != nil:
|
||||
incl(sym, lfDynamicLib)
|
||||
incl(sym.loc.flags, lfDynamicLib)
|
||||
addToLib(lib, sym)
|
||||
if sym.locImpl.snippet == "": sym.locImpl.snippet = rope(sym.name.s)
|
||||
if sym.loc.snippet == "": sym.loc.snippet = rope(sym.name.s)
|
||||
|
||||
proc hasPragma*(n: PNode, pragma: TSpecialWord): bool =
|
||||
if n == nil: return false
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{.used.}
|
||||
|
||||
import
|
||||
lexer, options, idents, ast, msgs, lineinfos, wordrecg, trees
|
||||
lexer, options, idents, ast, msgs, lineinfos, wordrecg
|
||||
|
||||
import std/[strutils]
|
||||
|
||||
@@ -30,7 +30,7 @@ type
|
||||
TRenderFlags* = set[TRenderFlag]
|
||||
TRenderTok* = object
|
||||
kind*: TokType
|
||||
length*: int32
|
||||
length*: int16
|
||||
sym*: PSym
|
||||
|
||||
Section = enum
|
||||
@@ -66,359 +66,6 @@ proc renderTree*(n: PNode, renderFlags: TRenderFlags = {}): string
|
||||
# determines how long the subtree will likely be, the second
|
||||
# phase appends to a buffer that will be the output.
|
||||
|
||||
type
|
||||
TPreferedDesc* = enum
|
||||
preferName, # default
|
||||
preferDesc, # probably should become what preferResolved is
|
||||
preferExported,
|
||||
preferModuleInfo, # fully qualified
|
||||
preferGenericArg,
|
||||
preferTypeName,
|
||||
preferResolved, # fully resolved symbols
|
||||
preferMixed,
|
||||
# most useful, shows: symbol + resolved symbols if it differs, e.g.:
|
||||
# tuple[a: MyInt{int}, b: float]
|
||||
preferInlayHint,
|
||||
preferInferredEffects,
|
||||
|
||||
proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string
|
||||
template `$`*(typ: PType): string = typeToString(typ)
|
||||
|
||||
proc valueToString(a: PNode): string =
|
||||
case a.kind
|
||||
of nkCharLit, nkUIntLit..nkUInt64Lit:
|
||||
result = $cast[uint64](a.intVal)
|
||||
of nkIntLit..nkInt64Lit:
|
||||
result = $a.intVal
|
||||
of nkFloatLit..nkFloat128Lit: result = $a.floatVal
|
||||
of nkStrLit..nkTripleStrLit: result = a.strVal
|
||||
of nkStaticExpr: result = "static(" & a[0].renderTree & ")"
|
||||
else: result = "<invalid value>"
|
||||
|
||||
proc rangeToStr(n: PNode): string =
|
||||
assert(n.kind == nkRange)
|
||||
result = valueToString(n[0]) & ".." & valueToString(n[1])
|
||||
|
||||
const preferToResolveSymbols = {preferName, preferTypeName, preferModuleInfo,
|
||||
preferGenericArg, preferResolved, preferMixed, preferInlayHint, preferInferredEffects}
|
||||
|
||||
|
||||
const
|
||||
typeToStr: array[TTypeKind, string] = ["None", "bool", "char", "empty",
|
||||
"Alias", "typeof(nil)", "untyped", "typed", "typeDesc",
|
||||
# xxx typeDesc=>typedesc: typedesc is declared as such, and is 10x more common.
|
||||
"GenericInvocation", "GenericBody", "GenericInst", "GenericParam",
|
||||
"distinct $1", "enum", "ordinal[$1]", "array[$1, $2]", "object", "tuple",
|
||||
"set[$1]", "range[$1]", "ptr ", "ref ", "var ", "seq[$1]", "proc",
|
||||
"pointer", "OpenArray[$1]", "string", "cstring", "Forward",
|
||||
"int", "int8", "int16", "int32", "int64",
|
||||
"float", "float32", "float64", "float128",
|
||||
"uint", "uint8", "uint16", "uint32", "uint64",
|
||||
"owned", "sink",
|
||||
"lent ", "varargs[$1]", "UncheckedArray[$1]", "Error Type",
|
||||
"BuiltInTypeClass", "UserTypeClass",
|
||||
"UserTypeClassInst", "CompositeTypeClass", "inferred",
|
||||
"and", "or", "not", "any", "static", "TypeFromExpr", "concept", # xxx bugfix
|
||||
"void", "iterable"]
|
||||
|
||||
proc addTypeFlags(name: var string, typ: PType) {.inline.} =
|
||||
if tfNotNil in typ.flags: name.add(" not nil")
|
||||
|
||||
proc isIntLit*(t: PType): bool {.inline.} =
|
||||
result = t.kind == tyInt and t.n != nil and t.n.kind == nkIntLit
|
||||
|
||||
proc isFloatLit*(t: PType): bool {.inline.} =
|
||||
result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit
|
||||
|
||||
# TODO: It would be a good idea to kill the special state of a resolved
|
||||
# concept by switching to tyAlias within the instantiated procs.
|
||||
# Currently, tyAlias is always skipped with skipModifier, which means that
|
||||
# we can store information about the matched concept in another position.
|
||||
# Then builtInFieldAccess can be modified to properly read the derived
|
||||
# consts and types stored within the concept.
|
||||
template isResolvedUserTypeClass*(t: PType): bool =
|
||||
tfResolved in t.flags
|
||||
|
||||
proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||
let preferToplevel = prefer
|
||||
proc getPrefer(prefer: TPreferedDesc): TPreferedDesc =
|
||||
if preferToplevel in {preferResolved, preferMixed}:
|
||||
preferToplevel # sticky option
|
||||
else:
|
||||
prefer
|
||||
|
||||
proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||
result = ""
|
||||
let prefer = getPrefer(prefer)
|
||||
let t = typ
|
||||
if t == nil: return
|
||||
if prefer in preferToResolveSymbols and t.sym != nil and
|
||||
sfAnon notin t.sym.flags and t.kind notin {tySequence, tyInferred}:
|
||||
if t.kind == tyInt and isIntLit(t):
|
||||
if prefer == preferInlayHint:
|
||||
result = t.sym.name.s
|
||||
else:
|
||||
result = t.sym.name.s & " literal(" & $t.n.intVal & ")"
|
||||
elif t.kind == tyAlias and t.elementType.kind != tyAlias:
|
||||
result = typeToString(t.elementType)
|
||||
elif prefer in {preferResolved, preferMixed}:
|
||||
case t.kind
|
||||
of IntegralTypes + {tyFloat..tyFloat128} + {tyString, tyCstring}:
|
||||
result = typeToStr[t.kind]
|
||||
of tyGenericBody:
|
||||
result = typeToString(t.last)
|
||||
of tyCompositeTypeClass:
|
||||
# avoids showing `A[any]` in `proc fun(a: A)` with `A = object[T]`
|
||||
result = typeToString(t.last.last)
|
||||
else:
|
||||
result = t.sym.name.s
|
||||
if prefer == preferMixed and result != t.sym.name.s:
|
||||
result = t.sym.name.s & "{" & result & "}"
|
||||
elif prefer in {preferName, preferTypeName, preferInlayHint, preferInferredEffects} or t.sym.owner.isNil:
|
||||
# note: should probably be: {preferName, preferTypeName, preferGenericArg}
|
||||
result = t.sym.name.s
|
||||
if t.kind == tyGenericParam and t.genericParamHasConstraints:
|
||||
result.add ": "
|
||||
result.add t.elementType.typeToString
|
||||
else:
|
||||
result = t.sym.owner.name.s & '.' & t.sym.name.s
|
||||
result.addTypeFlags(t)
|
||||
return
|
||||
case t.kind
|
||||
of tyInt:
|
||||
if not isIntLit(t) or prefer == preferExported:
|
||||
result = typeToStr[t.kind]
|
||||
else:
|
||||
case prefer:
|
||||
of preferGenericArg:
|
||||
result = $t.n.intVal
|
||||
of preferInlayHint:
|
||||
result = "int"
|
||||
else:
|
||||
result = "int literal(" & $t.n.intVal & ")"
|
||||
of tyGenericInst:
|
||||
result = typeToString(t.genericHead) & '['
|
||||
for needsComma, a in t.genericInstParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a, preferGenericArg))
|
||||
result.add(']')
|
||||
of tyGenericInvocation:
|
||||
result = typeToString(t.genericHead) & '['
|
||||
for needsComma, a in t.genericInvocationParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a, preferGenericArg))
|
||||
result.add(']')
|
||||
of tyGenericBody:
|
||||
result = typeToString(t.typeBodyImpl) & '['
|
||||
for i, a in t.genericBodyParams:
|
||||
if i > 0: result.add(", ")
|
||||
result.add(typeToString(a, preferTypeName))
|
||||
result.add(']')
|
||||
of tyTypeDesc:
|
||||
if t.elementType.kind == tyNone: result = "typedesc"
|
||||
else: result = "typedesc[" & typeToString(t.elementType) & "]"
|
||||
of tyStatic:
|
||||
if prefer == preferGenericArg and t.n != nil:
|
||||
result = t.n.renderTree
|
||||
else:
|
||||
result = "static[" & (if t.hasElementType: typeToString(t.skipModifier) else: "") & "]"
|
||||
if t.n != nil: result.add "(" & renderTree(t.n) & ")"
|
||||
of tyUserTypeClass:
|
||||
if t.sym != nil and t.sym.owner != nil:
|
||||
if t.isResolvedUserTypeClass: return typeToString(t.last)
|
||||
return t.sym.owner.name.s
|
||||
else:
|
||||
result = "<invalid tyUserTypeClass>"
|
||||
of tyBuiltInTypeClass:
|
||||
result =
|
||||
case t.base.kind
|
||||
of tyVar: "var"
|
||||
of tyRef: "ref"
|
||||
of tyPtr: "ptr"
|
||||
of tySequence: "seq"
|
||||
of tyArray: "array"
|
||||
of tySet: "set"
|
||||
of tyRange: "range"
|
||||
of tyDistinct: "distinct"
|
||||
of tyProc: "proc"
|
||||
of tyObject: "object"
|
||||
of tyTuple: "tuple"
|
||||
of tyOpenArray: "openArray"
|
||||
else: typeToStr[t.base.kind]
|
||||
of tyInferred:
|
||||
let concrete = t.previouslyInferred
|
||||
if concrete != nil: result = typeToString(concrete)
|
||||
else: result = "inferred[" & typeToString(t.base) & "]"
|
||||
of tyUserTypeClassInst:
|
||||
let body = t.base
|
||||
result = body.sym.name.s & "["
|
||||
for needsComma, a in t.userTypeClassInstParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a))
|
||||
result.add "]"
|
||||
of tyAnd:
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add(" and ")
|
||||
result.add(typeToString(son))
|
||||
of tyOr:
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add(" or ")
|
||||
result.add(typeToString(son))
|
||||
of tyNot:
|
||||
result = "not " & typeToString(t.elementType)
|
||||
of tyUntyped:
|
||||
#internalAssert t.len == 0
|
||||
result = "untyped"
|
||||
of tyFromExpr:
|
||||
if t.n == nil:
|
||||
result = "unknown"
|
||||
else:
|
||||
result = "typeof(" & renderTree(t.n) & ")"
|
||||
of tyArray:
|
||||
result = "array"
|
||||
if t.hasElementType:
|
||||
if t.indexType.kind == tyRange:
|
||||
result &= "[" & rangeToStr(t.indexType.n) & ", " &
|
||||
typeToString(t.elementType) & ']'
|
||||
else:
|
||||
result &= "[" & typeToString(t.indexType) & ", " &
|
||||
typeToString(t.elementType) & ']'
|
||||
of tyUncheckedArray:
|
||||
result = "UncheckedArray"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tySequence:
|
||||
if t.sym != nil and prefer != preferResolved:
|
||||
result = t.sym.name.s
|
||||
else:
|
||||
result = "seq"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyOrdinal:
|
||||
result = "ordinal"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.skipModifier) & ']'
|
||||
of tySet:
|
||||
result = "set"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyOpenArray:
|
||||
result = "openArray"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyDistinct:
|
||||
result = "distinct " & typeToString(t.elementType,
|
||||
if prefer == preferModuleInfo: preferModuleInfo else: preferTypeName)
|
||||
of tyIterable:
|
||||
# xxx factor this pattern
|
||||
result = "iterable"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.skipModifier) & ']'
|
||||
of tyTuple:
|
||||
# we iterate over t.sons here, because t.n may be nil
|
||||
if t.n != nil:
|
||||
result = "tuple["
|
||||
for i in 0..<t.n.len:
|
||||
assert(t.n[i].kind == nkSym)
|
||||
result.add(t.n[i].sym.name.s & ": " & typeToString(t.n[i].sym.typ))
|
||||
if i < t.n.len - 1: result.add(", ")
|
||||
result.add(']')
|
||||
elif t.isEmptyTupleType:
|
||||
result = "tuple[]"
|
||||
elif t.isSingletonTupleType:
|
||||
result = "("
|
||||
for son in t.kids:
|
||||
result.add(typeToString(son))
|
||||
result.add(",)")
|
||||
else:
|
||||
result = "("
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add ", "
|
||||
result.add(typeToString(son))
|
||||
result.add(')')
|
||||
of tyPtr, tyRef, tyVar, tyLent:
|
||||
result = if isOutParam(t): "out " else: typeToStr[t.kind]
|
||||
result.add typeToString(t.elementType)
|
||||
of tyRange:
|
||||
result = "range "
|
||||
if t.n != nil and t.n.kind == nkRange:
|
||||
result.add rangeToStr(t.n)
|
||||
if prefer != preferExported:
|
||||
result.add("(" & typeToString(t.elementType) & ")")
|
||||
of tyProc:
|
||||
result = if tfIterator in t.flags: "iterator "
|
||||
elif t.owner != nil:
|
||||
case t.owner.kind
|
||||
of skTemplate: "template "
|
||||
of skMacro: "macro "
|
||||
of skConverter: "converter "
|
||||
else: "proc "
|
||||
else:
|
||||
"proc "
|
||||
if tfUnresolved in t.flags: result.add "[*missing parameters*]"
|
||||
result.add "("
|
||||
for i, a in t.paramTypes:
|
||||
if i > FirstParamAt: result.add(", ")
|
||||
let j = paramTypeToNodeIndex(i)
|
||||
if t.n != nil and j < t.n.len and t.n[j].kind == nkSym:
|
||||
result.add(t.n[j].sym.name.s)
|
||||
result.add(": ")
|
||||
result.add(typeToString(a))
|
||||
result.add(')')
|
||||
if t.returnType != nil: result.add(": " & typeToString(t.returnType))
|
||||
var prag = if t.callConv == ccNimCall and tfExplicitCallConv notin t.flags: "" else: $t.callConv
|
||||
var hasImplicitRaises = false
|
||||
if not isNil(t.owner) and not isNil(t.owner.ast) and (t.owner.ast.len - 1) >= pragmasPos:
|
||||
let pragmasNode = t.owner.ast[pragmasPos]
|
||||
let raisesSpec = effectSpec(pragmasNode, wRaises)
|
||||
if not isNil(raisesSpec):
|
||||
addSep(prag)
|
||||
prag.add("raises: ")
|
||||
prag.add(renderTree raisesSpec)
|
||||
hasImplicitRaises = true
|
||||
if tfNoSideEffect in t.flags:
|
||||
addSep(prag)
|
||||
prag.add("noSideEffect")
|
||||
if tfThread in t.flags:
|
||||
addSep(prag)
|
||||
prag.add("gcsafe")
|
||||
var effectsOfStr = ""
|
||||
for i, a in t.paramTypes:
|
||||
let j = paramTypeToNodeIndex(i)
|
||||
if t.n != nil and j < t.n.len and t.n[j].kind == nkSym and t.n[j].sym.kind == skParam and sfEffectsDelayed in t.n[j].sym.flags:
|
||||
addSep(effectsOfStr)
|
||||
effectsOfStr.add(t.n[j].sym.name.s)
|
||||
if effectsOfStr != "":
|
||||
addSep(prag)
|
||||
prag.add("effectsOf: ")
|
||||
prag.add(effectsOfStr)
|
||||
if not hasImplicitRaises and prefer == preferInferredEffects and not isNil(t.owner) and not isNil(t.owner.typ) and not isNil(t.owner.typ.n) and (t.owner.typ.n.len > 0):
|
||||
let effects = t.n[0]
|
||||
if effects.kind == nkEffectList and effects.len == effectListLen:
|
||||
var inferredRaisesStr = ""
|
||||
let effs = effects[exceptionEffects]
|
||||
if not isNil(effs):
|
||||
for eff in items(effs):
|
||||
if not isNil(eff):
|
||||
addSep(inferredRaisesStr)
|
||||
inferredRaisesStr.add($eff.typ)
|
||||
addSep(prag)
|
||||
prag.add("raises: <inferred> [")
|
||||
prag.add(inferredRaisesStr)
|
||||
prag.add("]")
|
||||
if prag.len != 0: result.add("{." & prag & ".}")
|
||||
of tyVarargs:
|
||||
result = typeToStr[t.kind] % typeToString(t.elementType)
|
||||
of tySink:
|
||||
result = "sink " & typeToString(t.skipModifier)
|
||||
of tyOwned:
|
||||
result = "owned " & typeToString(t.elementType)
|
||||
else:
|
||||
result = typeToStr[t.kind]
|
||||
result.addTypeFlags(t)
|
||||
result = typeToString(typ, prefer)
|
||||
|
||||
|
||||
proc disamb(g: var TSrcGen; s: PSym): int =
|
||||
# we group by 's.name.s' to compute the stable name ID.
|
||||
result = 0
|
||||
@@ -507,7 +154,7 @@ proc initSrcGen(renderFlags: TRenderFlags; config: ConfigRef): TSrcGen =
|
||||
)
|
||||
|
||||
proc addTok(g: var TSrcGen, kind: TokType, s: string; sym: PSym = nil) =
|
||||
g.tokens.add TRenderTok(kind: kind, length: int32(s.len), sym: sym)
|
||||
g.tokens.add TRenderTok(kind: kind, length: int16(s.len), sym: sym)
|
||||
g.buf.add(s)
|
||||
if kind != tkSpaces:
|
||||
inc g.col, s.len
|
||||
@@ -582,7 +229,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
|
||||
@@ -612,12 +258,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)
|
||||
@@ -681,10 +327,6 @@ proc pushCom(g: var TSrcGen, n: PNode) =
|
||||
setLen(g.comStack, g.comStack.len + 1)
|
||||
g.comStack[^1] = n
|
||||
|
||||
proc popCom(g: var TSrcGen): PNode =
|
||||
result = g.comStack[^1]
|
||||
setLen(g.comStack, g.comStack.len - 1)
|
||||
|
||||
proc popAllComs(g: var TSrcGen) =
|
||||
setLen(g.comStack, 0)
|
||||
|
||||
@@ -1216,28 +858,10 @@ proc genSymSuffix(result: var string, s: PSym) {.inline.} =
|
||||
result.add '_'
|
||||
result.addInt s.id
|
||||
|
||||
proc gsemmedParams(g: var TSrcGen, n: PNode) =
|
||||
put(g, tkParLe, "(")
|
||||
for i in 1..<n.len:
|
||||
if i > 1:
|
||||
putWithSpace(g, tkComma, ";")
|
||||
let x {.cursor.} = n[i]
|
||||
if x.kind == nkSym:
|
||||
put g, tkSymbol, renderDefinitionName(x.sym)
|
||||
putWithSpace(g, tkColon, ":")
|
||||
put g, tkSymbol, typeToString(x.sym.typ)
|
||||
else:
|
||||
gsub(g, x)
|
||||
put(g, tkParRi, ")")
|
||||
if not isEmptyType(n[0].typ):
|
||||
putWithSpace(g, tkColon, ":")
|
||||
gsub(g, n[0])
|
||||
|
||||
proc gproc(g: var TSrcGen, n: PNode) =
|
||||
var c: TContext = initContext()
|
||||
var s: PSym = nil
|
||||
if n[namePos].kind == nkSym:
|
||||
s = n[namePos].sym
|
||||
let s = n[namePos].sym
|
||||
var ret = renderDefinitionName(s)
|
||||
ret.genSymSuffix(s)
|
||||
put(g, tkSymbol, ret)
|
||||
@@ -1252,10 +876,7 @@ proc gproc(g: var TSrcGen, n: PNode) =
|
||||
gsub(g, n[miscPos][1])
|
||||
else:
|
||||
gsub(g, n[genericParamsPos])
|
||||
if n[paramsPos].len == 0 and s != nil and s.typ != nil and s.typ.n != nil:
|
||||
gsemmedParams(g, s.typ.n)
|
||||
else:
|
||||
gsub(g, n[paramsPos])
|
||||
gsub(g, n[paramsPos])
|
||||
if renderNoPragmas notin g.flags:
|
||||
gsub(g, n[pragmasPos])
|
||||
if renderNoBody notin g.flags:
|
||||
@@ -1732,10 +1353,6 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
|
||||
if not n[0].isExported() and renderNonExportedFields notin g.flags:
|
||||
# Skip if this is a property in a type and its not exported
|
||||
# (While also not allowing rendering of non exported fields)
|
||||
if shouldRenderComment(g, n):
|
||||
# `shouldRenderComment` indicts that we have comments to render
|
||||
# but it's a non-exported field, so we just pop without rendering any comment
|
||||
discard popCom(g)
|
||||
return
|
||||
# render postfix for object fields:
|
||||
exclFlags = g.flags * {renderNoPostfix}
|
||||
@@ -2211,9 +1828,6 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
|
||||
putWithSpace(g, tkSymbol, "error")
|
||||
#gcomma(g, n, c)
|
||||
gsub(g, n[0], c)
|
||||
of nkReplayAction:
|
||||
put(g, tkSymbol, "replayaction")
|
||||
#gsons(g, n, c, 0)
|
||||
else:
|
||||
#nkNone, nkExplicitTypeListCall:
|
||||
internalError(g.config, n.info, "renderer.gsub(" & $n.kind & ')')
|
||||
|
||||
@@ -213,10 +213,9 @@ proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile;
|
||||
unregisterArcOrc(conf)
|
||||
conf.globalOptions.excl optOwnedRefs
|
||||
conf.selectedGC = gcUnselected
|
||||
conf.globalOptions.incl optWithinConfigSystem
|
||||
|
||||
var m = graph.makeModule(scriptName)
|
||||
incl(m, sfMainModule)
|
||||
incl(m.flags, sfMainModule)
|
||||
var vm = setupVM(m, cache, scriptName.string, graph, idgen)
|
||||
graph.vm = vm
|
||||
|
||||
@@ -231,7 +230,7 @@ proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile;
|
||||
if optOwnedRefs in oldGlobalOptions:
|
||||
conf.globalOptions.incl {optTinyRtti, optOwnedRefs, optSeqDestructors}
|
||||
defineSymbol(conf.symbols, "nimv2")
|
||||
if conf.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if conf.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
conf.globalOptions.incl {optTinyRtti, optSeqDestructors}
|
||||
defineSymbol(conf.symbols, "nimv2")
|
||||
defineSymbol(conf.symbols, "gcdestructors")
|
||||
@@ -241,8 +240,6 @@ proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile;
|
||||
defineSymbol(conf.symbols, "gcarc")
|
||||
of gcOrc:
|
||||
defineSymbol(conf.symbols, "gcorc")
|
||||
of gcYrc:
|
||||
defineSymbol(conf.symbols, "gcyrc")
|
||||
of gcAtomicArc:
|
||||
defineSymbol(conf.symbols, "gcatomicarc")
|
||||
else:
|
||||
@@ -254,5 +251,4 @@ proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile;
|
||||
#initDefines()
|
||||
undefSymbol(conf.symbols, "nimscript")
|
||||
undefSymbol(conf.symbols, "nimconfig")
|
||||
conf.globalOptions.excl optWithinConfigSystem
|
||||
conf.symbolFiles = oldSymbolFiles
|
||||
|
||||
102
compiler/sem.nim
102
compiler/sem.nim
@@ -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]
|
||||
@@ -114,15 +102,12 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
|
||||
renderTree(arg, {renderNoComments}))
|
||||
# error correction:
|
||||
result = copyTree(arg)
|
||||
result.typ = formal
|
||||
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:
|
||||
@@ -131,7 +116,7 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
|
||||
typeMismatch(c.config, info, formal, arg.typ, arg)
|
||||
# error correction:
|
||||
result = copyTree(arg)
|
||||
result.typ = formal
|
||||
result.typ() = formal
|
||||
else:
|
||||
result = fitNodePostMatch(c, formal, result)
|
||||
|
||||
@@ -141,7 +126,7 @@ proc fitNodeConsiderViewType(c: PContext, formal: PType, arg: PNode; info: TLine
|
||||
#classifyViewType(formal) != noView:
|
||||
result = newNodeIT(nkHiddenAddr, a.info, formal)
|
||||
result.add a
|
||||
formal.incl tfVarIsPtr
|
||||
formal.flags.incl tfVarIsPtr
|
||||
else:
|
||||
result = a
|
||||
|
||||
@@ -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
|
||||
@@ -303,7 +260,7 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
|
||||
else:
|
||||
result = newSym(kind, considerQuotedIdent(c, n), c.idgen, getCurrOwner(c), n.info)
|
||||
if find(result.name.s, '`') >= 0:
|
||||
result.flagsImpl.incl sfWasGenSym
|
||||
result.flags.incl sfWasGenSym
|
||||
#if kind in {skForVar, skLet, skVar} and result.owner.kind == skModule:
|
||||
# incl(result.flags, sfGlobal)
|
||||
when defined(nimsuggest):
|
||||
@@ -389,19 +346,6 @@ proc fixupTypeAfterEval(c: PContext, evaluated, eOrig: PNode; producedClosure: v
|
||||
isArrayConstr(arg):
|
||||
arg.typ = eOrig.typ
|
||||
|
||||
proc resetEvalPosition(n: PNode) =
|
||||
# resets the eval position of variables because `tryConstExpr` may be
|
||||
# called multiple times on the same node
|
||||
case n.kind
|
||||
of {nkNone..nkNilLit}-{nkSym}:
|
||||
discard
|
||||
of nkSym:
|
||||
if n.sym.kind in {skVar, skLet} and sfGlobal notin n.sym.flags:
|
||||
n.sym.position = 0
|
||||
else:
|
||||
for i in 0..<n.safeLen:
|
||||
resetEvalPosition(n[i])
|
||||
|
||||
proc tryConstExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
||||
var e = semExprWithType(c, n, expectedType = expectedType)
|
||||
if e == nil: return
|
||||
@@ -438,8 +382,6 @@ proc tryConstExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
||||
# Restore the error hook
|
||||
c.graph.config.structuredErrorHook = tempHook
|
||||
|
||||
resetEvalPosition(n)
|
||||
|
||||
c.config.errorCounter = oldErrorCount
|
||||
c.config.errorMax = oldErrorMax
|
||||
c.config.m.errorOutputs = oldErrorOutputs
|
||||
@@ -534,7 +476,7 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
|
||||
renderTree(result, {renderNoComments}))
|
||||
result = newSymNode(errorSym(c, result))
|
||||
else:
|
||||
result.typ = makeTypeDesc(c, typ)
|
||||
result.typ() = makeTypeDesc(c, typ)
|
||||
#result = symNodeFromType(c, typ, n.info)
|
||||
else:
|
||||
if s.ast[genericParamsPos] != nil and retType.isMetaType:
|
||||
@@ -584,12 +526,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:
|
||||
@@ -695,7 +635,7 @@ proc defaultFieldsForTuple(c: PContext, recNode: PNode, hasDefault: var bool, ch
|
||||
newNodeIT(nkType, recNode.info, asgnType)
|
||||
)
|
||||
asgnExpr.flags.incl nfSkipFieldChecking
|
||||
asgnExpr.typ = recNode.typ
|
||||
asgnExpr.typ() = recNode.typ
|
||||
result.add newTree(nkExprColonExpr, recNode, asgnExpr)
|
||||
else:
|
||||
raiseAssert "unreachable"
|
||||
@@ -717,7 +657,7 @@ proc defaultFieldsForTheUninitialized(c: PContext, recNode: PNode, checkDefault:
|
||||
if checkDefault: # don't add defaults when checking whether a case branch has default fields
|
||||
return
|
||||
defaultValue = newIntNode(nkIntLit#[c.graph]#, 0)
|
||||
defaultValue.typ = discriminator.typ
|
||||
defaultValue.typ() = discriminator.typ
|
||||
selectedBranch = recNode.pickCaseBranchIndex defaultValue
|
||||
defaultValue.flags.incl nfSkipFieldChecking
|
||||
result.add newTree(nkExprColonExpr, discriminator, defaultValue)
|
||||
@@ -730,7 +670,7 @@ proc defaultFieldsForTheUninitialized(c: PContext, recNode: PNode, checkDefault:
|
||||
elif recType.kind in {tyObject, tyArray, tyTuple}:
|
||||
let asgnExpr = defaultNodeField(c, recNode, recNode.typ, checkDefault)
|
||||
if asgnExpr != nil:
|
||||
asgnExpr.typ = recNode.typ
|
||||
asgnExpr.typ() = recNode.typ
|
||||
asgnExpr.flags.incl nfSkipFieldChecking
|
||||
result.add newTree(nkExprColonExpr, recNode, asgnExpr)
|
||||
else:
|
||||
@@ -743,7 +683,7 @@ proc defaultNodeField(c: PContext, a: PNode, aTyp: PType, checkDefault: bool): P
|
||||
let child = defaultFieldsForTheUninitialized(c, aTypSkip.n, checkDefault)
|
||||
if child.len > 0:
|
||||
var asgnExpr = newTree(nkObjConstr, newNodeIT(nkType, a.info, aTyp))
|
||||
asgnExpr.typ = aTyp
|
||||
asgnExpr.typ() = aTyp
|
||||
asgnExpr.sons.add child
|
||||
result = semExpr(c, asgnExpr)
|
||||
else:
|
||||
@@ -755,11 +695,11 @@ proc defaultNodeField(c: PContext, a: PNode, aTyp: PType, checkDefault: bool): P
|
||||
let node = newNode(nkIntLit)
|
||||
node.intVal = toInt64(lengthOrd(c.graph.config, aTypSkip))
|
||||
let typeNode = newNode(nkType)
|
||||
typeNode.typ = makeTypeDesc(c, aTypSkip[1])
|
||||
typeNode.typ() = makeTypeDesc(c, aTypSkip[1])
|
||||
result = semExpr(c, newTree(nkCall, newTree(nkBracketExpr, newSymNode(getSysSym(c.graph, a.info, "arrayWithDefault"), a.info), typeNode),
|
||||
node
|
||||
))
|
||||
result.typ = aTyp
|
||||
result.typ() = aTyp
|
||||
else:
|
||||
result = nil
|
||||
of tyTuple:
|
||||
@@ -768,7 +708,7 @@ proc defaultNodeField(c: PContext, a: PNode, aTyp: PType, checkDefault: bool): P
|
||||
let children = defaultFieldsForTuple(c, aTypSkip.n, hasDefault, checkDefault)
|
||||
if hasDefault and children.len > 0:
|
||||
result = newNodeI(nkTupleConstr, a.info)
|
||||
result.typ = aTyp
|
||||
result.typ() = aTyp
|
||||
result.sons.add children
|
||||
result = semExpr(c, result)
|
||||
else:
|
||||
@@ -896,11 +836,11 @@ 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
|
||||
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc} and
|
||||
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
||||
Feature.vtables in c.config.features:
|
||||
sortVTableDispatchers(c.graph)
|
||||
|
||||
@@ -933,7 +873,9 @@ 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)
|
||||
storeRodNode(c, result)
|
||||
|
||||
|
||||
proc reportUnusedModules(c: PContext) =
|
||||
if c.config.cmd == cmdM: return
|
||||
@@ -942,7 +884,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!
|
||||
|
||||
@@ -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,7 +671,7 @@ 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:
|
||||
@@ -709,7 +695,7 @@ proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) =
|
||||
internalError(c.config, a.info, "generic converter failed rematch")
|
||||
let finalCallee = generateInstance(c, s, convMatch.bindings, a.info)
|
||||
a[0].sym = finalCallee
|
||||
a[0].typ = finalCallee.typ
|
||||
a[0].typ() = finalCallee.typ
|
||||
#a.typ = finalCallee.typ.returnType
|
||||
|
||||
proc instGenericConvertersSons*(c: PContext, n: PNode, x: TCandidate) =
|
||||
@@ -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,
|
||||
" uniqueId=", f.uniqueId, " mod=", toFullPath(c.config, f.itemId.module.FileIndex),
|
||||
" sym=", (if f.sym != nil: $f.sym.itemId else: "nil"), " state=", f.state
|
||||
let a2 = a.skipTypes({tyRange})
|
||||
echo " a=", typeToString(a), " itemId=", a2.itemId, " uniqueId=", a2.uniqueId,
|
||||
" mod=", toFullPath(c.config, a2.itemId.module.FileIndex),
|
||||
" sym=", (if a2.sym != nil: $a2.sym.itemId else: "nil"), " state=", a2.state
|
||||
|
||||
proc inferWithMetatype(c: PContext, formal: PType,
|
||||
arg: PNode, coerceDistincts = false): PNode =
|
||||
@@ -753,13 +730,13 @@ proc inferWithMetatype(c: PContext, formal: PType,
|
||||
# This almost exactly replicates the steps taken by the compiler during
|
||||
# param matching. It performs an embarrassing amount of back-and-forth
|
||||
# type jugling, but it's the price to pay for consistency and correctness
|
||||
result.typ = generateTypeInstance(c, m.bindings, arg.info,
|
||||
result.typ() = generateTypeInstance(c, m.bindings, arg.info,
|
||||
formal.skipTypes({tyCompositeTypeClass}))
|
||||
else:
|
||||
typeMismatch(c.config, arg.info, formal, arg.typ, arg)
|
||||
# error correction:
|
||||
result = copyTree(arg)
|
||||
result.typ = formal
|
||||
result.typ() = formal
|
||||
|
||||
proc updateDefaultParams(c: PContext, call: PNode) =
|
||||
# In generic procs, the default parameter may be unique for each
|
||||
@@ -782,7 +759,7 @@ proc updateDefaultParams(c: PContext, call: PNode) =
|
||||
pushInfoContext(c.config, call.info, call[0].sym.detailedInfo)
|
||||
typeMismatch(c.config, def.info, formal.typ, def.typ, formal.ast)
|
||||
popInfoContext(c.config)
|
||||
def.typ = errorType(c)
|
||||
def.typ() = errorType(c)
|
||||
call[i] = def
|
||||
|
||||
proc getCallLineInfo(n: PNode): TLineInfo =
|
||||
@@ -853,21 +830,6 @@ proc inheritBindings(c: PContext, x: var TCandidate, expectedType: PType) =
|
||||
for i in 0 ..< flatUnbound.len():
|
||||
x.bindings.put(flatUnbound[i], flatBound[i])
|
||||
|
||||
proc compactVoidArgs(n: PNode): PNode =
|
||||
# deletes void args from the argument list, which are created by `setSon`
|
||||
var hasNil = false
|
||||
for i in 0..<n.len:
|
||||
if n[i] == nil:
|
||||
hasNil = true
|
||||
break
|
||||
if not hasNil:
|
||||
result = n
|
||||
else:
|
||||
result = copyNode(n)
|
||||
for i in 0..<n.len:
|
||||
if n[i] != nil:
|
||||
result.add n[i]
|
||||
|
||||
proc semResolvedCall(c: PContext, x: var TCandidate,
|
||||
n: PNode, flags: TExprFlags;
|
||||
expectedType: PType = nil): PNode =
|
||||
@@ -884,8 +846,8 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
|
||||
result = x.call
|
||||
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
|
||||
if containsGenericType(result.typ):
|
||||
result.typ = newTypeS(tyError, c)
|
||||
incl result.typ, tfCheckedForDestructor
|
||||
result.typ() = newTypeS(tyError, c)
|
||||
incl result.typ.flags, tfCheckedForDestructor
|
||||
return
|
||||
let gp = finalCallee.ast[genericParamsPos]
|
||||
if gp.isGenericParams:
|
||||
@@ -911,19 +873,19 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
|
||||
# this node will be used in template substitution,
|
||||
# pretend this is an untyped node and let regular sem handle the type
|
||||
# to prevent problems where a generic parameter is treated as a value
|
||||
tn.typ = nil
|
||||
tn.typ() = nil
|
||||
x.call.add tn
|
||||
else:
|
||||
internalAssert c.config, false
|
||||
markUsed(c, info, finalCallee, isGenericInstance = true)
|
||||
onUse(info, finalCallee, isGenericInstance = true)
|
||||
|
||||
result = compactVoidArgs(x.call)
|
||||
result = x.call
|
||||
instGenericConvertersSons(c, result, x)
|
||||
markConvertersUsed(c, result)
|
||||
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
|
||||
if finalCallee.magic notin {mArrGet, mArrPut}:
|
||||
result.typ = finalCallee.typ.returnType
|
||||
result.typ() = finalCallee.typ.returnType
|
||||
updateDefaultParams(c, result)
|
||||
|
||||
proc canDeref(n: PNode): bool {.inline.} =
|
||||
@@ -932,7 +894,7 @@ proc canDeref(n: PNode): bool {.inline.} =
|
||||
|
||||
proc tryDeref(n: PNode): PNode =
|
||||
result = newNodeI(nkHiddenDeref, n.info)
|
||||
result.typ = n.typ.skipTypes(abstractInst)[0]
|
||||
result.typ() = n.typ.skipTypes(abstractInst)[0]
|
||||
result.add n
|
||||
|
||||
proc semOverloadedCall(c: PContext, n, nOrig: PNode,
|
||||
@@ -951,7 +913,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
|
||||
else:
|
||||
if c.inGenericContext > 0 and c.matchedConcept == nil:
|
||||
result = semGenericStmt(c, n)
|
||||
result.typ = makeTypeFromExpr(c, result.copyTree)
|
||||
result.typ() = makeTypeFromExpr(c, result.copyTree)
|
||||
elif efNoUndeclared in flags:
|
||||
result = nil
|
||||
elif efExplain notin flags:
|
||||
@@ -983,12 +945,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.flags.excl tfUnresolved
|
||||
let info = getCallLineInfo(n)
|
||||
markUsed(c, info, s, isGenericInstance = false)
|
||||
onUse(info, s, isGenericInstance = false)
|
||||
@@ -1007,9 +964,9 @@ proc setGenericParams(c: PContext, n, expectedParams: PNode) =
|
||||
nil
|
||||
e = semExprWithType(c, n[i], expectedType = constraint)
|
||||
if e.typ == nil:
|
||||
n[i].typ = errorType(c)
|
||||
n[i].typ() = errorType(c)
|
||||
else:
|
||||
n[i].typ = e.typ
|
||||
n[i].typ() = e.typ.skipTypes({tyTypeDesc})
|
||||
|
||||
proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym, doError: bool): PNode =
|
||||
assert n.kind == nkBracketExpr
|
||||
@@ -1026,7 +983,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym, doError: bool)
|
||||
# same as in semOverloadedCall, make expression untyped,
|
||||
# may have failed match due to unresolved types
|
||||
result = semGenericStmt(c, n)
|
||||
result.typ = makeTypeFromExpr(c, result.copyTree)
|
||||
result.typ() = makeTypeFromExpr(c, result.copyTree)
|
||||
elif doError:
|
||||
notFoundError(c, n, errors)
|
||||
elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}:
|
||||
@@ -1044,7 +1001,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym, doError: bool)
|
||||
# any failing match stops building the symchoice for correctness,
|
||||
# can also make it untyped from the start
|
||||
result = semGenericStmt(c, n)
|
||||
result.typ = makeTypeFromExpr(c, result.copyTree)
|
||||
result.typ() = makeTypeFromExpr(c, result.copyTree)
|
||||
return
|
||||
# get rid of nkClosedSymChoice if not ambiguous:
|
||||
if result.len == 0:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user