Lucet uses stack probes rather than explicit stack limit checks as Wasmtime does. In bytecodealliance/lucet#616, I have discovered that I previously was not running some Lucet runtime tests with the new backend, so was missing some test failures due to missing pieces in the new backend. This PR adds (i) calls to probestack, when enabled, in the prologue of every function with a stack frame larger than one page (configurable via flags); and (ii) trap metadata for every instruction on x86-64 that can access the stack, hence be the first point at which a stack overflow is detected when the stack pointer is decremented.
824 lines
28 KiB
Rust
824 lines
28 KiB
Rust
//! Implementation of the standard x64 ABI.
|
|
|
|
use crate::ir::types::*;
|
|
use crate::ir::{self, types, ExternalName, LibCall, MemFlags, Opcode, 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_probestack(frame_size: u32) -> SmallVec<[Self::I; 2]> {
|
|
let mut insts = SmallVec::new();
|
|
insts.push(Inst::imm(
|
|
OperandSize::Size32,
|
|
frame_size as u64,
|
|
Writable::from_reg(regs::rax()),
|
|
));
|
|
insts.push(Inst::CallKnown {
|
|
dest: ExternalName::LibCall(LibCall::Probestack),
|
|
uses: vec![regs::rax()],
|
|
defs: vec![],
|
|
opcode: Opcode::Call,
|
|
});
|
|
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
|
|
}
|
|
|
|
fn get_ext_mode(
|
|
call_conv: isa::CallConv,
|
|
specified: ir::ArgumentExtension,
|
|
) -> ir::ArgumentExtension {
|
|
if call_conv.extends_baldrdash() {
|
|
// Baldrdash (SpiderMonkey) always extends args and return values to the full register.
|
|
specified
|
|
} else {
|
|
// No other supported ABI on x64 does so.
|
|
ir::ArgumentExtension::None
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|