use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, VirtReg};
use crate::x86::x86_instr_info::{X86InstrInfo, X86Opcode, RBP_CONST, RSP_CONST};
use crate::x86::x86_register_info::{EAX, R10, R11, R9, RAX, RBP, RSP};
use std::collections::HashMap;
pub const RED_ZONE_SIZE: i64 = 128;
pub const STACK_ALIGNMENT: i64 = 16;
pub const PUSH_SIZE_64: i64 = 8;
pub const PUSH_SIZE_32: i64 = 4;
const RBX: u16 = 3;
const R12: u16 = 12;
const R13: u16 = 13;
const R14: u16 = 14;
const R15: u16 = 15;
const EBX32: u16 = 19;
const ESI32: u16 = 22;
const EDI32: u16 = 23;
const EBP32: u16 = 21;
const ESP32: u16 = 20;
pub const CALLEE_SAVED_REGS_SYSV: &[u16] = &[RBX, R12, R13, R14, R15];
pub const CALLEE_SAVED_REGS_WIN64: &[u16] =
&[RBX, RBP_CONST, RSI_CONST, RDI_CONST, R12, R13, R14, R15];
pub const CALLEE_SAVED_REGS_X86_32: &[u16] = &[EBX32, ESI32, EDI32, EBP32];
const FLAGS: u16 = 200;
const RSI_CONST: u16 = 6;
const RDI_CONST: u16 = 7;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CallConv {
SystemV,
Win64,
CDecl32,
StdCall32,
FastCall32,
}
impl CallConv {
pub fn uses_red_zone(&self) -> bool {
matches!(self, CallConv::SystemV)
}
pub fn is_64bit(&self) -> bool {
matches!(self, CallConv::SystemV | CallConv::Win64)
}
pub fn is_32bit(&self) -> bool {
matches!(
self,
CallConv::CDecl32 | CallConv::StdCall32 | CallConv::FastCall32
)
}
pub fn push_size(&self) -> i64 {
if self.is_64bit() {
PUSH_SIZE_64
} else {
PUSH_SIZE_32
}
}
pub fn callee_saved_regs(&self) -> &'static [u16] {
match self {
CallConv::SystemV => CALLEE_SAVED_REGS_SYSV,
CallConv::Win64 => CALLEE_SAVED_REGS_WIN64,
CallConv::CDecl32 | CallConv::StdCall32 | CallConv::FastCall32 => {
CALLEE_SAVED_REGS_X86_32
}
}
}
pub fn frame_pointer_reg(&self) -> u16 {
if self.is_64bit() {
RBP_CONST
} else {
EBP32
}
}
pub fn stack_pointer_reg(&self) -> u16 {
if self.is_64bit() {
RSP_CONST
} else {
ESP32
}
}
}
#[derive(Debug, Clone)]
pub struct X86FrameInfo {
pub frame_size: i64,
pub saved_regs: Vec<u16>,
pub has_frame_pointer: bool,
pub has_calls: bool,
pub has_var_sized_objects: bool,
pub uses_red_zone: bool,
pub max_call_frame_size: i64,
pub local_area_offset: i64,
pub callee_saved_size: i64,
}
impl X86FrameInfo {
pub fn new() -> Self {
X86FrameInfo {
frame_size: 0,
saved_regs: Vec::new(),
has_frame_pointer: false,
has_calls: false,
has_var_sized_objects: false,
uses_red_zone: false,
max_call_frame_size: 0,
local_area_offset: 0,
callee_saved_size: 0,
}
}
pub fn total_frame_size(&self) -> i64 {
self.frame_size
}
pub fn local_start_offset(&self) -> i64 {
-(self.callee_saved_size) - PUSH_SIZE_64
}
}
impl Default for X86FrameInfo {
fn default() -> Self {
X86FrameInfo::new()
}
}
pub struct X86FrameLowering {
pub call_conv: CallConv,
pub instr_info: X86InstrInfo,
}
impl X86FrameLowering {
pub fn new(call_conv: CallConv) -> Self {
X86FrameLowering {
call_conv,
instr_info: X86InstrInfo::new(),
}
}
pub fn calculate_frame_size(&self, frame_info: &X86FrameInfo) -> i64 {
let mut size = frame_info.callee_saved_size;
size += frame_info.local_area_offset.abs();
size += frame_info.max_call_frame_size;
size = self.align_frame_size(size, STACK_ALIGNMENT);
size
}
pub fn align_frame_size(&self, size: i64, alignment: i64) -> i64 {
if size % alignment == 0 {
size
} else {
size + (alignment - (size % alignment))
}
}
pub fn needs_frame_pointer(&self, frame_info: &X86FrameInfo) -> bool {
if frame_info.has_var_sized_objects {
return true;
}
if self.call_conv.is_32bit() && frame_info.frame_size > 0 {
return true;
}
if frame_info.has_calls && frame_info.frame_size > RED_ZONE_SIZE {
return true;
}
if frame_info.frame_size > 4096 {
return true;
}
false
}
pub fn emit_prologue(&self, mf: &mut MachineFunction) -> Vec<MachineInstr> {
let frame_info = self.build_frame_info(mf);
let mut instrs = Vec::new();
let sp = self.call_conv.stack_pointer_reg();
let fp = self.call_conv.frame_pointer_reg();
let push_size = self.call_conv.push_size();
if frame_info.has_frame_pointer {
instrs.push(self.build_push(fp));
instrs.push(self.build_mov_fp_from_sp(fp, sp));
if frame_info.frame_size > 0 {
let alloc_size = self.align_frame_size(frame_info.frame_size, STACK_ALIGNMENT);
if !frame_info.uses_red_zone || alloc_size > RED_ZONE_SIZE {
instrs.push(self.build_sub_sp(sp, alloc_size));
}
}
} else {
if frame_info.frame_size > 0 {
let alloc_size = self.align_frame_size(frame_info.frame_size, STACK_ALIGNMENT);
if !frame_info.uses_red_zone || alloc_size > RED_ZONE_SIZE {
instrs.push(self.build_sub_sp(sp, alloc_size));
}
}
}
for ® in &frame_info.saved_regs {
if reg != fp {
instrs.push(self.build_push(reg));
}
}
if self.call_conv == CallConv::Win64 {
self.emit_seh_prologue(&mut instrs, &frame_info);
}
instrs
}
fn build_push(&self, reg: u16) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::PUSH as u32);
mi.operands.push(MachineOperand::PhysReg(reg as u32));
mi
}
fn build_mov_fp_from_sp(&self, fp: u16, sp: u16) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.operands.push(MachineOperand::PhysReg(fp as u32));
mi.operands.push(MachineOperand::PhysReg(sp as u32));
mi
}
fn build_sub_sp(&self, sp: u16, amount: i64) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::SUB as u32);
mi.operands.push(MachineOperand::PhysReg(sp as u32));
mi.operands.push(MachineOperand::Imm(amount));
mi
}
fn emit_seh_prologue(&self, _instrs: &mut Vec<MachineInstr>, frame_info: &X86FrameInfo) {
let _ = frame_info;
}
pub fn emit_epilogue(&self, mf: &mut MachineFunction) -> Vec<MachineInstr> {
let frame_info = self.build_frame_info(mf);
let mut instrs = Vec::new();
let sp = self.call_conv.stack_pointer_reg();
let fp = self.call_conv.frame_pointer_reg();
if self.call_conv == CallConv::Win64 {
self.emit_seh_epilogue(&mut instrs, &frame_info);
}
if frame_info.has_frame_pointer {
if !frame_info.saved_regs.is_empty() || frame_info.frame_size > 0 {
let offset = -(frame_info.callee_saved_size);
instrs.push(self.build_lea_sp_from_fp(sp, fp, offset));
} else {
instrs.push(self.build_mov_sp_from_fp(sp, fp));
}
for ® in frame_info.saved_regs.iter().rev() {
if reg != fp {
instrs.push(self.build_pop(reg));
}
}
instrs.push(self.build_pop(fp));
} else {
if frame_info.frame_size > 0 {
let alloc_size = self.align_frame_size(frame_info.frame_size, STACK_ALIGNMENT);
if !frame_info.uses_red_zone || alloc_size > RED_ZONE_SIZE {
instrs.push(self.build_add_sp(sp, alloc_size));
}
}
for ® in frame_info.saved_regs.iter().rev() {
instrs.push(self.build_pop(reg));
}
}
instrs.push(self.build_ret());
instrs
}
fn build_pop(&self, reg: u16) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::POP as u32);
mi.operands.push(MachineOperand::PhysReg(reg as u32));
mi
}
fn build_mov_sp_from_fp(&self, sp: u16, fp: u16) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.operands.push(MachineOperand::PhysReg(sp as u32));
mi.operands.push(MachineOperand::PhysReg(fp as u32));
mi
}
fn build_lea_sp_from_fp(&self, sp: u16, fp: u16, offset: i64) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::LEA as u32);
mi.operands.push(MachineOperand::PhysReg(sp as u32));
mi.operands.push(MachineOperand::PhysReg(fp as u32));
mi.operands.push(MachineOperand::Imm(offset));
mi
}
fn build_add_sp(&self, sp: u16, amount: i64) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
mi.operands.push(MachineOperand::PhysReg(sp as u32));
mi.operands.push(MachineOperand::Imm(amount));
mi
}
fn build_ret(&self) -> MachineInstr {
MachineInstr::new(X86Opcode::RET as u32)
}
fn emit_seh_epilogue(&self, _instrs: &mut Vec<MachineInstr>, frame_info: &X86FrameInfo) {
let _ = frame_info;
}
pub fn build_frame_info(&self, mf: &MachineFunction) -> X86FrameInfo {
let mut info = X86FrameInfo::new();
let used_regs = self.collect_used_callee_saved_regs(mf);
let callee_saved_size = used_regs.len() as i64 * self.call_conv.push_size();
info.saved_regs = used_regs;
info.callee_saved_size = callee_saved_size;
info.has_calls = self.function_has_calls(mf);
info.local_area_offset = self.estimate_local_area(mf);
info.frame_size = self.calculate_frame_size(&info);
info.uses_red_zone =
self.call_conv.uses_red_zone() && !info.has_calls && info.frame_size <= RED_ZONE_SIZE;
info.has_frame_pointer = self.needs_frame_pointer(&info);
info.max_call_frame_size = self.estimate_max_call_args(mf);
info
}
fn collect_used_callee_saved_regs(&self, mf: &MachineFunction) -> Vec<u16> {
let callee_regs = self.call_conv.callee_saved_regs();
let mut used = Vec::new();
let mut seen = HashMap::new();
for block in &mf.blocks {
for inst in &block.instructions {
for op in &inst.operands {
match op {
MachineOperand::PhysReg(reg) => {
seen.insert(*reg, true);
}
MachineOperand::Reg(_) => {
}
_ => {}
}
}
}
}
for ® in callee_regs {
if seen.contains_key(&(reg as u32)) {
used.push(reg);
}
}
used
}
fn function_has_calls(&self, mf: &MachineFunction) -> bool {
let call_opcode = X86Opcode::CALL as u32;
for block in &mf.blocks {
for inst in &block.instructions {
if inst.opcode == call_opcode {
return true;
}
}
}
false
}
fn estimate_local_area(&self, _mf: &MachineFunction) -> i64 {
0
}
fn estimate_max_call_args(&self, mf: &MachineFunction) -> i64 {
let mut max_args = 0i64;
let call_opcode = X86Opcode::CALL as u32;
for block in &mf.blocks {
for inst in &block.instructions {
if inst.opcode == call_opcode {
let arg_count = inst.operands.len() as i64;
if arg_count > max_args {
max_args = arg_count;
}
}
}
}
let arg_size = self.call_conv.push_size();
let register_args = if self.call_conv.is_64bit() { 6 } else { 0 };
let stack_args = (max_args - register_args).max(0);
self.align_frame_size(stack_args * arg_size, STACK_ALIGNMENT)
}
pub fn get_frame_index_reference(
&self,
mf: &mut MachineFunction,
fi: i64,
offset: i64,
) -> Vec<MachineInstr> {
let frame_info = self.build_frame_info(mf);
let fp = self.call_conv.frame_pointer_reg();
let mut instrs = Vec::new();
let base_offset = -(frame_info.callee_saved_size) - fi * PUSH_SIZE_64 + offset;
let vreg = mf.new_vreg();
let mut load = MachineInstr::new(X86Opcode::MOV as u32);
load.operands.push(MachineOperand::Reg(vreg));
load.operands.push(MachineOperand::PhysReg(fp as u32));
load.operands.push(MachineOperand::Imm(base_offset));
load.def = Some(vreg);
instrs.push(load);
let _ = mf; instrs
}
pub fn assign_callee_saved_spill_slots(&self, mf: &MachineFunction) -> Vec<(u16, i64)> {
let frame_info = self.build_frame_info(mf);
let push_size = self.call_conv.push_size();
let mut slots = Vec::new();
let mut current_offset: i64 = 0;
for ® in &frame_info.saved_regs {
if reg != self.call_conv.frame_pointer_reg() {
current_offset -= push_size;
slots.push((reg, current_offset));
}
}
slots
}
pub fn emit_prologue_32(&self, mf: &mut MachineFunction) -> Vec<MachineInstr> {
let frame_info = self.build_frame_info(mf);
let mut instrs = Vec::new();
let sp = ESP32;
let fp = EBP32;
instrs.push(self.build_push(fp));
instrs.push(self.build_mov_fp_from_sp(fp, sp));
if frame_info.frame_size > 0 {
let alloc_size = self.align_frame_size(frame_info.frame_size, STACK_ALIGNMENT);
instrs.push(self.build_sub_sp(sp, alloc_size));
}
for ® in &frame_info.saved_regs {
if reg != fp {
instrs.push(self.build_push(reg));
}
}
instrs
}
pub fn emit_epilogue_32(&self, mf: &mut MachineFunction) -> Vec<MachineInstr> {
let frame_info = self.build_frame_info(mf);
let mut instrs = Vec::new();
let sp = ESP32;
let fp = EBP32;
let alloc_size = self.align_frame_size(frame_info.frame_size, STACK_ALIGNMENT);
for ® in frame_info.saved_regs.iter().rev() {
if reg != fp {
instrs.push(self.build_pop(reg));
}
}
if frame_info.has_frame_pointer {
instrs.push(self.build_mov_sp_from_fp(sp, fp));
instrs.push(self.build_pop(fp));
} else if frame_info.frame_size > 0 {
instrs.push(self.build_add_sp(sp, alloc_size));
}
instrs.push(self.build_ret());
instrs
}
pub fn emit_prologue_win64(&self, mf: &mut MachineFunction) -> Vec<MachineInstr> {
let frame_info = self.build_frame_info(mf);
let mut instrs = Vec::new();
let sp = RSP_CONST;
let fp = RBP_CONST;
instrs.push(self.build_push(fp));
instrs.push(self.build_mov_fp_from_sp(fp, sp));
let alloc_size = self.align_frame_size(frame_info.frame_size + 32, STACK_ALIGNMENT);
if alloc_size > 0 {
instrs.push(self.build_sub_sp(sp, alloc_size));
}
for ® in &frame_info.saved_regs {
if reg != fp {
instrs.push(self.build_push(reg));
}
}
instrs
}
pub fn emit_epilogue_win64(&self, mf: &mut MachineFunction) -> Vec<MachineInstr> {
let frame_info = self.build_frame_info(mf);
let mut instrs = Vec::new();
let sp = RSP_CONST;
let fp = RBP_CONST;
let alloc_size = self.align_frame_size(frame_info.frame_size + 32, STACK_ALIGNMENT);
for ® in frame_info.saved_regs.iter().rev() {
if reg != fp {
instrs.push(self.build_pop(reg));
}
}
if frame_info.has_frame_pointer {
instrs.push(self.build_mov_sp_from_fp(sp, fp));
instrs.push(self.build_pop(fp));
} else if alloc_size > 0 {
instrs.push(self.build_add_sp(sp, alloc_size));
}
instrs.push(self.build_ret());
instrs
}
}
impl Default for X86FrameLowering {
fn default() -> Self {
X86FrameLowering::new(CallConv::SystemV)
}
}
pub const GUARD_PAGE_SIZE: i64 = 4096;
pub const STACK_PROBE_THRESHOLD: i64 = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum StackProbeKind {
#[default]
None,
ChkStk,
Inline,
Morestack,
}
impl X86FrameLowering {
pub fn probe_kind(&self, frame_size: i64, is_windows: bool) -> StackProbeKind {
if frame_size <= STACK_PROBE_THRESHOLD {
return StackProbeKind::None;
}
if is_windows {
StackProbeKind::ChkStk
} else {
StackProbeKind::Inline
}
}
pub fn emit_stack_probe(
&self,
frame_size: i64,
sp: u16,
scratch: u16,
is_windows: bool,
label_counter: &mut u64,
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let kind = self.probe_kind(frame_size, is_windows);
match kind {
StackProbeKind::None => {}
StackProbeKind::ChkStk => {
let mut mov_instr = MachineInstr::new(X86Opcode::MOV as u32);
mov_instr.operands.push(MachineOperand::PhysReg(RAX as u32));
mov_instr.operands.push(MachineOperand::Imm(frame_size));
instrs.push(mov_instr);
let mut call_instr = MachineInstr::new(X86Opcode::CALL as u32);
call_instr
.operands
.push(MachineOperand::Label("__chkstk".to_string()));
instrs.push(call_instr);
let mut sub_instr = MachineInstr::new(X86Opcode::SUB as u32);
sub_instr.operands.push(MachineOperand::PhysReg(sp as u32));
sub_instr.operands.push(MachineOperand::PhysReg(RAX as u32));
instrs.push(sub_instr);
}
StackProbeKind::Inline => {
let mut mov_target = MachineInstr::new(X86Opcode::MOV as u32);
mov_target
.operands
.push(MachineOperand::PhysReg(scratch as u32));
mov_target.operands.push(MachineOperand::PhysReg(sp as u32));
instrs.push(mov_target);
let mut sub_target = MachineInstr::new(X86Opcode::SUB as u32);
sub_target
.operands
.push(MachineOperand::PhysReg(scratch as u32));
sub_target.operands.push(MachineOperand::Imm(frame_size));
instrs.push(sub_target);
let loop_label = format!(".Lprobe_loop_{}", label_counter);
*label_counter += 1;
let loop_out = format!(".Lprobe_done_{}", label_counter);
*label_counter += 1;
let mut sub_probe = MachineInstr::new(X86Opcode::SUB as u32);
sub_probe.operands.push(MachineOperand::PhysReg(sp as u32));
sub_probe
.operands
.push(MachineOperand::Imm(GUARD_PAGE_SIZE));
instrs.push(sub_probe);
let mut touch_instr = MachineInstr::new(X86Opcode::TEST as u32);
touch_instr
.operands
.push(MachineOperand::PhysReg(sp as u32));
touch_instr
.operands
.push(MachineOperand::PhysReg(sp as u32));
instrs.push(touch_instr);
let mut cmp_instr = MachineInstr::new(X86Opcode::CMP as u32);
cmp_instr.operands.push(MachineOperand::PhysReg(sp as u32));
cmp_instr
.operands
.push(MachineOperand::PhysReg(scratch as u32));
instrs.push(cmp_instr);
let mut jne_instr = MachineInstr::new(X86Opcode::JNE as u32);
jne_instr.operands.push(MachineOperand::Label(loop_label));
instrs.push(jne_instr);
}
StackProbeKind::Morestack => {
let mut call_instr = MachineInstr::new(X86Opcode::CALL as u32);
call_instr
.operands
.push(MachineOperand::Label("__morestack".to_string()));
instrs.push(call_instr);
}
}
instrs
}
}
impl X86FrameLowering {
pub fn build_gpr_save<'a>(&self, saved: &[u16], exclude_fp: u16) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
for ® in saved {
if reg != exclude_fp {
let mut mi = MachineInstr::new(X86Opcode::PUSH as u32);
mi.operands.push(MachineOperand::PhysReg(reg as u32));
instrs.push(mi);
}
}
instrs
}
pub fn build_gpr_restore<'a>(&self, saved: &[u16], exclude_fp: u16) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
for ® in saved.iter().rev() {
if reg != exclude_fp {
let mut mi = MachineInstr::new(X86Opcode::POP as u32);
mi.operands.push(MachineOperand::PhysReg(reg as u32));
instrs.push(mi);
}
}
instrs
}
pub fn gpr_save_order(&self) -> Vec<u16> {
if self.call_conv.is_64bit() {
match self.call_conv {
CallConv::SystemV => {
vec![R15, R14, R13, R12, RBX]
}
CallConv::Win64 => {
vec![R15, R14, R13, R12, RDI_CONST, RSI_CONST, RBX]
}
_ => vec![R15, R14, R13, R12, RBX],
}
} else {
vec![ESI32, EDI32, EBX32]
}
}
}
pub const XMM_SAVE_ALIGN: i64 = 16;
pub const YMM_SAVE_ALIGN: i64 = 32;
pub const ZMM_SAVE_ALIGN: i64 = 64;
const MOVAPS_STORE: u32 = 2200;
const MOVAPS_LOAD: u32 = 2201;
#[derive(Debug, Clone)]
pub struct XmmSaveArea {
pub offset: i64,
pub count: usize,
pub slot_size: i64,
pub alignment: i64,
pub registers: Vec<u8>,
}
impl Default for XmmSaveArea {
fn default() -> Self {
XmmSaveArea {
offset: 0,
count: 0,
slot_size: 16,
alignment: XMM_SAVE_ALIGN,
registers: Vec::new(),
}
}
}
impl X86FrameLowering {
pub fn get_xmm_callee_saved(&self) -> Vec<u8> {
match self.call_conv {
CallConv::Win64 => {
(6..=15).collect()
}
_ => {
Vec::new()
}
}
}
pub fn build_xmm_save(
&self,
xmm_regs: &[u8],
base_reg: u16,
base_offset: i64,
slot_size: i64,
has_frame_pointer: bool,
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let fp = if has_frame_pointer { -1i64 } else { 0i64 };
let _ = fp;
for (i, &xmm_idx) in xmm_regs.iter().enumerate() {
let offset = base_offset - (i as i64 + 1) * slot_size;
let xmm_reg_id: u16 = 48u16 + xmm_idx as u16;
let mut mi = MachineInstr::new(MOVAPS_STORE);
mi.operands.push(MachineOperand::PhysReg(base_reg as u32));
mi.operands.push(MachineOperand::Imm(offset));
mi.operands.push(MachineOperand::PhysReg(xmm_reg_id as u32));
instrs.push(mi);
}
instrs
}
pub fn build_xmm_restore(
&self,
xmm_regs: &[u8],
base_reg: u16,
base_offset: i64,
slot_size: i64,
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
for (i, &xmm_idx) in xmm_regs.iter().enumerate() {
let offset = base_offset - (i as i64 + 1) * slot_size;
let xmm_reg_id: u16 = 48u16 + xmm_idx as u16;
let mut mi = MachineInstr::new(MOVAPS_LOAD);
mi.operands.push(MachineOperand::PhysReg(xmm_reg_id as u32));
mi.operands.push(MachineOperand::PhysReg(base_reg as u32));
mi.operands.push(MachineOperand::Imm(offset));
instrs.push(mi);
}
instrs
}
pub fn xmm_save_area_size(&self, xmm_regs: &[u8], slot_size: i64, alignment: i64) -> i64 {
let raw_size = xmm_regs.len() as i64 * slot_size;
if raw_size % alignment == 0 {
raw_size
} else {
raw_size + (alignment - (raw_size % alignment))
}
}
}
pub const MAX_STACK_ALIGNMENT: i64 = 64;
#[derive(Debug, Clone)]
pub struct StackRealignInfo {
pub needs_realignment: bool,
pub alignment: i64,
pub scratch_reg: u16,
pub adjustment_offset: i64,
}
impl Default for StackRealignInfo {
fn default() -> Self {
StackRealignInfo {
needs_realignment: false,
alignment: STACK_ALIGNMENT,
scratch_reg: RAX,
adjustment_offset: 0,
}
}
}
impl X86FrameLowering {
pub fn requires_stack_realignment(
&self,
frame_info: &X86FrameInfo,
local_alignment: i64,
) -> bool {
local_alignment > STACK_ALIGNMENT
|| frame_info.has_var_sized_objects
|| (self.call_conv == CallConv::Win64 && frame_info.frame_size > RED_ZONE_SIZE)
}
pub fn emit_realign_prologue(
&self,
frame_size: i64,
sp: u16,
fp: u16,
scratch: u16,
alignment: i64,
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let mut push_fp = MachineInstr::new(X86Opcode::PUSH as u32);
push_fp.operands.push(MachineOperand::PhysReg(fp as u32));
instrs.push(push_fp);
let mut mov_fp = MachineInstr::new(X86Opcode::MOV as u32);
mov_fp.operands.push(MachineOperand::PhysReg(fp as u32));
mov_fp.operands.push(MachineOperand::PhysReg(sp as u32));
instrs.push(mov_fp);
let mut push_scratch = MachineInstr::new(X86Opcode::PUSH as u32);
push_scratch
.operands
.push(MachineOperand::PhysReg(scratch as u32));
instrs.push(push_scratch);
let mut mov_scratch = MachineInstr::new(X86Opcode::MOV as u32);
mov_scratch
.operands
.push(MachineOperand::PhysReg(scratch as u32));
mov_scratch
.operands
.push(MachineOperand::PhysReg(sp as u32));
instrs.push(mov_scratch);
let mut and_align = MachineInstr::new(X86Opcode::AND as u32);
and_align.operands.push(MachineOperand::PhysReg(sp as u32));
and_align.operands.push(MachineOperand::Imm(-alignment));
instrs.push(and_align);
if frame_size > 0 {
let mut sub_frame = MachineInstr::new(X86Opcode::SUB as u32);
sub_frame.operands.push(MachineOperand::PhysReg(sp as u32));
sub_frame.operands.push(MachineOperand::Imm(frame_size));
instrs.push(sub_frame);
}
instrs
}
pub fn emit_realign_epilogue(
&self,
sp: u16,
fp: u16,
scratch: u16,
scratch_offset: i64,
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let mut lea_instr = MachineInstr::new(X86Opcode::LEA as u32);
lea_instr.operands.push(MachineOperand::PhysReg(sp as u32));
lea_instr.operands.push(MachineOperand::PhysReg(fp as u32));
lea_instr.operands.push(MachineOperand::Imm(scratch_offset));
instrs.push(lea_instr);
let mut pop_scratch = MachineInstr::new(X86Opcode::POP as u32);
pop_scratch
.operands
.push(MachineOperand::PhysReg(scratch as u32));
instrs.push(pop_scratch);
let mut pop_fp = MachineInstr::new(X86Opcode::POP as u32);
pop_fp.operands.push(MachineOperand::PhysReg(fp as u32));
instrs.push(pop_fp);
instrs.push(self.build_ret());
instrs
}
pub fn find_realignment_scratch(&self, used_regs: &[u16]) -> u16 {
let candidates = if self.call_conv.is_64bit() {
vec![RAX, R11, R10, R9]
} else {
vec![EAX]
};
for &cand in &candidates {
if !used_regs.contains(&cand) {
return cand;
}
}
if self.call_conv.is_64bit() {
RAX
} else {
EAX
}
}
}
#[derive(Debug, Clone)]
pub struct VarSizedFrameInfo {
pub has_dynamic_alloca: bool,
pub size_reg: Option<VirtReg>,
pub static_frame_size: i64,
pub dynamic_area_offset: i64,
}
impl Default for VarSizedFrameInfo {
fn default() -> Self {
VarSizedFrameInfo {
has_dynamic_alloca: false,
size_reg: None,
static_frame_size: 0,
dynamic_area_offset: 0,
}
}
}
impl X86FrameLowering {
pub fn has_dynamic_alloca(&self, mf: &MachineFunction) -> bool {
for bb in &mf.blocks {
for mi in &bb.instructions {
if mi.opcode == 3000u32 {
if mi.operands.len() >= 2 {
if let MachineOperand::Reg(_) = mi.operands[1] {
return true;
}
}
}
}
}
false
}
pub fn emit_var_sized_prologue(
&self,
static_frame_size: i64,
sp: u16,
fp: u16,
saved_regs: &[u16],
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
instrs.push(self.build_push(fp));
instrs.push(self.build_mov_fp_from_sp(fp, sp));
if static_frame_size > 0 {
instrs.push(self.build_sub_sp(sp, static_frame_size));
}
for ® in saved_regs {
if reg != fp {
instrs.push(self.build_push(reg));
}
}
instrs
}
pub fn emit_var_sized_epilogue(
&self,
sp: u16,
fp: u16,
saved_regs: &[u16],
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
instrs.push(self.build_mov_sp_from_fp(sp, fp));
for ® in saved_regs.iter().rev() {
if reg != fp {
instrs.push(self.build_pop(reg));
}
}
instrs.push(self.build_pop(fp));
instrs.push(self.build_ret());
instrs
}
pub fn emit_dynamic_alloca(
&self,
size_reg: VirtReg,
result_reg: VirtReg,
sp: u16,
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let mut mov_size = MachineInstr::new(X86Opcode::MOV as u32);
mov_size.operands.push(MachineOperand::PhysReg(RAX as u32));
mov_size.operands.push(MachineOperand::Reg(size_reg));
mov_size.def = Some(result_reg);
instrs.push(mov_size);
let mut add_align = MachineInstr::new(X86Opcode::ADD as u32);
add_align.operands.push(MachineOperand::PhysReg(RAX as u32));
add_align.operands.push(MachineOperand::Imm(15));
instrs.push(add_align);
let mut and_align = MachineInstr::new(X86Opcode::AND as u32);
and_align.operands.push(MachineOperand::PhysReg(RAX as u32));
and_align.operands.push(MachineOperand::Imm(-16));
instrs.push(and_align);
let mut sub_sp = MachineInstr::new(X86Opcode::SUB as u32);
sub_sp.operands.push(MachineOperand::PhysReg(sp as u32));
sub_sp.operands.push(MachineOperand::PhysReg(RAX as u32));
instrs.push(sub_sp);
let mut mov_result = MachineInstr::new(X86Opcode::MOV as u32);
mov_result.operands.push(MachineOperand::Reg(result_reg));
mov_result.operands.push(MachineOperand::PhysReg(sp as u32));
mov_result.def = Some(result_reg);
instrs.push(mov_result);
instrs
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SehUnwindOp {
PushNonVol(u16),
AllocLarge(i64),
AllocSmall(i64),
SetFpReg,
SaveNonVol(u16, i64),
SaveXmm128(u16, i64),
PushMachFrame,
EndPrologue,
}
#[derive(Debug, Clone)]
pub struct Win64UnwindInfo {
pub version: u8,
pub flags: u8,
pub prologue_size: u8,
pub unwind_code_count: u8,
pub frame_register: u8,
pub frame_register_offset: u8,
pub unwind_codes: Vec<SehUnwindOp>,
pub exception_handler: u32,
pub handler_data: u32,
}
impl Default for Win64UnwindInfo {
fn default() -> Self {
Win64UnwindInfo {
version: 1,
flags: 0,
prologue_size: 0,
unwind_code_count: 0,
frame_register: 0,
frame_register_offset: 0,
unwind_codes: Vec::new(),
exception_handler: 0,
handler_data: 0,
}
}
}
impl X86FrameLowering {
pub fn build_win64_unwind_info(
&self,
frame_info: &X86FrameInfo,
prologue_size: u8,
) -> Win64UnwindInfo {
let mut info = Win64UnwindInfo::default();
info.version = 1;
info.prologue_size = prologue_size;
let mut codes = Vec::new();
codes.push(SehUnwindOp::EndPrologue);
if frame_info.has_frame_pointer {
codes.push(SehUnwindOp::SetFpReg);
info.frame_register = RBP as u8;
info.frame_register_offset = 0;
}
if frame_info.frame_size > 0 {
let alloc_size = self.align_frame_size(frame_info.frame_size, STACK_ALIGNMENT);
if alloc_size <= 128 {
codes.push(SehUnwindOp::AllocSmall(alloc_size));
} else if alloc_size <= 524280 {
codes.push(SehUnwindOp::AllocLarge(alloc_size));
}
}
for ® in frame_info.saved_regs.iter() {
if reg != self.call_conv.frame_pointer_reg() {
codes.push(SehUnwindOp::PushNonVol(reg));
}
}
if frame_info.has_frame_pointer {
codes.push(SehUnwindOp::PushNonVol(self.call_conv.frame_pointer_reg()));
}
info.unwind_code_count = codes.len() as u8;
info.unwind_codes = codes;
info
}
pub fn emit_seh_directives(&self, unwind_info: &Win64UnwindInfo) -> Vec<String> {
let mut directives = Vec::new();
directives.push(format!(".seh_proc main",));
for code in &unwind_info.unwind_codes {
match *code {
SehUnwindOp::PushNonVol(reg) => {
directives.push(format!(".seh_pushreg {}", reg));
}
SehUnwindOp::AllocLarge(size) => {
directives.push(format!(".seh_stackalloc {}", size));
}
SehUnwindOp::AllocSmall(size) => {
directives.push(format!(".seh_stackalloc {}", size));
}
SehUnwindOp::SetFpReg => {
directives.push(".seh_setframe %rbp, 0".to_string());
}
SehUnwindOp::SaveNonVol(reg, offset) => {
directives.push(format!(".seh_savereg {}, {}", reg, offset));
}
SehUnwindOp::SaveXmm128(reg, offset) => {
directives.push(format!(".seh_savexmm {}, {}", reg, offset));
}
SehUnwindOp::PushMachFrame => {
directives.push(".seh_pushframe".to_string());
}
SehUnwindOp::EndPrologue => {
directives.push(".seh_endprologue".to_string());
}
}
}
if unwind_info.exception_handler != 0 {
directives.push(format!(".seh_handler __C_specific_handler, @except",));
}
directives.push(".seh_endproc".to_string());
directives
}
}
#[derive(Debug, Clone)]
pub struct ShrinkWrapInfo {
pub profitable: bool,
pub save_block: Option<usize>,
pub restore_block: Option<usize>,
pub shrinkable_regs: Vec<u16>,
}
impl Default for ShrinkWrapInfo {
fn default() -> Self {
ShrinkWrapInfo {
profitable: false,
save_block: None,
restore_block: None,
shrinkable_regs: Vec::new(),
}
}
}
impl X86FrameLowering {
pub fn analyze_shrink_wrap(&self, mf: &MachineFunction) -> ShrinkWrapInfo {
let mut info = ShrinkWrapInfo::default();
if mf.blocks.len() < 2 {
return info;
}
let callee_saved = self.call_conv.callee_saved_regs().to_vec();
for ® in &callee_saved {
let mut used_in_all = true;
for (block_idx, bb) in mf.blocks.iter().enumerate() {
if block_idx == 0 {
continue;
}
let is_ret_block = bb
.instructions
.iter()
.any(|mi| mi.opcode == X86Opcode::RET as u32);
if is_ret_block {
let reg_used = bb.instructions.iter().any(|mi| {
mi.operands.iter().any(|op| {
if let MachineOperand::PhysReg(r) = *op {
r == reg as u32
} else {
false
}
})
});
if !reg_used {
used_in_all = false;
break;
}
}
}
if !used_in_all {
info.shrinkable_regs.push(reg);
}
}
info.profitable = !info.shrinkable_regs.is_empty();
if info.profitable {
let mut first_use_block: Option<usize> = None;
for (idx, bb) in mf.blocks.iter().enumerate().skip(1) {
let uses_any = info.shrinkable_regs.iter().any(|&sr| {
bb.instructions.iter().any(|mi| {
mi.operands.iter().any(|op| {
if let MachineOperand::PhysReg(r) = *op {
r == sr as u32
} else {
false
}
})
})
});
if uses_any {
first_use_block = Some(idx);
break;
}
}
if let Some(idx) = first_use_block {
info.save_block = Some(idx);
info.restore_block = Some(mf.blocks.len() - 1); }
}
info
}
pub fn emit_shrink_wrapped_saves(&self, regs: &[u16], fp: u16) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
for ® in regs {
if reg != fp {
instrs.push(self.build_push(reg));
}
}
instrs
}
pub fn emit_shrink_wrapped_restores(&self, regs: &[u16], fp: u16) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
for ® in regs.iter().rev() {
if reg != fp {
instrs.push(self.build_pop(reg));
}
}
instrs
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CfiDirective {
DefCfa(u16, i64),
DefCfaOffset(i64),
DefCfaRegister(u16),
Offset(u16, i64),
SameValue(u16),
Register(u16, u16),
Restore(u16),
RememberState,
RestoreState,
Undefined(u16),
Escape(Vec<u8>),
WindowSave,
ArgPointer(u16),
ReturnColumn(u16),
SignalFrame,
}
#[derive(Debug, Clone)]
pub struct CallFrameInfo {
pub cfa_register: u16,
pub cfa_offset: i64,
pub reg_offsets: HashMap<u16, i64>,
pub reg_rules: HashMap<u16, u16>,
pub return_column: u16,
}
impl Default for CallFrameInfo {
fn default() -> Self {
CallFrameInfo {
cfa_register: RSP,
cfa_offset: PUSH_SIZE_64, reg_offsets: HashMap::new(),
reg_rules: HashMap::new(),
return_column: 16, }
}
}
impl X86FrameLowering {
pub fn build_cfi_prologue(
&self,
frame_info: &X86FrameInfo,
use_dwarf: bool,
) -> Vec<CfiDirective> {
if !use_dwarf {
return Vec::new();
}
let mut cfi = Vec::new();
let fp = self.call_conv.frame_pointer_reg();
if frame_info.has_frame_pointer {
cfi.push(CfiDirective::DefCfaOffset(PUSH_SIZE_64 * 2));
cfi.push(CfiDirective::Offset(fp, -(PUSH_SIZE_64 * 2)));
cfi.push(CfiDirective::DefCfaRegister(fp));
} else {
if frame_info.frame_size > 0 {
let alloc_size = self.align_frame_size(frame_info.frame_size, STACK_ALIGNMENT);
cfi.push(CfiDirective::DefCfaOffset(PUSH_SIZE_64 + alloc_size));
}
}
let mut offset_from_cfa = if frame_info.has_frame_pointer {
PUSH_SIZE_64 * 2
} else {
PUSH_SIZE_64
};
for ® in &frame_info.saved_regs {
if reg != fp {
cfi.push(CfiDirective::Offset(reg, -offset_from_cfa));
offset_from_cfa += self.call_conv.push_size();
}
}
cfi.push(CfiDirective::Offset(16, -PUSH_SIZE_64));
cfi
}
pub fn build_cfi_epilogue(
&self,
frame_info: &X86FrameInfo,
use_dwarf: bool,
) -> Vec<CfiDirective> {
if !use_dwarf {
return Vec::new();
}
let mut cfi = Vec::new();
let fp = self.call_conv.frame_pointer_reg();
let sp = self.call_conv.stack_pointer_reg();
for ® in &frame_info.saved_regs {
cfi.push(CfiDirective::Restore(reg));
}
if frame_info.has_frame_pointer {
cfi.push(CfiDirective::Restore(fp));
cfi.push(CfiDirective::DefCfa(sp, PUSH_SIZE_64));
} else {
cfi.push(CfiDirective::DefCfa(sp, PUSH_SIZE_64));
}
cfi
}
pub fn emit_cfi_directives(&self, cfi: &[CfiDirective]) -> Vec<String> {
let mut lines = Vec::new();
for directive in cfi {
match *directive {
CfiDirective::DefCfa(reg, offset) => {
lines.push(format!(".cfi_def_cfa {}, {}", reg, offset));
}
CfiDirective::DefCfaOffset(offset) => {
lines.push(format!(".cfi_def_cfa_offset {}", offset));
}
CfiDirective::DefCfaRegister(reg) => {
lines.push(format!(".cfi_def_cfa_register {}", reg));
}
CfiDirective::Offset(reg, offset) => {
lines.push(format!(".cfi_offset {}, {}", reg, offset));
}
CfiDirective::SameValue(reg) => {
lines.push(format!(".cfi_same_value {}", reg));
}
CfiDirective::Register(reg1, reg2) => {
lines.push(format!(".cfi_register {}, {}", reg1, reg2));
}
CfiDirective::Restore(reg) => {
lines.push(format!(".cfi_restore {}", reg));
}
CfiDirective::RememberState => {
lines.push(".cfi_remember_state".to_string());
}
CfiDirective::RestoreState => {
lines.push(".cfi_restore_state".to_string());
}
CfiDirective::Undefined(reg) => {
lines.push(format!(".cfi_undefined {}", reg));
}
CfiDirective::Escape(ref bytes) => {
let hex_bytes: Vec<String> =
bytes.iter().map(|b| format!("{:02x}", b)).collect();
lines.push(format!(".cfi_escape {}", hex_bytes.join(", ")));
}
CfiDirective::WindowSave => {
lines.push(".cfi_window_save".to_string());
}
CfiDirective::ArgPointer(reg) => {
lines.push(format!(".cfi_arg_pointer {}", reg));
}
CfiDirective::ReturnColumn(reg) => {
lines.push(format!(".cfi_return_column {}", reg));
}
CfiDirective::SignalFrame => {
lines.push(".cfi_signal_frame".to_string());
}
}
}
lines
}
}
#[derive(Debug, Clone)]
pub struct SpillSlot {
pub vreg: VirtReg,
pub offset: i64,
pub size: i64,
pub alignment: i64,
pub reg_class: SpillSlotClass,
pub fixed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpillSlotClass {
Gpr64,
Gpr32,
Gpr16,
Gpr8,
Xmm128,
Ymm256,
Zmm512,
X87,
Mmx64,
}
impl SpillSlotClass {
pub fn size(&self) -> i64 {
match self {
SpillSlotClass::Gpr64 | SpillSlotClass::X87 | SpillSlotClass::Mmx64 => 8,
SpillSlotClass::Gpr32 => 4,
SpillSlotClass::Gpr16 => 2,
SpillSlotClass::Gpr8 => 1,
SpillSlotClass::Xmm128 => 16,
SpillSlotClass::Ymm256 => 32,
SpillSlotClass::Zmm512 => 64,
}
}
pub fn alignment(&self) -> i64 {
match self {
SpillSlotClass::Gpr64
| SpillSlotClass::Gpr32
| SpillSlotClass::Gpr16
| SpillSlotClass::Gpr8 => PUSH_SIZE_64,
SpillSlotClass::Xmm128 => XMM_SAVE_ALIGN,
SpillSlotClass::Ymm256 => YMM_SAVE_ALIGN,
SpillSlotClass::Zmm512 => ZMM_SAVE_ALIGN,
SpillSlotClass::X87 | SpillSlotClass::Mmx64 => 8,
}
}
}
impl X86FrameLowering {
pub fn compute_spill_slots(
&self,
spilled: &[(VirtReg, SpillSlotClass)],
base_offset: i64,
) -> HashMap<VirtReg, SpillSlot> {
let mut slots = HashMap::new();
let mut current_offset = base_offset;
let mut sorted: Vec<_> = spilled.to_vec();
sorted.sort_by(|a, b| b.1.alignment().cmp(&a.1.alignment()));
for &(vreg, class) in &sorted {
let align = class.alignment();
let size = class.size();
current_offset = self.align_frame_size(current_offset, align);
slots.insert(
vreg,
SpillSlot {
vreg,
offset: current_offset,
size,
alignment: align,
reg_class: class,
fixed: false,
},
);
current_offset += size;
}
slots
}
pub fn compute_spill_area_size(&self, spills: &[(VirtReg, SpillSlotClass)]) -> i64 {
let slots = self.compute_spill_slots(spills, 0);
if slots.is_empty() {
return 0;
}
let max_end = slots.values().map(|s| s.offset + s.size).max().unwrap_or(0);
self.align_frame_size(max_end, STACK_ALIGNMENT)
}
pub fn classify_spill_slot(&self, bit_width: u16) -> SpillSlotClass {
match bit_width {
1..=8 => SpillSlotClass::Gpr8,
9..=16 => SpillSlotClass::Gpr16,
17..=32 => SpillSlotClass::Gpr32,
33..=64 => SpillSlotClass::Gpr64,
65..=128 => SpillSlotClass::Xmm128,
129..=256 => SpillSlotClass::Ymm256,
257..=512 => SpillSlotClass::Zmm512,
_ => SpillSlotClass::Gpr64, }
}
}
#[derive(Debug, Clone)]
pub struct FramePointerElimInfo {
pub eliminated: bool,
pub reason: Option<FpElimReason>,
pub sp_offset: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FpElimReason {
VarSizedObjects,
InlineAsmClobberSp,
StackRealignment,
ManyLocals32,
DebugInfo,
ExplicitRequest,
}
impl X86FrameLowering {
pub fn can_eliminate_frame_pointer(
&self,
frame_info: &X86FrameInfo,
has_inline_asm: bool,
force_frame_pointer: bool,
max_sp_offset: i64,
) -> FramePointerElimInfo {
let mut info = FramePointerElimInfo {
eliminated: false,
reason: None,
sp_offset: 0,
};
if force_frame_pointer {
info.reason = Some(FpElimReason::ExplicitRequest);
return info;
}
if frame_info.has_var_sized_objects {
info.reason = Some(FpElimReason::VarSizedObjects);
return info;
}
if has_inline_asm {
info.reason = Some(FpElimReason::InlineAsmClobberSp);
return info;
}
if self.call_conv.is_32bit() && frame_info.frame_size > max_sp_offset {
info.reason = Some(FpElimReason::ManyLocals32);
return info;
}
info.eliminated = true;
info.sp_offset = frame_info.frame_size;
info
}
pub fn adjust_sp_offset(
&self,
current_offset: i64,
opcode: u32,
operands: &[MachineOperand],
) -> i64 {
match opcode {
11u32 => current_offset + self.call_conv.push_size(), 12u32 => current_offset - self.call_conv.push_size(), 3u32 => {
if operands.len() >= 2 {
if let (MachineOperand::PhysReg(reg), MachineOperand::Imm(amount)) =
(&operands[0], &operands[1])
{
if *reg == self.call_conv.stack_pointer_reg() as u32 {
return current_offset + amount;
}
}
}
current_offset
}
2u32 => {
if operands.len() >= 2 {
if let (MachineOperand::PhysReg(reg), MachineOperand::Imm(amount)) =
(&operands[0], &operands[1])
{
if *reg == self.call_conv.stack_pointer_reg() as u32 {
return current_offset - amount;
}
}
}
current_offset
}
14u32 => {
current_offset
}
_ => current_offset,
}
}
}
impl X86FrameLowering {
pub fn can_use_red_zone(&self, frame_info: &X86FrameInfo, is_signal_handler: bool) -> bool {
if !self.call_conv.uses_red_zone() {
return false;
}
if is_signal_handler {
return false; }
if frame_info.has_calls {
return false; }
if frame_info.has_var_sized_objects {
return false; }
frame_info.frame_size <= RED_ZONE_SIZE
}
pub fn compute_red_zone_usage(
&self,
frame_info: &X86FrameInfo,
is_signal_handler: bool,
) -> i64 {
if self.can_use_red_zone(frame_info, is_signal_handler) {
frame_info.frame_size
} else {
0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_empty_mf(name: &str) -> MachineFunction {
MachineFunction::new(name)
}
#[allow(dead_code)]
fn make_leaf_mf(name: &str) -> MachineFunction {
let mut mf = MachineFunction::new(name);
let mut bb = MachineBasicBlock {
name: "entry".to_string(),
instructions: Vec::new(),
successors: Vec::new(),
};
bb.instructions.push({
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
mi.operands.push(MachineOperand::PhysReg(RBX as u32));
mi.operands.push(MachineOperand::PhysReg(R12 as u32));
mi
});
bb.instructions.push({
let mut mi = MachineInstr::new(X86Opcode::RET as u32);
mi
});
mf.push_block(bb);
mf
}
fn make_mf_with_calls(name: &str) -> MachineFunction {
let mut mf = MachineFunction::new(name);
let mut bb = MachineBasicBlock {
name: "entry".to_string(),
instructions: Vec::new(),
successors: Vec::new(),
};
bb.instructions.push({
let mut mi = MachineInstr::new(X86Opcode::CALL as u32);
mi.operands
.push(MachineOperand::Label("other_func".to_string()));
mi
});
bb.instructions.push({
let mut mi = MachineInstr::new(X86Opcode::RET as u32);
mi
});
mf.push_block(bb);
mf
}
#[test]
fn test_call_conv_uses_red_zone() {
assert!(CallConv::SystemV.uses_red_zone());
assert!(!CallConv::Win64.uses_red_zone());
assert!(!CallConv::CDecl32.uses_red_zone());
}
#[test]
fn test_call_conv_is_64bit() {
assert!(CallConv::SystemV.is_64bit());
assert!(CallConv::Win64.is_64bit());
assert!(!CallConv::CDecl32.is_64bit());
assert!(!CallConv::StdCall32.is_64bit());
}
#[test]
fn test_call_conv_is_32bit() {
assert!(!CallConv::SystemV.is_32bit());
assert!(!CallConv::Win64.is_32bit());
assert!(CallConv::CDecl32.is_32bit());
assert!(CallConv::FastCall32.is_32bit());
}
#[test]
fn test_call_conv_push_size() {
assert_eq!(CallConv::SystemV.push_size(), 8);
assert_eq!(CallConv::Win64.push_size(), 8);
assert_eq!(CallConv::CDecl32.push_size(), 4);
assert_eq!(CallConv::StdCall32.push_size(), 4);
}
#[test]
fn test_call_conv_callee_saved_regs() {
let sysv = CallConv::SystemV.callee_saved_regs();
assert!(sysv.contains(&RBX));
assert!(sysv.contains(&R12));
assert!(sysv.contains(&R15));
let win64 = CallConv::Win64.callee_saved_regs();
assert!(win64.contains(&RBX));
assert!(win64.contains(&RDI_CONST));
assert!(win64.contains(&RSI_CONST));
let cdecl32 = CallConv::CDecl32.callee_saved_regs();
assert!(cdecl32.contains(&EBX32));
assert!(cdecl32.contains(&ESI32));
}
#[test]
fn test_frame_info_default() {
let info = X86FrameInfo::new();
assert_eq!(info.frame_size, 0);
assert_eq!(info.saved_regs.len(), 0);
assert!(!info.has_frame_pointer);
assert!(!info.has_calls);
assert!(!info.uses_red_zone);
}
#[test]
fn test_frame_info_total_frame_size() {
let mut info = X86FrameInfo::new();
info.frame_size = 48;
assert_eq!(info.total_frame_size(), 48);
}
#[test]
fn test_frame_lowering_new() {
let fl = X86FrameLowering::new(CallConv::SystemV);
assert_eq!(fl.call_conv, CallConv::SystemV);
}
#[test]
fn test_frame_lowering_default() {
let fl = X86FrameLowering::default();
assert_eq!(fl.call_conv, CallConv::SystemV);
}
#[test]
fn test_align_frame_size() {
let fl = X86FrameLowering::new(CallConv::SystemV);
assert_eq!(fl.align_frame_size(0, 16), 0);
assert_eq!(fl.align_frame_size(16, 16), 16);
assert_eq!(fl.align_frame_size(17, 16), 32);
assert_eq!(fl.align_frame_size(20, 16), 32);
assert_eq!(fl.align_frame_size(31, 16), 32);
assert_eq!(fl.align_frame_size(32, 16), 32);
assert_eq!(fl.align_frame_size(33, 16), 48);
}
#[test]
fn test_calculate_frame_size() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut info = X86FrameInfo::new();
info.callee_saved_size = 16; info.local_area_offset = -24;
info.max_call_frame_size = 8;
let size = fl.calculate_frame_size(&info);
assert_eq!(size, 48);
let mut info2 = X86FrameInfo::new();
info2.callee_saved_size = 8; info2.local_area_offset = -10;
info2.max_call_frame_size = 0;
let size2 = fl.calculate_frame_size(&info2);
assert_eq!(size2, 32);
}
#[test]
fn test_needs_frame_pointer_var_sized() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut info = X86FrameInfo::new();
info.has_var_sized_objects = true;
assert!(fl.needs_frame_pointer(&info));
}
#[test]
fn test_needs_frame_pointer_large_frame_with_calls() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut info = X86FrameInfo::new();
info.has_calls = true;
info.frame_size = 256;
assert!(fl.needs_frame_pointer(&info));
}
#[test]
fn test_needs_frame_pointer_small_leaf() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut info = X86FrameInfo::new();
info.has_calls = false;
info.frame_size = 64; assert!(!fl.needs_frame_pointer(&info));
}
#[test]
fn test_needs_frame_pointer_32bit() {
let fl = X86FrameLowering::new(CallConv::CDecl32);
let mut info = X86FrameInfo::new();
info.frame_size = 16;
assert!(fl.needs_frame_pointer(&info));
}
#[test]
fn test_needs_frame_pointer_huge_frame() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut info = X86FrameInfo::new();
info.frame_size = 8192;
assert!(fl.needs_frame_pointer(&info));
}
#[test]
fn test_build_frame_info_leaf() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mf = make_leaf_mf("leaf_func");
let info = fl.build_frame_info(&mf);
assert!(!info.has_calls);
assert!(!info.saved_regs.is_empty());
assert!(info.uses_red_zone || info.frame_size <= RED_ZONE_SIZE);
}
#[test]
fn test_build_frame_info_with_calls() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mf = make_mf_with_calls("caller_func");
let info = fl.build_frame_info(&mf);
assert!(info.has_calls);
assert!(!info.uses_red_zone);
}
#[test]
fn test_emit_prologue_basic() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut mf = make_leaf_mf("basic_func");
let instrs = fl.emit_prologue(&mut mf);
assert!(!instrs.is_empty(), "Prologue should emit instructions");
}
#[test]
fn test_emit_epilogue_basic() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut mf = make_empty_mf("basic_func");
let instrs = fl.emit_epilogue(&mut mf);
assert!(!instrs.is_empty(), "Epilogue should emit instructions");
let last = instrs.last().unwrap();
assert_eq!(last.opcode, X86Opcode::RET as u32);
}
#[test]
fn test_emit_prologue_win64() {
let fl = X86FrameLowering::new(CallConv::Win64);
let mut mf = make_leaf_mf("win64_func");
let instrs = fl.emit_prologue(&mut mf);
assert!(!instrs.is_empty());
let push_count = instrs
.iter()
.filter(|i| i.opcode == X86Opcode::PUSH as u32)
.count();
assert!(
push_count > 0,
"Win64 prologue should contain push instructions"
);
}
#[test]
fn test_emit_prologue_32() {
let fl = X86FrameLowering::new(CallConv::CDecl32);
let mut mf = make_empty_mf("cdecl32_func");
let instrs = fl.emit_prologue_32(&mut mf);
assert!(!instrs.is_empty());
let push_count = instrs
.iter()
.filter(|i| i.opcode == X86Opcode::PUSH as u32)
.count();
assert!(
push_count > 0,
"32-bit prologue should contain push instructions"
);
}
#[test]
fn test_emit_epilogue_32() {
let fl = X86FrameLowering::new(CallConv::CDecl32);
let mut mf = make_empty_mf("cdecl32_func");
let instrs = fl.emit_epilogue_32(&mut mf);
assert!(!instrs.is_empty());
let last = instrs.last().unwrap();
assert_eq!(last.opcode, X86Opcode::RET as u32);
}
#[test]
fn test_assign_callee_saved_spill_slots() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mf = make_leaf_mf("spill_test");
let slots = fl.assign_callee_saved_spill_slots(&mf);
assert!(!slots.is_empty(), "Should have spill slots for used regs");
for (_reg, offset) in &slots {
assert!(*offset < 0, "Spill slot offsets should be negative");
}
}
#[test]
fn test_get_frame_index_reference() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut mf = make_empty_mf("fi_test");
let instrs = fl.get_frame_index_reference(&mut mf, 0, 0);
assert!(!instrs.is_empty());
let load = &instrs[0];
assert_eq!(load.opcode, X86Opcode::MOV as u32);
assert!(load.def.is_some(), "Load should define a virtual register");
}
#[test]
fn test_prologue_epilogue_roundtrip() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mut mf = make_empty_mf("roundtrip");
let prologue = fl.emit_prologue(&mut mf);
let epilogue = fl.emit_epilogue(&mut mf);
let prologue_pushes: Vec<_> = prologue
.iter()
.filter(|i| i.opcode == X86Opcode::PUSH as u32)
.collect();
let epilogue_pops: Vec<_> = epilogue
.iter()
.filter(|i| i.opcode == X86Opcode::POP as u32)
.collect();
assert!(
epilogue_pops.len() >= prologue_pushes.len(),
"Epilogue should restore at least as many regs as prologue saves"
);
}
#[test]
fn test_red_zone_detection() {
let fl = X86FrameLowering::new(CallConv::SystemV);
let mf = make_leaf_mf("red_zone_test");
let info = fl.build_frame_info(&mf);
if !info.has_calls {
assert!(
info.uses_red_zone || info.frame_size > RED_ZONE_SIZE,
"Leaf with small frame should use red zone, or frame_size exceeded red zone"
);
}
}
}