Path: blob/main/winch/codegen/src/isa/aarch64/abi.rs
1692 views
use super::regs;1use crate::RegIndexEnv;2use crate::abi::{ABI, ABIOperand, ABIParams, ABIResults, ABISig, ParamsOrReturns, align_to};3use crate::codegen::CodeGenError;4use crate::isa::{CallingConvention, reg::Reg};5use anyhow::{Result, bail};6use wasmtime_environ::{WasmHeapType, WasmValType};78#[derive(Default)]9pub(crate) struct Aarch64ABI;1011/// The x28 register serves as the shadow stack pointer. For further details,12/// please refer to [`crate::isa::aarch64::regs::shadow_sp`].13///14/// This register is designated as callee-saved to prevent corruption during15/// function calls. This is especially important for Wasm-to-Wasm calls in16/// Winch-generated code, as Winch's default calling convention does not define17/// any callee-saved registers.18///19/// Note that 16 bytes are used to save the shadow stack pointer register even20/// though only 8 are needed. 16 is used for simplicity to ensure that the21/// 16-byte alignment requirement for memory addressing is met at the function's22/// prologue and epilogue.23pub const SHADOW_STACK_POINTER_SLOT_SIZE: u8 = 16;2425impl ABI for Aarch64ABI {26// TODO change to 16 once SIMD is supported27fn stack_align() -> u8 {28829}3031fn call_stack_align() -> u8 {321633}3435fn arg_base_offset() -> u8 {36// Two 8-byte slots:37// * One for link register38// * One for the frame pointer39//40// ┌──────────┬───────── Argument base41// │ LR │42// │ │43// ├──────────┼44// │ │45// │ FP │46// └──────────┴ -> 16471648}4950fn initial_frame_size() -> u8 {51// The initial frame size is composed of 4 8-byte slots:52// * Two slots for the link register and the frame pointer. See53// [`Self::arg_base_offset`]54// * Two for the shadow stack pointer register. See55// [`SHADOW_STACK_POINTER_SIZE`]56//57// ┌──────────┬───────── Argument base58// │ LR │59// │ │60// ├──────────┼61// │ │62// │ FP │63// ┌──────────┬64// │ │65// │ │66// │ │67// │ x28 │68// └──────────┴ -> 3269Self::arg_base_offset() + SHADOW_STACK_POINTER_SLOT_SIZE70}7172fn word_bits() -> u8 {736474}7576fn sig_from(77params: &[WasmValType],78returns: &[WasmValType],79call_conv: &CallingConvention,80) -> Result<ABISig> {81assert!(call_conv.is_apple_aarch64() || call_conv.is_default());82// The first element tracks the general purpose register index, capped at 7 (x0-x7).83// The second element tracks the floating point register index, capped at 7 (v0-v7).84// Follows85// https://github.com/ARM-software/abi-aa/blob/2021Q1/aapcs64/aapcs64.rst#64parameter-passing86let mut params_index_env = RegIndexEnv::with_limits_per_class(8, 8);87let results = Self::abi_results(returns, call_conv)?;88let params =89ABIParams::from::<_, Self>(params, 0, results.on_stack(), |ty, stack_offset| {90Self::to_abi_operand(91ty,92stack_offset,93&mut params_index_env,94call_conv,95ParamsOrReturns::Params,96)97})?;9899Ok(ABISig::new(*call_conv, params, results))100}101102fn abi_results(returns: &[WasmValType], call_conv: &CallingConvention) -> Result<ABIResults> {103assert!(call_conv.is_apple_aarch64() || call_conv.is_default());104// Use absolute count for results given that for Winch's105// default CallingConvention only one register is used for results106// independent of the register class.107// In the case of 2+ results, the rest are passed in the stack,108// similar to how Wasmtime handles multi-value returns.109let mut returns_index_env = RegIndexEnv::with_absolute_limit(1);110111ABIResults::from(returns, call_conv, |ty, stack_offset| {112Self::to_abi_operand(113ty,114stack_offset,115&mut returns_index_env,116call_conv,117ParamsOrReturns::Returns,118)119})120}121122fn vmctx_reg() -> Reg {123regs::xreg(9)124}125126fn stack_slot_size() -> u8 {127Self::word_bytes()128}129130fn sizeof(ty: &WasmValType) -> u8 {131match ty {132WasmValType::Ref(rt) => match rt.heap_type {133WasmHeapType::Func => Self::word_bytes(),134ht => unimplemented!("Support for WasmHeapType: {ht}"),135},136WasmValType::F64 | WasmValType::I64 => Self::word_bytes(),137WasmValType::F32 | WasmValType::I32 => Self::word_bytes() / 2,138WasmValType::V128 => Self::word_bytes() * 2,139}140}141}142143impl Aarch64ABI {144fn to_abi_operand(145wasm_arg: &WasmValType,146stack_offset: u32,147index_env: &mut RegIndexEnv,148call_conv: &CallingConvention,149params_or_returns: ParamsOrReturns,150) -> Result<(ABIOperand, u32)> {151let (reg, ty) = match wasm_arg {152ty @ (WasmValType::I32 | WasmValType::I64) => {153(index_env.next_gpr().map(regs::xreg), ty)154}155156ty @ (WasmValType::F32 | WasmValType::F64) => {157(index_env.next_fpr().map(regs::vreg), ty)158}159160ty @ WasmValType::Ref(rt) => match rt.heap_type {161WasmHeapType::Func | WasmHeapType::Extern => {162(index_env.next_gpr().map(regs::xreg), ty)163}164_ => bail!(CodeGenError::unsupported_wasm_type()),165},166167_ => bail!(CodeGenError::unsupported_wasm_type()),168};169170let ty_size = <Self as ABI>::sizeof(wasm_arg);171let default = || {172let arg = ABIOperand::stack_offset(stack_offset, *ty, ty_size as u32);173let slot_size = Self::stack_slot_size();174// Stack slots for parameters are aligned to a fixed slot size,175// in the case of Aarch64, 8 bytes.176// For the non-default calling convention, stack slots for177// return values are type-sized aligned.178// For the default calling convention, we don't type-size align,179// given that results on the stack must match spills generated180// from within the compiler, which are not type-size aligned.181let next_stack = if params_or_returns == ParamsOrReturns::Params {182align_to(stack_offset, slot_size as u32) + (slot_size as u32)183} else if call_conv.is_default() {184stack_offset + (ty_size as u32)185} else {186align_to(stack_offset, ty_size as u32) + (ty_size as u32)187};188(arg, next_stack)189};190Ok(reg.map_or_else(default, |reg| {191(ABIOperand::reg(reg, *ty, ty_size as u32), stack_offset)192}))193}194}195196#[cfg(test)]197mod tests {198use super::Aarch64ABI;199use crate::{200abi::{ABI, ABIOperand},201isa::CallingConvention,202isa::aarch64::regs,203isa::reg::Reg,204};205use wasmtime_environ::{206WasmFuncType,207WasmValType::{self, *},208};209210use anyhow::Result;211212#[test]213fn xreg_abi_sig() -> Result<()> {214let wasm_sig = WasmFuncType::new(215[I32, I64, I32, I64, I32, I32, I64, I32, I64].into(),216[].into(),217);218219let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;220let params = sig.params;221222match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(0));223match_reg_arg(params.get(1).unwrap(), I64, regs::xreg(1));224match_reg_arg(params.get(2).unwrap(), I32, regs::xreg(2));225match_reg_arg(params.get(3).unwrap(), I64, regs::xreg(3));226match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(4));227match_reg_arg(params.get(5).unwrap(), I32, regs::xreg(5));228match_reg_arg(params.get(6).unwrap(), I64, regs::xreg(6));229match_reg_arg(params.get(7).unwrap(), I32, regs::xreg(7));230match_stack_arg(params.get(8).unwrap(), I64, 0);231Ok(())232}233234#[test]235fn vreg_abi_sig() -> Result<()> {236let wasm_sig = WasmFuncType::new(237[F32, F64, F32, F64, F32, F32, F64, F32, F64].into(),238[].into(),239);240241let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;242let params = sig.params;243244match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));245match_reg_arg(params.get(1).unwrap(), F64, regs::vreg(1));246match_reg_arg(params.get(2).unwrap(), F32, regs::vreg(2));247match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(3));248match_reg_arg(params.get(4).unwrap(), F32, regs::vreg(4));249match_reg_arg(params.get(5).unwrap(), F32, regs::vreg(5));250match_reg_arg(params.get(6).unwrap(), F64, regs::vreg(6));251match_reg_arg(params.get(7).unwrap(), F32, regs::vreg(7));252match_stack_arg(params.get(8).unwrap(), F64, 0);253Ok(())254}255256#[test]257fn mixed_abi_sig() -> Result<()> {258let wasm_sig = WasmFuncType::new(259[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),260[].into(),261);262263let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;264let params = sig.params;265266match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));267match_reg_arg(params.get(1).unwrap(), I32, regs::xreg(0));268match_reg_arg(params.get(2).unwrap(), I64, regs::xreg(1));269match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(1));270match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(2));271match_reg_arg(params.get(5).unwrap(), F32, regs::vreg(2));272match_reg_arg(params.get(6).unwrap(), F64, regs::vreg(3));273match_reg_arg(params.get(7).unwrap(), F32, regs::vreg(4));274match_reg_arg(params.get(8).unwrap(), F64, regs::vreg(5));275Ok(())276}277278#[test]279fn int_abi_sig_multi_returns() -> Result<()> {280let wasm_sig = WasmFuncType::new(281[I32, I64, I32, I64, I32, I32].into(),282[I32, I32, I32].into(),283);284285let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;286let params = sig.params;287let results = sig.results;288289match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(1));290match_reg_arg(params.get(1).unwrap(), I64, regs::xreg(2));291match_reg_arg(params.get(2).unwrap(), I32, regs::xreg(3));292match_reg_arg(params.get(3).unwrap(), I64, regs::xreg(4));293match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(5));294match_reg_arg(params.get(5).unwrap(), I32, regs::xreg(6));295296match_stack_arg(results.get(0).unwrap(), I32, 4);297match_stack_arg(results.get(1).unwrap(), I32, 0);298match_reg_arg(results.get(2).unwrap(), I32, regs::xreg(0));299Ok(())300}301302#[test]303fn mixed_abi_sig_multi_returns() -> Result<()> {304let wasm_sig = WasmFuncType::new(305[F32, I32, I64, F64, I32].into(),306[I32, F32, I32, F32, I64].into(),307);308309let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;310let params = sig.params;311let results = sig.results;312313match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));314match_reg_arg(params.get(1).unwrap(), I32, regs::xreg(1));315match_reg_arg(params.get(2).unwrap(), I64, regs::xreg(2));316match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(1));317match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(3));318319match_stack_arg(results.get(0).unwrap(), I32, 12);320match_stack_arg(results.get(1).unwrap(), F32, 8);321match_stack_arg(results.get(2).unwrap(), I32, 4);322match_stack_arg(results.get(3).unwrap(), F32, 0);323match_reg_arg(results.get(4).unwrap(), I64, regs::xreg(0));324Ok(())325}326327#[track_caller]328fn match_reg_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_reg: Reg) {329match abi_arg {330&ABIOperand::Reg { reg, ty, .. } => {331assert_eq!(reg, expected_reg);332assert_eq!(ty, expected_ty);333}334stack => panic!("Expected reg argument, got {stack:?}"),335}336}337338#[track_caller]339fn match_stack_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_offset: u32) {340match abi_arg {341&ABIOperand::Stack { offset, ty, .. } => {342assert_eq!(offset, expected_offset);343assert_eq!(ty, expected_ty);344}345reg => panic!("Expected stack argument, got {reg:?}"),346}347}348}349350351