A closure's environment type — and the `=destroy`/`=copy`/`=sink` the compiler
lifts for it — is minted by the BACKEND and exists in no module's semmed NIF.
Grinding a corpus of closure programs against `nim c` as the oracle turned up
three ways the per-module backend gets those wrong. `tests/closure` is now green
under `--ic:on`, and `tests/iter` goes from 13 failures to 9.
* **The env hook is emitted by nobody.** `emitsBodyInThisModule` walks up to the
outermost enclosing routine to pick the TU that emits a body. For the env
`=destroy` of a generic closure iterator defined in one module and instantiated
in another, that walk lands on the module of the ORIGINAL generic — a module
that never sees the instance — so the routine was emitted nowhere
(`undefined reference to eqdestroy__c485__…`). A backend-minted routine has no
owning module NIF at all: it is written into the `.t.bif` of every module that
references it, re-homed there with `@bk`. Every referencing TU emits it now and
the merge stage keeps one, like any other content-addressed definition.
* **The `:up` link is stored without an increment.** `env.:up = enclosingEnv` has
to go through `=copy` (with the cyclic incref) or the parent's refcount is one
too low, and at teardown the two envs' mutually recursive `=destroy`s each
believe they hold the last reference and recurse until the stack is gone — a
SIGSEGV after the program's own output has already been printed
(`tests/iter/tnestedclosures`, "Test 3"). Whether it becomes a `=copy` depends
on the up-field type's hooks existing at injection time. Whole-program cgen got
that for free: a LATER lifting pass creates them and it runs before any
routine's injection. The per-module backend injects a routine right after
lifting it (`lower`), long before the module's top level is transformed at all
(`cg`) — so both up-field assignment sites create the ops themselves.
* **Two backend hooks collide on one C name.** A backend-minted symbol is named
`_c<itemId.item>`, a PER-PROCESS counter — fine while "nifc lifts, emits and
compiles them in one run", which is what `mangleProcNameExt` assumed. But
`lower` mints the env hooks of nested routines and `cg` mints those of the
module's top level, and both land in the same translation unit: two unrelated
`=destroy`s became one C function. `mangleProcNameExt` already makes an
exception for hooks whose `disamb` is content-derived (`HookDisambBit`);
`toNifSymName` now mirrors it, so the content-derived value survives the round
trip instead of being overwritten by the loader.
`InstanceDisambBit`/`HookDisambBit` move to `astdef` — `ast2nif` names symbols by
them and cannot import `modulegraphs`.
Two new metamorphic tests, both of which fail on the previous compiler:
`tclosure_hooks` (generic closure iterator instantiated across modules) and
`tclosure_nested_iter` (closure iterator nested in a closure iterator).
`icFormatVersion` 37 -> 38 for the hook naming. `koch bootic` reaches its
byte-identical fixed point; `tests/closure`, `tests/destructor` (3 known
`--newruntime` failures), `tests/cpp` and the classic categories are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`nim ic` was a command of its own, which made it the C backend only and cut it
off from everything the ordinary compile commands accept. `--ic:on` is a switch
on `nim c` / `nim cpp` / `nim objc` instead, so `-r`, `-d:release`,
`--exceptions:`, and a project-wide opt-in from `nim.cfg` / `config.nims` all
work. `nim ic` still resolves to the same driver; `koch bootic`, `koch ic` and
`testament --ic` now go through the switch.
The switch was already parsed into a `conf.ic` nobody read. It is now read in
`passCmd1` as well, because `nim.nim` has to decide whether this run is an IC
DRIVER before config loading (`ensureIcConfig` produces the precompiled config
the driver itself replays); when the switch comes from a config file instead,
`main.nim` produces it late.
**C++.** `tests/cpp` passes under `--ic:on`, matching its classic result. Four
fixes, three of them the shape of every "C++ needs the whole program" problem the
per-module backend has:
* The driver DECLARES each module's translation unit to nifmake without loading a
module, so it cannot ask `cgen.getCFile` — and it hardcoded `.nim.c`, so the
merge stage went looking for `.c.nif` next to the `.cpp.nif` the children had
written. `options.icCFileExt` mirrors the formula at backend granularity.
* C++ has no designated initializers, so the RTTI record is a bare variable that
`DatInit` fills field by field. A bare `TNimTypeV2 x;` is a tentative
definition — C's linker merges those, C++'s does not — so every TU that
demanded the type defined it ("multiple definition of NTIv2__…"). It now gets
the same extern-declaration + owned-`'d'`-definition split the C flavour has.
* `memberProcsPerType` / `initializersPerType` live only in the sem process, so
the backend emitted a struct WITHOUT its in-class member declarations and the
out-of-class definitions did not match ("no declaration matches
'void Doo::memberProc()'"). They are replayed from a new `(repcppmember …)`
log entry; `replayCppMember` re-derives the type from the routine's signature
exactly as `semCppMember` does, so no type key has to survive the round trip.
* Two follow-ons for members: `loc.snippet` is a CALL PATTERN (`#->salute(@)`),
and only `genMemberProcHeader` derives it — whole-program cgen got it for free
by generating the defining module first, but the per-module backend emits that
body in another process, leaving the caller with the mangled Nim name
(`loo->salute_u0__vireouyks1()`). And that pattern is not a linker name: every
`salute` member in every class mints the same one, so the merge stage handed
them all to one artifact and dropped the rest (undefined vtable at link).
Member definitions are keyed by their NIF name there instead.
`--run` is now dropped when re-invoking for the config artifact: the producer has
no output binary and `nim.nim`'s run step asserted on the empty `outFile`.
testament's `--ic` appends the switch rather than rewriting the compile verb, so
a test that overrides `cmd:` wholesale keeps its verb, and the C++ corpus is
covered too (it never was — the old rewrite only matched `nim c `).
`icFormatVersion` 36 -> 37 for the new log entry. `koch bootic` reaches its
byte-identical fixed point through the new entry point; `koch ic` passes;
`tests/ic`, `tests/destructor` (3 known `--newruntime` failures) and the classic
categories are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`testament --ic` on `tests/destructor` went from 10 failures to 3; the three
left are all `--newruntime`, whose owned-ref RTTI destructor is still missing
(the object type's `=destroy` never reaches the `cg` that emits the type info).
* **global destructors are never run.** `graph.globalDestructors` is filled
while a module's top level goes through `injectDestructorCalls`, and
whole-program cgen empties the list into the main module's init proc — which
IS the program body, so the calls land at exit. Under `nim ic` every module's
`cg` is a separate process and main's only ever saw its own entries: a
module-level `var` with a `=destroy` in an imported module was simply never
destroyed. Each module now wraps its own list in a nullary exported proc and
announces the name in its `.c.nif` meta head (`CnifVersion` 4 -> 5); main's
`cg` reads the heads it already reads for init/datInit and calls them in
reverse dependency order. New test `tests/ic/tglobal_dtors.nim` pins the
order against the oracle.
* **`spawn` died with "system module needs: nimArgsPassingDone".** A module
loaded from a NIF is named by its mangled suffix, so `loadCompilerProc`'s
`module.name.s == "threadpool"` could never match. The backend loads the whole
program before codegen, so consult every loaded module's index instead; misses
are final and remembered (`getCompilerProc` doubles as a presence probe).
* **`new(x, finalizer)` died with "environment misses: x".** `semmagic`'s
finalizer-to-destructor wrapper copies the original's AST but only rewrites
the parameter, leaving `ast[namePos]` naming the ORIGINAL proc. ast2nif
re-derives a routine's serialized AST from `ast[namePos].sym.ast`, so the
wrapper serialized the original's body — whose parameter belongs to the
original — and lambda lifting saw it as a captured variable of another proc.
The copy now names itself, the invariant every other routine AST keeps.
* **a top-level `let (a, b) = f()` copied instead of moved.** A module's own
symbol is the owner of every top-level symbol and is written as a real `(sd)`,
so the loader minted a SECOND `skModule` PSym for it — and `sym.owner == owner`
is an identity test in `aliasanalysis.isAnalysableFieldAccess`, which made
every module-level location un-analysable. Hard error for a type with a
disabled `=copy`. Bind the NIF name to the one registered module symbol.
Backend only: doing it under `nim m` costs `times.toDateTimeByWeek` its
inferred `raises`.
* **a doubly linked list leaked its whole contents.** A field USE serializes as
a bare `SymUse` with nowhere to put symbol flags, so `trees.isCursor` said
"not a cursor" for every loaded field and `DoublyLinkedNode.prev` became a
COUNTED reference: every node held its predecessor alive and no refcount ever
hit zero. `{.cursor.}` now rides in the NIF name marker (`` `fc `` next to
`` `f ``), which def and use derive from the same `PSym`.
* **`--expandArc` came out shuffled.** `moduleSymbolStubs` iterated a `Table`,
i.e. hash order, so the `lower` stage transformed a module's routines in an
arbitrary order — not even stable between two compilers. Order by index
offset, which is source order.
Also: `testament`'s `generatedFile` did not include the matrix entry in the
nimcache key its caller uses, so every `ccodeCheck` test with a `matrix:`
reported `reCodeNotFound`.
`koch bootic` still reaches its fixed point; `tests/ic` and the classic
`tests/destructor` are unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Grinding a small figdraw-based program under `nim ic` and diffing its output
against the classic backend surfaced eight bugs, four of which silently produced
a wrong binary rather than an error.
Frontend / build graph (`deps.nim`):
* Dead `when`-guarded imports were compiled anyway. `when someStrdefine == "x":
import y` is `cvUnknown` to the scanner, which conservatively keeps the edge —
right for an edge, but it also gave `y` its own `nim m` rule, so a build died
on a package the user never installed because they never selected that backend.
Track which edges are speculative and drop a speculative subtree that cannot
compile; if the guard was in fact live, the discovery fixpoint puts the node
back with the honest `cannot open file`.
* Deleting a still-imported module went unnoticed: no mtime moves, so nothing
re-fires and `nim ic` relinked a stale binary while `nim c` reported `cannot
open file`. Report an unresolvable import from a non-speculatively reached
module during the graph scan.
* Macro-generated imports were discovered once and then forgotten. Discovery
only ran after a failure and the graph is re-derived statically every run, so
on a warm build the discovered module had no rules at all and editing it
changed nothing. Seed the graph from the `.s.deps` sidecars up front.
* Config changes invalidated nothing. nifmake decides staleness from file mtimes
and never looks at a rule's command line, so `-d:foo=bar` / `--mm:` / `--threads:`
regenerated the build file with the new switches and re-fired zero rules.
Reify the configuration as a file and make it an input of every rule.
* Command-line switches never reached the children: they replay the project's
config files, never the driver's argv, so `nim ic --opt:speed` produced a
byte-identical debug binary (likewise `--panics`, `--experimental`, `--passC`).
Forward the driver's switches, minus the ones that must differ per child.
Artifacts and codegen:
* A failed `nim m` still wrote its `.s.bif` and cookies, so nifmake saw the rule
as satisfied on the next run: `nim ic` then reported success for a program that
does not compile, and generated code from error-bearing AST (or hit an internal
error in `ccgexprs`). Never persist an artifact when `errorCounter > 0`.
* Top-level destructors were never injected. `sfInjectDestructors` lives on the
module symbol, which `moduleFromNifFile` rebuilds from scratch, so
`genTopLevelStmt` skipped `injectDestructorCalls` entirely: a module-level
`block: let h = openHandle()` never ran `=destroy`. Persist the flag as a
`(modflags)` record. `injectdestructors` also has to tolerate the
`nkReplayAction` entries the loader prepends to `topLevel`.
* `nfFirstWrite` / `nfLastRead` were dropped by the serializer. A sym node is
written as a bare NIF `SymUse` token, which has nowhere to put node flags, so
the frontend's move analysis never reached the backend: EVERY first assignment
to a destructor-bearing local compiled as `=sink`, i.e. `=destroy` on
still-zeroed memory followed by a copy, and no read was ever a move. Wrap a sym
use in `(nflags ...)` when it carries persistent node flags.
`icFormatVersion` 34 -> 35 for the two new NIF records.
Validation: `koch bootic` reaches its byte-identical fixed point; two clean-cache
builds from the same compiler are identical; testament `arc`, `destructor`,
`macros`, `template`, `iter`, `closure`, `ccg`, `codegen`, `types` and `effects`
pass, and `generics`, `ic` and `stdlib` show exactly the pre-existing failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## Summary
Adds `--genBif:on|off`, allowing regular compiler builds to generate
per-module semantic BIF artifacts in `nimcache`.
This reuses the semantic artifact format produced by incremental
compilation without enabling IC or changing the normal code-generation
and linking pipeline.
In comparison to `nim check --compress ...` this new flag `nim c
--genBif:on --compileOnly yourlib.nim` is considerably more useful for
tooling.
That produced full semantic proc declarations, Nim visibility,
signatures, overload disambiguators, and pragmas. For a proc that was
actually code-generated, it also recorded the exact backend name, for
example.
## Motivation
External tools such as language servers, debuggers, and binding
generators can benefit from resolved symbol and type information
produced during an ordinary build. Previously, these semantic BIF
artifacts were tied to the incremental compiler workflow.
## Details
With the option enabled:
```sh
nim c --genBif:on project.nim
```
the compiler writes semantic `.s.bif` files and their supporting
sidecars for each semantically checked module while continuing with the
requested backend normally.
The option:
- Works with non-IC builds.
- Does not enable incremental compilation.
- Does not change generated program behavior.
- Does not enable or introduce native ABI exports.
- Does not generate `.abi.nif` manifests.
- Is ignored for NimScript compilation.
The `genBif` name follows existing artifact-generation options such as
`genScript`, `genMapping`, and `genCDeps`.
## Testing
Added a focused C backend test that runs a regular build with
`--genBif:on` and verifies that semantic `.s.bif` artifacts are
generated.
A release-mode temporary compiler build and the focused Testament test
both pass.
This pull request allows setting `--cpu:wasm64`, allowing wasm64 as a
first class target. This avoids having to set `-cpu:riscv64` as a
workaround. Sane defaults for the emscripten toolchain are also
provided.
fixes#25617
This pull request introduces a stricter check for parameter type
relations in the `procParamTypeRel` procedure. Specifically, it ensures
that two types are not only structurally equal but also have the same
backend type, taking type aliases into account.
Type relation checks:
*
[`compiler/sigmatch.nim`](diffhunk://#diff-251afcd01d239369019495096c187998dd6695b6457528953237a7e4a10f7138R787-R789):
In `procParamTypeRel`, added a check to ensure that if two types are
considered equal (`isEqual`), they must also have the same backend type
(using `sameBackendTypePickyAliases`). If not, the result is set to
`isNone`, preventing false positives when type aliases differ.
First performance numbers:
time tests/arc/torcbench -- YRC
true peak memory: true
real 0m0,163s
user 0m0,161s
sys 0m0,002s
time tests/arc/torcbench -- ORC
true peak memory: true
real 0m0,107s
user 0m0,104s
sys 0m0,003s
So it's 1.6x slower. But it's threadsafe and provably correct. (Lean and
model checking via TLA+ used.)
Of course there is always the chance that the implementation is wrong
and doesn't match the model.
Implements #21928
Adds a `--raw` (since thats what the original issue used, suggestions
welcome) switch which stops the jsondoc gen from rendering rst/markdown.
Implemented by making `genComment` check if it needs to return the raw
string or not. This required switching the related procs to using
`Option` to handle how `nil` values were returned before. The `nil`
returns were eventually ignored so just ignoring `none(T)` has the same
effect.
Doesn't support `runnableExamples` since jsondocs doesn't support them
either
TODO:
- [ ] test writing of .nif files
- [x] implement loading of fields in PType/PSym that might not have been
loaded
- [ ] implement interface logic
- [ ] implement pragma "replays"
- [ ] implement special logic for `converter`
- [ ] implement special logic for `method`
- [ ] test the logic holds up for `export`
- [ ] implement logic to free the memory of PSym/PType if memory
pressure is high
- [ ] implement logic to close memory mapped files if too many are open.
---------
Co-authored-by: demotomohiro <gpuppur@gmail.com>
Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
Co-authored-by: Jacek Sieka <arnetheduck@gmail.com>
Implements #24569
Adds `--stdinfile` flag for specifying the file to use in place of
`stdinfile.nim` in error messages. Will enable easier integration of
tooling with nim check
implements https://github.com/nim-lang/RFCs/issues/557
It inserts defect handing into a bare except branch
```nim
try:
raiseAssert "test"
except:
echo "nope"
```
=>
```nim
try:
raiseAssert "test"
except:
# New behaviov, now well-defined: **never** catches the assert, regardless of panic mode
raiseDefect()
echo "nope"
```
In this way, `except` still catches foreign exceptions, but panics on
`Defect`. Probably when Nim has `except {.foreign.}`, we can extend
`raiseDefect` to foreign exceptions as well. That's supposed to be a
small use case anyway.
`--legacy:noPanicOnExcept` is provided for a transition period.
refs #24503
Infinite recursions currently are not tracked separately from infinite
loops, because they also increase the loop counter. However the max
infinite loop count is very high by default (10 million) and does not
reliably catch infinite recursions before consuming a lot of memory. So
to protect against infinite recursions, we separately track call depth,
and add a separate option for the maximum call depth, much lower than
the maximum iteration count by default (2000, the same as
`nimCallDepthLimit`).
---------
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
closes https://github.com/nim-lang/RFCs/issues/380, fixes#4773, fixes
#14729, fixes#16755, fixes#18150, fixes#22984, refs #11167 (only some
comments fixed), refs #12620 (needs tiny workaround)
The compiler gains a concept of root "nominal" types (i.e. objects,
enums, distincts, direct `Foo = ref object`s, generic versions of all of
these). Exported top-level routines in the same module as the nominal
types that their parameter types derive from (i.e. with
`var`/`sink`/`typedesc`/generic constraints) are considered attached to
the respective type, as the RFC states. This happens for every argument
regardless of placement.
When a call is overloaded and overload matching starts, for all
arguments in the call that already have a type, we add any operation
with the same name in the scope of the root nominal type of each
argument (if it exists) to the overload match. This also happens as
arguments gradually get typed after every overload match. This restricts
the considered overloads to ones attached to the given arguments, as
well as preventing `untyped` arguments from being forcefully typed due
to unrelated overloads. There are some caveats:
* If no overloads with a name are in scope, type bound ops are not
triggered, i.e. if `foo` is not declared, `foo(x)` will not consider a
type bound op for `x`.
* If overloads in scope do not have enough parameters up to the argument
which needs its type bound op considered, then type bound ops are also
not added. For example, if only `foo()` is in scope, `foo(x)` will not
consider a type bound op for `x`.
In the cases of "generic interfaces" like `hash`, `$`, `items` etc. this
is not really a problem since any code using it will have at least one
typed overload imported. For arbitrary versions of these though, as in
the test case for #12620, a workaround is to declare a temporary
"template" overload that never matches:
```nim
# neither have to be exported, just needed for any use of `foo`:
type Placeholder = object
proc foo(_: Placeholder) = discard
```
I don't know what a "proper" version of this could be, maybe something
to do with the new concepts.
Possible directions:
A limitation with the proposal is that parameters like `a: ref Foo` are
not attached to any type, even if `Foo` is nominal. Fixing this for just
`ptr`/`ref` would be a special case, parameters like `seq[Foo]` would
still not be attached to `Foo`. We could also skip any *structural* type
but this could produce more than one nominal type, i.e. `(Foo, Bar)`
(not that this is hard to implement, it just might be unexpected).
Converters do not use type bound ops, they still need to be in scope to
implicitly convert. But maybe they could also participate in the nominal
type consideration: if `Generic[T] = distinct T` has a converter to `T`,
both `Generic` and `T` can be considered as nominal roots.
The other restriction in the proposal, being in the same scope as the
nominal type, could maybe be worked around by explicitly attaching to
the type, i.e.: `proc foo(x: T) {.attach: T.}`, similar to class
extensions in newer OOP languages. The given type `T` needs to be
obtainable from the type of the given argument `x` however, i.e.
something like `proc foo(x: ref T) {.attach: T.}` doesn't work to fix
the `ref` issue since the compiler never obtains `T` from a given `ref
T` argument. Edit: Since the module is queried now, this is likely not
possible.
---------
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
alternative to #24101#23892 changed the opensym experimental switch so that it has to be
enabled in the context of the generic/template declarations capturing
the symbols, not the context of the instantiation of the
generics/templates. This was to be in line with where the compiler gives
the warnings and changes behavior in a potentially breaking way.
However `results` [depends on the old
behavior](71d404b314/results.nim (L1428)),
so that the callers of the macros provided by results always take
advantage of the opensym behavior. To accomodate this, we change the
behavior of the old experimental option that `results` uses,
`genericsOpenSym`, so that ignores the information of whether or not
symbols are intentionally opened and always gives the opensym behavior
as long as it's enabled at instantiation time. This should keep
`results` working as is. However this differs from the normal opensym
switch in that it doesn't generate `nnkOpenSym`.
Before it was just a generics-only version of `openSym` along with
`templateOpenSym` which was only for templates. So `templateOpenSym` is
removed along with this change, but no one appears to have used it.
fixes#15314, fixes#24002
The OpenSym behavior first added to generics in #23091 now also applies
to templates, since templates can also capture symbols that are meant to
be replaced by local symbols if the context imports symbols with the
same name, as in the issue #24002. The experimental switch
`templateOpenSym` is added to enable this behavior for templates only,
and the experimental switch `openSym` is added to enable it for both
templates and generics, and the documentation now mainly mentions this
switch.
Additionally the logic for `nkOpenSymChoice` nodes that were previously
wrapped in `nkOpenSym` now apply to all `nkOpenSymChoice` nodes, and so
these nodes aren't wrapped in `nkOpenSym` anymore. This means
`nkOpenSym` can only have children of kind `nkSym` again, so it is more
in line with the structure of symchoice nodes. As for why they aren't
merged with `nkOpenSymChoice` nodes yet, we need some way to signal that
the node shouldn't become ambiguous if other options exist at
instantiation time, we already captured a symbol at the beginning and
another symbol can only replace it if it's closer in scope and
unambiguous.
refs https://github.com/nim-lang/Nim/pull/23873#discussion_r1687995060,
fixes#23386, fixes#23385, supersedes #23572
Turns the `nfOpenSym` node flag implemented in #23091 and extended in
#23102 and #23873, into a node kind `nkOpenSym` that forms a unary node
containing either `nkSym` or `nkOpenSymChoice`. Since this affects
macros working on generic proc AST, the node kind is now only generated
when the experimental switch `genericsOpenSym` is enabled, and a new
node flag `nfDisabledOpenSym` is set to the `nkSym` or `nkOpenSymChoice`
when the switch is not enabled so that we can give a warning.
Now that the experimental switch has more reasonable semantics, we
define `nimHasGenericsOpenSym2`.
I've been working on making some basic cuda examples work, both with
cuda (nvcc) and with AMD HIP (hipcc) https://github.com/monofuel/hippo
- hipcc is just a drop-in replacement for clang and works out of the box
with clang settings in Nim. hipcc is capable of compiling for AMD ROCm
or to CUDA, depending on how HIP_PLATFORM is set.
- nvcc is a little quirky. we can use `-x cu` to tell it to handle nim's
`.cpp` files as if they were `.cu` files. nvcc expects all backend
compiler flags to be wrapped with a special `-Xcompiler=""` flag when
compiling and also when linking.
I manually tested on a linux desktop with amd and a laptop with nvidia.
This adds nimsuggest support for displaying inlay hints for exceptions.
An inlay hint is displayed around function calls, that can raise an
exception, which isn't handled in the current subroutine (in other
words, exceptions that can propagate back to the caller). On mouse hover
on top of the hint, a list of exceptions that could propagate is shown.
The changes, required to support this are already commited to
nimlangserver and the VS code extension. The extension and the server
allow configuration for whether these new exception hints are enabled
(they can be enabled or disabled independently from the type hints), as
well as the inlay strings that are inserted before and after the name of
the function, around the function call. Potentially, one of these
strings can be empty, for example, the user can choose to add an inlay
hint only before the name of the function, or only after the name of the
function.