Skip to content

Commit c634c9b

Browse files
Fix a couple of issues that prevent wasmtime for compiling/running on arm64_32 (Apple Watch) (#13259)
* unwinder: type aarch64 register-bearing locals as u64 `crates/unwinder/src/arch/aarch64.rs` has inline-asm operands that take register-width values. They were typed `usize`, which works on the usual `aarch64-*` LP64 targets where `usize` is `u64` and the operand class is unambiguously the 64-bit GPR view. On `arm64_32-apple-watchos` (ILP32 ABI: 64-bit registers, 32-bit pointers) `usize` is `u32`, which makes the same operands ambiguous between the `w<N>` (32-bit lane) and `x<N>` (64-bit GPR) views — exactly what rustc's `asm_sub_register` lint flags. Relying on the ISA-side zero-extend that aarch64 happens to perform on `mov w<N>, ...` would also be relying on a property the language doesn't promise: the Rust Reference is explicit that the upper bits of a register holding a sub-register-width input are *undefined*[0]. Rather than leak `u64` into the public surface (the `Unwind` trait, the shared `arch/mod.rs` dispatch, and the per-arch backends in `x86.rs`, `riscv64.rs`, `s390x.rs`), keep the public function signatures `usize` — that's the existing convention shared with the other backends, and the `u64`-vs-pointer-width split is unique to aarch64-on-ILP32. Inside this module, type any register-bearing local that participates in inline asm as `u64`, and cast at the boundaries: - `u64::try_from(v).unwrap()` widens `usize` → `u64` (infallible on every supported Rust target, the `.unwrap()` documents that any failure would be a target-property issue). - `as usize` narrows `u64` → `usize` at the return — truncates on `arm64_32` by design (the saved PC/SP there is a 32-bit host pointer that fits exactly in the low 32 bits) and is the identity on aarch64 LP64. Also switch the saved-LR load from `*(fp as *mut usize).offset(1)` to `*(fp as *mut u64).offset(1)`. AAPCS64 reserves two 64-bit slots for the frame record on every aarch64 ABI variant — including `arm64_32` — so an 8-byte stride is correct regardless of pointer width. With `*mut usize` on `arm64_32` `.offset(1)` would advance by only 4 bytes and read the upper half of the saved-FP slot. This is a latent correctness fix; today the unwinder isn't exercised on `arm64_32` (which runs Pulley, not Cranelift-compiled native code), but the corrected form is the right one to land alongside the type change. No behaviour change on existing aarch64 LP64 targets. Silences two `asm_sub_register` warnings on a future `arm64_32-apple-watchos` build of this crate. [0]: https://doc.rust-lang.org/reference/inline-assembly.html#r-asm.register-operands.smaller-value * Bump mach2 dep from 0.4.2 to 0.6 mach2 v0.4.2 emits `compile_error!("mach requires macOS or iOS")` on any target where neither `target_os = "macos"` nor `target_os = "ios"` matches. That blocks every Apple non-iOS-non-macOS platform — most pressingly arm64_32-apple-watchos for embedders shipping wasmtime on Apple Watch. The fix has been upstream in mach2 since 0.6.0 (commit `538ce75`, 2025-08-16, "Add support for tvOS, watchOS and visionOS"), which widens the cfg gate from `cfg(any(macos, ios))` to `cfg(target_vendor = "apple")` on both the `compile_error!` and the `libc` build-dep, with no public-API changes in the modules wasmtime imports (`exc`, `exception_types`, `kern_return`, `mach_init`, `mach_port`, `message`, `ndr`, `port`, `thread_act`, `thread_status`). Verified by building wasmtime as a `staticlib` for `arm64_32-apple-watchos` under `nightly-2026-01-25 + -Z build-std=std,panic_abort` with `--features pulley,runtime,std,cranelift,anyhow` — no other changes needed in `crates/wasmtime/src/runtime/vm/sys/unix/machports.rs`. The dev-only path (`cranelift-jit -> region -> mach2 0.4.x`) keeps an older mach2 in the lockfile for cranelift-jit's own host tests; that path is not part of any production embedder build and stays unchanged. Closes the watchOS port story without needing a separate mach2 release. * Add vets for mach2 --------- Co-authored-by: Alex Crichton <[email protected]>
1 parent 689eae7 commit c634c9b

5 files changed

Lines changed: 104 additions & 15 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ async-trait = "0.1.89"
394394
heck = "0.5"
395395
similar = "2.7.0"
396396
toml = "0.9.8"
397-
mach2 = "0.4.2"
397+
mach2 = "0.6"
398398
memfd = "0.6.5"
399399
psm = "0.1.11"
400400
proptest = "1.11.0"

crates/unwinder/src/arch/aarch64.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,63 @@
11
//! Arm64-specific definitions of architecture-specific functions in Wasmtime.
2+
//!
3+
//! ## ILP32 vs LP64 on aarch64
4+
//!
5+
//! Aarch64 GPRs are 64 bits regardless of pointer width. On the usual
6+
//! `aarch64-*` (LP64) targets `usize` is `u64` and the two are
7+
//! interchangeable, but on `arm64_32-apple-watchos` (ILP32 ABI: 64-bit
8+
//! registers, 32-bit pointers) `usize` is `u32`. An inline-asm operand
9+
//! typed as `usize` is therefore ambiguous on `arm64_32` between the
10+
//! `w<N>` (32-bit lane) and `x<N>` (64-bit GPR) views — exactly what
11+
//! rustc's `asm_sub_register` lint flags. The Rust Reference is also
12+
//! explicit that the upper bits of a register holding a sub-register-
13+
//! width input are *undefined*[0]; relying on the ISA-side zero-extend
14+
//! that aarch64 happens to perform on `mov w<N>, ...` would be relying
15+
//! on a property the language doesn't promise.
16+
//!
17+
//! The public functions in this module keep their `usize` signatures —
18+
//! that's the convention shared with the other unwinder backends
19+
//! (`x86.rs`, `riscv64.rs`, `s390x.rs`), and the `u64`-vs-pointer-width
20+
//! split is unique to aarch64-on-ILP32. Inside this module, any
21+
//! register-bearing local that participates in inline asm is typed
22+
//! `u64` so the operand class is unambiguously the 64-bit GPR view.
23+
//! The cross-boundary casts are explicit:
24+
//!
25+
//! - `u64::try_from(v).unwrap()` widens `usize` → `u64`. Infallible
26+
//! on every supported Rust target (`usize` is at most 64 bits
27+
//! everywhere today), and the `.unwrap()` documents that any
28+
//! failure would be a target-property issue rather than a runtime
29+
//! one.
30+
//! - `as usize` narrows `u64` → `usize` at the return. Truncates on
31+
//! `arm64_32` by design — the saved PC/SP there is a 32-bit host
32+
//! pointer that fits exactly in the low 32 bits of the register —
33+
//! and is the identity on aarch64 LP64 targets.
34+
//!
35+
//! ## AAPCS64 frame-record stride
36+
//!
37+
//! Reads of the AAPCS64 frame record (saved FP / saved LR) use
38+
//! `*mut u64` rather than `*mut usize`. AAPCS64 reserves two 64-bit
39+
//! slots for the frame record on every aarch64 ABI variant — including
40+
//! `arm64_32` — so `.offset(1)` advancing by 8 bytes is correct
41+
//! regardless of pointer width. With `*mut usize` on `arm64_32`
42+
//! `.offset(1)` would advance by only 4 bytes and read the upper half
43+
//! of the saved-FP slot. This matters for a future `arm64_32` Cranelift
44+
//! port; today the `arm64_32-apple-watchos` toolchain only runs Pulley.
45+
//!
46+
//! [0]: https://doc.rust-lang.org/reference/inline-assembly.html#r-asm.register-operands.smaller-value
247
348
#[inline]
449
pub fn get_stack_pointer() -> usize {
5-
let stack_pointer: usize;
50+
let stack_pointer: u64;
651
unsafe {
752
core::arch::asm!(
853
"mov {}, sp",
954
out(reg) stack_pointer,
1055
options(nostack,nomem),
1156
);
1257
}
13-
stack_pointer
58+
// Truncates u64 → u32 on arm64_32 (the host SP is a 32-bit pointer
59+
// that fits in the low 32 bits); identity on aarch64 LP64.
60+
stack_pointer as usize
1461
}
1562

1663
// The aarch64 calling conventions save the return PC one i64 above the FP and
@@ -26,7 +73,9 @@ pub fn get_stack_pointer() -> usize {
2673
// - AAPCS64 section 6.2.3 The Frame Pointer[0]
2774
pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize {
2875
unsafe {
29-
let mut pc = *(fp as *mut usize).offset(1);
76+
// `*mut u64` (not `*mut usize`) so `.offset(1)` advances by 8 bytes
77+
// on every aarch64 ABI variant — see module docs.
78+
let mut pc: u64 = *(fp as *mut u64).offset(1);
3079

3180
// The return address might be signed, so we need to strip the highest bits
3281
// (where the authentication code might be located) in order to obtain a
@@ -35,6 +84,9 @@ pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize {
3584
// the implementation is backward-compatible and there is no duplication.
3685
// However, this instruction requires the LR register for both its input and
3786
// output.
87+
//
88+
// `pc` is `u64` so the operand class is unambiguously `x<N>` (the 64-bit
89+
// GPR view); see module docs for why.
3890
core::arch::asm!(
3991
"mov lr, {pc}",
4092
"xpaclri",
@@ -44,7 +96,11 @@ pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize {
4496
options(nomem, nostack, preserves_flags, pure),
4597
);
4698

47-
pc
99+
// Truncates u64 → u32 on arm64_32 (the saved PC there is a 32-bit
100+
// host pointer; XPACLRI strips any PAC bits on v8.3+, no-op on
101+
// earlier cores like the A12 in Apple Watch SE2's S8 SoC);
102+
// identity on aarch64 LP64.
103+
pc as usize
48104
}
49105
}
50106

@@ -55,6 +111,15 @@ pub unsafe fn resume_to_exception_handler(
55111
payload1: usize,
56112
payload2: usize,
57113
) -> ! {
114+
// The asm operands name registers explicitly (`in("x0")` etc.), so the
115+
// `asm_sub_register` lint doesn't fire here even with `usize` operands.
116+
// Widen anyway for consistency with the rest of this module's "register-
117+
// bearing locals are `u64`" rule — see module docs.
118+
let pc = u64::try_from(pc).unwrap();
119+
let sp = u64::try_from(sp).unwrap();
120+
let fp = u64::try_from(fp).unwrap();
121+
let payload1 = u64::try_from(payload1).unwrap();
122+
let payload2 = u64::try_from(payload2).unwrap();
58123
unsafe {
59124
core::arch::asm!(
60125
"mov sp, x2",

supply-chain/audits.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7095,6 +7095,18 @@ user-id = 2915 # Amanieu d'Antras (Amanieu)
70957095
start = "2019-05-04"
70967096
end = "2024-07-06"
70977097

7098+
[[trusted.mach2]]
7099+
criteria = "safe-to-deploy"
7100+
user-id = 51017 # Yuki Okushi (JohnTitor)
7101+
start = "2021-11-15"
7102+
end = "2027-05-04"
7103+
7104+
[[trusted.mach2]]
7105+
criteria = "safe-to-deploy"
7106+
trusted-publisher = "github:JohnTitor/mach2"
7107+
start = "2025-11-16"
7108+
end = "2027-05-04"
7109+
70987110
[[trusted.macro-string]]
70997111
criteria = "safe-to-deploy"
71007112
user-id = 3618 # David Tolnay (dtolnay)

supply-chain/imports.lock

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,18 @@ user-id = 6825
10311031
user-login = "sunfishcode"
10321032
user-name = "Dan Gohman"
10331033

1034+
[[publisher.mach2]]
1035+
version = "0.4.3"
1036+
when = "2025-06-22"
1037+
user-id = 51017
1038+
user-login = "JohnTitor"
1039+
user-name = "Yuki Okushi"
1040+
1041+
[[publisher.mach2]]
1042+
version = "0.6.0"
1043+
when = "2025-11-16"
1044+
trusted-publisher = "github:JohnTitor/mach2"
1045+
10341046
[[publisher.macro-string]]
10351047
version = "0.2.0"
10361048
when = "2026-02-24"
@@ -3186,12 +3198,6 @@ delta = "0.4.18 -> 0.4.20"
31863198
notes = "Only cfg attribute and internal macro changes and module refactorings"
31873199
aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml"
31883200

3189-
[[audits.mozilla.audits.mach2]]
3190-
who = "Gabriele Svelto <[email protected]>"
3191-
criteria = "safe-to-deploy"
3192-
version = "0.4.1"
3193-
aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
3194-
31953201
[[audits.mozilla.audits.num-conv]]
31963202
who = "Alex Franchuk <[email protected]>"
31973203
criteria = "safe-to-deploy"

0 commit comments

Comments
 (0)