Commit Graph

10 Commits

Author SHA1 Message Date
araq
f3bdc6c5f2 IC: nim c --ic:on / nim cpp --ic:on replace the nim ic command
`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>
2026-08-26 10:32:31 +02:00
araq
7ddfc44c0f IC: six more correctness fixes found by the nim c oracle
`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>
2026-08-26 08:43:42 +02:00
araq
467c911dc5 IC testing: compare against nim c, and run the real corpus under nim ic
Every invariant `tests/ic` checked was IC-against-IC — clean == incremental, a
no-op edit changes nothing, a body edit moves no interface cookie. An IC that is
*consistently* wrong satisfies all of them, and that is exactly how two silent
miscompilations survived. `koch bootic` has the same blind spot: it proves the
compiler reproduces itself, not that it agrees with the reference backend.

Two mechanisms, at different scales.

**The oracle, in the metamorphic runner.** Every successful step is now also
compiled with `nim c` and run, and the two outputs must agree. Unlike the
hand-written `expect:` strings this needs no foresight from the test author: it
compares everything the program does, not only what someone thought to print,
which is what a silently-skipped destructor evades. `no-oracle` opts out.

The format also grew the expressiveness the recent bug hunt showed was missing —
every one of these described a state the suite could not reach:

* `#!DELETE <file>` removes a module. Deleting a still-imported file moves no
  mtime, so nothing re-fires.
* `#!FLAGS <switches>` changes the compiler switches between steps. A config
  change is not a file either.
* `fails: <substring>` asserts that BOTH compilers reject the program with that
  text. Previously every step had to succeed, so the whole error path — and
  recovery from it — was untested.

Six regression tests cover the eight bugs the last round fixed.
`testament r <file>` now dispatches metamorphic tests like `testament cat ic`.

**`testament --ic` runs the whole corpus through the incremental compiler**, so
IC inherits ~10k programs with expected output instead of 30 bespoke tests.
Two things had to change for that to mean anything:

* `nimcacheDir` now keys on the `matrix:` entry too. Two matrix variants of one
  file are two different compilations; sharing a cache meant each run
  invalidated what the previous left — harmless for a backend that caches only
  object files, useless for an incremental one.
* About half the corpus overrides the command wholesale (`cmd: "nim c --gc:arc
  $file"`), bypassing both `$target` and `$options`. Those are rewritten to `ic`
  and given a private cache.

**Warm cache, hastur-style.** A generated warmup program pulling in `system` and
the most-imported stdlib modules is compiled once per distinct compile
configuration into `nimcache/ic_warmup_<hash>`, and each test's empty cache is
seeded from it with mtimes preserved (nifmake compares output-mtime >
input-mtime, so stamping the copies "now" re-fires the whole graph). Only
program-independent artifacts are copied: the frontend NIFs and cookies plus the
per-module `lower`/`cg` output. The `.c`/`.o` are left behind on purpose — the
merge decision is whole-program, so they are re-rendered for every program
anyway.

`tests/destructor` (97 runs): `nim c` 35s cold / 32s warm; `nim ic` ~3m30 cold /
**9.8s warm**.

Compiler changes this required or uncovered:

* `merge` read the live-module list from a manifest the driver writes instead of
  globbing `*.c.nif` off the nimcache. Globbing absorbed artifacts belonging to
  any other program sharing the directory — which is what made a prefilled cache
  produce undefined symbols at link.
* The build-arg signature no longer includes `--icproject:`/`--icPreparsedConfig:`
  (they name where a build lives, not what it produces, so two caches holding
  identical artifacts got different signatures). The precompiled config still
  counts, by content hash, minus its `(nimcache …)` line.
* `.s.deps` seeding is speculative and runs before the prune, so a sidecar entry
  that has gone stale (an import that a `when` no longer takes) can be dropped
  instead of lingering forever; a pruned module's scan artifacts are deleted so
  an edit-accumulated cache still matches a clean one.
* A failed nifmake run no longer prints an `Error:` of its own. The children have
  already reported; adding a build-system status as the LAST error hid the
  compiler's real message from anything reading the final error — every
  reject-style test under `nim ic` said "nifmake failed with exit code: 1".
* `--mm:hooks` fed the mm mode to an on/off switch and failed outright with
  "'on' or 'off' expected, but 'hooks' found". Pre-existing and unrelated to IC;
  only reachable through the explicit switch, since `--newruntime` sets
  `selectedGC` directly.

Running `tests/destructor` under `--ic` currently leaves 10 failures. They are
genuine IC defects, not harness noise (all 97 pass under `nim c`) — the clearest
is `tglobaldestructor`: `graph.globalDestructors` is accumulated while injecting
destructors into a module's top level, but the main module's `cg` — which emits
the teardown — is a different process, so a module-level `var` with a `=destroy`
is never destroyed. Same shape as the init/datInit metas, and it wants the same
fix: record it in the `.c.nif` head.

Validation: `koch bootic` reaches its byte-identical fixed point; `tests/ic` is
36/36; arc, destructor, macros, template, iter, closure, ccg, codegen, types and
effects pass under `nim c`, with generics showing only its pre-existing failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 21:00:13 +02:00
Andreas Rumpf
8ca7b75b8b refactoring: better IC + no unique Id (#26137) 2026-08-25 11:59:19 +02:00
Jaremy Creechley
adda34bcb8 add --genBif for semantic BIF output on non-IC builds (#26001)
## 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.
2026-07-20 13:01:30 +02:00
Andreas Rumpf
1c61307692 IC related bugfixes (#25946) 2026-07-03 15:52:41 +02:00
Andreas Rumpf
7171e6f01f IC: progress (#25879) 2026-06-14 22:35:06 +02:00
Andreas Rumpf
df7a114d7a IC: use the newer nif27 format (#25792) 2026-05-06 08:41:59 +02:00
Andreas Rumpf
efc3a7429b IC: progress (#25440) 2026-01-16 15:24:21 +01:00
Andreas Rumpf
cf388722db IC: massive cleanup, NIF26 support, docs about its inner workings (#25427) 2026-01-16 12:19:17 +01:00