Path: blob/main/winch/codegen/src/isa/aarch64/abi.rs
3069 views
use super::regs;1use crate::abi::{ABI, ABIOperand, ABIParams, ABIResults, ABISig, ParamsOrReturns, align_to};2use crate::codegen::CodeGenError;3use crate::isa::{CallingConvention, reg::Reg};4use crate::{RegIndexEnv, Result, bail};5use wasmtime_environ::{WasmHeapType, WasmValType};67#[derive(Default)]8pub(crate) struct Aarch64ABI;910/// The x28 register serves as the shadow stack pointer. For further details,11/// please refer to [`crate::isa::aarch64::regs::shadow_sp`].12///13/// This register is designated as callee-saved to prevent corruption during14/// function calls. This is especially important for Wasm-to-Wasm calls in15/// Winch-generated code, as Winch's default calling convention does not define16/// any callee-saved registers.17///18/// Note that 16 bytes are used to save the shadow stack pointer register even19/// though only 8 are needed. 16 is used for simplicity to ensure that the20/// 16-byte alignment requirement for memory addressing is met at the function's21/// prologue and epilogue.22pub const SHADOW_STACK_POINTER_SLOT_SIZE: u8 = 16;2324impl ABI for Aarch64ABI {25// TODO change to 16 once SIMD is supported26fn stack_align() -> u8 {27828}2930fn call_stack_align() -> u8 {311632}3334fn arg_base_offset() -> u8 {35// Two 8-byte slots:36// * One for link register37// * One for the frame pointer38//39// ┌──────────┬───────── Argument base40// │ LR │41// │ │42// ├──────────┼43// │ │44// │ FP │45// └──────────┴ -> 16461647}4849fn initial_frame_size() -> u8 {50// The initial frame size is composed of 4 8-byte slots:51// * Two slots for the link register and the frame pointer. See52// [`Self::arg_base_offset`]53// * Two for the shadow stack pointer register. See54// [`SHADOW_STACK_POINTER_SIZE`]55//56// ┌──────────┬───────── Argument base57// │ LR │58// │ │59// ├──────────┼60// │ │61// │ FP │62// ┌──────────┬63// │ │64// │ │65// │ │66// │ x28 │67// └──────────┴ -> 3268Self::arg_base_offset() + SHADOW_STACK_POINTER_SLOT_SIZE69}7071fn word_bits() -> u8 {726473}7475fn sig_from(76params: &[WasmValType],77returns: &[WasmValType],78call_conv: &CallingConvention,79) -> Result<ABISig> {80assert!(call_conv.is_apple_aarch64() || call_conv.is_default());81// The first element tracks the general purpose register index, capped at 7 (x0-x7).82// The second element tracks the floating point register index, capped at 7 (v0-v7).83// Follows84// https://github.com/ARM-software/abi-aa/blob/2021Q1/aapcs64/aapcs64.rst#64parameter-passing85let mut params_index_env = RegIndexEnv::with_limits_per_class(8, 8);86let results = Self::abi_results(returns, call_conv)?;87let params =88ABIParams::from::<_, Self>(params, 0, results.on_stack(), |ty, stack_offset| {89Self::to_abi_operand(90ty,91stack_offset,92&mut params_index_env,93call_conv,94ParamsOrReturns::Params,95)96})?;9798Ok(ABISig::new(*call_conv, params, results))99}100101fn abi_results(returns: &[WasmValType], call_conv: &CallingConvention) -> Result<ABIResults> {102assert!(call_conv.is_apple_aarch64() || call_conv.is_default());103// Use absolute count for results given that for Winch's104// default CallingConvention only one register is used for results105// independent of the register class.106// In the case of 2+ results, the rest are passed in the stack,107// similar to how Wasmtime handles multi-value returns.108let mut returns_index_env = RegIndexEnv::with_absolute_limit(1);109110ABIResults::from(returns, call_conv, |ty, stack_offset| {111Self::to_abi_operand(112ty,113stack_offset,114&mut returns_index_env,115call_conv,116ParamsOrReturns::Returns,117)118})119}120121fn vmctx_reg() -> Reg {122regs::xreg(9)123}124125fn stack_slot_size() -> u8 {126Self::word_bytes()127}128129fn sizeof(ty: &WasmValType) -> u8 {130match ty {131WasmValType::Ref(rt) => match rt.heap_type {132WasmHeapType::Func => Self::word_bytes(),133ht => unimplemented!("Support for WasmHeapType: {ht}"),134},135WasmValType::F64 | WasmValType::I64 => Self::word_bytes(),136WasmValType::F32 | WasmValType::I32 => Self::word_bytes() / 2,137WasmValType::V128 => Self::word_bytes() * 2,138}139}140}141142impl Aarch64ABI {143fn to_abi_operand(144wasm_arg: &WasmValType,145stack_offset: u32,146index_env: &mut RegIndexEnv,147call_conv: &CallingConvention,148params_or_returns: ParamsOrReturns,149) -> Result<(ABIOperand, u32)> {150let (reg, ty) = match wasm_arg {151ty @ (WasmValType::I32 | WasmValType::I64) => {152(index_env.next_gpr().map(regs::xreg), ty)153}154155ty @ (WasmValType::F32 | WasmValType::F64) => {156(index_env.next_fpr().map(regs::vreg), ty)157}158159ty @ WasmValType::Ref(rt) => match rt.heap_type {160WasmHeapType::Func | WasmHeapType::Extern => {161(index_env.next_gpr().map(regs::xreg), ty)162}163_ => bail!(CodeGenError::unsupported_wasm_type()),164},165166_ => bail!(CodeGenError::unsupported_wasm_type()),167};168169let ty_size = <Self as ABI>::sizeof(wasm_arg);170let default = || {171let arg = ABIOperand::stack_offset(stack_offset, *ty, ty_size as u32);172let slot_size = Self::stack_slot_size();173// Stack slots for parameters are aligned to a fixed slot size,174// in the case of Aarch64, 8 bytes.175// For the non-default calling convention, stack slots for176// return values are type-sized aligned.177// For the default calling convention, we don't type-size align,178// given that results on the stack must match spills generated179// from within the compiler, which are not type-size aligned.180let next_stack = if params_or_returns == ParamsOrReturns::Params {181align_to(stack_offset, slot_size as u32) + (slot_size as u32)182} else if call_conv.is_default() {183stack_offset + (ty_size as u32)184} else {185align_to(stack_offset, ty_size as u32) + (ty_size as u32)186};187(arg, next_stack)188};189Ok(reg.map_or_else(default, |reg| {190(ABIOperand::reg(reg, *ty, ty_size as u32), stack_offset)191}))192}193}194195#[cfg(test)]196mod tests {197use super::Aarch64ABI;198use crate::{199Result,200abi::{ABI, ABIOperand},201isa::CallingConvention,202isa::aarch64::regs,203isa::reg::Reg,204};205use wasmtime_environ::{206WasmFuncType,207WasmValType::{self, *},208};209210#[test]211fn xreg_abi_sig() -> Result<()> {212let wasm_sig = WasmFuncType::new(213[I32, I64, I32, I64, I32, I32, I64, I32, I64].into(),214[].into(),215);216217let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;218let params = sig.params;219220match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(0));221match_reg_arg(params.get(1).unwrap(), I64, regs::xreg(1));222match_reg_arg(params.get(2).unwrap(), I32, regs::xreg(2));223match_reg_arg(params.get(3).unwrap(), I64, regs::xreg(3));224match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(4));225match_reg_arg(params.get(5).unwrap(), I32, regs::xreg(5));226match_reg_arg(params.get(6).unwrap(), I64, regs::xreg(6));227match_reg_arg(params.get(7).unwrap(), I32, regs::xreg(7));228match_stack_arg(params.get(8).unwrap(), I64, 0);229Ok(())230}231232#[test]233fn vreg_abi_sig() -> Result<()> {234let wasm_sig = WasmFuncType::new(235[F32, F64, F32, F64, F32, F32, F64, F32, F64].into(),236[].into(),237);238239let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;240let params = sig.params;241242match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));243match_reg_arg(params.get(1).unwrap(), F64, regs::vreg(1));244match_reg_arg(params.get(2).unwrap(), F32, regs::vreg(2));245match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(3));246match_reg_arg(params.get(4).unwrap(), F32, regs::vreg(4));247match_reg_arg(params.get(5).unwrap(), F32, regs::vreg(5));248match_reg_arg(params.get(6).unwrap(), F64, regs::vreg(6));249match_reg_arg(params.get(7).unwrap(), F32, regs::vreg(7));250match_stack_arg(params.get(8).unwrap(), F64, 0);251Ok(())252}253254#[test]255fn mixed_abi_sig() -> Result<()> {256let wasm_sig = WasmFuncType::new(257[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),258[].into(),259);260261let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;262let params = sig.params;263264match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));265match_reg_arg(params.get(1).unwrap(), I32, regs::xreg(0));266match_reg_arg(params.get(2).unwrap(), I64, regs::xreg(1));267match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(1));268match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(2));269match_reg_arg(params.get(5).unwrap(), F32, regs::vreg(2));270match_reg_arg(params.get(6).unwrap(), F64, regs::vreg(3));271match_reg_arg(params.get(7).unwrap(), F32, regs::vreg(4));272match_reg_arg(params.get(8).unwrap(), F64, regs::vreg(5));273Ok(())274}275276#[test]277fn int_abi_sig_multi_returns() -> Result<()> {278let wasm_sig = WasmFuncType::new(279[I32, I64, I32, I64, I32, I32].into(),280[I32, I32, I32].into(),281);282283let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;284let params = sig.params;285let results = sig.results;286287match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(1));288match_reg_arg(params.get(1).unwrap(), I64, regs::xreg(2));289match_reg_arg(params.get(2).unwrap(), I32, regs::xreg(3));290match_reg_arg(params.get(3).unwrap(), I64, regs::xreg(4));291match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(5));292match_reg_arg(params.get(5).unwrap(), I32, regs::xreg(6));293294match_stack_arg(results.get(0).unwrap(), I32, 4);295match_stack_arg(results.get(1).unwrap(), I32, 0);296match_reg_arg(results.get(2).unwrap(), I32, regs::xreg(0));297Ok(())298}299300#[test]301fn mixed_abi_sig_multi_returns() -> Result<()> {302let wasm_sig = WasmFuncType::new(303[F32, I32, I64, F64, I32].into(),304[I32, F32, I32, F32, I64].into(),305);306307let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;308let params = sig.params;309let results = sig.results;310311match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));312match_reg_arg(params.get(1).unwrap(), I32, regs::xreg(1));313match_reg_arg(params.get(2).unwrap(), I64, regs::xreg(2));314match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(1));315match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(3));316317match_stack_arg(results.get(0).unwrap(), I32, 12);318match_stack_arg(results.get(1).unwrap(), F32, 8);319match_stack_arg(results.get(2).unwrap(), I32, 4);320match_stack_arg(results.get(3).unwrap(), F32, 0);321match_reg_arg(results.get(4).unwrap(), I64, regs::xreg(0));322Ok(())323}324325#[track_caller]326fn match_reg_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_reg: Reg) {327match abi_arg {328&ABIOperand::Reg { reg, ty, .. } => {329assert_eq!(reg, expected_reg);330assert_eq!(ty, expected_ty);331}332stack => panic!("Expected reg argument, got {stack:?}"),333}334}335336#[track_caller]337fn match_stack_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_offset: u32) {338match abi_arg {339&ABIOperand::Stack { offset, ty, .. } => {340assert_eq!(offset, expected_offset);341assert_eq!(ty, expected_ty);342}343reg => panic!("Expected stack argument, got {reg:?}"),344}345}346}347348349