Skip to content

A global declared in top-level code owns what it is given (rc; -mm=own phase 6, PR 1) - #444

Merged
ASDAlexander77 merged 1 commit into
mainfrom
global-declaration-owns
Oct 2, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
global-declaration-owns

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

The first PR of -mm=own phase 6 (corpus report and diagnostics polish). It is an rc-side fix, so it stands alone and can merge on its own.

The bug

When top-level code declares a global (const t = new C(), or let/var), its initial value is stored into the global with no reference of its own. The end-of-block release (§9.30) then gives the value's reference back when the top-level code ends, so the global points at a freed block. 5ai fixed this for an assignment to a global (t = new C(), through isOwnedGlobalSlot), but not for the declaration's store (MLIRGenImpl.h, the "not global scope" branch).

The fix

The declaration's store now takes the owning-slot path, the same one an assignment takes:

  • it takes over an owned reference, or retains;
  • it releases what the slot held before, which matters for a var met again in a loop. A global starts at zero, so the first release does nothing.

This doesn't branch on the memory model in MLIRGen (spec §4.3): gc erases the retain.

Why it matters

  • rc: not observable today. Nothing runs after a program's top-level code, and a module with top-level code can't be imported (below). The test's teeth check confirms this: with the fix reverted, rc still passes.
  • own: the store reads as a move and the release as a second reference. That made 28 corpus files fail with the catch-all "takes a second reference"; 8 of them now compile.

Found, not fixed (every model, outside this PR)

  • A module with a top-level statement (print(...)), imported as source, fails with "Global is referenced by parentless instruction! @puts" (invalid LLVM IR).
  • With -shared, an exported const from such a module can't be resolved by the importer ("can't resolve name").

Verification

  • Release suite: 3399 of 3400 pass. The one failure, test-compile-gc-defaultlib-collector, is the stale installed default library, as on -mm=own phase 5a: a closure that does not escape borrows what it captures #436-Builtin iterator methods without the default library: a.map(f).map(g) #443. The rc suite is unchanged.
  • WSL/GCC: builds, and the 2056 tests run there pass (every own test, the rc and none corpus, and the new test).
  • Own corpus: 364 → 372 of 591, none lost. Each new file passes test-runner -mm=own, AOT and JIT, with gc's output (import_class_interface.ts as its export_ pair; its non--shared JIT mode is unsupported under every model): 00class_from_tuple, 00class_indexer, 00class_protected_constr, 00if_conditional_compile, 00safe_cast_typeof, 00toplevel_control_flow, 00toplevel_no_main_with_helpers, import_class_interface.
  • Teeth: with the fix reverted, the test fails under own (AOT, JIT and the no-counting check).

Test

own/own_global_script.ts runs under own, rc, none and gc. It covers const, let and var globals in top-level code (a class and a string), a var in a nested block, reads from a function after the heap is churned, and an overwrite.

🤖 Generated with Claude Code

`const t = new C()` in top-level code stored the instance into the global
with no reference of its own, and the end-of-block release (§9.30) then
gave the value's reference back, leaving the global pointing at a freed
block once the top-level code ended. An assignment to a global already
hands the count over (isOwnedGlobalSlot, 5ai); the declaration's store
now does the same: it takes over an owned reference or retains, and
releases what the slot held before (a `var` met again in a loop).

Under rc it is not observable today: nothing runs after a program's
top-level code (a `var` in a nested block is released at the end of
the top-level code too, not of its block), and a module with top-level
code cannot be imported. Under -mm=own the store read as a move and the
release as a second reference, which failed 28 corpus files.

No branch on the memory model in MLIRGen: gc erases the retain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 570c02d into main Oct 2, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the global-declaration-owns branch October 2, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant