Path: blob/main/winch/codegen/src/isa/x64/abi.rs
1693 views
use super::regs;1use crate::{2RegIndexEnv,3abi::{ABI, ABIOperand, ABIParams, ABIResults, ABISig, ParamsOrReturns, align_to},4codegen::CodeGenError,5isa::{CallingConvention, reg::Reg},6};7use anyhow::{Result, bail};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::{275abi::{ABI, ABIOperand},276isa::{CallingConvention, reg::Reg, x64::regs},277};278279use anyhow::Result;280281use wasmtime_environ::{282WasmFuncType,283WasmValType::{self, *},284};285286#[test]287fn int_abi_sig() -> Result<()> {288let wasm_sig =289WasmFuncType::new([I32, I64, I32, I64, I32, I32, I64, I32].into(), [].into());290291let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;292let params = sig.params;293294match_reg_arg(params.get(0).unwrap(), I32, regs::rdi());295match_reg_arg(params.get(1).unwrap(), I64, regs::rsi());296match_reg_arg(params.get(2).unwrap(), I32, regs::rdx());297match_reg_arg(params.get(3).unwrap(), I64, regs::rcx());298match_reg_arg(params.get(4).unwrap(), I32, regs::r8());299match_reg_arg(params.get(5).unwrap(), I32, regs::r9());300match_stack_arg(params.get(6).unwrap(), I64, 0);301match_stack_arg(params.get(7).unwrap(), I32, 8);302Ok(())303}304305#[test]306fn int_abi_sig_multi_returns() -> Result<()> {307let wasm_sig = WasmFuncType::new(308[I32, I64, I32, I64, I32, I32, I64, I32].into(),309[I32, I32, I32].into(),310);311312let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;313let params = sig.params;314let results = sig.results;315316match_reg_arg(params.get(0).unwrap(), I32, regs::rsi());317match_reg_arg(params.get(1).unwrap(), I64, regs::rdx());318match_reg_arg(params.get(2).unwrap(), I32, regs::rcx());319match_reg_arg(params.get(3).unwrap(), I64, regs::r8());320match_reg_arg(params.get(4).unwrap(), I32, regs::r9());321match_stack_arg(params.get(5).unwrap(), I32, 0);322match_stack_arg(params.get(6).unwrap(), I64, 8);323match_stack_arg(params.get(7).unwrap(), I32, 16);324325match_stack_arg(results.get(0).unwrap(), I32, 4);326match_stack_arg(results.get(1).unwrap(), I32, 0);327match_reg_arg(results.get(2).unwrap(), I32, regs::rax());328Ok(())329}330331#[test]332fn float_abi_sig() -> Result<()> {333let wasm_sig = WasmFuncType::new(334[F32, F64, F32, F64, F32, F32, F64, F32, F64].into(),335[].into(),336);337338let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;339let params = sig.params;340341match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());342match_reg_arg(params.get(1).unwrap(), F64, regs::xmm1());343match_reg_arg(params.get(2).unwrap(), F32, regs::xmm2());344match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());345match_reg_arg(params.get(4).unwrap(), F32, regs::xmm4());346match_reg_arg(params.get(5).unwrap(), F32, regs::xmm5());347match_reg_arg(params.get(6).unwrap(), F64, regs::xmm6());348match_reg_arg(params.get(7).unwrap(), F32, regs::xmm7());349match_stack_arg(params.get(8).unwrap(), F64, 0);350Ok(())351}352353#[test]354fn vector_abi_sig() -> Result<()> {355let wasm_sig = WasmFuncType::new(356[V128, V128, V128, V128, V128, V128, V128, V128, V128, V128].into(),357[].into(),358);359360let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;361let params = sig.params;362363match_reg_arg(params.get(0).unwrap(), V128, regs::xmm0());364match_reg_arg(params.get(1).unwrap(), V128, regs::xmm1());365match_reg_arg(params.get(2).unwrap(), V128, regs::xmm2());366match_reg_arg(params.get(3).unwrap(), V128, regs::xmm3());367match_reg_arg(params.get(4).unwrap(), V128, regs::xmm4());368match_reg_arg(params.get(5).unwrap(), V128, regs::xmm5());369match_reg_arg(params.get(6).unwrap(), V128, regs::xmm6());370match_reg_arg(params.get(7).unwrap(), V128, regs::xmm7());371match_stack_arg(params.get(8).unwrap(), V128, 0);372match_stack_arg(params.get(9).unwrap(), V128, 16);373Ok(())374}375376#[test]377fn vector_abi_sig_multi_returns() -> Result<()> {378let wasm_sig = WasmFuncType::new([].into(), [V128, V128, V128].into());379380let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;381let results = sig.results;382383match_stack_arg(results.get(0).unwrap(), V128, 16);384match_stack_arg(results.get(1).unwrap(), V128, 0);385match_reg_arg(results.get(2).unwrap(), V128, regs::xmm0());386Ok(())387}388389#[test]390fn mixed_abi_sig() -> Result<()> {391let wasm_sig = WasmFuncType::new(392[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),393[].into(),394);395396let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;397let params = sig.params;398399match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());400match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());401match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());402match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());403match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());404match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());405match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());406match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());407match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());408409let wasm_sig = WasmFuncType::new(410[F32, F32, F32, F32, F32, F32, F32, F32, F32, V128].into(),411[V128].into(),412);413414let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;415let params = sig.params;416417match_stack_arg(params.get(8).unwrap(), F32, 0);418match_stack_arg(params.get(9).unwrap(), V128, 16);419Ok(())420}421422#[test]423fn system_v_call_conv() -> Result<()> {424let wasm_sig = WasmFuncType::new(425[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),426[].into(),427);428429let sig = X64ABI::sig(&wasm_sig, &CallingConvention::SystemV)?;430let params = sig.params;431432match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());433match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());434match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());435match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());436match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());437match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());438match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());439match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());440match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());441Ok(())442}443444#[test]445fn fastcall_call_conv() -> Result<()> {446let wasm_sig = WasmFuncType::new(447[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),448[].into(),449);450451let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;452let params = sig.params;453454match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());455match_reg_arg(params.get(1).unwrap(), I32, regs::rdx());456match_reg_arg(params.get(2).unwrap(), I64, regs::r8());457match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());458match_stack_arg(params.get(4).unwrap(), I32, 32);459match_stack_arg(params.get(5).unwrap(), F32, 40);460Ok(())461}462463#[test]464fn fastcall_call_conv_multi_returns() -> Result<()> {465let wasm_sig = WasmFuncType::new(466[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),467[I32, F32, I32, F32, I64].into(),468);469470let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;471let params = sig.params;472let results = sig.results;473474match_reg_arg(params.get(0).unwrap(), F32, regs::xmm1());475match_reg_arg(params.get(1).unwrap(), I32, regs::r8());476match_reg_arg(params.get(2).unwrap(), I64, regs::r9());477// Each argument stack slot is 8 bytes.478match_stack_arg(params.get(3).unwrap(), F64, 32);479match_stack_arg(params.get(4).unwrap(), I32, 40);480match_stack_arg(params.get(5).unwrap(), F32, 48);481482match_reg_arg(results.get(0).unwrap(), I32, regs::rax());483484match_stack_arg(results.get(1).unwrap(), F32, 0);485match_stack_arg(results.get(2).unwrap(), I32, 4);486match_stack_arg(results.get(3).unwrap(), F32, 8);487match_stack_arg(results.get(4).unwrap(), I64, 16);488Ok(())489}490491#[track_caller]492fn match_reg_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_reg: Reg) {493match abi_arg {494&ABIOperand::Reg { reg, ty, .. } => {495assert_eq!(reg, expected_reg);496assert_eq!(ty, expected_ty);497}498stack => panic!("Expected reg argument, got {stack:?}"),499}500}501502#[track_caller]503fn match_stack_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_offset: u32) {504match abi_arg {505&ABIOperand::Stack { offset, ty, .. } => {506assert_eq!(offset, expected_offset);507assert_eq!(ty, expected_ty);508}509reg => panic!("Expected stack argument, got {reg:?}"),510}511}512}513514515