Skip to content

A global initialised from another global runs after it (#446) - #476

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-global-from-global
Oct 3, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-global-from-global

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Closes #446.

Problem

A module-level variable initialised from another one read garbage:

let a: number[] = [1, 2];
let b = a;   // b.length was 0 / garbage

Every global whose initializer runs code gets a constructor, and all of them had the same priority (1000). LLVM leaves the order of equal priorities undefined, and GlobalOpt uses that: optimised, b's constructor was evaluated before a's. The JIT's __mlir_gctors inserted each constructor at the start of the function, so it ran them in reverse.

Fix

  • MLIRGenModule.cpp (addGlobalConstructor) and LowerToLLVM.cpp (GlobalOpLowering): each constructor gets a priority one above every one made before it.
  • GlobalConstructorOpLowering (JIT): the calls in __mlir_gctors are kept in priority order. Each call goes before the first one with a higher priority, otherwise after the last one. The three per-function-op branches collapse into one.

Tests

  • New 00global_from_global.ts covers an array, a class instance, a union, a string and a computed number, each read from another global. It runs compile + JIT, plus the rc and none corpora.
  • Windows Release ctest: 3690/3691. The one failure, test-compile-gc-defaultlib-collector, also fails without this change: the local default lib is stale (Error..size 32 vs 48, ExactMatch comdat).
  • Linux (WSL) full ctest: 3676/3676.
  • -mm=own corpus: unchanged (with and without --opt).

🤖 Generated with Claude Code

Every global whose initializer runs code got a constructor at the same
priority, 1000 (LAST_GLOBAL_CONSTRUCTOR_PRIORITY). LLVM leaves the
order of equal priorities undefined, and GlobalOpt uses that: optimised, `b`'s
constructor in `let a = [1, 2]; let b = a;` was evaluated before `a`'s and
read zeroes. The JIT's __mlir_gctors put each constructor at the start of the
function, so it ran them in reverse.

Each constructor now gets a priority one above every one before it, in
MLIRGen and in GlobalOpLowering, and the JIT inserts each call in priority
order (after the calls it already holds with a lower or equal priority).

Closes #446

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 725d57b into main Oct 3, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-global-from-global branch October 3, 2026 23:11
ASDAlexander77 added a commit that referenced this pull request Oct 22, 2026
…636)

`let k = a` at module level, with `a` another global, and `let k = h.c`
gave the global no reference of its own: rc and own both read freed memory
once `a` (or `h.c`) was given a new value. Under own a Shared<T> handle
copied this way (`let k = a` in the initializer, then `k = a` releasing a
count it never took) did the same. claimGlobalInitializers skipped a read
of another global (#446 made it read null; #476 fixed that) and retained a
field read only for handles.

- rc: a read of another global, a field or an element in a global's
  initializer is retained for the global.
- own: a handle read that way is retained too (handles are counted); any
  other such initializer is an error, since a global cannot borrow
  (rejectBorrowedGlobalInitializers).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

A global initialized from another global reads garbage (heap-typed globals; crash with a class)

1 participant