Path: blob/main/winch/codegen/src/isa/x64/abi.rs
3070 views
use super::regs;1use crate::{2RegIndexEnv, Result,3abi::{ABI, ABIOperand, ABIParams, ABIResults, ABISig, ParamsOrReturns, align_to},4bail,5codegen::CodeGenError,6isa::{CallingConvention, reg::Reg},7};8use wasmtime_environ::{WasmHeapType, WasmValType};910#[derive(Default)]11pub(crate) struct X64ABI;1213impl ABI for X64ABI {14fn stack_align() -> u8 {151616}1718fn call_stack_align() -> u8 {191620}2122fn arg_base_offset() -> u8 {23// Two 8-byte slots, one for the return address and another24// one for the frame pointer.25// ┌──────────┬───────── Argument base26// │ Ret │27// │ Addr │28// ├──────────┼29// │ │30// │ FP │31// └──────────┴321633}3435fn initial_frame_size() -> u8 {36// The initial frame size is equal to the space allocated to save the37// return address and the frame pointer.38Self::arg_base_offset()39}4041fn word_bits() -> u8 {426443}4445fn sig_from(46params: &[WasmValType],47returns: &[WasmValType],48call_conv: &CallingConvention,49) -> Result<ABISig> {50assert!(call_conv.is_fastcall() || call_conv.is_systemv() || call_conv.is_default());51let is_fastcall = call_conv.is_fastcall();52// In the fastcall calling convention, the callee gets a contiguous53// stack area of 32 bytes (4 register arguments) just before its frame.54// See55// https://learn.microsoft.com/en-us/cpp/build/stack-usage?view=msvc-170#stack-allocation56let (params_stack_offset, mut params_index_env) = if is_fastcall {57(32, RegIndexEnv::with_absolute_limit(4))58} else {59(0, RegIndexEnv::with_limits_per_class(6, 8))60};6162let results = Self::abi_results(returns, call_conv)?;63let params = ABIParams::from::<_, Self>(64params,65params_stack_offset,66results.on_stack(),67|ty, stack_offset| {68Self::to_abi_operand(69ty,70stack_offset,71&mut params_index_env,72call_conv,73ParamsOrReturns::Params,74)75},76)?;7778Ok(ABISig::new(*call_conv, params, results))79}8081fn abi_results(returns: &[WasmValType], call_conv: &CallingConvention) -> Result<ABIResults> {82assert!(call_conv.is_default() || call_conv.is_fastcall() || call_conv.is_systemv());83// Use absolute count for results given that for Winch's84// default CallingConvention only one register is used for results85// independent of the register class.86// In the case of 2+ results, the rest are passed in the stack,87// similar to how Wasmtime handles multi-value returns.88let mut results_index_env = RegIndexEnv::with_absolute_limit(1);89ABIResults::from(returns, call_conv, |ty, offset| {90Self::to_abi_operand(91ty,92offset,93&mut results_index_env,94call_conv,95ParamsOrReturns::Returns,96)97})98}99100fn vmctx_reg() -> Reg {101regs::vmctx()102}103104fn stack_slot_size() -> u8 {105// Winch default calling convention follows SysV calling convention so106// we use one 8 byte slot for values that are smaller or equal to 8107// bytes in size and 2 8 byte slots for values that are 128 bits.108// See Section 3.2.3 in109// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf for further110// details.111Self::word_bytes()112}113114fn sizeof(ty: &WasmValType) -> u8 {115match ty {116WasmValType::Ref(rt) => match rt.heap_type {117WasmHeapType::Func | WasmHeapType::Extern => Self::word_bytes(),118ht => unimplemented!("Support for WasmHeapType: {ht}"),119},120WasmValType::F64 | WasmValType::I64 => Self::word_bytes(),121WasmValType::F32 | WasmValType::I32 => Self::word_bytes() / 2,122WasmValType::V128 => Self::word_bytes() * 2,123}124}125}126127impl X64ABI {128fn to_abi_operand(129wasm_arg: &WasmValType,130stack_offset: u32,131index_env: &mut RegIndexEnv,132call_conv: &CallingConvention,133params_or_returns: ParamsOrReturns,134) -> Result<(ABIOperand, u32)> {135let (reg, ty) = match wasm_arg {136ty @ WasmValType::Ref(rt) => match rt.heap_type {137WasmHeapType::Func | WasmHeapType::Extern => (138Self::int_reg_for(index_env.next_gpr(), call_conv, params_or_returns),139ty,140),141_ => bail!(CodeGenError::unsupported_wasm_type()),142},143144ty @ (WasmValType::I32 | WasmValType::I64) => (145Self::int_reg_for(index_env.next_gpr(), call_conv, params_or_returns),146ty,147),148149// v128 also uses an XMM register (that is, an fpr).150ty @ (WasmValType::F32 | WasmValType::F64 | WasmValType::V128) => (151Self::float_reg_for(index_env.next_fpr(), call_conv, params_or_returns),152ty,153),154};155156let ty_size = <Self as ABI>::sizeof(wasm_arg);157let default = || {158let slot_size = Self::stack_slot_size();159if params_or_returns == ParamsOrReturns::Params {160// Stack slots for parameters are aligned to a fixed slot size,161// 8 bytes if the type size is 8 or less and type-sized aligned162// if the type size is greater than 8 bytes.163let alignment = std::cmp::max(ty_size, slot_size);164let offset = align_to(stack_offset, u32::from(alignment));165let arg = ABIOperand::stack_offset(offset, *ty, u32::from(ty_size));166(arg, offset + u32::from(alignment))167} else {168// For the default calling convention, we don't type-size align,169// given that results on the stack must match spills generated170// from within the compiler, which are not type-size aligned.171// In all other cases the results are type-sized aligned.172if call_conv.is_default() {173let arg = ABIOperand::stack_offset(stack_offset, *ty, u32::from(ty_size));174(arg, stack_offset + (ty_size as u32))175} else {176let offset = align_to(stack_offset, u32::from(ty_size));177(178ABIOperand::stack_offset(offset, *ty, u32::from(ty_size)),179offset + u32::from(ty_size),180)181}182}183};184185Ok(reg.map_or_else(default, |reg| {186(ABIOperand::reg(reg, *ty, ty_size as u32), stack_offset)187}))188}189190fn int_reg_for(191index: Option<u8>,192call_conv: &CallingConvention,193params_or_returns: ParamsOrReturns,194) -> Option<Reg> {195use ParamsOrReturns::*;196197let index = match index {198None => return None,199Some(index) => index,200};201202if call_conv.is_fastcall() {203return match (index, params_or_returns) {204(0, Params) => Some(regs::rcx()),205(1, Params) => Some(regs::rdx()),206(2, Params) => Some(regs::r8()),207(3, Params) => Some(regs::r9()),208(0, Returns) => Some(regs::rax()),209_ => None,210};211}212213if call_conv.is_systemv() || call_conv.is_default() {214return match (index, params_or_returns) {215(0, Params) => Some(regs::rdi()),216(1, Params) => Some(regs::rsi()),217(2, Params) => Some(regs::rdx()),218(3, Params) => Some(regs::rcx()),219(4, Params) => Some(regs::r8()),220(5, Params) => Some(regs::r9()),221(0, Returns) => Some(regs::rax()),222_ => None,223};224}225226None227}228229fn float_reg_for(230index: Option<u8>,231call_conv: &CallingConvention,232params_or_returns: ParamsOrReturns,233) -> Option<Reg> {234use ParamsOrReturns::*;235236let index = match index {237None => return None,238Some(index) => index,239};240241if call_conv.is_fastcall() {242return match (index, params_or_returns) {243(0, Params) => Some(regs::xmm0()),244(1, Params) => Some(regs::xmm1()),245(2, Params) => Some(regs::xmm2()),246(3, Params) => Some(regs::xmm3()),247(0, Returns) => Some(regs::xmm0()),248_ => None,249};250}251252if call_conv.is_systemv() || call_conv.is_default() {253return match (index, params_or_returns) {254(0, Params) => Some(regs::xmm0()),255(1, Params) => Some(regs::xmm1()),256(2, Params) => Some(regs::xmm2()),257(3, Params) => Some(regs::xmm3()),258(4, Params) => Some(regs::xmm4()),259(5, Params) => Some(regs::xmm5()),260(6, Params) => Some(regs::xmm6()),261(7, Params) => Some(regs::xmm7()),262(0, Returns) => Some(regs::xmm0()),263_ => None,264};265}266267None268}269}270271#[cfg(test)]272mod tests {273use super::X64ABI;274use crate::{275Result,276abi::{ABI, ABIOperand},277isa::{CallingConvention, reg::Reg, x64::regs},278};279use wasmtime_environ::{280WasmFuncType,281WasmValType::{self, *},282};283284#[test]285fn int_abi_sig() -> Result<()> {286let wasm_sig =287WasmFuncType::new([I32, I64, I32, I64, I32, I32, I64, I32].into(), [].into());288289let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;290let params = sig.params;291292match_reg_arg(params.get(0).unwrap(), I32, regs::rdi());293match_reg_arg(params.get(1).unwrap(), I64, regs::rsi());294match_reg_arg(params.get(2).unwrap(), I32, regs::rdx());295match_reg_arg(params.get(3).unwrap(), I64, regs::rcx());296match_reg_arg(params.get(4).unwrap(), I32, regs::r8());297match_reg_arg(params.get(5).unwrap(), I32, regs::r9());298match_stack_arg(params.get(6).unwrap(), I64, 0);299match_stack_arg(params.get(7).unwrap(), I32, 8);300Ok(())301}302303#[test]304fn int_abi_sig_multi_returns() -> Result<()> {305let wasm_sig = WasmFuncType::new(306[I32, I64, I32, I64, I32, I32, I64, I32].into(),307[I32, I32, I32].into(),308);309310let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;311let params = sig.params;312let results = sig.results;313314match_reg_arg(params.get(0).unwrap(), I32, regs::rsi());315match_reg_arg(params.get(1).unwrap(), I64, regs::rdx());316match_reg_arg(params.get(2).unwrap(), I32, regs::rcx());317match_reg_arg(params.get(3).unwrap(), I64, regs::r8());318match_reg_arg(params.get(4).unwrap(), I32, regs::r9());319match_stack_arg(params.get(5).unwrap(), I32, 0);320match_stack_arg(params.get(6).unwrap(), I64, 8);321match_stack_arg(params.get(7).unwrap(), I32, 16);322323match_stack_arg(results.get(0).unwrap(), I32, 4);324match_stack_arg(results.get(1).unwrap(), I32, 0);325match_reg_arg(results.get(2).unwrap(), I32, regs::rax());326Ok(())327}328329#[test]330fn float_abi_sig() -> Result<()> {331let wasm_sig = WasmFuncType::new(332[F32, F64, F32, F64, F32, F32, F64, F32, F64].into(),333[].into(),334);335336let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;337let params = sig.params;338339match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());340match_reg_arg(params.get(1).unwrap(), F64, regs::xmm1());341match_reg_arg(params.get(2).unwrap(), F32, regs::xmm2());342match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());343match_reg_arg(params.get(4).unwrap(), F32, regs::xmm4());344match_reg_arg(params.get(5).unwrap(), F32, regs::xmm5());345match_reg_arg(params.get(6).unwrap(), F64, regs::xmm6());346match_reg_arg(params.get(7).unwrap(), F32, regs::xmm7());347match_stack_arg(params.get(8).unwrap(), F64, 0);348Ok(())349}350351#[test]352fn vector_abi_sig() -> Result<()> {353let wasm_sig = WasmFuncType::new(354[V128, V128, V128, V128, V128, V128, V128, V128, V128, V128].into(),355[].into(),356);357358let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;359let params = sig.params;360361match_reg_arg(params.get(0).unwrap(), V128, regs::xmm0());362match_reg_arg(params.get(1).unwrap(), V128, regs::xmm1());363match_reg_arg(params.get(2).unwrap(), V128, regs::xmm2());364match_reg_arg(params.get(3).unwrap(), V128, regs::xmm3());365match_reg_arg(params.get(4).unwrap(), V128, regs::xmm4());366match_reg_arg(params.get(5).unwrap(), V128, regs::xmm5());367match_reg_arg(params.get(6).unwrap(), V128, regs::xmm6());368match_reg_arg(params.get(7).unwrap(), V128, regs::xmm7());369match_stack_arg(params.get(8).unwrap(), V128, 0);370match_stack_arg(params.get(9).unwrap(), V128, 16);371Ok(())372}373374#[test]375fn vector_abi_sig_multi_returns() -> Result<()> {376let wasm_sig = WasmFuncType::new([].into(), [V128, V128, V128].into());377378let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;379let results = sig.results;380381match_stack_arg(results.get(0).unwrap(), V128, 16);382match_stack_arg(results.get(1).unwrap(), V128, 0);383match_reg_arg(results.get(2).unwrap(), V128, regs::xmm0());384Ok(())385}386387#[test]388fn mixed_abi_sig() -> Result<()> {389let wasm_sig = WasmFuncType::new(390[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),391[].into(),392);393394let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;395let params = sig.params;396397match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());398match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());399match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());400match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());401match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());402match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());403match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());404match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());405match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());406407let wasm_sig = WasmFuncType::new(408[F32, F32, F32, F32, F32, F32, F32, F32, F32, V128].into(),409[V128].into(),410);411412let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;413let params = sig.params;414415match_stack_arg(params.get(8).unwrap(), F32, 0);416match_stack_arg(params.get(9).unwrap(), V128, 16);417Ok(())418}419420#[test]421fn system_v_call_conv() -> Result<()> {422let wasm_sig = WasmFuncType::new(423[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),424[].into(),425);426427let sig = X64ABI::sig(&wasm_sig, &CallingConvention::SystemV)?;428let params = sig.params;429430match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());431match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());432match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());433match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());434match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());435match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());436match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());437match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());438match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());439Ok(())440}441442#[test]443fn fastcall_call_conv() -> Result<()> {444let wasm_sig = WasmFuncType::new(445[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),446[].into(),447);448449let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;450let params = sig.params;451452match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());453match_reg_arg(params.get(1).unwrap(), I32, regs::rdx());454match_reg_arg(params.get(2).unwrap(), I64, regs::r8());455match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());456match_stack_arg(params.get(4).unwrap(), I32, 32);457match_stack_arg(params.get(5).unwrap(), F32, 40);458Ok(())459}460461#[test]462fn fastcall_call_conv_multi_returns() -> Result<()> {463let wasm_sig = WasmFuncType::new(464[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),465[I32, F32, I32, F32, I64].into(),466);467468let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;469let params = sig.params;470let results = sig.results;471472match_reg_arg(params.get(0).unwrap(), F32, regs::xmm1());473match_reg_arg(params.get(1).unwrap(), I32, regs::r8());474match_reg_arg(params.get(2).unwrap(), I64, regs::r9());475// Each argument stack slot is 8 bytes.476match_stack_arg(params.get(3).unwrap(), F64, 32);477match_stack_arg(params.get(4).unwrap(), I32, 40);478match_stack_arg(params.get(5).unwrap(), F32, 48);479480match_reg_arg(results.get(0).unwrap(), I32, regs::rax());481482match_stack_arg(results.get(1).unwrap(), F32, 0);483match_stack_arg(results.get(2).unwrap(), I32, 4);484match_stack_arg(results.get(3).unwrap(), F32, 8);485match_stack_arg(results.get(4).unwrap(), I64, 16);486Ok(())487}488489#[track_caller]490fn match_reg_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_reg: Reg) {491match abi_arg {492&ABIOperand::Reg { reg, ty, .. } => {493assert_eq!(reg, expected_reg);494assert_eq!(ty, expected_ty);495}496stack => panic!("Expected reg argument, got {stack:?}"),497}498}499500#[track_caller]501fn match_stack_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_offset: u32) {502match abi_arg {503&ABIOperand::Stack { offset, ty, .. } => {504assert_eq!(offset, expected_offset);505assert_eq!(ty, expected_ty);506}507reg => panic!("Expected stack argument, got {reg:?}"),508}509}510}511512513