Skip to content

rc: a global object literal takes over what its fields were given (#461) - #470

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-rc-global-object-literal
Oct 3, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-rc-global-object-literal

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Fixes #461.

Under rc and own, a module-level let o = { s: mk() } was freed at start.

The literal is built in a temporary slot:

  1. The call's result is stored into the literal's field.
  2. The whole literal is read out and given to ts.GlobalResult.

The store in step 1 takes no reference, so the call's +1 was given back at the end of the initializer region as a discarded temporary, and the global's field pointed at freed memory. Inside a function the same literal survives only because the block it ends with is the function body itself.

claimGlobalInitializers (from #468) now follows a global's value, when it is read out of a literal's temporary slot that owns nothing itself, into the stores to that slot's fields:

  • each owned producer stored there, through the same views as before, is marked consumed;
  • a field that is itself such a literal is followed into.

The global then keeps each count.

Tests

  • New 00global_object_literal.ts. It covers a let and a const literal global with class fields from calls, a nested literal, and a reassignment. Each field is read after allocations of the same size have reused anything freed. It is registered as compile and JIT tests and in the rc/none corpus, and it failed under rc before the change.
  • The -mm=rc/own: a global object literal { s: mk() } is freed at start #461 repro prints made1 under rc and own; it printed k15 and k31 before.
  • The own corpus is unchanged.
  • Full Release ctest: 3660/3661. The one failure is test-compile-gc-defaultlib-collector, the known local LNK2005 against the stale installed default library, which fails on main the same way.

🤖 Generated with Claude Code

`let o = { s: mk() }` at module level was freed at start under rc (and own). The literal is built
in a temporary slot: the call's result is stored into its field, and the whole literal is read out
and given to ts.GlobalResult. The store took no reference, so the call's +1 was given back at the
end of the initializer region as a discarded temporary, and the global's field pointed at freed
memory. Inside a function the same literal survives only because the block it ends with is the
function's.

claimGlobalInitializers now follows a global's value read out of a literal's temporary slot (one
that owns nothing of its own) into the stores to that slot's fields: each owned producer stored
there, through the same views, is marked consumed, and a field that is itself such a literal is
followed into. The global then keeps each count.

Test: 00global_object_literal (compile and JIT, and in the rc/none corpus): a let and a const
literal global with class fields from calls, a nested literal, and a reassignment, read after
allocations of the same size have reused anything freed. It failed under rc before the change. The
#461 repro prints made1 under rc and own (it printed k15 and k31). The own corpus is unchanged.

Fixes #461.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit efb1677 into main Oct 3, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-rc-global-object-literal branch October 3, 2026 19:17
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 global object literal { s: mk() } is freed at start

1 participant