Commit Graph

23082 Commits

Author SHA1 Message Date
Araq
3b2e0e08c5 IC: per-module backend emit stage (Phase 2b, B3)
--icBackendStage:emit --icBackendModule:<suffix> renders one module's final
.c from its .c.nif and ic.backend.merge.nif. cnif.renderCFromArtifact walks
the artifact token stream: string literals verbatim, symbols by name, and a
(cdef ...) body is dropped when the name is dead OR it is a 'u' unique
definition this module does not own. The prototype lives in the surrounding
raw text (cgen emits a forward declaration for every used proc regardless of
where the body lands), so a dropped body keeps a valid declaration -- no
synthesis needed.

emit loads the module graph the same way cg does (factored into
loadBackendModules/findTargetModule) so getCFile yields the identical path cg
wrote to -- in particular the main module's source-vs-suffix aliasing.

Validated end-to-end on a 3-module diamond (lib.shared demanded by siblings
a and b at top level): cg all modules, merge, emit all, cc, link. The proc
shared lands in exactly one object (its assigned owner a) and is referenced
(U) from the other -- proc dedup + ownership works at the object level. The
only remaining link failures are DATA (RTTI NTIv2, const tables): those are
emit-everywhere'd as cdata, which is not yet wrapped in a droppable directive
nor given a guaranteed extern in non-owners -- the next unit (data ownership).
koch ic thallo green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:37:47 +02:00
Araq
b4c71af517 IC: module suffix is now the trailing token of mangled C names
Reorder mangleProcNameExt and makeUnique so the module suffix comes LAST:
name_u<disamb>__<suffix> (was name__<suffix>_u<disamb>). The suffix is now a
strippable trailing token, so content-addressed cross-module merging (the
per-module backend's instance/hook dedup) can recover a mint-site-independent
name by chopping everything from the final "__" -- no reference rewriting.

Also drops the main-module special case in mangleProcNameExt: it omitted the
suffix because the main module's symbols key on its NIF-suffix file index. But
the backend already aliases that suffix to the main's source index
(nifbackend.loadModuleDependencies), so graph.ifaces[s.itemId.module] is
populated for the main module too -- the guard was redundant. Main-module
procs now mangle uniformly (e.g. mainProc_u0__<mainname>).

icFormatVersion 3 -> 4: cached .c.nif artifacts hold the old name scheme and
must be wiped.

Validated: koch boot (non-IC self-host) reaches fixed point; koch ic thallo
tconverter timp tmiscs tparseutils all green; a 3-module diamond IC build
runs correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:09:16 +02:00
Araq
4434e2d6bd IC: per-module backend merge stage (Phase 2b, B2)
--icBackendStage:merge reads every module's .c.nif (the cg stages'
emit-everywhere output), computes the global live set and, for each
externally-linked definition that several cg processes emitted, the single
artifact allowed to embed its body; it writes ic.backend.merge.nif for the
emit stage to consume. This is the cross-process replacement for the
whole-program backend's in-process icSharedDefOwner/DCE coordination.

Mechanism:
- cgen marks every unique program-wide definition (callConv != ccInline and
  not a dispatcher) with a new 'u' cdef flag. Its complement -- inline procs
  (static per-TU) and method dispatchers (main-only) -- is emitted into every
  using TU and must never be deduplicated, so it carries no flag. The flag is
  inert for the whole-program path (renderMarkedC/computeLiveFromCArtifacts
  ignore it).
- cnif.computeMergeDecision does one mark&sweep pass over all artifacts
  (same liveness as computeLiveFromCArtifacts) plus owner assignment: the
  owner of a 'u' definition is the lexicographically smallest artifact that
  emits it -- a pure function of the claimant set, stable across rebuilds.
  writeMergeDecision/readMergeDecision serialize the result as
  (merge (live ...) (owners (own Symbol StrLit)*)).
- generateMergeStage is a pure artifact operation (no module graph loaded):
  glob the nimcache's .c.nif, compute, write the decision.

Validated on a diamond (lib.shared called from sibling modules a and b, both
with top-level demands): cg emits shared into a, b and lib; merge assigns
owner = lib (smallest claimant) so a/b will prototype it, while the inline
nimFrame stays out of the owners map (kept everywhere). Whole-program backend
path unchanged (dispatch guarded on icBackendStage); koch ic thallo green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:47:55 +02:00
Araq
9916d09db3 IC: per-module backend codegen stage (Phase 2b, B1)
--icBackendStage:cg --icBackendModule:<suffix> generates C for a single module
and writes only its .c.nif (no merge, no .c render, no cc/link -- separate
stages). The whole program is still loaded so types resolve, but only the
target module is code-generated; findPendingModule routes every demand into it
(emit-everywhere into the current module), so a definition gets its canonical
owner-suffixed C name regardless of which module's process emits it -- cross-
process duplicates then collide by exact name, ready for the merge stage to keep
one and prototype the rest.

Validated: cg of the main module of a 2-module project recreates its .c.nif with
the demanded closure (greet/add named by their owner suffix); a leaf module whose
procs only callers use yields an (correct) empty .c.nif. Whole-program backend
path unchanged (dispatch guarded on icBackendStage), koch ic thallo green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:20:14 +02:00
Araq
7ea5aeedfd IC: add inert --icBackendStage/--icBackendModule switches (Phase 2b scaffold)
Config plumbing for the per-module backend: icBackendStage (cg|merge|emit, empty
= today's whole-program backend) and icBackendModule (the NIF suffix the cg/emit
stage operates on). No behavior yet -- the nifbackend stages and deps.nim rules
that consume these land next. Whole-program backend, koch boot, and a 2-module
IC build are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:03:55 +02:00
Araq
051ac67968 IC: split the build file into frontend and backend (Phase 2a)
generateBuildFile becomes three procs: computeForwardedArgs (config/define
forwarding + writeIcConfig, depends only on config so computed once),
generateFrontendBuildFile (nifler + nim m rules), and generateBackendBuildFile
(today's single whole-program nim nifc rule; semmed NIFs enter as leaf inputs
with no producing rule, like nifler's .nim source inputs).

commandIc now runs two nifmake passes: phase 1 drives the frontend to the
.s.deps discovery fixpoint, phase 2 runs the backend once over the now-final
graph. Backend rebuilds are then a pure nifmake mtime decision, independent of
frontend discovery -- and the backend file is the slot the per-module codegen
+ DCE + link rules drop into next.

Observably inert: koch boot and koch bootic both reach their byte-identical
fixed points (clean ric_ cache), 2-module cold/warm/body-edit correct,
koch ic thallo green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:50:11 +02:00
Araq
d269ccff26 IC: correct instance-name format in design doc
The content key is hashed into <disamb> (setInstanceDisamb), not a separate
.key. token; document that the cross-TU merge and DCE already key on the
module-suffix-stripped name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:28:43 +02:00
Araq
934bf39029 tests: add openSym dot-RHS regression test
Tests the fix in 7148ae347: the RHS of a dot expression wrapped in
nkOpenSym by the generic prepass must use the captured symbol when nothing
is injected, while an injected symbol still overrides it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:21:39 +02:00
Araq
37d6ea691d IC: document parallel default, status/perf, cross-mm limitation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:21:39 +02:00
Araq
7ab7630117 IC: drive nifmake with --parallel by default
Cold builds were serial (one nim m at a time), leaving the machine idle.
nifmake fans out commands at each DAG depth via execProcesses, so pass
--parallel by default; this roughly halved cold compiler self-build wall
time (81s -> 53s on a 32-core box). Opt out with -d:icNoParallel for
readable, non-interleaved child output when debugging a build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:21:39 +02:00
Araq
1c434b7638 IC/refc: no-op value hooks for tyUncheckedArray
An UncheckedArray has no known length, so it cannot be copied, moved or
destroyed as a value; it only ever lives behind a pointer. The pointer-like
group emitted x = y for it, an assignment of an unsized array the C backend
cannot lower (genAssignment: tyUncheckedArray) -- which surfaced under
nifc's hook-driven refc codegen (e.g. ref UncheckedArray in widestrs).
Give it its own discard branch so all value hooks no-op; seq/string element
ops still go through the seq/string hooks, which know the length.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:21:30 +02:00
Araq
3e95fcb4b0 IC: cleaner design, updated design document 2026-06-13 13:55:58 +02:00
Araq
47ea8c84bc missing file 2026-06-13 10:16:29 +02:00
Araq
7148ae3474 IC: progress, hacks included 2026-06-13 10:08:46 +02:00
Araq
002d9ed0ef progress 2026-06-12 16:27:41 +02:00
Araq
4307b1872a IC: Iface-gate the nifc step 2026-06-12 12:47:06 +02:00
Araq
8b0058efc4 IC: proper iface vs impl distinction, only rebuild dependents if the interface changed or if they depend on implementation (proc bodies) 2026-06-12 09:50:23 +02:00
Araq
cca0fa2f4f make the backend incremental too 2026-06-12 06:05:31 +02:00
Araq
2524b8a1b1 IC: beginnings of the backend porting 2026-06-11 21:51:59 +02:00
Araq
efc52a5635 refactoring 2026-06-11 10:36:04 +02:00
Araq
c91960ff53 test fixup 2026-06-11 08:56:45 +02:00
Araq
0457ebc170 progress 2026-06-10 19:51:25 +02:00
Araq
b72affa1a1 fix arraymancer regression: gate handleGenericInvocation's copy on Sealed
The early IC fix made the else branch (concrete generic args) copy the
invocation type unconditionally before propagateToOwner. Besides
avoiding the in-place mutation, the copy flips `header != t` for
all-concrete invocations, which activates the searchInstTypes/sameFlags
cached-instance return path that devel skipped - a cached, meta-flagged
instance could be returned where a fresh one was expected.
Arraymancer's build then failed with "cannot cast to a non concrete
type: 'ptr NimSeqV2[Node[Tensor[float32]]]'" in seqs_v2.setLen.

Copy only when the invocation type is actually immutable
(IC-loaded/Sealed); non-IC behavior is devel's again, the IC assert
stays fixed.

Verified: arraymancer tests_cpu.nim builds and links (its test-suite
SIGSEGV in io_npy is pre-existing - a devel-built compiler produces the
identical 226-tests-then-crash). Macro sweep 93/95, tests/ic 5/5,
koch boot -d:release and clean koch bootic reach bit-identical fixed
points.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:53:16 +02:00
Araq
cc2afc616f IC: don't mutate loaded types in handleGenericInvocation
When a generic's body is computed by a macro (Bar[T, U] = makeBar(T, U)),
`newbody` after replaceTypeVarsT can be a type loaded from a dep module
- even a builtin like `int` - which is Sealed under IC:

- the in-place flag accumulation `newbody.flags = newbody.flags + ...`
  asserted (and under non-IC silently pollutes the shared type's flags,
  e.g. the global `int`); compute the flags into a local, skip the
  in-place store for Sealed types and feed `result.flags` from the
  local - value-identical for the instance.
- `newbody.typeInst = result` likewise; a loaded body keeps whatever
  its defining module serialized (the field was first-wins anyway).

Both changes are no-ops for non-IC (types are never Sealed there).
Fixes tmacrogenerics. Macro sweep 93/95 - the two remaining fails are
tmacro7 (disabled test, fails identically under non-IC) and
tmacrogetimpl (needs a design decision on getImplTransformed sym
sharing). tests/ic 5/5, koch boot -d:release and clean koch bootic
both reach bit-identical fixed points.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:41:10 +02:00
Araq
5ce2c8f95d IC: codegen the main module in a single C translation unit
Under `nim nifc` the main module is loaded by its source FileIndex, but
its serialized symbols carry the module's NIF suffix, so
registerNifSuffix allocated a SECOND FileIndex for the same module:
top-level globals were emitted into the source-index BModule while
procs went into the suffix-index BModule, and a N_LIB_PRIVATE global
declared in one TU was undeclared in the other (tincremental,
tmacros_various).

Pre-aliasing the suffix to the source index in loadModuleDependencies
unifies the TUs. This was tried before and reverted: the split was
masking a hook C-name disamb collision between sem-lifted (loaded) and
codegen-lifted hooks in the same module. That collision class is gone
since backend-minted symbols mangle as _c<item> (BackendIdOffset), so
the unification is safe now.

Macro sweep 92/95 (fixes tincremental + tmacros_various; remaining:
tmacro7 which fails identically under non-IC and is disabled,
tmacrogenerics, tmacrogetimpl), tests/ic 5/5, clean koch bootic
reaches the bit-identical fixed point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:17:03 +02:00
Araq
34b3893316 IC: keep NIF-only package-name marker out of symbol stubs
The `pkg marker appended to skPackage NIF names leaked into the
user-visible name of Partial stubs: vmgen's toKey built callback keys
like 'getCurrentException.system.stdlib`pkg', so the VM compiled the
real body of getCurrentException instead of dispatching to the vmops
callback and failed with 'cannot evaluate at compile time:
currException' (tparsefile, ttryparseexpr). The marked name was also a
latent hash-divergence source: sighashes' hashNonProc/hashOwner hash
package names straight off possibly-Partial stubs.

Stubs are now created with the clean name; the marker doubles as a kind
signal, so the stub starts as skPackage and globalName re-appends the
marker when rebuilding the NIF index key. NIF file content is
unchanged.

Macro sweep 90/95 (up from 88, restores the baseline; the 5 remaining
fails are the known deep ones), tests/ic 5/5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:32:57 +02:00
Araq
89e8a91db4 IC: progress 2026-06-10 12:04:42 +02:00
Araq
9abbb5c281 deal with regressions 2026-06-09 23:42:00 +02:00
Araq
26a24879c8 progress 2026-06-09 19:40:00 +02:00
Araq
4c8de3bcb8 progress 2026-06-09 16:12:38 +02:00
Araq
4e6e9beea8 IC: fix fwd-decl dup, import-except deps, and field interface leak
- ast2nif.canonicalRoutine: collapse a forward-decl's discarded impl sym
  onto the surviving proto so it is not serialized twice (was an ambiguous
  overload in importers; fixes tnewlit).
- deps.nim: handle `import m except syms` (importexcept) in the dependency
  scanner so the build-order edge is not dropped (fixes strformat->strutils
  ordering).
- ast2nif.writeSymDef: object fields (skField) are no longer marked
  bare-importable (x) in the NIF index; an exported field name leaked into
  importer scope and shadowed a template's open symbol (type mismatch 'T').
  Together these fix tmacro8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 22:49:23 +02:00
Araq
8047413632 IC: proctype handling 2026-06-08 18:33:59 +02:00
Araq
fac1eefd99 IC: bugfixes 2026-06-08 17:25:48 +02:00
Araq
da419ea8c0 progress 2026-06-08 15:43:56 +02:00
Araq
69280d6d75 IC: progress 2026-06-08 13:51:02 +02:00
Andreas Rumpf
d9e28aac8e parser: concept of (#25878)
Co-authored-by: Gerke Max Preussner <gmpreussner@headcrash.industries>
2026-06-08 11:32:04 +02:00
Andreas Rumpf
c84764a097 emit modern NIF-27 (#25877) 2026-06-08 09:13:26 +02:00
ringabout
1d7510dff0 fixes #22936; Generic inheritance matching gives type mismatch when object has members (#25836)
fixes #22936

This pull request improves the compiler's handling of generic type
constraints, specifically for subtypes of generics, and adds a test to
cover this behavior. The main changes are an enhancement to the type
relationship logic in the compiler and a new test case for generic
subtyping with `Future`.

### Compiler improvements for generic subtyping

* Updated `typeRel` in `compiler/sigmatch.nim` to allow generic
constraints (like `F: Future`) to accept not just direct instantiations
but also descendants of the generic family, ensuring more flexible and
correct overload resolution. Inheritance depth is now considered for
overload ranking, making deeper descendants slightly less preferred,
consistent with other inheritance-based matches.

### New test coverage

* Added a test in `tests/typerel/t8905.nim` to verify that generic
constraints correctly accept subtypes of `Future`, including a custom
`B[T, E] = ref object of Future[T]` type, and that overloads like
`take`, `takeMany`, and the macro `checkFutures` work as expected with
these types.
2026-06-08 09:12:00 +02:00
Tomohiro
9b80b2e868 fixes-25655; defining >= operator generates compile error (#25787)
Fixes https://github.com/nim-lang/Nim/issues/25655

---------

Co-authored-by: Andreas Rumpf <araq4k@proton.me>
2026-06-08 09:00:00 +02:00
ringabout
f5930d0bb3 fixes #20811; Nested proc with inner being generic cannot access parameters of outer proc (#25837)
fixes  #20811

This pull request addresses issues with parameter capture in nested
generic procedures and templates, ensuring that outer parameters are
correctly visible and accessible within nested scopes. The main changes
include a fix in the semantic analysis logic and the addition of
targeted regression tests.

### Semantic analysis improvements:
* Updated `semGenericStmtSymbol` in `compiler/semgnrc.nim` to ensure
that parameters from outer scopes are preserved and accessible in nested
generic procedures, fixing visibility issues with captured parameters.

### Added regression tests:
* Added `tests/generics/t20811.nim` to verify that both generic and
plain inner procedures can access parameters from their enclosing
procedure.
* Extended `tests/template/topensym.nim` with a new block for issue
#20811 to test that template-injected parameters are correctly captured
and visible in nested generic procedures.
2026-06-08 08:55:37 +02:00
ringabout
4497d89267 fixes #18238; Nested object construction can zero same memory multiple times for --mm:refc (#25834)
fixes #18238

This pull request makes a targeted change to the object construction
logic in the `genObjConstr` procedure. The main update refines the
conditions under which memory zeroing is required during object
construction, making the behavior more accurate for different garbage
collection and destructor options.

Key logic update:

- Improved the `needsZeroMem` condition in `genObjConstr` to check for
the presence of garbage-collected references and the `optSeqDestructors`
option, instead of relying solely on the selected garbage collector and
field flags. This ensures memory is zeroed only when necessary,
potentially improving performance and correctness.


```c
T1_ = NIM_NIL;
T1_ = ((tyObject_E__uEKympBdEK4SY9anUbpNaLQ*) newObj((&NTIrefe__bJ9cSuxv8xHYxmdolQqFkUw_), sizeof(tyObject_E__uEKympBdEK4SY9anUbpNaLQ)));
nimZeroMem(((void*) ((&(*T1_).z.z.z.z))), sizeof(tyObject_A__G2lWlL9cFqoiWWwZmWqfJ9bA));
(*T1_).z.z.z.z.y = ((NI) 5);
asgnRef(((void**) ((&z1__test8_u12))), T1_);
asgnRef(((void**) ((&z2__test8_u55))), new__test8_u13());
(*z2__test8_u55).z.z.z.z.y = ((NI) 5);
T2_ = NIM_NIL;
T2_ = ((tyObject_E__uEKympBdEK4SY9anUbpNaLQ*) newObj((&NTIrefe__bJ9cSuxv8xHYxmdolQqFkUw_), sizeof(tyObject_E__uEKympBdEK4SY9anUbpNaLQ)));
asgnRef(((void**) ((&z3__test8_u56))), T2_);
(*z3__test8_u56).z.z.z.z.y = ((NI) 5);
```


The original test case has already been fixed for `ORC`, now extends it
to `refc`: if a constructor is fully initialized, it does not need a
zero-fill step
2026-06-08 08:54:15 +02:00
ringabout
f959a02037 fixes #25725; environment misses: s with iterator (#25828)
fixes #25725

This pull request makes significant improvements to symbol handling
during transformation passes in the compiler, particularly for routines
(procedures, iterators) and their parameters. The changes ensure that
when routines are copied (for inlining, closure generation, etc.), all
relevant symbols and type headers are also freshly copied and correctly
owned, preventing subtle bugs from symbol reuse. Additionally, new
regression tests are added to cover previously problematic iterator
cases.

**Improvements to symbol copying and ownership:**

* Introduced `freshOwnedSym` to create a fresh copy of a symbol with a
specified owner, ensuring that transformed routines and their parameters
do not share symbols with the originals, which prevents accidental
aliasing and ownership issues.
* Refactored `freshVar` to use `freshOwnedSym`, centralizing fresh
symbol creation logic.
* Added `introduceNewRoutineHeaderSyms` and `copyRoutineTypeHeader` to
ensure that when routines are copied, all parameter/result symbols and
their types are also freshly copied and mapped, avoiding shared state
between original and transformed routines.
* Updated `introduceNewLocalVars` to use `freshOwnedSym` for routine
symbols and to invoke the new header/type copying procedures, ensuring
correctness in routine transformation.

**Testing and regression coverage:**

* Added new blocks to `tests/iter/titer_issues.nim` to test iterator
transformation edge cases, including scenarios that previously led to
symbol reuse bugs (e.g., bugs #25724 and #25725).
2026-06-08 08:53:10 +02:00
Andreas Rumpf
3c6449dbdd fixes #25850 (#25875) 2026-06-07 19:55:56 +02:00
ringabout
f1ff8b6d9e fixes #25849; fixes #25872; Iteration on elements of array (#25860)
fixes #25849
fixes https://github.com/nim-lang/Nim/issues/25872
2026-06-06 07:58:19 +02:00
Ryan McConnell
46259cd0b8 fix sortVTableDispatchers KeyError on re-entrant method registration via when isMainModule (#25856)
Encountered in realistic scenario. Didn't really look at this one. AI
one shot it lol

When a module defines method-bearing types and a when isMainModule
block imports additional modules that also define methods on the same
type hierarchy, sortVTableDispatchers crashes with:

Error: unhandled exception: key not found: (module: N, item: M)
[KeyError]

Root cause: the itemTable built during vtable sorting is populated
from g.objectTree[baseType], which only contains types from the
current compilation pass. When when isMainModule triggers re-import
of method-bearing modules, the method bucket contains types from both
passes. Types from the first pass have ItemIds not present in the
second pass's object tree, so itemTable[obj.itemId] raises KeyError
at line 155.

Fix: if obj.itemId is missing from itemTable, create an empty slot
array of the correct length. The entry is a local temporary — the
second loop in sortVTableDispatchers only calls setMethodsPerType
for types in the current object tree, so types from the prior pass
retain their already-established dispatch. The entry exists solely to
prevent the KeyError during the assignment loop.

The methodIndexLen used for the new entry is the bucket's slot count,
which is correct for any type in the hierarchy.

Added test tests/method/tvtable_reentry.nim that defines methods
across three types in two compilation passes and verifies dispatch
correctness for all three.
2026-06-05 16:37:00 +02:00
ringabout
4b374eb0a6 stop a temp register from being freed if addressed for lent (#25861)
ref https://github.com/nim-lang/Nim/issues/25849

The important part is in compiler/vmgen.nim:1838: when the VM lowers
a[i] or a.b as an address-producing operation, it emits opcLdArrAddr /
opcLdObjAddr. That returns an alias into the storage owned by the source
register. Before the patch, that source register could still betreated
as a normal temporary and later reclaimed or reused by the allocator.
Once that happened, the address result was still live, but the backing
temp was no longer guaranteed to exist, which is what led to the
nil/illegal-storage crash.

The fix is to pin that source temp by changing its slot kind to
slotTempPerm right after emitting the address load. You can see the same
lifetime rule already existed for the generic addr(...) path around
compiler/vmgen.nim:1551: if the source is a temporary and we take its
address, the compiler marks it permanent so freeTemp won’t recycle it.
The patch extends that exact rule to array and object address loads:

- compiler/vmgen.nim:1843
- compiler/vmgen.nim:1861

slotTempPerm is outside the normal freeTemp range in
compiler/vmgen.nim:248, so once a temp is upgraded to permanent, the VM
allocator stops treating it as reusable. That is the actual root-cause
fix: it preserves the backing storage for the address result until the
surrounding evaluation is done.

The regression test in tests/vm/t25849.nim:8 forces exactly that path
with a local lent iterator over an array and a static VM evaluation.
2026-06-04 13:29:48 +02:00
Corey Leavitt
c8e805a2fa fixes #25595; cursor inference: a recorded mutation extends the variable's liveness (#25864)
fixes #25595

## Bug

A `let` bound to a field of a value-type **case object** with a `ref`
field is inferred as a non-owning cursor, but the cursor's source can be
mutated through the cursor's own ref during a call, freeing the ref
while the borrow still reads it. Use-after-free under arc/orc (refc is
unaffected, it has no cursor inference):

```nim
var destroyed = false
type
  O = ref object
    value: int
    home: H
  W = object
    case k: bool
    of true: r: O
    of false: discard
  H = ref object
    w: W
proc `=destroy`(o: var typeof(O()[])) =
  destroyed = true
proc clear(o: O): int =
  o.home.w = W()             # overwrites h.w via the back-reference -> frees the ref
  doAssert not destroyed     # fails: the element was destroyed during the call
  result = o.value
proc go(h: H): int =
  let c = h.w                # inferred cursor (borrow of h.w)
  result = clear(c.r)
proc main =
  let h = H()
  let o = O(value: 42)
  o.home = h
  h.w = W(k: true, r: o)
  doAssert go(h) == 42
main()
```

The `not destroyed` assert fails: the element is destroyed during the
call, so the following `o.value` read is a use-after-free. The same code
with the `=destroy` guard removed (so the freed `o.value` is actually
read) is reported as `heap-use-after-free` by ASan under `-d:useMalloc
-fsanitize=address`. Longstanding (reproduces back to 2.2.0).
`--cursorInference:off` is a workaround.

## Root cause

Cursor inference (`varpartitions.computeCursors`) cursors `let c = h.w`
unless `dangerousMutation` finds a mutation of `c`'s graph within `c`'s
alive range `aliveStart..aliveEnd`. Here the mutation (the `clear(c.r)`
call) *is* connected to `c`'s graph and *is* recorded with `isMutated`,
but it is recorded at an `abstractTime` just past `c.aliveEnd`, so the
range check misses it.

The gap is timing. `aliveEnd` is set from the last `nkSym` use of `c`. A
call records its argument's mutation *after* traversing the whole
argument subtree (`potentialMutationViaArg`). When the argument is `c.r`
on a case object it is an `nkCheckedFieldExpr` (the discriminant check),
whose extra nodes advance `abstractTime` past `c`'s last `nkSym`. A
plain `nkDotExpr` has no such gap, so the bug needs a case object.

## Fix

In `potentialMutation`, extend the mutated variable's liveness to the
mutation time:

```nim
v.s[id].aliveEnd = max(v.s[id].aliveEnd, v.abstractTime)
```

A variable mutated at time T is provably alive at T, so this only
completes the liveness computation that `dangerousMutation` relies on.
The worst case is an extra copy, never an unsound cursor.

## Note on the locus

The fix is conservative by mechanism (it runs at every recorded
mutation) but perf-neutral in practice: it only suppresses a cursor
where the corrected liveness proves the borrow unsafe (cursor counts are
unchanged on the suites). I can scope it to call arguments if you'd
prefer it narrower.

## Test

`tests/arc/t25595.nim`, matrix `--mm:orc; --mm:arc; --mm:refc`: the
repro above as a `doAssert`. Fails (UAF) on arc/orc before the fix and
passes after. refc passes throughout.

## Checks

- repro passes on orc/arc after the fix. The guard-removed variant
(which reads the freed value) is ASan-clean after the fix and was
heap-use-after-free before. refc unaffected.
- testament `destructor` 90/90, `arc` 120/120. `views` 5/6, same as
stock (the one failure is environmental and pre-exists this change).
- perf-neutral: inferred-cursor count is identical stock vs fix across
the `arc` and `destructor` test files under `--mm:orc` (322 vs 322).
2026-06-03 07:25:33 +02:00
Corey Leavitt
73986c03a1 fixes #25857; don't treat typeof(result) as a use-before-init of result (#25858)
fixes #25857

## Bug

`typeof(result)` inside the expression that builds `result` gets counted
as a read
of `result` before it's set. On a `{.requiresInit.}` return type that's
a hard error
("'result' requires explicit initialization"). `typeof` never evaluates
its operand,
so it's a false positive. On 2.2.4 it compiles, but the same line still
emits a bogus
`ProveInit` warning, so no released version gets it right.

Regression from #25151. That PR made a used-before-init `requiresInit`
result a hard
error instead of a warning, which is correct on its own. The side effect
was that
this old false-positive warning became a build error.

## Root cause

`track` in `compiler/sempass2.nim` has no arm for `nkTypeOfExpr`, so it
hits the
default that recurses into every child, reaches the `result` `nkSym`
inside the
`typeof`, and calls `useVar`. `sizeof`/`compiles`/`declared` don't hit
this because
they fold to a constant before `track` runs. A `typeof(result)` typedesc
argument
survives into `track`.

## Fix

Skip `nkTypeOfExpr` in `track`. Its operand is never evaluated, so it
isn't a
definite-assignment use. After the patch there's no error and no warning
here, even
with `--warnings:on`. The #25151 check is untouched: a real use of
`result` before
init is a plain `nkSym`, not inside a `typeof`, so it still reaches
`useVar`.

## Test

`tests/init/t25857.nim`, a positive test that compiles and prints `1`.

## Checks

- Repro compiles and runs on patched 2.2.6 and patched devel.
- `tests/errmsgs/t25117.nim` still fails as expected. A real
`xxx(result)` before
  init still errors.
- `testament cat init` and `testament cat errmsgs` green on patched
devel (55 tests,
  0 failures), including the `--warningAsError:ProveInit` tests.
- Bisect: parent `1ab68797` good, `576c4018` (#25151) bad.
2026-06-02 07:07:44 +02:00
ringabout
88a18de44f fixes #25851; ensure --panics:on does not skip nimErr_ check after closure calls (#25855)
fixes #25851

## Summary: `--panics:on` drops `nimErr_` check after closure calls
(#25851)

### Bug

With `--exceptions:goto` and `--panics:on`, the compiler skipped the
`nimErr_` check after indirect closure calls whose result flows directly
into another call (e.g., `result.add elem(src)`). A raise inside the
closure was silently swallowed — the loop continued, and the next
`raise` hit the already-set `nimInErrorMode` flag, overflowing its
`bool` storage into `OverflowDefect`.

### Root Cause

**ast.nim** — `canRaise` checked `fn.typ.n[0].len < effectListLen` first
(false after the expansion) and then `exceptionEffects != nil` (also
false, nil), so it returned `false` — meaning "cannot raise." The C
codegen trusted this and omitted the `nimErr_` check.

### Fix

**ast.nim** — `canRaise` now treats `nil` `exceptionEffects` as "unknown
→ can raise" (`exceptionEffects == nil` as an additional true
condition). This is defense-in-depth: even if some other path expands
the list but leaves `exceptionEffects` nil (e.g., a type with `{.tags.}`
but no `{.raises.}`), the error check is still emitted.

### Test

tclosure_err_panic_goto.nim — exercises the double-trigger pattern
(`drawBool` sets the error flag → closure call must propagate it) with
`matrix: "; --panics:on"` covering both exception modes.
2026-06-01 16:21:37 +02:00
Andreas Rumpf
7813bd8b92 fixes #25693 (#25842) 2026-05-29 08:08:42 +02:00