diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..90e05c40d0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# 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" diff --git a/.github/workflows/bisects.yml b/.github/workflows/bisects.yml index 4a6a92f018..ef6e160a06 100644 --- a/.github/workflows/bisects.yml +++ b/.github/workflows/bisects.yml @@ -15,9 +15,16 @@ jobs: name: ${{ matrix.platform }}-bisects runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: jiro4989/setup-nim-action@v1 + - 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 with: nim-version: 'devel' diff --git a/.github/workflows/ci_docs.yml b/.github/workflows/ci_docs.yml index 8461fb5432..69d317cef6 100644 --- a/.github/workflows/ci_docs.yml +++ b/.github/workflows/ci_docs.yml @@ -43,9 +43,9 @@ jobs: - target: linux os: ubuntu-22.04 - target: windows - os: windows-2019 + os: windows-latest - target: osx - os: macos-13 + os: macos-15 name: ${{ matrix.target }} runs-on: ${{ matrix.os }} @@ -53,7 +53,7 @@ jobs: steps: - name: 'Checkout' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 2 @@ -109,7 +109,7 @@ jobs: if: | github.event_name == 'push' && github.ref == 'refs/heads/devel' && matrix.target == 'linux' - uses: crazy-max/ghaction-github-pages@v4 + uses: crazy-max/ghaction-github-pages@v5 with: build_dir: doc/html env: diff --git a/.github/workflows/ci_packages.yml b/.github/workflows/ci_packages.yml index fec634966b..6cef86c28a 100644 --- a/.github/workflows/ci_packages.yml +++ b/.github/workflows/ci_packages.yml @@ -18,9 +18,13 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04, macos-13] - cpu: [amd64] + 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-14 + cpu: arm64 name: '${{ matrix.os }} (batch: ${{ matrix.batch }})' runs-on: ${{ matrix.os }} timeout-minutes: 60 # refs bug #18178 @@ -29,23 +33,23 @@ jobs: NIM_TESTAMENT_BATCH: ${{ matrix.batch }} steps: - name: 'Checkout' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 2 - - name: 'Install node.js 20.x' - uses: actions/setup-node@v4 + - name: 'Install node.js' + uses: actions/setup-node@v6 with: - node-version: '20.x' + node-version: 24 - name: 'Install dependencies (Linux amd64)' if: runner.os == 'Linux' && matrix.cpu == 'amd64' run: | - sudo apt-fast update -qq + sudo apt-get update -qq DEBIAN_FRONTEND='noninteractive' \ - sudo apt-fast install --no-install-recommends -yq \ + sudo apt-get install --no-install-recommends -yq \ libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev \ - valgrind libc6-dbg libblas-dev xorg-dev + valgrind libc6-dbg libblas-dev liblapack-dev libpcre3 xorg-dev - name: 'Install dependencies (macOS)' if: runner.os == 'macOS' run: brew install boehmgc make sfml gtk+3 diff --git a/.github/workflows/ci_publish.yml b/.github/workflows/ci_publish.yml index 39fae32fea..67b78092f8 100644 --- a/.github/workflows/ci_publish.yml +++ b/.github/workflows/ci_publish.yml @@ -17,14 +17,14 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: 'Checkout' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 2 - name: 'Install node.js' - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '' + node-version: 24 - 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@v7 + uses: actions/github-script@v9 with: script: | const fs = require('fs'); diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 0c5a533e1d..b918b21050 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -9,7 +9,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: days-before-pr-stale: 365 days-before-pr-close: 30 diff --git a/.gitignore b/.gitignore index fad7909bd8..efb7dfe61e 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,7 @@ 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 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9696c2086d..96e747a730 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -28,24 +28,24 @@ 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_amd64: - vmImage: 'macOS-13' - CPU: amd64 - OSX_amd64_cpp: - vmImage: 'macOS-13' - CPU: amd64 + OSX_arm64: + vmImage: 'macos-15' + CPU: arm64 + OSX_arm64_cpp: + vmImage: 'macos-15' + CPU: arm64 NIM_COMPILE_TO_CPP: true Windows_amd64_batch0_3: - vmImage: 'windows-2019' + vmImage: 'windows-2025' CPU: amd64 # see also: `NIM_TEST_PACKAGES` NIM_TESTAMENT_BATCH: "0_3" Windows_amd64_batch1_3: - vmImage: 'windows-2019' + vmImage: 'windows-2025' CPU: amd64 NIM_TESTAMENT_BATCH: "1_3" Windows_amd64_batch2_3: - vmImage: 'windows-2019' + vmImage: 'windows-2025' CPU: amd64 NIM_TESTAMENT_BATCH: "2_3" diff --git a/changelog.md b/changelog.md index b9671147f0..af91b49773 100644 --- a/changelog.md +++ b/changelog.md @@ -19,15 +19,71 @@ errors. - With `-d:nimPreviewAsmSemSymbol`, backticked symbols are type checked in the `asm/emit` statements. +- 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 `!=`. + +- `std/parsesql` has been moved to a nimble package, use `nimble` or `atlas` to install it. + +- 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:" + - `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. +- `strutils.multiReplace` overload for character set replacements in a single pass. + Useful for string sanitation. Follows existing multiReplace semantics. + +- `std/files` adds: + - Exports `CopyFlag` enum and `FilePermission` type for fine-grained control of file operations + - New file operation procs with `Path` support: + - `getFilePermissions`, `setFilePermissions` for managing permissions + - `tryRemoveFile` for file deletion + - `copyFile` with configurable buffer size and symlink handling + - `copyFileWithPermissions` to preserve file attributes + - `copyFileToDir` for copying files into directories + +- `std/dirs` adds: + - New directory operation procs with `Path` support: + - `copyDir` with special file handling options + - `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/nre2` is added to replace deprecated NRE. [//]: # "Changes:" + - `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type. +- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function. +- `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation. +- `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`. +- `std/re` and `std/nre` are deprecated as PCRE library is obsolete. + Use https://github.com/nitely/nim-regex or `std/nre2`. + See: https://github.com/nim-lang/Nim/issues/23668. +- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style + terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``) + works without single-quoting. ## Language changes @@ -68,7 +124,17 @@ errors. ## 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. diff --git a/compiler/ast.nim b/compiler/ast.nim index 3ed9a7c675..5e9680a278 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -10,7 +10,7 @@ # abstract syntax tree + symbol table import - lineinfos, options, ropes, idents, int128, wordrecg + lineinfos, options, idents, int128, wordrecg import std/[tables, hashes] from std/strutils import toLowerAscii @@ -23,808 +23,417 @@ export int128 import nodekinds export nodekinds -type - TCallingConvention* = enum - ccNimCall = "nimcall" # nimcall, also the default - ccStdCall = "stdcall" # procedure is stdcall - ccCDecl = "cdecl" # cdecl - ccSafeCall = "safecall" # safecall - ccSysCall = "syscall" # system call - ccInline = "inline" # proc should be inlined - ccNoInline = "noinline" # proc should not be inlined - ccFastCall = "fastcall" # fastcall (pass parameters in registers) - ccThisCall = "thiscall" # thiscall (parameters are pushed right-to-left) - ccClosure = "closure" # proc has a closure - ccNoConvention = "noconv" # needed for generating proper C procs sometimes - ccMember = "member" # proc is a (cpp) member - - TNodeKinds* = set[TNodeKind] - -type - TSymFlag* = enum # 63 flags! - sfUsed, # read access of sym (for warnings) or simply used - sfExported, # symbol is exported from module - sfFromGeneric, # symbol is instantiation of a generic; this is needed - # for symbol file generation; such symbols should always - # be written into the ROD file - sfGlobal, # symbol is at global scope - - sfForward, # symbol is forward declared - sfWasForwarded, # symbol had a forward declaration - # (implies it's too dangerous to patch its type signature) - sfImportc, # symbol is external; imported - sfExportc, # symbol is exported (under a specified name) - sfMangleCpp, # mangle as cpp (combines with `sfExportc`) - sfVolatile, # variable is volatile - sfRegister, # variable should be placed in a register - sfPure, # object is "pure" that means it has no type-information - # enum is "pure", its values need qualified access - # variable is "pure"; it's an explicit "global" - sfNoSideEffect, # proc has no side effects - sfSideEffect, # proc may have side effects; cannot prove it has none - sfMainModule, # module is the main module - sfSystemModule, # module is the system module - sfNoReturn, # proc never returns (an exit proc) - sfAddrTaken, # the variable's address is taken (ex- or implicitly); - # *OR*: a proc is indirectly called (used as first class) - sfCompilerProc, # proc is a compiler proc, that is a C proc that is - # needed for the code generator - sfEscapes # param escapes - # currently unimplemented - sfDiscriminant, # field is a discriminant in a record/object - sfRequiresInit, # field must be initialized during construction - sfDeprecated, # symbol is deprecated - sfExplain, # provide more diagnostics when this symbol is used - sfError, # usage of symbol should trigger a compile-time error - sfShadowed, # a symbol that was shadowed in some inner scope - sfThread, # proc will run as a thread - # variable is a thread variable - sfCppNonPod, # tells compiler to treat such types as non-pod's, so that - # `thread_local` is used instead of `__thread` for - # {.threadvar.} + `--threads`. Only makes sense for importcpp types. - # This has a performance impact so isn't set by default. - sfCompileTime, # proc can be evaluated at compile time - sfConstructor, # proc is a C++ constructor - sfDispatcher, # copied method symbol is the dispatcher - # deprecated and unused, except for the con - sfBorrow, # proc is borrowed - sfInfixCall, # symbol needs infix call syntax in target language; - # for interfacing with C++, JS - sfNamedParamCall, # symbol needs named parameter call syntax in target - # language; for interfacing with Objective C - sfDiscardable, # returned value may be discarded implicitly - sfOverridden, # proc is overridden - sfCallsite # A flag for template symbols to tell the - # compiler it should use line information from - # the calling side of the macro, not from the - # implementation. - sfGenSym # symbol is 'gensym'ed; do not add to symbol table - sfNonReloadable # symbol will be left as-is when hot code reloading is on - - # meaning that it won't be renamed and/or changed in any way - sfGeneratedOp # proc is a generated '='; do not inject destructors in it - # variable is generated closure environment; requires early - # destruction for --newruntime. - sfTemplateParam # symbol is a template parameter - sfCursor # variable/field is a cursor, see RFC 177 for details - sfInjectDestructors # whether the proc needs the 'injectdestructors' transformation - sfNeverRaises # proc can never raise an exception, not even OverflowDefect - # or out-of-memory - sfSystemRaisesDefect # proc in the system can raise defects - sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally' - sfSingleUsedTemp # For temporaries that we know will only be used once - sfNoalias # 'noalias' annotation, means C's 'restrict' - # for templates and macros, means cannot be called - # as a lone symbol (cannot use alias syntax) - sfEffectsDelayed # an 'effectsDelayed' parameter - sfGeneratedType # A anonymous generic type that is generated by the compiler for - # objects that do not have generic parameters in case one of the - # object fields has one. - # - # This is disallowed but can cause the typechecking to go into - # an infinite loop, this flag is used as a sentinel to stop it. - sfVirtual # proc is a C++ virtual function - sfByCopy # param is marked as pass bycopy - sfMember # proc is a C++ member of a type - sfCodegenDecl # type, proc, global or proc param is marked as codegenDecl - sfWasGenSym # symbol was 'gensym'ed - sfForceLift # variable has to be lifted into closure environment - - sfDirty # template is not hygienic (old styled template) module, - # compiled from a dirty-buffer - sfCustomPragma # symbol is custom pragma template - sfBase, # a base method - sfGoto # var is used for 'goto' code generation - sfAnon, # symbol name that was generated by the compiler - # the compiler will avoid printing such names - # in user messages. - sfAllUntyped # macro or template is immediately expanded in a generic context - sfTemplateRedefinition # symbol is a redefinition of an earlier template - - TSymFlags* = set[TSymFlag] - -const - sfNoInit* = sfMainModule # don't generate code to init the variable - - sfNoForward* = sfRegister - # forward declarations are not required (per module) - sfReorder* = sfForward - # reordering pass is enabled - - sfCompileToCpp* = sfInfixCall # compile the module as C++ code - sfCompileToObjc* = sfNamedParamCall # compile the module as Objective-C code - sfExperimental* = sfOverridden # module uses the .experimental switch - sfWrittenTo* = sfBorrow # param is assigned to - # currently unimplemented - sfCppMember* = { sfVirtual, sfMember, sfConstructor } # proc is a C++ member, meaning it will be attached to the type definition - -const - # getting ready for the future expr/stmt merge - nkWhen* = nkWhenStmt - nkWhenExpr* = nkWhenStmt - nkEffectList* = nkArgList - # hacks ahead: an nkEffectList is a node with 4 children: - exceptionEffects* = 0 # exceptions at position 0 - requiresEffects* = 1 # 'requires' annotation - ensuresEffects* = 2 # 'ensures' annotation - tagEffects* = 3 # user defined tag ('gc', 'time' etc.) - pragmasEffects* = 4 # not an effect, but a slot for pragmas in proc type - forbiddenEffects* = 5 # list of illegal effects - effectListLen* = 6 # list of effects list - nkLastBlockStmts* = {nkRaiseStmt, nkReturnStmt, nkBreakStmt, nkContinueStmt} - # these must be last statements in a block - -type - TTypeKind* = enum # order is important! - # Don't forget to change hti.nim if you make a change here - # XXX put this into an include file to avoid this issue! - # several types are no longer used (guess which), but a - # spot in the sequence is kept for backwards compatibility - # (apparently something with bootstrapping) - # if you need to add a type, they can apparently be reused - tyNone, tyBool, tyChar, - tyEmpty, tyAlias, tyNil, tyUntyped, tyTyped, tyTypeDesc, - tyGenericInvocation, # ``T[a, b]`` for types to invoke - tyGenericBody, # ``T[a, b, body]`` last parameter is the body - tyGenericInst, # ``T[a, b, realInstance]`` instantiated generic type - # realInstance will be a concrete type like tyObject - # unless this is an instance of a generic alias type. - # then realInstance will be the tyGenericInst of the - # completely (recursively) resolved alias. - - tyGenericParam, # ``a`` in the above patterns - tyDistinct, - tyEnum, - tyOrdinal, # integer types (including enums and boolean) - tyArray, - tyObject, - tyTuple, - tySet, - tyRange, - tyPtr, tyRef, - tyVar, - tySequence, - tyProc, - tyPointer, tyOpenArray, - tyString, tyCstring, tyForward, - tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers - tyFloat, tyFloat32, tyFloat64, tyFloat128, - tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64, - tyOwned, tySink, tyLent, - tyVarargs, - tyUncheckedArray - # An array with boundaries [0,+∞] - - tyError # used as erroneous type (for idetools) - # as an erroneous node should match everything - - tyBuiltInTypeClass - # Type such as the catch-all object, tuple, seq, etc - - tyUserTypeClass - # the body of a user-defined type class - - tyUserTypeClassInst - # Instance of a parametric user-defined type class. - # Structured similarly to tyGenericInst. - # tyGenericInst represents concrete types, while - # this is still a "generic param" that will bind types - # and resolves them during sigmatch and instantiation. - - tyCompositeTypeClass - # Type such as seq[Number] - # The notes for tyUserTypeClassInst apply here as well - # sons[0]: the original expression used by the user. - # sons[1]: fully expanded and instantiated meta type - # (potentially following aliases) - - tyInferred - # In the initial state `base` stores a type class constraining - # the types that can be inferred. After a candidate type is - # selected, it's stored in `last`. Between `base` and `last` - # there may be 0, 2 or more types that were also considered as - # possible candidates in the inference process (i.e. last will - # be updated to store a type best conforming to all candidates) - - tyAnd, tyOr, tyNot - # boolean type classes such as `string|int`,`not seq`, - # `Sortable and Enumable`, etc - - tyAnything - # a type class matching any type - - tyStatic - # a value known at compile type (the underlying type is .base) - - tyFromExpr - # This is a type representing an expression that depends - # on generic parameters (the expression is stored in t.n) - # It will be converted to a real type only during generic - # instantiation and prior to this it has the potential to - # be any type. - - tyConcept - # new style concept. - - tyVoid - # now different from tyEmpty, hurray! - tyIterable - -static: - # remind us when TTypeKind stops to fit in a single 64-bit word - # assert TTypeKind.high.ord <= 63 - discard - -const - tyPureObject* = tyTuple - GcTypeKinds* = {tyRef, tySequence, tyString} - - tyTypeClasses* = {tyBuiltInTypeClass, tyCompositeTypeClass, - tyUserTypeClass, tyUserTypeClassInst, tyConcept, - tyAnd, tyOr, tyNot, tyAnything} - - tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyUntyped} + tyTypeClasses - tyUserTypeClasses* = {tyUserTypeClass, tyUserTypeClassInst} - # consider renaming as `tyAbstractVarRange` - abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias, tyInferred, tySink, tyOwned} - abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, - tyInferred, tySink, tyOwned} # xxx what about tyStatic? - -type - TTypeKinds* = set[TTypeKind] - - TNodeFlag* = enum - nfNone, - nfBase2, # nfBase10 is default, so not needed - nfBase8, - nfBase16, - nfAllConst, # used to mark complex expressions constant; easy to get rid of - # but unfortunately it has measurable impact for compilation - # efficiency - nfTransf, # node has been transformed - nfNoRewrite # node should not be transformed anymore - nfSem # node has been checked for semantics - nfLL # node has gone through lambda lifting - nfDotField # the call can use a dot operator - nfDotSetter # the call can use a setter dot operarator - nfExplicitCall # x.y() was used instead of x.y - nfExprCall # this is an attempt to call a regular expression - nfIsRef # this node is a 'ref' node; used for the VM - nfIsPtr # this node is a 'ptr' node; used for the VM - nfPreventCg # this node should be ignored by the codegen - nfBlockArg # this a stmtlist appearing in a call (e.g. a do block) - nfFromTemplate # a top-level node returned from a template - nfDefaultParam # an automatically inserter default parameter - nfDefaultRefsParam # a default param value references another parameter - # the flag is applied to proc default values and to calls - nfExecuteOnReload # A top-level statement that will be executed during reloads - nfLastRead # this node is a last read - nfFirstWrite # this node is a first write - nfHasComment # node has a comment - nfSkipFieldChecking # node skips field visable checking - nfDisabledOpenSym # temporary: node should be nkOpenSym but cannot - # because openSym experimental switch is disabled - # gives warning instead - - TNodeFlags* = set[TNodeFlag] - TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 47) - tfVarargs, # procedure has C styled varargs - # tyArray type represeting a varargs list - tfNoSideEffect, # procedure type does not allow side effects - tfFinal, # is the object final? - tfInheritable, # is the object inheritable? - tfHasOwned, # type contains an 'owned' type and must be moved - tfEnumHasHoles, # enum cannot be mapped into a range - tfShallow, # type can be shallow copied on assignment - tfThread, # proc type is marked as ``thread``; alias for ``gcsafe`` - tfFromGeneric, # type is an instantiation of a generic; this is needed - # because for instantiations of objects, structural - # type equality has to be used - tfUnresolved, # marks unresolved typedesc/static params: e.g. - # proc foo(T: typedesc, list: seq[T]): var T - # proc foo(L: static[int]): array[L, int] - # can be attached to ranges to indicate that the range - # can be attached to generic procs with free standing - # type parameters: e.g. proc foo[T]() - # depends on unresolved static params. - tfResolved # marks a user type class, after it has been bound to a - # concrete type (lastSon becomes the concrete type) - tfRetType, # marks return types in proc (used to detect type classes - # used as return types for return type inference) - tfCapturesEnv, # whether proc really captures some environment - tfByCopy, # pass object/tuple by copy (C backend) - tfByRef, # pass object/tuple by reference (C backend) - tfIterator, # type is really an iterator, not a tyProc - tfPartial, # type is declared as 'partial' - tfNotNil, # type cannot be 'nil' - tfRequiresInit, # type contains a "not nil" constraint somewhere or - # a `requiresInit` field, so the default zero init - # is not appropriate - tfNeedsFullInit, # object type marked with {.requiresInit.} - # all fields must be initialized - tfVarIsPtr, # 'var' type is translated like 'ptr' even in C++ mode - tfHasMeta, # type contains "wildcard" sub-types such as generic params - # or other type classes - tfHasGCedMem, # type contains GC'ed memory - tfPacked - tfHasStatic - tfGenericTypeParam - tfImplicitTypeParam - tfInferrableStatic - tfConceptMatchedTypeSym - tfExplicit # for typedescs, marks types explicitly prefixed with the - # `type` operator (e.g. type int) - tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) - # T and I here can bind to both typedesc and static types - # before this is determined, we'll consider them to be a - # wildcard type. - tfHasAsgn # type has overloaded assignment operator - tfBorrowDot # distinct type borrows '.' - tfTriggersCompileTime # uses the NimNode type which make the proc - # implicitly '.compiletime' - tfRefsAnonObj # used for 'ref object' and 'ptr object' - tfCovariant # covariant generic param mimicking a ptr type - tfWeakCovariant # covariant generic param mimicking a seq/array type - tfContravariant # contravariant generic param - tfCheckedForDestructor # type was checked for having a destructor. - # If it has one, t.destructor is not nil. - tfAcyclic # object type was annotated as .acyclic - tfIncompleteStruct # treat this type as if it had sizeof(pointer) - tfCompleteStruct - # (for importc types); type is fully specified, allowing to compute - # sizeof, alignof, offsetof at CT - tfExplicitCallConv - tfIsConstructor - tfEffectSystemWorkaround - tfIsOutParam - tfSendable - tfImplicitStatic - - TTypeFlags* = set[TTypeFlag] - - TSymKind* = enum # the different symbols (start with the prefix sk); - # order is important for the documentation generator! - skUnknown, # unknown symbol: used for parsing assembler blocks - # and first phase symbol lookup in generics - skConditional, # symbol for the preprocessor (may become obsolete) - skDynLib, # symbol represents a dynamic library; this is used - # internally; it does not exist in Nim code - skParam, # a parameter - skGenericParam, # a generic parameter; eq in ``proc x[eq=`==`]()`` - skTemp, # a temporary variable (introduced by compiler) - skModule, # module identifier - skType, # a type - skVar, # a variable - skLet, # a 'let' symbol - skConst, # a constant - skResult, # special 'result' variable - skProc, # a proc - skFunc, # a func - skMethod, # a method - skIterator, # an iterator - skConverter, # a type converter - skMacro, # a macro - skTemplate, # a template; currently also misused for user-defined - # pragmas - skField, # a field in a record or object - skEnumField, # an identifier in an enum - skForVar, # a for loop variable - skLabel, # a label (for block statement) - skStub, # symbol is a stub and not yet loaded from the ROD - # file (it is loaded on demand, which may - # mean: never) - skPackage, # symbol is a package (used for canonicalization) - TSymKinds* = set[TSymKind] - -const - routineKinds* = {skProc, skFunc, skMethod, skIterator, - skConverter, skMacro, skTemplate} - ExportableSymKinds* = {skVar, skLet, skConst, skType, skEnumField, skStub} + routineKinds - - tfUnion* = tfNoSideEffect - tfGcSafe* = tfThread - tfObjHasKids* = tfEnumHasHoles - tfReturnsNew* = tfInheritable - tfNonConstExpr* = tfExplicitCallConv - ## tyFromExpr where the expression shouldn't be evaluated as a static value - tfGenericHasDestructor* = tfExplicitCallConv - ## tyGenericBody where an instance has a generated destructor - skError* = skUnknown - -var - eqTypeFlags* = {tfIterator, tfNotNil, tfVarIsPtr, tfGcSafe, tfNoSideEffect, tfIsOutParam} - ## type flags that are essential for type equality. - ## This is now a variable because for emulation of version:1.0 we - ## might exclude {tfGcSafe, tfNoSideEffect}. - -type - TMagic* = enum # symbols that require compiler magic: - mNone, - mDefined, mDeclared, mDeclaredInScope, mCompiles, mArrGet, mArrPut, mAsgn, - mLow, mHigh, mSizeOf, mAlignOf, mOffsetOf, mTypeTrait, - mIs, mOf, mAddr, mType, mTypeOf, - mPlugin, mEcho, mShallowCopy, mSlurp, mStaticExec, mStatic, - mParseExprToAst, mParseStmtToAst, mExpandToAst, mQuoteAst, - mInc, mDec, mOrd, - mNew, mNewFinalize, mNewSeq, mNewSeqOfCap, - mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq, - mIncl, mExcl, mCard, mChr, - mGCref, mGCunref, - mAddI, mSubI, mMulI, mDivI, mModI, - mSucc, mPred, - mAddF64, mSubF64, mMulF64, mDivF64, - mShrI, mShlI, mAshrI, mBitandI, mBitorI, mBitxorI, - mMinI, mMaxI, - mAddU, mSubU, mMulU, mDivU, mModU, - mEqI, mLeI, mLtI, - mEqF64, mLeF64, mLtF64, - mLeU, mLtU, - mEqEnum, mLeEnum, mLtEnum, - mEqCh, mLeCh, mLtCh, - mEqB, mLeB, mLtB, - mEqRef, mLePtr, mLtPtr, - mXor, mEqCString, mEqProc, - mUnaryMinusI, mUnaryMinusI64, mAbsI, mNot, - mUnaryPlusI, mBitnotI, - mUnaryPlusF64, mUnaryMinusF64, - mCharToStr, mBoolToStr, - mCStrToStr, - mStrToStr, mEnumToStr, - mAnd, mOr, - mImplies, mIff, mExists, mForall, mOld, - mEqStr, mLeStr, mLtStr, - mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet, mXorSet, - mConStrStr, mSlice, - mDotDot, # this one is only necessary to give nice compile time warnings - mFields, mFieldPairs, mOmpParFor, - mAppendStrCh, mAppendStrStr, mAppendSeqElem, - mInSet, mRepr, mExit, - mSetLengthStr, mSetLengthSeq, - mIsPartOf, mAstToStr, mParallel, - mSwap, mIsNil, mArrToSeq, mOpenArrayToSeq, - mNewString, mNewStringOfCap, mParseBiggestFloat, - mMove, mEnsureMove, mWasMoved, mDup, mDestroy, mTrace, - mDefault, mUnown, mFinished, mIsolate, mAccessEnv, mAccessTypeField, - mArray, mOpenArray, mRange, mSet, mSeq, mVarargs, - mRef, mPtr, mVar, mDistinct, mVoid, mTuple, - mOrdinal, mIterableType, - mInt, mInt8, mInt16, mInt32, mInt64, - mUInt, mUInt8, mUInt16, mUInt32, mUInt64, - mFloat, mFloat32, mFloat64, mFloat128, - mBool, mChar, mString, mCstring, - mPointer, mNil, mExpr, mStmt, mTypeDesc, - mVoidType, mPNimrodNode, mSpawn, mDeepCopy, - mIsMainModule, mCompileDate, mCompileTime, mProcCall, - mCpuEndian, mHostOS, mHostCPU, mBuildOS, mBuildCPU, mAppType, - mCompileOption, mCompileOptionArg, - mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, - mNKind, mNSymKind, - - mNccValue, mNccInc, mNcsAdd, mNcsIncl, mNcsLen, mNcsAt, - mNctPut, mNctLen, mNctGet, mNctHasNext, mNctNext, - - mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal, - mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetStrVal, mNLineInfo, - mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mNSigHash, mNSizeOf, - mNBindSym, mNCallSite, - mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNGenSym, - mNHint, mNWarning, mNError, - mInstantiationInfo, mGetTypeInfo, mGetTypeInfoV2, - mNimvm, mIntDefine, mStrDefine, mBoolDefine, mGenericDefine, mRunnableExamples, - mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf, - mSymIsInstantiationOf, mNodeId, mPrivateAccess, mZeroDefault - - -const - # things that we can evaluate safely at compile time, even if not asked for it: - ctfeWhitelist* = {mNone, mSucc, - mPred, mInc, mDec, mOrd, mLengthOpenArray, - mLengthStr, mLengthArray, mLengthSeq, - mArrGet, mArrPut, mAsgn, mDestroy, - mIncl, mExcl, mCard, mChr, - mAddI, mSubI, mMulI, mDivI, mModI, - mAddF64, mSubF64, mMulF64, mDivF64, - mShrI, mShlI, mBitandI, mBitorI, mBitxorI, - mMinI, mMaxI, - mAddU, mSubU, mMulU, mDivU, mModU, - mEqI, mLeI, mLtI, - mEqF64, mLeF64, mLtF64, - mLeU, mLtU, - mEqEnum, mLeEnum, mLtEnum, - mEqCh, mLeCh, mLtCh, - mEqB, mLeB, mLtB, - mEqRef, mEqProc, mLePtr, mLtPtr, mEqCString, mXor, - mUnaryMinusI, mUnaryMinusI64, mAbsI, mNot, mUnaryPlusI, mBitnotI, - mUnaryPlusF64, mUnaryMinusF64, - mCharToStr, mBoolToStr, - mCStrToStr, - mStrToStr, mEnumToStr, - mAnd, mOr, - mEqStr, mLeStr, mLtStr, - mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet, mXorSet, - mConStrStr, mAppendStrCh, mAppendStrStr, mAppendSeqElem, - mInSet, mRepr, mOpenArrayToSeq} - - generatedMagics* = {mNone, mIsolate, mFinished, mOpenArrayToSeq} - ## magics that are generated as normal procs in the backend - -type - ItemId* = object - module*: int32 - item*: int32 - -proc `$`*(x: ItemId): string = - "(module: " & $x.module & ", item: " & $x.item & ")" - -proc `==`*(a, b: ItemId): bool {.inline.} = - a.item == b.item and a.module == b.module - -proc hash*(x: ItemId): Hash = - var h: Hash = hash(x.module) - h = h !& hash(x.item) - result = !$h - - -type - PNode* = ref TNode - TNodeSeq* = seq[PNode] - PType* = ref TType - PSym* = ref TSym - TNode*{.final, acyclic.} = object # on a 32bit machine, this takes 32 bytes - when defined(useNodeIds): - id*: int - typField: PType - info*: TLineInfo - flags*: TNodeFlags - case kind*: TNodeKind - of nkCharLit..nkUInt64Lit: - intVal*: BiggestInt - of nkFloatLit..nkFloat128Lit: - floatVal*: BiggestFloat - of nkStrLit..nkTripleStrLit: - strVal*: string - of nkSym: - sym*: PSym - of nkIdent: - ident*: PIdent - else: - sons*: TNodeSeq - when defined(nimsuggest): - endInfo*: TLineInfo - - TStrTable* = object # a table[PIdent] of PSym - counter*: int - data*: seq[PSym] - - # -------------- backend information ------------------------------- - TLocKind* = enum - locNone, # no location - locTemp, # temporary location - locLocalVar, # location is a local variable - locGlobalVar, # location is a global variable - locParam, # location is a parameter - locField, # location is a record field - locExpr, # "location" is really an expression - locProc, # location is a proc (an address of a procedure) - locData, # location is a constant - locCall, # location is a call expression - locOther # location is something other - TLocFlag* = enum - lfIndirect, # backend introduced a pointer - lfNoDeepCopy, # no need for a deep copy - lfNoDecl, # do not declare it in C - lfDynamicLib, # link symbol to dynamic library - lfExportLib, # export symbol for dynamic library generation - lfHeader, # include header file for symbol - lfImportCompilerProc, # ``importc`` of a compilerproc - lfSingleUse # no location yet and will only be used once - lfEnforceDeref # a copyMem is required to dereference if this a - # ptr array due to C array limitations. - # See #1181, #6422, #11171 - lfPrepareForMutation # string location is about to be mutated (V2) - TStorageLoc* = enum - OnUnknown, # location is unknown (stack, heap or static) - OnStatic, # in a static section - OnStack, # location is on hardware stack - OnHeap # location is on heap or global - # (reference counting needed) - TLocFlags* = set[TLocFlag] - TLoc* = object - k*: TLocKind # kind of location - storage*: TStorageLoc - flags*: TLocFlags # location's flags - lode*: PNode # Node where the location came from; can be faked - snippet*: Rope # C code snippet of location (code generators) - - # ---------------- end of backend information ------------------------------ - - TLibKind* = enum - libHeader, libDynamic - - TLib* = object # also misused for headers! - # keep in sync with PackedLib - kind*: TLibKind - generated*: bool # needed for the backends: - isOverridden*: bool - name*: Rope - path*: PNode # can be a string literal! - - - CompilesId* = int ## id that is used for the caching logic within - ## ``system.compiles``. See the seminst module. - TInstantiation* = object - sym*: PSym - concreteTypes*: seq[PType] - compilesId*: CompilesId - - PInstantiation* = ref TInstantiation - - TScope* {.acyclic.} = object - depthLevel*: int - symbols*: TStrTable - parent*: PScope - allowPrivateAccess*: seq[PSym] # # enable access to private fields - - PScope* = ref TScope - - PLib* = ref TLib - TSym* {.acyclic.} = object # Keep in sync with PackedSym - itemId*: ItemId - # proc and type instantiations are cached in the generic symbol - case kind*: TSymKind - of routineKinds: - #procInstCache*: seq[PInstantiation] - gcUnsafetyReason*: PSym # for better error messages regarding gcsafe - transformedBody*: PNode # cached body after transf pass - of skLet, skVar, skField, skForVar: - guard*: PSym - bitsize*: int - alignment*: int # for alignment - else: nil - magic*: TMagic - typ*: PType - name*: PIdent - info*: TLineInfo - when defined(nimsuggest): - endInfo*: TLineInfo - hasUserSpecifiedType*: bool # used for determining whether to display inlay type hints - ownerField: PSym - flags*: TSymFlags - ast*: PNode # syntax tree of proc, iterator, etc.: - # the whole proc including header; this is used - # for easy generation of proper error messages - # for variant record fields the discriminant - # expression - # for modules, it's a placeholder for compiler - # generated code that will be appended to the - # module after the sem pass (see appendToModule) - options*: TOptions - position*: int # used for many different things: - # for enum fields its position; - # for fields its offset - # for parameters its position (starting with 0) - # for a conditional: - # 1 iff the symbol is defined, else 0 - # (or not in symbol table) - # for modules, an unique index corresponding - # to the module's fileIdx - # for variables a slot index for the evaluator - offset*: int32 # offset of record field - disamb*: int32 # disambiguation number; the basic idea is that - # `___` is unique - loc*: TLoc - annex*: PLib # additional fields (seldom used, so we use a - # reference to another object to save space) - when hasFFI: - cname*: string # resolved C declaration name in importc decl, e.g.: - # proc fun() {.importc: "$1aux".} => cname = funaux - constraint*: PNode # additional constraints like 'lit|result'; also - # misused for the codegenDecl and virtual pragmas in the hope - # it won't cause problems - # for skModule the string literal to output for - # deprecated modules. - instantiatedFrom*: PSym # for instances, the generic symbol where it came from. - when defined(nimsuggest): - allUsages*: seq[TLineInfo] - - TTypeSeq* = seq[PType] - - TTypeAttachedOp* = enum ## as usual, order is important here - attachedWasMoved, - attachedDestructor, - attachedAsgn, - attachedDup, - attachedSink, - attachedTrace, - attachedDeepCopy - - TType* {.acyclic.} = object # \ - # types are identical iff they have the - # same id; there may be multiple copies of a type - # in memory! - # Keep in sync with PackedType - itemId*: ItemId - kind*: TTypeKind # kind of type - callConv*: TCallingConvention # for procs - flags*: TTypeFlags # flags of the type - sons: TTypeSeq # base types, etc. - n*: PNode # node for types: - # for range types a nkRange node - # for record types a nkRecord node - # for enum types a list of symbols - # if kind == tyInt: it is an 'int literal(x)' type - # for procs and tyGenericBody, it's the - # formal param list - # for concepts, the concept body - # else: unused - ownerField: PSym # the 'owner' of the type - sym*: PSym # types have the sym associated with them - # it is used for converting types to strings - size*: BiggestInt # the size of the type in bytes - # -1 means that the size is unkwown - align*: int16 # the type's alignment requirements - paddingAtEnd*: int16 # - loc*: TLoc - typeInst*: PType # for generic instantiations the tyGenericInst that led to this - # type. - uniqueId*: ItemId # due to a design mistake, we need to keep the real ID here as it - # is required by the --incremental:on mode. - - TPair* = object - key*, val*: RootRef - - TPairSeq* = seq[TPair] - - TNodePair* = object - h*: Hash # because it is expensive to compute! - key*: PNode - val*: int - - TNodePairSeq* = seq[TNodePair] - TNodeTable* = object # the same as table[PNode] of int; - # nodes are compared by structure! - counter*: int - data*: TNodePairSeq - - TObjectSeq* = seq[RootRef] - TObjectSet* = object - counter*: int - data*: TObjectSeq - - TImplication* = enum - impUnknown, impNo, impYes +import astdef +export astdef + +when not defined(nimKochBootstrap): + import ast2nif + +when not defined(nimKochBootstrap): + var program* {.threadvar.}: DecodeContext + +proc setupProgram*(config: ConfigRef; cache: IdentCache) = + when not defined(nimKochBootstrap): + program = createDecodeContext(config, cache) + +template loadSym(s: PSym) = + ## Loads a symbol from NIF file if it's in Partial state. + when not defined(nimKochBootstrap): + ast2nif.loadSym(program, s) + +template loadType(t: PType) = + ## Loads a type from NIF file if it's in Partial state. + when not defined(nimKochBootstrap): + ast2nif.loadType(program, t) + +proc loadSymCallback*(s: PSym) {.nimcall.} = + loadSym(s) + +proc loadTypeCallback*(t: PType) {.nimcall.} = + loadType(t) + +proc ensureMutable*(s: PSym) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + +proc ensureMutable*(t: PType) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + +proc backendEnsureMutable*(s: PSym) {.inline.} = + #assert s.state != Sealed + # ^ IC review this later + if s.state == Partial: loadSym(s) + +proc backendEnsureMutable*(t: PType) {.inline.} = + #assert t.state != Sealed + # ^ IC review this later + if t.state == Partial: loadType(t) + +proc owner*(s: PSym): PSym {.inline.} = + if s.state == Partial: loadSym(s) + result = s.ownerFieldImpl + +proc owner*(s: PType): PSym {.inline.} = + if s.state == Partial: loadType(s) + result = s.ownerFieldImpl + +proc setOwner*(s: PSym; owner: PSym) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.ownerFieldImpl = owner + +proc setOwner*(s: PType; owner: PSym) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadType(s) + s.ownerFieldImpl = owner + +proc kind*(s: PSym): TSymKind {.inline.} = + if s.state == Partial: loadSym(s) + result = s.kindImpl + +proc `kind=`*(s: PSym, val: TSymKind) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.kindImpl = val + +proc gcUnsafetyReason*(s: PSym): PSym {.inline.} = + if s.state == Partial: loadSym(s) + result = s.gcUnsafetyReasonImpl + +proc `gcUnsafetyReason=`*(s: PSym, val: PSym) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.gcUnsafetyReasonImpl = val + +proc transformedBody*(s: PSym): PNode {.inline.} = + if s.state == Partial: loadSym(s) + result = s.transformedBodyImpl + +proc `transformedBody=`*(s: PSym, val: PNode) {.inline.} = + #assert s.state != Sealed + # Make an exception here for this misfeature... + if s.state == Partial: loadSym(s) + s.transformedBodyImpl = val + +proc guard*(s: PSym): PSym {.inline.} = + if s.state == Partial: loadSym(s) + result = s.guardImpl + +proc `guard=`*(s: PSym, val: PSym) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.guardImpl = val + +proc bitsize*(s: PSym): int {.inline.} = + if s.state == Partial: loadSym(s) + result = s.bitsizeImpl + +proc `bitsize=`*(s: PSym, val: int) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.bitsizeImpl = val + +proc alignment*(s: PSym): int {.inline.} = + if s.state == Partial: loadSym(s) + result = s.alignmentImpl + +proc `alignment=`*(s: PSym, val: int) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.alignmentImpl = val + +proc magic*(s: PSym): TMagic {.inline.} = + if s.state == Partial: loadSym(s) + result = s.magicImpl + +proc `magic=`*(s: PSym, val: TMagic) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.magicImpl = val + +proc typ*(s: PSym): PType {.inline.} = + if s.state == Partial: loadSym(s) + result = s.typImpl + +proc `typ=`*(s: PSym, val: PType) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.typImpl = val + +proc info*(s: PSym): TLineInfo {.inline.} = + if s.state == Partial: loadSym(s) + result = s.infoImpl + +proc `info=`*(s: PSym, val: TLineInfo) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.infoImpl = val + +when defined(nimsuggest): + proc endInfo*(s: PSym): TLineInfo {.inline.} = + if s.state == Partial: loadSym(s) + result = s.endInfoImpl + + proc `endInfo=`*(s: PSym, val: TLineInfo) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.endInfoImpl = val + + proc hasUserSpecifiedType*(s: PSym): bool {.inline.} = + if s.state == Partial: loadSym(s) + result = s.hasUserSpecifiedTypeImpl + + proc `hasUserSpecifiedType=`*(s: PSym, val: bool) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.hasUserSpecifiedTypeImpl = val + +proc flags*(s: PSym): TSymFlags {.inline.} = + if s.state == Partial: loadSym(s) + result = s.flagsImpl + +proc `flags=`*(s: PSym, val: TSymFlags) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.flagsImpl = val + +proc ast*(s: PSym): PNode {.inline.} = + if s.state == Partial: loadSym(s) + result = s.astImpl + +proc `ast=`*(s: PSym, val: PNode) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.astImpl = val + +proc options*(s: PSym): TOptions {.inline.} = + if s.state == Partial: loadSym(s) + result = s.optionsImpl + +proc `options=`*(s: PSym, val: TOptions) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.optionsImpl = val + +proc position*(s: PSym): int {.inline.} = + if s.state == Partial: loadSym(s) + result = s.positionImpl + +proc `position=`*(s: PSym, val: int) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.positionImpl = val + +proc offset*(s: PSym): int32 {.inline.} = + if s.state == Partial: loadSym(s) + result = s.offsetImpl + +proc `offset=`*(s: PSym, val: int32) {.inline.} = + #assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.offsetImpl = val + +proc loc*(s: PSym): TLoc {.inline.} = + if s.state == Partial: loadSym(s) + result = s.locImpl + +proc `loc=`*(s: PSym, val: TLoc) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.locImpl = val + +proc annex*(s: PSym): PLib {.inline.} = + if s.state == Partial: loadSym(s) + result = s.annexImpl + +proc `annex=`*(s: PSym, val: PLib) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.annexImpl = val + +when hasFFI: + proc cname*(s: PSym): string {.inline.} = + if s.state == Partial: loadSym(s) + result = s.cnameImpl + + proc `cname=`*(s: PSym, val: string) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.cnameImpl = val + +proc constraint*(s: PSym): PNode {.inline.} = + if s.state == Partial: loadSym(s) + result = s.constraintImpl + +proc `constraint=`*(s: PSym, val: PNode) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.constraintImpl = val + +proc instantiatedFrom*(s: PSym): PSym {.inline.} = + if s.state == Partial: loadSym(s) + result = s.instantiatedFromImpl + +proc `instantiatedFrom=`*(s: PSym, val: PSym) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.instantiatedFromImpl = val + +proc setSnippet*(s: PSym; val: sink string) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.locImpl.snippet = val + +proc incl*(s: PSym; flag: TSymFlag) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.flagsImpl.incl(flag) + +proc incl*(s: PSym; flags: set[TSymFlag]) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.flagsImpl.incl(flags) + +proc incl*(s: PSym; flag: TLocFlag) {.inline.} = + #assert s.state != Sealed + # locImpl is a backend field so do not protect it against mutations + if s.state == Partial: loadSym(s) + s.locImpl.flags.incl(flag) + +proc excl*(s: PSym; flag: TSymFlag) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.flagsImpl.excl(flag) + +when defined(nimsuggest): + proc allUsages*(s: PSym): var seq[TLineInfo] {.inline.} = + if s.state == Partial: loadSym(s) + result = s.allUsagesImpl + + proc `allUsages=`*(s: PSym, val: sink seq[TLineInfo]) {.inline.} = + assert s.state != Sealed + if s.state == Partial: loadSym(s) + s.allUsagesImpl = val + +# Accessor procs for TType fields +proc callConv*(t: PType): TCallingConvention {.inline.} = + if t.state == Partial: loadType(t) + result = t.callConvImpl + +proc `callConv=`*(t: PType, val: TCallingConvention) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.callConvImpl = val + +proc flags*(t: PType): TTypeFlags {.inline.} = + if t.state == Partial: loadType(t) + result = t.flagsImpl + +proc `flags=`*(t: PType, val: TTypeFlags) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.flagsImpl = val + +proc sons*(t: PType): var TTypeSeq {.inline.} = + if t.state == Partial: loadType(t) + result = t.sonsImpl + +proc `sons=`*(t: PType, val: sink TTypeSeq) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.sonsImpl = val + +proc n*(t: PType): PNode {.inline.} = + if t.state == Partial: loadType(t) + result = t.nImpl + +proc `n=`*(t: PType, val: PNode) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.nImpl = val + +proc sym*(t: PType): PSym {.inline.} = + if t.state == Partial: loadType(t) + result = t.symImpl + +proc `sym=`*(t: PType, val: PSym) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.symImpl = val + +proc size*(t: PType): BiggestInt {.inline.} = + if t.state == Partial: loadType(t) + result = t.sizeImpl + +proc `size=`*(t: PType, val: BiggestInt) {.inline.} = + backendEnsureMutable t + t.sizeImpl = val + +proc align*(t: PType): int16 {.inline.} = + if t.state == Partial: loadType(t) + result = t.alignImpl + +proc `align=`*(t: PType, val: int16) {.inline.} = + backendEnsureMutable t + t.alignImpl = val + +proc paddingAtEnd*(t: PType): int16 {.inline.} = + if t.state == Partial: loadType(t) + result = t.paddingAtEndImpl + +proc `paddingAtEnd=`*(t: PType, val: int16) {.inline.} = + backendEnsureMutable t + t.paddingAtEndImpl = val + +proc loc*(t: PType): TLoc {.inline.} = + if t.state == Partial: loadType(t) + result = t.locImpl + +proc `loc=`*(t: PType, val: TLoc) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.locImpl = val + +proc typeInst*(t: PType): PType {.inline.} = + if t.state == Partial: loadType(t) + result = t.typeInstImpl + +proc `typeInst=`*(t: PType, val: PType) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.typeInstImpl = val + +proc incl*(t: PType; flag: TTypeFlag) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.flagsImpl.incl(flag) + +proc incl*(t: PType; flags: set[TTypeFlag]) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.flagsImpl.incl(flags) + +proc excl*(t: PType; flag: TTypeFlag) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.flagsImpl.excl(flag) + +proc excl*(t: PType; flags: set[TTypeFlag]) {.inline.} = + assert t.state != Sealed + if t.state == Partial: loadType(t) + t.flagsImpl.excl(flags) + +proc typ*(n: PNode): PType {.inline.} = + result = n.typField + if result == nil and nfLazyType in n.flags: + result = n.sym.typ + +proc `typ=`*(n: PNode, val: sink PType) {.inline.} = + n.typField = val template nodeId(n: PNode): int = cast[int](n) -template typ*(n: PNode): PType = - n.typField - -proc owner*(s: PSym|PType): PSym {.inline.} = - result = s.ownerField - -proc setOwner*(s: PSym|PType, owner: PSym) {.inline.} = - s.ownerField = owner - type Gconfig = object # we put comments in a side channel to avoid increasing `sizeof(TNode)`, which # reduces memory usage given that `PNode` is the most allocated type by far. @@ -861,88 +470,22 @@ proc `comment=`*(n: PNode, a: string) = # same name as an imported module. This is necessary because of # the poor naming choices in the standard library. -const - OverloadableSyms* = {skProc, skFunc, skMethod, skIterator, - skConverter, skModule, skTemplate, skMacro, skEnumField} - - GenericTypes*: TTypeKinds = {tyGenericInvocation, tyGenericBody, - tyGenericParam} - - StructuralEquivTypes*: TTypeKinds = {tyNil, tyTuple, tyArray, - tySet, tyRange, tyPtr, tyRef, tyVar, tyLent, tySequence, tyProc, tyOpenArray, - tyVarargs} - - ConcreteTypes*: TTypeKinds = { # types of the expr that may occur in:: - # var x = expr - tyBool, tyChar, tyEnum, tyArray, tyObject, - tySet, tyTuple, tyRange, tyPtr, tyRef, tyVar, tyLent, tySequence, tyProc, - tyPointer, - tyOpenArray, tyString, tyCstring, tyInt..tyInt64, tyFloat..tyFloat128, - tyUInt..tyUInt64} - IntegralTypes* = {tyBool, tyChar, tyEnum, tyInt..tyInt64, - tyFloat..tyFloat128, tyUInt..tyUInt64} # weird name because it contains tyFloat - ConstantDataTypes*: TTypeKinds = {tyArray, tySet, - tyTuple, tySequence} - NilableTypes*: TTypeKinds = {tyPointer, tyCstring, tyRef, tyPtr, - tyProc, tyError} # TODO - PtrLikeKinds*: TTypeKinds = {tyPointer, tyPtr} # for VM - PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, - nfDotSetter, nfDotField, - nfIsRef, nfIsPtr, nfPreventCg, nfLL, - nfFromTemplate, nfDefaultRefsParam, - nfExecuteOnReload, nfLastRead, - nfFirstWrite, nfSkipFieldChecking, - nfDisabledOpenSym} - namePos* = 0 - patternPos* = 1 # empty except for term rewriting macros - genericParamsPos* = 2 - paramsPos* = 3 - pragmasPos* = 4 - miscPos* = 5 # used for undocumented and hacky stuff - bodyPos* = 6 # position of body; use rodread.getBody() instead! - resultPos* = 7 - dispatcherPos* = 8 - - nfAllFieldsSet* = nfBase2 - - nkIdentKinds* = {nkIdent, nkSym, nkAccQuoted, nkOpenSymChoice, - nkClosedSymChoice, nkOpenSym} - - nkPragmaCallKinds* = {nkExprColonExpr, nkCall, nkCallStrLit} - nkLiterals* = {nkCharLit..nkTripleStrLit} - nkFloatLiterals* = {nkFloatLit..nkFloat128Lit} - nkLambdaKinds* = {nkLambda, nkDo} - declarativeDefs* = {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef} - routineDefs* = declarativeDefs + {nkMacroDef, nkTemplateDef} - procDefs* = nkLambdaKinds + declarativeDefs - callableDefs* = nkLambdaKinds + routineDefs - - nkSymChoices* = {nkClosedSymChoice, nkOpenSymChoice} - nkStrKinds* = {nkStrLit..nkTripleStrLit} - - skLocalVars* = {skVar, skLet, skForVar, skParam, skResult} - skProcKinds* = {skProc, skFunc, skTemplate, skMacro, skIterator, - skMethod, skConverter} - - defaultSize = -1 - defaultAlignment = -1 - defaultOffset* = -1 - proc getPIdent*(a: PNode): PIdent {.inline.} = ## Returns underlying `PIdent` for `{nkSym, nkIdent}`, or `nil`. case a.kind of nkSym: a.sym.name of nkIdent: a.ident - of nkOpenSymChoice, nkClosedSymChoice: a.sons[0].sym.name - of nkOpenSym: getPIdent(a.sons[0]) + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: a.sons[0].sym.name else: nil const moduleShift = when defined(cpu32): 20 else: 24 -template id*(a: PType | PSym): int = +template toId*(a: ItemId): int = let x = a - (x.itemId.module.int shl moduleShift) + x.itemId.item.int + (x.module.int shl moduleShift) + x.item.int + +template id*(a: PType | PSym): int = toId(a.itemId) type IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here. @@ -958,6 +501,7 @@ const proc idGeneratorFromModule*(m: PSym): IdGenerator = assert m.kind == skModule result = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0, disambTable: initCountTable[PIdent]()) + result.disambTable.inc m.name proc idGeneratorForPackage*(nextIdWillBe: int32): IdGenerator = result = IdGenerator(module: PackageModuleId, symId: nextIdWillBe - 1'i32, typeId: 0, disambTable: initCountTable[PIdent]()) @@ -992,14 +536,6 @@ proc isCallExpr*(n: PNode): bool = proc discardSons*(father: PNode) -proc len*(n: PNode): int {.inline.} = - result = n.sons.len - -proc safeLen*(n: PNode): int {.inline.} = - ## works even for leaves. - if n.kind in {nkNone..nkNilLit}: result = 0 - else: result = n.len - proc safeArrLen*(n: PNode): int {.inline.} = ## works for array-like objects (strings passed as openArray in VM). if n.kind in {nkStrLit..nkTripleStrLit}: result = n.strVal.len @@ -1013,24 +549,40 @@ proc add*(father, son: PNode) = proc addAllowNil*(father, son: PNode) {.inline.} = father.sons.add(son) -template `[]`*(n: PNode, i: int): PNode = n.sons[i] -template `[]=`*(n: PNode, i: int; x: PNode) = n.sons[i] = x - -template `[]`*(n: PNode, i: BackwardsIndex): PNode = n[n.len - i.int] -template `[]=`*(n: PNode, i: BackwardsIndex; x: PNode) = n[n.len - i.int] = x - proc add*(father, son: PType) = + ensureMutable father + assert father.kind != tyProc or father.sonsImpl.len == 0 assert son != nil - father.sons.add(son) + father.sonsImpl.add son proc addAllowNil*(father, son: PType) {.inline.} = - father.sons.add(son) + ensureMutable father + assert father.kind != tyProc or father.sonsImpl.len == 0 + father.sonsImpl.add son -template `[]`*(n: PType, i: int): PType = n.sons[i] -template `[]=`*(n: PType, i: int; x: PType) = n.sons[i] = x +proc `[]`*(n: PType, i: int): PType {.inline.} = + if n.state == Partial: loadType(n) + if n.kind == tyProc and i > 0: + assert n.nImpl[i] != nil and n.nImpl[i].sym != nil + n.nImpl[i].sym.typ + else: + n.sonsImpl[i] -template `[]`*(n: PType, i: BackwardsIndex): PType = n[n.len - i.int] -template `[]=`*(n: PType, i: BackwardsIndex; x: PType) = n[n.len - i.int] = x +proc `[]=`*(n: PType, i: int; x: PType) {.inline.} = + if n.state == Partial: loadType(n) + if n.kind == tyProc and i > 0: + assert n.nImpl[i] != nil and n.nImpl[i].sym != nil + n.nImpl[i].sym.typ = x + else: + n.sonsImpl[i] = x + +proc `[]`*(n: PType, i: BackwardsIndex): PType {.inline.} = + if n.state == Partial: loadType(n) + n[n.sonsImpl.len - i.int] + +proc `[]=`*(n: PType, i: BackwardsIndex; x: PType) {.inline.} = + if n.state == Partial: loadType(n) + n[n.sonsImpl.len - i.int] = x proc getDeclPragma*(n: PNode): PNode = ## return the `nkPragma` node for declaration `n`, or `nil` if no pragma was found. @@ -1069,15 +621,17 @@ proc getDeclPragma*(n: PNode): PNode = proc extractPragma*(s: PSym): PNode = ## gets the pragma node of routine/type/var/let/const symbol `s` if s.kind in routineKinds: # bug #24167 - if s.ast[pragmasPos] != nil and s.ast[pragmasPos].kind != nkEmpty: - result = s.ast[pragmasPos] + let astVal = s.ast + if astVal != nil and astVal[pragmasPos] != nil and astVal[pragmasPos].kind != nkEmpty: + result = astVal[pragmasPos] else: result = nil elif s.kind in {skType, skVar, skLet, skConst}: - if s.ast != nil and s.ast.len > 0: - if s.ast[0].kind == nkPragmaExpr and s.ast[0].len > 1: + let astVal = s.ast + if astVal != nil and astVal.len > 0: + if astVal[0].kind == nkPragmaExpr and astVal[0].len > 1: # s.ast = nkTypedef / nkPragmaExpr / [nkSym, nkPragma] - result = s.ast[0][1] + result = astVal[0][1] else: result = nil else: @@ -1100,56 +654,6 @@ proc setInfoRecursive*(n: PNode, info: TLineInfo) = for i in 0.. 0: - newSeq(result.sons, children) - setIdMaybe() - -proc newNodeIT*(kind: TNodeKind, info: TLineInfo, typ: PType): PNode = - ## new node with line info, type, and no children - result = newNode(kind) - result.info = info - result.typ() = typ - -proc newNode*(kind: TNodeKind, info: TLineInfo): PNode = - ## new node with line info, no type, and no children - newNodeImpl(info) - setIdMaybe() - proc newAtom*(ident: PIdent, info: TLineInfo): PNode = result = newNode(nkIdent, info) result.ident = ident @@ -1207,8 +711,8 @@ proc newSym*(symKind: TSymKind, name: PIdent, idgen: IdGenerator; owner: PSym, # generates a symbol and initializes the hash field too assert not name.isNil let id = nextSymId idgen - result = PSym(name: name, kind: symKind, flags: {}, info: info, itemId: id, - options: options, ownerField: owner, offset: defaultOffset, + result = PSym(name: name, kindImpl: symKind, flagsImpl: {}, infoImpl: info, itemId: id, + optionsImpl: options, ownerFieldImpl: owner, offsetImpl: defaultOffset, disamb: getOrDefault(idgen.disambTable, name).int32) idgen.disambTable.inc name when false: @@ -1219,10 +723,11 @@ proc newSym*(symKind: TSymKind, name: PIdent, idgen: IdGenerator; owner: PSym, proc astdef*(s: PSym): PNode = # get only the definition (initializer) portion of the ast - if s.ast != nil and s.ast.kind in {nkIdentDefs, nkConstDef}: - s.ast[2] + let astVal = s.ast + if astVal != nil and astVal.kind in {nkIdentDefs, nkConstDef}: + astVal[2] else: - s.ast + astVal proc isMetaType*(t: PType): bool = return t.kind in tyMetaTypes or @@ -1234,41 +739,41 @@ proc isUnresolvedStatic*(t: PType): bool = proc linkTo*(t: PType, s: PSym): PType {.discardable.} = t.sym = s - s.typ = t + s.typImpl = t result = t proc linkTo*(s: PSym, t: PType): PSym {.discardable.} = t.sym = s - s.typ = t + s.typImpl = t result = s template fileIdx*(c: PSym): FileIndex = # XXX: this should be used only on module symbols - c.position.FileIndex + c.position().FileIndex template filename*(c: PSym): string = # XXX: this should be used only on module symbols - c.position.FileIndex.toFilename + c.position().FileIndex.toFilename proc appendToModule*(m: PSym, n: PNode) = ## The compiler will use this internally to add nodes that will be ## appended to the module after the sem pass - if m.ast == nil: - m.ast = newNode(nkStmtList) - m.ast.sons = @[n] + if m.astImpl == nil: + m.astImpl = newNode(nkStmtList) else: - assert m.ast.kind == nkStmtList - m.ast.sons.add(n) - -const # for all kind of hash tables: - GrowthFactor* = 2 # must be power of 2, > 0 - StartSize* = 8 # must be power of 2, > 0 + assert m.astImpl.kind == nkStmtList + m.astImpl.add(n) proc copyStrTable*(dest: var TStrTable, src: TStrTable) = dest.counter = src.counter setLen(dest.data, src.data.len) for i in 0..high(src.data): dest.data[i] = src.data[i] +proc copyIdTable*[T](dest: var TIdTable[T], src: TIdTable[T]) = + dest.counter = src.counter + newSeq(dest.data, src.data.len) + for i in 0..high(src.data): dest.data[i] = src.data[i] + proc copyObjectSet*(dest: var TObjectSet, src: TObjectSet) = dest.counter = src.counter setLen(dest.data, src.data.len) @@ -1278,26 +783,16 @@ proc discardSons*(father: PNode) = father.sons = @[] proc withInfo*(n: PNode, info: TLineInfo): PNode = + # XXX Dead code. Remove n.info = info return n -proc newIdentNode*(ident: PIdent, info: TLineInfo): PNode = - result = newNode(nkIdent) - result.ident = ident - result.info = info - proc newSymNode*(sym: PSym): PNode = result = newNode(nkSym) result.sym = sym - result.typ() = sym.typ + result.typField = sym.typ result.info = sym.info -proc newSymNode*(sym: PSym, info: TLineInfo): PNode = - result = newNode(nkSym) - result.sym = sym - result.typ() = sym.typ - result.info = info - proc newOpenSym*(n: PNode): PNode {.inline.} = result = newTreeI(nkOpenSym, n.info, n) @@ -1324,27 +819,63 @@ proc replaceFirstSon*(n, newson: PNode) {.inline.} = proc replaceSon*(n: PNode; i: int; newson: PNode) {.inline.} = n.sons[i] = newson -proc last*(n: PType): PType {.inline.} = n.sons[^1] +proc last*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + if n.kind == tyProc and n.nImpl.len > 1: + n.nImpl[^1].sym.typ + else: + n.sonsImpl[^1] -proc elementType*(n: PType): PType {.inline.} = n.sons[^1] -proc skipModifier*(n: PType): PType {.inline.} = n.sons[^1] +proc elementType*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[^1] -proc indexType*(n: PType): PType {.inline.} = n.sons[0] -proc baseClass*(n: PType): PType {.inline.} = n.sons[0] +proc skipModifier*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[^1] + +proc indexType*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[0] + +proc baseClass*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[0] proc base*(t: PType): PType {.inline.} = - result = t.sons[0] + if t.state == Partial: loadType(t) + result = t.sonsImpl[0] -proc returnType*(n: PType): PType {.inline.} = n.sons[0] -proc setReturnType*(n, r: PType) {.inline.} = n.sons[0] = r -proc setIndexType*(n, idx: PType) {.inline.} = n.sons[0] = idx +proc returnType*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[0] -proc firstParamType*(n: PType): PType {.inline.} = n.sons[1] -proc firstGenericParam*(n: PType): PType {.inline.} = n.sons[1] +proc setReturnType*(n, r: PType) {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[0] = r -proc typeBodyImpl*(n: PType): PType {.inline.} = n.sons[^1] +proc setIndexType*(n, idx: PType) {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[0] = idx -proc genericHead*(n: PType): PType {.inline.} = n.sons[0] +proc firstParamType*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + if n.kind == tyProc: + n.nImpl[1].sym.typ + else: + n.sonsImpl[1] + +proc firstGenericParam*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[1] + +proc typeBodyImpl*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[^1] + +proc genericHead*(n: PType): PType {.inline.} = + if n.state == Partial: loadType(n) + n.sonsImpl[0] proc skipTypes*(t: PType, kinds: TTypeKinds): PType = ## Used throughout the compiler code to test whether a type tree contains or @@ -1375,7 +906,7 @@ proc newIntTypeNode*(intVal: BiggestInt, typ: PType): PNode = result = newNode(nkIntLit) else: raiseAssert $kind result.intVal = intVal - result.typ() = typ + result.typField = typ proc newIntTypeNode*(intVal: Int128, typ: PType): PNode = # XXX: introduce range check @@ -1385,14 +916,6 @@ proc newFloatNode*(kind: TNodeKind, floatVal: BiggestFloat): PNode = result = newNode(kind) result.floatVal = floatVal -proc newStrNode*(kind: TNodeKind, strVal: string): PNode = - result = newNode(kind) - result.strVal = strVal - -proc newStrNode*(strVal: string; info: TLineInfo): PNode = - result = newNodeI(nkStrLit, info) - result.strVal = strVal - proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode, params, name, pattern, genericParams, @@ -1411,121 +934,124 @@ proc `$`*(s: PSym): string = else: result = "" -when false: - iterator items*(t: PType): PType = - for i in 0.. 0 +proc genericParamHasConstraints*(t: PType): bool {.inline.} = t.len > 0 -proc hasElementType*(t: PType): bool {.inline.} = t.sons.len > 0 -proc isEmptyTupleType*(t: PType): bool {.inline.} = t.sons.len == 0 -proc isSingletonTupleType*(t: PType): bool {.inline.} = t.sons.len == 1 +proc hasElementType*(t: PType): bool {.inline.} = t.len > 0 +proc isEmptyTupleType*(t: PType): bool {.inline.} = t.len == 0 +proc isSingletonTupleType*(t: PType): bool {.inline.} = t.len == 1 -proc genericConstraint*(t: PType): PType {.inline.} = t.sons[0] +proc genericConstraint*(t: PType): PType {.inline.} = t[0] iterator genericInstParams*(t: PType): (bool, PType) = - for i in 1..= b.sons.len: + for i in 1..= b.len: yield (false, nil, nil) else: - yield (true, a.sons[i], b.sons[i]) + yield (true, a[i], b[i]) iterator genericBodyParams*(t: PType): (int, PType) = - for i in 0.. 1: - setLen(t.sons, 1) + if t.len > 1: + setLen(t.sonsImpl, 1) proc assignType*(dest, src: PType) = dest.kind = src.kind - dest.flags = src.flags - dest.callConv = src.callConv - dest.n = src.n - dest.size = src.size - dest.align = src.align + dest.flagsImpl = src.flags + dest.callConvImpl = src.callConv + dest.nImpl = src.n + dest.sizeImpl = src.size + dest.alignImpl = src.align # this fixes 'type TLock = TSysLock': if src.sym != nil: if dest.sym != nil: - dest.sym.flags.incl src.sym.flags-{sfUsed, sfExported} - if dest.sym.annex == nil: dest.sym.annex = src.sym.annex - mergeLoc(dest.sym.loc, src.sym.loc) + var destFlags = dest.sym.flags + var srcFlags = src.sym.flags + dest.sym.flagsImpl = destFlags + (srcFlags - {sfUsed, sfExported}) + if dest.sym.annex == nil: dest.sym.annexImpl = src.sym.annex + mergeLoc(dest.sym.locImpl, src.sym.loc) else: - dest.sym = src.sym - newSons(dest, src.sons.len) - for i in 0.. 0: + setLen(dest.sonsImpl, 1) + dest.sonsImpl[0] = src.sonsImpl[0] + else: + newSons(dest, src.len) + for i in 0.. genericParamsPos and + hookSym.astImpl[genericParamsPos].kind != nkEmpty + result = HookIndexEntry(typ: typSymId, hook: hookSymId, isGeneric: isGeneric) + +proc toConverterIndexEntry*(config: ConfigRef; converterSym: PSym): (nifstreams.SymId, nifstreams.SymId) = + ## Converts a converter symbol to an index entry (destType, converterSym). + ## Returns the destination type's SymId and the converter's SymId. + # Get the return type of the converter (destination type) + let retType = converterSym.typImpl + if retType != nil and retType.sonsImpl.len > 0: + let destType = retType.sonsImpl[0] # Return type is first son + if destType != nil: + let destTypeSymName = "`t" & $destType.itemId.item & "." & cachedModuleSuffix(config, destType.itemId.module.FileIndex) + let convSymName = converterSym.name.s & "." & $converterSym.disamb & "." & cachedModuleSuffix(config, converterSym.itemId.module.FileIndex) + result = (pool.syms.getOrIncl(destTypeSymName), pool.syms.getOrIncl(convSymName)) + return + # Fallback: return empty entry + result = (nifstreams.SymId(0), nifstreams.SymId(0)) + +proc toMethodIndexEntry*(config: ConfigRef; methodSym: PSym; signature: string): (nifstreams.SymId, nifstreams.StrId) = + ## Converts a method symbol/signature to a method index entry. + let methodSymName = methodSym.name.s & "." & $methodSym.disamb & "." & cachedModuleSuffix(config, methodSym.itemId.module.FileIndex) + result = ( + pool.syms.getOrIncl(methodSymName), + pool.strings.getOrIncl(signature) + ) + +proc toClassSymId*(config: ConfigRef; typeId: ItemId): nifstreams.SymId = + ## Converts a type ItemId to its SymId for the class index. + let typeSymName = "`t" & $typeId.item & "." & cachedModuleSuffix(config, typeId.module.FileIndex) + result = pool.syms.getOrIncl(typeSymName) + +# ---------------- Line info handling ----------------------------------------- + +type + LineInfoWriter = object + fileK: FileIndex # remember the current pair, even faster than the hash table + fileV: FileId + tab: Table[FileIndex, FileId] + revTab: Table[FileId, FileIndex] # reverse mapping for oldLineInfo + man: LineInfoManager + config: ConfigRef + +proc get(w: var LineInfoWriter; key: FileIndex): FileId = + if w.fileK == key: + result = w.fileV + else: + if key in w.tab: + result = w.tab[key] + w.fileK = key + w.fileV = result + else: + result = pool.files.getOrIncl(msgs.toFullPath(w.config, key)) + w.fileK = key + w.fileV = result + w.tab[key] = result + w.revTab[result] = key + +proc nifLineInfo(w: var LineInfoWriter; info: TLineInfo): PackedLineInfo = + if info == unknownLineInfo: + result = NoLineInfo + else: + let fid = get(w, info.fileIndex) + # Must use pool.man since toString uses pool.man to unpack + result = pack(pool.man, fid, info.line.int32, info.col) + +proc oldLineInfo(w: var LineInfoWriter; info: PackedLineInfo): TLineInfo = + if info == NoLineInfo: + result = unknownLineInfo + else: + var x = unpack(pool.man, info) + var fileIdx: FileIndex + if w.fileV == x.file: + fileIdx = w.fileK + elif x.file in w.revTab: + fileIdx = w.revTab[x.file] + else: + # Need to look up FileId -> FileIndex via the file path + let filePath = pool.files[x.file] + fileIdx = msgs.fileInfoIdx(w.config, AbsoluteFile filePath) + w.revTab[x.file] = fileIdx + result = TLineInfo(line: x.line.uint16, col: x.col.int16, fileIndex: fileIdx) + + +# ------------- Writer --------------------------------------------------------------- + +#[ + +Strategy: + +We produce NIF from the PNode structure as the single source of truth. NIF nodes can +however, refer to PSym and PType, these get NIF names. If the PSym/PType belongs to +the module that we are currently writing, we emit these fields as an inner NIF +structure via the special tags `sd` and `td`. In fact it is only these tags +that get the NIF `SymbolDef` kinds so that the lazy loading mechanism cannot +be confused. + +We could also emit non-local symbols and types later as the index structure +will tell us the precise offsets anyway. + +]# + +const + hiddenTypeTagName = "ht" + symDefTagName = "sd" + typeDefTagName = "td" + +let + sdefTag = registerTag(symDefTagName) + tdefTag = registerTag(typeDefTagName) + hiddenTypeTag = registerTag(hiddenTypeTagName) + +type + Writer = object + deps: TokenBuf # include&import deps + infos: LineInfoWriter + currentModule: int32 + decodedFileIndices: HashSet[FileIndex] + locals: HashSet[ItemId] # track proc-local symbols + inProc: int + #writtenTypes: seq[PType] # types written in this module, to be unloaded later + #writtenSyms: seq[PSym] # symbols written in this module, to be unloaded later + writtenPackages: HashSet[string] + +const + # Symbol kinds that are always local to a proc and should never have module suffix + skLocalSymKinds = {skParam, skForVar, skResult, skTemp} + +proc isLocalSym(sym: PSym): bool {.inline.} = + sym.kindImpl in skLocalSymKinds or + (sym.kindImpl in {skVar, skLet} and {sfGlobal, sfThread} * sym.flagsImpl == {} and + (sym.ownerFieldImpl == nil or sym.ownerFieldImpl.kindImpl != skModule)) + +proc toNifSymName(w: var Writer; sym: PSym): string = + ## Generate NIF name for a symbol: local names are `ident.disamb`, + ## global names are `ident.disamb.moduleSuffix` + result = sym.name.s + result.add '.' + result.addInt sym.disamb + if not isLocalSym(sym) and sym.itemId notin w.locals: + # Global symbol: ident.disamb.moduleSuffix + result.add '.' + let module = if sym.kindImpl == skPackage: w.currentModule else: sym.itemId.module + result.add modname(module, w.infos.config) + + +proc globalName(sym: PSym; config: ConfigRef): string = + result = sym.name.s + result.add '.' + result.addInt sym.disamb + result.add '.' + result.add modname(sym.itemId.module, config) + +type + ParsedSymName* = object + name*: string + module*: string + count*: int + +proc parseSymName*(s: string): ParsedSymName = + var i = s.len - 2 + while i > 0: + if s[i] == '.': + if s[i+1] in {'0'..'9'}: + var count = ord(s[i+1]) - ord('0') + var j = i+2 + while j < s.len and s[j] in {'0'..'9'}: + count = count * 10 + ord(s[j]) - ord('0') + inc j + return ParsedSymName(name: substr(s, 0, i-1), module: "", count: count) + else: + let mend = s.high + var b = i-1 + while b > 0 and s[b] != '.': dec b + var j = b+1 + var count = 0 + while j < s.len and s[j] in {'0'..'9'}: + count = count * 10 + ord(s[j]) - ord('0') + inc j + + return ParsedSymName(name: substr(s, 0, b-1), module: substr(s, i+1, mend), count: count) + dec i + return ParsedSymName(name: s, module: "") + +template buildTree(dest: var TokenBuf; tag: TagId; body: untyped) = + dest.addParLe tag + body + dest.addParRi + +template buildTree(dest: var TokenBuf; tag: string; body: untyped) = + buildTree dest, pool.tags.getOrIncl(tag), body + +proc writeFlags[E](dest: var TokenBuf; flags: set[E]) = + var flagsAsIdent = "" + genFlags(flags, flagsAsIdent) + if flagsAsIdent.len > 0: + dest.addIdent flagsAsIdent + else: + dest.addDotToken + +proc trLineInfo(w: var Writer; info: TLineInfo): PackedLineInfo {.inline.} = + result = nifLineInfo(w.infos, info) + +proc writeNode(w: var Writer; dest: var TokenBuf; n: PNode; forAst = false) +proc writeType(w: var Writer; dest: var TokenBuf; typ: PType) +proc writeSym(w: var Writer; dest: var TokenBuf; sym: PSym) + +proc writeLoc(w: var Writer; dest: var TokenBuf; loc: TLoc) = + dest.addIdent toNifTag(loc.k) + dest.addIdent toNifTag(loc.storage) + writeFlags(dest, loc.flags) # TLocFlags + dest.addStrLit loc.snippet + +proc writeTypeDef(w: var Writer; dest: var TokenBuf; typ: PType) = + dest.buildTree tdefTag: + dest.addSymDef pool.syms.getOrIncl(typeToNifSym(typ, w.infos.config)), NoLineInfo + dest.addDotToken # always private for the index generator + + #dest.addIdent toNifTag(typ.kind) + writeFlags(dest, typ.flagsImpl) + dest.addIdent toNifTag(typ.callConvImpl) + dest.addIntLit typ.sizeImpl + dest.addIntLit typ.alignImpl + dest.addIntLit typ.paddingAtEndImpl + dest.addIntLit typ.itemId.item # nonUniqueId + + writeType(w, dest, typ.typeInstImpl) + #if typ.kind in {tyProc, tyIterator} and typ.nImpl != nil and typ.nImpl.kind != nkFormalParams: + + writeNode(w, dest, typ.nImpl) + writeSym(w, dest, typ.ownerFieldImpl) + writeSym(w, dest, typ.symImpl) + + # Write TLoc structure + writeLoc w, dest, typ.locImpl + # we store the type's elements here at the end so that + # it is not ambiguous and saves space: + for ch in typ.sonsImpl: + writeType(w, dest, ch) + + +proc writeType(w: var Writer; dest: var TokenBuf; typ: PType) = + if typ == nil: + dest.addDotToken() + elif typ.itemId.module == w.currentModule and typ.state == Complete: + typ.state = Sealed + writeTypeDef(w, dest, typ) + else: + dest.addSymUse pool.syms.getOrIncl(typeToNifSym(typ, w.infos.config)), NoLineInfo + +proc writeBool(dest: var TokenBuf; b: bool) = + dest.buildTree (if b: "true" else: "false"): + discard + +proc writeLib(w: var Writer; dest: var TokenBuf; lib: PLib) = + if lib == nil: + dest.addDotToken() + else: + dest.buildTree toNifTag(lib.kind): + dest.writeBool lib.generated + dest.writeBool lib.isOverridden + dest.addStrLit lib.name + writeNode w, dest, lib.path + +proc collectGenericParams(w: var Writer; n: PNode) = + ## Pre-collect generic param symbols into w.locals before writing the type. + ## This ensures generic params get consistent short names, and their sdefs + ## are written in the type (where lazy loading can find them). + if n == nil: return + case n.kind + of nkSym: + if n.sym != nil and w.inProc > 0: + w.locals.incl(n.sym.itemId) + of nkIdentDefs, nkVarTuple: + for i in 0 ..< max(0, n.len - 2): + collectGenericParams(w, n[i]) + of nkGenericParams: + for child in n: + collectGenericParams(w, child) + else: + discard + +proc writeSymDef(w: var Writer; dest: var TokenBuf; sym: PSym) = + dest.addParLe sdefTag, trLineInfo(w, sym.infoImpl) + dest.addSymDef pool.syms.getOrIncl(w.toNifSymName(sym)), NoLineInfo + if {sfExported, sfFromGeneric} * sym.flagsImpl == {sfExported}: + dest.addIdent "x" + else: + dest.addDotToken + # field `disamb` made part of the name, so do not store it here + dest.buildTree sym.kindImpl.toNifTag: + case sym.kindImpl + of skLet, skVar, skField, skForVar: + writeSym(w, dest, sym.guardImpl) + dest.addIntLit sym.bitsizeImpl + dest.addIntLit sym.alignmentImpl + else: + discard + + if sym.magicImpl == mNone: + dest.addDotToken + else: + dest.addIdent toNifTag(sym.magicImpl) + writeFlags(dest, sym.flagsImpl) + writeFlags(dest, sym.optionsImpl) + dest.addIntLit sym.offsetImpl + + if sym.kindImpl == skModule: + dest.addDotToken() # position will be set by the loader! + elif sym.kindImpl in {skVar, skLet, skForVar, skResult}: + dest.addIntLit 0 # hack for the VM which uses this field to store information + else: + dest.addIntLit sym.positionImpl + + writeLib(w, dest, sym.annexImpl) + + # For routine symbols, pre-collect generic params into w.locals before writing + # the type. This ensures they get consistent short names, and their sdefs are + # written in the type where lazy loading can find them via extractLocalSymsFromTree. + if sym.kindImpl in routineKinds and sym.astImpl != nil and sym.astImpl.len > genericParamsPos: + inc w.inProc + collectGenericParams(w, sym.astImpl[genericParamsPos]) + dec w.inProc + + writeType(w, dest, sym.typImpl) + writeSym(w, dest, sym.ownerFieldImpl) + # Store the AST for routine symbols and constants + # Constants need their AST for astdef() to return the constant's value + writeNode(w, dest, sym.astImpl, forAst = true) + writeLoc w, dest, sym.locImpl + writeNode(w, dest, sym.constraintImpl) + writeSym(w, dest, sym.instantiatedFromImpl) + dest.addParRi + + +proc shouldWriteSymDef(w: var Writer; sym: PSym): bool {.inline.} = + # Don't write module/package symbols - they don't have NIF files + if sym.kindImpl == skPackage: + return not w.writtenPackages.containsOrIncl(sym.name.s) + # Already written - don't write again + if sym.state == Sealed: + return false + # If the symbol belongs to current module and would be written WITHOUT module suffix + # (due to being in w.locals or being in skLocalSymKinds), it MUST have an sdef. + # Otherwise it gets written as a bare SymUse and can't be found when loading. + if sym.itemId.module == w.currentModule: + if sym.itemId in w.locals or isLocalSym(sym): + return true # Would be written without module suffix, needs sdef + if sym.state == Complete: + return true # Normal case for global symbols + return false + +proc writeSym(w: var Writer; dest: var TokenBuf; sym: PSym) = + if sym == nil: + dest.addDotToken() + elif shouldWriteSymDef(w, sym): + sym.state = Sealed + writeSymDef(w, dest, sym) + else: + # NIF has direct support for symbol references so we don't need to use a tag here, + # unlike what we do for types! + dest.addSymUse pool.syms.getOrIncl(w.toNifSymName(sym)), NoLineInfo + +proc writeSymNode(w: var Writer; dest: var TokenBuf; n: PNode; sym: PSym) = + if sym == nil: + dest.addDotToken() + elif shouldWriteSymDef(w, sym): + sym.state = Sealed + if n.typField != n.sym.typImpl: + dest.buildTree hiddenTypeTag, trLineInfo(w, n.info): + writeType(w, dest, n.typField) + writeSymDef(w, dest, sym) + else: + writeSymDef(w, dest, sym) + else: + # NIF has direct support for symbol references so we don't need to use a tag here, + # unlike what we do for types! + let info = trLineInfo(w, n.info) + if n.typField != n.sym.typImpl: + dest.buildTree hiddenTypeTag, info: + writeType(w, dest, n.typField) + dest.addSymUse pool.syms.getOrIncl(w.toNifSymName(sym)), info + else: + dest.addSymUse pool.syms.getOrIncl(w.toNifSymName(sym)), info + +proc writeNodeFlags(dest: var TokenBuf; flags: set[TNodeFlag]) {.inline.} = + writeFlags(dest, flags) + +template withNode(w: var Writer; dest: var TokenBuf; n: PNode; body: untyped) = + dest.addParLe pool.tags.getOrIncl(toNifTag(n.kind)), trLineInfo(w, n.info) + writeNodeFlags(dest, n.flags) + writeType(w, dest, n.typField) + body + dest.addParRi + +proc addLocalSym(w: var Writer; n: PNode) = + ## Add symbol from a node to locals set if it's a symbol node + if n != nil and n.kind == nkSym and n.sym != nil and w.inProc > 0: + w.locals.incl(n.sym.itemId) + +proc addLocalSyms(w: var Writer; n: PNode) = + case n.kind + of nkIdentDefs, nkVarTuple: + # nkIdentDefs: [ident1, ident2, ..., type, default] + # All children except the last two are identifiers + for i in 0 ..< max(0, n.len - 2): + addLocalSyms(w, n[i]) + of nkPostfix: + addLocalSyms(w, n[1]) + of nkPragmaExpr: + addLocalSyms(w, n[0]) + of nkSym: + addLocalSym(w, n) + else: + discard + +proc trInclude(w: var Writer; n: PNode) = + w.deps.addParLe pool.tags.getOrIncl(toNifTag(n.kind)), trLineInfo(w, n.info) + w.deps.addDotToken # flags + w.deps.addDotToken # type + for child in n: + assert child.kind == nkStrLit + w.deps.addStrLit child.strVal # raw string literal, no wrapper needed + w.deps.addParRi + +proc moduleSuffix(conf: ConfigRef; f: FileIndex): string = + cachedModuleSuffix(conf, f) + +proc trImport(w: var Writer; n: PNode) = + for child in n: + if child.kind == nkSym: + w.deps.addParLe pool.tags.getOrIncl(toNifTag(n.kind)), trLineInfo(w, n.info) + w.deps.addDotToken # flags + w.deps.addDotToken # type + let s = child.sym + assert s.kindImpl == skModule + let fp = moduleSuffix(w.infos.config, s.positionImpl.FileIndex) + w.deps.addStrLit fp # raw string literal, no wrapper needed + w.deps.addParRi + +proc trExport(w: var Writer; n: PNode) = + # Collect export information for the index + # nkExportStmt children are nkSym nodes + # When exporting a module (export dollars), the module symbol is a child + # followed by all symbols from that module - we use empty set to mean "export all" + # When exporting specific symbols (export foo, bar), we collect their names + w.deps.addParLe pool.tags.getOrIncl(toNifTag(n.kind)), trLineInfo(w, n.info) + w.deps.addDotToken # flags + w.deps.addDotToken # type + for child in n: + if child.kind == nkSym: + let s = child.sym + if s.kindImpl == skModule: + discard "do not write module syms here" + else: + w.deps.addSymUse pool.syms.getOrIncl(w.toNifSymName(s)), NoLineInfo + w.deps.addParRi + +let replayTag = registerTag("replay") +let repConverterTag = registerTag("repconverter") +let repDestroyTag = registerTag("repdestroy") +let repWasMovedTag = registerTag("repwasmoved") +let repCopyTag = registerTag("repcopy") +let repSinkTag = registerTag("repsink") +let repDupTag = registerTag("repdup") +let repTraceTag = registerTag("reptrace") +let repDeepCopyTag = registerTag("repdeepcopy") +let repEnumToStrTag = registerTag("repenumtostr") +let repMethodTag = registerTag("repmethod") +#let repClassTag = registerTag("repclass") +let includeTag = registerTag("include") +let importTag = registerTag("import") +let implTag = registerTag("implementation") + +proc writeNode(w: var Writer; dest: var TokenBuf; n: PNode; forAst = false) = + if n == nil: + dest.addDotToken + else: + case n.kind + of nkNone: + assert n.typField == nil, "nkNone should not have a type" + let info = trLineInfo(w, n.info) + dest.addParLe pool.tags.getOrIncl(toNifTag(n.kind)), info + dest.addParRi + of nkEmpty: + if n.typField != nil: + w.withNode dest, n: + discard + else: + let info = trLineInfo(w, n.info) + dest.addParLe pool.tags.getOrIncl(toNifTag(n.kind)), info + dest.addParRi + of nkIdent: + # nkIdent uses flags and typ when it is a generic parameter + w.withNode dest, n: + dest.addIdent n.ident.s + of nkSym: + writeSymNode(w, dest, n, n.sym) + of nkCharLit: + w.withNode dest, n: + dest.add charToken(n.intVal.char, NoLineInfo) + of nkIntLit .. nkInt64Lit: + w.withNode dest, n: + dest.addIntLit n.intVal + of nkUIntLit .. nkUInt64Lit: + w.withNode dest, n: + dest.addUIntLit cast[BiggestUInt](n.intVal) + of nkFloatLit .. nkFloat128Lit: + w.withNode dest, n: + dest.add floatToken(pool.floats.getOrIncl(n.floatVal), NoLineInfo) + of nkStrLit .. nkTripleStrLit: + w.withNode dest, n: + dest.addStrLit n.strVal + of nkNilLit: + w.withNode dest, n: + discard + of nkLetSection, nkVarSection, nkConstSection: + # Track local variables declared in let/var sections + w.withNode dest, n: + for child in n: + addLocalSyms w, child + # Process the child node + writeNode(w, dest, child, forAst) + of nkForStmt: + # Track for loop variable (first child is the loop variable) + w.withNode dest, n: + if n.len > 0: + addLocalSyms(w, n[0]) + for i in 0 ..< n.len: + writeNode(w, dest, n[i], forAst) + of nkFormalParams: + # Track parameters (first child is return type, rest are parameters) + inc w.inProc + w.withNode dest, n: + for i in 0 ..< n.len: + if i > 0: # Skip return type + addLocalSyms(w, n[i]) + writeNode(w, dest, n[i], forAst) + dec w.inProc + of nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef, nkMacroDef, nkTemplateDef: + # For top-level named routines (not forAst), just write the symbol. + # The full AST will be stored in the symbol's sdef. + if not forAst and n[namePos].kind == nkSym: + writeSym(w, dest, n[namePos].sym) + else: + # Writing AST inside sdef or anonymous proc: write full structure + inc w.inProc + var ast = n + var skipParams = false + if n[namePos].kind == nkSym: + ast = n[namePos].sym.astImpl + if ast == nil: ast = n + else: skipParams = true + w.withNode dest, ast: + for i in 0 ..< ast.len: + if i == paramsPos and skipParams: + # Parameter are redundant with s.typ.n and even dangerous as for generic instances + # we do not adapt the symbols properly + addDotToken(dest) + else: + writeNode(w, dest, ast[i], forAst) + dec w.inProc + of nkLambda, nkDo: + # Lambdas are expressions, always write full structure + inc w.inProc + var ast = n + if n[namePos].kind == nkSym: + ast = n[namePos].sym.astImpl + if ast == nil: ast = n + w.withNode dest, ast: + for i in 0 ..< ast.len: + writeNode(w, dest, ast[i], forAst) + dec w.inProc + of nkImportStmt: + # this has been transformed for us, see `importer.nim` to contain a list of module syms: + trImport w, n + of nkIncludeStmt: + trInclude w, n + of nkExportStmt, nkExportExceptStmt: + # Note: nkExportExceptStmt is transformed to nkExportStmt by semExportExcept, + # but we handle both just in case + trExport w, n + else: + w.withNode dest, n: + for i in 0 ..< n.len: + writeNode(w, dest, n[i], forAst) + +proc writeGlobal(w: var Writer; dest: var TokenBuf; n: PNode) = + case n.kind + of nkVarTuple: + writeNode(w, dest, n) + of nkIdentDefs, nkConstDef: + # nkIdentDefs: [ident1, ident2, ..., type, default] + # All children except the last two are identifiers + for i in 0 ..< max(0, n.len - 2): + writeGlobal(w, dest, n[i]) + of nkPostfix: + writeGlobal(w, dest, n[1]) + of nkPragmaExpr: + writeGlobal(w, dest, n[0]) + of nkSym: + writeSym(w, dest, n.sym) + else: + discard + +proc writeGlobals(w: var Writer; dest: var TokenBuf; n: PNode) = + w.withNode dest, n: + for child in n: + writeGlobal(w, dest, child) + +proc writeToplevelNode(w: var Writer; dest, bottom: var TokenBuf; n: PNode) = + case n.kind + of nkStmtList, nkStmtListExpr: + for son in n: writeToplevelNode(w, dest, bottom, son) + of nkEmpty: + discard "ignore" + of nkTypeSection, nkCommentStmt, nkMixinStmt, nkBindStmt, nkUsingStmt, + nkPragma, + nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef, nkMacroDef, nkTemplateDef: + # We write purely declarative nodes at the bottom of the file + writeNode(w, bottom, n) + of nkConstSection: + writeGlobals(w, bottom, n) + of nkLetSection, nkVarSection: + writeGlobals(w, dest, n) + else: + writeNode w, dest, n + +proc createStmtList(buf: var TokenBuf; info: PackedLineInfo) {.inline.} = + buf.addParLe pool.tags.getOrIncl(toNifTag(nkStmtList)), info + buf.addDotToken # flags + buf.addDotToken # type + +proc writeOp(w: var Writer; content: var TokenBuf; op: LogEntry) = + case op.kind + of HookEntry: + case op.op + of attachedDestructor: + content.addParLe repDestroyTag, NoLineInfo + of attachedAsgn: + content.addParLe repCopyTag, NoLineInfo + of attachedWasMoved: + content.addParLe repWasMovedTag, NoLineInfo + of attachedDup: + content.addParLe repDupTag, NoLineInfo + of attachedSink: + content.addParLe repSinkTag, NoLineInfo + of attachedTrace: + content.addParLe repTraceTag, NoLineInfo + of attachedDeepCopy: + content.addParLe repDeepCopyTag, NoLineInfo + content.add strToken(pool.strings.getOrIncl(op.key), NoLineInfo) + content.add symToken(pool.syms.getOrIncl(w.toNifSymName(op.sym)), NoLineInfo) + content.addParRi() + of ConverterEntry: + content.addParLe repConverterTag, NoLineInfo + content.add strToken(pool.strings.getOrIncl(op.key), NoLineInfo) + content.add symToken(pool.syms.getOrIncl(w.toNifSymName(op.sym)), NoLineInfo) + content.addParRi() + of MethodEntry: + discard "to implement" + of EnumToStrEntry: + content.addParLe repEnumToStrTag, NoLineInfo + content.add strToken(pool.strings.getOrIncl(op.key), NoLineInfo) + content.add symToken(pool.syms.getOrIncl(w.toNifSymName(op.sym)), NoLineInfo) + content.addParRi() + of GenericInstEntry: + discard "will only be written later to ensure it is materialized" + +proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode; + opsLog: seq[LogEntry]; + replayActions: seq[PNode] = @[]) = + var w = Writer(infos: LineInfoWriter(config: config), currentModule: thisModule) + var content = createTokenBuf(300) + + let rootInfo = trLineInfo(w, n.info) + createStmtList(content, rootInfo) + + # Write replay actions first, wrapped in a (replay ...) node + if replayActions.len > 0: + content.addParLe replayTag, rootInfo + for action in replayActions: + writeNode(w, content, action) + content.addParRi() + # Only write ops that belong to this module + for op in opsLog: + if op.module == thisModule.int: + writeOp(w, content, op) + + var bottom = createTokenBuf(300) + w.writeToplevelNode content, bottom, n + + # the implTag is used to tell the loader that the + # bottom of the file is the implementation of the module: + content.addParLe implTag, NoLineInfo + content.addParRi() + content.add bottom + content.addParRi() + + let m = modname(w.currentModule, w.infos.config) + let nifFilename = AbsoluteFile(m).changeFileExt(".nif") + let d = completeGeneratedFilePath(config, nifFilename).string + + var dest = createTokenBuf(600) + createStmtList(dest, rootInfo) + dest.add w.deps + # do not write the (stmts .. ) wrapper: + for i in 3 ..< content.len-1: + dest.add content[i] + + # ensure the hooks we announced end up in the NIF file regardless of + # whether they have been used: + for op in opsLog: + if op.module == thisModule.int: + let s = op.sym + if s.state != Sealed: + s.state = Sealed + writeSymDef w, dest, s + + dest.addParRi() + + writeFile(dest, d) + +# --------------------------- Loader (lazy!) ----------------------------------------------- + +proc nodeKind(n: Cursor): TNodeKind {.inline.} = + assert n.kind == ParLe + parse(TNodeKind, pool.tags[n.tagId]) + +proc expect(n: Cursor; k: set[NifKind]) = + if n.kind notin k: + when defined(debug): + writeStackTrace() + quit "[NIF decoder] expected: " & $k & " but got: " & $n.kind & toString n + +proc expect(n: Cursor; k: NifKind) {.inline.} = + expect n, {k} + +proc incExpect(n: var Cursor; k: set[NifKind]) = + inc n + expect n, k + +proc incExpect(n: var Cursor; k: NifKind) {.inline.} = + incExpect n, {k} + +proc skipParRi(n: var Cursor) = + expect n, {ParRi} + inc n + +proc firstSon*(n: Cursor): Cursor {.inline.} = + result = n + inc result + +proc expectTag(n: Cursor; tagId: TagId) = + if n.kind == ParLe and n.tagId == tagId: + discard + else: + when defined(debug): + writeStackTrace() + if n.kind != ParLe: + quit "[NIF decoder] expected: ParLe but got: " & $n.kind & toString n + else: + quit "[NIF decoder] expected: " & pool.tags[tagId] & " but got: " & pool.tags[n.tagId] & toString n + +proc incExpectTag(n: var Cursor; tagId: TagId) = + inc n + expectTag(n, tagId) + +proc loadBool(n: var Cursor): bool = + if n.kind == ParLe: + result = pool.tags[n.tagId] == "true" + inc n + skipParRi n + else: + raiseAssert "(true)/(false) expected" + +type + NifModule = ref object + stream: nifstreams.Stream + symCounter: int32 + index: Table[string, NifIndexEntry] # Simple embedded index for offsets + suffix: string + + DecodeContext* = object + infos: LineInfoWriter + #moduleIds: Table[string, int32] + types: Table[string, (PType, NifIndexEntry)] + syms: Table[string, (PSym, NifIndexEntry)] + mods: Table[FileIndex, NifModule] + cache: IdentCache + +proc createDecodeContext*(config: ConfigRef; cache: IdentCache): DecodeContext = + ## Supposed to be a global variable + result = DecodeContext(infos: LineInfoWriter(config: config), cache: cache) + +proc cursorFromIndexEntry(c: var DecodeContext; module: FileIndex; entry: NifIndexEntry; + buf: var TokenBuf): Cursor = + let s = addr c.mods[module].stream + s.r.jumpTo entry.offset + nifcursors.parse(s[], buf, entry.info) + result = cursorAt(buf, 0) + +type + LoadFlag* = enum + LoadFullAst, AlwaysLoadInterface + +proc readEmbeddedIndex(s: var Stream): Table[string, NifIndexEntry] = + ## Reads the simple embedded index (index (kv sym offset)...) from indexStartsAt position. + result = initTable[string, NifIndexEntry]() + let indexPos = indexStartsAt(s.r) + if indexPos <= 0: + return + let contentPos = offset(s.r) # Save position + s.r.jumpTo(indexPos) + + var previousOffset = 0 + var t = next(s) + let exportedTagId = pool.tags.getOrIncl("x") + if t.kind == ParLe and pool.tags[t.tagId] == ".index": + t = next(s) + while t.kind != EofToken and t.kind != ParRi: + if t.kind == ParLe: + let vis = if t.tagId == exportedTagId: Exported else: Hidden + let info = t.info + t = next(s) # skip (kv + var key = "" + if t.kind == Symbol: + key = pool.syms[t.symId] + elif t.kind == Ident: + key = pool.strings[t.litId] + t = next(s) # skip symbol + if t.kind == IntLit: + let offset = int(pool.integers[t.intId]) + previousOffset + result[key] = NifIndexEntry(offset: offset, info: info, vis: vis) + previousOffset = offset + t = next(s) # skip offset + if t.kind == ParRi: + t = next(s) # skip ) + else: + t = next(s) + + s.r.jumpTo(contentPos) # Restore position + +proc moduleId(c: var DecodeContext; suffix: string; flags: set[LoadFlag] = {}): FileIndex = + var isKnownFile = false + result = c.infos.config.registerNifSuffix(suffix, isKnownFile) + # Always load the module's index if it's not already in c.mods + # This is needed when resolving symbols from modules that were registered elsewhere + # but haven't had their NIF index loaded yet + let hasEntry = c.mods.hasKey(result) + if not hasEntry or AlwaysLoadInterface in flags: + let modFile = (getNimcacheDir(c.infos.config) / RelativeFile(suffix & ".nif")).string + if not fileExists(modFile): + raiseAssert "NIF file not found for module suffix '" & suffix & "': " & modFile & + ". This can happen when loading a module from NIF that references another module " & + "whose NIF file hasn't been written yet." + var stream = nifstreams.open(modFile) + let index = readEmbeddedIndex(stream) + c.mods[result] = NifModule(stream: stream, index: index, suffix: suffix) + +proc getOffset(c: var DecodeContext; module: FileIndex; nifName: string): NifIndexEntry = + let ii = addr c.mods[module].index + result = ii[].getOrDefault(nifName) + if result.offset == 0: + raiseAssert "symbol has no offset: " & nifName + +proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string; + localSyms: var Table[string, PSym]): PNode + +proc loadSymFromCursor(c: var DecodeContext; s: PSym; n: var Cursor; thisModule: string; + localSyms: var Table[string, PSym]) + +proc createTypeStub(c: var DecodeContext; t: SymId): PType = + let name = pool.syms[t] + assert name.startsWith("`t") + result = c.types.getOrDefault(name)[0] + if result == nil: + var i = len("`t") + var k = 0 + while i < name.len and name[i] in {'0'..'9'}: + k = k * 10 + name[i].ord - ord('0') + inc i + if i < name.len and name[i] == '.': inc i + var itemId = 0'i32 + while i < name.len and name[i] in {'0'..'9'}: + itemId = itemId * 10'i32 + int32(name[i].ord - ord('0')) + inc i + if i < name.len and name[i] == '.': inc i + let suffix = name.substr(i) + let id = ItemId(module: moduleId(c, suffix).int32, item: itemId) + let offs = c.getOffset(id.module.FileIndex, name) + result = PType(itemId: id, uniqueId: id, kind: TTypeKind(k), state: Partial) + c.types[name] = (result, offs) + +proc extractLocalSymsFromTree(c: var DecodeContext; n: var Cursor; thisModule: string; + localSyms: var Table[string, PSym]) = + ## Scan a tree for local symbol definitions (sdef tags) and add them to localSyms. + ## For local symbols, fully load them immediately since they have no index offsets. + ## After this proc returns, n is positioned AFTER the tree. + # Handle atoms (non-compound nodes) - just skip them + if n.kind != ParLe: + inc n + return + var depth = 0 + while true: + if n.kind == ParLe: + if n.tagId == sdefTag: + # Found an sdef - check if it's local + let name = n.firstSon + expect name, SymbolDef + let symName = pool.syms[name.symId] + let sn = parseSymName(symName) + if sn.module.len == 0 and symName notin localSyms: + # Local symbol - create stub and immediately load it fully + # since local symbols have no index offsets for lazy loading + let module = moduleId(c, thisModule) + let val = addr c.mods[module].symCounter + inc val[] + let id = ItemId(module: module.int32, item: val[]) + let sym = PSym(itemId: id, kindImpl: skStub, name: c.cache.getIdent(sn.name), + disamb: sn.count.int32, state: Complete) + localSyms[symName] = sym + # Load the full symbol definition immediately + # We're currently at the `(sd` position, need to skip to SymbolDef + inc n # skip past `sd` tag to get to SymbolDef + loadSymFromCursor(c, sym, n, thisModule, localSyms) + sym.state = Sealed # mark as fully loaded + # Continue processing - loadSymFromCursor already advanced n past the closing `)` + continue + inc depth + elif n.kind == ParRi: + dec depth + if depth == 0: + inc n # Move PAST the closing ) + break + inc n + +proc loadTypeFromCursor(c: var DecodeContext; n: var Cursor; t: PType; localSyms: var Table[string, PSym]) + +proc loadTypeStub(c: var DecodeContext; n: var Cursor; localSyms: var Table[string, PSym]): PType = + if n.kind == DotToken: + result = nil + inc n + elif n.kind == Symbol: + let s = n.symId + result = createTypeStub(c, s) + inc n + elif n.kind == ParLe and n.tagId == tdefTag: + let s = n.firstSon.symId + result = createTypeStub(c, s) + if result.state == Partial: + result.state = Sealed # Mark as loaded to prevent loadType from re-loading with empty localSyms + loadTypeFromCursor(c, n, result, localSyms) + else: + skip n # Type already loaded, skip over the td block + else: + raiseAssert "type expected but got " & $n.kind + +proc loadSymStub(c: var DecodeContext; t: SymId; thisModule: string; + localSyms: var Table[string, PSym]): PSym = + let symAsStr = pool.syms[t] + let sn = parseSymName(symAsStr) + # For local symbols (no module suffix), they MUST be in localSyms. + # Local symbols are not in the index - they're defined inline in the NIF file. + # If not found, it's a bug in how we populate localSyms. + if sn.module.len == 0: + result = localSyms.getOrDefault(symAsStr) + if result != nil: + return result + else: + raiseAssert "local symbol '" & symAsStr & "' not found in localSyms." + # Global symbol - look up in index for lazy loading + result = c.syms.getOrDefault(symAsStr)[0] + if result == nil: + let module = moduleId(c, sn.module) + let val = addr c.mods[module].symCounter + inc val[] + let id = ItemId(module: module.int32, item: val[]) + + let offs = c.getOffset(module, symAsStr) + result = PSym(itemId: id, kindImpl: skStub, name: c.cache.getIdent(sn.name), disamb: sn.count.int32, state: Partial) + c.syms[symAsStr] = (result, offs) + +proc loadSymStub(c: var DecodeContext; n: var Cursor; thisModule: string; + localSyms: var Table[string, PSym]): PSym = + if n.kind == DotToken: + result = nil + inc n + elif n.kind == Symbol: + let s = n.symId + result = loadSymStub(c, s, thisModule, localSyms) + inc n + elif n.kind == ParLe and n.tagId == sdefTag: + let s = n.firstSon.symId + skip n + result = loadSymStub(c, s, thisModule, localSyms) + else: + raiseAssert "sym expected but got " & $n.kind + +proc isStub*(t: PType): bool {.inline.} = t.state == Partial +proc isStub*(s: PSym): bool {.inline.} = s.state == Partial + +proc loadAtom[T](t: typedesc[set[T]]; n: var Cursor): set[T] = + if n.kind == DotToken: + result = {} + inc n + else: + expect n, Ident + result = parse(T, pool.strings[n.litId]) + inc n + +proc loadAtom[T: enum](t: typedesc[T]; n: var Cursor): T = + if n.kind == DotToken: + result = default(T) + inc n + else: + expect n, Ident + result = parse(T, pool.strings[n.litId]) + inc n + +proc loadAtom(t: typedesc[string]; n: var Cursor): string = + expect n, StringLit + result = pool.strings[n.litId] + inc n + +proc loadAtom[T: int16|int32|int64](t: typedesc[T]; n: var Cursor): T = + expect n, IntLit + result = pool.integers[n.intId].T + inc n + +template loadField(field) {.dirty.} = + field = loadAtom(typeof(field), n) + +proc loadLoc(c: var DecodeContext; n: var Cursor; loc: var TLoc) = + loadField loc.k + loadField loc.storage + loadField loc.flags + loadField loc.snippet + +proc loadTypeFromCursor(c: var DecodeContext; n: var Cursor; t: PType; localSyms: var Table[string, PSym]) = + expect n, ParLe + if n.tagId != tdefTag: + raiseAssert "(td) expected" + + var scanCursor = n # copy cursor at start of type + let typesModule = parseSymName(pool.syms[n.firstSon.symId]).module + extractLocalSymsFromTree(c, scanCursor, typesModule, localSyms) + + inc n # move past (td + expect n, SymbolDef + # ignore the type's name, we have already used it to create this PType's itemId! + inc n + expect n, DotToken + inc n + #loadField t.kind + loadField t.flagsImpl + loadField t.callConvImpl + loadField t.sizeImpl + loadField t.alignImpl + loadField t.paddingAtEndImpl + loadField t.itemId.item # nonUniqueId + + t.typeInstImpl = loadTypeStub(c, n, localSyms) + t.nImpl = loadNode(c, n, typesModule, localSyms) + t.ownerFieldImpl = loadSymStub(c, n, typesModule, localSyms) + t.symImpl = loadSymStub(c, n, typesModule, localSyms) + loadLoc c, n, t.locImpl + + while n.kind != ParRi: + t.sonsImpl.add loadTypeStub(c, n, localSyms) + + skipParRi n + +proc loadType*(c: var DecodeContext; t: PType) = + if t.state != Partial: return + t.state = Sealed + var buf = createTokenBuf(30) + let typeName = typeToNifSym(t, c.infos.config) + var n = cursorFromIndexEntry(c, t.itemId.module.FileIndex, c.types[typeName][1], buf) + var localSyms = initTable[string, PSym]() + loadTypeFromCursor(c, n, t, localSyms) + +proc loadAnnex(c: var DecodeContext; n: var Cursor; thisModule: string; localSyms: var Table[string, PSym]): PLib = + if n.kind == DotToken: + result = nil + inc n + elif n.kind == ParLe: + result = PLib(kind: parse(TLibKind, pool.tags[n.tagId])) + inc n + result.generated = loadBool(n) + result.isOverridden = loadBool(n) + expect n, StringLit + result.name = pool.strings[n.litId] + inc n + result.path = loadNode(c, n, thisModule, localSyms) + skipParRi n + else: + raiseAssert "`lib/annex` information expected" + +proc loadSymFromCursor(c: var DecodeContext; s: PSym; n: var Cursor; thisModule: string; + localSyms: var Table[string, PSym]) = + ## Loads a symbol definition from the current cursor position. + ## The cursor should be positioned after the opening (sd tag. + expect n, SymbolDef + # ignore the symbol's name, we have already used it to create this PSym instance! + inc n + if n.kind == Ident: + if pool.strings[n.litId] == "x": + s.flagsImpl.incl sfExported + inc n + else: + raiseAssert "expected `x` as the export marker" + elif n.kind == DotToken: + inc n + else: + raiseAssert "expected `x` or '.' but got " & $n.kind + + expect n, ParLe + {.cast(uncheckedAssign).}: + s.kindImpl = parse(TSymKind, pool.tags[n.tagId]) + inc n + + case s.kindImpl + of skLet, skVar, skField, skForVar: + s.guardImpl = loadSymStub(c, n, thisModule, localSyms) + loadField s.bitsizeImpl + loadField s.alignmentImpl + else: + discard + skipParRi n + + loadField s.magicImpl + loadField s.flagsImpl + loadField s.optionsImpl + loadField s.offsetImpl + + if s.kindImpl == skModule: + expect n, DotToken + inc n + var isKnownFile = false + s.positionImpl = int c.infos.config.registerNifSuffix(thisModule, isKnownFile) + # do to the precompiled mechanism things end up as main modules which are not! + excl s.flagsImpl, sfMainModule + else: + loadField s.positionImpl + + s.annexImpl = loadAnnex(c, n, thisModule, localSyms) + + # Local symbols were already extracted upfront in loadSym, so we can use + # the simple loadTypeStub here. + s.typImpl = loadTypeStub(c, n, localSyms) + s.ownerFieldImpl = loadSymStub(c, n, thisModule, localSyms) + # Load the AST for routine symbols and constants + # Constants need their AST for astdef() to return the constant's value + s.astImpl = loadNode(c, n, thisModule, localSyms) + loadLoc c, n, s.locImpl + s.constraintImpl = loadNode(c, n, thisModule, localSyms) + s.instantiatedFromImpl = loadSymStub(c, n, thisModule, localSyms) + skipParRi n + +proc loadSym*(c: var DecodeContext; s: PSym) = + if s.state != Partial: return + s.state = Sealed + var buf = createTokenBuf(30) + let symsModule = s.itemId.module.FileIndex + let nifname = globalName(s, c.infos.config) + var n = cursorFromIndexEntry(c, symsModule, c.syms[nifname][1], buf) + + expect n, ParLe + if n.tagId != sdefTag: + raiseAssert "(sd) expected" + + # Pre-scan the ENTIRE symbol definition to extract ALL local symbols upfront. + # This ensures local symbols are registered before any references to them, + # regardless of where they appear in the definition (in types, nested procs, etc.) + var localSyms = initTable[string, PSym]() + var scanCursor = n + extractLocalSymsFromTree(c, scanCursor, c.mods[symsModule].suffix, localSyms) + + # Now parse the symbol definition with all local symbols pre-registered + s.infoImpl = c.infos.oldLineInfo(n.info) + inc n + loadSymFromCursor(c, s, n, c.mods[symsModule].suffix, localSyms) + + +template withNode(c: var DecodeContext; n: var Cursor; result: PNode; kind: TNodeKind; body: untyped) = + let info = c.infos.oldLineInfo(n.info) + inc n + let flags = loadAtom(TNodeFlags, n) + result = newNodeI(kind, info) + result.flags = flags + result.typField = c.loadTypeStub(n, localSyms) + body + skipParRi n + +proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string; + localSyms: var Table[string, PSym]): PNode = + result = nil + case n.kind + of Symbol: + let info = c.infos.oldLineInfo(n.info) + let symName = pool.syms[n.symId] + # Check local symbols first + let localSym = localSyms.getOrDefault(symName) + if localSym != nil: + result = newSymNode(localSym, info) + inc n + else: + result = newSymNode(c.loadSymStub(n, thisModule, localSyms), info) + if result.typField == nil: + result.flags.incl nfLazyType + of DotToken: + result = nil + inc n + of StringLit: + result = newStrNode(pool.strings[n.litId], c.infos.oldLineInfo(n.info)) + inc n + of ParLe: + let kind = n.nodeKind + case kind + of nkNone: + # special NIF introduced tag? + case pool.tags[n.tagId] + of hiddenTypeTagName: + inc n + let typ = c.loadTypeStub(n, localSyms) + let info = c.infos.oldLineInfo(n.info) + result = newSymNode(c.loadSymStub(n, thisModule, localSyms), info) + result.typField = typ + skipParRi n + of symDefTagName: + let info = c.infos.oldLineInfo(n.info) + let name = n.firstSon + assert name.kind == SymbolDef + let symName = pool.syms[name.symId] + # Check if this is a local symbol (no module suffix in name) + let sn = parseSymName(symName) + let isLocal = sn.module.len == 0 + var sym: PSym + if isLocal: + # Local symbol - not in the index, defined inline in NIF. + # Check if we already have a stub from extractLocalSymsFromType + sym = localSyms.getOrDefault(symName) + if sym == nil: + # First time seeing this local symbol - create it + let module = moduleId(c, thisModule) + let val = addr c.mods[module].symCounter + inc val[] + let id = ItemId(module: module.int32, item: val[]) + sym = PSym(itemId: id, kindImpl: skStub, name: c.cache.getIdent(sn.name), + disamb: sn.count.int32, state: Complete) + localSyms[symName] = sym # register for later references + # Now fully load the symbol from the sdef + inc n # skip `sd` tag + loadSymFromCursor(c, sym, n, thisModule, localSyms) + sym.state = Sealed # mark as fully loaded + result = newSymNode(sym, info) + else: + sym = c.loadSymStub(name.symId, thisModule, localSyms) + skip n # skip the entire sdef for indexed symbols + result = newSymNode(sym, info) + result.flags.incl nfLazyType + of typeDefTagName: + raiseAssert "`td` tag in invalid context" + of "none": + result = newNodeI(nkNone, c.infos.oldLineInfo(n.info)) + inc n + result.flags = loadAtom(TNodeFlags, n) + skipParRi n + else: + raiseAssert "Unknown NIF tag " & pool.tags[n.tagId] + of nkEmpty: + result = newNodeI(nkEmpty, c.infos.oldLineInfo(n.info)) + inc n + if n.kind != ParRi: + result.flags = loadAtom(TNodeFlags, n) + result.typField = c.loadTypeStub(n, localSyms) + skipParRi n + of nkIdent: + let info = c.infos.oldLineInfo(n.info) + inc n + let flags = loadAtom(TNodeFlags, n) + let typ = c.loadTypeStub(n, localSyms) + expect n, Ident + result = newIdentNode(c.cache.getIdent(pool.strings[n.litId]), info) + inc n + result.flags = flags + result.typField = typ + skipParRi n + of nkSym: + #let info = c.infos.oldLineInfo(n.info) + #result = newSymNode(c.loadSymStub n, info) + raiseAssert "nkSym should be mapped to a NIF symbol, not a tag" + of nkCharLit: + c.withNode n, result, kind: + expect n, CharLit + result.intVal = n.charLit.int + inc n + of nkIntLit .. nkInt64Lit: + c.withNode n, result, kind: + expect n, IntLit + result.intVal = pool.integers[n.intId] + inc n + of nkUIntLit .. nkUInt64Lit: + c.withNode n, result, kind: + expect n, UIntLit + result.intVal = cast[BiggestInt](pool.uintegers[n.uintId]) + inc n + of nkFloatLit .. nkFloat128Lit: + c.withNode n, result, kind: + if n.kind == FloatLit: + result.floatVal = pool.floats[n.floatId] + inc n + elif n.kind == ParLe: + case pool.tags[n.tagId] + of "inf": + result.floatVal = Inf + of "nan": + result.floatVal = NaN + of "neginf": + result.floatVal = NegInf + else: + raiseAssert "expected float literal but got " & pool.tags[n.tagId] + inc n + skipParRi n + else: + raiseAssert "expected float literal but got " & $n.kind + of nkStrLit .. nkTripleStrLit: + c.withNode n, result, kind: + expect n, StringLit + result.strVal = pool.strings[n.litId] + inc n + of nkNilLit: + c.withNode n, result, kind: + discard + else: + c.withNode n, result, kind: + while n.kind != ParRi: + result.sons.add c.loadNode(n, thisModule, localSyms) + else: + raiseAssert "expected string literal but got " & $n.kind + +proc loadSymFromIndexEntry(c: var DecodeContext; module: FileIndex; + nifName: string; entry: NifIndexEntry; thisModule: string): PSym = + ## Loads a symbol from the NIF index entry using the entry directly. + ## Creates a symbol stub without looking up in the index (since the index may be moved out). + result = c.syms.getOrDefault(nifName)[0] + if result == nil: + let symAsStr = nifName + let sn = parseSymName(symAsStr) + let symModule = moduleId(c, if sn.module.len > 0: sn.module else: thisModule) + let val = addr c.mods[symModule].symCounter + inc val[] + + let id = ItemId(module: symModule.int32, item: val[]) + result = PSym(itemId: id, kindImpl: skStub, name: c.cache.getIdent(sn.name), disamb: sn.count.int32, state: Partial) + c.syms[symAsStr] = (result, entry) + +proc extractBasename(nifName: string): string = + ## Extract the base name from a NIF name (ident.disamb.module -> ident) + result = "" + for c in nifName: + if c == '.': break + result.add c + +proc populateInterfaceTablesFromIndex(c: var DecodeContext; module: FileIndex; + interf, interfHidden: var TStrTable; thisModule: string) = + ## Populates interface tables from the NIF index structure. + ## Uses the simple embedded index for offsets, exports passed from processTopLevel. + + # Move the index table out to avoid iterator invalidation + # (moduleId can add to c.mods which would invalidate Table iterators) + var indexTab = move c.mods[module].index + + # Add all symbols to interf (exported interface) and interfHidden + for nifName, entry in indexTab: + if entry.vis == Exported: + let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule) + if sym != nil: + strTableAdd(interf, sym) + strTableAdd(interfHidden, sym) + elif not nifName.startsWith("`t"): + # do not load types, they are not part of an interface but an implementation detail! + #echo "LOADING SYM ", nifName, " ", entry.offset + let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule) + if sym != nil: + strTableAdd(interfHidden, sym) + + # Move index table back + c.mods[module].index = move indexTab + +proc toNifFilename*(conf: ConfigRef; f: FileIndex): string = + let suffix = moduleSuffix(conf, f) + result = toGeneratedFile(conf, AbsoluteFile(suffix), ".nif").string + +proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: bool): PSym = + result = c.syms.getOrDefault(symAsStr)[0] + if result != nil: + return result + + let sn = parseSymName(symAsStr) + if sn.module.len == 0: + return nil # Local symbols shouldn't be hooks + let module = moduleId(c, sn.module) + # Look up the symbol in the module's index + # Try both formats: with module suffix (e.g., "foo.0.modulename") and without (e.g., "foo.0.") + # NIF spec allows local symbols to be stored without module suffix + var offs = c.mods[module].index.getOrDefault(symAsStr) + if offs.offset == 0: + # Try the format without module suffix + let localKey = sn.name & "." & $sn.count & "." + offs = c.mods[module].index.getOrDefault(localKey) + if offs.offset == 0: + return nil + if not alsoConsiderPrivate and offs.vis == Hidden: + return nil + # Create a stub symbol + let val = addr c.mods[module].symCounter + inc val[] + let id = ItemId(module: int32(module), item: val[]) + result = PSym(itemId: id, kindImpl: skProc, name: c.cache.getIdent(sn.name), + disamb: sn.count.int32, state: Partial) + c.syms[symAsStr] = (result, offs) + +proc resolveHookSym*(c: var DecodeContext; symId: nifstreams.SymId): PSym = + ## Resolves a hook SymId to PSym. + ## Hook symbols are often private (generated =destroy, =wasMoved, etc.) + let symAsStr = pool.syms[symId] + result = resolveSym(c, symAsStr, true) + +proc tryResolveCompilerProc*(c: var DecodeContext; name: string; moduleFileIdx: FileIndex): PSym = + ## Tries to resolve a compiler proc from a module by checking the NIF index. + ## Returns nil if the symbol doesn't exist. + let suffix = moduleSuffix(c.infos.config, moduleFileIdx) + let symName = name & ".0." & suffix + result = resolveSym(c, symName, true) + +proc loadLogOp(c: var DecodeContext; logOps: var seq[LogEntry]; s: var Stream; kind: LogEntryKind; op: TTypeAttachedOp; module: int): PackedToken = + result = next(s) + var key = "" + if result.kind == StringLit: + key = pool.strings[result.litId] + result = next(s) + else: + raiseAssert "expected StringLit but got " & $result.kind + if result.kind == Symbol: + let sym = resolveHookSym(c, result.symId) + if sym != nil: + logOps.add LogEntry(kind: kind, op: op, module: module, key: key, sym: sym) + # else: symbol not indexed, skip this hook entry + result = next(s) + if result.kind == ParRi: + result = next(s) + else: + raiseAssert "expected ParRi but got " & $result.kind + +proc skipTree(s: var Stream): PackedToken = + result = next(s) + var nested = 1 + while nested > 0: + if result.kind == ParLe: + inc nested + elif result.kind == ParRi: + dec nested + elif result.kind == EofToken: + break + result = next(s) + +proc nextSubtree(r: var Stream; dest: var TokenBuf; tok: var PackedToken) = + r.parents[0] = tok.info + var nested = 1 + dest.add tok # tag + while true: + tok = r.next() + dest.add tok + if tok.kind == EofToken: + break + elif tok.kind == ParLe: + inc nested + elif tok.kind == ParRi: + dec nested + if nested == 0: break + +type + ModuleSuffix* = distinct string + PrecompiledModule* = object + topLevel*: PNode # top level statements of the main module + deps*: seq[ModuleSuffix] # other modules we need to process the top level statements of + logOps*: seq[LogEntry] + module*: PSym # set by modulegraphs.nim! + +proc loadImport(c: var DecodeContext; s: var Stream; deps: var seq[ModuleSuffix]; tok: var PackedToken) = + tok = next(s) # skip `(import` + if tok.kind == DotToken: + tok = next(s) # skip dot + if tok.kind == DotToken: + tok = next(s) # skip dot + if tok.kind == StringLit: + deps.add ModuleSuffix(pool.strings[tok.litId]) + tok = next(s) + else: + raiseAssert "expected StringLit but got " & $tok.kind + if tok.kind == ParRi: + tok = next(s) # skip ) + else: + raiseAssert "expected ParRi but got " & $tok.kind + +proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag]; + interf: var TStrTable; suffix: string; module: int): PrecompiledModule = + result = PrecompiledModule(topLevel: newNode(nkStmtList)) + var localSyms = initTable[string, PSym]() + + var t = next(s) # skip dot + var cont = true + let exportTag = pool.tags.getOrIncl"export" + while cont and t.kind != EofToken: + if t.kind == ParLe: + if t.tagId == replayTag: + # Always load replay actions (macro cache operations) + t = next(s) # move past (replay + while t.kind != ParRi and t.kind != EofToken: + if t.kind == ParLe: + var buf = createTokenBuf(50) + nextSubtree(s, buf, t) + var cursor = cursorAt(buf, 0) + let replayNode = loadNode(c, cursor, suffix, localSyms) + if replayNode != nil: + result.topLevel.sons.add replayNode + t = next(s) + if t.kind == ParRi: + t = next(s) + else: + raiseAssert "expected ParRi but got " & $t.kind + elif t.tagId == repConverterTag: + t = loadLogOp(c, result.logOps, s, ConverterEntry, attachedTrace, module) + elif t.tagId == repDestroyTag: + t = loadLogOp(c, result.logOps, s, HookEntry, attachedDestructor, module) + elif t.tagId == repWasMovedTag: + t = loadLogOp(c, result.logOps, s, HookEntry, attachedWasMoved, module) + elif t.tagId == repCopyTag: + t = loadLogOp(c, result.logOps, s, HookEntry, attachedAsgn, module) + elif t.tagId == repSinkTag: + t = loadLogOp(c, result.logOps, s, HookEntry, attachedSink, module) + elif t.tagId == repDupTag: + t = loadLogOp(c, result.logOps, s, HookEntry, attachedDup, module) + elif t.tagId == repTraceTag: + t = loadLogOp(c, result.logOps, s, HookEntry, attachedTrace, module) + elif t.tagId == repDeepCopyTag: + t = loadLogOp(c, result.logOps, s, HookEntry, attachedDeepCopy, module) + elif t.tagId == repEnumToStrTag: + t = loadLogOp(c, result.logOps, s, EnumToStrEntry, attachedTrace, module) + elif t.tagId == repMethodTag: + t = loadLogOp(c, result.logOps, s, MethodEntry, attachedTrace, module) + #elif t.tagId == repClassTag: + # t = loadLogOp(c, logOps, s, ClassEntry, attachedTrace, module) + elif t.tagId == exportTag: + t = next(s) # skip (export + if t.kind == DotToken: + t = next(s) # skip dot + if t.kind == DotToken: + t = next(s) # skip dot + while true: + if t.kind == Symbol: + let symAsStr = pool.syms[t.symId] + let sym = resolveSym(c, symAsStr, false) + if sym != nil: + strTableAdd(interf, sym) + t = next(s) + elif t.kind == ParRi: + break + else: + raiseAssert "expected Symbol or ParRi but got " & $t.kind + t = next(s) + elif t.tagId == includeTag: + t = skipTree(s) + elif t.tagId == importTag: + loadImport(c, s, result.deps, t) + elif t.tagId == implTag: + cont = false + elif LoadFullAst in flags: + # Parse the full statement + var buf = createTokenBuf(50) + nextSubtree(s, buf, t) + t = next(s) # skip ParRi + var cursor = cursorAt(buf, 0) + let stmtNode = loadNode(c, cursor, suffix, localSyms) + if stmtNode != nil: + result.topLevel.sons.add stmtNode + else: + cont = false + else: + cont = false + +proc loadNifModule*(c: var DecodeContext; suffix: ModuleSuffix; interf, interfHidden: var TStrTable; + flags: set[LoadFlag] = {}): PrecompiledModule = + # Ensure module index is loaded - moduleId returns the FileIndex for this suffix + let module = moduleId(c, string(suffix), flags) + + # Load the module AST (or just replay actions if loadFullAst is false) + # processTopLevel also collects export instructions + let s = addr c.mods[module].stream + var t = next(s[]) + if t.kind == ParLe and pool.tags[t.tagId] == toNifTag(nkStmtList): + t = next(s[]) # skip (stmts + t = next(s[]) # skip flags + result = processTopLevel(c, s[], flags, interf, string(suffix), module.int) + else: + result = PrecompiledModule(topLevel: newNode(nkStmtList)) + + # Populate interface tables from the NIF index structure + # Symbols are created as stubs (Partial state) and will be loaded lazily via loadSym + # Use exports collected by processTopLevel + populateInterfaceTablesFromIndex(c, module, interf, interfHidden, string(suffix)) + +proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: var TStrTable; + flags: set[LoadFlag] = {}): PrecompiledModule = + let suffix = ModuleSuffix(moduleSuffix(c.infos.config, f)) + result = loadNifModule(c, suffix, interf, interfHidden, flags) + +when isMainModule: + import std / syncio + let obj = parseSymName("a.123.sys") + echo obj.name, " ", obj.module, " ", obj.count + let objb = parseSymName("abcdef.0121") + echo objb.name, " ", objb.module, " ", objb.count diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 7a9892f78a..baa852b9e0 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -68,8 +68,6 @@ 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 @@ -216,10 +214,6 @@ 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" @@ -484,12 +478,6 @@ 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 @@ -537,95 +525,6 @@ 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 @@ -713,6 +612,70 @@ iterator items*(tab: TStrTable): PSym = yield s s = nextIter(it, tab) +proc isNil(x: ItemId): bool {.inline.} = + x.module == 0 and x.item == 0 + +proc hasEmptySlot[T](data: TIdPairSeq[T]): bool = + for h in 0..high(data): + if isNil(data[h].key): + return true + result = false + +proc idTableRawGet[T](t: TIdTable[T], key: int): int = + var h: Hash + h = key and high(t.data) # start with real hash value + while not isNil(t.data[h].key): + if toId(t.data[h].key) == key: + return h + h = nextTry(h, high(t.data)) + result = - 1 + +proc getOrDefault*[T](t: TIdTable[T], key: ItemId): T = + var index = idTableRawGet(t, toId(key)) + if index >= 0: result = t.data[index].val + else: result = default(T) + +template idTableGet*[T](t: TIdTable[T], key: PType | PSym): T = + getOrDefault(t, key.itemId) + +proc idTableRawInsert[T](data: var TIdPairSeq[T], key: ItemId, val: T) = + var h: Hash + let keyId = toId(key) + h = keyId and high(data) + while not isNil(data[h].key): + assert(toId(data[h].key) != keyId) + h = nextTry(h, high(data)) + assert(isNil(data[h].key)) + data[h].key = key + data[h].val = val + +proc `[]=`*[T](t: var TIdTable[T], key: ItemId, val: T) = + var + index: int + n: TIdPairSeq[T] + index = idTableRawGet(t, toId(key)) + if index >= 0: + assert(not isNil(t.data[index].key)) + t.data[index].val = val + else: + if mustRehash(t.data.len, t.counter): + newSeq(n, t.data.len * GrowthFactor) + for i in 0..high(t.data): + if not isNil(t.data[i].key): + idTableRawInsert(n, t.data[i].key, t.data[i].val) + assert(hasEmptySlot(n)) + swap(t.data, n) + idTableRawInsert(t.data, key, val) + inc(t.counter) + +template idTablePut*[T](t: var TIdTable[T], key: PType | PSym, val: T) = + t[key.itemId] = val + +iterator idTablePairs*[T](t: TIdTable[T]): tuple[key: ItemId, val: T] = + for i in 0..high(t.data): + if not isNil(t.data[i].key): + yield (t.data[i].key, t.data[i].val) + proc initIITable(x: var TIITable) = x.counter = 0 newSeq(x.data, StartSize) diff --git a/compiler/astdef.nim b/compiler/astdef.nim new file mode 100644 index 0000000000..6a7b4d7788 --- /dev/null +++ b/compiler/astdef.nim @@ -0,0 +1,1165 @@ +# +# +# The Nim Compiler +# (c) Copyright 2025 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +import + lineinfos, options, ropes, idents, int128 + +import std/[tables, hashes] + +when defined(nimPreviewSlimSystem): + import std/assertions + +export int128 + +import nodekinds +export nodekinds + +type + TCallingConvention* = enum + ccNimCall = "nimcall" # nimcall, also the default + ccStdCall = "stdcall" # procedure is stdcall + ccCDecl = "cdecl" # cdecl + ccSafeCall = "safecall" # safecall + ccSysCall = "syscall" # system call + ccInline = "inline" # proc should be inlined + ccNoInline = "noinline" # proc should not be inlined + ccFastCall = "fastcall" # fastcall (pass parameters in registers) + ccThisCall = "thiscall" # thiscall (parameters are pushed right-to-left) + ccClosure = "closure" # proc has a closure + ccNoConvention = "noconv" # needed for generating proper C procs sometimes + ccMember = "member" # proc is a (cpp) member + + TNodeKinds* = set[TNodeKind] + +type + TSymFlag* = enum # 63 flags! + sfUsed, # read access of sym (for warnings) or simply used + sfExported, # symbol is exported from module + sfFromGeneric, # symbol is instantiation of a generic; this is needed + # for symbol file generation; such symbols should always + # be written into the ROD file + sfGlobal, # symbol is at global scope + + sfForward, # symbol is forward declared + sfWasForwarded, # symbol had a forward declaration + # (implies it's too dangerous to patch its type signature) + sfImportc, # symbol is external; imported + sfExportc, # symbol is exported (under a specified name) + sfMangleCpp, # mangle as cpp (combines with `sfExportc`) + sfVolatile, # variable is volatile + sfRegister, # variable should be placed in a register + sfPure, # object is "pure" that means it has no type-information + # enum is "pure", its values need qualified access + # variable is "pure"; it's an explicit "global" + sfNoSideEffect, # proc has no side effects + sfSideEffect, # proc may have side effects; cannot prove it has none + sfMainModule, # module is the main module + sfSystemModule, # module is the system module + sfNoReturn, # proc never returns (an exit proc) + sfAddrTaken, # the variable's address is taken (ex- or implicitly); + # *OR*: a proc is indirectly called (used as first class) + sfCompilerProc, # proc is a compiler proc, that is a C proc that is + # needed for the code generator + sfEscapes # param escapes + # currently unimplemented + sfDiscriminant, # field is a discriminant in a record/object + sfRequiresInit, # field must be initialized during construction + sfDeprecated, # symbol is deprecated + sfExplain, # provide more diagnostics when this symbol is used + sfError, # usage of symbol should trigger a compile-time error + sfShadowed, # a symbol that was shadowed in some inner scope + sfThread, # proc will run as a thread + # variable is a thread variable + sfCppNonPod, # tells compiler to treat such types as non-pod's, so that + # `thread_local` is used instead of `__thread` for + # {.threadvar.} + `--threads`. Only makes sense for importcpp types. + # This has a performance impact so isn't set by default. + sfCompileTime, # proc can be evaluated at compile time + sfConstructor, # proc is a C++ constructor + sfDispatcher, # copied method symbol is the dispatcher + # deprecated and unused, except for the con + sfBorrow, # proc is borrowed + sfInfixCall, # symbol needs infix call syntax in target language; + # for interfacing with C++, JS + sfNamedParamCall, # symbol needs named parameter call syntax in target + # language; for interfacing with Objective C + sfDiscardable, # returned value may be discarded implicitly + sfOverridden, # proc is overridden + sfCallsite # A flag for template symbols to tell the + # compiler it should use line information from + # the calling side of the macro, not from the + # implementation. + sfGenSym # symbol is 'gensym'ed; do not add to symbol table + sfNonReloadable # symbol will be left as-is when hot code reloading is on - + # meaning that it won't be renamed and/or changed in any way + sfGeneratedOp # proc is a generated '='; do not inject destructors in it + # variable is generated closure environment; requires early + # destruction for --newruntime. + sfTemplateParam # symbol is a template parameter + sfCursor # variable/field is a cursor, see RFC 177 for details + sfInjectDestructors # whether the proc needs the 'injectdestructors' transformation + sfNeverRaises # proc can never raise an exception, not even OverflowDefect + # or out-of-memory + sfSystemRaisesDefect # proc in the system can raise defects + sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally' + sfSingleUsedTemp # For temporaries that we know will only be used once + sfNoalias # 'noalias' annotation, means C's 'restrict' + # for templates and macros, means cannot be called + # as a lone symbol (cannot use alias syntax) + sfEffectsDelayed # an 'effectsDelayed' parameter + sfGeneratedType # A anonymous generic type that is generated by the compiler for + # objects that do not have generic parameters in case one of the + # object fields has one. + # + # This is disallowed but can cause the typechecking to go into + # an infinite loop, this flag is used as a sentinel to stop it. + sfVirtual # proc is a C++ virtual function + sfByCopy # param is marked as pass bycopy + sfMember # proc is a C++ member of a type + sfCodegenDecl # type, proc, global or proc param is marked as codegenDecl + sfWasGenSym # symbol was 'gensym'ed + sfForceLift # variable has to be lifted into closure environment + + sfDirty # template is not hygienic (old styled template) module, + # compiled from a dirty-buffer + sfCustomPragma # symbol is custom pragma template + sfBase, # a base method + sfGoto # var is used for 'goto' code generation + sfAnon, # symbol name that was generated by the compiler + # the compiler will avoid printing such names + # in user messages. + sfAllUntyped # macro or template is immediately expanded in a generic context + sfTemplateRedefinition # symbol is a redefinition of an earlier template + + TSymFlags* = set[TSymFlag] + +const + sfNoInit* = sfMainModule # don't generate code to init the variable + + sfNoForward* = sfRegister + # forward declarations are not required (per module) + sfReorder* = sfForward + # reordering pass is enabled + + sfCompileToCpp* = sfInfixCall # compile the module as C++ code + sfCompileToObjc* = sfNamedParamCall # compile the module as Objective-C code + sfExperimental* = sfOverridden # module uses the .experimental switch + sfWrittenTo* = sfBorrow # param is assigned to + # currently unimplemented + sfCppMember* = { sfVirtual, sfMember, sfConstructor } # proc is a C++ member, meaning it will be attached to the type definition + +const + # getting ready for the future expr/stmt merge + nkWhen* = nkWhenStmt + nkWhenExpr* = nkWhenStmt + nkEffectList* = nkArgList + # hacks ahead: an nkEffectList is a node with 4 children: + exceptionEffects* = 0 # exceptions at position 0 + requiresEffects* = 1 # 'requires' annotation + ensuresEffects* = 2 # 'ensures' annotation + tagEffects* = 3 # user defined tag ('gc', 'time' etc.) + pragmasEffects* = 4 # not an effect, but a slot for pragmas in proc type + forbiddenEffects* = 5 # list of illegal effects + effectListLen* = 6 # list of effects list + nkLastBlockStmts* = {nkRaiseStmt, nkReturnStmt, nkBreakStmt, nkContinueStmt} + # these must be last statements in a block + +type + TTypeKind* = enum # order is important! + # Don't forget to change hti.nim if you make a change here + # XXX put this into an include file to avoid this issue! + # several types are no longer used (guess which), but a + # spot in the sequence is kept for backwards compatibility + # (apparently something with bootstrapping) + # if you need to add a type, they can apparently be reused + tyNone, tyBool, tyChar, + tyEmpty, tyAlias, tyNil, tyUntyped, tyTyped, tyTypeDesc, + tyGenericInvocation, # ``T[a, b]`` for types to invoke + tyGenericBody, # ``T[a, b, body]`` last parameter is the body + tyGenericInst, # ``T[a, b, realInstance]`` instantiated generic type + # realInstance will be a concrete type like tyObject + # unless this is an instance of a generic alias type. + # then realInstance will be the tyGenericInst of the + # completely (recursively) resolved alias. + + tyGenericParam, # ``a`` in the above patterns + tyDistinct, + tyEnum, + tyOrdinal, # integer types (including enums and boolean) + tyArray, + tyObject, + tyTuple, + tySet, + tyRange, + tyPtr, tyRef, + tyVar, + tySequence, + tyProc, + tyPointer, tyOpenArray, + tyString, tyCstring, + tyForward, + # a type not yet semchecked + # When semcheck a type section, all types defined in it are initialized to tyForward + + tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers + tyFloat, tyFloat32, tyFloat64, tyFloat128, + tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64, + tyOwned, tySink, tyLent, + tyVarargs, + tyUncheckedArray + # An array with boundaries [0,+∞] + + tyError # used as erroneous type (for idetools) + # as an erroneous node should match everything + + tyBuiltInTypeClass + # Type such as the catch-all object, tuple, seq, etc + + tyUserTypeClass + # the body of a user-defined type class + + tyUserTypeClassInst + # Instance of a parametric user-defined type class. + # Structured similarly to tyGenericInst. + # tyGenericInst represents concrete types, while + # this is still a "generic param" that will bind types + # and resolves them during sigmatch and instantiation. + + tyCompositeTypeClass + # Type such as seq[Number] + # The notes for tyUserTypeClassInst apply here as well + # sons[0]: the original expression used by the user. + # sons[1]: fully expanded and instantiated meta type + # (potentially following aliases) + + tyInferred + # In the initial state `base` stores a type class constraining + # the types that can be inferred. After a candidate type is + # selected, it's stored in `last`. Between `base` and `last` + # there may be 0, 2 or more types that were also considered as + # possible candidates in the inference process (i.e. last will + # be updated to store a type best conforming to all candidates) + + tyAnd, tyOr, tyNot + # boolean type classes such as `string|int`,`not seq`, + # `Sortable and Enumable`, etc + + tyAnything + # a type class matching any type + + tyStatic + # a value known at compile type (the underlying type is .base) + + tyFromExpr + # This is a type representing an expression that depends + # on generic parameters (the expression is stored in t.n) + # It will be converted to a real type only during generic + # instantiation and prior to this it has the potential to + # be any type. + + tyConcept + # new style concept. + + tyVoid + # now different from tyEmpty, hurray! + tyIterable + +static: + # remind us when TTypeKind stops to fit in a single 64-bit word + # assert TTypeKind.high.ord <= 63 + discard + +const + tyPureObject* = tyTuple + GcTypeKinds* = {tyRef, tySequence, tyString} + + tyTypeClasses* = {tyBuiltInTypeClass, tyCompositeTypeClass, + tyUserTypeClass, tyUserTypeClassInst, tyConcept, + tyAnd, tyOr, tyNot, tyAnything} + + tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyUntyped} + tyTypeClasses + tyUserTypeClasses* = {tyUserTypeClass, tyUserTypeClassInst} + # consider renaming as `tyAbstractVarRange` + abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal, + tyTypeDesc, tyAlias, tyInferred, tySink, tyOwned} + abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, + tyInferred, tySink, tyOwned} # xxx what about tyStatic? + +type + TTypeKinds* = set[TTypeKind] + + TNodeFlag* = enum + nfNone, + nfBase2, # nfBase10 is default, so not needed + nfBase8, + nfBase16, + nfAllConst, # used to mark complex expressions constant; easy to get rid of + # but unfortunately it has measurable impact for compilation + # efficiency + nfTransf, # node has been transformed + nfNoRewrite # node should not be transformed anymore + nfSem # node has been checked for semantics + nfLL # node has gone through lambda lifting + nfDotField # the call can use a dot operator + nfDotSetter # the call can use a setter dot operarator + nfExplicitCall # x.y() was used instead of x.y + nfExprCall # this is an attempt to call a regular expression + nfIsRef # this node is a 'ref' node; used for the VM + nfIsPtr # this node is a 'ptr' node; used for the VM + nfPreventCg # this node should be ignored by the codegen + nfBlockArg # this a stmtlist appearing in a call (e.g. a do block) + nfFromTemplate # a top-level node returned from a template + nfDefaultParam # an automatically inserter default parameter + nfDefaultRefsParam # a default param value references another parameter + # the flag is applied to proc default values and to calls + nfExecuteOnReload # A top-level statement that will be executed during reloads + nfLastRead # this node is a last read + nfFirstWrite # this node is a first write + nfHasComment # node has a comment + nfSkipFieldChecking # node skips field visable checking + nfDisabledOpenSym # temporary: node should be nkOpenSym but cannot + # because openSym experimental switch is disabled + # gives warning instead + nfLazyType # node has a lazy type + + TNodeFlags* = set[TNodeFlag] + TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 47) + tfVarargs, # procedure has C styled varargs + # tyArray type represeting a varargs list + tfNoSideEffect, # procedure type does not allow side effects + tfFinal, # is the object final? + tfInheritable, # is the object inheritable? + tfHasOwned, # type contains an 'owned' type and must be moved + tfEnumHasHoles, # enum cannot be mapped into a range + tfShallow, # type can be shallow copied on assignment + tfThread, # proc type is marked as ``thread``; alias for ``gcsafe`` + tfFromGeneric, # type is an instantiation of a generic; this is needed + # because for instantiations of objects, structural + # type equality has to be used + tfUnresolved, # marks unresolved typedesc/static params: e.g. + # proc foo(T: typedesc, list: seq[T]): var T + # proc foo(L: static[int]): array[L, int] + # can be attached to ranges to indicate that the range + # can be attached to generic procs with free standing + # type parameters: e.g. proc foo[T]() + # depends on unresolved static params. + tfResolved # marks a user type class, after it has been bound to a + # concrete type (lastSon becomes the concrete type) + tfRetType, # marks return types in proc (used to detect type classes + # used as return types for return type inference) + tfCapturesEnv, # whether proc really captures some environment + tfByCopy, # pass object/tuple by copy (C backend) + tfByRef, # pass object/tuple by reference (C backend) + tfIterator, # type is really an iterator, not a tyProc + tfPartial, # type is declared as 'partial' + tfNotNil, # type cannot be 'nil' + tfRequiresInit, # type contains a "not nil" constraint somewhere or + # a `requiresInit` field, so the default zero init + # is not appropriate + tfNeedsFullInit, # object type marked with {.requiresInit.} + # all fields must be initialized + tfVarIsPtr, # 'var' type is translated like 'ptr' even in C++ mode + tfHasMeta, # type contains "wildcard" sub-types such as generic params + # or other type classes + tfHasGCedMem, # type contains GC'ed memory + tfPacked + tfHasStatic + tfGenericTypeParam + tfImplicitTypeParam + tfInferrableStatic + tfConceptMatchedTypeSym + tfExplicit # for typedescs, marks types explicitly prefixed with the + # `type` operator (e.g. type int) + tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) + # T and I here can bind to both typedesc and static types + # before this is determined, we'll consider them to be a + # wildcard type. + tfHasAsgn # type has overloaded assignment operator + tfBorrowDot # distinct type borrows '.' + tfTriggersCompileTime # uses the NimNode type which make the proc + # implicitly '.compiletime' + tfRefsAnonObj # used for 'ref object' and 'ptr object' + tfCovariant # covariant generic param mimicking a ptr type + tfWeakCovariant # covariant generic param mimicking a seq/array type + tfContravariant # contravariant generic param + tfCheckedForDestructor # type was checked for having a destructor. + # If it has one, t.destructor is not nil. + tfAcyclic # object type was annotated as .acyclic + tfIncompleteStruct # treat this type as if it had sizeof(pointer) + tfCompleteStruct + # (for importc types); type is fully specified, allowing to compute + # sizeof, alignof, offsetof at CT + tfExplicitCallConv + tfIsConstructor + tfEffectSystemWorkaround + tfIsOutParam + tfSendable + tfImplicitStatic + + TTypeFlags* = set[TTypeFlag] + + TSymKind* = enum # the different symbols (start with the prefix sk); + # order is important for the documentation generator! + skUnknown, # unknown symbol: used for parsing assembler blocks + # and first phase symbol lookup in generics + skConditional, # symbol for the preprocessor (may become obsolete) + skDynLib, # symbol represents a dynamic library; this is used + # internally; it does not exist in Nim code + skParam, # a parameter + skGenericParam, # a generic parameter; eq in ``proc x[eq=`==`]()`` + skTemp, # a temporary variable (introduced by compiler) + skModule, # module identifier + skType, # a type + skVar, # a variable + skLet, # a 'let' symbol + skConst, # a constant + skResult, # special 'result' variable + skProc, # a proc + skFunc, # a func + skMethod, # a method + skIterator, # an iterator + skConverter, # a type converter + skMacro, # a macro + skTemplate, # a template; currently also misused for user-defined + # pragmas + skField, # a field in a record or object + skEnumField, # an identifier in an enum + skForVar, # a for loop variable + skLabel, # a label (for block statement) + skStub, # symbol is a stub and not yet loaded from the ROD + # file (it is loaded on demand, which may + # mean: never) + skPackage, # symbol is a package (used for canonicalization) + TSymKinds* = set[TSymKind] + +const + routineKinds* = {skProc, skFunc, skMethod, skIterator, + skConverter, skMacro, skTemplate} + ExportableSymKinds* = {skVar, skLet, skConst, skType, skEnumField, skStub} + routineKinds + + tfUnion* = tfNoSideEffect + tfGcSafe* = tfThread + tfObjHasKids* = tfEnumHasHoles + tfReturnsNew* = tfInheritable + tfNonConstExpr* = tfExplicitCallConv + ## tyFromExpr where the expression shouldn't be evaluated as a static value + tfGenericHasDestructor* = tfExplicitCallConv + ## tyGenericBody where an instance has a generated destructor + skError* = skUnknown + +var + eqTypeFlags* = {tfIterator, tfNotNil, tfVarIsPtr, tfGcSafe, tfNoSideEffect, tfIsOutParam} + ## type flags that are essential for type equality. + ## This is now a variable because for emulation of version:1.0 we + ## might exclude {tfGcSafe, tfNoSideEffect}. + +type + TMagic* = enum # symbols that require compiler magic: + mNone, + mDefined, mDeclared, mDeclaredInScope, mCompiles, mArrGet, mArrPut, mAsgn, + mLow, mHigh, mSizeOf, mAlignOf, mOffsetOf, mTypeTrait, + mIs, mOf, mAddr, mType, mTypeOf, + mPlugin, mEcho, mShallowCopy, mSlurp, mStaticExec, mStatic, + mParseExprToAst, mParseStmtToAst, mExpandToAst, mQuoteAst, + mInc, mDec, mOrd, + mNew, mNewFinalize, mNewSeq, mNewSeqOfCap, + mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq, + mIncl, mExcl, mCard, mChr, + mGCref, mGCunref, + mAddI, mSubI, mMulI, mDivI, mModI, + mSucc, mPred, + mAddF64, mSubF64, mMulF64, mDivF64, + mShrI, mShlI, mAshrI, mBitandI, mBitorI, mBitxorI, + mMinI, mMaxI, + mAddU, mSubU, mMulU, mDivU, mModU, + mEqI, mLeI, mLtI, + mEqF64, mLeF64, mLtF64, + mLeU, mLtU, + mEqEnum, mLeEnum, mLtEnum, + mEqCh, mLeCh, mLtCh, + mEqB, mLeB, mLtB, + mEqRef, mLePtr, mLtPtr, + mXor, mEqCString, mEqProc, + mUnaryMinusI, mUnaryMinusI64, mAbsI, mNot, + mUnaryPlusI, mBitnotI, + mUnaryPlusF64, mUnaryMinusF64, + mCharToStr, mBoolToStr, + mCStrToStr, + mStrToStr, mEnumToStr, + mAnd, mOr, + mImplies, mIff, mExists, mForall, mOld, + mEqStr, mLeStr, mLtStr, + mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet, mXorSet, + mConStrStr, mSlice, + mDotDot, # this one is only necessary to give nice compile time warnings + mFields, mFieldPairs, mOmpParFor, + mAppendStrCh, mAppendStrStr, mAppendSeqElem, + mInSet, mRepr, mExit, + mSetLengthStr, mSetLengthSeq, + mSetLengthSeqUninit, + mIsPartOf, mAstToStr, mParallel, + mSwap, mIsNil, mArrToSeq, mOpenArrayToSeq, + mNewString, mNewStringOfCap, mParseBiggestFloat, + mMove, mEnsureMove, mWasMoved, mDup, mDestroy, mTrace, + mDefault, mUnown, mFinished, mIsolate, mAccessEnv, mAccessTypeField, + mArray, mOpenArray, mRange, mSet, mSeq, mVarargs, + mRef, mPtr, mVar, mDistinct, mVoid, mTuple, + mOrdinal, mIterableType, + mInt, mInt8, mInt16, mInt32, mInt64, + mUInt, mUInt8, mUInt16, mUInt32, mUInt64, + mFloat, mFloat32, mFloat64, mFloat128, + mBool, mChar, mString, mCstring, + mPointer, mNil, mExpr, mStmt, mTypeDesc, + mVoidType, mPNimrodNode, mSpawn, mDeepCopy, + mIsMainModule, mCompileDate, mCompileTime, mProcCall, + mCpuEndian, mHostOS, mHostCPU, mBuildOS, mBuildCPU, mAppType, + mCompileOption, mCompileOptionArg, + mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, + mNKind, mNSymKind, + + mNccValue, mNccInc, mNcsAdd, mNcsIncl, mNcsLen, mNcsAt, + mNctPut, mNctLen, mNctGet, mNctHasNext, mNctNext, + + mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal, + mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetStrVal, mNLineInfo, + mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mNSigHash, mNSizeOf, + mNBindSym, mNCallSite, + mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNGenSym, + mNHint, mNWarning, mNError, + mInstantiationInfo, mGetTypeInfo, mGetTypeInfoV2, + mNimvm, mIntDefine, mStrDefine, mBoolDefine, mGenericDefine, mRunnableExamples, + mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf, + mSymIsInstantiationOf, mNodeId, mPrivateAccess, mZeroDefault + + +const + # things that we can evaluate safely at compile time, even if not asked for it: + ctfeWhitelist* = {mNone, mSucc, + mPred, mInc, mDec, mOrd, mLengthOpenArray, + mLengthStr, mLengthArray, mLengthSeq, + mArrGet, mArrPut, mAsgn, mDestroy, + mIncl, mExcl, mCard, mChr, + mAddI, mSubI, mMulI, mDivI, mModI, + mAddF64, mSubF64, mMulF64, mDivF64, + mShrI, mShlI, mBitandI, mBitorI, mBitxorI, + mMinI, mMaxI, + mAddU, mSubU, mMulU, mDivU, mModU, + mEqI, mLeI, mLtI, + mEqF64, mLeF64, mLtF64, + mLeU, mLtU, + mEqEnum, mLeEnum, mLtEnum, + mEqCh, mLeCh, mLtCh, + mEqB, mLeB, mLtB, + mEqRef, mEqProc, mLePtr, mLtPtr, mEqCString, mXor, + mUnaryMinusI, mUnaryMinusI64, mAbsI, mNot, mUnaryPlusI, mBitnotI, + mUnaryPlusF64, mUnaryMinusF64, + mCharToStr, mBoolToStr, + mCStrToStr, + mStrToStr, mEnumToStr, + mAnd, mOr, + mEqStr, mLeStr, mLtStr, + mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet, mXorSet, + mConStrStr, mAppendStrCh, mAppendStrStr, mAppendSeqElem, + mInSet, mRepr, mOpenArrayToSeq} + + generatedMagics* = {mNone, mIsolate, mFinished, mOpenArrayToSeq} + ## magics that are generated as normal procs in the backend + +type + ItemId* = object + module*: int32 + item*: int32 + +proc `$`*(x: ItemId): string = + "(module: " & $x.module & ", item: " & $x.item & ")" + +proc `==`*(a, b: ItemId): bool {.inline.} = + a.item == b.item and a.module == b.module + +proc hash*(x: ItemId): Hash = + var h: Hash = hash(x.module) + h = h !& hash(x.item) + result = !$h + + +type + PNode* = ref TNode + TNodeSeq* = seq[PNode] + PType* = ref TType + PSym* = ref TSym + TNode*{.final, acyclic.} = object # on a 32bit machine, this takes 32 bytes + when defined(useNodeIds): + id*: int + typField*: PType + info*: TLineInfo + flags*: TNodeFlags + case kind*: TNodeKind + of nkCharLit..nkUInt64Lit: + intVal*: BiggestInt + of nkFloatLit..nkFloat128Lit: + floatVal*: BiggestFloat + of nkStrLit..nkTripleStrLit: + strVal*: string + of nkSym: + sym*: PSym + of nkIdent: + ident*: PIdent + else: + sons*: TNodeSeq + when defined(nimsuggest): + endInfo*: TLineInfo + + TStrTable* = object # a table[PIdent] of PSym + counter*: int + data*: seq[PSym] + + # -------------- backend information ------------------------------- + TLocKind* = enum + locNone, # no location + locTemp, # temporary location + locLocalVar, # location is a local variable + locGlobalVar, # location is a global variable + locParam, # location is a parameter + locField, # location is a record field + locExpr, # "location" is really an expression + locProc, # location is a proc (an address of a procedure) + locData, # location is a constant + locCall, # location is a call expression + locOther # location is something other + TLocFlag* = enum + lfIndirect, # backend introduced a pointer + lfNoDeepCopy, # no need for a deep copy + lfNoDecl, # do not declare it in C + lfDynamicLib, # link symbol to dynamic library + lfExportLib, # export symbol for dynamic library generation + lfHeader, # include header file for symbol + lfImportCompilerProc, # ``importc`` of a compilerproc + lfSingleUse # no location yet and will only be used once + lfEnforceDeref # a copyMem is required to dereference if this a + # ptr array due to C array limitations. + # See #1181, #6422, #11171 + lfPrepareForMutation # string location is about to be mutated (V2) + TStorageLoc* = enum + OnUnknown, # location is unknown (stack, heap or static) + OnStatic, # in a static section + OnStack, # location is on hardware stack + OnHeap # location is on heap or global + # (reference counting needed) + TLocFlags* = set[TLocFlag] + TLoc* = object + k*: TLocKind # kind of location + storage*: TStorageLoc + flags*: TLocFlags # location's flags + lode*: PNode # Node where the location came from; can be faked + snippet*: Rope # C code snippet of location (code generators) + + # ---------------- end of backend information ------------------------------ + + TLibKind* = enum + libHeader, libDynamic + + TLib* = object # also misused for headers! + # keep in sync with PackedLib + kind*: TLibKind + generated*: bool # needed for the backends: + isOverridden*: bool + name*: Rope + path*: PNode # can be a string literal! + + + CompilesId* = int ## id that is used for the caching logic within + ## ``system.compiles``. See the seminst module. + TInstantiation* = object + sym*: PSym + concreteTypes*: seq[PType] + genericParamsCount*: int # for terrible reasons `concreteTypes` contains all the types, + # so we need to know how many generic params there were + # this is not serialized for IC and that is fine. + compilesId*: CompilesId + + PInstantiation* = ref TInstantiation + + TScope* {.acyclic.} = object + depthLevel*: int + symbols*: TStrTable + parent*: PScope + allowPrivateAccess*: seq[PSym] # # enable access to private fields + optionStackLen*: int + + PScope* = ref TScope + + ItemState* = enum + Complete # completely in memory + Partial # partially in memory + Sealed # complete in memory, already written to NIF file, so further mutations are not allowed + + PLib* = ref TLib + TSym* {.acyclic.} = object # Keep in sync with ast2nif.nim + # Check `transitionSymKindCommon` in ast.nim when add a new field. + itemId*: ItemId + # proc and type instantiations are cached in the generic symbol + state*: ItemState + case kindImpl*: TSymKind # Note: kept as 'kind' for case statement, but accessor checks state + of routineKinds: + #procInstCache*: seq[PInstantiation] + gcUnsafetyReasonImpl*: PSym # for better error messages regarding gcsafe + transformedBodyImpl*: PNode # cached body after transf pass + of skLet, skVar, skField, skForVar: + guardImpl*: PSym + bitsizeImpl*: int + alignmentImpl*: int # for alignment + else: nil + magicImpl*: TMagic + typImpl*: PType + name*: PIdent + infoImpl*: TLineInfo + when defined(nimsuggest): + endInfoImpl*: TLineInfo + hasUserSpecifiedTypeImpl*: bool # used for determining whether to display inlay type hints + ownerFieldImpl*: PSym + flagsImpl*: TSymFlags + astImpl*: PNode # syntax tree of proc, iterator, etc.: + # the whole proc including header; this is used + # for easy generation of proper error messages + # for variant record fields the discriminant + # expression + # for modules, it's a placeholder for compiler + # generated code that will be appended to the + # module after the sem pass (see appendToModule) + optionsImpl*: TOptions + positionImpl*: int # used for many different things: + # for enum fields its position; + # for fields its offset + # for parameters its position (starting with 0) + # for a conditional: + # 1 iff the symbol is defined, else 0 + # (or not in symbol table) + # for modules, an unique index corresponding + # to the module's fileIdx + # for variables a slot index for the evaluator + offsetImpl*: int32 # offset of record field + disamb*: int32 # disambiguation number; the basic idea is that + # `___` is unique + locImpl*: TLoc + annexImpl*: PLib # additional fields (seldom used, so we use a + # reference to another object to save space) + when hasFFI: + cnameImpl*: string # resolved C declaration name in importc decl, e.g.: + # proc fun() {.importc: "$1aux".} => cname = funaux + constraintImpl*: PNode # additional constraints like 'lit|result'; also + # misused for the codegenDecl and virtual pragmas in the hope + # it won't cause problems + # for skModule the string literal to output for + # deprecated modules. + instantiatedFromImpl*: PSym # for instances, the generic symbol where it came from. + when defined(nimsuggest): + allUsagesImpl*: seq[TLineInfo] + + TTypeSeq* = seq[PType] + + TTypeAttachedOp* = enum ## as usual, order is important here + attachedWasMoved, + attachedDestructor, + attachedAsgn, + attachedDup, + attachedSink, + attachedTrace, + attachedDeepCopy + + TType* {.acyclic.} = object # \ + # types are identical iff they have the + # same id; there may be multiple copies of a type + # in memory! + # Keep in sync with PackedType + itemId*: ItemId + kind*: TTypeKind # kind of type + state*: ItemState + uniqueId*: ItemId # due to a design mistake, we need to keep the real ID here as it + # is required by the --incremental:on mode. + callConvImpl*: TCallingConvention # for procs + flagsImpl*: TTypeFlags # flags of the type + sonsImpl*: TTypeSeq # base types, etc. + nImpl*: PNode # node for types: + # for range types a nkRange node + # for record types a nkRecord node + # for enum types a list of symbols + # if kind == tyInt: it is an 'int literal(x)' type + # for procs and tyGenericBody, it's the + # formal param list + # for concepts, the concept body + # else: unused + ownerFieldImpl*: PSym # the 'owner' of the type + symImpl*: PSym # types have the sym associated with them + # it is used for converting types to strings + sizeImpl*: BiggestInt # the size of the type in bytes + # -1 means that the size is unknown + alignImpl*: int16 # the type's alignment requirements + paddingAtEndImpl*: int16 # + locImpl*: TLoc + typeInstImpl*: PType # for generic instantiations the tyGenericInst that led to this + # type. + + TPair* = object + key*, val*: RootRef + + TPairSeq* = seq[TPair] + + TIdPair*[T] = object + key*: ItemId + val*: T + + TIdPairSeq*[T] = seq[TIdPair[T]] + TIdTable*[T] = object + counter*: int + data*: TIdPairSeq[T] + + TNodePair* = object + h*: Hash # because it is expensive to compute! + key*: PNode + val*: int + + TNodePairSeq* = seq[TNodePair] + TNodeTable* = object # the same as table[PNode] of int; + # nodes are compared by structure! + counter*: int + data*: TNodePairSeq + ignoreTypes*: bool + + TObjectSeq* = seq[RootRef] + TObjectSet* = object + counter*: int + data*: TObjectSeq + + TImplication* = enum + impUnknown, impNo, impYes + + +const + OverloadableSyms* = {skProc, skFunc, skMethod, skIterator, + skConverter, skModule, skTemplate, skMacro, skEnumField} + + GenericTypes*: TTypeKinds = {tyGenericInvocation, tyGenericBody, + tyGenericParam} + + StructuralEquivTypes*: TTypeKinds = {tyNil, tyTuple, tyArray, + tySet, tyRange, tyPtr, tyRef, tyVar, tyLent, tySequence, tyProc, tyOpenArray, + tyVarargs} + + ConcreteTypes*: TTypeKinds = { # types of the expr that may occur in:: + # var x = expr + tyBool, tyChar, tyEnum, tyArray, tyObject, + tySet, tyTuple, tyRange, tyPtr, tyRef, tyVar, tyLent, tySequence, tyProc, + tyPointer, + tyOpenArray, tyString, tyCstring, tyInt..tyInt64, tyFloat..tyFloat128, + tyUInt..tyUInt64} + IntegralTypes* = {tyBool, tyChar, tyEnum, tyInt..tyInt64, + tyFloat..tyFloat128, tyUInt..tyUInt64} # weird name because it contains tyFloat + ConstantDataTypes*: TTypeKinds = {tyArray, tySet, + tyTuple, tySequence} + NilableTypes*: TTypeKinds = {tyPointer, tyCstring, tyRef, tyPtr, + tyProc, tyError} # TODO + PtrLikeKinds*: TTypeKinds = {tyPointer, tyPtr} # for VM + PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, + nfDotSetter, nfDotField, + nfIsRef, nfIsPtr, nfPreventCg, nfLL, + nfFromTemplate, nfDefaultRefsParam, + nfExecuteOnReload, nfLastRead, + nfFirstWrite, nfSkipFieldChecking, + nfDisabledOpenSym, nfLazyType} + namePos* = 0 + patternPos* = 1 # empty except for term rewriting macros + genericParamsPos* = 2 + paramsPos* = 3 + pragmasPos* = 4 + miscPos* = 5 # used for undocumented and hacky stuff + bodyPos* = 6 # position of body; use rodread.getBody() instead! + resultPos* = 7 + dispatcherPos* = 8 + + nfAllFieldsSet* = nfBase2 + + nkIdentKinds* = {nkIdent, nkSym, nkAccQuoted, nkOpenSymChoice, + nkClosedSymChoice, nkOpenSym} + + nkPragmaCallKinds* = {nkExprColonExpr, nkCall, nkCallStrLit} + nkLiterals* = {nkCharLit..nkTripleStrLit} + nkFloatLiterals* = {nkFloatLit..nkFloat128Lit} + nkLambdaKinds* = {nkLambda, nkDo} + declarativeDefs* = {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef} + routineDefs* = declarativeDefs + {nkMacroDef, nkTemplateDef} + procDefs* = nkLambdaKinds + declarativeDefs + callableDefs* = nkLambdaKinds + routineDefs + + nkSymChoices* = {nkClosedSymChoice, nkOpenSymChoice} + nkStrKinds* = {nkStrLit..nkTripleStrLit} + + skLocalVars* = {skVar, skLet, skForVar, skParam, skResult} + skProcKinds* = {skProc, skFunc, skTemplate, skMacro, skIterator, + skMethod, skConverter} + + defaultSize* = -1 + defaultAlignment* = -1 + defaultOffset* = -1 + + +proc len*(n: PNode): int {.inline.} = + result = n.sons.len + +proc safeLen*(n: PNode): int {.inline.} = + ## works even for leaves. + if n.kind in {nkNone..nkNilLit}: result = 0 + else: result = n.len + +template `[]`*(n: PNode, i: int): PNode = n.sons[i] +template `[]=`*(n: PNode, i: int; x: PNode) = n.sons[i] = x + +template `[]`*(n: PNode, i: BackwardsIndex): PNode = n[n.len - i.int] +template `[]=`*(n: PNode, i: BackwardsIndex; x: PNode) = n[n.len - i.int] = x + +iterator items*(n: PNode): PNode = + for i in 0.. 0: + newSeq(result.sons, children) + setIdMaybe() + +proc newNodeIT*(kind: TNodeKind, info: TLineInfo, typ: PType): PNode = + ## new node with line info, type, and no children + result = newNode(kind) + result.info = info + result.typField = typ + +proc newNode*(kind: TNodeKind, info: TLineInfo): PNode = + ## new node with line info, no type, and no children + newNodeImpl(info) + setIdMaybe() + +proc newIdentNode*(ident: PIdent, info: TLineInfo): PNode = + result = newNode(nkIdent) + result.ident = ident + result.info = info + +proc newSymNode*(sym: PSym, info: TLineInfo): PNode = + result = newNode(nkSym) + result.sym = sym + result.typField = sym.typImpl + result.info = info + +proc newStrNode*(kind: TNodeKind, strVal: string): PNode = + result = newNode(kind) + result.strVal = strVal + +proc newStrNode*(strVal: string; info: TLineInfo): PNode = + result = newNodeI(nkStrLit, info) + result.strVal = strVal + +# Hooks, converters, method dispatchers and enum-to-string generated procs need special +# handling for IC, they end up in IC indexes etc. Thus we "log" them in the module graph +# and to pass them around to the NIF writer. This is not very elegant but it works. + +type + LogEntryKind* = enum + HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry + LogEntry* = object + kind*: LogEntryKind + op*: TTypeAttachedOp + isGeneric*: bool + module*: int # Which module this entry belongs to + key*: string + sym*: PSym + + +proc forcePartial*(s: PSym) = + ## Resets all impl-fields to their default values and sets state to Partial. + ## This is useful for creating a stub symbol that can be lazily loaded later. + ## The fields itemId, name, and disamb are preserved. + s.state = Partial + case s.kindImpl + of routineKinds: + s.gcUnsafetyReasonImpl = nil + s.transformedBodyImpl = nil + of skLet, skVar, skField, skForVar: + s.guardImpl = nil + s.bitsizeImpl = 0 + s.alignmentImpl = 0 # for alignment + else: discard + s.magicImpl = mNone + s.typImpl = nil + s.infoImpl = unknownLineInfo + s.ownerFieldImpl = nil + s.flagsImpl = {} + s.astImpl = nil + s.optionsImpl = {} + s.positionImpl = 0 + s.offsetImpl = 0 + s.locImpl = TLoc() + s.annexImpl = nil + s.constraintImpl = nil + s.instantiatedFromImpl = nil + when defined(nimsuggest): + s.endInfoImpl = unknownLineInfo + s.hasUserSpecifiedTypeImpl = false + s.allUsagesImpl = @[] + when hasFFI: + s.cnameImpl = "" + +proc forcePartial*(t: PType) = + ## Resets all impl-fields to their default values and sets state to Partial. + ## This is useful for creating a stub type that can be lazily loaded later. + ## The fields itemId, kind, uniqueId are preserved. + t.state = Partial + t.callConvImpl = ccNimCall + t.flagsImpl = {} + t.sonsImpl = @[] + t.nImpl = nil + t.ownerFieldImpl = nil + t.symImpl = nil + t.sizeImpl = defaultSize + t.alignImpl = defaultAlignment + t.paddingAtEndImpl = 0'i16 + t.locImpl = TLoc() + t.typeInstImpl = nil + +const # for all kind of hash tables: + GrowthFactor* = 2 # must be power of 2, > 0 + StartSize* = 8 # must be power of 2, > 0 + +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 mustRehash*(length, counter: int): bool = + assert(length > counter) + result = (length * 2 < counter * 3) or (length - counter < 4) + +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)) diff --git a/compiler/btrees.nim b/compiler/btrees.nim index 3b737b1bc9..1e5538d820 100644 --- a/compiler/btrees.nim +++ b/compiler/btrees.nim @@ -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.. conv ( addr ( x ) ) + var exprLoc: TLoc = initLocExpr(p, e[0][1]) + var tmp = getTemp(p, e.typ, needsInit=false) + putIntoDest(p, tmp, e, cCast(getTypeDesc(p.module, e.typ), addrLoc(p.config, exprLoc))) + putIntoDest(p, d, e, rdLoc(tmp)) + else: + putIntoDest(p, d, e, addrLoc(p.config, a), a.storage) template inheritLocation(d: var TLoc, a: TLoc) = if d.k == locNone: d.storage = a.storage @@ -989,7 +1015,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}) + let tupType = a.t.skipTypes(abstractInst+{tyVar}+tyUserTypeClasses) # ref #25227 assert tupType.kind == tyTuple d.inheritLocation(a) discard getTypeDesc(p.module, a.t) # fill the record's fields.loc @@ -1292,13 +1318,24 @@ 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 lfPrepareForMutation in d.flags and ty.kind == tyString and - optSeqDestructors in p.config.globalOptions: + if p.config.usesSso() and ty.kind == tyString: 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) + 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) proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) = var ty = skipTypes(n[0].typ, abstractVarRange + tyUserTypeClasses) @@ -1570,6 +1607,51 @@ 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) @@ -1705,15 +1787,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(et), - cAlignof(et)) + cSizeof(pe), + cAlignof(pe)) else: let lenIsZero = e[2].kind == nkIntLit and e[2].intVal == 0 genNewSeqAux(p, a, b.rdLoc, lenIsZero) @@ -1726,15 +1808,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(et), - cAlignof(et)) + cSizeof(pe), + cAlignof(pe)) else: if d.k == locNone: d = getTemp(p, e.typ, needsInit=false) # bug #22560 let ra = a.rdLoc @@ -1807,15 +1889,22 @@ 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 + # 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) var useTemp = isRef or - (d.k notin {locTemp,locLocalVar,locGlobalVar,locParam,locField}) or + d.k == locNone or + (d.t != nil and not sameBackendType(t, d.t.skipTypes(abstractInstOwned))) or (isPartOf(d.lode, e) != arNo) var tmp: TLoc = default(TLoc) var r: Rope - let needsZeroMem = p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc} or nfAllFieldsSet notin e.flags + let needsZeroMem = p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcYrc} or nfAllFieldsSet notin e.flags if useTemp: tmp = getTemp(p, t) r = rdLoc(tmp) @@ -1870,15 +1959,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(et), - cAlignof(et)) + cSizeof(pe), + cAlignof(pe)) else: # generate call to newSeq before adding the elements per hand: genNewSeqAux(p, dest[], lit, n.len == 0) @@ -1899,7 +1988,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: @@ -1911,15 +2000,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(et), - cAlignof(et)) + cSizeof(pe), + cAlignof(pe)) else: let lit = cIntLiteral(L) genNewSeqAux(p, d, lit, L == 0) @@ -2060,12 +2149,20 @@ 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) - putIntoDest(p, b, e, - cIfExpr(dataFieldAccessor(p, ra), dataField(p, ra), NimNil) & - cArgumentSeparator & la, - a.storage) + 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) of tyArray: let ra = rdLoc(a) let la = cIntValue(lengthOrd(p.config, a.t)) @@ -2205,7 +2302,7 @@ proc isTrivialTypesToSnippet(t: PType): Snippet = else: result = NimTrue -proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = +proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc, noinit = false) = if optSeqDestructors in p.config.globalOptions: e[1] = makeAddr(e[1], p.module.idgen) genCall(p, e, d) @@ -2227,7 +2324,9 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = pExpr = cIfExpr(ra, cAddr(derefField(ra, "Sup")), NimNil) else: pExpr = ra - call.snippet = cCast(rt, cgCall(p, "setLengthSeqV2", pExpr, rti, rb, + + let name = if noinit: "setLengthSeqUninit" else: "setLengthSeqV2" + call.snippet = cCast(rt, cgCall(p, name, pExpr, rti, rb, isTrivialTypesToSnippet(t.skipTypes(abstractInst)[0]))) genAssignment(p, a, call, {}) @@ -2637,17 +2736,16 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc) = putIntoDest(p, d, n, cCast(destType, wrapPar(val)), a.storage) proc genConv(p: BProc, e: PNode, d: var TLoc) = - let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) - if sameBackendTypeIgnoreRange(destType, e[1].typ): + if ignoreConv(e): expr(p, e[1], d) else: genSomeCast(p, e, d) 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", rdLoc(a)), -# "($1 ? $1->data : (NCSTRING)\"\")" % [a.rdLoc], + cgCall(p, "nimToCStringConv", arg), a.storage) proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = @@ -2718,45 +2816,38 @@ 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) - p.s(cpsStmts).addSingleIfStmt( - cOp(NotEqual, - dotField(destVal, "p"), - dotField(srcVal, "p"))): + if p.config.usesSso() and + n[1].typ.skipTypes(abstractVar).kind == tyString: + # SmallString: destroy dst then struct-copy src; no .p field aliasing needed genStmts(p, n[3]) - p.s(cpsStmts).addFieldAssignment(destVal, "len", dotField(srcVal, "len")) - p.s(cpsStmts).addFieldAssignment(destVal, "p", dotField(srcVal, "p")) + 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")) else: if d.k == locNone: d = getTemp(p, n.typ) - if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}: - genAssignment(p, d, a, {}) + if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}: var op = getAttachedOp(p.module.g.graph, n.typ, attachedWasMoved) - if op == nil: + if op == nil or sfOverridden notin op.flags: + var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation}) + genAssignment(p, d, a, {}) resetLoc(p, a) else: - 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) + n[1] = makeAddr(n[1], p.module.idgen) + genCall(p, n, d) else: + var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation}) genAssignment(p, d, a, {}) resetLoc(p, a) @@ -2767,15 +2858,19 @@ proc genDestroy(p: BProc; n: PNode) = case t.kind of tyString: var a: TLoc = initLocExpr(p, arg) - 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) + 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) of tySequence: var a: TLoc = initLocExpr(p, arg) let ra = rdLoc(a) @@ -2817,7 +2912,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}) + p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}) if d.k == locNone: d = getTemp(p, e.typ) let dest = rdLoc(d) p.s(cpsStmts).addFieldAssignment(dest, "Field0", x) @@ -2880,8 +2975,15 @@ 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: - e[1] = makeAddr(e[1], p.module.idgen) - genCall(p, e, d) + 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) else: genSeqElemAppend(p, e, d) of mEqStr: genStrEquals(p, e, d) @@ -2967,6 +3069,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimGCunref"), ra) of mSetLengthStr: genSetLengthStr(p, e, d) of mSetLengthSeq: genSetLengthSeq(p, e, d) + of mSetLengthSeqUninit: genSetLengthSeq(p, e, d, noinit = true) of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet, mInSet, mXorSet: genSetOp(p, e, d, op) @@ -3020,7 +3123,7 @@ 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} and optEnableDeepCopy notin p.config.globalOptions: + if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} 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") @@ -3136,6 +3239,8 @@ proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) = for i in 0.. 0) and noSafePoints notin p.flags: # If we're in a finally block, and we came here by exception # consume it before we return. @@ -736,8 +743,10 @@ proc genBlock(p: BProc, n: PNode, d: var TLoc) = # named block? assert(n[0].kind == nkSym) var sym = n[0].sym - sym.loc.k = locOther - sym.position = p.breakIdx+1 + backendEnsureMutable sym + sym.locImpl.k = locOther + sym.positionImpl = p.breakIdx+1 + # ^ IC: review this expr(p, n[1], d) endSimpleBlock(p, scope) @@ -827,12 +836,26 @@ proc raiseExitCleanup(p: BProc, destroy: string) = p.s(cpsStmts).addGoto("LA" & $p.nestedTryStmts[^1].label & "_") proc finallyActions(p: BProc) = - 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]) + 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 proc raiseInstr(p: BProc; result: var Builder) = if p.config.exc == excGoto: @@ -1156,7 +1179,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = throw; } } catch(...) { - // C++ exception occured, not under Nim's control. + // C++ exception occurred, not under Nim's control. } { /* finally: */ @@ -1176,7 +1199,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, 0.Natural)) + p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural)) if t.kind == nkHiddenTryStmt: lineCg(p, cpsStmts, "try {$n", []) @@ -1250,7 +1273,8 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = initElifBranch(p.s(cpsStmts), ifStmt, orExpr) if exvar != nil: fillLocalName(p, exvar.sym) - fillLoc(exvar.sym.loc, locTemp, exvar, OnStack) + backendEnsureMutable exvar.sym + fillLoc(exvar.sym.locImpl, 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: @@ -1298,7 +1322,8 @@ 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) - fillLoc(exvar.sym.loc, locTemp, exvar, OnStack) + backendEnsureMutable exvar.sym + fillLoc(exvar.sym.locImpl, 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 @@ -1360,7 +1385,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, 0.Natural)) + p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural)) startBlockWith(p): p.s(cpsStmts).add("try {\n") expr(p, t[0], d) @@ -1389,7 +1414,8 @@ 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) - fillLoc(exvar.sym.loc, locTemp, exvar, OnUnknown) + backendEnsureMutable exvar.sym + fillLoc(exvar.sym.locImpl, 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: @@ -1438,7 +1464,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, Natural lab)) + p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, Natural lab)) p.flags.incl nimErrorFlagAccessed @@ -1644,7 +1670,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, 0.Natural)) + p.nestedTryStmts.add((fin, quirkyExceptions, t.kind == nkHiddenTryStmt, 0.Natural)) expr(p, t[0], d) var quirkyIf = default(IfBuilder) var quirkyScope = default(ScopeBuilder) @@ -1928,6 +1954,15 @@ 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] diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index a49ea802ac..8e6b44c81d 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -11,7 +11,7 @@ # ------------------------- Name Mangling -------------------------------- -import sighashes, modulegraphs, std/strscans +import sighashes, std/strscans import ../dist/checksums/src/checksums/md5 import std/sequtils @@ -75,7 +75,7 @@ proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string = proc fillBackendName(m: BModule; s: PSym) = if s.loc.snippet == "": var result: Rope - if not m.compileToCpp and s.kind in routineKinds and optCDebug in m.g.config.globalOptions and + if s.kind in routineKinds and {optCDebug, optItaniumMangle} * m.g.config.globalOptions == {optCDebug, optItaniumMangle} and m.g.config.symbolFiles == disabledSf: result = mangleProc(m, s, false).rope else: @@ -84,7 +84,8 @@ proc fillBackendName(m: BModule; s: PSym) = if m.hcrOn: result.add '_' result.add(idOrSig(s, m.module.name.s.mangle, m.sigConflicts, m.config)) - s.loc.snippet = result + backendEnsureMutable s + s.locImpl.snippet = result proc fillParamName(m: BModule; s: PSym) = if s.loc.snippet == "": @@ -107,7 +108,8 @@ 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. - s.loc.snippet = res.rope + backendEnsureMutable s + s.locImpl.snippet = res.rope proc fillLocalName(p: BProc; s: PSym) = assert s.kind in skLocalVars+{skTemp} @@ -122,7 +124,8 @@ proc fillLocalName(p: BProc; s: PSym) = elif s.kind != skResult: result.add "_" & rope(counter+1) p.sigConflicts.inc(key) - s.loc.snippet = result + backendEnsureMutable s + s.locImpl.snippet = result proc scopeMangledParam(p: BProc; param: PSym) = ## parameter generation only takes BModule, not a BProc, so we have to @@ -156,8 +159,9 @@ 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 == "": - typ.typeName(typ.loc.snippet) - typ.loc.snippet.add $sig + backendEnsureMutable typ + typ.typeName(typ.locImpl.snippet) + typ.locImpl.snippet.add $sig else: when defined(debugSigHashes): # check consistency: @@ -247,6 +251,7 @@ 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 @@ -272,7 +277,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}): + (typ.callConv == ccCDecl and conf.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}): # prevents nrvo for cdecl procs; # bug #23401 result = false else: @@ -289,7 +294,12 @@ 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): + 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 var msg = "backend & Nim disagree on size for: " msg.addTypeHeader(m.config, t) var msg2 = "" @@ -299,12 +309,13 @@ proc addAbiCheck(m: BModule; t: PType, name: Rope) = proc fillResult(conf: ConfigRef; param: PNode, proctype: PType) = - fillLoc(param.sym.loc, locParam, param, "Result", + backendEnsureMutable param.sym + fillLoc(param.sym.locImpl, locParam, param, "Result", OnStack) let t = param.sym.typ if mapReturnType(conf, t) != ctArray and isInvalidReturnType(conf, proctype): - incl(param.sym.loc.flags, lfIndirect) - param.sym.loc.storage = OnUnknown + incl(param.sym.locImpl.flags, lfIndirect) + param.sym.locImpl.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: @@ -328,6 +339,10 @@ 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*") @@ -442,6 +457,14 @@ 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) @@ -523,14 +546,15 @@ 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.loc, locParam, t.n[1], + fillLoc(this.locImpl, locParam, t.n[1], this.paramStorageLoc) if this.typ.kind == tyPtr: - this.loc.snippet = "this" + this.locImpl.snippet = "this" else: - this.loc.snippet = "(*this)" - names.add this.loc.snippet + this.locImpl.snippet = "(*this)" + names.add this.locImpl.snippet types.add getTypeDescWeak(m, this.typ, check, dkParam) let firstParam = if isCtor: 1 else: 2 @@ -544,13 +568,14 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params else: descKind = dkRefParam var typ, name: string + backendEnsureMutable param fillParamName(m, param) - fillLoc(param.loc, locParam, t.n[i], + fillLoc(param.locImpl, locParam, t.n[i], param.paramStorageLoc) if ccgIntroducedPtr(m.config, param, t.returnType) and descKind == dkParam: typ = getTypeDescWeak(m, param.typ, check, descKind) & "*" - incl(param.loc.flags, lfIndirect) - param.loc.storage = OnUnknown + incl(param.locImpl.flags, lfIndirect) + param.locImpl.storage = OnUnknown elif weakDep: typ = getTypeDescWeak(m, param.typ, check, descKind) else: @@ -558,7 +583,7 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params if sfNoalias in param.flags: typ.add("NIM_NOALIAS ") - name = param.loc.snippet + name = param.locImpl.snippet types.add typ names.add name if sfCodegenDecl notin param.flags: @@ -587,7 +612,7 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder, if t.returnType == nil or isInvalidReturnType(m.config, t): rettype = CVoid else: - rettype = getTypeDescAux(m, t.returnType, check, dkResult) + rettype = getTypeDescWeak(m, t.returnType, check, dkResult) var paramBuilder: ProcParamBuilder params.addProcParams(paramBuilder): for i in 1..