diff options
Diffstat (limited to 'third_party/rust/cranelift-codegen/src/isa/x64/abi.rs')
-rw-r--r-- | third_party/rust/cranelift-codegen/src/isa/x64/abi.rs | 794 |
1 files changed, 794 insertions, 0 deletions
diff --git a/third_party/rust/cranelift-codegen/src/isa/x64/abi.rs b/third_party/rust/cranelift-codegen/src/isa/x64/abi.rs new file mode 100644 index 0000000000..f4c7624f36 --- /dev/null +++ b/third_party/rust/cranelift-codegen/src/isa/x64/abi.rs @@ -0,0 +1,794 @@ +//! Implementation of the standard x64 ABI. + +use crate::ir::types::*; +use crate::ir::{self, types, MemFlags, TrapCode, Type}; +use crate::isa; +use crate::isa::{x64::inst::*, CallConv}; +use crate::machinst::abi_impl::*; +use crate::machinst::*; +use crate::settings; +use crate::{CodegenError, CodegenResult}; +use alloc::boxed::Box; +use alloc::vec::Vec; +use args::*; +use regalloc::{RealReg, Reg, RegClass, Set, Writable}; +use smallvec::{smallvec, SmallVec}; +use std::convert::TryFrom; + +/// This is the limit for the size of argument and return-value areas on the +/// stack. We place a reasonable limit here to avoid integer overflow issues +/// with 32-bit arithmetic: for now, 128 MB. +static STACK_ARG_RET_SIZE_LIMIT: u64 = 128 * 1024 * 1024; + +/// Offset in stack-arg area to callee-TLS slot in Baldrdash-2020 calling convention. +static BALDRDASH_CALLEE_TLS_OFFSET: i64 = 0; +/// Offset in stack-arg area to caller-TLS slot in Baldrdash-2020 calling convention. +static BALDRDASH_CALLER_TLS_OFFSET: i64 = 8; + +/// Try to fill a Baldrdash register, returning it if it was found. +fn try_fill_baldrdash_reg(call_conv: CallConv, param: &ir::AbiParam) -> Option<ABIArg> { + if call_conv.extends_baldrdash() { + match ¶m.purpose { + &ir::ArgumentPurpose::VMContext => { + // This is SpiderMonkey's `WasmTlsReg`. + Some(ABIArg::Reg( + regs::r14().to_real_reg(), + types::I64, + param.extension, + param.purpose, + )) + } + &ir::ArgumentPurpose::SignatureId => { + // This is SpiderMonkey's `WasmTableCallSigReg`. + Some(ABIArg::Reg( + regs::r10().to_real_reg(), + types::I64, + param.extension, + param.purpose, + )) + } + &ir::ArgumentPurpose::CalleeTLS => { + // This is SpiderMonkey's callee TLS slot in the extended frame of Wasm's ABI-2020. + assert!(call_conv == isa::CallConv::Baldrdash2020); + Some(ABIArg::Stack( + BALDRDASH_CALLEE_TLS_OFFSET, + ir::types::I64, + ir::ArgumentExtension::None, + param.purpose, + )) + } + &ir::ArgumentPurpose::CallerTLS => { + // This is SpiderMonkey's caller TLS slot in the extended frame of Wasm's ABI-2020. + assert!(call_conv == isa::CallConv::Baldrdash2020); + Some(ABIArg::Stack( + BALDRDASH_CALLER_TLS_OFFSET, + ir::types::I64, + ir::ArgumentExtension::None, + param.purpose, + )) + } + _ => None, + } + } else { + None + } +} + +/// Support for the x64 ABI from the callee side (within a function body). +pub(crate) type X64ABICallee = ABICalleeImpl<X64ABIMachineSpec>; + +/// Support for the x64 ABI from the caller side (at a callsite). +pub(crate) type X64ABICaller = ABICallerImpl<X64ABIMachineSpec>; + +/// Implementation of ABI primitives for x64. +pub(crate) struct X64ABIMachineSpec; + +impl ABIMachineSpec for X64ABIMachineSpec { + type I = Inst; + + fn word_bits() -> u32 { + 64 + } + + /// Return required stack alignment in bytes. + fn stack_align(_call_conv: isa::CallConv) -> u32 { + 16 + } + + fn compute_arg_locs( + call_conv: isa::CallConv, + params: &[ir::AbiParam], + args_or_rets: ArgsOrRets, + add_ret_area_ptr: bool, + ) -> CodegenResult<(Vec<ABIArg>, i64, Option<usize>)> { + let is_baldrdash = call_conv.extends_baldrdash(); + let has_baldrdash_tls = call_conv == isa::CallConv::Baldrdash2020; + + let mut next_gpr = 0; + let mut next_vreg = 0; + let mut next_stack: u64 = 0; + let mut ret = vec![]; + + if args_or_rets == ArgsOrRets::Args && has_baldrdash_tls { + // Baldrdash ABI-2020 always has two stack-arg slots reserved, for the callee and + // caller TLS-register values, respectively. + next_stack = 16; + } + + for i in 0..params.len() { + // Process returns backward, according to the SpiderMonkey ABI (which we + // adopt internally if `is_baldrdash` is set). + let param = match (args_or_rets, is_baldrdash) { + (ArgsOrRets::Args, _) => ¶ms[i], + (ArgsOrRets::Rets, false) => ¶ms[i], + (ArgsOrRets::Rets, true) => ¶ms[params.len() - 1 - i], + }; + + // Validate "purpose". + match ¶m.purpose { + &ir::ArgumentPurpose::VMContext + | &ir::ArgumentPurpose::Normal + | &ir::ArgumentPurpose::StackLimit + | &ir::ArgumentPurpose::SignatureId + | &ir::ArgumentPurpose::CalleeTLS + | &ir::ArgumentPurpose::CallerTLS => {} + _ => panic!( + "Unsupported argument purpose {:?} in signature: {:?}", + param.purpose, params + ), + } + + let intreg = in_int_reg(param.value_type); + let vecreg = in_vec_reg(param.value_type); + debug_assert!(intreg || vecreg); + debug_assert!(!(intreg && vecreg)); + + let (next_reg, candidate) = if intreg { + let candidate = match args_or_rets { + ArgsOrRets::Args => get_intreg_for_arg_systemv(&call_conv, next_gpr), + ArgsOrRets::Rets => get_intreg_for_retval_systemv(&call_conv, next_gpr, i), + }; + debug_assert!(candidate + .map(|r| r.get_class() == RegClass::I64) + .unwrap_or(true)); + (&mut next_gpr, candidate) + } else { + let candidate = match args_or_rets { + ArgsOrRets::Args => get_fltreg_for_arg_systemv(&call_conv, next_vreg), + ArgsOrRets::Rets => get_fltreg_for_retval_systemv(&call_conv, next_vreg, i), + }; + debug_assert!(candidate + .map(|r| r.get_class() == RegClass::V128) + .unwrap_or(true)); + (&mut next_vreg, candidate) + }; + + if let Some(param) = try_fill_baldrdash_reg(call_conv, param) { + assert!(intreg); + ret.push(param); + } else if let Some(reg) = candidate { + ret.push(ABIArg::Reg( + reg.to_real_reg(), + param.value_type, + param.extension, + param.purpose, + )); + *next_reg += 1; + } else { + // Compute size. Every arg takes a minimum slot of 8 bytes. (16-byte + // stack alignment happens separately after all args.) + let size = (param.value_type.bits() / 8) as u64; + let size = std::cmp::max(size, 8); + // Align. + debug_assert!(size.is_power_of_two()); + next_stack = (next_stack + size - 1) & !(size - 1); + ret.push(ABIArg::Stack( + next_stack as i64, + param.value_type, + param.extension, + param.purpose, + )); + next_stack += size; + } + } + + if args_or_rets == ArgsOrRets::Rets && is_baldrdash { + ret.reverse(); + } + + let extra_arg = if add_ret_area_ptr { + debug_assert!(args_or_rets == ArgsOrRets::Args); + if let Some(reg) = get_intreg_for_arg_systemv(&call_conv, next_gpr) { + ret.push(ABIArg::Reg( + reg.to_real_reg(), + types::I64, + ir::ArgumentExtension::None, + ir::ArgumentPurpose::Normal, + )); + } else { + ret.push(ABIArg::Stack( + next_stack as i64, + types::I64, + ir::ArgumentExtension::None, + ir::ArgumentPurpose::Normal, + )); + next_stack += 8; + } + Some(ret.len() - 1) + } else { + None + }; + + next_stack = (next_stack + 15) & !15; + + // To avoid overflow issues, limit the arg/return size to something reasonable. + if next_stack > STACK_ARG_RET_SIZE_LIMIT { + return Err(CodegenError::ImplLimitExceeded); + } + + Ok((ret, next_stack as i64, extra_arg)) + } + + fn fp_to_arg_offset(call_conv: isa::CallConv, flags: &settings::Flags) -> i64 { + if call_conv.extends_baldrdash() { + let num_words = flags.baldrdash_prologue_words() as i64; + debug_assert!(num_words > 0, "baldrdash must set baldrdash_prologue_words"); + num_words * 8 + } else { + 16 // frame pointer + return address. + } + } + + fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Self::I { + let ext_kind = match ty { + types::B1 + | types::B8 + | types::I8 + | types::B16 + | types::I16 + | types::B32 + | types::I32 => ExtKind::SignExtend, + types::B64 | types::I64 | types::R64 | types::F32 | types::F64 => ExtKind::None, + _ if ty.bytes() == 16 => ExtKind::None, + _ => panic!("load_stack({})", ty), + }; + Inst::load(ty, mem, into_reg, ext_kind) + } + + fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Self::I { + Inst::store(ty, from_reg, mem) + } + + fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Self::I { + Inst::gen_move(to_reg, from_reg, ty) + } + + /// Generate an integer-extend operation. + fn gen_extend( + to_reg: Writable<Reg>, + from_reg: Reg, + is_signed: bool, + from_bits: u8, + to_bits: u8, + ) -> Self::I { + let ext_mode = ExtMode::new(from_bits as u16, to_bits as u16) + .expect(&format!("invalid extension: {} -> {}", from_bits, to_bits)); + if is_signed { + Inst::movsx_rm_r(ext_mode, RegMem::reg(from_reg), to_reg) + } else { + Inst::movzx_rm_r(ext_mode, RegMem::reg(from_reg), to_reg) + } + } + + fn gen_ret() -> Self::I { + Inst::ret() + } + + fn gen_epilogue_placeholder() -> Self::I { + Inst::epilogue_placeholder() + } + + fn gen_add_imm(into_reg: Writable<Reg>, from_reg: Reg, imm: u32) -> SmallVec<[Self::I; 4]> { + let mut ret = SmallVec::new(); + if from_reg != into_reg.to_reg() { + ret.push(Inst::gen_move(into_reg, from_reg, I64)); + } + ret.push(Inst::alu_rmi_r( + true, + AluRmiROpcode::Add, + RegMemImm::imm(imm), + into_reg, + )); + ret + } + + fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallVec<[Self::I; 2]> { + smallvec![ + Inst::cmp_rmi_r(/* bytes = */ 8, RegMemImm::reg(regs::rsp()), limit_reg), + Inst::TrapIf { + // NBE == "> unsigned"; args above are reversed; this tests limit_reg > rsp. + cc: CC::NBE, + trap_code: TrapCode::StackOverflow, + }, + ] + } + + fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Self::I { + let mem: SyntheticAmode = mem.into(); + Inst::lea(mem, into_reg) + } + + fn get_stacklimit_reg() -> Reg { + debug_assert!( + !is_callee_save_systemv(regs::r10().to_real_reg()) + && !is_callee_save_baldrdash(regs::r10().to_real_reg()) + ); + + // As per comment on trait definition, we must return a caller-save + // register here. + regs::r10() + } + + fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, ty: Type) -> Self::I { + // Only ever used for I64s; if that changes, see if the ExtKind below needs to be changed. + assert_eq!(ty, I64); + let simm32 = offset as u32; + let mem = Amode::imm_reg(simm32, base); + Inst::load(ty, mem, into_reg, ExtKind::None) + } + + fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Self::I { + let simm32 = offset as u32; + let mem = Amode::imm_reg(simm32, base); + Inst::store(ty, from_reg, mem) + } + + fn gen_sp_reg_adjust(amount: i32) -> SmallVec<[Self::I; 2]> { + let (alu_op, amount) = if amount >= 0 { + (AluRmiROpcode::Add, amount) + } else { + (AluRmiROpcode::Sub, -amount) + }; + + let amount = amount as u32; + + smallvec![Inst::alu_rmi_r( + true, + alu_op, + RegMemImm::imm(amount), + Writable::from_reg(regs::rsp()), + )] + } + + fn gen_nominal_sp_adj(offset: i32) -> Self::I { + Inst::VirtualSPOffsetAdj { + offset: offset as i64, + } + } + + fn gen_prologue_frame_setup() -> SmallVec<[Self::I; 2]> { + let r_rsp = regs::rsp(); + let r_rbp = regs::rbp(); + let w_rbp = Writable::from_reg(r_rbp); + let mut insts = SmallVec::new(); + // RSP before the call will be 0 % 16. So here, it is 8 % 16. + insts.push(Inst::push64(RegMemImm::reg(r_rbp))); + // RSP is now 0 % 16 + insts.push(Inst::mov_r_r(true, r_rsp, w_rbp)); + insts + } + + fn gen_epilogue_frame_restore() -> SmallVec<[Self::I; 2]> { + let mut insts = SmallVec::new(); + insts.push(Inst::mov_r_r( + true, + regs::rbp(), + Writable::from_reg(regs::rsp()), + )); + insts.push(Inst::pop64(Writable::from_reg(regs::rbp()))); + insts + } + + fn gen_clobber_save( + call_conv: isa::CallConv, + _: &settings::Flags, + clobbers: &Set<Writable<RealReg>>, + fixed_frame_storage_size: u32, + _outgoing_args_size: u32, + ) -> (u64, SmallVec<[Self::I; 16]>) { + let mut insts = SmallVec::new(); + // Find all clobbered registers that are callee-save. These are only I64 + // registers (all XMM registers are caller-save) so we can compute the + // total size of the needed stack space easily. + let clobbered = get_callee_saves(&call_conv, clobbers); + let clobbered_size = 8 * clobbered.len() as u32; + let stack_size = clobbered_size + fixed_frame_storage_size; + // Align to 16 bytes. + let stack_size = (stack_size + 15) & !15; + // Adjust the stack pointer downward with one `sub rsp, IMM` + // instruction. + if stack_size > 0 { + insts.push(Inst::alu_rmi_r( + true, + AluRmiROpcode::Sub, + RegMemImm::imm(stack_size), + Writable::from_reg(regs::rsp()), + )); + } + // Store each clobbered register in order at offsets from RSP. + let mut cur_offset = 0; + for reg in &clobbered { + let r_reg = reg.to_reg(); + match r_reg.get_class() { + RegClass::I64 => { + insts.push(Inst::mov_r_m( + /* bytes = */ 8, + r_reg.to_reg(), + Amode::imm_reg(cur_offset, regs::rsp()), + )); + cur_offset += 8; + } + // No XMM regs are callee-save, so we do not need to implement + // this. + _ => unimplemented!(), + } + } + + (clobbered_size as u64, insts) + } + + fn gen_clobber_restore( + call_conv: isa::CallConv, + flags: &settings::Flags, + clobbers: &Set<Writable<RealReg>>, + _fixed_frame_storage_size: u32, + _outgoing_args_size: u32, + ) -> SmallVec<[Self::I; 16]> { + let mut insts = SmallVec::new(); + + let clobbered = get_callee_saves(&call_conv, clobbers); + let stack_size = 8 * clobbered.len() as u32; + let stack_size = (stack_size + 15) & !15; + + // Restore regs by loading from offsets of RSP. + let mut cur_offset = 0; + for reg in &clobbered { + let rreg = reg.to_reg(); + match rreg.get_class() { + RegClass::I64 => { + insts.push(Inst::mov64_m_r( + Amode::imm_reg(cur_offset, regs::rsp()), + Writable::from_reg(rreg.to_reg()), + )); + cur_offset += 8; + } + _ => unimplemented!(), + } + } + // Adjust RSP back upward. + if stack_size > 0 { + insts.push(Inst::alu_rmi_r( + true, + AluRmiROpcode::Add, + RegMemImm::imm(stack_size), + Writable::from_reg(regs::rsp()), + )); + } + + // If this is Baldrdash-2020, restore the callee (i.e., our) TLS + // register. We may have allocated it for something else and clobbered + // it, but the ABI expects us to leave the TLS register unchanged. + if call_conv == isa::CallConv::Baldrdash2020 { + let off = BALDRDASH_CALLEE_TLS_OFFSET + Self::fp_to_arg_offset(call_conv, flags); + insts.push(Inst::mov64_m_r( + Amode::imm_reg(off as u32, regs::rbp()), + Writable::from_reg(regs::r14()), + )); + } + + insts + } + + /// Generate a call instruction/sequence. + fn gen_call( + dest: &CallDest, + uses: Vec<Reg>, + defs: Vec<Writable<Reg>>, + opcode: ir::Opcode, + tmp: Writable<Reg>, + _callee_conv: isa::CallConv, + _caller_conv: isa::CallConv, + ) -> SmallVec<[(InstIsSafepoint, Self::I); 2]> { + let mut insts = SmallVec::new(); + match dest { + &CallDest::ExtName(ref name, RelocDistance::Near) => { + insts.push(( + InstIsSafepoint::Yes, + Inst::call_known(name.clone(), uses, defs, opcode), + )); + } + &CallDest::ExtName(ref name, RelocDistance::Far) => { + insts.push(( + InstIsSafepoint::No, + Inst::LoadExtName { + dst: tmp, + name: Box::new(name.clone()), + offset: 0, + }, + )); + insts.push(( + InstIsSafepoint::Yes, + Inst::call_unknown(RegMem::reg(tmp.to_reg()), uses, defs, opcode), + )); + } + &CallDest::Reg(reg) => { + insts.push(( + InstIsSafepoint::Yes, + Inst::call_unknown(RegMem::reg(reg), uses, defs, opcode), + )); + } + } + insts + } + + fn get_number_of_spillslots_for_value(rc: RegClass, ty: Type) -> u32 { + // We allocate in terms of 8-byte slots. + match (rc, ty) { + (RegClass::I64, _) => 1, + (RegClass::V128, types::F32) | (RegClass::V128, types::F64) => 1, + (RegClass::V128, _) => 2, + _ => panic!("Unexpected register class!"), + } + } + + fn get_virtual_sp_offset_from_state(s: &<Self::I as MachInstEmit>::State) -> i64 { + s.virtual_sp_offset + } + + fn get_nominal_sp_to_fp(s: &<Self::I as MachInstEmit>::State) -> i64 { + s.nominal_sp_to_fp + } + + fn get_regs_clobbered_by_call(call_conv_of_callee: isa::CallConv) -> Vec<Writable<Reg>> { + let mut caller_saved = vec![ + // Systemv calling convention: + // - GPR: all except RBX, RBP, R12 to R15 (which are callee-saved). + Writable::from_reg(regs::rsi()), + Writable::from_reg(regs::rdi()), + Writable::from_reg(regs::rax()), + Writable::from_reg(regs::rcx()), + Writable::from_reg(regs::rdx()), + Writable::from_reg(regs::r8()), + Writable::from_reg(regs::r9()), + Writable::from_reg(regs::r10()), + Writable::from_reg(regs::r11()), + // - XMM: all the registers! + Writable::from_reg(regs::xmm0()), + Writable::from_reg(regs::xmm1()), + Writable::from_reg(regs::xmm2()), + Writable::from_reg(regs::xmm3()), + Writable::from_reg(regs::xmm4()), + Writable::from_reg(regs::xmm5()), + Writable::from_reg(regs::xmm6()), + Writable::from_reg(regs::xmm7()), + Writable::from_reg(regs::xmm8()), + Writable::from_reg(regs::xmm9()), + Writable::from_reg(regs::xmm10()), + Writable::from_reg(regs::xmm11()), + Writable::from_reg(regs::xmm12()), + Writable::from_reg(regs::xmm13()), + Writable::from_reg(regs::xmm14()), + Writable::from_reg(regs::xmm15()), + ]; + + if call_conv_of_callee.extends_baldrdash() { + caller_saved.push(Writable::from_reg(regs::r12())); + caller_saved.push(Writable::from_reg(regs::r13())); + // Not r14; implicitly preserved in the entry. + caller_saved.push(Writable::from_reg(regs::r15())); + caller_saved.push(Writable::from_reg(regs::rbx())); + } + + caller_saved + } +} + +impl From<StackAMode> for SyntheticAmode { + fn from(amode: StackAMode) -> Self { + // We enforce a 128 MB stack-frame size limit above, so these + // `expect()`s should never fail. + match amode { + StackAMode::FPOffset(off, _ty) => { + let off = i32::try_from(off) + .expect("Offset in FPOffset is greater than 2GB; should hit impl limit first"); + let simm32 = off as u32; + SyntheticAmode::Real(Amode::ImmReg { + simm32, + base: regs::rbp(), + flags: MemFlags::trusted(), + }) + } + StackAMode::NominalSPOffset(off, _ty) => { + let off = i32::try_from(off).expect( + "Offset in NominalSPOffset is greater than 2GB; should hit impl limit first", + ); + let simm32 = off as u32; + SyntheticAmode::nominal_sp_offset(simm32) + } + StackAMode::SPOffset(off, _ty) => { + let off = i32::try_from(off) + .expect("Offset in SPOffset is greater than 2GB; should hit impl limit first"); + let simm32 = off as u32; + SyntheticAmode::Real(Amode::ImmReg { + simm32, + base: regs::rsp(), + flags: MemFlags::trusted(), + }) + } + } + } +} + +fn in_int_reg(ty: types::Type) -> bool { + match ty { + types::I8 + | types::I16 + | types::I32 + | types::I64 + | types::B1 + | types::B8 + | types::B16 + | types::B32 + | types::B64 + | types::R64 => true, + types::R32 => panic!("unexpected 32-bits refs on x64!"), + _ => false, + } +} + +fn in_vec_reg(ty: types::Type) -> bool { + match ty { + types::F32 | types::F64 => true, + _ if ty.is_vector() => true, + _ => false, + } +} + +fn get_intreg_for_arg_systemv(call_conv: &CallConv, idx: usize) -> Option<Reg> { + match call_conv { + CallConv::Fast + | CallConv::Cold + | CallConv::SystemV + | CallConv::BaldrdashSystemV + | CallConv::Baldrdash2020 => {} + _ => panic!("int args only supported for SysV calling convention"), + }; + match idx { + 0 => Some(regs::rdi()), + 1 => Some(regs::rsi()), + 2 => Some(regs::rdx()), + 3 => Some(regs::rcx()), + 4 => Some(regs::r8()), + 5 => Some(regs::r9()), + _ => None, + } +} + +fn get_fltreg_for_arg_systemv(call_conv: &CallConv, idx: usize) -> Option<Reg> { + match call_conv { + CallConv::Fast + | CallConv::Cold + | CallConv::SystemV + | CallConv::BaldrdashSystemV + | CallConv::Baldrdash2020 => {} + _ => panic!("float args only supported for SysV calling convention"), + }; + match idx { + 0 => Some(regs::xmm0()), + 1 => Some(regs::xmm1()), + 2 => Some(regs::xmm2()), + 3 => Some(regs::xmm3()), + 4 => Some(regs::xmm4()), + 5 => Some(regs::xmm5()), + 6 => Some(regs::xmm6()), + 7 => Some(regs::xmm7()), + _ => None, + } +} + +fn get_intreg_for_retval_systemv( + call_conv: &CallConv, + intreg_idx: usize, + retval_idx: usize, +) -> Option<Reg> { + match call_conv { + CallConv::Fast | CallConv::Cold | CallConv::SystemV => match intreg_idx { + 0 => Some(regs::rax()), + 1 => Some(regs::rdx()), + _ => None, + }, + CallConv::BaldrdashSystemV | CallConv::Baldrdash2020 => { + if intreg_idx == 0 && retval_idx == 0 { + Some(regs::rax()) + } else { + None + } + } + CallConv::WindowsFastcall | CallConv::BaldrdashWindows | CallConv::Probestack => todo!(), + } +} + +fn get_fltreg_for_retval_systemv( + call_conv: &CallConv, + fltreg_idx: usize, + retval_idx: usize, +) -> Option<Reg> { + match call_conv { + CallConv::Fast | CallConv::Cold | CallConv::SystemV => match fltreg_idx { + 0 => Some(regs::xmm0()), + 1 => Some(regs::xmm1()), + _ => None, + }, + CallConv::BaldrdashSystemV | CallConv::Baldrdash2020 => { + if fltreg_idx == 0 && retval_idx == 0 { + Some(regs::xmm0()) + } else { + None + } + } + CallConv::WindowsFastcall | CallConv::BaldrdashWindows | CallConv::Probestack => todo!(), + } +} + +fn is_callee_save_systemv(r: RealReg) -> bool { + use regs::*; + match r.get_class() { + RegClass::I64 => match r.get_hw_encoding() as u8 { + ENC_RBX | ENC_RBP | ENC_R12 | ENC_R13 | ENC_R14 | ENC_R15 => true, + _ => false, + }, + RegClass::V128 => false, + _ => unimplemented!(), + } +} + +fn is_callee_save_baldrdash(r: RealReg) -> bool { + use regs::*; + match r.get_class() { + RegClass::I64 => { + if r.get_hw_encoding() as u8 == ENC_R14 { + // r14 is the WasmTlsReg and is preserved implicitly. + false + } else { + // Defer to native for the other ones. + is_callee_save_systemv(r) + } + } + RegClass::V128 => false, + _ => unimplemented!(), + } +} + +fn get_callee_saves(call_conv: &CallConv, regs: &Set<Writable<RealReg>>) -> Vec<Writable<RealReg>> { + let mut regs: Vec<Writable<RealReg>> = match call_conv { + CallConv::BaldrdashSystemV | CallConv::Baldrdash2020 => regs + .iter() + .cloned() + .filter(|r| is_callee_save_baldrdash(r.to_reg())) + .collect(), + CallConv::BaldrdashWindows => { + todo!("baldrdash windows"); + } + CallConv::Fast | CallConv::Cold | CallConv::SystemV => regs + .iter() + .cloned() + .filter(|r| is_callee_save_systemv(r.to_reg())) + .collect(), + CallConv::WindowsFastcall => todo!("windows fastcall"), + CallConv::Probestack => todo!("probestack?"), + }; + // Sort registers for deterministic code output. We can do an unstable sort because the + // registers will be unique (there are no dups). + regs.sort_unstable_by_key(|r| r.to_reg().get_index()); + regs +} |