Skip to content

fix(runtime): arm64_32 ILP32 — box oversized thread_locals, guard GC pointer classifier#6118

Merged
proggeramlug merged 2 commits into
mainfrom
fix/arm64_32-box-oversized-thread-locals
Jul 8, 2026
Merged

fix(runtime): arm64_32 ILP32 — box oversized thread_locals, guard GC pointer classifier#6118
proggeramlug merged 2 commits into
mainfrom
fix/arm64_32-box-oversized-thread-locals

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

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:

static size file
INTERN_TABLE ~128 KB string/intern.rs
TRANSITION_CACHE_GLOBAL ~320 KB object/mod.rs
VTABLE_IC ~160 KB object/class_registry/dispatch.rs

Fix: 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 the Box indirection, which is target-agnostic and free in the hot path (.with() still hands back a *mut [T; N]). cargo check -p perry-runtime is 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

  • Bug Fixes
    • Improved 32-bit compatibility by preventing invalid pointer/address decoding and truncation of non-pointer payload bits.
    • Reduced instability on 32-bit targets by moving large per-thread caches (vtable, transition, string interning) from fixed TLS arrays to safer heap-backed storage.
    • Updated global allocator selection to use mimalloc only on 64-bit targets, with a System fallback on 32-bit.

…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).
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d98b94f-4737-4840-8dc7-be996d1c55d4

📥 Commits

Reviewing files that changed from the base of the PR and between 1cf73c1 and 1061eb4.

📒 Files selected for processing (1)
  • crates/perry-runtime/Cargo.toml

📝 Walkthrough

Walkthrough

This 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.

Changes

ILP32 TLS layout and pointer decoding fixes

Layer / File(s) Summary
Pointer candidate decoding guards
crates/perry-runtime/src/gc/barrier.rs
Adds ILP32 range checks rejecting payloads exceeding 0xFFFF_FFFF in both the tagged-pointer decode path and the raw-pointer-shaped fallback path before casting to usize.
Global allocator selection by pointer width
crates/perry-runtime/Cargo.toml, crates/perry-runtime/src/lib.rs
Uses mimalloc::MiMalloc only on 64-bit targets, falls back to std::alloc::System on non-64-bit targets, and gates the mimalloc dependency to 64-bit pointer-width builds.
VTable inline cache TLS boxed storage
crates/perry-runtime/src/object/class_registry/dispatch.rs
Converts VTABLE_IC from an inline TLS array to a heap-allocated boxed slice, updating lookup and insert to double-dereference the cell.
Transition cache TLS boxed storage
crates/perry-runtime/src/object/mod.rs
Converts TRANSITION_CACHE_GLOBAL from an inline TLS array to a boxed heap slice, casting the pointer back to the fixed-size array type in with_transition_cache.
String intern table TLS boxed storage
crates/perry-runtime/src/string/intern.rs
Converts INTERN_TABLE from an inline TLS array to a boxed heap slice, casting the pointer back to the fixed-size array type in with_intern_table.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required template and omits Summary, Changes, Related issue, Test plan, and Checklist sections. Rewrite the PR description using the repository template, including Summary, Changes, Related issue, Test plan, and Checklist sections.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main ILP32 runtime fix: boxing oversized TLS and guarding GC pointer classification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/arm64_32-box-oversized-thread-locals

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1c5383b and 1cf73c1.

📒 Files selected for processing (5)
  • crates/perry-runtime/src/gc/barrier.rs
  • crates/perry-runtime/src/lib.rs
  • crates/perry-runtime/src/object/class_registry/dispatch.rs
  • crates/perry-runtime/src/object/mod.rs
  • crates/perry-runtime/src/string/intern.rs

Comment thread crates/perry-runtime/src/lib.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.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Addressed in 1061eb4 — moved mimalloc into a [target.'cfg(target_pointer_width = "64")'.dependencies] section so it matches the #[cfg(target_pointer_width = "64")] allocator selection in lib.rs. On arm64_32/ILP32 mimalloc is now neither used nor compiled. Verified: cargo tree -i mimalloc still resolves on 64-bit; cargo check -p perry-runtime clean.

@proggeramlug proggeramlug merged commit d663587 into main Jul 8, 2026
14 of 24 checks passed
@proggeramlug proggeramlug deleted the fix/arm64_32-box-oversized-thread-locals branch July 8, 2026 08:15
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.

1 participant