754 lines
28 KiB
Rust
754 lines
28 KiB
Rust
//! x86 ABI implementation.
|
||
|
||
use super::super::settings as shared_settings;
|
||
use super::registers::{FPR, GPR, RU};
|
||
use super::settings as isa_settings;
|
||
use super::unwind::UnwindInfo;
|
||
use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion};
|
||
use crate::cursor::{Cursor, CursorPosition, EncCursor};
|
||
use crate::ir;
|
||
use crate::ir::immediates::Imm64;
|
||
use crate::ir::stackslot::{StackOffset, StackSize};
|
||
use crate::ir::{
|
||
get_probestack_funcref, AbiParam, ArgumentExtension, ArgumentLoc, ArgumentPurpose, InstBuilder,
|
||
ValueLoc,
|
||
};
|
||
use crate::isa::{CallConv, RegClass, RegUnit, TargetIsa};
|
||
use crate::regalloc::RegisterSet;
|
||
use crate::result::CodegenResult;
|
||
use crate::stack_layout::layout_stack;
|
||
use alloc::borrow::Cow;
|
||
use alloc::vec::Vec;
|
||
use core::i32;
|
||
use target_lexicon::{PointerWidth, Triple};
|
||
|
||
/// Argument registers for x86-64
|
||
static ARG_GPRS: [RU; 6] = [RU::rdi, RU::rsi, RU::rdx, RU::rcx, RU::r8, RU::r9];
|
||
|
||
/// Return value registers.
|
||
static RET_GPRS: [RU; 3] = [RU::rax, RU::rdx, RU::rcx];
|
||
|
||
/// Argument registers for x86-64, when using windows fastcall
|
||
static ARG_GPRS_WIN_FASTCALL_X64: [RU; 4] = [RU::rcx, RU::rdx, RU::r8, RU::r9];
|
||
|
||
/// Return value registers for x86-64, when using windows fastcall
|
||
static RET_GPRS_WIN_FASTCALL_X64: [RU; 1] = [RU::rax];
|
||
|
||
/// The win64 fastcall ABI uses some shadow stack space, allocated by the caller, that can be used
|
||
/// by the callee for temporary values.
|
||
///
|
||
/// [1] "Space is allocated on the call stack as a shadow store for callees to save" This shadow
|
||
/// store contains the parameters which are passed through registers (ARG_GPRS) and is eventually
|
||
/// used by the callee to save & restore the values of the arguments.
|
||
///
|
||
/// [2] https://blogs.msdn.microsoft.com/oldnewthing/20110302-00/?p=11333 "Although the x64 calling
|
||
/// convention reserves spill space for parameters, you don’t have to use them as such"
|
||
const WIN_SHADOW_STACK_SPACE: i32 = 32;
|
||
|
||
/// Stack alignment requirement for functions.
|
||
///
|
||
/// 16 bytes is the perfect stack alignment, because:
|
||
///
|
||
/// - On Win64, "The primary exceptions are the stack pointer and malloc or alloca memory, which
|
||
/// are aligned to 16 bytes in order to aid performance".
|
||
/// - The original 32-bit x86 ELF ABI had a 4-byte aligned stack pointer, but newer versions use a
|
||
/// 16-byte aligned stack pointer.
|
||
/// - This allows using aligned loads and stores on SIMD vectors of 16 bytes that are located
|
||
/// higher up in the stack.
|
||
const STACK_ALIGNMENT: u32 = 16;
|
||
|
||
#[derive(Clone)]
|
||
struct Args {
|
||
pointer_bytes: u8,
|
||
pointer_bits: u8,
|
||
pointer_type: ir::Type,
|
||
gpr: &'static [RU],
|
||
gpr_used: usize,
|
||
fpr_limit: usize,
|
||
fpr_used: usize,
|
||
offset: u32,
|
||
call_conv: CallConv,
|
||
shared_flags: shared_settings::Flags,
|
||
#[allow(dead_code)]
|
||
isa_flags: isa_settings::Flags,
|
||
}
|
||
|
||
impl Args {
|
||
fn new(
|
||
bits: u8,
|
||
gpr: &'static [RU],
|
||
fpr_limit: usize,
|
||
call_conv: CallConv,
|
||
shared_flags: &shared_settings::Flags,
|
||
isa_flags: &isa_settings::Flags,
|
||
) -> Self {
|
||
let offset = if call_conv.extends_windows_fastcall() {
|
||
WIN_SHADOW_STACK_SPACE
|
||
} else {
|
||
0
|
||
} as u32;
|
||
|
||
Self {
|
||
pointer_bytes: bits / 8,
|
||
pointer_bits: bits,
|
||
pointer_type: ir::Type::int(u16::from(bits)).unwrap(),
|
||
gpr,
|
||
gpr_used: 0,
|
||
fpr_limit,
|
||
fpr_used: 0,
|
||
offset,
|
||
call_conv,
|
||
shared_flags: shared_flags.clone(),
|
||
isa_flags: isa_flags.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ArgAssigner for Args {
|
||
fn assign(&mut self, arg: &AbiParam) -> ArgAction {
|
||
let ty = arg.value_type;
|
||
|
||
// Vectors should stay in vector registers unless SIMD is not enabled--then they are split
|
||
if ty.is_vector() {
|
||
if self.shared_flags.enable_simd() {
|
||
let reg = FPR.unit(self.fpr_used);
|
||
self.fpr_used += 1;
|
||
return ArgumentLoc::Reg(reg).into();
|
||
}
|
||
return ValueConversion::VectorSplit.into();
|
||
}
|
||
|
||
// Large integers and booleans are broken down to fit in a register.
|
||
if !ty.is_float() && ty.bits() > u16::from(self.pointer_bits) {
|
||
return ValueConversion::IntSplit.into();
|
||
}
|
||
|
||
// Small integers are extended to the size of a pointer register.
|
||
if ty.is_int() && ty.bits() < u16::from(self.pointer_bits) {
|
||
match arg.extension {
|
||
ArgumentExtension::None => {}
|
||
ArgumentExtension::Uext => return ValueConversion::Uext(self.pointer_type).into(),
|
||
ArgumentExtension::Sext => return ValueConversion::Sext(self.pointer_type).into(),
|
||
}
|
||
}
|
||
|
||
// Handle special-purpose arguments.
|
||
if ty.is_int() && self.call_conv.extends_baldrdash() {
|
||
match arg.purpose {
|
||
// This is SpiderMonkey's `WasmTlsReg`.
|
||
ArgumentPurpose::VMContext => {
|
||
return ArgumentLoc::Reg(if self.pointer_bits == 64 {
|
||
RU::r14
|
||
} else {
|
||
RU::rsi
|
||
} as RegUnit)
|
||
.into();
|
||
}
|
||
// This is SpiderMonkey's `WasmTableCallSigReg`.
|
||
ArgumentPurpose::SignatureId => {
|
||
return ArgumentLoc::Reg(if self.pointer_bits == 64 {
|
||
RU::r10
|
||
} else {
|
||
RU::rcx
|
||
} as RegUnit)
|
||
.into()
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
// Try to use a GPR.
|
||
if !ty.is_float() && self.gpr_used < self.gpr.len() {
|
||
let reg = self.gpr[self.gpr_used] as RegUnit;
|
||
self.gpr_used += 1;
|
||
return ArgumentLoc::Reg(reg).into();
|
||
}
|
||
|
||
// Try to use an FPR.
|
||
let fpr_offset = if self.call_conv.extends_windows_fastcall() {
|
||
// Float and general registers on windows share the same parameter index.
|
||
// The used register depends entirely on the parameter index: Even if XMM0
|
||
// is not used for the first parameter, it cannot be used for the second parameter.
|
||
debug_assert_eq!(self.fpr_limit, self.gpr.len());
|
||
&mut self.gpr_used
|
||
} else {
|
||
&mut self.fpr_used
|
||
};
|
||
|
||
if ty.is_float() && *fpr_offset < self.fpr_limit {
|
||
let reg = FPR.unit(*fpr_offset);
|
||
*fpr_offset += 1;
|
||
return ArgumentLoc::Reg(reg).into();
|
||
}
|
||
|
||
// Assign a stack location.
|
||
let loc = ArgumentLoc::Stack(self.offset as i32);
|
||
self.offset += u32::from(self.pointer_bytes);
|
||
debug_assert!(self.offset <= i32::MAX as u32);
|
||
loc.into()
|
||
}
|
||
}
|
||
|
||
/// Legalize `sig`.
|
||
pub fn legalize_signature(
|
||
sig: &mut Cow<ir::Signature>,
|
||
triple: &Triple,
|
||
_current: bool,
|
||
shared_flags: &shared_settings::Flags,
|
||
isa_flags: &isa_settings::Flags,
|
||
) {
|
||
let bits;
|
||
let mut args;
|
||
|
||
match triple.pointer_width().unwrap() {
|
||
PointerWidth::U16 => panic!(),
|
||
PointerWidth::U32 => {
|
||
bits = 32;
|
||
args = Args::new(bits, &[], 0, sig.call_conv, shared_flags, isa_flags);
|
||
}
|
||
PointerWidth::U64 => {
|
||
bits = 64;
|
||
args = if sig.call_conv.extends_windows_fastcall() {
|
||
Args::new(
|
||
bits,
|
||
&ARG_GPRS_WIN_FASTCALL_X64[..],
|
||
4,
|
||
sig.call_conv,
|
||
shared_flags,
|
||
isa_flags,
|
||
)
|
||
} else {
|
||
Args::new(
|
||
bits,
|
||
&ARG_GPRS[..],
|
||
8,
|
||
sig.call_conv,
|
||
shared_flags,
|
||
isa_flags,
|
||
)
|
||
};
|
||
}
|
||
}
|
||
|
||
let (ret_regs, ret_fpr_limit) = if sig.call_conv.extends_windows_fastcall() {
|
||
// windows-x64 calling convention only uses XMM0 or RAX for return values
|
||
(&RET_GPRS_WIN_FASTCALL_X64[..], 1)
|
||
} else {
|
||
(&RET_GPRS[..], 2)
|
||
};
|
||
|
||
let mut rets = Args::new(
|
||
bits,
|
||
ret_regs,
|
||
ret_fpr_limit,
|
||
sig.call_conv,
|
||
shared_flags,
|
||
isa_flags,
|
||
);
|
||
|
||
let sig_is_multi_return = sig.is_multi_return();
|
||
|
||
// If this is a multi-value return and we don't have enough available return
|
||
// registers to fit all of the return values, we need to backtrack and start
|
||
// assigning locations all over again with a different strategy. In order to
|
||
// do that, we need a copy of the original assigner for the returns.
|
||
let backup_rets_for_struct_return = if sig_is_multi_return {
|
||
Some(rets.clone())
|
||
} else {
|
||
None
|
||
};
|
||
|
||
if let Some(new_returns) = legalize_args(&sig.returns, &mut rets) {
|
||
if sig.is_multi_return()
|
||
&& new_returns
|
||
.iter()
|
||
.filter(|r| r.purpose == ArgumentPurpose::Normal)
|
||
.any(|r| !r.location.is_reg())
|
||
{
|
||
// The return values couldn't all fit into available return
|
||
// registers. Introduce the use of a struct-return parameter.
|
||
debug_assert!(!sig.uses_struct_return_param());
|
||
|
||
// We're using the first register for the return pointer parameter.
|
||
let mut ret_ptr_param = AbiParam {
|
||
value_type: args.pointer_type,
|
||
purpose: ArgumentPurpose::StructReturn,
|
||
extension: ArgumentExtension::None,
|
||
location: ArgumentLoc::Unassigned,
|
||
};
|
||
match args.assign(&ret_ptr_param) {
|
||
ArgAction::Assign(ArgumentLoc::Reg(reg)) => {
|
||
ret_ptr_param.location = ArgumentLoc::Reg(reg);
|
||
sig.to_mut().params.push(ret_ptr_param);
|
||
}
|
||
_ => unreachable!("return pointer should always get a register assignment"),
|
||
}
|
||
|
||
let mut backup_rets = backup_rets_for_struct_return.unwrap();
|
||
|
||
// We're using the first return register for the return pointer (like
|
||
// sys v does).
|
||
let mut ret_ptr_return = AbiParam {
|
||
value_type: args.pointer_type,
|
||
purpose: ArgumentPurpose::StructReturn,
|
||
extension: ArgumentExtension::None,
|
||
location: ArgumentLoc::Unassigned,
|
||
};
|
||
match backup_rets.assign(&ret_ptr_return) {
|
||
ArgAction::Assign(ArgumentLoc::Reg(reg)) => {
|
||
ret_ptr_return.location = ArgumentLoc::Reg(reg);
|
||
sig.to_mut().returns.push(ret_ptr_return);
|
||
}
|
||
_ => unreachable!("return pointer should always get a register assignment"),
|
||
}
|
||
|
||
sig.to_mut().returns.retain(|ret| {
|
||
// Either this is the return pointer, in which case we want to keep
|
||
// it, or else assume that it is assigned for a reason and doesn't
|
||
// conflict with our return pointering legalization.
|
||
debug_assert_eq!(
|
||
ret.location.is_assigned(),
|
||
ret.purpose != ArgumentPurpose::Normal
|
||
);
|
||
ret.location.is_assigned()
|
||
});
|
||
|
||
if let Some(new_returns) = legalize_args(&sig.returns, &mut backup_rets) {
|
||
sig.to_mut().returns = new_returns;
|
||
}
|
||
} else {
|
||
sig.to_mut().returns = new_returns;
|
||
}
|
||
}
|
||
|
||
if let Some(new_params) = legalize_args(&sig.params, &mut args) {
|
||
sig.to_mut().params = new_params;
|
||
}
|
||
}
|
||
|
||
/// Get register class for a type appearing in a legalized signature.
|
||
pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass {
|
||
if ty.is_int() || ty.is_bool() {
|
||
GPR
|
||
} else {
|
||
FPR
|
||
}
|
||
}
|
||
|
||
/// Get the set of allocatable registers for `func`.
|
||
pub fn allocatable_registers(triple: &Triple, flags: &shared_settings::Flags) -> RegisterSet {
|
||
let mut regs = RegisterSet::new();
|
||
regs.take(GPR, RU::rsp as RegUnit);
|
||
regs.take(GPR, RU::rbp as RegUnit);
|
||
|
||
// 32-bit arch only has 8 registers.
|
||
if triple.pointer_width().unwrap() != PointerWidth::U64 {
|
||
for i in 8..16 {
|
||
regs.take(GPR, GPR.unit(i));
|
||
regs.take(FPR, FPR.unit(i));
|
||
}
|
||
if flags.enable_pinned_reg() {
|
||
unimplemented!("Pinned register not implemented on x86-32.");
|
||
}
|
||
} else {
|
||
// Choose r15 as the pinned register on 64-bits: it is non-volatile on native ABIs and
|
||
// isn't the fixed output register of any instruction.
|
||
if flags.enable_pinned_reg() {
|
||
regs.take(GPR, RU::r15 as RegUnit);
|
||
}
|
||
}
|
||
|
||
regs
|
||
}
|
||
|
||
/// Get the set of callee-saved registers.
|
||
fn callee_saved_gprs(isa: &dyn TargetIsa, call_conv: CallConv) -> &'static [RU] {
|
||
match isa.triple().pointer_width().unwrap() {
|
||
PointerWidth::U16 => panic!(),
|
||
PointerWidth::U32 => &[RU::rbx, RU::rsi, RU::rdi],
|
||
PointerWidth::U64 => {
|
||
if call_conv.extends_windows_fastcall() {
|
||
// "registers RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 are considered nonvolatile
|
||
// and must be saved and restored by a function that uses them."
|
||
// as per https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention
|
||
// RSP & RSB are not listed below, since they are restored automatically during
|
||
// a function call. If that wasn't the case, function calls (RET) would not work.
|
||
&[
|
||
RU::rbx,
|
||
RU::rdi,
|
||
RU::rsi,
|
||
RU::r12,
|
||
RU::r13,
|
||
RU::r14,
|
||
RU::r15,
|
||
]
|
||
} else {
|
||
&[RU::rbx, RU::r12, RU::r13, RU::r14, RU::r15]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Get the set of callee-saved registers that are used.
|
||
fn callee_saved_gprs_used(isa: &dyn TargetIsa, func: &ir::Function) -> RegisterSet {
|
||
let mut all_callee_saved = RegisterSet::empty();
|
||
for reg in callee_saved_gprs(isa, func.signature.call_conv) {
|
||
all_callee_saved.free(GPR, *reg as RegUnit);
|
||
}
|
||
|
||
let mut used = RegisterSet::empty();
|
||
for value_loc in func.locations.values() {
|
||
// Note that `value_loc` here contains only a single unit of a potentially multi-unit
|
||
// register. We don't use registers that overlap each other in the x86 ISA, but in others
|
||
// we do. So this should not be blindly reused.
|
||
if let ValueLoc::Reg(ru) = *value_loc {
|
||
if !used.is_avail(GPR, ru) {
|
||
used.free(GPR, ru);
|
||
}
|
||
}
|
||
}
|
||
|
||
// regmove and regfill instructions may temporarily divert values into other registers,
|
||
// and these are not reflected in `func.locations`. Scan the function for such instructions
|
||
// and note which callee-saved registers they use.
|
||
//
|
||
// TODO: Consider re-evaluating how regmove/regfill/regspill work and whether it's possible
|
||
// to avoid this step.
|
||
for ebb in &func.layout {
|
||
for inst in func.layout.ebb_insts(ebb) {
|
||
match func.dfg[inst] {
|
||
ir::instructions::InstructionData::RegMove { dst, .. }
|
||
| ir::instructions::InstructionData::RegFill { dst, .. } => {
|
||
if !used.is_avail(GPR, dst) {
|
||
used.free(GPR, dst);
|
||
}
|
||
}
|
||
_ => (),
|
||
}
|
||
}
|
||
}
|
||
|
||
used.intersect(&all_callee_saved);
|
||
used
|
||
}
|
||
|
||
pub fn prologue_epilogue(func: &mut ir::Function, isa: &dyn TargetIsa) -> CodegenResult<()> {
|
||
match func.signature.call_conv {
|
||
// For now, just translate fast and cold as system_v.
|
||
CallConv::Fast | CallConv::Cold | CallConv::SystemV => {
|
||
system_v_prologue_epilogue(func, isa)
|
||
}
|
||
CallConv::WindowsFastcall => fastcall_prologue_epilogue(func, isa),
|
||
CallConv::BaldrdashSystemV | CallConv::BaldrdashWindows => {
|
||
baldrdash_prologue_epilogue(func, isa)
|
||
}
|
||
CallConv::Probestack => unimplemented!("probestack calling convention"),
|
||
}
|
||
}
|
||
|
||
fn baldrdash_prologue_epilogue(func: &mut ir::Function, isa: &dyn TargetIsa) -> CodegenResult<()> {
|
||
debug_assert!(
|
||
!isa.flags().probestack_enabled(),
|
||
"baldrdash does not expect cranelift to emit stack probes"
|
||
);
|
||
|
||
let word_size = StackSize::from(isa.pointer_bytes());
|
||
let shadow_store_size = if func.signature.call_conv.extends_windows_fastcall() {
|
||
WIN_SHADOW_STACK_SPACE as u32
|
||
} else {
|
||
0
|
||
};
|
||
|
||
let bytes =
|
||
StackSize::from(isa.flags().baldrdash_prologue_words()) * word_size + shadow_store_size;
|
||
|
||
let mut ss = ir::StackSlotData::new(ir::StackSlotKind::IncomingArg, bytes);
|
||
ss.offset = Some(-(bytes as StackOffset));
|
||
func.stack_slots.push(ss);
|
||
|
||
let is_leaf = func.is_leaf();
|
||
layout_stack(&mut func.stack_slots, is_leaf, STACK_ALIGNMENT)?;
|
||
Ok(())
|
||
}
|
||
|
||
/// Implementation of the fastcall-based Win64 calling convention described at [1]
|
||
/// [1] https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention
|
||
fn fastcall_prologue_epilogue(func: &mut ir::Function, isa: &dyn TargetIsa) -> CodegenResult<()> {
|
||
if isa.triple().pointer_width().unwrap() != PointerWidth::U64 {
|
||
panic!("TODO: windows-fastcall: x86-32 not implemented yet");
|
||
}
|
||
|
||
let csrs = callee_saved_gprs_used(isa, func);
|
||
|
||
// The reserved stack area is composed of:
|
||
// return address + frame pointer + all callee-saved registers + shadow space
|
||
//
|
||
// Pushing the return address is an implicit function of the `call`
|
||
// instruction. Each of the others we will then push explicitly. Then we
|
||
// will adjust the stack pointer to make room for the rest of the required
|
||
// space for this frame.
|
||
let word_size = isa.pointer_bytes() as usize;
|
||
let csr_stack_size = ((csrs.iter(GPR).len() + 2) * word_size) as i32;
|
||
|
||
// TODO: eventually use the 32 bytes (shadow store) as spill slot. This currently doesn't work
|
||
// since cranelift does not support spill slots before incoming args
|
||
|
||
func.create_stack_slot(ir::StackSlotData {
|
||
kind: ir::StackSlotKind::IncomingArg,
|
||
size: csr_stack_size as u32,
|
||
offset: Some(-(WIN_SHADOW_STACK_SPACE + csr_stack_size)),
|
||
});
|
||
|
||
let is_leaf = func.is_leaf();
|
||
let total_stack_size = layout_stack(&mut func.stack_slots, is_leaf, STACK_ALIGNMENT)? as i32;
|
||
let local_stack_size = i64::from(total_stack_size - csr_stack_size);
|
||
|
||
// Add CSRs to function signature
|
||
let reg_type = isa.pointer_type();
|
||
let fp_arg = ir::AbiParam::special_reg(
|
||
reg_type,
|
||
ir::ArgumentPurpose::FramePointer,
|
||
RU::rbp as RegUnit,
|
||
);
|
||
func.signature.params.push(fp_arg);
|
||
func.signature.returns.push(fp_arg);
|
||
|
||
for csr in csrs.iter(GPR) {
|
||
let csr_arg = ir::AbiParam::special_reg(reg_type, ir::ArgumentPurpose::CalleeSaved, csr);
|
||
func.signature.params.push(csr_arg);
|
||
func.signature.returns.push(csr_arg);
|
||
}
|
||
|
||
// Set up the cursor and insert the prologue
|
||
let entry_ebb = func.layout.entry_block().expect("missing entry block");
|
||
let mut pos = EncCursor::new(func, isa).at_first_insertion_point(entry_ebb);
|
||
insert_common_prologue(&mut pos, local_stack_size, reg_type, &csrs, isa);
|
||
|
||
// Reset the cursor and insert the epilogue
|
||
let mut pos = pos.at_position(CursorPosition::Nowhere);
|
||
insert_common_epilogues(&mut pos, local_stack_size, reg_type, &csrs);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Insert a System V-compatible prologue and epilogue.
|
||
fn system_v_prologue_epilogue(func: &mut ir::Function, isa: &dyn TargetIsa) -> CodegenResult<()> {
|
||
let pointer_width = isa.triple().pointer_width().unwrap();
|
||
let word_size = pointer_width.bytes() as usize;
|
||
|
||
let csrs = callee_saved_gprs_used(isa, func);
|
||
|
||
// The reserved stack area is composed of:
|
||
// return address + frame pointer + all callee-saved registers
|
||
//
|
||
// Pushing the return address is an implicit function of the `call`
|
||
// instruction. Each of the others we will then push explicitly. Then we
|
||
// will adjust the stack pointer to make room for the rest of the required
|
||
// space for this frame.
|
||
let csr_stack_size = ((csrs.iter(GPR).len() + 2) * word_size) as i32;
|
||
func.create_stack_slot(ir::StackSlotData {
|
||
kind: ir::StackSlotKind::IncomingArg,
|
||
size: csr_stack_size as u32,
|
||
offset: Some(-csr_stack_size),
|
||
});
|
||
|
||
let is_leaf = func.is_leaf();
|
||
let total_stack_size = layout_stack(&mut func.stack_slots, is_leaf, STACK_ALIGNMENT)? as i32;
|
||
let local_stack_size = i64::from(total_stack_size - csr_stack_size);
|
||
|
||
// Add CSRs to function signature
|
||
let reg_type = ir::Type::int(u16::from(pointer_width.bits())).unwrap();
|
||
let fp_arg = ir::AbiParam::special_reg(
|
||
reg_type,
|
||
ir::ArgumentPurpose::FramePointer,
|
||
RU::rbp as RegUnit,
|
||
);
|
||
func.signature.params.push(fp_arg);
|
||
func.signature.returns.push(fp_arg);
|
||
|
||
for csr in csrs.iter(GPR) {
|
||
let csr_arg = ir::AbiParam::special_reg(reg_type, ir::ArgumentPurpose::CalleeSaved, csr);
|
||
func.signature.params.push(csr_arg);
|
||
func.signature.returns.push(csr_arg);
|
||
}
|
||
|
||
// Set up the cursor and insert the prologue
|
||
let entry_ebb = func.layout.entry_block().expect("missing entry block");
|
||
let mut pos = EncCursor::new(func, isa).at_first_insertion_point(entry_ebb);
|
||
insert_common_prologue(&mut pos, local_stack_size, reg_type, &csrs, isa);
|
||
|
||
// Reset the cursor and insert the epilogue
|
||
let mut pos = pos.at_position(CursorPosition::Nowhere);
|
||
insert_common_epilogues(&mut pos, local_stack_size, reg_type, &csrs);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Insert the prologue for a given function.
|
||
/// This is used by common calling conventions such as System V.
|
||
fn insert_common_prologue(
|
||
pos: &mut EncCursor,
|
||
stack_size: i64,
|
||
reg_type: ir::types::Type,
|
||
csrs: &RegisterSet,
|
||
isa: &dyn TargetIsa,
|
||
) {
|
||
if stack_size > 0 {
|
||
// Check if there is a special stack limit parameter. If so insert stack check.
|
||
if let Some(stack_limit_arg) = pos.func.special_param(ArgumentPurpose::StackLimit) {
|
||
// Total stack size is the size of all stack area used by the function, including
|
||
// pushed CSRs, frame pointer.
|
||
// Also, the size of a return address, implicitly pushed by a x86 `call` instruction,
|
||
// also should be accounted for.
|
||
// TODO: Check if the function body actually contains a `call` instruction.
|
||
let word_size = isa.pointer_bytes();
|
||
let total_stack_size = (csrs.iter(GPR).len() + 1 + 1) as i64 * word_size as i64;
|
||
|
||
insert_stack_check(pos, total_stack_size, stack_limit_arg);
|
||
}
|
||
}
|
||
|
||
// Append param to entry EBB
|
||
let ebb = pos.current_ebb().expect("missing ebb under cursor");
|
||
let fp = pos.func.dfg.append_ebb_param(ebb, reg_type);
|
||
pos.func.locations[fp] = ir::ValueLoc::Reg(RU::rbp as RegUnit);
|
||
|
||
pos.ins().x86_push(fp);
|
||
pos.ins()
|
||
.copy_special(RU::rsp as RegUnit, RU::rbp as RegUnit);
|
||
|
||
for reg in csrs.iter(GPR) {
|
||
// Append param to entry EBB
|
||
let csr_arg = pos.func.dfg.append_ebb_param(ebb, reg_type);
|
||
|
||
// Assign it a location
|
||
pos.func.locations[csr_arg] = ir::ValueLoc::Reg(reg);
|
||
|
||
// Remember it so we can push it momentarily
|
||
pos.ins().x86_push(csr_arg);
|
||
}
|
||
|
||
// Allocate stack frame storage.
|
||
if stack_size > 0 {
|
||
if isa.flags().probestack_enabled()
|
||
&& stack_size > (1 << isa.flags().probestack_size_log2())
|
||
{
|
||
// Emit a stack probe.
|
||
let rax = RU::rax as RegUnit;
|
||
let rax_val = ir::ValueLoc::Reg(rax);
|
||
|
||
// The probestack function expects its input in %rax.
|
||
let arg = pos.ins().iconst(reg_type, stack_size);
|
||
pos.func.locations[arg] = rax_val;
|
||
|
||
// Call the probestack function.
|
||
let callee = get_probestack_funcref(pos.func, reg_type, rax, isa);
|
||
|
||
// Make the call.
|
||
let call = if !isa.flags().is_pic()
|
||
&& isa.triple().pointer_width().unwrap() == PointerWidth::U64
|
||
&& !pos.func.dfg.ext_funcs[callee].colocated
|
||
{
|
||
// 64-bit non-PIC non-colocated calls need to be legalized to call_indirect.
|
||
// Use r11 as it may be clobbered under all supported calling conventions.
|
||
let r11 = RU::r11 as RegUnit;
|
||
let sig = pos.func.dfg.ext_funcs[callee].signature;
|
||
let addr = pos.ins().func_addr(reg_type, callee);
|
||
pos.func.locations[addr] = ir::ValueLoc::Reg(r11);
|
||
pos.ins().call_indirect(sig, addr, &[arg])
|
||
} else {
|
||
// Otherwise just do a normal call.
|
||
pos.ins().call(callee, &[arg])
|
||
};
|
||
|
||
// If the probestack function doesn't adjust sp, do it ourselves.
|
||
if !isa.flags().probestack_func_adjusts_sp() {
|
||
let result = pos.func.dfg.inst_results(call)[0];
|
||
pos.func.locations[result] = rax_val;
|
||
pos.func.prologue_end = Some(pos.ins().adjust_sp_down(result));
|
||
}
|
||
} else {
|
||
// Simply decrement the stack pointer.
|
||
pos.func.prologue_end = Some(pos.ins().adjust_sp_down_imm(Imm64::new(stack_size)));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Insert a check that generates a trap if the stack pointer goes
|
||
/// below a value in `stack_limit_arg`.
|
||
fn insert_stack_check(pos: &mut EncCursor, stack_size: i64, stack_limit_arg: ir::Value) {
|
||
use crate::ir::condcodes::IntCC;
|
||
|
||
// Copy `stack_limit_arg` into a %rax and use it for calculating
|
||
// a SP threshold.
|
||
let stack_limit_copy = pos.ins().copy(stack_limit_arg);
|
||
pos.func.locations[stack_limit_copy] = ir::ValueLoc::Reg(RU::rax as RegUnit);
|
||
let sp_threshold = pos.ins().iadd_imm(stack_limit_copy, stack_size);
|
||
pos.func.locations[sp_threshold] = ir::ValueLoc::Reg(RU::rax as RegUnit);
|
||
|
||
// If the stack pointer currently reaches the SP threshold or below it then after opening
|
||
// the current stack frame, the current stack pointer will reach the limit.
|
||
let cflags = pos.ins().ifcmp_sp(sp_threshold);
|
||
pos.func.locations[cflags] = ir::ValueLoc::Reg(RU::rflags as RegUnit);
|
||
pos.ins().trapif(
|
||
IntCC::UnsignedGreaterThanOrEqual,
|
||
cflags,
|
||
ir::TrapCode::StackOverflow,
|
||
);
|
||
}
|
||
|
||
/// Find all `return` instructions and insert epilogues before them.
|
||
fn insert_common_epilogues(
|
||
pos: &mut EncCursor,
|
||
stack_size: i64,
|
||
reg_type: ir::types::Type,
|
||
csrs: &RegisterSet,
|
||
) {
|
||
while let Some(ebb) = pos.next_ebb() {
|
||
pos.goto_last_inst(ebb);
|
||
if let Some(inst) = pos.current_inst() {
|
||
if pos.func.dfg[inst].opcode().is_return() {
|
||
insert_common_epilogue(inst, stack_size, pos, reg_type, csrs);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Insert an epilogue given a specific `return` instruction.
|
||
/// This is used by common calling conventions such as System V.
|
||
fn insert_common_epilogue(
|
||
inst: ir::Inst,
|
||
stack_size: i64,
|
||
pos: &mut EncCursor,
|
||
reg_type: ir::types::Type,
|
||
csrs: &RegisterSet,
|
||
) {
|
||
if stack_size > 0 {
|
||
pos.ins().adjust_sp_up_imm(Imm64::new(stack_size));
|
||
}
|
||
|
||
// Pop all the callee-saved registers, stepping backward each time to
|
||
// preserve the correct order.
|
||
let fp_ret = pos.ins().x86_pop(reg_type);
|
||
pos.prev_inst();
|
||
|
||
pos.func.locations[fp_ret] = ir::ValueLoc::Reg(RU::rbp as RegUnit);
|
||
pos.func.dfg.append_inst_arg(inst, fp_ret);
|
||
|
||
for reg in csrs.iter(GPR) {
|
||
let csr_ret = pos.ins().x86_pop(reg_type);
|
||
pos.prev_inst();
|
||
|
||
pos.func.locations[csr_ret] = ir::ValueLoc::Reg(reg);
|
||
pos.func.dfg.append_inst_arg(inst, csr_ret);
|
||
}
|
||
}
|
||
|
||
pub fn emit_unwind_info(func: &ir::Function, isa: &dyn TargetIsa, mem: &mut Vec<u8>) {
|
||
// Assumption: RBP is being used as the frame pointer
|
||
// In the future, Windows fastcall codegen should usually omit the frame pointer
|
||
if let Some(info) = UnwindInfo::try_from_func(func, isa, Some(RU::rbp.into())) {
|
||
info.emit(mem);
|
||
}
|
||
}
|