From 139314466555bf44cab1e5edb687ba61428ab406 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Mon, 4 May 2026 13:41:29 -0700 Subject: [PATCH 1/3] unwinder: type aarch64 register-bearing locals as u64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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` (32-bit lane) and `x` (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, ...` 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 --- crates/unwinder/src/arch/aarch64.rs | 73 +++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/crates/unwinder/src/arch/aarch64.rs b/crates/unwinder/src/arch/aarch64.rs index bc639267a805..a79ccfba97ba 100644 --- a/crates/unwinder/src/arch/aarch64.rs +++ b/crates/unwinder/src/arch/aarch64.rs @@ -1,8 +1,53 @@ //! Arm64-specific definitions of architecture-specific functions in Wasmtime. +//! +//! ## ILP32 vs LP64 on aarch64 +//! +//! Aarch64 GPRs are 64 bits regardless of pointer width. On the usual +//! `aarch64-*` (LP64) targets `usize` is `u64` and the two are +//! interchangeable, but on `arm64_32-apple-watchos` (ILP32 ABI: 64-bit +//! registers, 32-bit pointers) `usize` is `u32`. An inline-asm operand +//! typed as `usize` is therefore ambiguous on `arm64_32` between the +//! `w` (32-bit lane) and `x` (64-bit GPR) views — exactly what +//! rustc's `asm_sub_register` lint flags. The Rust Reference is also +//! explicit that the upper bits of a register holding a sub-register- +//! width input are *undefined*[0]; relying on the ISA-side zero-extend +//! that aarch64 happens to perform on `mov w, ...` would be relying +//! on a property the language doesn't promise. +//! +//! The public functions in this module keep their `usize` signatures — +//! that's the convention shared with the other unwinder backends +//! (`x86.rs`, `riscv64.rs`, `s390x.rs`), and the `u64`-vs-pointer-width +//! split is unique to aarch64-on-ILP32. Inside this module, any +//! register-bearing local that participates in inline asm is typed +//! `u64` so the operand class is unambiguously the 64-bit GPR view. +//! The cross-boundary casts are explicit: +//! +//! - `u64::try_from(v).unwrap()` widens `usize` → `u64`. Infallible +//! on every supported Rust target (`usize` is at most 64 bits +//! everywhere today), and the `.unwrap()` documents that any +//! failure would be a target-property issue rather than a runtime +//! one. +//! - `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 of the register — +//! and is the identity on aarch64 LP64 targets. +//! +//! ## AAPCS64 frame-record stride +//! +//! Reads of the AAPCS64 frame record (saved FP / saved LR) use +//! `*mut u64` rather than `*mut usize`. AAPCS64 reserves two 64-bit +//! slots for the frame record on every aarch64 ABI variant — including +//! `arm64_32` — so `.offset(1)` advancing by 8 bytes 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 matters for a future `arm64_32` Cranelift +//! port; today the `arm64_32-apple-watchos` toolchain only runs Pulley. +//! +//! [0]: https://doc.rust-lang.org/reference/inline-assembly.html#r-asm.register-operands.smaller-value #[inline] pub fn get_stack_pointer() -> usize { - let stack_pointer: usize; + let stack_pointer: u64; unsafe { core::arch::asm!( "mov {}, sp", @@ -10,7 +55,9 @@ pub fn get_stack_pointer() -> usize { options(nostack,nomem), ); } - stack_pointer + // Truncates u64 → u32 on arm64_32 (the host SP is a 32-bit pointer + // that fits in the low 32 bits); identity on aarch64 LP64. + stack_pointer as usize } // The aarch64 calling conventions save the return PC one i64 above the FP and @@ -26,7 +73,9 @@ pub fn get_stack_pointer() -> usize { // - AAPCS64 section 6.2.3 The Frame Pointer[0] pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize { unsafe { - let mut pc = *(fp as *mut usize).offset(1); + // `*mut u64` (not `*mut usize`) so `.offset(1)` advances by 8 bytes + // on every aarch64 ABI variant — see module docs. + let mut pc: u64 = *(fp as *mut u64).offset(1); // The return address might be signed, so we need to strip the highest bits // (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 { // the implementation is backward-compatible and there is no duplication. // However, this instruction requires the LR register for both its input and // output. + // + // `pc` is `u64` so the operand class is unambiguously `x` (the 64-bit + // GPR view); see module docs for why. core::arch::asm!( "mov lr, {pc}", "xpaclri", @@ -44,7 +96,11 @@ pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize { options(nomem, nostack, preserves_flags, pure), ); - pc + // Truncates u64 → u32 on arm64_32 (the saved PC there is a 32-bit + // host pointer; XPACLRI strips any PAC bits on v8.3+, no-op on + // earlier cores like the A12 in Apple Watch SE2's S8 SoC); + // identity on aarch64 LP64. + pc as usize } } @@ -55,6 +111,15 @@ pub unsafe fn resume_to_exception_handler( payload1: usize, payload2: usize, ) -> ! { + // The asm operands name registers explicitly (`in("x0")` etc.), so the + // `asm_sub_register` lint doesn't fire here even with `usize` operands. + // Widen anyway for consistency with the rest of this module's "register- + // bearing locals are `u64`" rule — see module docs. + let pc = u64::try_from(pc).unwrap(); + let sp = u64::try_from(sp).unwrap(); + let fp = u64::try_from(fp).unwrap(); + let payload1 = u64::try_from(payload1).unwrap(); + let payload2 = u64::try_from(payload2).unwrap(); unsafe { core::arch::asm!( "mov sp, x2", From f6d629b281cf749fa3c2d369756c084244875a49 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Sat, 2 May 2026 01:40:25 -0700 Subject: [PATCH 2/3] Bump mach2 dep from 0.4.2 to 0.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Cargo.lock | 14 ++++++++++---- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca4b4b1d464a..7d1478df18a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2395,13 +2395,19 @@ dependencies = [ [[package]] name = "mach2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + [[package]] name = "macro-string" version = "0.2.0" @@ -3213,7 +3219,7 @@ checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" dependencies = [ "bitflags 1.3.2", "libc", - "mach2", + "mach2 0.4.3", "windows-sys 0.52.0", ] @@ -4727,7 +4733,7 @@ dependencies = [ "libc", "libtest-mimic", "log", - "mach2", + "mach2 0.6.0", "memfd", "object 0.39.0", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index fa5a426b504b..cbcc57706d5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -394,7 +394,7 @@ async-trait = "0.1.89" heck = "0.5" similar = "2.7.0" toml = "0.9.8" -mach2 = "0.4.2" +mach2 = "0.6" memfd = "0.6.5" psm = "0.1.11" proptest = "1.11.0" From 3c0c73fbde5d95b8bb7cb9ab5fd3c0c37f5fde72 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 4 May 2026 14:16:54 -0700 Subject: [PATCH 3/3] Add vets for mach2 --- supply-chain/audits.toml | 12 ++++++++++++ supply-chain/imports.lock | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 04085992fe02..1cc3ac5fb803 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -7095,6 +7095,18 @@ user-id = 2915 # Amanieu d'Antras (Amanieu) start = "2019-05-04" end = "2024-07-06" +[[trusted.mach2]] +criteria = "safe-to-deploy" +user-id = 51017 # Yuki Okushi (JohnTitor) +start = "2021-11-15" +end = "2027-05-04" + +[[trusted.mach2]] +criteria = "safe-to-deploy" +trusted-publisher = "github:JohnTitor/mach2" +start = "2025-11-16" +end = "2027-05-04" + [[trusted.macro-string]] criteria = "safe-to-deploy" user-id = 3618 # David Tolnay (dtolnay) diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 3edcfead8117..38d5bc871dcf 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -799,6 +799,18 @@ user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" +[[publisher.mach2]] +version = "0.4.3" +when = "2025-06-22" +user-id = 51017 +user-login = "JohnTitor" +user-name = "Yuki Okushi" + +[[publisher.mach2]] +version = "0.6.0" +when = "2025-11-16" +trusted-publisher = "github:JohnTitor/mach2" + [[publisher.macro-string]] version = "0.2.0" when = "2026-02-24" @@ -2954,12 +2966,6 @@ delta = "0.4.18 -> 0.4.20" notes = "Only cfg attribute and internal macro changes and module refactorings" aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" -[[audits.mozilla.audits.mach2]] -who = "Gabriele Svelto " -criteria = "safe-to-deploy" -version = "0.4.1" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - [[audits.mozilla.audits.num-conv]] who = "Alex Franchuk " criteria = "safe-to-deploy"