Skip to content

rc: a global takes the reference of an owned producer through a widening view (#459) - #468

Merged
ASDAlexander77 merged 2 commits into
mainfrom
fix-rc-global-initializers
Oct 3, 2026
Merged

ASDAlexander77 merged 2 commits into
mainfrom
fix-rc-global-initializers

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Fixes #459.

Under rc and own, a module-level let G: Node | null = mk() was freed at start. The initializer passes the call's result to ts.GlobalResult through a cast to the union. That cast is the call's only use, and a releasable one, so the call's reference was given back at the end of the region as a discarded temporary. The global then pointed at freed memory. A plain let G = mk() passes the call to the terminator directly, so it was fine.

claimHandleGlobalInitializers already made exactly this claim for Shared<T> handles (own spec 23.1). It is renamed claimGlobalInitializers and now covers every global whose type owns a block. When a producer carrying an owned reference is reached through ts.Cast, ts.OptionalValue or ts.CreateUnionInstance, it is marked consumed, and the global keeps its count.

The retain for a handle read out of a field or element stays handle-only, because under own a retain left in a global's region is an error for any other type.

Tests

  • New 00global_union_from_call.ts:
    • It covers Node | null let and const globals and a Node | undefined global, each initialised from a call, read after allocations of the same size have reused anything freed, then reassigned.
    • It is registered as compile and JIT tests and in the rc/none corpus.
    • It failed under rc before the change.
  • The -mm=rc/own: a Node | null global initialised from a call is freed at start #459 repro now prints made1 under rc and own; before, it printed k1 and k26.
  • The own corpus is unchanged: 453 / 593 plain, 451 / 593 with --opt, same files.
  • Full Release ctest: 3636/3637. The one failure is test-compile-gc-defaultlib-collector, the known local LNK2005 against the stale installed default library, which fails the same way on main.

Not covered here: #461, a global object literal { s: mk() }. There the call's result is stored into a field of the literal's temporary rather than reaching the global through a view, so it needs its own fix.

🤖 Generated with Claude Code

ASDAlexander77 and others added 2 commits October 3, 2026 19:06
…ing view (#459)

`let G: Node | null = mk()` at module level was freed at start under rc (and own). The
initializer gives the call's result to `ts.GlobalResult` through a cast to the union; the cast is
the call's one use, and a releasable one, so the call's reference was given back at the end of
the region as a discarded temporary, and the global pointed at freed memory. A plain
`let G = mk()` gives the call to the terminator straight and was fine.

claimHandleGlobalInitializers did exactly this claim for Shared<T> handles (own spec 23.1); it is
now claimGlobalInitializers and covers every global whose type owns a block: a producer that
carries an owned reference, reached through ts.Cast, ts.OptionalValue or ts.CreateUnionInstance,
is marked consumed, and the global keeps its count. The retain for a handle read out of a field or
element stays handle-only, since under own a retain left in a global's region is an error for any
other type.

Test: 00global_union_from_call (compile and JIT, and in the rc/none corpus): `Node | null` let and
const globals and a `Node | undefined` global from calls, read after allocations of the same size
have reused anything freed, then reassigned. It failed under rc before the change. The own corpus
is unchanged (453 / 593, 451 with --opt).

Fixes #459.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…izers

# Conflicts:
#	tslang/test/tester/CMakeLists.txt
@ASDAlexander77
ASDAlexander77 merged commit 966e6d7 into main Oct 3, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-rc-global-initializers branch October 3, 2026 18:43
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.

-mm=rc/own: a Node | null global initialised from a call is freed at start

1 participant