Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment thread
matthargett marked this conversation as resolved.
memfd = "0.6.5"
psm = "0.1.11"
proptest = "1.11.0"
Expand Down
73 changes: 69 additions & 4 deletions crates/unwinder/src/arch/aarch64.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,63 @@
//! 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<N>` (32-bit lane) and `x<N>` (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<N>, ...` 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",
out(reg) stack_pointer,
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
Expand All @@ -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
Expand All @@ -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<N>` (the 64-bit
// GPR view); see module docs for why.
core::arch::asm!(
"mov lr, {pc}",
"xpaclri",
Expand All @@ -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
}
}

Expand All @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions supply-chain/audits.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 12 additions & 6 deletions supply-chain/imports.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 <gsvelto@mozilla.com>"
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 <afranchuk@mozilla.com>"
criteria = "safe-to-deploy"
Expand Down
Loading