summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cranelift-codegen/src/isa/x64/abi.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/cranelift-codegen/src/isa/x64/abi.rs
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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.rs794
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 &param.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, _) => &params[i],
+ (ArgsOrRets::Rets, false) => &params[i],
+ (ArgsOrRets::Rets, true) => &params[params.len() - 1 - i],
+ };
+
+ // Validate "purpose".
+ match &param.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
+}