You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
gc_runtime_root_holders: raw thread_local! blocks escape classification, and no verdict fits census.rs's PASS1_MARKED #9740
While clearing the raw thread_local! blocks that keep tls-budget red on main (four converted in the linked PR), gc/census.rs could not be converted honestly. It surfaced both a gate blind spot and a gap in the verdict vocabulary.
1. A raw thread_local! is invisible to gc_runtime_root_holders.py
The holder inventory enumerates perry_thread_local! declarations (rule T). A rawthread_local! is therefore audited by neither gate: check_thread_locals.py flags it as a TLS-perf violation, but nothing asks whether it holds a heap pointer.
That is the wrong way round — the declarations that skipped the convention are exactly the ones least likely to have had their GC contract considered. Converting four files immediately exposed six previously-unclassified holders, five trivial and one not.
Suggestion: have the holder inventory enumerate raw thread_local! blocks too, so skipping the convention cannot also skip classification.
2. PASS1_MARKED holds heap addresses that no verdict describes truthfully
/// Pass-1 snapshot: sorted header addresses that were marked when mark/// propagation finished (see the module docs).staticPASS1_MARKED:RefCell<Option<Vec<usize>>> = const{RefCell::new(None)};
Those usizes are real GC header addresses. Against the vocabulary:
not_a_gc_pointer — defined as "an id, a counter, an epoch, a code address, a .rodata object, or Rust-owned state". A heap address is none of these. Using it would be false.
covered_elsewhere — requires a named registered scanner. None visits this, and none should: tracing the marked set would retain exactly the garbage the census exists to observe.
open_gap — asserts a real unrooted GC pointer, and fails the gate. It is not a gap.
unverified — fails the gate, and the contract is verified (below).
test_only — it is env-gated diagnostic code, not #[cfg(test)].
The actual contract, from the module's own docs:
Inside a SYNCHRONOUS full cycle the mark bits are authoritative and nothing moves.
The addresses are written in census_pass1_if_armed at the end of mark propagation and take()n at sweep entry in the same synchronous full cycle, with no relocation and no mutator resumption in that window. They are safe by construction, and deliberately untraced. Off by default — ARMED is false unless PERRY_GC_CENSUS=<path> is set, leaving the cell None.
So the holder is fine and the vocabulary cannot say so. Rather than force a wrong label to make a gate green, census.rs is left as a raw thread_local! and tls-budget stays red on that one file.
Suggestion: add a verdict for "holds addresses, deliberately untraced, valid only within a single non-moving window", carrying the window's boundaries so a future change that widens it fails the gate. That is a real contract worth pinning — it is the same shape as the retained-array-growth-stub class in #9717.
Happy to implement whichever direction the maintainers prefer.
Fixed by #9750, which landed on main via merge train #9798. The train was merged as its own branch, so the Fixes #9740 keyword in #9750 never fired — closing manually. Verified the fix is on main by patch-id (git cherry), not just by the PR being closed.
Activity
proggeramlug commentedon Sep 5, 2026
Fixed by #9750, which landed on
mainvia merge train #9798. The train was merged as its own branch, so theFixes #9740keyword in #9750 never fired — closing manually. Verified the fix is onmainby patch-id (git cherry), not just by the PR being closed.fix(gc): convert census.rs to hot TLS and re-pin its window (#9740)