fix(runtime): arm64_32 ILP32 — box oversized thread_locals, guard GC pointer classifier#6118
Conversation
…pointer classifier
On arm64_32 (Apple Watch Series 4–8 / SE), oversized `#[thread_local]` statics
overflow the ILP32 TLS layout and their writes silently corrupt adjacent
thread-locals. Heap-allocate (Box) the three large per-thread caches so only a
pointer lives in TLS:
- INTERN_TABLE (~128KB, string/intern.rs)
- TRANSITION_CACHE_GLOBAL (~320KB, object/mod.rs)
- VTABLE_IC (~160KB, object/class_registry/dispatch.rs)
Confirmed on a real Series 7: shrinking OR boxing each table removes the
corruption; boxing keeps full cache capacity.
Also on ILP32 only:
- gc/barrier.rs: reject heap-word candidates whose tagged payload exceeds 32
bits, so a mistagged/immediate value can't truncate to a garbage 32-bit
address the GC then marks or dereferences.
- lib.rs: use the system allocator (libsystem_malloc, solid on watchOS) on
32-bit targets — mimalloc on ILP32 is unproven and a corruption suspect.
64-bit keeps mimalloc.
No behavior change on 64-bit targets (all new code is `cfg`-gated to 32-bit,
except the Box indirection which is target-agnostic and free in the hot path).
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds ILP32-specific pointer decoding guards, gates the global allocator and mimalloc dependency by pointer width, and converts several thread-local caches from inline arrays to boxed slices to avoid oversized TLS storage. ChangesILP32 TLS layout and pointer decoding fixes
Estimated code review effort: 3 (Moderate) | ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-runtime/src/lib.rs`:
- Around line 26-31: The global allocator selection in `GLOBAL` already falls
back to `std::alloc::System` on non-64-bit targets, but `mimalloc` is still
being pulled in unconditionally. Update the `crates/perry-runtime` dependency
setup so `mimalloc` is declared only for 64-bit targets, keeping the allocator
choice in `lib.rs` aligned with a target-specific dependency and avoiding
unnecessary inclusion on other architectures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8e455183-7696-4b0a-8c52-f01ddd7366e4
📒 Files selected for processing (5)
crates/perry-runtime/src/gc/barrier.rscrates/perry-runtime/src/lib.rscrates/perry-runtime/src/object/class_registry/dispatch.rscrates/perry-runtime/src/object/mod.rscrates/perry-runtime/src/string/intern.rs
Addresses CodeRabbit: with lib.rs selecting std::alloc::System on 32-bit, mimalloc was still pulled in and compiled (dead C) on arm64_32. Gate the dependency on cfg(target_pointer_width = "64") so the ILP32 build neither uses nor compiles it — shrinking the arm64_32 build surface of an allocator that is itself a corruption suspect on that tier.
|
Addressed in 1061eb4 — moved |
What
The final runtime piece for building a watchOS arm64_32 app on
main. Follows #6107 (target-aware object-header size), #6108 (boxed exception TLS), and #6109 (AVFAudio/mic linking) — all merged. Without this, the next arm64_32 app links but corrupts memory at runtime.Why
On arm64_32 (ILP32: 64-bit registers, 32-bit pointers — Apple Watch Series 4–8 / SE), oversized
#[thread_local]statics overflow the ILP32 TLS layout, and their writes silently corrupt adjacent thread-locals. Three large per-thread caches trip this:INTERN_TABLEstring/intern.rsTRANSITION_CACHE_GLOBALobject/mod.rsVTABLE_ICobject/class_registry/dispatch.rsFix: heap-allocate each via
Box<[T]>so only a pointer lives in TLS. Confirmed on a real Series 7 — shrinking or boxing each table removes the corruption; boxing keeps full cache capacity.Also (ILP32-only,
cfg-gated)gc/barrier.rs— reject heap-word candidates whose tagged payload exceeds 32 bits, so a mistagged/immediate value can't truncate to a garbage 32-bit address the GC then marks or dereferences.lib.rs— use the system allocator (libsystem_malloc, solid on watchOS) on 32-bit targets; mimalloc on ILP32 is unproven and a corruption suspect. 64-bit keeps mimalloc.Impact on 64-bit
None. Every new branch is gated to
not(target_pointer_width = "64"), except theBoxindirection, which is target-agnostic and free in the hot path (.with()still hands back a*mut [T; N]).cargo check -p perry-runtimeis clean on host.Provenance
Extracted from the dbmeter watch app's device-test branch — the clean runtime fixes only. App-specific debugging scaffolding (a game-thread runner, a data-container trace logger) was deliberately left out.
Summary by CodeRabbit