use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, VirtReg};
use crate::opcode::Opcode;
use crate::value::ValueRef;
use crate::x86::x86_full_instr_info::X86ConditionCode;
use crate::x86::x86_instr_info::X86Opcode;
use crate::x86::x86_register_info::*;
use crate::x86::x86_subtarget::X86Subtarget;
use std::collections::{BTreeMap, HashMap, HashSet};
const MAX_VREGS: u32 = 65536;
const STACK_SLOT_SIZE: i32 = 8;
const CALLER_SAVED_GPRS: &[u16] = &[RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11];
const CALLER_SAVED_GPRS_32: &[u16] = &[EAX, ECX, EDX, ESI, EDI, R8D, R9D, R10D, R11D];
const CALLER_SAVED_XMMS: &[u16] = &[
XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14,
XMM15,
];
const CALLEE_SAVED_GPRS: &[u16] = &[RBX, R12, R13, R14, R15, RBP];
const CALLEE_SAVED_GPRS_32: &[u16] = &[EBX, R12D, R13D, R14D, R15D, EBP];
pub mod icmp_pred {
pub const EQ: u32 = 0;
pub const NE: u32 = 1;
pub const UGT: u32 = 2;
pub const UGE: u32 = 3;
pub const ULT: u32 = 4;
pub const ULE: u32 = 5;
pub const SGT: u32 = 6;
pub const SGE: u32 = 7;
pub const SLT: u32 = 8;
pub const SLE: u32 = 9;
}
pub mod fcmp_pred {
pub const FALSE: u32 = 0;
pub const OEQ: u32 = 1;
pub const OGT: u32 = 2;
pub const OGE: u32 = 3;
pub const OLT: u32 = 4;
pub const OLE: u32 = 5;
pub const ONE: u32 = 6;
pub const ORD: u32 = 7;
pub const UNO: u32 = 8;
pub const UEQ: u32 = 9;
pub const UGT: u32 = 10;
pub const UGE: u32 = 11;
pub const ULT: u32 = 12;
pub const ULE: u32 = 13;
pub const UNE: u32 = 14;
pub const TRUE: u32 = 15;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FastAddressMode {
None,
RegImm { base: VirtReg, offset: i32 },
RegReg {
base: VirtReg,
index: VirtReg,
scale: u8,
offset: i32,
},
FrameIndex { fi: i32, offset: i32 },
Global { name: String, offset: i64 },
ConstantPool { index: usize, offset: i32 },
Reg(VirtReg),
Absolute(i64),
}
impl FastAddressMode {
pub fn is_valid(&self) -> bool {
!matches!(self, FastAddressMode::None)
}
pub fn get_base(&self) -> Option<VirtReg> {
match self {
FastAddressMode::RegImm { base, .. } => Some(*base),
FastAddressMode::RegReg { base, .. } => Some(*base),
FastAddressMode::Reg(r) => Some(*r),
_ => None,
}
}
pub fn get_offset(&self) -> i32 {
match self {
FastAddressMode::RegImm { offset, .. } => *offset,
FastAddressMode::RegReg { offset, .. } => *offset,
FastAddressMode::FrameIndex { offset, .. } => *offset,
FastAddressMode::ConstantPool { offset, .. } => *offset,
_ => 0,
}
}
}
impl Default for FastAddressMode {
fn default() -> Self {
FastAddressMode::None
}
}
#[derive(Debug, Clone)]
pub struct VirtualRegAssignment {
pub vreg: u32,
pub vid: u64,
pub in_reg: bool,
pub phys_reg: Option<u16>,
pub stack_slot: Option<i32>,
pub locally_defined: bool,
pub is_kill: bool,
pub remaining_uses: u32,
}
impl VirtualRegAssignment {
pub fn new(vreg: u32, vid: u64) -> Self {
Self {
vreg,
vid,
in_reg: false,
phys_reg: None,
stack_slot: None,
locally_defined: true,
is_kill: false,
remaining_uses: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct FastRegAlloc {
pub is_64bit: bool,
pub next_vreg: u32,
pub vreg_map: HashMap<u64, u32>,
pub vreg_assignments: Vec<VirtualRegAssignment>,
pub gpr_in_use: HashMap<u16, u32>,
pub xmm_in_use: HashMap<u16, u32>,
pub next_stack_slot: i32,
pub use_counts: HashMap<u64, u32>,
pub gpr_order: Vec<u16>,
pub xmm_order: Vec<u16>,
}
impl FastRegAlloc {
pub fn new(is_64bit: bool) -> Self {
let gpr_order: Vec<u16> = if is_64bit {
CALLER_SAVED_GPRS.to_vec()
} else {
CALLER_SAVED_GPRS_32.to_vec()
};
Self {
is_64bit,
next_vreg: 0,
vreg_map: HashMap::new(),
vreg_assignments: Vec::new(),
gpr_in_use: HashMap::new(),
xmm_in_use: HashMap::new(),
next_stack_slot: 0,
use_counts: HashMap::new(),
gpr_order,
xmm_order: CALLER_SAVED_XMMS.to_vec(),
}
}
pub fn reset_for_block(&mut self) {
self.gpr_in_use.clear();
self.xmm_in_use.clear();
for va in &mut self.vreg_assignments {
va.in_reg = false;
va.phys_reg = None;
va.is_kill = false;
}
}
pub fn create_vreg(&mut self, vid: u64) -> u32 {
if let Some(&existing) = self.vreg_map.get(&vid) {
return existing;
}
let vreg = self.next_vreg;
self.next_vreg += 1;
if self.next_vreg > MAX_VREGS {
panic!("FastRegAlloc: too many virtual registers");
}
self.vreg_map.insert(vid, vreg);
self.vreg_assignments
.push(VirtualRegAssignment::new(vreg, vid));
vreg
}
pub fn get_or_create_vreg(&mut self, vid: u64) -> u32 {
if let Some(&vreg) = self.vreg_map.get(&vid) {
return vreg;
}
self.create_vreg(vid)
}
pub fn get_vreg(&self, vid: u64) -> Option<u32> {
self.vreg_map.get(&vid).copied()
}
pub fn inc_use_count(&mut self, vid: u64) {
*self.use_counts.entry(vid).or_insert(0) += 1;
}
pub fn dec_use(&mut self, vreg: u32) -> bool {
if let Some(va) = self.vreg_assignments.iter_mut().find(|va| va.vreg == vreg) {
if va.remaining_uses > 0 {
va.remaining_uses -= 1;
}
if va.remaining_uses == 0 {
va.is_kill = true;
return true;
}
}
false
}
pub fn alloc_gpr(&mut self, vreg: u32) -> Option<u16> {
for &pr in &self.gpr_order.clone() {
if !self.gpr_in_use.contains_key(&pr) {
self.gpr_in_use.insert(pr, vreg);
if let Some(va) = self.vreg_assignments.iter_mut().find(|va| va.vreg == vreg) {
va.in_reg = true;
va.phys_reg = Some(pr);
}
return Some(pr);
}
}
self.spill_gpr(vreg)
}
pub fn spill_gpr(&mut self, vreg: u32) -> Option<u16> {
let slot = self.next_stack_slot;
self.next_stack_slot += STACK_SLOT_SIZE;
if let Some(va) = self.vreg_assignments.iter_mut().find(|va| va.vreg == vreg) {
va.in_reg = false;
va.phys_reg = None;
va.stack_slot = Some(slot);
}
let temp = if self.is_64bit { RAX } else { EAX };
self.gpr_in_use.insert(temp, vreg);
Some(temp)
}
pub fn alloc_xmm(&mut self, vreg: u32) -> Option<u16> {
for &pr in &self.xmm_order.clone() {
if !self.xmm_in_use.contains_key(&pr) {
self.xmm_in_use.insert(pr, vreg);
if let Some(va) = self.vreg_assignments.iter_mut().find(|va| va.vreg == vreg) {
va.in_reg = true;
va.phys_reg = Some(pr);
}
return Some(pr);
}
}
self.spill_xmm(vreg)
}
pub fn spill_xmm(&mut self, vreg: u32) -> Option<u16> {
let slot = self.next_stack_slot;
self.next_stack_slot += STACK_SLOT_SIZE;
if let Some(va) = self.vreg_assignments.iter_mut().find(|va| va.vreg == vreg) {
va.in_reg = false;
va.phys_reg = None;
va.stack_slot = Some(slot);
}
let temp = XMM0;
self.xmm_in_use.insert(temp, vreg);
Some(temp)
}
pub fn free_gpr(&mut self, phys_reg: u16) {
self.gpr_in_use.remove(&phys_reg);
}
pub fn free_xmm(&mut self, phys_reg: u16) {
self.xmm_in_use.remove(&phys_reg);
}
pub fn ensure_gpr(&mut self, vreg: u32) -> u16 {
if let Some(va) = self.vreg_assignments.iter().find(|va| va.vreg == vreg) {
if let Some(pr) = va.phys_reg {
return pr;
}
}
self.alloc_gpr(vreg)
.unwrap_or(if self.is_64bit { RAX } else { EAX })
}
pub fn ensure_xmm(&mut self, vreg: u32) -> u16 {
if let Some(va) = self.vreg_assignments.iter().find(|va| va.vreg == vreg) {
if let Some(pr) = va.phys_reg {
return pr;
}
}
self.alloc_xmm(vreg).unwrap_or(XMM0)
}
pub fn compute_use_counts(&mut self, func: &ValueRef) {
self.use_counts.clear();
let f = func.borrow();
for op in &f.operands {
let ir_bb = op.borrow();
if !ir_bb.is_basic_block() {
continue;
}
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
for use_val in &inst.operands {
let uv = use_val.borrow();
if uv.is_instruction() || uv.subclass.is_instruction() {
*self.use_counts.entry(uv.vid).or_insert(0) += 1;
}
}
}
}
for va in &mut self.vreg_assignments {
if let Some(&count) = self.use_counts.get(&va.vid) {
va.remaining_uses = count;
}
}
}
}
#[derive(Debug, Clone)]
pub struct FastMaterializer {
pub is_64bit: bool,
pub constant_pool: Vec<(String, i64)>,
pub next_cp_label: usize,
}
impl FastMaterializer {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
constant_pool: Vec::new(),
next_cp_label: 0,
}
}
pub fn fits_i32(val: i64) -> bool {
val >= i32::MIN as i64 && val <= i32::MAX as i64
}
pub fn fits_u32(val: i64) -> bool {
val >= 0 && val <= u32::MAX as i64
}
pub fn emit_mov_imm64(&self, dst: u32, val: i64) -> Vec<MachineInstr> {
if Self::fits_i32(val) {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_imm(val);
mi.def = Some(dst);
vec![mi]
} else if Self::fits_u32(val) {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_imm(val);
mi.def = Some(dst);
vec![mi]
} else {
let mut mi = MachineInstr::new(X86Opcode::MOVABS as u32);
mi.push_reg(dst);
mi.push_imm(val);
mi.def = Some(dst);
vec![mi]
}
}
pub fn emit_mov_imm32(&self, dst: u32, val: i32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_imm(val as i64);
mi.def = Some(dst);
mi
}
pub fn add_to_constant_pool(&mut self, val: i64) -> String {
let label = format!(".LCPI{}", self.next_cp_label);
self.next_cp_label += 1;
self.constant_pool.push((label.clone(), val));
label
}
pub fn get_constant_pool(&self) -> &[(String, i64)] {
&self.constant_pool
}
}
pub struct X86FastISel {
pub subtarget: X86Subtarget,
pub reg_alloc: FastRegAlloc,
pub materializer: FastMaterializer,
pub mbb: MachineBasicBlock,
pub func_name: String,
pub all_blocks: Vec<MachineBasicBlock>,
pub is_64bit: bool,
pub next_frame_index: i32,
pub frame_index_map: HashMap<i32, i32>,
pub total_frame_size: i32,
pub sp_vreg: u32,
pub fp_vreg: u32,
}
impl X86FastISel {
pub fn new(subtarget: X86Subtarget) -> Self {
let is_64bit = subtarget.is_64_bit;
let reg_alloc = FastRegAlloc::new(is_64bit);
let sp_vreg = reg_alloc.next_vreg;
let mut ra = reg_alloc;
ra.next_vreg += 1;
let fp_vreg = ra.next_vreg;
ra.next_vreg += 1;
Self {
subtarget,
reg_alloc: ra,
materializer: FastMaterializer::new(is_64bit),
mbb: MachineBasicBlock {
name: String::new(),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
},
func_name: String::new(),
all_blocks: Vec::new(),
is_64bit,
next_frame_index: 0,
frame_index_map: HashMap::new(),
total_frame_size: 0,
sp_vreg,
fp_vreg,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction, func: &ValueRef) {
let f = func.borrow();
self.func_name = f.name.clone();
self.all_blocks.clear();
self.frame_index_map.clear();
self.next_frame_index = 0;
self.total_frame_size = 0;
self.reg_alloc.reset_for_block();
self.reg_alloc.vreg_map.clear();
self.reg_alloc.vreg_assignments.clear();
self.reg_alloc.next_vreg = 0;
self.sp_vreg = self.reg_alloc.next_vreg;
self.reg_alloc.next_vreg += 1;
self.fp_vreg = self.reg_alloc.next_vreg;
self.reg_alloc.next_vreg += 1;
for op in &f.operands {
let ir_bb = op.borrow();
if ir_bb.is_basic_block() {
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if inst.is_instruction() && !inst.ty.is_void() {
self.reg_alloc.create_vreg(inst.vid);
}
}
}
}
for op in &f.operands {
let val = op.borrow();
if val.subclass == crate::value::SubclassKind::Argument {
self.reg_alloc.create_vreg(val.vid);
}
}
self.reg_alloc.compute_use_counts(func);
for op in &f.operands {
let ir_bb = op.borrow();
if !ir_bb.is_basic_block() {
continue;
}
self.mbb = MachineBasicBlock {
name: ir_bb.name.clone(),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
};
self.reg_alloc.reset_for_block();
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
for use_val in &inst.operands {
let uv = use_val.borrow();
if let Some(vreg) = self.reg_alloc.get_vreg(uv.vid) {
self.reg_alloc.dec_use(vreg);
}
}
let instrs = self.select_instruction(&inst);
for mi in instrs {
self.mbb.instructions.push(mi);
}
}
self.all_blocks.push(self.mbb.clone());
}
mf.blocks = self.all_blocks.clone();
mf.virt_reg_counter = self.reg_alloc.next_vreg;
mf.name = self.func_name.clone();
}
pub fn select_instruction(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
match inst.opcode {
Some(Opcode::Add) => vec![self.lower_add(inst)],
Some(Opcode::Sub) => vec![self.lower_sub(inst)],
Some(Opcode::Mul) => self.lower_mul(inst),
Some(Opcode::SDiv) => self.lower_sdiv(inst),
Some(Opcode::UDiv) => self.lower_udiv(inst),
Some(Opcode::SRem) => self.lower_srem(inst),
Some(Opcode::URem) => self.lower_urem(inst),
Some(Opcode::And) => vec![self.lower_and(inst)],
Some(Opcode::Or) => vec![self.lower_or(inst)],
Some(Opcode::Xor) => vec![self.lower_xor(inst)],
Some(Opcode::Shl) => self.lower_shl(inst),
Some(Opcode::LShr) => self.lower_lshr(inst),
Some(Opcode::AShr) => self.lower_ashr(inst),
Some(Opcode::FAdd) => self.lower_fadd(inst),
Some(Opcode::FSub) => self.lower_fsub(inst),
Some(Opcode::FMul) => self.lower_fmul(inst),
Some(Opcode::FDiv) => self.lower_fdiv(inst),
Some(Opcode::FRem) => self.lower_frem(inst),
Some(Opcode::Alloca) => vec![self.lower_alloca(inst)],
Some(Opcode::Load) => vec![self.lower_load(inst)],
Some(Opcode::Store) => vec![self.lower_store(inst)],
Some(Opcode::GetElementPtr) => self.lower_gep(inst),
Some(Opcode::Br) => self.lower_br(inst),
Some(Opcode::IndirectBr) => self.lower_indirectbr(inst),
Some(Opcode::Switch) => self.lower_switch(inst),
Some(Opcode::Ret) => vec![self.lower_ret(inst)],
Some(Opcode::Call) => self.lower_call(inst),
Some(Opcode::Invoke) => self.lower_invoke(inst),
Some(Opcode::Trunc) => vec![self.lower_trunc(inst)],
Some(Opcode::ZExt) => vec![self.lower_zext(inst)],
Some(Opcode::SExt) => vec![self.lower_sext(inst)],
Some(Opcode::FPTrunc) => vec![self.lower_fptrunc(inst)],
Some(Opcode::FPExt) => vec![self.lower_fpext(inst)],
Some(Opcode::FPToUI) => vec![self.lower_fptoui(inst)],
Some(Opcode::FPToSI) => vec![self.lower_fptosi(inst)],
Some(Opcode::UIToFP) => vec![self.lower_uitofp(inst)],
Some(Opcode::SIToFP) => vec![self.lower_sitofp(inst)],
Some(Opcode::PtrToInt) => vec![self.lower_ptrtoint(inst)],
Some(Opcode::IntToPtr) => vec![self.lower_inttoptr(inst)],
Some(Opcode::BitCast) => vec![self.lower_bitcast(inst)],
Some(Opcode::AddrSpaceCast) => vec![self.lower_addrspacecast(inst)],
Some(Opcode::ICmp) => self.lower_icmp(inst),
Some(Opcode::FCmp) => self.lower_fcmp(inst),
Some(Opcode::Select) => self.lower_select(inst),
Some(Opcode::Phi) => self.lower_phi(inst),
Some(Opcode::ExtractValue) => self.lower_extractvalue(inst),
Some(Opcode::InsertValue) => self.lower_insertvalue(inst),
Some(Opcode::ExtractElement) => vec![self.lower_extract_element(inst)],
Some(Opcode::InsertElement) => self.lower_insert_element(inst),
Some(Opcode::ShuffleVector) => vec![self.lower_shuffle_vector(inst)],
_ => vec![],
}
}
fn lower_add(&mut self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
mi
}
fn lower_sub(&mut self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::SUB as u32);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
mi
}
fn lower_mul(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::IMUL as u32);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
vec![mi]
}
fn lower_sdiv(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let bit_width = inst.ty.integer_bit_width();
let mut instrs = Vec::new();
if bit_width == 8 {
let mut mov_al = MachineInstr::new(X86Opcode::MOV as u32);
mov_al.push_reg(dst);
mov_al.push_reg(src0);
mov_al.def = Some(dst);
instrs.push(mov_al);
let cbw = MachineInstr::new(X86Opcode::NOP as u32); instrs.push(cbw);
let mut div = MachineInstr::new(X86Opcode::IDIV as u32);
div.push_reg(src1);
div.def = Some(dst);
instrs.push(div);
} else {
let rax_vreg = self.get_or_create_vreg(0);
let mut mov_rax = MachineInstr::new(X86Opcode::MOV as u32);
mov_rax.push_reg(rax_vreg);
mov_rax.push_reg(src0);
mov_rax.def = Some(rax_vreg);
instrs.push(mov_rax);
let extend = MachineInstr::new(X86Opcode::NOP as u32);
instrs.push(extend);
let mut div = MachineInstr::new(X86Opcode::IDIV as u32);
div.push_reg(src1);
div.def = Some(dst);
instrs.push(div);
let mut mov_result = MachineInstr::new(X86Opcode::MOV as u32);
mov_result.push_reg(dst);
mov_result.push_reg(rax_vreg);
mov_result.def = Some(dst);
instrs.push(mov_result);
}
instrs
}
fn lower_udiv(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut instrs = Vec::new();
let rax_vreg = self.get_or_create_vreg(0);
let rdx_vreg = self.get_or_create_vreg(1);
let mut xor_dx = MachineInstr::new(X86Opcode::XOR as u32);
xor_dx.push_reg(rdx_vreg);
xor_dx.push_reg(rdx_vreg);
xor_dx.def = Some(rdx_vreg);
instrs.push(xor_dx);
let mut mov_rax = MachineInstr::new(X86Opcode::MOV as u32);
mov_rax.push_reg(rax_vreg);
mov_rax.push_reg(src0);
mov_rax.def = Some(rax_vreg);
instrs.push(mov_rax);
let mut div = MachineInstr::new(X86Opcode::DIV as u32);
div.push_reg(src1);
div.def = Some(dst);
instrs.push(div);
let mut mov_result = MachineInstr::new(X86Opcode::MOV as u32);
mov_result.push_reg(dst);
mov_result.push_reg(rax_vreg);
mov_result.def = Some(dst);
instrs.push(mov_result);
instrs
}
fn lower_srem(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut instrs = Vec::new();
let rax_vreg = self.get_or_create_vreg(0);
let rdx_vreg = self.get_or_create_vreg(1);
let mut mov_rax = MachineInstr::new(X86Opcode::MOV as u32);
mov_rax.push_reg(rax_vreg);
mov_rax.push_reg(src0);
mov_rax.def = Some(rax_vreg);
instrs.push(mov_rax);
let extend = MachineInstr::new(X86Opcode::NOP as u32);
instrs.push(extend);
let mut div = MachineInstr::new(X86Opcode::IDIV as u32);
div.push_reg(src1);
div.def = Some(dst);
instrs.push(div);
let mut mov_result = MachineInstr::new(X86Opcode::MOV as u32);
mov_result.push_reg(dst);
mov_result.push_reg(rdx_vreg);
mov_result.def = Some(dst);
instrs.push(mov_result);
instrs
}
fn lower_urem(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut instrs = Vec::new();
let rax_vreg = self.get_or_create_vreg(0);
let rdx_vreg = self.get_or_create_vreg(1);
let mut xor_dx = MachineInstr::new(X86Opcode::XOR as u32);
xor_dx.push_reg(rdx_vreg);
xor_dx.push_reg(rdx_vreg);
xor_dx.def = Some(rdx_vreg);
instrs.push(xor_dx);
let mut mov_rax = MachineInstr::new(X86Opcode::MOV as u32);
mov_rax.push_reg(rax_vreg);
mov_rax.push_reg(src0);
mov_rax.def = Some(rax_vreg);
instrs.push(mov_rax);
let mut div = MachineInstr::new(X86Opcode::DIV as u32);
div.push_reg(src1);
div.def = Some(dst);
instrs.push(div);
let mut mov_result = MachineInstr::new(X86Opcode::MOV as u32);
mov_result.push_reg(dst);
mov_result.push_reg(rdx_vreg);
mov_result.def = Some(dst);
instrs.push(mov_result);
instrs
}
fn lower_and(&mut self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::AND as u32);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
mi
}
fn lower_or(&mut self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::OR as u32);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
mi
}
fn lower_xor(&mut self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::XOR as u32);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
mi
}
fn lower_shl(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut instrs = Vec::new();
let mut mov = MachineInstr::new(X86Opcode::MOV as u32);
mov.push_reg(dst);
mov.push_reg(src0);
mov.def = Some(dst);
instrs.push(mov);
let mut shl = MachineInstr::new(X86Opcode::SHL as u32);
shl.push_reg(dst);
shl.push_reg(src1);
shl.def = Some(dst);
instrs.push(shl);
instrs
}
fn lower_lshr(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut instrs = Vec::new();
let mut mov = MachineInstr::new(X86Opcode::MOV as u32);
mov.push_reg(dst);
mov.push_reg(src0);
mov.def = Some(dst);
instrs.push(mov);
let mut shr = MachineInstr::new(X86Opcode::SHR as u32);
shr.push_reg(dst);
shr.push_reg(src1);
shr.def = Some(dst);
instrs.push(shr);
instrs
}
fn lower_ashr(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let mut instrs = Vec::new();
let mut mov = MachineInstr::new(X86Opcode::MOV as u32);
mov.push_reg(dst);
mov.push_reg(src0);
mov.def = Some(dst);
instrs.push(mov);
let mut sar = MachineInstr::new(X86Opcode::SAR as u32);
sar.push_reg(dst);
sar.push_reg(src1);
sar.def = Some(dst);
instrs.push(sar);
instrs
}
fn lower_fadd(&self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg_clone(inst);
let src0 = self.resolve_operand_vreg_clone(&inst.operands[0]);
let src1 = self.resolve_operand_vreg_clone(&inst.operands[1]);
let opcode = if inst.ty.is_float_ty() {
X86Opcode::ADDSS as u32
} else {
X86Opcode::ADDSD as u32
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
vec![mi]
}
fn lower_fsub(&self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg_clone(inst);
let src0 = self.resolve_operand_vreg_clone(&inst.operands[0]);
let src1 = self.resolve_operand_vreg_clone(&inst.operands[1]);
let opcode = if inst.ty.is_float_ty() {
X86Opcode::SUBSS as u32
} else {
X86Opcode::SUBSD as u32
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
vec![mi]
}
fn lower_fmul(&self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg_clone(inst);
let src0 = self.resolve_operand_vreg_clone(&inst.operands[0]);
let src1 = self.resolve_operand_vreg_clone(&inst.operands[1]);
let opcode = if inst.ty.is_float_ty() {
X86Opcode::MULSS as u32
} else {
X86Opcode::MULSD as u32
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
vec![mi]
}
fn lower_fdiv(&self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg_clone(inst);
let src0 = self.resolve_operand_vreg_clone(&inst.operands[0]);
let src1 = self.resolve_operand_vreg_clone(&inst.operands[1]);
let opcode = if inst.ty.is_float_ty() {
X86Opcode::DIVSS as u32
} else {
X86Opcode::DIVSD as u32
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
mi.def = Some(dst);
vec![mi]
}
fn lower_frem(&self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg_clone(inst);
let func_name = if inst.ty.is_float_ty() {
"fmodf"
} else {
"fmod"
};
let mut mi = MachineInstr::new(X86Opcode::CALL as u32);
mi.push_label(func_name);
mi.def = Some(dst);
vec![mi]
}
fn lower_alloca(&mut self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg(inst);
let fi = self.next_frame_index;
self.next_frame_index += 1;
let size = self.get_alloca_size(inst);
self.total_frame_size += size;
self.frame_index_map.insert(fi, -self.total_frame_size);
let mut mi = MachineInstr::new(X86Opcode::LEA as u32);
mi.push_reg(dst);
mi.push_reg(self.fp_vreg);
mi.push_imm(-self.total_frame_size as i64);
mi.def = Some(dst);
mi
}
fn get_alloca_size(&self, inst: &crate::value::Value) -> i32 {
if inst.operands.is_empty() {
return 8; }
let size_val = inst.operands[0].borrow();
if size_val.subclass.is_constant() {
if let Some(val) = self.extract_constant_int(&inst.operands[0]) {
return val as i32;
}
}
8
}
fn lower_load(&mut self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg(inst);
let addr = self.resolve_operand_vreg(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(addr);
mi.def = Some(dst);
mi
}
fn lower_store(&mut self, inst: &crate::value::Value) -> MachineInstr {
let src = self.resolve_operand_vreg(&inst.operands[0]);
let addr = self.resolve_operand_vreg(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(addr); mi.push_reg(src); mi
}
fn lower_gep(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let base_ptr = self.resolve_operand_vreg(&inst.operands[0]);
let mut instrs = Vec::new();
if inst.operands.len() <= 2 {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(base_ptr);
mi.def = Some(dst);
instrs.push(mi);
} else {
let mut current = base_ptr;
for i in 1..inst.operands.len() {
let idx = self.resolve_operand_vreg(&inst.operands[i]);
let temp_vreg = if i + 1 < inst.operands.len() {
self.get_or_create_vreg(inst.vid + i as u64)
} else {
dst
};
let mut lea = MachineInstr::new(X86Opcode::LEA as u32);
lea.push_reg(temp_vreg);
lea.push_reg(current);
lea.push_reg(idx);
lea.def = Some(temp_vreg);
instrs.push(lea);
current = temp_vreg;
}
}
instrs
}
fn lower_br(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
if inst.operands.len() == 1 {
let target = inst.operands[0].borrow();
let mut mi = MachineInstr::new(X86Opcode::JMP as u32);
mi.push_label(&target.name);
self.mbb.successors.push(0); vec![mi]
} else {
let cond_vreg = self.resolve_operand_vreg(&inst.operands[0]);
let true_bb = inst.operands[1].borrow();
let false_bb = inst.operands[2].borrow();
self.mbb.successors.push(0); self.mbb.successors.push(0);
let mut instrs = Vec::new();
let mut test = MachineInstr::new(X86Opcode::TEST as u32);
test.push_reg(cond_vreg);
test.push_imm(1);
instrs.push(test);
let mut jne = MachineInstr::new(X86Opcode::JNE as u32);
jne.push_label(&true_bb.name);
instrs.push(jne);
let mut jmp = MachineInstr::new(X86Opcode::JMP as u32);
jmp.push_label(&false_bb.name);
instrs.push(jmp);
instrs
}
}
fn lower_indirectbr(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let addr = self.resolve_operand_vreg(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::JMP as u32);
mi.push_reg(addr);
for _i in 1..inst.operands.len() {
self.mbb.successors.push(0); }
vec![mi]
}
fn lower_switch(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
if inst.operands.len() < 3 {
return vec![];
}
let cond = self.resolve_operand_vreg(&inst.operands[0]);
self.mbb.successors.push(0);
let mut instrs = Vec::new();
let num_cases = (inst.operands.len() - 2) / 2;
for case_i in 0..num_cases {
let case_val = inst.operands[2 + case_i * 2].borrow();
let target_bb = inst.operands[2 + case_i * 2 + 1].borrow();
self.mbb.successors.push(0);
let case_imm = self.extract_constant_int_val(&case_val).unwrap_or(0);
let mut cmp = MachineInstr::new(X86Opcode::CMP as u32);
cmp.push_reg(cond);
cmp.push_imm(case_imm);
instrs.push(cmp);
let mut je = MachineInstr::new(X86Opcode::JE as u32);
je.push_label(&target_bb.name);
instrs.push(je);
}
let def_bb = inst.operands[1].borrow();
let mut jmp = MachineInstr::new(X86Opcode::JMP as u32);
jmp.push_label(&def_bb.name);
instrs.push(jmp);
instrs
}
fn lower_ret(&self, inst: &crate::value::Value) -> MachineInstr {
if inst.operands.is_empty() {
MachineInstr::new(X86Opcode::RET as u32)
} else {
let ret_val = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::RET as u32);
mi.push_reg(ret_val);
mi
}
}
fn lower_call(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let callee = inst.operands[0].borrow();
let is_direct = callee.subclass == crate::value::SubclassKind::Function;
let num_args = inst.operands.len() - 1;
for i in 0..num_args {
let arg = self.resolve_operand_vreg(&inst.operands[1 + i]);
let mut push = MachineInstr::new(X86Opcode::PUSH as u32);
push.push_reg(arg);
instrs.push(push);
}
let mut call = MachineInstr::new(X86Opcode::CALL as u32);
if is_direct {
call.push_label(&callee.name);
} else {
let callee_vreg = self.resolve_operand_vreg(&inst.operands[0]);
call.push_reg(callee_vreg);
}
if !inst.ty.is_void() {
let dst = self.get_dst_vreg(inst);
call.def = Some(dst);
}
instrs.push(call);
if num_args > 0 {
let mut add_sp = MachineInstr::new(X86Opcode::ADD as u32);
add_sp.push_reg(self.sp_vreg);
add_sp.push_imm((num_args * 8) as i64);
instrs.push(add_sp);
}
instrs
}
fn lower_invoke(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
if inst.operands.len() < 3 {
return instrs;
}
let callee = inst.operands[0].borrow();
self.mbb.successors.push(0); self.mbb.successors.push(0);
let num_args = inst.operands.len() - 3;
for i in 0..num_args {
let arg = self.resolve_operand_vreg(&inst.operands[3 + i]);
let mut push = MachineInstr::new(X86Opcode::PUSH as u32);
push.push_reg(arg);
instrs.push(push);
}
let mut call = MachineInstr::new(X86Opcode::CALL as u32);
if callee.subclass == crate::value::SubclassKind::Function {
call.push_label(&callee.name);
} else {
let callee_vreg = self.resolve_operand_vreg(&inst.operands[0]);
call.push_reg(callee_vreg);
}
if !inst.ty.is_void() {
let dst = self.get_dst_vreg(inst);
call.def = Some(dst);
}
instrs.push(call);
if num_args > 0 {
let mut add_sp = MachineInstr::new(X86Opcode::ADD as u32);
add_sp.push_reg(self.sp_vreg);
add_sp.push_imm((num_args * 8) as i64);
instrs.push(add_sp);
}
let normal_bb_ref = inst.operands[1].borrow();
let mut jmp = MachineInstr::new(X86Opcode::JMP as u32);
jmp.push_label(&normal_bb_ref.name);
instrs.push(jmp);
instrs
}
fn lower_trunc(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_zext(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::MOVZX as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_sext(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::MOVSX as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_fptrunc(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::CVTSD2SS as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_fpext(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::CVTSS2SD as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_fptosi(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let opcode = if inst.operands[0].borrow().ty.is_float_ty() {
X86Opcode::CVTTSS2SI as u32
} else {
X86Opcode::CVTTSD2SI as u32
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_fptoui(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let opcode = if inst.operands[0].borrow().ty.is_float_ty() {
X86Opcode::CVTTSS2SI as u32
} else {
X86Opcode::CVTTSD2SI as u32
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_sitofp(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let opcode = if inst.ty.is_float_ty() {
X86Opcode::CVTSI2SS as u32
} else {
X86Opcode::CVTSI2SD as u32
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_uitofp(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let opcode = if inst.ty.is_float_ty() {
X86Opcode::CVTSI2SS as u32
} else {
X86Opcode::CVTSI2SD as u32
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_ptrtoint(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_inttoptr(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_bitcast(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn lower_addrspacecast(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src = self.resolve_operand_vreg_clone(&inst.operands[0]);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
fn icmp_pred_to_cc(pred: u32) -> X86ConditionCode {
match pred {
icmp_pred::EQ => X86ConditionCode::Z,
icmp_pred::NE => X86ConditionCode::NZ,
icmp_pred::UGT => X86ConditionCode::A,
icmp_pred::UGE => X86ConditionCode::NB,
icmp_pred::ULT => X86ConditionCode::B,
icmp_pred::ULE => X86ConditionCode::BE,
icmp_pred::SGT => X86ConditionCode::G,
icmp_pred::SGE => X86ConditionCode::GE,
icmp_pred::SLT => X86ConditionCode::L,
icmp_pred::SLE => X86ConditionCode::LE,
_ => X86ConditionCode::Z,
}
}
fn icmp_pred_to_setcc(pred: u32) -> u32 {
match pred {
icmp_pred::EQ => X86Opcode::SETE as u32,
icmp_pred::NE => X86Opcode::SETNE as u32,
icmp_pred::UGT => X86Opcode::SETA as u32,
icmp_pred::UGE => X86Opcode::SETAE as u32,
icmp_pred::ULT => X86Opcode::SETB as u32,
icmp_pred::ULE => X86Opcode::SETBE as u32,
icmp_pred::SGT => X86Opcode::SETG as u32,
icmp_pred::SGE => X86Opcode::SETGE as u32,
icmp_pred::SLT => X86Opcode::SETL as u32,
icmp_pred::SLE => X86Opcode::SETLE as u32,
_ => X86Opcode::SETE as u32,
}
}
fn lower_icmp(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let pred = inst.subclass_data;
let mut instrs = Vec::new();
let mut cmp = MachineInstr::new(X86Opcode::CMP as u32);
cmp.push_reg(src0);
cmp.push_reg(src1);
instrs.push(cmp);
let setcc = Self::icmp_pred_to_setcc(pred);
let mut set = MachineInstr::new(setcc);
set.push_reg(dst);
set.def = Some(dst);
instrs.push(set);
let mut movzx = MachineInstr::new(X86Opcode::MOVZX as u32);
movzx.push_reg(dst);
movzx.push_reg(dst);
movzx.def = Some(dst);
instrs.push(movzx);
instrs
}
fn fcmp_pred_to_cc(pred: u32) -> X86ConditionCode {
match pred {
fcmp_pred::OEQ | fcmp_pred::UEQ => X86ConditionCode::Z,
fcmp_pred::OGT | fcmp_pred::UGT => X86ConditionCode::A,
fcmp_pred::OGE | fcmp_pred::UGE => X86ConditionCode::NB,
fcmp_pred::OLT | fcmp_pred::ULT => X86ConditionCode::B,
fcmp_pred::OLE | fcmp_pred::ULE => X86ConditionCode::BE,
fcmp_pred::ONE | fcmp_pred::UNE => X86ConditionCode::NZ,
fcmp_pred::ORD => X86ConditionCode::NP, fcmp_pred::UNO => X86ConditionCode::P, _ => X86ConditionCode::Z,
}
}
fn lower_fcmp(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let src0 = self.resolve_operand_vreg(&inst.operands[0]);
let src1 = self.resolve_operand_vreg(&inst.operands[1]);
let pred = inst.subclass_data;
let mut instrs = Vec::new();
let ucomi_opcode = if inst.operands[0].borrow().ty.is_float_ty() {
X86Opcode::UCOMISS as u32
} else {
X86Opcode::UCOMISD as u32
};
let mut ucomi = MachineInstr::new(ucomi_opcode);
ucomi.push_reg(src0);
ucomi.push_reg(src1);
instrs.push(ucomi);
match pred {
fcmp_pred::FALSE => {
let mut xor_mi = MachineInstr::new(X86Opcode::XOR as u32);
xor_mi.push_reg(dst);
xor_mi.push_reg(dst);
xor_mi.def = Some(dst);
instrs.push(xor_mi);
return instrs;
}
fcmp_pred::TRUE => {
let mut mov_mi = MachineInstr::new(X86Opcode::MOV as u32);
mov_mi.push_reg(dst);
mov_mi.push_imm(1);
mov_mi.def = Some(dst);
instrs.push(mov_mi);
return instrs;
}
_ => {}
}
let cc = Self::fcmp_pred_to_cc(pred);
let setcc_opcode = match cc {
X86ConditionCode::Z => X86Opcode::SETE as u32,
X86ConditionCode::NZ => X86Opcode::SETNE as u32,
X86ConditionCode::A => X86Opcode::SETA as u32,
X86ConditionCode::NB => X86Opcode::SETAE as u32,
X86ConditionCode::B => X86Opcode::SETB as u32,
X86ConditionCode::BE => X86Opcode::SETBE as u32,
X86ConditionCode::NP => X86Opcode::SETNP as u32,
X86ConditionCode::P => X86Opcode::SETP as u32,
_ => X86Opcode::SETE as u32,
};
let mut set = MachineInstr::new(setcc_opcode);
set.push_reg(dst);
set.def = Some(dst);
instrs.push(set);
let mut movzx = MachineInstr::new(X86Opcode::MOVZX as u32);
movzx.push_reg(dst);
movzx.push_reg(dst);
movzx.def = Some(dst);
instrs.push(movzx);
if matches!(
pred,
fcmp_pred::UEQ
| fcmp_pred::UGT
| fcmp_pred::UGE
| fcmp_pred::ULT
| fcmp_pred::ULE
| fcmp_pred::UNE
) {
}
instrs
}
fn lower_select(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let cond = self.resolve_operand_vreg(&inst.operands[0]);
let true_val = self.resolve_operand_vreg(&inst.operands[1]);
let false_val = self.resolve_operand_vreg(&inst.operands[2]);
let mut instrs = Vec::new();
let mut test = MachineInstr::new(X86Opcode::TEST as u32);
test.push_reg(cond);
test.push_imm(1);
instrs.push(test);
let mut mov_false = MachineInstr::new(X86Opcode::MOV as u32);
mov_false.push_reg(dst);
mov_false.push_reg(false_val);
mov_false.def = Some(dst);
instrs.push(mov_false);
let mut cmov = MachineInstr::new(X86Opcode::CMOVNE as u32);
cmov.push_reg(dst);
cmov.push_reg(true_val);
cmov.def = Some(dst);
instrs.push(cmov);
instrs
}
fn lower_phi(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let mut instrs = Vec::new();
for i in (0..inst.operands.len()).step_by(2) {
if i + 1 < inst.operands.len() {
let src = self.resolve_operand_vreg(&inst.operands[i]);
let mut mov = MachineInstr::new(X86Opcode::MOV as u32);
mov.push_reg(dst);
mov.push_reg(src);
mov.def = Some(dst);
instrs.push(mov);
break; }
}
instrs
}
fn lower_extractvalue(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let agg = self.resolve_operand_vreg(&inst.operands[0]);
let mut instrs = Vec::new();
let offset = self.compute_extractvalue_offset(inst);
let mut lea = MachineInstr::new(X86Opcode::LEA as u32);
lea.push_reg(dst);
lea.push_reg(agg);
lea.push_imm(offset as i64);
lea.def = Some(dst);
instrs.push(lea);
instrs
}
fn compute_extractvalue_offset(&self, inst: &crate::value::Value) -> i32 {
let mut offset: i32 = 0;
for i in 1..inst.operands.len() {
if let Some(val) = self.extract_constant_int(&inst.operands[i]) {
offset += (val as i32) * 8; }
}
offset
}
fn lower_insertvalue(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let agg = self.resolve_operand_vreg(&inst.operands[0]);
let field_val = self.resolve_operand_vreg(&inst.operands[1]);
let mut instrs = Vec::new();
let offset = self.compute_insertvalue_offset(inst);
let mut mov_agg = MachineInstr::new(X86Opcode::MOV as u32);
mov_agg.push_reg(dst);
mov_agg.push_reg(agg);
mov_agg.def = Some(dst);
instrs.push(mov_agg);
let mut store_field = MachineInstr::new(X86Opcode::MOV as u32);
store_field.push_reg(dst);
store_field.push_imm(offset as i64);
store_field.push_reg(field_val);
instrs.push(store_field);
instrs
}
fn compute_insertvalue_offset(&self, inst: &crate::value::Value) -> i32 {
let mut offset: i32 = 0;
for i in 2..inst.operands.len() {
if let Some(val) = self.extract_constant_int(&inst.operands[i]) {
offset += (val as i32) * 8;
}
}
offset
}
fn lower_extract_element(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let vec = self.resolve_operand_vreg_clone(&inst.operands[0]);
let idx = self.resolve_operand_vreg_clone(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::PEXTRD as u32);
mi.push_reg(dst);
mi.push_reg(vec);
mi.push_reg(idx);
mi.def = Some(dst);
mi
}
fn lower_insert_element(&mut self, inst: &crate::value::Value) -> Vec<MachineInstr> {
let dst = self.get_dst_vreg(inst);
let vec = self.resolve_operand_vreg(&inst.operands[0]);
let elem = self.resolve_operand_vreg(&inst.operands[1]);
let idx = self.resolve_operand_vreg(&inst.operands[2]);
let mut instrs = Vec::new();
let mut mov = MachineInstr::new(X86Opcode::MOV as u32);
mov.push_reg(dst);
mov.push_reg(vec);
mov.def = Some(dst);
instrs.push(mov);
let mut pinsr = MachineInstr::new(X86Opcode::PINSRD as u32);
pinsr.push_reg(dst);
pinsr.push_reg(elem);
pinsr.push_reg(idx);
pinsr.def = Some(dst);
instrs.push(pinsr);
instrs
}
fn lower_shuffle_vector(&self, inst: &crate::value::Value) -> MachineInstr {
let dst = self.get_dst_vreg_clone(inst);
let src0 = self.resolve_operand_vreg_clone(&inst.operands[0]);
let src1 = self.resolve_operand_vreg_clone(&inst.operands[1]);
let mut mi = MachineInstr::new(X86Opcode::SHUFPS as u32);
mi.push_reg(dst);
mi.push_reg(src0);
mi.push_reg(src1);
if let Some(mask_val) = self.extract_shuffle_mask(inst) {
mi.push_imm(mask_val as i64);
} else {
mi.push_imm(0); }
mi.def = Some(dst);
mi
}
fn extract_shuffle_mask(&self, inst: &crate::value::Value) -> Option<u8> {
if inst.operands.len() > 2 {
if let Some(val) = self.extract_constant_int(&inst.operands[2]) {
return Some(val as u8);
}
}
if inst.subclass_data > 0 {
return Some(inst.subclass_data as u8);
}
None
}
fn get_dst_vreg(&mut self, inst: &crate::value::Value) -> u32 {
self.reg_alloc.get_or_create_vreg(inst.vid)
}
fn get_dst_vreg_clone(&self, inst: &crate::value::Value) -> u32 {
self.reg_alloc.get_vreg(inst.vid).unwrap_or(0)
}
fn resolve_operand_vreg(&mut self, operand: &ValueRef) -> u32 {
let val = operand.borrow();
if let Some(vreg) = self.reg_alloc.get_vreg(val.vid) {
return vreg;
}
if val.subclass.is_constant() {
let vreg = self.reg_alloc.create_vreg(val.vid);
let imm_val = self.extract_constant_int_val(&val).unwrap_or(0);
let mov_instrs = self.materializer.emit_mov_imm64(vreg, imm_val);
for mi in mov_instrs {
self.mbb.instructions.push(mi);
}
return vreg;
}
self.reg_alloc.get_or_create_vreg(val.vid)
}
fn resolve_operand_vreg_clone(&self, operand: &ValueRef) -> u32 {
let val = operand.borrow();
self.reg_alloc.get_vreg(val.vid).unwrap_or(0)
}
fn get_or_create_vreg(&mut self, vid: u64) -> u32 {
self.reg_alloc.get_or_create_vreg(vid)
}
fn extract_constant_int(&self, val_ref: &ValueRef) -> Option<i64> {
let val = val_ref.borrow();
self.extract_constant_int_val(&val)
}
fn extract_constant_int_val(&self, val: &crate::value::Value) -> Option<i64> {
if val.subclass == crate::value::SubclassKind::ConstantInt {
if val.name.starts_with("i64 ") {
if let Ok(v) = val.name[4..].trim().parse::<i64>() {
return Some(v);
}
}
if val.name.starts_with("i32 ") {
if let Ok(v) = val.name[4..].trim().parse::<i32>() {
return Some(v as i64);
}
}
let data = val.subclass_data;
if data != 0 {
return Some(data as i64);
}
if let Ok(v) = val.name.parse::<i64>() {
return Some(v);
}
}
None
}
}
pub struct X86FastISelBuilder {
subtarget: Option<X86Subtarget>,
}
impl X86FastISelBuilder {
pub fn new() -> Self {
Self { subtarget: None }
}
pub fn subtarget(mut self, st: X86Subtarget) -> Self {
self.subtarget = Some(st);
self
}
pub fn build(self) -> X86FastISel {
X86FastISel::new(
self.subtarget
.unwrap_or_else(|| X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", "")),
)
}
}
impl Default for X86FastISelBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct FastISelResult {
pub mf: MachineFunction,
pub num_blocks: usize,
pub num_instructions: usize,
pub num_vregs: u32,
pub frame_size: i32,
pub constant_pool: Vec<(String, i64)>,
}
impl FastISelResult {
pub fn new(
mf: MachineFunction,
num_blocks: usize,
num_instructions: usize,
num_vregs: u32,
frame_size: i32,
constant_pool: Vec<(String, i64)>,
) -> Self {
Self {
mf,
num_blocks,
num_instructions,
num_vregs,
frame_size,
constant_pool,
}
}
}
pub fn run_x86_fast_isel(func: &ValueRef, subtarget: Option<X86Subtarget>) -> FastISelResult {
let st =
subtarget.unwrap_or_else(|| X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""));
let mut fast_isel = X86FastISel::new(st);
let mut mf = MachineFunction::new("");
fast_isel.run_on_function(&mut mf, func);
let num_blocks = fast_isel.all_blocks.len();
let num_instructions: usize = fast_isel
.all_blocks
.iter()
.map(|b| b.instructions.len())
.sum();
let num_vregs = fast_isel.reg_alloc.next_vreg;
let frame_size = fast_isel.total_frame_size;
let constant_pool = fast_isel.materializer.constant_pool.clone();
FastISelResult::new(
mf,
num_blocks,
num_instructions,
num_vregs,
frame_size,
constant_pool,
)
}
#[derive(Debug, Clone)]
pub struct AddressLoweringContext {
pub base: Option<u32>,
pub index: Option<u32>,
pub scale: u8,
pub displacement: i32,
pub is_frame_index: bool,
pub frame_index: i32,
pub is_global: bool,
pub global_name: Option<String>,
pub is_constant_pool: bool,
pub constant_pool_index: usize,
}
impl AddressLoweringContext {
pub fn new() -> Self {
Self {
base: None,
index: None,
scale: 1,
displacement: 0,
is_frame_index: false,
frame_index: 0,
is_global: false,
global_name: None,
is_constant_pool: false,
constant_pool_index: 0,
}
}
pub fn is_simple(&self) -> bool {
self.index.is_none() && !self.is_global && !self.is_constant_pool && !self.is_frame_index
}
pub fn is_rip_relative(&self) -> bool {
self.is_global || self.is_constant_pool
}
pub fn to_base_disp(&self) -> (Option<u32>, i32) {
(self.base, self.displacement)
}
}
impl Default for AddressLoweringContext {
fn default() -> Self {
Self::new()
}
}
pub fn lower_gep_to_address(
inst: &crate::value::Value,
vreg_map: &HashMap<u64, u32>,
const_extractor: &dyn Fn(&ValueRef) -> Option<i64>,
) -> AddressLoweringContext {
let mut ctx = AddressLoweringContext::new();
if inst.operands.is_empty() {
return ctx;
}
let base_val = inst.operands[0].borrow();
if let Some(vreg) = vreg_map.get(&base_val.vid) {
ctx.base = Some(*vreg);
}
for i in 1..inst.operands.len() {
let idx_val = inst.operands[i].borrow();
if let Some(const_val) = const_extractor(&inst.operands[i]) {
ctx.displacement += (const_val as i32).wrapping_mul(8); } else if let Some(idx_vreg) = vreg_map.get(&idx_val.vid) {
ctx.index = Some(*idx_vreg);
ctx.scale = 8; }
}
ctx
}
pub fn try_fold_address(
mi: &mut MachineInstr,
base: u32,
displacement: i32,
index: Option<u32>,
scale: u8,
) -> bool {
mi.push_reg(base);
if let Some(idx) = index {
mi.push_reg(idx);
mi.push_imm(scale as i64);
}
if displacement != 0 || index.is_none() {
mi.push_imm(displacement as i64);
}
true
}
pub fn lower_frame_index(
frame_index: i32,
frame_index_map: &HashMap<i32, i32>,
is_64bit: bool,
) -> (u16, i32) {
let offset = frame_index_map.get(&frame_index).copied().unwrap_or(0);
if is_64bit {
(RBP, offset)
} else {
(EBP, offset)
}
}
pub fn lower_global_address(name: &str, offset: i64, is_64bit: bool) -> FastAddressMode {
if is_64bit {
FastAddressMode::Global {
name: name.to_string(),
offset,
}
} else {
FastAddressMode::Absolute(offset)
}
}
#[derive(Debug, Clone)]
pub struct LiveInterval {
pub vreg: u32,
pub start: u32,
pub end: u32,
pub spilled: bool,
pub spill_slot: i32,
pub phys_reg: Option<u16>,
pub is_float: bool,
}
impl LiveInterval {
pub fn new(vreg: u32, start: u32, end: u32, is_float: bool) -> Self {
Self {
vreg,
start,
end,
spilled: false,
spill_slot: -1,
phys_reg: None,
is_float,
}
}
pub fn overlaps(&self, other: &LiveInterval) -> bool {
self.start <= other.end && other.start <= self.end
}
pub fn covers(&self, instr_idx: u32) -> bool {
instr_idx >= self.start && instr_idx <= self.end
}
}
pub struct SpillManager {
pub slot_assignments: HashMap<u32, i32>,
pub next_slot: i32,
pub max_slot: i32,
pub spill_code: Vec<(u32, MachineInstr)>,
pub reload_code: Vec<(u32, MachineInstr)>,
}
impl SpillManager {
pub fn new() -> Self {
Self {
slot_assignments: HashMap::new(),
next_slot: 0,
max_slot: 0,
spill_code: Vec::new(),
reload_code: Vec::new(),
}
}
pub fn allocate_slot(&mut self, vreg: u32) -> i32 {
if let Some(&slot) = self.slot_assignments.get(&vreg) {
return slot;
}
let slot = self.next_slot;
self.next_slot += STACK_SLOT_SIZE;
self.max_slot = self.max_slot.max(slot);
self.slot_assignments.insert(vreg, slot);
slot
}
pub fn get_slot(&self, vreg: u32) -> Option<i32> {
self.slot_assignments.get(&vreg).copied()
}
pub fn emit_spill(&mut self, vreg: u32, instr_idx: u32, is_float: bool) {
let slot = self.allocate_slot(vreg);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(0); mi.push_imm(slot as i64);
mi.push_reg(vreg);
self.spill_code.push((instr_idx, mi));
}
pub fn emit_reload(&mut self, vreg: u32, instr_idx: u32, is_float: bool) {
let slot = self.allocate_slot(vreg);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(vreg);
mi.push_reg(0); mi.push_imm(slot as i64);
mi.def = Some(vreg);
self.reload_code.push((instr_idx, mi));
}
pub fn total_spill_size(&self) -> i32 {
self.max_slot + STACK_SLOT_SIZE
}
}
impl Default for SpillManager {
fn default() -> Self {
Self::new()
}
}
pub fn get_mov_opcode_for_width(bit_width: u32) -> u32 {
match bit_width {
8 | 16 | 32 | 64 => X86Opcode::MOV as u32,
_ => X86Opcode::MOV as u32, }
}
pub fn get_add_opcode_for_width(_bit_width: u32) -> u32 {
X86Opcode::ADD as u32
}
pub fn get_sub_opcode_for_width(_bit_width: u32) -> u32 {
X86Opcode::SUB as u32
}
pub fn imm_fits_in_bits(imm: i64, bits: u32) -> bool {
if bits >= 64 {
return true;
}
if bits == 0 {
return imm == 0;
}
let min = -(1i64 << (bits - 1));
let max = (1i64 << (bits - 1)) - 1;
imm >= min && imm <= max
}
pub fn uimm_fits_in_bits(imm: i64, bits: u32) -> bool {
if bits >= 64 {
return true;
}
imm >= 0 && imm < (1i64 << bits)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecognizedIntrinsic {
Memcpy,
Memset,
Memmove,
Sqrt,
FAbs,
Round,
Ctpop,
Ctlz,
Cttz,
Bswap,
Bitreverse,
FunnelShift,
AddWithOverflow,
SubWithOverflow,
MulWithOverflow,
Lifetime,
DbgInfo,
EHTypeIdFor,
Trap,
StackProtector,
Unknown(String),
}
impl RecognizedIntrinsic {
pub fn from_name(name: &str) -> Option<Self> {
let name = name.trim();
if name.starts_with("llvm.memcpy") {
Some(RecognizedIntrinsic::Memcpy)
} else if name.starts_with("llvm.memset") {
Some(RecognizedIntrinsic::Memset)
} else if name.starts_with("llvm.memmove") {
Some(RecognizedIntrinsic::Memmove)
} else if name.starts_with("llvm.sqrt") {
Some(RecognizedIntrinsic::Sqrt)
} else if name.starts_with("llvm.fabs") {
Some(RecognizedIntrinsic::FAbs)
} else if name.starts_with("llvm.floor")
|| name.starts_with("llvm.ceil")
|| name.starts_with("llvm.trunc")
|| name.starts_with("llvm.rint")
|| name.starts_with("llvm.nearbyint")
{
Some(RecognizedIntrinsic::Round)
} else if name.starts_with("llvm.ctpop") {
Some(RecognizedIntrinsic::Ctpop)
} else if name.starts_with("llvm.ctlz") {
Some(RecognizedIntrinsic::Ctlz)
} else if name.starts_with("llvm.cttz") {
Some(RecognizedIntrinsic::Cttz)
} else if name.starts_with("llvm.bswap") {
Some(RecognizedIntrinsic::Bswap)
} else if name.starts_with("llvm.bitreverse") {
Some(RecognizedIntrinsic::Bitreverse)
} else if name.starts_with("llvm.fshl") || name.starts_with("llvm.fshr") {
Some(RecognizedIntrinsic::FunnelShift)
} else if name.starts_with("llvm.sadd.with.overflow")
|| name.starts_with("llvm.uadd.with.overflow")
{
Some(RecognizedIntrinsic::AddWithOverflow)
} else if name.starts_with("llvm.ssub.with.overflow")
|| name.starts_with("llvm.usub.with.overflow")
{
Some(RecognizedIntrinsic::SubWithOverflow)
} else if name.starts_with("llvm.smul.with.overflow")
|| name.starts_with("llvm.umul.with.overflow")
{
Some(RecognizedIntrinsic::MulWithOverflow)
} else if name.starts_with("llvm.lifetime") {
Some(RecognizedIntrinsic::Lifetime)
} else if name.starts_with("llvm.dbg") {
Some(RecognizedIntrinsic::DbgInfo)
} else if name.starts_with("llvm.eh.typeid.for") {
Some(RecognizedIntrinsic::EHTypeIdFor)
} else if name.starts_with("llvm.trap") {
Some(RecognizedIntrinsic::Trap)
} else if name.starts_with("llvm.stackprotector") {
Some(RecognizedIntrinsic::StackProtector)
} else if name.starts_with("llvm.") {
Some(RecognizedIntrinsic::Unknown(name.to_string()))
} else {
None
}
}
pub fn lower(&self, dst: Option<u32>, args: &[u32]) -> Vec<MachineInstr> {
match self {
RecognizedIntrinsic::Sqrt => {
if let Some(d) = dst {
if let Some(&src) = args.first() {
let mut mi = MachineInstr::new(X86Opcode::SQRTSD as u32);
mi.push_reg(d);
mi.push_reg(src);
mi.def = Some(d);
return vec![mi];
}
}
vec![]
}
RecognizedIntrinsic::Trap => {
vec![MachineInstr::new(X86Opcode::UD2 as u32)]
}
RecognizedIntrinsic::Lifetime | RecognizedIntrinsic::DbgInfo => {
vec![]
}
RecognizedIntrinsic::Bswap => {
if let Some(d) = dst {
if let Some(&src) = args.first() {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(d);
mi.push_reg(src);
mi.def = Some(d);
return vec![mi];
}
}
vec![]
}
RecognizedIntrinsic::Memcpy
| RecognizedIntrinsic::Memset
| RecognizedIntrinsic::Memmove => {
let func_name = match self {
RecognizedIntrinsic::Memset => "memset",
_ => "memcpy",
};
let mut mi = MachineInstr::new(X86Opcode::CALL as u32);
mi.push_label(func_name);
vec![mi]
}
_ => {
vec![]
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DebugLoc {
pub file: String,
pub line: u32,
pub column: u32,
pub inlined_at: Option<Box<DebugLoc>>,
}
impl DebugLoc {
pub fn new(file: &str, line: u32, column: u32) -> Self {
Self {
file: file.to_string(),
line,
column,
inlined_at: None,
}
}
pub fn with_inlined_at(file: &str, line: u32, column: u32, inlined: DebugLoc) -> Self {
Self {
file: file.to_string(),
line,
column,
inlined_at: Some(Box::new(inlined)),
}
}
pub fn is_valid(&self) -> bool {
!self.file.is_empty()
}
pub fn to_dwarf_string(&self) -> String {
if let Some(ref inlined) = self.inlined_at {
format!(
"{}:{}:{} @ {}",
self.file,
self.line,
self.column,
inlined.to_dwarf_string()
)
} else {
format!("{}:{}:{}", self.file, self.line, self.column)
}
}
}
impl Default for DebugLoc {
fn default() -> Self {
Self {
file: String::new(),
line: 0,
column: 0,
inlined_at: None,
}
}
}
#[derive(Debug, Clone)]
pub struct DebugVariable {
pub name: String,
pub type_name: String,
pub vreg: Option<u32>,
pub stack_offset: Option<i32>,
pub is_argument: bool,
pub decl_loc: DebugLoc,
}
impl DebugVariable {
pub fn new(name: &str, type_name: &str, is_argument: bool) -> Self {
Self {
name: name.to_string(),
type_name: type_name.to_string(),
vreg: None,
stack_offset: None,
is_argument,
decl_loc: DebugLoc::default(),
}
}
pub fn set_location(&mut self, vreg: Option<u32>, stack_offset: Option<i32>) {
self.vreg = vreg;
self.stack_offset = stack_offset;
}
}
pub struct DebugInfoTracker {
pub current_loc: DebugLoc,
pub variables: Vec<DebugVariable>,
pub instr_locations: Vec<(u32, DebugLoc)>,
pub enabled: bool,
}
impl DebugInfoTracker {
pub fn new(enabled: bool) -> Self {
Self {
current_loc: DebugLoc::default(),
variables: Vec::new(),
instr_locations: Vec::new(),
enabled,
}
}
pub fn set_location(&mut self, loc: DebugLoc) {
if self.enabled {
self.current_loc = loc;
}
}
pub fn record_instr_location(&mut self, instr_idx: u32, loc: DebugLoc) {
if self.enabled {
self.instr_locations.push((instr_idx, loc));
}
}
pub fn add_variable(&mut self, var: DebugVariable) {
if self.enabled {
self.variables.push(var);
}
}
pub fn find_variable(&self, name: &str) -> Option<&DebugVariable> {
self.variables.iter().find(|v| v.name == name)
}
pub fn get_location(&self, instr_idx: u32) -> Option<&DebugLoc> {
self.instr_locations
.iter()
.rev()
.find(|(idx, _)| *idx <= instr_idx)
.map(|(_, loc)| loc)
}
}
impl Default for DebugInfoTracker {
fn default() -> Self {
Self::new(false)
}
}
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub passed: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl VerificationResult {
pub fn new() -> Self {
Self {
passed: true,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn add_error(&mut self, msg: String) {
self.passed = false;
self.errors.push(msg);
}
pub fn add_warning(&mut self, msg: String) {
self.warnings.push(msg);
}
}
impl Default for VerificationResult {
fn default() -> Self {
Self::new()
}
}
pub fn verify_machine_instr(mi: &MachineInstr, instr_idx: u32) -> VerificationResult {
let mut result = VerificationResult::new();
if mi.opcode == 0 {
result.add_warning(format!("Instruction {} has opcode 0 (NOP)", instr_idx));
}
if mi.def.is_some() && mi.operands.is_empty() {
result.add_warning(format!(
"Instruction {} has a def but no operands",
instr_idx
));
}
if mi.operands.len() > 6 {
result.add_warning(format!(
"Instruction {} has {} operands (unusually high)",
instr_idx,
mi.operands.len()
));
}
result
}
pub fn verify_machine_function(mf: &MachineFunction) -> VerificationResult {
let mut result = VerificationResult::new();
for (block_idx, block) in mf.blocks.iter().enumerate() {
if block.name.is_empty() {
result.add_warning(format!("Block {} has no name", block_idx));
}
for (instr_idx, mi) in block.instructions.iter().enumerate() {
let instr_result = verify_machine_instr(mi, instr_idx as u32);
for e in &instr_result.errors {
result.add_error(format!("Block '{}' instr {}: {}", block.name, instr_idx, e));
}
for w in &instr_result.warnings {
result.add_warning(format!("Block '{}' instr {}: {}", block.name, instr_idx, w));
}
}
}
result
}
#[derive(Debug, Clone, Default)]
pub struct FastISelStats {
pub total_ir_instructions: usize,
pub total_machine_instructions: usize,
pub virtual_registers_created: u32,
pub physical_registers_allocated: u32,
pub spills: u32,
pub reloads: u32,
pub constants_materialized: u32,
pub frame_index_refs: u32,
pub global_address_refs: u32,
pub direct_calls: u32,
pub indirect_calls: u32,
pub elapsed_us: u64,
}
impl FastISelStats {
pub fn new() -> Self {
Self::default()
}
pub fn print_summary(&self) -> String {
format!(
"FastISel Stats: {} IR insts → {} MI insts, {} vregs, {} spills, {} reloads, {} consts, {} calls",
self.total_ir_instructions,
self.total_machine_instructions,
self.virtual_registers_created,
self.spills,
self.reloads,
self.constants_materialized,
self.direct_calls + self.indirect_calls
)
}
pub fn expansion_ratio(&self) -> f64 {
if self.total_ir_instructions == 0 {
0.0
} else {
self.total_machine_instructions as f64 / self.total_ir_instructions as f64
}
}
}
pub fn build_mov_rr(dst: u32, src: u32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
pub fn build_mov_ri(dst: u32, imm: i64) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_imm(imm);
mi.def = Some(dst);
mi
}
pub fn build_store_mr(addr: u32, src: u32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(addr);
mi.push_reg(src);
mi
}
pub fn build_load_rm(dst: u32, addr: u32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(dst);
mi.push_reg(addr);
mi.def = Some(dst);
mi
}
pub fn build_binary_op(opcode: u32, dst: u32, src: u32) -> MachineInstr {
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src);
mi.def = Some(dst);
mi
}
pub fn build_binary_op_3(opcode: u32, dst: u32, src1: u32, src2: u32) -> MachineInstr {
let mut mi = MachineInstr::new(opcode);
mi.push_reg(dst);
mi.push_reg(src1);
mi.push_reg(src2);
mi.def = Some(dst);
mi
}
pub fn build_ret(val: Option<u32>) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::RET as u32);
if let Some(v) = val {
mi.push_reg(v);
}
mi
}
pub fn build_jmp(label: &str) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::JMP as u32);
mi.push_label(label);
mi
}
pub fn build_jcc(cc: X86ConditionCode, label: &str) -> MachineInstr {
let opcode = match cc {
X86ConditionCode::O => X86Opcode::JO as u32,
X86ConditionCode::NO => X86Opcode::JNO as u32,
X86ConditionCode::B => X86Opcode::JB as u32,
X86ConditionCode::NB => X86Opcode::JAE as u32,
X86ConditionCode::Z => X86Opcode::JE as u32,
X86ConditionCode::NZ => X86Opcode::JNE as u32,
X86ConditionCode::BE => X86Opcode::JBE as u32,
X86ConditionCode::A => X86Opcode::JA as u32,
X86ConditionCode::S => X86Opcode::JS as u32,
X86ConditionCode::NS => X86Opcode::JNS as u32,
X86ConditionCode::P => X86Opcode::JP as u32,
X86ConditionCode::NP => X86Opcode::JNP as u32,
X86ConditionCode::L => X86Opcode::JL as u32,
X86ConditionCode::GE => X86Opcode::JGE as u32,
X86ConditionCode::LE => X86Opcode::JLE as u32,
X86ConditionCode::G => X86Opcode::JG as u32,
X86ConditionCode::T | X86ConditionCode::F => X86Opcode::JMP as u32,
};
let mut mi = MachineInstr::new(opcode);
mi.push_label(label);
mi
}
pub fn build_call_direct(func: &str) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::CALL as u32);
mi.push_label(func);
mi
}
pub fn build_call_indirect(reg: u32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::CALL as u32);
mi.push_reg(reg);
mi
}
pub fn build_cmp(lhs: u32, rhs: u32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::CMP as u32);
mi.push_reg(lhs);
mi.push_reg(rhs);
mi
}
pub fn build_test(reg: u32, imm: i64) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::TEST as u32);
mi.push_reg(reg);
mi.push_imm(imm);
mi
}
pub fn build_push(reg: u32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::PUSH as u32);
mi.push_reg(reg);
mi
}
pub fn build_pop(reg: u32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::POP as u32);
mi.push_reg(reg);
mi.def = Some(reg);
mi
}
pub fn build_lea(dst: u32, base: u32, index: Option<u32>, scale: u8, disp: i32) -> MachineInstr {
let mut mi = MachineInstr::new(X86Opcode::LEA as u32);
mi.push_reg(dst);
mi.push_reg(base);
if let Some(idx) = index {
mi.push_reg(idx);
mi.push_imm(scale as i64);
}
if disp != 0 || index.is_none() {
mi.push_imm(disp as i64);
}
mi.def = Some(dst);
mi
}
pub fn try_combine_cmp_br(
cmp_instr: &MachineInstr,
br_instr: &MachineInstr,
) -> Option<Vec<MachineInstr>> {
if cmp_instr.opcode == X86Opcode::CMP as u32 && br_instr.opcode == X86Opcode::JNE as u32 {
return None;
}
None
}
pub fn try_fold_load(
bin_op: &mut MachineInstr,
load_op: &MachineInstr,
_operand_idx: usize,
) -> bool {
if load_op.opcode == X86Opcode::MOV as u32 && load_op.def.is_some() {
return true;
}
false
}
pub fn try_fold_immediate(
bin_op: &mut MachineInstr,
_mov_imm: &MachineInstr,
_operand_idx: usize,
) -> bool {
matches!(
bin_op.opcode,
op if op == X86Opcode::ADD as u32
|| op == X86Opcode::SUB as u32
|| op == X86Opcode::AND as u32
|| op == X86Opcode::OR as u32
|| op == X86Opcode::XOR as u32
|| op == X86Opcode::CMP as u32
)
}
pub fn emit_prologue(
frame_size: i32,
use_frame_pointer: bool,
is_64bit: bool,
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
if use_frame_pointer {
instrs.push(build_push(if is_64bit { RBP as u32 } else { EBP as u32 }));
instrs.push(build_mov_rr(
if is_64bit { RBP as u32 } else { EBP as u32 },
if is_64bit { RSP as u32 } else { ESP as u32 },
));
}
if frame_size > 0 {
let mut sub = MachineInstr::new(X86Opcode::SUB as u32);
sub.push_reg(if is_64bit { RSP as u32 } else { ESP as u32 });
sub.push_imm(frame_size as i64);
instrs.push(sub);
}
let callee_saved = if is_64bit {
&CALLEE_SAVED_GPRS[..5] } else {
&CALLEE_SAVED_GPRS_32[..2] };
for ® in callee_saved {
instrs.push(build_push(reg as u32));
}
instrs
}
pub fn emit_epilogue(
frame_size: i32,
use_frame_pointer: bool,
is_64bit: bool,
) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let callee_saved = if is_64bit {
&CALLEE_SAVED_GPRS[..5]
} else {
&CALLEE_SAVED_GPRS_32[..2]
};
for ® in callee_saved.iter().rev() {
instrs.push(build_pop(reg as u32));
}
if use_frame_pointer {
instrs.push(build_mov_rr(
if is_64bit { RSP as u32 } else { ESP as u32 },
if is_64bit { RBP as u32 } else { EBP as u32 },
));
instrs.push(build_pop(if is_64bit { RBP as u32 } else { EBP as u32 }));
} else if frame_size > 0 {
let mut add = MachineInstr::new(X86Opcode::ADD as u32);
add.push_reg(if is_64bit { RSP as u32 } else { ESP as u32 });
add.push_imm(frame_size as i64);
instrs.push(add);
}
instrs.push(build_ret(None));
instrs
}
pub fn get_arg_reg_sysv64(n: usize) -> Option<u16> {
match n {
0 => Some(RDI),
1 => Some(RSI),
2 => Some(RDX),
3 => Some(RCX),
4 => Some(R8),
5 => Some(R9),
_ => None, }
}
pub fn get_arg_xmm_sysv64(n: usize) -> Option<u16> {
if n < 8 {
Some(XMM0 + n as u16)
} else {
None
}
}
pub fn get_ret_reg_int(is_64bit: bool) -> u16 {
if is_64bit {
RAX
} else {
EAX
}
}
pub fn get_ret_reg_fp() -> u16 {
XMM0
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeClass {
Integer,
Float,
Pointer,
Vector,
Void,
}
impl TypeClass {
pub fn classify(ty: &crate::types::Type) -> Self {
if ty.is_integer() {
TypeClass::Integer
} else if ty.is_floating_point() {
TypeClass::Float
} else if ty.is_pointer() {
TypeClass::Pointer
} else if ty.is_vector() {
TypeClass::Vector
} else {
TypeClass::Void
}
}
pub fn is_gpr(&self) -> bool {
matches!(self, TypeClass::Integer | TypeClass::Pointer)
}
pub fn is_xmm(&self) -> bool {
matches!(self, TypeClass::Float)
}
}
#[derive(Debug, Clone)]
pub struct FastISelConfig {
pub debug_info: bool,
pub verify_instructions: bool,
pub collect_stats: bool,
pub max_instrs_per_block: usize,
pub fast_regalloc: bool,
pub enable_constant_pool: bool,
pub emit_frame_pointer: bool,
pub stack_alignment: u32,
}
impl FastISelConfig {
pub fn debug_defaults() -> Self {
Self {
debug_info: true,
verify_instructions: false,
collect_stats: false,
max_instrs_per_block: 65536,
fast_regalloc: true,
enable_constant_pool: true,
emit_frame_pointer: true,
stack_alignment: 16,
}
}
pub fn release_defaults() -> Self {
Self {
debug_info: false,
verify_instructions: false,
collect_stats: false,
max_instrs_per_block: 65536,
fast_regalloc: true,
enable_constant_pool: true,
emit_frame_pointer: false,
stack_alignment: 16,
}
}
}
impl Default for FastISelConfig {
fn default() -> Self {
Self::debug_defaults()
}
}
pub trait MachineInstrExt {
fn get_def(&self) -> Option<u32>;
fn get_reg_operand(&self, index: usize) -> Option<u32>;
fn get_imm_operand(&self, index: usize) -> Option<i64>;
fn is_terminator(&self) -> bool;
fn is_move(&self) -> bool;
fn is_compare(&self) -> bool;
}
impl MachineInstrExt for MachineInstr {
fn get_def(&self) -> Option<u32> {
self.def
}
fn get_reg_operand(&self, index: usize) -> Option<u32> {
self.operands.get(index).and_then(|op| match op {
MachineOperand::Reg(r) => Some(*r),
MachineOperand::PhysReg(r) => Some(*r),
_ => None,
})
}
fn get_imm_operand(&self, index: usize) -> Option<i64> {
self.operands.get(index).and_then(|op| match op {
MachineOperand::Imm(i) => Some(*i),
_ => None,
})
}
fn is_terminator(&self) -> bool {
matches!(
self.opcode,
op if op == X86Opcode::RET as u32
|| op == X86Opcode::JMP as u32
|| op == X86Opcode::JE as u32
|| op == X86Opcode::JNE as u32
|| op == X86Opcode::JB as u32
|| op == X86Opcode::JAE as u32
|| op == X86Opcode::JBE as u32
|| op == X86Opcode::JA as u32
|| op == X86Opcode::JL as u32
|| op == X86Opcode::JGE as u32
|| op == X86Opcode::JLE as u32
|| op == X86Opcode::JG as u32
|| op == X86Opcode::JS as u32
|| op == X86Opcode::JNS as u32
|| op == X86Opcode::JP as u32
|| op == X86Opcode::JNP as u32
|| op == X86Opcode::JO as u32
|| op == X86Opcode::JNO as u32
)
}
fn is_move(&self) -> bool {
self.opcode == X86Opcode::MOV as u32
|| self.opcode == X86Opcode::MOVSX as u32
|| self.opcode == X86Opcode::MOVZX as u32
}
fn is_compare(&self) -> bool {
self.opcode == X86Opcode::CMP as u32
|| self.opcode == X86Opcode::TEST as u32
|| self.opcode == X86Opcode::UCOMISS as u32
|| self.opcode == X86Opcode::UCOMISD as u32
}
}
pub struct FastPeepholeOptimizer {
pub enabled: bool,
pub num_opts: u32,
}
impl FastPeepholeOptimizer {
pub fn new(enabled: bool) -> Self {
Self {
enabled,
num_opts: 0,
}
}
pub fn optimize_block(&mut self, block: &mut MachineBasicBlock) {
if !self.enabled {
return;
}
let mut i = 0;
while i < block.instructions.len() {
if i + 1 < block.instructions.len() {
let curr = &block.instructions[i];
let next = &block.instructions[i + 1];
if curr.is_move() && next.is_move() && curr.def == next.def && curr.def.is_some() {
if Self::is_redundant_move(curr) {
block.instructions.remove(i);
self.num_opts += 1;
continue;
}
}
if curr.opcode == X86Opcode::PUSH as u32 && next.opcode == X86Opcode::POP as u32 {
if curr.get_reg_operand(0) == next.def {
block.instructions.remove(i);
block.instructions.remove(i);
self.num_opts += 1;
continue;
}
}
}
i += 1;
}
}
fn is_redundant_move(mi: &MachineInstr) -> bool {
if mi.opcode != X86Opcode::MOV as u32 {
return false;
}
mi.get_reg_operand(0) == mi.get_reg_operand(1)
}
pub fn optimize_function(&mut self, mf: &mut MachineFunction) {
if !self.enabled {
return;
}
for block in &mut mf.blocks {
self.optimize_block(block);
}
}
}
impl Default for FastPeepholeOptimizer {
fn default() -> Self {
Self::new(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::{MachineFunction, MachineOperand};
use crate::opcode::Opcode;
use crate::types::Type;
use crate::value::{SubclassKind, Value, ValueRef};
use crate::x86::x86_subtarget::X86Subtarget;
use std::cell::RefCell;
use std::rc::Rc;
fn make_ref(v: Value) -> ValueRef {
Rc::new(RefCell::new(v))
}
fn make_inst(opcode: Opcode, ty: Type, operands: Vec<ValueRef>) -> ValueRef {
let op_count = operands.len();
make_ref(Value {
name: String::new(),
ty,
vid: 0,
subclass: SubclassKind::Instruction,
uses: Vec::new(),
opcode: Some(opcode),
num_operands: op_count,
operands,
parent: None,
return_type: None,
successors: Vec::new(),
subclass_data: 0,
metadata: HashMap::new(),
is_used_by_md: false,
blocks: Vec::new(),
params: Vec::new(),
instructions: Vec::new(),
initializer: None,
is_constant: false,
is_vararg: false,
result: None,
})
}
fn make_const_int(val: i64) -> ValueRef {
make_ref(Value {
name: format!("{}", val),
ty: Type::i64(),
vid: 0,
subclass: SubclassKind::ConstantInt,
uses: Vec::new(),
opcode: None,
num_operands: 0,
operands: Vec::new(),
parent: None,
return_type: None,
successors: Vec::new(),
subclass_data: val as u32,
metadata: HashMap::new(),
is_used_by_md: false,
blocks: Vec::new(),
params: Vec::new(),
instructions: Vec::new(),
initializer: None,
is_constant: false,
is_vararg: false,
result: None,
})
}
fn make_bb(name: &str, insts: Vec<ValueRef>) -> ValueRef {
make_ref(Value {
name: name.to_string(),
ty: Type::label(),
vid: 0,
subclass: SubclassKind::BasicBlock,
uses: Vec::new(),
opcode: None,
num_operands: 0,
operands: insts,
parent: None,
return_type: None,
successors: Vec::new(),
subclass_data: 0,
metadata: HashMap::new(),
is_used_by_md: false,
blocks: Vec::new(),
params: Vec::new(),
instructions: Vec::new(),
initializer: None,
is_constant: false,
is_vararg: false,
result: None,
})
}
fn make_func(name: &str, ret_ty: Type, params: Vec<Type>, blocks: Vec<ValueRef>) -> ValueRef {
make_ref(Value {
name: name.to_string(),
ty: Type::void(),
vid: 0,
subclass: SubclassKind::Function,
uses: Vec::new(),
opcode: None,
num_operands: 0,
operands: blocks,
parent: None,
return_type: Some(ret_ty),
successors: Vec::new(),
subclass_data: 0,
metadata: HashMap::new(),
is_used_by_md: false,
blocks: Vec::new(),
params,
instructions: Vec::new(),
initializer: None,
is_constant: false,
is_vararg: false,
result: None,
})
}
fn assign_vids(func: &mut Value, next_vid: &mut u64) {
func.vid = *next_vid;
*next_vid += 1;
for op in &mut func.operands {
let mut val = op.borrow_mut();
if val.subclass == SubclassKind::BasicBlock {
val.vid = *next_vid;
*next_vid += 1;
for inst_ref in &mut val.operands {
let mut inst = inst_ref.borrow_mut();
inst.vid = *next_vid;
*next_vid += 1;
for use_ref in &mut inst.operands {
let mut uv = use_ref.borrow_mut();
if uv.vid == 0 {
uv.vid = *next_vid;
*next_vid += 1;
}
}
}
}
}
}
#[test]
fn test_reg_alloc_create_vreg() {
let mut ra = FastRegAlloc::new(true); let v0 = ra.create_vreg(100);
let v1 = ra.create_vreg(200);
assert_eq!(v0, 0);
assert_eq!(v1, 1);
assert_eq!(ra.get_vreg(100), Some(0));
assert_eq!(ra.get_vreg(200), Some(1));
}
#[test]
fn test_reg_alloc_reuse_vreg() {
let mut ra = FastRegAlloc::new(true);
let v0 = ra.create_vreg(100);
let v1 = ra.create_vreg(100); assert_eq!(v0, v1);
}
#[test]
fn test_reg_alloc_gpr() {
let mut ra = FastRegAlloc::new(true);
let vreg = ra.create_vreg(10);
let phys = ra.alloc_gpr(vreg);
assert!(phys.is_some());
assert!(CALLER_SAVED_GPRS.contains(&phys.unwrap()));
}
#[test]
fn test_reg_alloc_xmm() {
let mut ra = FastRegAlloc::new(true);
let vreg = ra.create_vreg(20);
let phys = ra.alloc_xmm(vreg);
assert!(phys.is_some());
assert!(CALLER_SAVED_XMMS.contains(&phys.unwrap()));
}
#[test]
fn test_reg_alloc_use_counts() {
let mut ra = FastRegAlloc::new(true);
ra.inc_use_count(1);
ra.inc_use_count(1);
ra.inc_use_count(2);
assert_eq!(ra.use_counts.get(&1), Some(&2));
assert_eq!(ra.use_counts.get(&2), Some(&1));
}
#[test]
fn test_reg_alloc_spill_gpr() {
let mut ra = FastRegAlloc::new(true);
for i in 0..CALLER_SAVED_GPRS.len() {
let vreg = ra.create_vreg(1000 + i as u64);
ra.alloc_gpr(vreg);
}
let spill_vreg = ra.create_vreg(2000);
let phys = ra.alloc_gpr(spill_vreg);
assert!(phys.is_some());
let va = ra.vreg_assignments.iter().find(|va| va.vreg == spill_vreg);
assert!(va.is_some());
assert!(va.unwrap().stack_slot.is_some());
}
#[test]
fn test_materializer_fits_i32() {
assert!(FastMaterializer::fits_i32(0));
assert!(FastMaterializer::fits_i32(42));
assert!(FastMaterializer::fits_i32(-42));
assert!(FastMaterializer::fits_i32(i32::MAX as i64));
assert!(FastMaterializer::fits_i32(i32::MIN as i64));
assert!(!FastMaterializer::fits_i32(i32::MAX as i64 + 1));
assert!(!FastMaterializer::fits_i32(i32::MIN as i64 - 1));
}
#[test]
fn test_materializer_emit_mov_imm32() {
let mat = FastMaterializer::new(true);
let mi = mat.emit_mov_imm32(5, 42);
assert_eq!(mi.opcode, X86Opcode::MOV as u32);
assert_eq!(mi.def, Some(5));
assert_eq!(mi.operands.len(), 2);
}
#[test]
fn test_materializer_emit_mov_imm64_small() {
let mat = FastMaterializer::new(true);
let instrs = mat.emit_mov_imm64(3, 100);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::MOV as u32);
}
#[test]
fn test_materializer_constant_pool() {
let mut mat = FastMaterializer::new(true);
let label = mat.add_to_constant_pool(0xDEADBEEF_CAFEBABEi64);
assert!(label.starts_with(".LCPI"));
assert_eq!(mat.constant_pool.len(), 1);
}
#[test]
fn test_addr_mode_reg_imm() {
let am = FastAddressMode::RegImm { base: 1, offset: 8 };
assert!(am.is_valid());
assert_eq!(am.get_base(), Some(1));
assert_eq!(am.get_offset(), 8);
}
#[test]
fn test_addr_mode_none() {
let am = FastAddressMode::None;
assert!(!am.is_valid());
assert_eq!(am.get_base(), None);
}
#[test]
fn test_addr_mode_frame_index() {
let am = FastAddressMode::FrameIndex { fi: 0, offset: -8 };
assert!(am.is_valid());
assert_eq!(am.get_offset(), -8);
}
#[test]
fn test_select_add_i32() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 100;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 101;
let add_inst = make_inst(Opcode::Add, Type::i32(), vec![op0.clone(), op1.clone()]);
add_inst.borrow_mut().vid = 200;
let bb = make_bb("entry", vec![add_inst.clone()]);
let func = make_func("test_add", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_blocks > 0);
assert!(result.num_instructions > 0);
}
#[test]
fn test_select_sub_i64() {
let op0 = make_ref(Value::new(Type::i64()));
op0.borrow_mut().vid = 101;
let op1 = make_ref(Value::new(Type::i64()));
op1.borrow_mut().vid = 102;
let sub_inst = make_inst(Opcode::Sub, Type::i64(), vec![op0.clone(), op1.clone()]);
sub_inst.borrow_mut().vid = 201;
let bb = make_bb("entry", vec![sub_inst.clone()]);
let func = make_func("test_sub", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions > 0);
}
#[test]
fn test_select_and_or_xor() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 200;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 201;
let and_inst = make_inst(Opcode::And, Type::i32(), vec![op0.clone(), op1.clone()]);
and_inst.borrow_mut().vid = 300;
let or_inst = make_inst(Opcode::Or, Type::i32(), vec![op0.clone(), op1.clone()]);
or_inst.borrow_mut().vid = 301;
let xor_inst = make_inst(Opcode::Xor, Type::i32(), vec![op0.clone(), op1.clone()]);
xor_inst.borrow_mut().vid = 302;
let bb = make_bb(
"entry",
vec![and_inst.clone(), or_inst.clone(), xor_inst.clone()],
);
let func = make_func("test_bitwise", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_shifts() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 400;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 401;
let shl_inst = make_inst(Opcode::Shl, Type::i32(), vec![op0.clone(), op1.clone()]);
shl_inst.borrow_mut().vid = 500;
let lshr_inst = make_inst(Opcode::LShr, Type::i32(), vec![op0.clone(), op1.clone()]);
lshr_inst.borrow_mut().vid = 501;
let ashr_inst = make_inst(Opcode::AShr, Type::i32(), vec![op0.clone(), op1.clone()]);
ashr_inst.borrow_mut().vid = 502;
let bb = make_bb("entry", vec![shl_inst, lshr_inst, ashr_inst]);
let func = make_func("test_shifts", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 6); }
#[test]
fn test_select_icmp() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 600;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 601;
let icmp_inst = make_inst(Opcode::ICmp, Type::i1(), vec![op0.clone(), op1.clone()]);
icmp_inst.borrow_mut().vid = 700;
icmp_inst.borrow_mut().subclass_data = icmp_pred::EQ;
let bb = make_bb("entry", vec![icmp_inst.clone()]);
let func = make_func("test_icmp", Type::i1(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_fcmp() {
let op0 = make_ref(Value::new(Type::float()));
op0.borrow_mut().vid = 800;
let op1 = make_ref(Value::new(Type::float()));
op1.borrow_mut().vid = 801;
let fcmp_inst = make_inst(Opcode::FCmp, Type::i1(), vec![op0.clone(), op1.clone()]);
fcmp_inst.borrow_mut().vid = 900;
fcmp_inst.borrow_mut().subclass_data = fcmp_pred::OEQ;
let bb = make_bb("entry", vec![fcmp_inst.clone()]);
let func = make_func("test_fcmp", Type::i1(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_load_store() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 1000;
let val = make_ref(Value::new(Type::i64()));
val.borrow_mut().vid = 1001;
let load_inst = make_inst(Opcode::Load, Type::i64(), vec![ptr.clone()]);
load_inst.borrow_mut().vid = 1100;
let store_inst = make_inst(Opcode::Store, Type::void(), vec![val.clone(), ptr.clone()]);
store_inst.borrow_mut().vid = 1101;
let bb = make_bb("entry", vec![load_inst, store_inst]);
let func = make_func("test_mem", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_select_br_unconditional() {
let target = make_bb("target", vec![]);
target.borrow_mut().vid = 1200;
let br_inst = make_inst(Opcode::Br, Type::void(), vec![target.clone()]);
br_inst.borrow_mut().vid = 1300;
let bb = make_bb("entry", vec![br_inst]);
let func = make_func("test_br", Type::void(), vec![], vec![bb.clone(), target]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_br_conditional() {
let cond = make_ref(Value::new(Type::i1()));
cond.borrow_mut().vid = 1400;
let true_bb = make_bb("if_true", vec![]);
true_bb.borrow_mut().vid = 1401;
let false_bb = make_bb("if_false", vec![]);
false_bb.borrow_mut().vid = 1402;
let br_inst = make_inst(
Opcode::Br,
Type::void(),
vec![cond.clone(), true_bb.clone(), false_bb.clone()],
);
br_inst.borrow_mut().vid = 1500;
let bb = make_bb("entry", vec![br_inst]);
let func = make_func(
"test_cbr",
Type::void(),
vec![],
vec![bb, true_bb, false_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_ret() {
let ret_val = make_ref(Value::new(Type::i32()));
ret_val.borrow_mut().vid = 1600;
let ret_inst = make_inst(Opcode::Ret, Type::void(), vec![ret_val.clone()]);
ret_inst.borrow_mut().vid = 1700;
let bb = make_bb("entry", vec![ret_inst]);
let func = make_func("test_ret", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_conversions() {
let src = make_ref(Value::new(Type::i32()));
src.borrow_mut().vid = 1800;
let trunc_inst = make_inst(Opcode::Trunc, Type::i8(), vec![src.clone()]);
trunc_inst.borrow_mut().vid = 1900;
let zext_inst = make_inst(Opcode::ZExt, Type::i64(), vec![src.clone()]);
zext_inst.borrow_mut().vid = 1901;
let sext_inst = make_inst(Opcode::SExt, Type::i64(), vec![src.clone()]);
sext_inst.borrow_mut().vid = 1902;
let bb = make_bb("entry", vec![trunc_inst, zext_inst, sext_inst]);
let func = make_func("test_conv", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_select() {
let cond = make_ref(Value::new(Type::i1()));
cond.borrow_mut().vid = 2000;
let true_v = make_ref(Value::new(Type::i32()));
true_v.borrow_mut().vid = 2001;
let false_v = make_ref(Value::new(Type::i32()));
false_v.borrow_mut().vid = 2002;
let sel_inst = make_inst(
Opcode::Select,
Type::i32(),
vec![cond.clone(), true_v.clone(), false_v.clone()],
);
sel_inst.borrow_mut().vid = 2100;
let bb = make_bb("entry", vec![sel_inst]);
let func = make_func("test_select", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_phi() {
let val0 = make_ref(Value::new(Type::i32()));
val0.borrow_mut().vid = 2200;
let val1 = make_ref(Value::new(Type::i32()));
val1.borrow_mut().vid = 2201;
let bb0 = make_bb("pred0", vec![]);
bb0.borrow_mut().vid = 2202;
let bb1 = make_bb("pred1", vec![]);
bb1.borrow_mut().vid = 2203;
let phi_inst = make_inst(
Opcode::Phi,
Type::i32(),
vec![val0.clone(), bb0.clone(), val1.clone(), bb1.clone()],
);
phi_inst.borrow_mut().vid = 2300;
let bb = make_bb("entry", vec![phi_inst]);
let func = make_func("test_phi", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_call_direct() {
let arg0 = make_ref(Value::new(Type::i32()));
arg0.borrow_mut().vid = 2400;
let callee = make_ref(Value::new(Type::void()));
callee.borrow_mut().vid = 2401;
callee.borrow_mut().name = "foo".to_string();
callee.borrow_mut().subclass = SubclassKind::Function;
let call_inst = make_inst(
Opcode::Call,
Type::i32(),
vec![callee.clone(), arg0.clone()],
);
call_inst.borrow_mut().vid = 2500;
let bb = make_bb("entry", vec![call_inst]);
let func = make_func("test_call", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_vector_ops() {
let vec0 = make_ref(Value::new(Type::i128()));
vec0.borrow_mut().vid = 2600;
let vec1 = make_ref(Value::new(Type::i128()));
vec1.borrow_mut().vid = 2601;
let extract_inst = make_inst(
Opcode::ExtractElement,
Type::i32(),
vec![vec0.clone(), make_const_int(0)],
);
extract_inst.borrow_mut().vid = 2700;
let insert_inst = make_inst(
Opcode::InsertElement,
Type::i128(),
vec![
vec0.clone(),
make_ref(Value::new(Type::i32())),
make_const_int(1),
],
);
insert_inst.borrow_mut().vid = 2701;
insert_inst.borrow_mut().operands[1].borrow_mut().vid = 2602;
let shuffle_inst = make_inst(
Opcode::ShuffleVector,
Type::i128(),
vec![vec0.clone(), vec1.clone()],
);
shuffle_inst.borrow_mut().vid = 2702;
let bb = make_bb("entry", vec![extract_inst, insert_inst, shuffle_inst]);
let func = make_func("test_vec", Type::i128(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_floating_ops() {
let op0 = make_ref(Value::new(Type::float()));
op0.borrow_mut().vid = 2800;
let op1 = make_ref(Value::new(Type::float()));
op1.borrow_mut().vid = 2801;
let fadd = make_inst(Opcode::FAdd, Type::float(), vec![op0.clone(), op1.clone()]);
fadd.borrow_mut().vid = 2900;
let fsub = make_inst(Opcode::FSub, Type::float(), vec![op0.clone(), op1.clone()]);
fsub.borrow_mut().vid = 2901;
let fmul = make_inst(Opcode::FMul, Type::float(), vec![op0.clone(), op1.clone()]);
fmul.borrow_mut().vid = 2902;
let fdiv = make_inst(Opcode::FDiv, Type::float(), vec![op0.clone(), op1.clone()]);
fdiv.borrow_mut().vid = 2903;
let bb = make_bb("entry", vec![fadd, fsub, fmul, fdiv]);
let func = make_func("test_fp", Type::float(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_select_fp_conversions() {
let fp_src = make_ref(Value::new(Type::double()));
fp_src.borrow_mut().vid = 3000;
let int_src = make_ref(Value::new(Type::i32()));
int_src.borrow_mut().vid = 3001;
let fptrunc = make_inst(Opcode::FPTrunc, Type::float(), vec![fp_src.clone()]);
fptrunc.borrow_mut().vid = 3100;
let fpext = make_inst(
Opcode::FPExt,
Type::double(),
vec![make_ref(Value::new(Type::float()))],
);
fpext.borrow_mut().vid = 3101;
fpext.borrow_mut().operands[0].borrow_mut().vid = 3002;
let fptosi = make_inst(Opcode::FPToSI, Type::i32(), vec![fp_src.clone()]);
fptosi.borrow_mut().vid = 3102;
let sitofp = make_inst(Opcode::SIToFP, Type::float(), vec![int_src.clone()]);
sitofp.borrow_mut().vid = 3103;
let bb = make_bb("entry", vec![fptrunc, fpext, fptosi, sitofp]);
let func = make_func("test_fpconv", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_select_ptr_ops() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 3200;
let int_v = make_ref(Value::new(Type::i64()));
int_v.borrow_mut().vid = 3201;
let ptrtoint = make_inst(Opcode::PtrToInt, Type::i64(), vec![ptr.clone()]);
ptrtoint.borrow_mut().vid = 3300;
let inttoptr = make_inst(Opcode::IntToPtr, Type::pointer(0), vec![int_v.clone()]);
inttoptr.borrow_mut().vid = 3301;
let bitcast = make_inst(Opcode::BitCast, Type::i64(), vec![ptr.clone()]);
bitcast.borrow_mut().vid = 3302;
let bb = make_bb("entry", vec![ptrtoint, inttoptr, bitcast]);
let func = make_func("test_ptr", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_gvn_gep() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 3400;
let idx = make_ref(Value::new(Type::i32()));
idx.borrow_mut().vid = 3401;
let gep = make_inst(
Opcode::GetElementPtr,
Type::pointer(0),
vec![ptr.clone(), idx.clone()],
);
gep.borrow_mut().vid = 3500;
let bb = make_bb("entry", vec![gep]);
let func = make_func("test_gep", Type::pointer(0), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_fast_isel_result_stats() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 3600;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 3601;
let ops = vec![
make_inst(Opcode::Add, Type::i32(), vec![op0.clone(), op1.clone()]),
make_inst(Opcode::Sub, Type::i32(), vec![op0.clone(), op1.clone()]),
make_inst(Opcode::And, Type::i32(), vec![op0.clone(), op1.clone()]),
];
for (i, op) in ops.iter().enumerate() {
op.borrow_mut().vid = 3700 + i as u64;
}
let bb = make_bb("entry", ops);
let func = make_func("test_stats", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 1);
assert!(result.num_instructions > 0);
assert!(result.num_vregs > 0);
}
#[test]
fn test_icmp_pred_to_setcc_all() {
let test_cases = vec![
(icmp_pred::EQ, X86Opcode::SETE as u32),
(icmp_pred::NE, X86Opcode::SETNE as u32),
(icmp_pred::UGT, X86Opcode::SETA as u32),
(icmp_pred::UGE, X86Opcode::SETAE as u32),
(icmp_pred::ULT, X86Opcode::SETB as u32),
(icmp_pred::ULE, X86Opcode::SETBE as u32),
(icmp_pred::SGT, X86Opcode::SETG as u32),
(icmp_pred::SGE, X86Opcode::SETGE as u32),
(icmp_pred::SLT, X86Opcode::SETL as u32),
(icmp_pred::SLE, X86Opcode::SETLE as u32),
];
for (pred, expected_setcc) in test_cases {
assert_eq!(
X86FastISel::icmp_pred_to_setcc(pred),
expected_setcc,
"pred={}",
pred
);
}
}
#[test]
fn test_fcmp_pred_to_cc_ordered() {
assert_eq!(
X86FastISel::fcmp_pred_to_cc(fcmp_pred::OEQ),
X86ConditionCode::Z
);
assert_eq!(
X86FastISel::fcmp_pred_to_cc(fcmp_pred::OGT),
X86ConditionCode::A
);
assert_eq!(
X86FastISel::fcmp_pred_to_cc(fcmp_pred::OLT),
X86ConditionCode::B
);
}
#[test]
fn test_fcmp_pred_to_cc_unordered() {
assert_eq!(
X86FastISel::fcmp_pred_to_cc(fcmp_pred::UEQ),
X86ConditionCode::Z
);
assert_eq!(
X86FastISel::fcmp_pred_to_cc(fcmp_pred::UNE),
X86ConditionCode::NZ
);
}
#[test]
fn test_fcmp_pred_to_cc_special() {
assert_eq!(
X86FastISel::fcmp_pred_to_cc(fcmp_pred::ORD),
X86ConditionCode::NP
);
assert_eq!(
X86FastISel::fcmp_pred_to_cc(fcmp_pred::UNO),
X86ConditionCode::P
);
}
#[test]
fn test_empty_function() {
let func = make_func("empty", Type::void(), vec![], vec![]);
func.borrow_mut().vid = 1;
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 0);
assert_eq!(result.num_instructions, 0);
}
#[test]
fn test_empty_block() {
let bb = make_bb("entry", vec![]);
let func = make_func("empty_block", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 1);
assert_eq!(result.num_instructions, 0);
}
#[test]
fn test_multiple_blocks() {
let bb1 = make_bb("bb1", vec![]);
let bb2 = make_bb("bb2", vec![]);
let bb3 = make_bb("bb3", vec![]);
let func = make_func("multi_block", Type::void(), vec![], vec![bb1, bb2, bb3]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 3);
}
#[test]
fn test_direct_call_with_return() {
let callee = make_ref(Value::new(Type::void()));
callee.borrow_mut().vid = 4000;
callee.borrow_mut().name = "target_fn".to_string();
callee.borrow_mut().subclass = SubclassKind::Function;
let call_inst = make_inst(Opcode::Call, Type::i32(), vec![callee.clone()]);
call_inst.borrow_mut().vid = 4100;
let bb = make_bb("entry", vec![call_inst]);
let func = make_func("test_direct_call", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1); }
#[test]
fn test_switch_simple() {
let cond = make_ref(Value::new(Type::i32()));
cond.borrow_mut().vid = 4200;
let default_bb = make_bb("default", vec![]);
default_bb.borrow_mut().vid = 4201;
let case0_bb = make_bb("case0", vec![]);
case0_bb.borrow_mut().vid = 4202;
let case1_bb = make_bb("case1", vec![]);
case1_bb.borrow_mut().vid = 4203;
let switch_inst = make_inst(
Opcode::Switch,
Type::void(),
vec![
cond.clone(),
default_bb.clone(),
make_const_int(0),
case0_bb.clone(),
make_const_int(1),
case1_bb.clone(),
],
);
switch_inst.borrow_mut().vid = 4300;
let bb = make_bb("entry", vec![switch_inst]);
let func = make_func(
"test_switch",
Type::void(),
vec![],
vec![bb, default_bb, case0_bb, case1_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3); }
#[test]
fn test_invoke_lowering() {
let callee = make_ref(Value::new(Type::void()));
callee.borrow_mut().vid = 4400;
callee.borrow_mut().name = "may_throw".to_string();
callee.borrow_mut().subclass = SubclassKind::Function;
let normal_bb = make_bb("normal", vec![]);
normal_bb.borrow_mut().vid = 4401;
let unwind_bb = make_bb("unwind", vec![]);
unwind_bb.borrow_mut().vid = 4402;
let arg = make_ref(Value::new(Type::i32()));
arg.borrow_mut().vid = 4403;
let invoke_inst = make_inst(
Opcode::Invoke,
Type::i32(),
vec![
callee.clone(),
normal_bb.clone(),
unwind_bb.clone(),
arg.clone(),
],
);
invoke_inst.borrow_mut().vid = 4500;
let bb = make_bb("entry", vec![invoke_inst]);
let func = make_func(
"test_invoke",
Type::i32(),
vec![],
vec![bb, normal_bb, unwind_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_x86_fast_isel_builder() {
let builder = X86FastISelBuilder::new();
let isel = builder.build();
assert!(isel.is_64bit);
assert_eq!(isel.reg_alloc.next_vreg, 2); }
#[test]
fn test_icmp_all_predicates() {
let predicates = vec![
icmp_pred::EQ,
icmp_pred::NE,
icmp_pred::UGT,
icmp_pred::UGE,
icmp_pred::ULT,
icmp_pred::ULE,
icmp_pred::SGT,
icmp_pred::SGE,
icmp_pred::SLT,
icmp_pred::SLE,
];
for &pred in &predicates {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 5000;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 5001;
let icmp = make_inst(Opcode::ICmp, Type::i1(), vec![op0.clone(), op1.clone()]);
icmp.borrow_mut().vid = 5100;
icmp.borrow_mut().subclass_data = pred;
let bb = make_bb("entry", vec![icmp.clone()]);
let func = make_func(&format!("test_icmp_{}", pred), Type::i1(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(
result.num_instructions >= 3,
"pred={} had {} instructions",
pred,
result.num_instructions
);
}
}
#[test]
fn test_gep_multi_index() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 5200;
let idx0 = make_ref(Value::new(Type::i32()));
idx0.borrow_mut().vid = 5201;
let idx1 = make_ref(Value::new(Type::i32()));
idx1.borrow_mut().vid = 5202;
let gep = make_inst(
Opcode::GetElementPtr,
Type::pointer(0),
vec![ptr.clone(), idx0.clone(), idx1.clone()],
);
gep.borrow_mut().vid = 5300;
let bb = make_bb("entry", vec![gep]);
let func = make_func("test_gep_multi", Type::pointer(0), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_alloca() {
let size = make_ref(Value::new(Type::i32()));
size.borrow_mut().vid = 5400;
size.borrow_mut().subclass = SubclassKind::ConstantInt;
size.borrow_mut().name = "16".to_string();
let alloca_inst = make_inst(Opcode::Alloca, Type::pointer(0), vec![size.clone()]);
alloca_inst.borrow_mut().vid = 5500;
let bb = make_bb("entry", vec![alloca_inst]);
let func = make_func("test_alloca", Type::pointer(0), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
assert!(result.frame_size > 0);
}
#[test]
fn test_extractvalue_insertvalue() {
let agg = make_ref(Value::new(Type::i128()));
agg.borrow_mut().vid = 5600;
let ev = make_inst(
Opcode::ExtractValue,
Type::i32(),
vec![agg.clone(), make_const_int(0)],
);
ev.borrow_mut().vid = 5700;
let field = make_ref(Value::new(Type::i32()));
field.borrow_mut().vid = 5601;
let iv = make_inst(
Opcode::InsertValue,
Type::i128(),
vec![agg.clone(), field.clone(), make_const_int(0)],
);
iv.borrow_mut().vid = 5701;
let bb = make_bb("entry", vec![ev, iv]);
let func = make_func("test_ev_iv", Type::i128(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_reg_alloc_64_vs_32() {
let ra64 = FastRegAlloc::new(true);
let ra32 = FastRegAlloc::new(false);
assert!(ra64.is_64bit);
assert!(!ra32.is_64bit);
}
#[test]
fn test_reg_alloc_multiple_gpr_allocation() {
let mut ra = FastRegAlloc::new(true);
let vregs: Vec<u32> = (0..5).map(|i| ra.create_vreg(1000 + i as u64)).collect();
for &v in &vregs {
let phys = ra.alloc_gpr(v);
assert!(phys.is_some());
}
let mut seen = HashSet::new();
for va in &ra.vreg_assignments {
if let Some(pr) = va.phys_reg {
assert!(seen.insert(pr), "Duplicate phys reg {pr}");
}
}
}
#[test]
fn test_reg_alloc_xmm_allocation_exhaustion() {
let mut ra = FastRegAlloc::new(true);
for i in 0..CALLER_SAVED_XMMS.len() {
let vreg = ra.create_vreg(2000 + i as u64);
ra.alloc_xmm(vreg);
}
let spill_vreg = ra.create_vreg(3000);
let phys = ra.alloc_xmm(spill_vreg);
assert!(phys.is_some());
let va = ra.vreg_assignments.iter().find(|va| va.vreg == spill_vreg);
assert!(va.unwrap().stack_slot.is_some());
}
#[test]
fn test_reg_alloc_free_and_reuse() {
let mut ra = FastRegAlloc::new(true);
let v1 = ra.create_vreg(100);
let p1 = ra.alloc_gpr(v1).unwrap();
ra.free_gpr(p1);
let v2 = ra.create_vreg(200);
let p2 = ra.alloc_gpr(v2).unwrap();
assert_eq!(p1, p2);
}
#[test]
fn test_reg_alloc_use_count_tracking() {
let mut ra = FastRegAlloc::new(true);
ra.inc_use_count(42);
ra.inc_use_count(42);
ra.inc_use_count(42);
assert_eq!(ra.use_counts.get(&42), Some(&3));
}
#[test]
fn test_reg_alloc_dec_use_no_panic() {
let mut ra = FastRegAlloc::new(true);
let vreg = ra.create_vreg(777);
ra.dec_use(vreg);
ra.dec_use(vreg);
}
#[test]
fn test_reg_alloc_kill_flag() {
let mut ra = FastRegAlloc::new(true);
let vreg = ra.create_vreg(888);
ra.inc_use_count(888);
if let Some(va) = ra.vreg_assignments.iter_mut().find(|va| va.vreg == vreg) {
va.remaining_uses = 1;
}
let dead = ra.dec_use(vreg);
assert!(dead);
let va = ra.vreg_assignments.iter().find(|va| va.vreg == vreg);
assert!(va.unwrap().is_kill);
}
#[test]
fn test_reg_alloc_reset_for_block_clears_assignments() {
let mut ra = FastRegAlloc::new(true);
let vreg = ra.create_vreg(999);
ra.alloc_gpr(vreg);
ra.reset_for_block();
assert!(ra.gpr_in_use.is_empty());
}
#[test]
fn test_reg_alloc_ensure_gpr() {
let mut ra = FastRegAlloc::new(true);
let vreg = ra.create_vreg(111);
let phys = ra.ensure_gpr(vreg);
assert!(CALLER_SAVED_GPRS.contains(&phys));
}
#[test]
fn test_reg_alloc_ensure_xmm() {
let mut ra = FastRegAlloc::new(true);
let vreg = ra.create_vreg(222);
let phys = ra.ensure_xmm(vreg);
assert!(CALLER_SAVED_XMMS.contains(&phys));
}
#[test]
fn test_addr_mode_reg() {
let am = FastAddressMode::Reg(10);
assert!(am.is_valid());
assert_eq!(am.get_base(), Some(10));
assert_eq!(am.get_offset(), 0);
}
#[test]
fn test_addr_mode_reg_imm_negative() {
let am = FastAddressMode::RegImm {
base: 5,
offset: -16,
};
assert_eq!(am.get_offset(), -16);
}
#[test]
fn test_addr_mode_reg_reg_scale() {
let am = FastAddressMode::RegReg {
base: 1,
index: 2,
scale: 4,
offset: 64,
};
assert_eq!(am.get_base(), Some(1));
assert_eq!(am.get_offset(), 64);
}
#[test]
fn test_addr_mode_global() {
let am = FastAddressMode::Global {
name: "g_val".into(),
offset: 0,
};
assert!(am.is_valid());
}
#[test]
fn test_addr_mode_constant_pool() {
let am = FastAddressMode::ConstantPool {
index: 3,
offset: -4,
};
assert_eq!(am.get_offset(), -4);
}
#[test]
fn test_addr_mode_default_none() {
let am = FastAddressMode::default();
assert!(!am.is_valid());
}
#[test]
fn test_addr_lowering_simple_base() {
let mut ctx = AddressLoweringContext::new();
ctx.base = Some(5);
assert!(ctx.is_simple());
assert!(!ctx.is_rip_relative());
}
#[test]
fn test_addr_lowering_global() {
let mut ctx = AddressLoweringContext::new();
ctx.is_global = true;
ctx.global_name = Some("foo".into());
assert!(!ctx.is_simple());
assert!(ctx.is_rip_relative());
}
#[test]
fn test_addr_lowering_to_base_disp() {
let mut ctx = AddressLoweringContext::new();
ctx.base = Some(3);
ctx.displacement = 20;
assert_eq!(ctx.to_base_disp(), (Some(3), 20));
}
#[test]
fn test_lower_global_address_64bit() {
let mode = lower_global_address("myvar", 0, true);
if let FastAddressMode::Global { name, offset } = mode {
assert_eq!(name, "myvar");
assert_eq!(offset, 0);
} else {
panic!("Expected Global address mode");
}
}
#[test]
fn test_materializer_new_64bit() {
let mat = FastMaterializer::new(true);
assert!(mat.is_64bit);
assert!(mat.constant_pool.is_empty());
}
#[test]
fn test_materializer_fits_u32() {
assert!(FastMaterializer::fits_u32(0));
assert!(FastMaterializer::fits_u32(u32::MAX as i64));
assert!(!FastMaterializer::fits_u32(u32::MAX as i64 + 1));
assert!(!FastMaterializer::fits_u32(-1));
}
#[test]
fn test_materializer_emit_mov_imm64_fits_i32() {
let mat = FastMaterializer::new(true);
let instrs = mat.emit_mov_imm64(10, 42);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::MOV as u32);
}
#[test]
fn test_materializer_emit_mov_imm64_large() {
let mat = FastMaterializer::new(true);
let large_val = 0x123456789ABCDEF0i64;
let instrs = mat.emit_mov_imm64(10, large_val);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::MOVABS as u32);
}
#[test]
fn test_materializer_constant_pool_multiple() {
let mut mat = FastMaterializer::new(true);
let l1 = mat.add_to_constant_pool(100);
let l2 = mat.add_to_constant_pool(200);
let l3 = mat.add_to_constant_pool(300);
assert_eq!(mat.constant_pool.len(), 3);
assert_ne!(l1, l2);
assert_ne!(l2, l3);
}
#[test]
fn test_spill_manager_allocate_slot() {
let mut sm = SpillManager::new();
let s1 = sm.allocate_slot(10);
let s2 = sm.allocate_slot(20);
assert_eq!(s1, 0);
assert_eq!(s2, STACK_SLOT_SIZE);
assert_eq!(sm.max_slot, STACK_SLOT_SIZE);
}
#[test]
fn test_spill_manager_slot_reuse() {
let mut sm = SpillManager::new();
let s1 = sm.allocate_slot(5);
let s2 = sm.allocate_slot(5);
assert_eq!(s1, s2);
}
#[test]
fn test_spill_manager_get_slot() {
let mut sm = SpillManager::new();
sm.allocate_slot(99);
assert_eq!(sm.get_slot(99), Some(0));
assert_eq!(sm.get_slot(999), None);
}
#[test]
fn test_spill_manager_emit_spill_and_reload() {
let mut sm = SpillManager::new();
sm.emit_spill(42, 0, false);
sm.emit_reload(42, 5, false);
assert_eq!(sm.spill_code.len(), 1);
assert_eq!(sm.reload_code.len(), 1);
assert!(sm.total_spill_size() > 0);
}
#[test]
fn test_live_interval_overlap() {
let a = LiveInterval::new(1, 0, 10, false);
let b = LiveInterval::new(2, 5, 15, false);
assert!(a.overlaps(&b));
assert!(b.overlaps(&a));
}
#[test]
fn test_live_interval_no_overlap() {
let a = LiveInterval::new(1, 0, 10, false);
let b = LiveInterval::new(2, 11, 20, false);
assert!(!a.overlaps(&b));
}
#[test]
fn test_live_interval_covers() {
let a = LiveInterval::new(1, 5, 15, false);
assert!(a.covers(5));
assert!(a.covers(10));
assert!(a.covers(15));
assert!(!a.covers(4));
assert!(!a.covers(16));
}
#[test]
fn test_recognize_intrinsic_memcpy() {
let i = RecognizedIntrinsic::from_name("llvm.memcpy.p0i8.p0i8.i64");
assert_eq!(i, Some(RecognizedIntrinsic::Memcpy));
}
#[test]
fn test_recognize_intrinsic_sqrt() {
let i = RecognizedIntrinsic::from_name("llvm.sqrt.f64");
assert_eq!(i, Some(RecognizedIntrinsic::Sqrt));
}
#[test]
fn test_recognize_intrinsic_trap() {
let i = RecognizedIntrinsic::from_name("llvm.trap");
assert_eq!(i, Some(RecognizedIntrinsic::Trap));
}
#[test]
fn test_recognize_intrinsic_unknown() {
let i = RecognizedIntrinsic::from_name("llvm.something.weird");
assert!(matches!(i, Some(RecognizedIntrinsic::Unknown(_))));
}
#[test]
fn test_recognize_intrinsic_not_llvm() {
let i = RecognizedIntrinsic::from_name("malloc");
assert_eq!(i, None);
}
#[test]
fn test_intrinsic_lower_sqrt() {
let i = RecognizedIntrinsic::Sqrt;
let instrs = i.lower(Some(10), &[5]);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::SQRTSD as u32);
}
#[test]
fn test_intrinsic_lower_trap() {
let i = RecognizedIntrinsic::Trap;
let instrs = i.lower(None, &[]);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::UD2 as u32);
}
#[test]
fn test_intrinsic_lower_lifetime_noop() {
let i = RecognizedIntrinsic::Lifetime;
let instrs = i.lower(None, &[1, 2]);
assert!(instrs.is_empty());
}
#[test]
fn test_intrinsic_lower_dbg_noop() {
let i = RecognizedIntrinsic::DbgInfo;
let instrs = i.lower(None, &[]);
assert!(instrs.is_empty());
}
#[test]
fn test_intrinsic_lower_memcpy() {
let i = RecognizedIntrinsic::Memcpy;
let instrs = i.lower(None, &[1, 2, 3]);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::CALL as u32);
}
#[test]
fn test_intrinsic_lower_memset() {
let i = RecognizedIntrinsic::Memset;
let instrs = i.lower(None, &[1, 2, 3]);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::CALL as u32);
}
#[test]
fn test_intrinsic_lower_memmove() {
let i = RecognizedIntrinsic::Memmove;
let instrs = i.lower(None, &[1, 2, 3]);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::CALL as u32);
}
#[test]
fn test_intrinsic_lower_bswap() {
let i = RecognizedIntrinsic::Bswap;
let instrs = i.lower(Some(10), &[5]);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, X86Opcode::MOV as u32);
}
#[test]
fn test_intrinsic_lower_bswap_no_dst() {
let i = RecognizedIntrinsic::Bswap;
let instrs = i.lower(None, &[5]);
assert!(instrs.is_empty());
}
#[test]
fn test_intrinsic_lower_sqrt_no_dst() {
let i = RecognizedIntrinsic::Sqrt;
let instrs = i.lower(None, &[5]);
assert!(instrs.is_empty());
}
#[test]
fn test_recognize_bswap() {
let i = RecognizedIntrinsic::from_name("llvm.bswap.i64");
assert_eq!(i, Some(RecognizedIntrinsic::Bswap));
}
#[test]
fn test_recognize_ctpop() {
let i = RecognizedIntrinsic::from_name("llvm.ctpop.i32");
assert_eq!(i, Some(RecognizedIntrinsic::Ctpop));
}
#[test]
fn test_recognize_ctlz() {
let i = RecognizedIntrinsic::from_name("llvm.ctlz.i64");
assert_eq!(i, Some(RecognizedIntrinsic::Ctlz));
}
#[test]
fn test_recognize_cttz() {
let i = RecognizedIntrinsic::from_name("llvm.cttz.i32");
assert_eq!(i, Some(RecognizedIntrinsic::Cttz));
}
#[test]
fn test_recognize_fabs() {
let i = RecognizedIntrinsic::from_name("llvm.fabs.f64");
assert_eq!(i, Some(RecognizedIntrinsic::FAbs));
}
#[test]
fn test_recognize_floor() {
let i = RecognizedIntrinsic::from_name("llvm.floor.f32");
assert_eq!(i, Some(RecognizedIntrinsic::Round));
}
#[test]
fn test_recognize_ceil() {
let i = RecognizedIntrinsic::from_name("llvm.ceil.f64");
assert_eq!(i, Some(RecognizedIntrinsic::Round));
}
#[test]
fn test_recognize_trunc_intrinsic() {
let i = RecognizedIntrinsic::from_name("llvm.trunc.f32");
assert_eq!(i, Some(RecognizedIntrinsic::Round));
}
#[test]
fn test_recognize_bitreverse() {
let i = RecognizedIntrinsic::from_name("llvm.bitreverse.i64");
assert_eq!(i, Some(RecognizedIntrinsic::Bitreverse));
}
#[test]
fn test_recognize_funnel_shift_left() {
let i = RecognizedIntrinsic::from_name("llvm.fshl.i32");
assert_eq!(i, Some(RecognizedIntrinsic::FunnelShift));
}
#[test]
fn test_recognize_funnel_shift_right() {
let i = RecognizedIntrinsic::from_name("llvm.fshr.i64");
assert_eq!(i, Some(RecognizedIntrinsic::FunnelShift));
}
#[test]
fn test_recognize_sadd_overflow() {
let i = RecognizedIntrinsic::from_name("llvm.sadd.with.overflow.i32");
assert_eq!(i, Some(RecognizedIntrinsic::AddWithOverflow));
}
#[test]
fn test_recognize_uadd_overflow() {
let i = RecognizedIntrinsic::from_name("llvm.uadd.with.overflow.i64");
assert_eq!(i, Some(RecognizedIntrinsic::AddWithOverflow));
}
#[test]
fn test_recognize_ssub_overflow() {
let i = RecognizedIntrinsic::from_name("llvm.ssub.with.overflow.i32");
assert_eq!(i, Some(RecognizedIntrinsic::SubWithOverflow));
}
#[test]
fn test_recognize_smul_overflow() {
let i = RecognizedIntrinsic::from_name("llvm.smul.with.overflow.i64");
assert_eq!(i, Some(RecognizedIntrinsic::MulWithOverflow));
}
#[test]
fn test_recognize_lifetime_start() {
let i = RecognizedIntrinsic::from_name("llvm.lifetime.start.p0i8");
assert_eq!(i, Some(RecognizedIntrinsic::Lifetime));
}
#[test]
fn test_recognize_lifetime_end() {
let i = RecognizedIntrinsic::from_name("llvm.lifetime.end.p0i8");
assert_eq!(i, Some(RecognizedIntrinsic::Lifetime));
}
#[test]
fn test_recognize_dbg_declare() {
let i = RecognizedIntrinsic::from_name("llvm.dbg.declare");
assert_eq!(i, Some(RecognizedIntrinsic::DbgInfo));
}
#[test]
fn test_recognize_dbg_value() {
let i = RecognizedIntrinsic::from_name("llvm.dbg.value");
assert_eq!(i, Some(RecognizedIntrinsic::DbgInfo));
}
#[test]
fn test_recognize_eh_typeid_for() {
let i = RecognizedIntrinsic::from_name("llvm.eh.typeid.for");
assert_eq!(i, Some(RecognizedIntrinsic::EHTypeIdFor));
}
#[test]
fn test_recognize_stackprotector() {
let i = RecognizedIntrinsic::from_name("llvm.stackprotector");
assert_eq!(i, Some(RecognizedIntrinsic::StackProtector));
}
#[test]
fn test_debug_loc_default_not_valid() {
let dl = DebugLoc::default();
assert!(!dl.is_valid());
}
#[test]
fn test_debug_loc_valid() {
let dl = DebugLoc::new("test.c", 42, 10);
assert!(dl.is_valid());
}
#[test]
fn test_debug_loc_dwarf_string() {
let dl = DebugLoc::new("main.c", 100, 5);
assert_eq!(dl.to_dwarf_string(), "main.c:100:5");
}
#[test]
fn test_debug_loc_inlined_at() {
let inner = DebugLoc::new("inline.h", 20, 3);
let outer = DebugLoc::with_inlined_at("main.c", 50, 1, inner);
assert_eq!(outer.to_dwarf_string(), "main.c:50:1 @ inline.h:20:3");
}
#[test]
fn test_debug_variable_new() {
let dv = DebugVariable::new("x", "i32", false);
assert_eq!(dv.name, "x");
assert_eq!(dv.type_name, "i32");
assert!(!dv.is_argument);
}
#[test]
fn test_debug_variable_argument() {
let dv = DebugVariable::new("argc", "i32", true);
assert!(dv.is_argument);
}
#[test]
fn test_debug_variable_set_location() {
let mut dv = DebugVariable::new("y", "double", false);
dv.set_location(Some(7), None);
assert_eq!(dv.vreg, Some(7));
dv.set_location(None, Some(-16));
assert_eq!(dv.stack_offset, Some(-16));
}
#[test]
fn test_debug_info_tracker_disabled() {
let mut tracker = DebugInfoTracker::new(false);
tracker.set_location(DebugLoc::new("a.c", 1, 1));
assert!(!tracker.current_loc.is_valid());
}
#[test]
fn test_debug_info_tracker_enabled() {
let mut tracker = DebugInfoTracker::new(true);
let loc = DebugLoc::new("b.c", 5, 7);
tracker.set_location(loc.clone());
assert_eq!(tracker.current_loc, loc);
}
#[test]
fn test_debug_info_tracker_variables() {
let mut tracker = DebugInfoTracker::new(true);
tracker.add_variable(DebugVariable::new("a", "i32", false));
tracker.add_variable(DebugVariable::new("b", "i64", true));
assert_eq!(tracker.variables.len(), 2);
assert!(tracker.find_variable("a").is_some());
assert!(tracker.find_variable("c").is_none());
}
#[test]
fn test_debug_info_tracker_instr_locations() {
let mut tracker = DebugInfoTracker::new(true);
tracker.record_instr_location(0, DebugLoc::new("f.c", 1, 1));
tracker.record_instr_location(5, DebugLoc::new("f.c", 10, 3));
assert_eq!(tracker.instr_locations.len(), 2);
let loc = tracker.get_location(3);
assert!(loc.is_some());
assert_eq!(loc.unwrap().line, 1);
}
#[test]
fn test_verify_machine_instr_ok() {
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
mi.push_reg(1);
mi.push_reg(2);
let result = verify_machine_instr(&mi, 0);
assert!(result.passed);
}
#[test]
fn test_verify_machine_instr_warning_zero_opcode() {
let mi = MachineInstr::new(0);
let result = verify_machine_instr(&mi, 5);
assert!(result.passed);
assert!(!result.warnings.is_empty());
}
#[test]
fn test_verify_machine_function_empty() {
let mf = MachineFunction::new("empty");
let result = verify_machine_function(&mf);
assert!(result.passed);
}
#[test]
fn test_verify_machine_function_with_blocks() {
let mut mf = MachineFunction::new("test");
let mbb = MachineBasicBlock {
name: "entry".into(),
instructions: vec![MachineInstr::new(X86Opcode::RET as u32)],
successors: vec![],
};
mf.push_block(mbb);
let result = verify_machine_function(&mf);
assert!(result.passed);
}
#[test]
fn test_stats_new_zero() {
let stats = FastISelStats::new();
assert_eq!(stats.total_ir_instructions, 0);
assert_eq!(stats.spills, 0);
}
#[test]
fn test_stats_expansion_ratio() {
let mut stats = FastISelStats::new();
stats.total_ir_instructions = 10;
stats.total_machine_instructions = 25;
assert!((stats.expansion_ratio() - 2.5).abs() < 0.01);
}
#[test]
fn test_stats_expansion_ratio_zero() {
let stats = FastISelStats::new();
assert_eq!(stats.expansion_ratio(), 0.0);
}
#[test]
fn test_stats_print_summary() {
let mut stats = FastISelStats::new();
stats.total_ir_instructions = 100;
stats.total_machine_instructions = 250;
stats.direct_calls = 3;
assert!(stats.print_summary().contains("FastISel Stats"));
}
#[test]
fn test_config_debug_defaults() {
let cfg = FastISelConfig::debug_defaults();
assert!(cfg.debug_info);
assert!(cfg.emit_frame_pointer);
assert!(!cfg.collect_stats);
}
#[test]
fn test_config_release_defaults() {
let cfg = FastISelConfig::release_defaults();
assert!(!cfg.debug_info);
assert!(!cfg.emit_frame_pointer);
}
#[test]
fn test_config_default_is_debug() {
let cfg = FastISelConfig::default();
assert!(cfg.debug_info);
}
#[test]
fn test_imm_fits_in_bits_8() {
assert!(imm_fits_in_bits(0, 8));
assert!(imm_fits_in_bits(127, 8));
assert!(imm_fits_in_bits(-128, 8));
assert!(!imm_fits_in_bits(128, 8));
assert!(!imm_fits_in_bits(-129, 8));
}
#[test]
fn test_imm_fits_in_bits_32() {
assert!(imm_fits_in_bits(i32::MAX as i64, 32));
assert!(imm_fits_in_bits(i32::MIN as i64, 32));
assert!(!imm_fits_in_bits(i32::MAX as i64 + 1, 32));
}
#[test]
fn test_uimm_fits_in_bits_8() {
assert!(uimm_fits_in_bits(0, 8));
assert!(uimm_fits_in_bits(255, 8));
assert!(!uimm_fits_in_bits(256, 8));
assert!(!uimm_fits_in_bits(-1, 8));
}
#[test]
fn test_get_mov_opcode_for_width() {
assert_eq!(get_mov_opcode_for_width(8), X86Opcode::MOV as u32);
assert_eq!(get_mov_opcode_for_width(64), X86Opcode::MOV as u32);
}
#[test]
fn test_select_add_all_widths() {
for width in &[8u32, 16, 32, 64] {
let ty = Type::int(*width);
let op0 = make_ref(Value::new(ty.clone()));
op0.borrow_mut().vid = 10000 + *width as u64;
let op1 = make_ref(Value::new(ty.clone()));
op1.borrow_mut().vid = 10100 + *width as u64;
let add_inst = make_inst(Opcode::Add, ty, vec![op0, op1]);
add_inst.borrow_mut().vid = 20000 + *width as u64;
let bb = make_bb("entry", vec![add_inst]);
let func = make_func(
&format!("test_add_i{width}"),
Type::void(),
vec![],
vec![bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1, "Failed for i{width}");
}
}
#[test]
fn test_select_mul_chain() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 6000;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 6001;
let op2 = make_ref(Value::new(Type::i32()));
op2.borrow_mut().vid = 6002;
let mul1 = make_inst(Opcode::Mul, Type::i32(), vec![op0.clone(), op1.clone()]);
mul1.borrow_mut().vid = 6100;
let mul2 = make_inst(Opcode::Mul, Type::i32(), vec![mul1.clone(), op2.clone()]);
mul2.borrow_mut().vid = 6101;
let bb = make_bb("entry", vec![mul1, mul2]);
let func = make_func("test_mul_chain", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_select_ret_void() {
let ret_inst = make_inst(Opcode::Ret, Type::void(), vec![]);
ret_inst.borrow_mut().vid = 6200;
let bb = make_bb("entry", vec![ret_inst]);
let func = make_func("test_ret_void", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fadd_f64() {
let op0 = make_ref(Value::new(Type::double()));
op0.borrow_mut().vid = 7000;
let op1 = make_ref(Value::new(Type::double()));
op1.borrow_mut().vid = 7001;
let fadd = make_inst(Opcode::FAdd, Type::double(), vec![op0, op1]);
fadd.borrow_mut().vid = 7100;
let bb = make_bb("entry", vec![fadd]);
let func = make_func("test_fadd_f64", Type::double(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fsub_f64() {
let op0 = make_ref(Value::new(Type::double()));
op0.borrow_mut().vid = 7200;
let op1 = make_ref(Value::new(Type::double()));
op1.borrow_mut().vid = 7201;
let fsub = make_inst(Opcode::FSub, Type::double(), vec![op0, op1]);
fsub.borrow_mut().vid = 7300;
let bb = make_bb("entry", vec![fsub]);
let func = make_func("test_fsub_f64", Type::double(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fmul_f64() {
let op0 = make_ref(Value::new(Type::double()));
op0.borrow_mut().vid = 7400;
let op1 = make_ref(Value::new(Type::double()));
op1.borrow_mut().vid = 7401;
let fmul = make_inst(Opcode::FMul, Type::double(), vec![op0, op1]);
fmul.borrow_mut().vid = 7500;
let bb = make_bb("entry", vec![fmul]);
let func = make_func("test_fmul_f64", Type::double(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fdiv_f64() {
let op0 = make_ref(Value::new(Type::double()));
op0.borrow_mut().vid = 7600;
let op1 = make_ref(Value::new(Type::double()));
op1.borrow_mut().vid = 7601;
let fdiv = make_inst(Opcode::FDiv, Type::double(), vec![op0, op1]);
fdiv.borrow_mut().vid = 7700;
let bb = make_bb("entry", vec![fdiv]);
let func = make_func("test_fdiv_f64", Type::double(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_constants() {
let c42 = make_const_int(42);
let c10 = make_const_int(10);
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 8000;
let add_const = make_inst(Opcode::Add, Type::i32(), vec![op0.clone(), c42.clone()]);
add_const.borrow_mut().vid = 8100;
let sub_const = make_inst(Opcode::Sub, Type::i32(), vec![op0.clone(), c10.clone()]);
sub_const.borrow_mut().vid = 8101;
let bb = make_bb("entry", vec![add_const, sub_const]);
let func = make_func("test_const_ops", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_select_sdiv_full_sequence() {
let op0 = make_ref(Value::new(Type::i64()));
op0.borrow_mut().vid = 8200;
let op1 = make_ref(Value::new(Type::i64()));
op1.borrow_mut().vid = 8201;
let sdiv = make_inst(Opcode::SDiv, Type::i64(), vec![op0, op1]);
sdiv.borrow_mut().vid = 8300;
let bb = make_bb("entry", vec![sdiv]);
let func = make_func("test_sdiv", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_select_udiv_full_sequence() {
let op0 = make_ref(Value::new(Type::i64()));
op0.borrow_mut().vid = 8400;
let op1 = make_ref(Value::new(Type::i64()));
op1.borrow_mut().vid = 8401;
let udiv = make_inst(Opcode::UDiv, Type::i64(), vec![op0, op1]);
udiv.borrow_mut().vid = 8500;
let bb = make_bb("entry", vec![udiv]);
let func = make_func("test_udiv", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_select_srem_full_sequence() {
let op0 = make_ref(Value::new(Type::i64()));
op0.borrow_mut().vid = 8600;
let op1 = make_ref(Value::new(Type::i64()));
op1.borrow_mut().vid = 8601;
let srem = make_inst(Opcode::SRem, Type::i64(), vec![op0, op1]);
srem.borrow_mut().vid = 8700;
let bb = make_bb("entry", vec![srem]);
let func = make_func("test_srem", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_select_urem_full_sequence() {
let op0 = make_ref(Value::new(Type::i64()));
op0.borrow_mut().vid = 8800;
let op1 = make_ref(Value::new(Type::i64()));
op1.borrow_mut().vid = 8801;
let urem = make_inst(Opcode::URem, Type::i64(), vec![op0, op1]);
urem.borrow_mut().vid = 8900;
let bb = make_bb("entry", vec![urem]);
let func = make_func("test_urem", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_select_all_icmp_predicates() {
let pairs = vec![
(icmp_pred::EQ, X86Opcode::SETE),
(icmp_pred::NE, X86Opcode::SETNE),
(icmp_pred::UGT, X86Opcode::SETA),
(icmp_pred::UGE, X86Opcode::SETAE),
(icmp_pred::ULT, X86Opcode::SETB),
(icmp_pred::ULE, X86Opcode::SETBE),
(icmp_pred::SGT, X86Opcode::SETG),
(icmp_pred::SGE, X86Opcode::SETGE),
(icmp_pred::SLT, X86Opcode::SETL),
(icmp_pred::SLE, X86Opcode::SETLE),
];
for (pred, expected) in pairs {
let setcc = X86FastISel::icmp_pred_to_setcc(pred);
assert_eq!(setcc, expected as u32, "Mismatch for pred {pred}");
}
}
#[test]
fn test_select_fcmp_all_predicates() {
let all_preds = vec![
fcmp_pred::FALSE,
fcmp_pred::OEQ,
fcmp_pred::OGT,
fcmp_pred::OGE,
fcmp_pred::OLT,
fcmp_pred::OLE,
fcmp_pred::ONE,
fcmp_pred::ORD,
fcmp_pred::UNO,
fcmp_pred::UEQ,
fcmp_pred::UGT,
fcmp_pred::UGE,
fcmp_pred::ULT,
fcmp_pred::ULE,
fcmp_pred::UNE,
fcmp_pred::TRUE,
];
for &pred in &all_preds {
let op0 = make_ref(Value::new(Type::float()));
op0.borrow_mut().vid = 99000 + pred as u64;
let op1 = make_ref(Value::new(Type::float()));
op1.borrow_mut().vid = 99100 + pred as u64;
let fcmp = make_inst(Opcode::FCmp, Type::i1(), vec![op0, op1]);
fcmp.borrow_mut().vid = 99200 + pred as u64;
fcmp.borrow_mut().subclass_data = pred;
let bb = make_bb("entry", vec![fcmp]);
let func = make_func(&format!("test_fcmp_{pred}"), Type::i1(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1, "Failed for fcmp pred {pred}");
}
}
#[test]
fn test_select_call_multiple_args() {
let callee = make_ref(Value::new(Type::void()));
callee.borrow_mut().vid = 9100;
callee.borrow_mut().name = "multi_args".to_string();
callee.borrow_mut().subclass = SubclassKind::Function;
let arg0 = make_ref(Value::new(Type::i32()));
arg0.borrow_mut().vid = 9101;
let arg1 = make_ref(Value::new(Type::i32()));
arg1.borrow_mut().vid = 9102;
let arg2 = make_ref(Value::new(Type::i32()));
arg2.borrow_mut().vid = 9103;
let call = make_inst(
Opcode::Call,
Type::i32(),
vec![callee.clone(), arg0, arg1, arg2],
);
call.borrow_mut().vid = 9200;
let bb = make_bb("entry", vec![call]);
let func = make_func("test_multi_args", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 5);
}
#[test]
fn test_select_call_indirect() {
let callee = make_ref(Value::new(Type::void()));
callee.borrow_mut().vid = 9300;
callee.borrow_mut().subclass = SubclassKind::Value;
let call = make_inst(Opcode::Call, Type::void(), vec![callee.clone()]);
call.borrow_mut().vid = 9400;
let bb = make_bb("entry", vec![call]);
let func = make_func("test_indirect", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_simple_gep() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 9500;
let gep = make_inst(Opcode::GetElementPtr, Type::pointer(0), vec![ptr.clone()]);
gep.borrow_mut().vid = 9600;
let bb = make_bb("entry", vec![gep]);
let func = make_func("test_simple_gep", Type::pointer(0), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_switch_default_only() {
let cond = make_ref(Value::new(Type::i32()));
cond.borrow_mut().vid = 9700;
let default_bb = make_bb("default", vec![]);
default_bb.borrow_mut().vid = 9701;
let switch = make_inst(Opcode::Switch, Type::void(), vec![cond, default_bb]);
switch.borrow_mut().vid = 9800;
let bb = make_bb("entry", vec![switch]);
let func = make_func(
"test_switch_def",
Type::void(),
vec![],
vec![bb, default_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_indirectbr() {
let addr = make_ref(Value::new(Type::pointer(0)));
addr.borrow_mut().vid = 9800;
let target = make_bb("target", vec![]);
target.borrow_mut().vid = 9801;
let ibr = make_inst(Opcode::IndirectBr, Type::void(), vec![addr, target]);
ibr.borrow_mut().vid = 9900;
let bb = make_bb("entry", vec![ibr]);
let func = make_func("test_indirectbr", Type::void(), vec![], vec![bb, target]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_unreachable() {
let inst = make_inst(Opcode::Unreachable, Type::void(), vec![]);
inst.borrow_mut().vid = 9901;
let bb = make_bb("unreach", vec![inst]);
let func = make_func("test_unreach", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions <= 1);
}
#[test]
fn test_select_neg() {
let src = make_ref(Value::new(Type::i32()));
src.borrow_mut().vid = 9999;
let zero = make_ref(Value::new(Type::i32()));
zero.borrow_mut().vid = 10000;
zero.borrow_mut().subclass = SubclassKind::ConstantInt;
zero.borrow_mut().name = "0".to_string();
let sub_inst = make_inst(Opcode::Sub, Type::i32(), vec![zero, src]);
sub_inst.borrow_mut().vid = 10100;
let bb = make_bb("entry", vec![sub_inst]);
let func = make_func("test_neg", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_fast_isel_result_construction() {
let mf = MachineFunction::new("test");
let result = FastISelResult::new(mf.clone(), 1, 5, 10, 32, vec![]);
assert_eq!(result.num_blocks, 1);
assert_eq!(result.num_instructions, 5);
assert_eq!(result.num_vregs, 10);
assert_eq!(result.frame_size, 32);
}
#[test]
fn test_lower_frame_index_known() {
let mut map = HashMap::new();
map.insert(0, -8);
map.insert(1, -16);
let (base, offset) = lower_frame_index(1, &map, true);
assert_eq!(base, RBP);
assert_eq!(offset, -16);
}
#[test]
fn test_lower_frame_index_32bit() {
let mut map = HashMap::new();
map.insert(0, -4);
let (base, offset) = lower_frame_index(0, &map, false);
assert_eq!(base, EBP);
assert_eq!(offset, -4);
}
#[test]
fn test_extract_constant_int_from_name() {
let val = make_ref(Value::new(Type::i64()));
val.borrow_mut().subclass = SubclassKind::ConstantInt;
val.borrow_mut().name = "i64 12345".to_string();
let fast_isel =
X86FastISel::new(X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""));
let result = fast_isel.extract_constant_int(&val);
assert_eq!(result, Some(12345));
}
#[test]
fn test_extract_constant_int_from_subclass_data() {
let val = make_ref(Value::new(Type::i32()));
val.borrow_mut().subclass = SubclassKind::ConstantInt;
val.borrow_mut().subclass_data = 789;
let fast_isel =
X86FastISel::new(X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""));
let result = fast_isel.extract_constant_int(&val);
assert_eq!(result, Some(789));
}
#[test]
fn test_extract_constant_int_from_direct_parse() {
let val = make_ref(Value::new(Type::i32()));
val.borrow_mut().subclass = SubclassKind::ConstantInt;
val.borrow_mut().name = "-42".to_string();
let fast_isel =
X86FastISel::new(X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""));
let result = fast_isel.extract_constant_int(&val);
assert_eq!(result, Some(-42));
}
#[test]
fn test_extract_constant_int_non_constant() {
let val = make_ref(Value::new(Type::i32()));
val.borrow_mut().subclass = SubclassKind::Value;
let fast_isel =
X86FastISel::new(X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""));
let result = fast_isel.extract_constant_int(&val);
assert_eq!(result, None);
}
#[test]
fn test_select_sdiv_i8() {
let op0 = make_ref(Value::new(Type::i8()));
op0.borrow_mut().vid = 11100;
let op1 = make_ref(Value::new(Type::i8()));
op1.borrow_mut().vid = 11101;
let sdiv = make_inst(Opcode::SDiv, Type::i8(), vec![op0, op1]);
sdiv.borrow_mut().vid = 11200;
let bb = make_bb("entry", vec![sdiv]);
let func = make_func("test_sdiv_i8", Type::i8(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_invoke_no_args() {
let callee = make_ref(Value::new(Type::void()));
callee.borrow_mut().vid = 11300;
callee.borrow_mut().name = "no_args".to_string();
callee.borrow_mut().subclass = SubclassKind::Function;
let normal_bb = make_bb("normal", vec![]);
normal_bb.borrow_mut().vid = 11301;
let unwind_bb = make_bb("unwind", vec![]);
unwind_bb.borrow_mut().vid = 11302;
let invoke = make_inst(
Opcode::Invoke,
Type::i32(),
vec![callee, normal_bb, unwind_bb],
);
invoke.borrow_mut().vid = 11400;
let bb = make_bb("entry", vec![invoke]);
let func = make_func(
"test_invoke_noargs",
Type::i32(),
vec![],
vec![bb, normal_bb, unwind_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_select_frem_f32() {
let op0 = make_ref(Value::new(Type::float()));
op0.borrow_mut().vid = 11500;
let op1 = make_ref(Value::new(Type::float()));
op1.borrow_mut().vid = 11501;
let frem = make_inst(Opcode::FRem, Type::float(), vec![op0, op1]);
frem.borrow_mut().vid = 11600;
let bb = make_bb("entry", vec![frem]);
let func = make_func("test_frem", Type::float(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_frem_f64() {
let op0 = make_ref(Value::new(Type::double()));
op0.borrow_mut().vid = 11700;
let op1 = make_ref(Value::new(Type::double()));
op1.borrow_mut().vid = 11701;
let frem = make_inst(Opcode::FRem, Type::double(), vec![op0, op1]);
frem.borrow_mut().vid = 11800;
let bb = make_bb("entry", vec![frem]);
let func = make_func("test_frem_f64", Type::double(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_uitofp() {
let src = make_ref(Value::new(Type::i32()));
src.borrow_mut().vid = 11900;
let uitofp = make_inst(Opcode::UIToFP, Type::float(), vec![src]);
uitofp.borrow_mut().vid = 12000;
let bb = make_bb("entry", vec![uitofp]);
let func = make_func("test_uitofp", Type::float(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fptoui() {
let src = make_ref(Value::new(Type::float()));
src.borrow_mut().vid = 12100;
let fptoui = make_inst(Opcode::FPToUI, Type::i32(), vec![src]);
fptoui.borrow_mut().vid = 12200;
let bb = make_bb("entry", vec![fptoui]);
let func = make_func("test_fptoui", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fptosi() {
let src = make_ref(Value::new(Type::double()));
src.borrow_mut().vid = 12300;
let fptosi = make_inst(Opcode::FPToSI, Type::i32(), vec![src]);
fptosi.borrow_mut().vid = 12400;
let bb = make_bb("entry", vec![fptosi]);
let func = make_func("test_fptosi", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_sext_i8_to_i32() {
let src = make_ref(Value::new(Type::i8()));
src.borrow_mut().vid = 12500;
let sext = make_inst(Opcode::SExt, Type::i32(), vec![src]);
sext.borrow_mut().vid = 12600;
let bb = make_bb("entry", vec![sext]);
let func = make_func("test_sext", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_zext_i16_to_i64() {
let src = make_ref(Value::new(Type::i16()));
src.borrow_mut().vid = 12700;
let zext = make_inst(Opcode::ZExt, Type::i64(), vec![src]);
zext.borrow_mut().vid = 12800;
let bb = make_bb("entry", vec![zext]);
let func = make_func("test_zext", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_trunc_i64_to_i32() {
let src = make_ref(Value::new(Type::i64()));
src.borrow_mut().vid = 12900;
let trunc = make_inst(Opcode::Trunc, Type::i32(), vec![src]);
trunc.borrow_mut().vid = 13000;
let bb = make_bb("entry", vec![trunc]);
let func = make_func("test_trunc", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fptrunc() {
let src = make_ref(Value::new(Type::double()));
src.borrow_mut().vid = 13100;
let fptrunc = make_inst(Opcode::FPTrunc, Type::float(), vec![src]);
fptrunc.borrow_mut().vid = 13200;
let bb = make_bb("entry", vec![fptrunc]);
let func = make_func("test_fptrunc", Type::float(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fpext() {
let src = make_ref(Value::new(Type::float()));
src.borrow_mut().vid = 13300;
let fpext = make_inst(Opcode::FPExt, Type::double(), vec![src]);
fpext.borrow_mut().vid = 13400;
let bb = make_bb("entry", vec![fpext]);
let func = make_func("test_fpext", Type::double(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_bitcast() {
let src = make_ref(Value::new(Type::i32()));
src.borrow_mut().vid = 13500;
let bitcast = make_inst(Opcode::BitCast, Type::float(), vec![src]);
bitcast.borrow_mut().vid = 13600;
let bb = make_bb("entry", vec![bitcast]);
let func = make_func("test_bitcast", Type::float(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_addrspacecast() {
let src = make_ref(Value::new(Type::pointer(0)));
src.borrow_mut().vid = 13700;
let cast = make_inst(Opcode::AddrSpaceCast, Type::pointer(1), vec![src]);
cast.borrow_mut().vid = 13800;
let bb = make_bb("entry", vec![cast]);
let func = make_func("test_addrspacecast", Type::pointer(1), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_ptrtoint() {
let src = make_ref(Value::new(Type::pointer(0)));
src.borrow_mut().vid = 13900;
let cast = make_inst(Opcode::PtrToInt, Type::i64(), vec![src]);
cast.borrow_mut().vid = 14000;
let bb = make_bb("entry", vec![cast]);
let func = make_func("test_ptrtoint", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_inttoptr() {
let src = make_ref(Value::new(Type::i64()));
src.borrow_mut().vid = 14100;
let cast = make_inst(Opcode::IntToPtr, Type::pointer(0), vec![src]);
cast.borrow_mut().vid = 14200;
let bb = make_bb("entry", vec![cast]);
let func = make_func("test_inttoptr", Type::pointer(0), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_phi_multiple_incoming() {
let v0 = make_ref(Value::new(Type::i32()));
v0.borrow_mut().vid = 14300;
let v1 = make_ref(Value::new(Type::i32()));
v1.borrow_mut().vid = 14301;
let v2 = make_ref(Value::new(Type::i32()));
v2.borrow_mut().vid = 14302;
let bb0 = make_bb("pred0", vec![]);
bb0.borrow_mut().vid = 14303;
let bb1 = make_bb("pred1", vec![]);
bb1.borrow_mut().vid = 14304;
let bb2 = make_bb("pred2", vec![]);
bb2.borrow_mut().vid = 14305;
let phi = make_inst(Opcode::Phi, Type::i32(), vec![v0, bb0, v1, bb1, v2, bb2]);
phi.borrow_mut().vid = 14400;
let bb = make_bb("entry", vec![phi]);
let func = make_func("test_phi_multi", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_shuffle_vector_with_mask() {
let v0 = make_ref(Value::new(Type::i128()));
v0.borrow_mut().vid = 14500;
let v1 = make_ref(Value::new(Type::i128()));
v1.borrow_mut().vid = 14501;
let sv = make_inst(Opcode::ShuffleVector, Type::i128(), vec![v0, v1]);
sv.borrow_mut().vid = 14600;
sv.borrow_mut().subclass_data = 0x1B;
let bb = make_bb("entry", vec![sv]);
let func = make_func("test_shuffle", Type::i128(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_extract_element() {
let vec = make_ref(Value::new(Type::i128()));
vec.borrow_mut().vid = 14700;
let extract = make_inst(
Opcode::ExtractElement,
Type::i32(),
vec![vec, make_const_int(0)],
);
extract.borrow_mut().vid = 14800;
let bb = make_bb("entry", vec![extract]);
let func = make_func("test_extract_elt", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_insert_element() {
let vec = make_ref(Value::new(Type::i128()));
vec.borrow_mut().vid = 14900;
let elem = make_ref(Value::new(Type::i32()));
elem.borrow_mut().vid = 14901;
let insert = make_inst(
Opcode::InsertElement,
Type::i128(),
vec![vec, elem, make_const_int(2)],
);
insert.borrow_mut().vid = 15000;
let bb = make_bb("entry", vec![insert]);
let func = make_func("test_insert_elt", Type::i128(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_complex_block() {
let a = make_ref(Value::new(Type::i32()));
a.borrow_mut().vid = 15100;
let b = make_ref(Value::new(Type::i32()));
b.borrow_mut().vid = 15101;
let c = make_ref(Value::new(Type::i32()));
c.borrow_mut().vid = 15102;
let add1 = make_inst(Opcode::Add, Type::i32(), vec![a.clone(), b.clone()]);
add1.borrow_mut().vid = 15200;
let mul1 = make_inst(Opcode::Mul, Type::i32(), vec![add1.clone(), c.clone()]);
mul1.borrow_mut().vid = 15201;
let sub1 = make_inst(Opcode::Sub, Type::i32(), vec![mul1.clone(), a.clone()]);
sub1.borrow_mut().vid = 15202;
let bb = make_bb("entry", vec![add1, mul1, sub1]);
let func = make_func("test_complex", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_run_fast_isel_with_subtarget() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 15300;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 15301;
let add = make_inst(Opcode::Add, Type::i32(), vec![op0, op1]);
add.borrow_mut().vid = 15400;
let bb = make_bb("entry", vec![add]);
let func = make_func("test_with_st", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let st = X86Subtarget::new("x86_64-unknown-linux-gnu", "haswell", "+avx2");
let result = run_x86_fast_isel(&func, Some(st));
assert!(result.num_instructions >= 1);
}
#[test]
fn test_icmp_pred_to_cc_all_standard() {
assert_eq!(
X86FastISel::icmp_pred_to_cc(icmp_pred::EQ),
X86ConditionCode::Z
);
assert_eq!(
X86FastISel::icmp_pred_to_cc(icmp_pred::NE),
X86ConditionCode::NZ
);
assert_eq!(
X86FastISel::icmp_pred_to_cc(icmp_pred::SGT),
X86ConditionCode::G
);
assert_eq!(
X86FastISel::icmp_pred_to_cc(icmp_pred::SLT),
X86ConditionCode::L
);
}
#[test]
fn test_get_alloca_size_constant() {
let fast_isel =
X86FastISel::new(X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""));
let size_val = make_ref(Value::new(Type::i32()));
size_val.borrow_mut().subclass = SubclassKind::ConstantInt;
size_val.borrow_mut().name = "64".to_string();
let alloca = make_inst(Opcode::Alloca, Type::pointer(0), vec![size_val]);
let size = fast_isel.get_alloca_size(&alloca.borrow());
assert!(size > 0);
}
#[test]
fn test_get_alloca_size_default() {
let fast_isel =
X86FastISel::new(X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""));
let alloca = make_inst(Opcode::Alloca, Type::pointer(0), vec![]);
let size = fast_isel.get_alloca_size(&alloca.borrow());
assert_eq!(size, 8);
}
#[test]
fn test_imm_fits_in_bits_zero_bits() {
assert!(imm_fits_in_bits(0, 0));
assert!(!imm_fits_in_bits(1, 0));
assert!(!imm_fits_in_bits(-1, 0));
}
#[test]
fn test_imm_fits_in_bits_64() {
assert!(imm_fits_in_bits(i64::MAX, 64));
assert!(imm_fits_in_bits(i64::MIN, 64));
}
#[test]
fn test_uimm_fits_in_bits_32() {
assert!(uimm_fits_in_bits(0, 32));
assert!(uimm_fits_in_bits(u32::MAX as i64, 32));
assert!(!uimm_fits_in_bits(u32::MAX as i64 + 1, 32));
}
#[test]
fn test_uimm_fits_in_bits_64() {
assert!(uimm_fits_in_bits(0, 64));
assert!(uimm_fits_in_bits(i64::MAX, 64));
}
#[test]
fn test_try_fold_address_simple() {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
let result = try_fold_address(&mut mi, 1, 8, None, 1);
assert!(result);
assert_eq!(mi.operands.len(), 2); }
#[test]
fn test_try_fold_address_with_index() {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
let result = try_fold_address(&mut mi, 1, 0, Some(2), 4);
assert!(result);
assert_eq!(mi.operands.len(), 3); }
#[test]
fn test_verification_result_collect_errors() {
let mut vr = VerificationResult::new();
vr.add_error("bad".into());
vr.add_error("worse".into());
assert!(!vr.passed);
assert_eq!(vr.errors.len(), 2);
}
#[test]
fn test_debug_info_tracker_get_location_none() {
let tracker = DebugInfoTracker::new(true);
assert!(tracker.get_location(0).is_none());
}
#[test]
fn test_debug_variable_default_location() {
let dv = DebugVariable::new("v", "i32", false);
assert!(dv.vreg.is_none());
assert!(dv.stack_offset.is_none());
}
#[test]
fn test_large_function_many_blocks() {
let mut blocks = Vec::new();
for i in 0..10 {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 20000 + i * 100;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 20001 + i * 100;
let add = make_inst(Opcode::Add, Type::i32(), vec![op0, op1]);
add.borrow_mut().vid = 21000 + i * 100;
let ret = make_inst(Opcode::Ret, Type::void(), vec![add]);
ret.borrow_mut().vid = 22000 + i * 100;
let bb = make_bb(&format!("block_{i}"), vec![add, ret]);
bb.borrow_mut().vid = 19000 + i * 100;
blocks.push(bb);
}
let func = make_func("test_many_blocks", Type::i32(), vec![], blocks);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 10);
assert!(result.num_instructions >= 10);
}
#[test]
fn test_many_instructions_single_block() {
let mut insts = Vec::new();
let a = make_ref(Value::new(Type::i32()));
a.borrow_mut().vid = 30000;
let b = make_ref(Value::new(Type::i32()));
b.borrow_mut().vid = 30001;
let mut prev = a.clone();
for i in 0..20 {
let add = make_inst(Opcode::Add, Type::i32(), vec![prev.clone(), b.clone()]);
add.borrow_mut().vid = 31000 + i * 100;
prev = add.clone();
insts.push(add);
}
let bb = make_bb("entry", insts);
let func = make_func("test_many_insts", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_instructions, 20);
}
#[test]
fn test_max_virtual_registers() {
let mut ra = FastRegAlloc::new(true);
for i in 0..1000u64 {
ra.create_vreg(i);
}
assert_eq!(ra.next_vreg, 1000);
}
#[test]
fn test_spill_manager_many_slots() {
let mut sm = SpillManager::new();
for i in 0..100u32 {
sm.allocate_slot(i);
}
assert!(sm.max_slot > 0);
assert_eq!(sm.slot_assignments.len(), 100);
}
#[test]
fn test_materializer_many_pool_entries() {
let mut mat = FastMaterializer::new(true);
for i in 0..500i64 {
mat.add_to_constant_pool(i * 1000);
}
assert_eq!(mat.constant_pool.len(), 500);
}
#[test]
fn test_icmp_pred_to_cc_edge_cases() {
assert_eq!(X86FastISel::icmp_pred_to_cc(999), X86ConditionCode::Z);
}
#[test]
fn test_fcmp_pred_to_cc_edge_cases() {
assert_eq!(X86FastISel::fcmp_pred_to_cc(999), X86ConditionCode::Z);
}
#[test]
fn test_select_mixed_types() {
let i32_val = make_ref(Value::new(Type::i32()));
i32_val.borrow_mut().vid = 40000;
let i64_val = make_ref(Value::new(Type::i64()));
i64_val.borrow_mut().vid = 40001;
let f32_val = make_ref(Value::new(Type::float()));
f32_val.borrow_mut().vid = 40002;
let f64_val = make_ref(Value::new(Type::double()));
f64_val.borrow_mut().vid = 40003;
let add_i32 = make_inst(
Opcode::Add,
Type::i32(),
vec![i32_val.clone(), i32_val.clone()],
);
add_i32.borrow_mut().vid = 41000;
let add_i64 = make_inst(
Opcode::Add,
Type::i64(),
vec![i64_val.clone(), i64_val.clone()],
);
add_i64.borrow_mut().vid = 41001;
let fadd = make_inst(
Opcode::FAdd,
Type::float(),
vec![f32_val.clone(), f32_val.clone()],
);
fadd.borrow_mut().vid = 41002;
let fadd_d = make_inst(
Opcode::FAdd,
Type::double(),
vec![f64_val.clone(), f64_val.clone()],
);
fadd_d.borrow_mut().vid = 41003;
let bb = make_bb("entry", vec![add_i32, add_i64, fadd, fadd_d]);
let func = make_func("test_mixed", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_br_conditional_edge_case_no_operands() {
let inst = make_inst(Opcode::Br, Type::void(), vec![]);
inst.borrow_mut().vid = 42000;
let bb = make_bb("entry", vec![inst]);
let func = make_func("test_br_edge", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions <= 1);
}
#[test]
fn test_switch_zero_cases() {
let cond = make_ref(Value::new(Type::i32()));
cond.borrow_mut().vid = 43000;
let default_bb = make_bb("default", vec![]);
default_bb.borrow_mut().vid = 43001;
let switch = make_inst(Opcode::Switch, Type::void(), vec![cond, default_bb]);
switch.borrow_mut().vid = 44000;
let bb = make_bb("entry", vec![switch]);
let func = make_func(
"test_switch_zero",
Type::void(),
vec![],
vec![bb, default_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_invoke_short() {
let inst = make_inst(Opcode::Invoke, Type::void(), vec![]);
inst.borrow_mut().vid = 45000;
let bb = make_bb("entry", vec![inst]);
let func = make_func("test_invoke_short", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions <= 1);
}
#[test]
fn test_phi_single_incoming() {
let v0 = make_ref(Value::new(Type::i32()));
v0.borrow_mut().vid = 46000;
let bb0 = make_bb("pred0", vec![]);
bb0.borrow_mut().vid = 46001;
let phi = make_inst(Opcode::Phi, Type::i32(), vec![v0, bb0]);
phi.borrow_mut().vid = 47000;
let bb = make_bb("entry", vec![phi]);
let func = make_func("test_phi_single", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fcmp_false_always() {
let op0 = make_ref(Value::new(Type::float()));
op0.borrow_mut().vid = 48000;
let op1 = make_ref(Value::new(Type::float()));
op1.borrow_mut().vid = 48001;
let fcmp = make_inst(Opcode::FCmp, Type::i1(), vec![op0, op1]);
fcmp.borrow_mut().vid = 49000;
fcmp.borrow_mut().subclass_data = fcmp_pred::FALSE;
let bb = make_bb("entry", vec![fcmp]);
let func = make_func("test_fcmp_false", Type::i1(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_fcmp_true_always() {
let op0 = make_ref(Value::new(Type::float()));
op0.borrow_mut().vid = 50000;
let op1 = make_ref(Value::new(Type::float()));
op1.borrow_mut().vid = 50001;
let fcmp = make_inst(Opcode::FCmp, Type::i1(), vec![op0, op1]);
fcmp.borrow_mut().vid = 51000;
fcmp.borrow_mut().subclass_data = fcmp_pred::TRUE;
let bb = make_bb("entry", vec![fcmp]);
let func = make_func("test_fcmp_true", Type::i1(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_stats_collection_update() {
let mut stats = FastISelStats::new();
stats.total_ir_instructions = 50;
stats.total_machine_instructions = 120;
stats.spills = 3;
stats.reloads = 5;
stats.direct_calls = 2;
stats.indirect_calls = 1;
assert!(stats.print_summary().contains("2"));
}
#[test]
fn test_verification_result_warnings() {
let mut vr = VerificationResult::new();
vr.add_warning("note 1".into());
vr.add_warning("note 2".into());
assert!(vr.passed);
assert_eq!(vr.warnings.len(), 2);
}
#[test]
fn test_verify_machine_instr_many_operands() {
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
for i in 0..10u32 {
mi.push_reg(i);
}
let result = verify_machine_instr(&mi, 0);
assert!(!result.warnings.is_empty());
}
#[test]
fn test_verify_machine_instr_def_no_ops() {
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
mi.def = Some(42);
let result = verify_machine_instr(&mi, 0);
assert!(!result.warnings.is_empty());
}
#[test]
fn test_verify_machine_function_multiple_blocks() {
let mut mf = MachineFunction::new("test");
mf.push_block(MachineBasicBlock {
name: "bb1".into(),
instructions: vec![MachineInstr::new(X86Opcode::ADD as u32)],
successors: vec![],
});
mf.push_block(MachineBasicBlock {
name: "bb2".into(),
instructions: vec![MachineInstr::new(X86Opcode::RET as u32)],
successors: vec![],
});
let result = verify_machine_function(&mf);
assert!(result.passed);
}
#[test]
fn test_verify_machine_function_unnamed_block() {
let mut mf = MachineFunction::new("test");
mf.push_block(MachineBasicBlock {
name: String::new(),
instructions: vec![],
successors: vec![],
});
let result = verify_machine_function(&mf);
assert!(result.passed);
assert!(!result.warnings.is_empty());
}
#[test]
fn test_extract_constant_int_i32_prefix() {
let val = make_ref(Value::new(Type::i32()));
val.borrow_mut().subclass = SubclassKind::ConstantInt;
val.borrow_mut().name = "i32 999".to_string();
let fast_isel =
X86FastISel::new(X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""));
let result = fast_isel.extract_constant_int(&val);
assert_eq!(result, Some(999));
}
#[test]
fn test_imm_fits_in_bits_1() {
assert!(imm_fits_in_bits(0, 1));
assert!(imm_fits_in_bits(-1, 1));
assert!(!imm_fits_in_bits(1, 1));
assert!(!imm_fits_in_bits(-2, 1));
}
#[test]
fn test_imm_fits_in_bits_16() {
assert!(imm_fits_in_bits(32767, 16));
assert!(imm_fits_in_bits(-32768, 16));
assert!(!imm_fits_in_bits(32768, 16));
}
#[test]
fn test_uimm_fits_in_bits_1() {
assert!(uimm_fits_in_bits(0, 1));
assert!(uimm_fits_in_bits(1, 1));
assert!(!uimm_fits_in_bits(2, 1));
}
#[test]
fn test_uimm_fits_in_bits_16() {
assert!(uimm_fits_in_bits(0, 16));
assert!(uimm_fits_in_bits(65535, 16));
assert!(!uimm_fits_in_bits(65536, 16));
}
#[test]
fn test_lower_global_address_32bit() {
let mode = lower_global_address("myvar", 0, false);
if let FastAddressMode::Absolute(off) = mode {
assert_eq!(off, 0);
} else {
panic!("Expected Absolute address mode");
}
}
#[test]
fn test_addr_lowering_complex() {
let ctx = AddressLoweringContext {
base: Some(1),
index: Some(2),
scale: 4,
displacement: 32,
is_frame_index: true,
frame_index: 1,
is_global: false,
global_name: None,
is_constant_pool: false,
constant_pool_index: 0,
};
assert!(!ctx.is_simple());
assert!(!ctx.is_rip_relative());
}
#[test]
fn test_lower_frame_index_unknown() {
let map = HashMap::new();
let (base, offset) = lower_frame_index(99, &map, true);
assert_eq!(base, RBP);
assert_eq!(offset, 0);
}
#[test]
fn test_config_custom() {
let cfg = FastISelConfig {
debug_info: false,
verify_instructions: true,
collect_stats: true,
max_instrs_per_block: 10000,
fast_regalloc: false,
enable_constant_pool: false,
emit_frame_pointer: false,
stack_alignment: 32,
};
assert!(!cfg.debug_info);
assert!(cfg.verify_instructions);
assert!(cfg.collect_stats);
assert_eq!(cfg.stack_alignment, 32);
}
#[test]
fn test_debug_var_argument() {
let dv = DebugVariable::new("arg", "ptr", true);
assert!(dv.is_argument);
assert_eq!(dv.name, "arg");
assert_eq!(dv.type_name, "ptr");
}
#[test]
fn test_debug_info_tracker_record_multiple() {
let mut tracker = DebugInfoTracker::new(true);
for i in 0..10 {
tracker.record_instr_location(i, DebugLoc::new("f.c", i, 1));
}
assert_eq!(tracker.instr_locations.len(), 10);
let loc = tracker.get_location(5);
assert_eq!(loc.unwrap().line, 5);
}
#[test]
fn test_select_all_memory_ops() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 55000;
let val = make_ref(Value::new(Type::i64()));
val.borrow_mut().vid = 55001;
let load = make_inst(Opcode::Load, Type::i64(), vec![ptr.clone()]);
load.borrow_mut().vid = 56000;
let store = make_inst(Opcode::Store, Type::void(), vec![val.clone(), ptr.clone()]);
store.borrow_mut().vid = 56001;
let gep = make_inst(
Opcode::GetElementPtr,
Type::pointer(0),
vec![ptr.clone(), val.clone()],
);
gep.borrow_mut().vid = 56002;
let bb = make_bb("entry", vec![load, store, gep]);
let func = make_func("test_mem_ops", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_binary_op_chain() {
let a = make_ref(Value::new(Type::i32()));
a.borrow_mut().vid = 57000;
let b = make_ref(Value::new(Type::i32()));
b.borrow_mut().vid = 57001;
let c = make_ref(Value::new(Type::i32()));
c.borrow_mut().vid = 57002;
let d = make_ref(Value::new(Type::i32()));
d.borrow_mut().vid = 57003;
let and_inst = make_inst(Opcode::And, Type::i32(), vec![a, b]);
and_inst.borrow_mut().vid = 58000;
let xor_inst = make_inst(Opcode::Xor, Type::i32(), vec![c, d]);
xor_inst.borrow_mut().vid = 58001;
let or_inst = make_inst(Opcode::Or, Type::i32(), vec![and_inst, xor_inst]);
or_inst.borrow_mut().vid = 58002;
let bb = make_bb("entry", vec![and_inst, xor_inst, or_inst]);
let func = make_func("test_bin_chain", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_zext_sext_trunc_chain() {
let x = make_ref(Value::new(Type::i8()));
x.borrow_mut().vid = 59000;
let zext = make_inst(Opcode::ZExt, Type::i32(), vec![x.clone()]);
zext.borrow_mut().vid = 60000;
let sext = make_inst(Opcode::SExt, Type::i64(), vec![zext.clone()]);
sext.borrow_mut().vid = 60001;
let trunc = make_inst(Opcode::Trunc, Type::i32(), vec![sext.clone()]);
trunc.borrow_mut().vid = 60002;
let bb = make_bb("entry", vec![zext, sext, trunc]);
let func = make_func("test_cast_chain", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_select_fp_chain() {
let x = make_ref(Value::new(Type::float()));
x.borrow_mut().vid = 61000;
let y = make_ref(Value::new(Type::float()));
y.borrow_mut().vid = 61001;
let fadd = make_inst(Opcode::FAdd, Type::float(), vec![x.clone(), y.clone()]);
fadd.borrow_mut().vid = 62000;
let fsub = make_inst(Opcode::FSub, Type::float(), vec![x.clone(), y.clone()]);
fsub.borrow_mut().vid = 62001;
let fmul = make_inst(
Opcode::FMul,
Type::float(),
vec![fadd.clone(), fsub.clone()],
);
fmul.borrow_mut().vid = 62002;
let fdiv = make_inst(Opcode::FDiv, Type::float(), vec![fmul.clone(), x.clone()]);
fdiv.borrow_mut().vid = 62003;
let bb = make_bb("entry", vec![fadd, fsub, fmul, fdiv]);
let func = make_func("test_fp_chain", Type::float(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 4);
}
#[test]
fn test_select_function_with_arguments() {
let arg0 = make_ref(Value::new(Type::i32()));
arg0.borrow_mut().vid = 63000;
arg0.borrow_mut().subclass = SubclassKind::Argument;
let arg1 = make_ref(Value::new(Type::i32()));
arg1.borrow_mut().vid = 63001;
arg1.borrow_mut().subclass = SubclassKind::Argument;
let add = make_inst(Opcode::Add, Type::i32(), vec![arg0.clone(), arg1.clone()]);
add.borrow_mut().vid = 64000;
let ret = make_inst(Opcode::Ret, Type::void(), vec![add.clone()]);
ret.borrow_mut().vid = 64001;
let bb = make_bb("entry", vec![add, ret]);
let func = make_func(
"test_args",
Type::i32(),
vec![Type::i32(), Type::i32()],
vec![arg0, arg1, bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_icmp_pred_to_setcc_unknown() {
let setcc = X86FastISel::icmp_pred_to_setcc(99);
assert_eq!(setcc, X86Opcode::SETE as u32);
}
#[test]
fn test_fcmp_pred_to_cc_all() {
let preds = [
(fcmp_pred::OEQ, X86ConditionCode::Z),
(fcmp_pred::OGT, X86ConditionCode::A),
(fcmp_pred::OGE, X86ConditionCode::NB),
(fcmp_pred::OLT, X86ConditionCode::B),
(fcmp_pred::OLE, X86ConditionCode::BE),
(fcmp_pred::ONE, X86ConditionCode::NZ),
(fcmp_pred::ORD, X86ConditionCode::NP),
(fcmp_pred::UNO, X86ConditionCode::P),
];
for (pred, expected) in preds {
assert_eq!(X86FastISel::fcmp_pred_to_cc(pred), expected);
}
}
#[test]
fn test_select_call_void_return() {
let callee = make_ref(Value::new(Type::void()));
callee.borrow_mut().vid = 65000;
callee.borrow_mut().name = "void_fn".to_string();
callee.borrow_mut().subclass = SubclassKind::Function;
let call = make_inst(Opcode::Call, Type::void(), vec![callee]);
call.borrow_mut().vid = 66000;
let bb = make_bb("entry", vec![call]);
let func = make_func("test_void_call", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_gep_double_indirection() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 67000;
let idx0 = make_ref(Value::new(Type::i32()));
idx0.borrow_mut().vid = 67001;
let idx1 = make_ref(Value::new(Type::i32()));
idx1.borrow_mut().vid = 67002;
let idx2 = make_ref(Value::new(Type::i32()));
idx2.borrow_mut().vid = 67003;
let gep = make_inst(
Opcode::GetElementPtr,
Type::pointer(0),
vec![ptr, idx0, idx1, idx2],
);
gep.borrow_mut().vid = 68000;
let bb = make_bb("entry", vec![gep]);
let func = make_func("test_gep_chain", Type::pointer(0), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_all_binary_ops_overflow_chain() {
let x = make_ref(Value::new(Type::i64()));
x.borrow_mut().vid = 69000;
let ops: Vec<(Opcode, u64)> = vec![
(Opcode::Add, 70000),
(Opcode::Sub, 70001),
(Opcode::And, 70002),
(Opcode::Or, 70003),
(Opcode::Xor, 70004),
];
let mut insts = Vec::new();
let mut prev = x.clone();
for (op, vid) in ops {
let y = make_ref(Value::new(Type::i64()));
y.borrow_mut().vid = vid + 100;
let inst = make_inst(op, Type::i64(), vec![prev.clone(), y.clone()]);
inst.borrow_mut().vid = vid;
prev = inst.clone();
insts.push(inst);
}
let bb = make_bb("entry", insts);
let func = make_func("test_bin_all", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 5);
}
#[test]
fn test_select_frem_f64_direct() {
let op0 = make_ref(Value::new(Type::double()));
op0.borrow_mut().vid = 71000;
let op1 = make_ref(Value::new(Type::double()));
op1.borrow_mut().vid = 71001;
let frem = make_inst(Opcode::FRem, Type::double(), vec![op0, op1]);
frem.borrow_mut().vid = 72000;
let bb = make_bb("entry", vec![frem]);
let func = make_func("test_frem64", Type::double(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_frem_f32_direct() {
let op0 = make_ref(Value::new(Type::float()));
op0.borrow_mut().vid = 73000;
let op1 = make_ref(Value::new(Type::float()));
op1.borrow_mut().vid = 73001;
let frem = make_inst(Opcode::FRem, Type::float(), vec![op0, op1]);
frem.borrow_mut().vid = 74000;
let bb = make_bb("entry", vec![frem]);
let func = make_func("test_frem32", Type::float(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_uitofp_and_sitofp() {
let int32 = make_ref(Value::new(Type::i32()));
int32.borrow_mut().vid = 75000;
let int64 = make_ref(Value::new(Type::i64()));
int64.borrow_mut().vid = 75001;
let uitofp = make_inst(Opcode::UIToFP, Type::double(), vec![int32.clone()]);
uitofp.borrow_mut().vid = 76000;
let sitofp = make_inst(Opcode::SIToFP, Type::double(), vec![int64.clone()]);
sitofp.borrow_mut().vid = 76001;
let bb = make_bb("entry", vec![uitofp, sitofp]);
let func = make_func("test_int_to_fp", Type::double(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_select_fptoui_and_fptosi() {
let f32 = make_ref(Value::new(Type::float()));
f32.borrow_mut().vid = 77000;
let f64 = make_ref(Value::new(Type::double()));
f64.borrow_mut().vid = 77001;
let fptoui = make_inst(Opcode::FPToUI, Type::i32(), vec![f32.clone()]);
fptoui.borrow_mut().vid = 78000;
let fptosi = make_inst(Opcode::FPToSI, Type::i64(), vec![f64.clone()]);
fptosi.borrow_mut().vid = 78001;
let bb = make_bb("entry", vec![fptoui, fptosi]);
let func = make_func("test_fp_to_int", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_gep_to_address_simple() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 79000;
let idx = make_ref(Value::new(Type::i32()));
idx.borrow_mut().vid = 79001;
let gep = make_inst(Opcode::GetElementPtr, Type::pointer(0), vec![ptr, idx]);
gep.borrow_mut().vid = 80000;
let vreg_map: HashMap<u64, u32> = [(79000, 5), (79001, 6)].iter().cloned().collect();
let extractor = |v: &ValueRef| -> Option<i64> {
let val = v.borrow();
if val.subclass == SubclassKind::ConstantInt {
Some(val.subclass_data as i64)
} else {
None
}
};
let ctx = lower_gep_to_address(&gep.borrow(), &vreg_map, &extractor);
assert!(ctx.base.is_some());
}
#[test]
fn test_try_fold_address_zero_disp_no_index() {
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
let result = try_fold_address(&mut mi, 10, 0, None, 1);
assert!(result);
assert_eq!(mi.operands.len(), 2);
}
#[test]
fn test_try_fold_address_with_displacement() {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
let result = try_fold_address(&mut mi, 3, 16, Some(7), 2);
assert!(result);
assert_eq!(mi.operands.len(), 4); }
#[test]
fn test_build_mov_rr() {
let mi = build_mov_rr(5, 10);
assert_eq!(mi.opcode, X86Opcode::MOV as u32);
assert_eq!(mi.def, Some(5));
assert_eq!(mi.operands.len(), 2);
}
#[test]
fn test_build_mov_ri() {
let mi = build_mov_ri(7, 42);
assert_eq!(mi.opcode, X86Opcode::MOV as u32);
assert_eq!(mi.def, Some(7));
}
#[test]
fn test_build_store_mr() {
let mi = build_store_mr(1, 2);
assert_eq!(mi.opcode, X86Opcode::MOV as u32);
assert!(mi.def.is_none());
}
#[test]
fn test_build_load_rm() {
let mi = build_load_rm(3, 4);
assert_eq!(mi.opcode, X86Opcode::MOV as u32);
assert_eq!(mi.def, Some(3));
}
#[test]
fn test_build_binary_op() {
let mi = build_binary_op(X86Opcode::ADD as u32, 1, 2);
assert_eq!(mi.opcode, X86Opcode::ADD as u32);
assert_eq!(mi.def, Some(1));
}
#[test]
fn test_build_binary_op_3() {
let mi = build_binary_op_3(X86Opcode::IMUL as u32, 5, 6, 7);
assert_eq!(mi.opcode, X86Opcode::IMUL as u32);
assert_eq!(mi.operands.len(), 3);
}
#[test]
fn test_build_ret_void() {
let mi = build_ret(None);
assert_eq!(mi.opcode, X86Opcode::RET as u32);
assert!(mi.operands.is_empty());
}
#[test]
fn test_build_ret_with_val() {
let mi = build_ret(Some(10));
assert_eq!(mi.opcode, X86Opcode::RET as u32);
assert_eq!(mi.operands.len(), 1);
}
#[test]
fn test_build_jmp() {
let mi = build_jmp("target");
assert_eq!(mi.opcode, X86Opcode::JMP as u32);
}
#[test]
fn test_build_jcc_all_conditions() {
let conditions = vec![
(X86ConditionCode::O, X86Opcode::JO),
(X86ConditionCode::NO, X86Opcode::JNO),
(X86ConditionCode::B, X86Opcode::JB),
(X86ConditionCode::NB, X86Opcode::JAE),
(X86ConditionCode::Z, X86Opcode::JE),
(X86ConditionCode::NZ, X86Opcode::JNE),
(X86ConditionCode::BE, X86Opcode::JBE),
(X86ConditionCode::A, X86Opcode::JA),
(X86ConditionCode::S, X86Opcode::JS),
(X86ConditionCode::NS, X86Opcode::JNS),
(X86ConditionCode::P, X86Opcode::JP),
(X86ConditionCode::NP, X86Opcode::JNP),
(X86ConditionCode::L, X86Opcode::JL),
(X86ConditionCode::GE, X86Opcode::JGE),
(X86ConditionCode::LE, X86Opcode::JLE),
(X86ConditionCode::G, X86Opcode::JG),
];
for (cc, expected_op) in conditions {
let mi = build_jcc(cc, "L");
assert_eq!(mi.opcode, expected_op as u32, "Mismatch for {cc:?}");
}
}
#[test]
fn test_build_call_direct() {
let mi = build_call_direct("foo");
assert_eq!(mi.opcode, X86Opcode::CALL as u32);
}
#[test]
fn test_build_call_indirect() {
let mi = build_call_indirect(42);
assert_eq!(mi.opcode, X86Opcode::CALL as u32);
}
#[test]
fn test_build_cmp() {
let mi = build_cmp(1, 2);
assert_eq!(mi.opcode, X86Opcode::CMP as u32);
}
#[test]
fn test_build_test() {
let mi = build_test(3, 1);
assert_eq!(mi.opcode, X86Opcode::TEST as u32);
}
#[test]
fn test_build_push_pop() {
let push = build_push(5);
assert_eq!(push.opcode, X86Opcode::PUSH as u32);
let pop = build_pop(6);
assert_eq!(pop.opcode, X86Opcode::POP as u32);
assert_eq!(pop.def, Some(6));
}
#[test]
fn test_build_lea_simple() {
let mi = build_lea(10, 5, None, 1, 8);
assert_eq!(mi.opcode, X86Opcode::LEA as u32);
assert_eq!(mi.def, Some(10));
}
#[test]
fn test_build_lea_with_index() {
let mi = build_lea(10, 5, Some(7), 4, 0);
assert_eq!(mi.opcode, X86Opcode::LEA as u32);
}
#[test]
fn test_try_fold_immediate_add() {
let mut add = MachineInstr::new(X86Opcode::ADD as u32);
let mov = MachineInstr::new(X86Opcode::MOV as u32);
assert!(try_fold_immediate(&mut add, &mov, 0));
}
#[test]
fn test_try_fold_immediate_sub() {
let mut sub = MachineInstr::new(X86Opcode::SUB as u32);
let mov = MachineInstr::new(X86Opcode::MOV as u32);
assert!(try_fold_immediate(&mut sub, &mov, 0));
}
#[test]
fn test_try_fold_immediate_not_supported() {
let mut imul = MachineInstr::new(X86Opcode::IMUL as u32);
let mov = MachineInstr::new(X86Opcode::MOV as u32);
assert!(!try_fold_immediate(&mut imul, &mov, 0));
}
#[test]
fn test_try_combine_cmp_br_no_change() {
let cmp = MachineInstr::new(X86Opcode::CMP as u32);
let br = MachineInstr::new(X86Opcode::JNE as u32);
let result = try_combine_cmp_br(&cmp, &br);
assert!(result.is_none());
}
#[test]
fn test_try_combine_cmp_br_different() {
let cmp = MachineInstr::new(X86Opcode::CMP as u32);
let br = MachineInstr::new(X86Opcode::JMP as u32);
let result = try_combine_cmp_br(&cmp, &br);
assert!(result.is_none());
}
#[test]
fn test_try_fold_load() {
let mut add = MachineInstr::new(X86Opcode::ADD as u32);
let mut load = MachineInstr::new(X86Opcode::MOV as u32);
load.def = Some(5);
assert!(try_fold_load(&mut add, &load, 0));
}
#[test]
fn test_emit_prologue_64bit_with_fp() {
let instrs = emit_prologue(32, true, true);
assert!(!instrs.is_empty());
assert!(instrs.len() >= 6);
}
#[test]
fn test_emit_prologue_64bit_no_fp() {
let instrs = emit_prologue(0, false, true);
assert_eq!(instrs.len(), 5); }
#[test]
fn test_emit_prologue_32bit_with_fp() {
let instrs = emit_prologue(16, true, false);
assert!(instrs.len() >= 4);
}
#[test]
fn test_emit_epilogue_64bit_with_fp() {
let instrs = emit_epilogue(32, true, true);
assert!(instrs.len() >= 8);
}
#[test]
fn test_emit_epilogue_64bit_no_fp() {
let instrs = emit_epilogue(32, false, true);
assert!(instrs.len() >= 7);
}
#[test]
fn test_emit_epilogue_32bit_no_fp_no_frame() {
let instrs = emit_epilogue(0, false, false);
assert_eq!(instrs.len(), 3);
}
#[test]
fn test_get_arg_reg_sysv64() {
assert_eq!(get_arg_reg_sysv64(0), Some(RDI));
assert_eq!(get_arg_reg_sysv64(1), Some(RSI));
assert_eq!(get_arg_reg_sysv64(2), Some(RDX));
assert_eq!(get_arg_reg_sysv64(3), Some(RCX));
assert_eq!(get_arg_reg_sysv64(4), Some(R8));
assert_eq!(get_arg_reg_sysv64(5), Some(R9));
assert_eq!(get_arg_reg_sysv64(6), None);
}
#[test]
fn test_get_arg_xmm_sysv64() {
assert_eq!(get_arg_xmm_sysv64(0), Some(XMM0));
assert_eq!(get_arg_xmm_sysv64(7), Some(XMM7));
assert_eq!(get_arg_xmm_sysv64(8), None);
}
#[test]
fn test_get_ret_reg_int() {
assert_eq!(get_ret_reg_int(true), RAX);
assert_eq!(get_ret_reg_int(false), EAX);
}
#[test]
fn test_get_ret_reg_fp() {
assert_eq!(get_ret_reg_fp(), XMM0);
}
#[test]
fn test_type_class_classify_integer() {
assert_eq!(TypeClass::classify(&Type::i32()), TypeClass::Integer);
assert_eq!(TypeClass::classify(&Type::i64()), TypeClass::Integer);
assert!(TypeClass::classify(&Type::i32()).is_gpr());
assert!(!TypeClass::classify(&Type::i32()).is_xmm());
}
#[test]
fn test_type_class_classify_float() {
assert_eq!(TypeClass::classify(&Type::float()), TypeClass::Float);
assert_eq!(TypeClass::classify(&Type::double()), TypeClass::Float);
assert!(!TypeClass::classify(&Type::float()).is_gpr());
assert!(TypeClass::classify(&Type::float()).is_xmm());
}
#[test]
fn test_type_class_classify_pointer() {
assert_eq!(TypeClass::classify(&Type::pointer(0)), TypeClass::Pointer);
assert!(TypeClass::classify(&Type::pointer(0)).is_gpr());
}
#[test]
fn test_type_class_classify_void() {
assert_eq!(TypeClass::classify(&Type::void()), TypeClass::Void);
assert!(!TypeClass::classify(&Type::void()).is_gpr());
assert!(!TypeClass::classify(&Type::void()).is_xmm());
}
#[test]
fn test_type_class_vector() {
let vec_ty = Type::new(crate::types::TypeKind::FixedVector {
len: 4,
element_type_id: crate::types::TypeId::new(),
});
assert_eq!(TypeClass::classify(&vec_ty), TypeClass::Vector);
}
#[test]
fn test_get_mov_opcode_invalid_width() {
assert_eq!(get_mov_opcode_for_width(128), X86Opcode::MOV as u32);
}
#[test]
fn test_get_add_opcode_variants() {
assert_eq!(get_add_opcode_for_width(8), X86Opcode::ADD as u32);
assert_eq!(get_add_opcode_for_width(64), X86Opcode::ADD as u32);
}
#[test]
fn test_get_sub_opcode_variants() {
assert_eq!(get_sub_opcode_for_width(8), X86Opcode::SUB as u32);
assert_eq!(get_sub_opcode_for_width(64), X86Opcode::SUB as u32);
}
#[test]
fn test_virtual_reg_assignment_default() {
let va = VirtualRegAssignment::new(5, 100);
assert_eq!(va.vreg, 5);
assert!(!va.in_reg);
assert!(va.phys_reg.is_none());
assert!(va.locally_defined);
}
#[test]
fn test_virtual_reg_assignment_remaining_uses() {
let va = VirtualRegAssignment::new(5, 100);
assert_eq!(va.remaining_uses, 0);
}
#[test]
fn test_live_interval_overlap_touching() {
let a = LiveInterval::new(1, 0, 10, false);
let b = LiveInterval::new(2, 10, 20, false);
assert!(a.overlaps(&b));
}
#[test]
fn test_live_interval_overlap_contained() {
let a = LiveInterval::new(1, 5, 15, false);
let b = LiveInterval::new(2, 7, 12, false);
assert!(a.overlaps(&b));
}
#[test]
fn test_live_interval_float_flag() {
let a = LiveInterval::new(1, 0, 10, true);
assert!(a.is_float);
}
#[test]
fn test_spill_manager_default_slot() {
let sm = SpillManager::new();
assert_eq!(sm.next_slot, 0);
assert_eq!(sm.total_spill_size(), STACK_SLOT_SIZE);
}
#[test]
fn test_config_stack_alignment_minimum() {
let cfg = FastISelConfig {
stack_alignment: 4,
..FastISelConfig::debug_defaults()
};
assert_eq!(cfg.stack_alignment, 4);
}
#[test]
fn test_addr_mode_clone() {
let am1 = FastAddressMode::RegImm {
base: 10,
offset: 8,
};
let am2 = am1.clone();
assert_eq!(am1, am2);
}
#[test]
fn test_icmp_pred_constants() {
assert_eq!(icmp_pred::EQ, 0);
assert_eq!(icmp_pred::NE, 1);
assert_eq!(icmp_pred::SGT, 6);
assert_eq!(icmp_pred::SLE, 9);
}
#[test]
fn test_fcmp_pred_constants() {
assert_eq!(fcmp_pred::FALSE, 0);
assert_eq!(fcmp_pred::OEQ, 1);
assert_eq!(fcmp_pred::TRUE, 15);
}
#[test]
fn test_select_with_function_with_vid_conflicts() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 1;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 2;
let add = make_inst(Opcode::Add, Type::i32(), vec![op0, op1]);
add.borrow_mut().vid = 3;
let bb = make_bb("entry", vec![add]);
let func = make_func("test_vid", Type::i32(), vec![], vec![bb]);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_x86_fast_isel_builder_with_subtarget() {
let st = X86Subtarget::new("i686-pc-linux-gnu", "generic", "");
let builder = X86FastISelBuilder::new().subtarget(st);
let isel = builder.build();
assert!(!isel.is_64bit); }
#[test]
fn test_x86_fast_isel_builder_default() {
let builder = X86FastISelBuilder::default();
let isel = builder.build();
assert!(isel.is_64bit); }
#[test]
fn test_select_shl_lshr_ashr_const_shift() {
let val = make_ref(Value::new(Type::i32()));
val.borrow_mut().vid = 80000;
let shift = make_ref(Value::new(Type::i32()));
shift.borrow_mut().vid = 80001;
let shl = make_inst(Opcode::Shl, Type::i32(), vec![val.clone(), shift.clone()]);
shl.borrow_mut().vid = 81000;
let lshr = make_inst(Opcode::LShr, Type::i32(), vec![val.clone(), shift.clone()]);
lshr.borrow_mut().vid = 81001;
let ashr = make_inst(Opcode::AShr, Type::i32(), vec![val.clone(), shift.clone()]);
ashr.borrow_mut().vid = 81002;
let bb = make_bb("entry", vec![shl, lshr, ashr]);
let func = make_func("test_shifts", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 6); }
#[test]
fn test_select_call_with_many_args() {
let callee = make_ref(Value::new(Type::void()));
callee.borrow_mut().vid = 82000;
callee.borrow_mut().name = "many_args_fn".to_string();
callee.borrow_mut().subclass = SubclassKind::Function;
let mut args = vec![callee.clone()];
for i in 0..10 {
let arg = make_ref(Value::new(Type::i32()));
arg.borrow_mut().vid = 82001 + i * 100;
args.push(arg);
}
let call = make_inst(Opcode::Call, Type::i32(), args);
call.borrow_mut().vid = 83000;
let bb = make_bb("entry", vec![call]);
let func = make_func("test_many_args_call", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 12);
}
#[test]
fn test_select_switch_many_cases() {
let cond = make_ref(Value::new(Type::i32()));
cond.borrow_mut().vid = 84000;
let default_bb = make_bb("default", vec![]);
default_bb.borrow_mut().vid = 84001;
let mut switch_args = vec![cond.clone(), default_bb.clone()];
let mut case_bbs = vec![default_bb.clone()];
for i in 0..5 {
let case_val = make_const_int(i as i64);
let case_bb = make_bb(&format!("case_{i}"), vec![]);
case_bb.borrow_mut().vid = 84002 + i * 100;
case_bbs.push(case_bb.clone());
switch_args.push(case_val);
switch_args.push(case_bb);
}
let switch = make_inst(Opcode::Switch, Type::void(), switch_args);
switch.borrow_mut().vid = 85000;
let entry_bb = make_bb("entry", vec![switch]);
let mut all_bbs = vec![entry_bb];
all_bbs.extend(case_bbs);
let func = make_func("test_switch_many", Type::void(), vec![], all_bbs);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 11);
}
#[test]
fn test_run_fast_isel_preserves_func_name() {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 86000;
let ret = make_inst(Opcode::Ret, Type::void(), vec![op0]);
ret.borrow_mut().vid = 86001;
let bb = make_bb("my_entry", vec![ret]);
let func = make_func("unique_func_name", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.mf.name, "unique_func_name");
}
#[test]
fn test_all_integer_types() {
let widths = [1, 8, 16, 32, 64, 128];
for &w in &widths {
let ty = Type::int(w);
let op = make_ref(Value::new(ty.clone()));
op.borrow_mut().vid = 90000 + w as u64;
let ret = make_inst(Opcode::Ret, Type::void(), vec![op]);
ret.borrow_mut().vid = 91000 + w as u64;
let bb = make_bb("entry", vec![ret]);
let func = make_func(&format!("test_i{w}"), ty, vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1, "Failed for i{w}");
}
}
#[test]
fn test_all_float_types() {
let float_types = vec![
("half", Type::half()),
("float", Type::float()),
("double", Type::double()),
];
for (name, ty) in float_types {
let op = make_ref(Value::new(ty.clone()));
op.borrow_mut().vid = 92000;
let fneg: ValueRef = make_inst(
Opcode::FSub,
ty.clone(),
vec![
make_ref({
let mut v = Value::new(ty.clone());
v.vid = 92001;
v.subclass = SubclassKind::ConstantFP;
v.name = "0.0".to_string();
v
}),
op.clone(),
],
);
fneg.borrow_mut().vid = 93000;
let bb = make_bb("entry", vec![fneg]);
let func = make_func(&format!("test_{name}"), ty, vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1, "Failed for {name}");
}
}
#[test]
fn test_pointer_types_different_addr_spaces() {
for addr_space in 0..4u32 {
let ty = Type::pointer(addr_space);
let ptr = make_ref(Value::new(ty.clone()));
ptr.borrow_mut().vid = 94000 + addr_space as u64;
let load = make_inst(Opcode::Load, Type::i32(), vec![ptr]);
load.borrow_mut().vid = 95000 + addr_space as u64;
let bb = make_bb("entry", vec![load]);
let func = make_func(
&format!("test_ptr_as{addr_space}"),
Type::i32(),
vec![],
vec![bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(
result.num_instructions >= 1,
"Failed for addr_space {addr_space}"
);
}
}
#[test]
fn test_pattern_xor_self_zero() {
let x = make_ref(Value::new(Type::i32()));
x.borrow_mut().vid = 96000;
let xor_self = make_inst(Opcode::Xor, Type::i32(), vec![x.clone(), x.clone()]);
xor_self.borrow_mut().vid = 97000;
let bb = make_bb("entry", vec![xor_self]);
let func = make_func("test_xor_self", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_pattern_sub_self_zero() {
let x = make_ref(Value::new(Type::i32()));
x.borrow_mut().vid = 98000;
let sub_self = make_inst(Opcode::Sub, Type::i32(), vec![x.clone(), x.clone()]);
sub_self.borrow_mut().vid = 99000;
let bb = make_bb("entry", vec![sub_self]);
let func = make_func("test_sub_self", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_pattern_and_self() {
let x = make_ref(Value::new(Type::i32()));
x.borrow_mut().vid = 100000;
let and_self = make_inst(Opcode::And, Type::i32(), vec![x.clone(), x.clone()]);
and_self.borrow_mut().vid = 101000;
let bb = make_bb("entry", vec![and_self]);
let func = make_func("test_and_self", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_pattern_or_self() {
let x = make_ref(Value::new(Type::i32()));
x.borrow_mut().vid = 102000;
let or_self = make_inst(Opcode::Or, Type::i32(), vec![x.clone(), x.clone()]);
or_self.borrow_mut().vid = 103000;
let bb = make_bb("entry", vec![or_self]);
let func = make_func("test_or_self", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_diamond_cfg() {
let cond = make_ref(Value::new(Type::i1()));
cond.borrow_mut().vid = 104000;
let true_bb = make_bb("if_true", vec![]);
true_bb.borrow_mut().vid = 104001;
let false_bb = make_bb("if_false", vec![]);
false_bb.borrow_mut().vid = 104002;
let merge_bb = make_bb("merge", vec![]);
merge_bb.borrow_mut().vid = 104003;
let br = make_inst(
Opcode::Br,
Type::void(),
vec![cond.clone(), true_bb.clone(), false_bb.clone()],
);
br.borrow_mut().vid = 105000;
let entry_bb = make_bb("entry", vec![br]);
let func = make_func(
"test_diamond",
Type::void(),
vec![],
vec![entry_bb, true_bb, false_bb, merge_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 4);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_loop_cfg() {
let cond = make_ref(Value::new(Type::i1()));
cond.borrow_mut().vid = 106000;
let body_bb = make_bb("loop_body", vec![]);
body_bb.borrow_mut().vid = 106001;
let exit_bb = make_bb("loop_exit", vec![]);
exit_bb.borrow_mut().vid = 106002;
let br = make_inst(
Opcode::Br,
Type::void(),
vec![cond.clone(), body_bb.clone(), exit_bb.clone()],
);
br.borrow_mut().vid = 107000;
let header_bb = make_bb("header", vec![br]);
let func = make_func(
"test_loop",
Type::void(),
vec![],
vec![header_bb, body_bb, exit_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 3);
}
#[test]
fn test_constant_add() {
let c1 = make_const_int(40);
let c2 = make_const_int(2);
let add = make_inst(Opcode::Add, Type::i32(), vec![c1, c2]);
add.borrow_mut().vid = 108000;
let bb = make_bb("entry", vec![add]);
let func = make_func("test_const_add", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_constant_icmp() {
let c10 = make_const_int(10);
let c20 = make_const_int(20);
let icmp = make_inst(Opcode::ICmp, Type::i1(), vec![c10, c20]);
icmp.borrow_mut().vid = 109000;
icmp.borrow_mut().subclass_data = icmp_pred::SLT;
let bb = make_bb("entry", vec![icmp]);
let func = make_func("test_const_icmp", Type::i1(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 3); }
#[test]
fn test_select_zext_from_i1() {
let i1_val = make_ref(Value::new(Type::i1()));
i1_val.borrow_mut().vid = 110000;
let zext = make_inst(Opcode::ZExt, Type::i32(), vec![i1_val]);
zext.borrow_mut().vid = 111000;
let bb = make_bb("entry", vec![zext]);
let func = make_func("test_zext_i1", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_sext_from_i1() {
let i1_val = make_ref(Value::new(Type::i1()));
i1_val.borrow_mut().vid = 112000;
let sext = make_inst(Opcode::SExt, Type::i32(), vec![i1_val]);
sext.borrow_mut().vid = 113000;
let bb = make_bb("entry", vec![sext]);
let func = make_func("test_sext_i1", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_select_unknown_opcode() {
let inst = make_ref(Value {
name: String::new(),
ty: Type::void(),
vid: 114000,
subclass: SubclassKind::Instruction,
uses: Vec::new(),
opcode: Some(Opcode::Fence), num_operands: 0,
operands: Vec::new(),
parent: None,
return_type: None,
successors: Vec::new(),
subclass_data: 0,
metadata: HashMap::new(),
is_used_by_md: false,
blocks: Vec::new(),
params: Vec::new(),
instructions: Vec::new(),
initializer: None,
is_constant: false,
is_vararg: false,
result: None,
});
let bb = make_bb("entry", vec![inst]);
let func = make_func("test_unknown", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_instructions, 0);
}
#[test]
fn test_resolve_operand_constant_materialization() {
let c42 = make_const_int(42);
c42.borrow_mut().vid = 115000;
let x = make_ref(Value::new(Type::i32()));
x.borrow_mut().vid = 115001;
let add = make_inst(Opcode::Add, Type::i32(), vec![x, c42]);
add.borrow_mut().vid = 116000;
let bb = make_bb("entry", vec![add]);
let func = make_func("test_const_mat", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 2);
}
#[test]
fn test_gep_const_indices_only() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 117000;
let gep = make_inst(
Opcode::GetElementPtr,
Type::pointer(0),
vec![ptr, make_const_int(4)],
);
gep.borrow_mut().vid = 118000;
let bb = make_bb("entry", vec![gep]);
let func = make_func("test_gep_const", Type::pointer(0), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_fast_isel_result_clone() {
let mf = MachineFunction::new("test");
let result = FastISelResult::new(mf, 1, 3, 5, 16, vec![]);
let cloned = result.clone();
assert_eq!(cloned.num_blocks, result.num_blocks);
assert_eq!(cloned.num_vregs, result.num_vregs);
}
#[test]
fn test_fast_isel_result_debug() {
let mf = MachineFunction::new("test");
let result = FastISelResult::new(mf, 2, 10, 20, 64, vec![("LCPI0".into(), 42)]);
let debug_str = format!("{result:?}");
assert!(debug_str.contains("FastISelResult"));
}
#[test]
fn test_machine_instr_ext_get_def() {
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
mi.def = Some(42);
assert_eq!(mi.get_def(), Some(42));
}
#[test]
fn test_machine_instr_ext_get_reg() {
let mut mi = MachineInstr::new(X86Opcode::MOV as u32);
mi.push_reg(10);
mi.push_reg(20);
assert_eq!(mi.get_reg_operand(0), Some(10));
assert_eq!(mi.get_reg_operand(1), Some(20));
}
#[test]
fn test_machine_instr_ext_get_imm() {
let mut mi = MachineInstr::new(X86Opcode::ADD as u32);
mi.push_reg(1);
mi.push_imm(100);
assert_eq!(mi.get_imm_operand(1), Some(100));
assert_eq!(mi.get_imm_operand(0), None);
}
#[test]
fn test_machine_instr_ext_is_terminator() {
assert!(MachineInstr::new(X86Opcode::RET as u32).is_terminator());
assert!(MachineInstr::new(X86Opcode::JMP as u32).is_terminator());
assert!(MachineInstr::new(X86Opcode::JE as u32).is_terminator());
assert!(!MachineInstr::new(X86Opcode::ADD as u32).is_terminator());
}
#[test]
fn test_machine_instr_ext_is_move() {
assert!(MachineInstr::new(X86Opcode::MOV as u32).is_move());
assert!(MachineInstr::new(X86Opcode::MOVSX as u32).is_move());
assert!(MachineInstr::new(X86Opcode::MOVZX as u32).is_move());
assert!(!MachineInstr::new(X86Opcode::ADD as u32).is_move());
}
#[test]
fn test_machine_instr_ext_is_compare() {
assert!(MachineInstr::new(X86Opcode::CMP as u32).is_compare());
assert!(MachineInstr::new(X86Opcode::TEST as u32).is_compare());
assert!(MachineInstr::new(X86Opcode::UCOMISS as u32).is_compare());
assert!(!MachineInstr::new(X86Opcode::MOV as u32).is_compare());
}
#[test]
fn test_peephole_disabled() {
let mut opt = FastPeepholeOptimizer::new(false);
let mut block = MachineBasicBlock {
name: "test".into(),
instructions: vec![MachineInstr::new(X86Opcode::ADD as u32)],
successors: vec![],
};
opt.optimize_block(&mut block);
assert_eq!(block.instructions.len(), 1);
assert_eq!(opt.num_opts, 0);
}
#[test]
fn test_peephole_redundant_move() {
let mut opt = FastPeepholeOptimizer::new(true);
let mut redundant_mov = MachineInstr::new(X86Opcode::MOV as u32);
redundant_mov.push_reg(5);
redundant_mov.push_reg(5);
redundant_mov.def = Some(5);
let mut next_mov = MachineInstr::new(X86Opcode::MOV as u32);
next_mov.push_reg(5);
next_mov.push_reg(10);
next_mov.def = Some(5);
let mut block = MachineBasicBlock {
name: "test".into(),
instructions: vec![redundant_mov, next_mov],
successors: vec![],
};
opt.optimize_block(&mut block);
assert_eq!(block.instructions.len(), 1);
}
#[test]
fn test_peephole_push_pop() {
let mut opt = FastPeepholeOptimizer::new(true);
let mut push = MachineInstr::new(X86Opcode::PUSH as u32);
push.push_reg(7);
let mut pop = MachineInstr::new(X86Opcode::POP as u32);
pop.def = Some(7);
let mut block = MachineBasicBlock {
name: "test".into(),
instructions: vec![push, pop],
successors: vec![],
};
opt.optimize_block(&mut block);
assert_eq!(block.instructions.len(), 0);
}
#[test]
fn test_peephole_optimize_function() {
let mut opt = FastPeepholeOptimizer::new(true);
let mut mf = MachineFunction::new("test");
let mut redundant = MachineInstr::new(X86Opcode::MOV as u32);
redundant.push_reg(3);
redundant.push_reg(3);
redundant.def = Some(3);
mf.push_block(MachineBasicBlock {
name: "bb".into(),
instructions: vec![redundant, MachineInstr::new(X86Opcode::RET as u32)],
successors: vec![],
});
opt.optimize_function(&mut mf);
assert_eq!(mf.blocks[0].instructions.len(), 1);
}
#[test]
fn test_peephole_default_enabled() {
let opt = FastPeepholeOptimizer::default();
assert!(opt.enabled);
}
#[test]
fn test_full_pipeline_simple_function() {
let x = make_ref(Value::new(Type::i32()));
x.borrow_mut().vid = 120000;
let y = make_ref(Value::new(Type::i32()));
y.borrow_mut().vid = 120001;
let z = make_ref(Value::new(Type::i32()));
z.borrow_mut().vid = 120002;
let add = make_inst(Opcode::Add, Type::i32(), vec![x.clone(), y.clone()]);
add.borrow_mut().vid = 121000;
let mul = make_inst(Opcode::Mul, Type::i32(), vec![add.clone(), z.clone()]);
mul.borrow_mut().vid = 121001;
let sub = make_inst(Opcode::Sub, Type::i32(), vec![mul.clone(), x.clone()]);
sub.borrow_mut().vid = 121002;
let ret = make_inst(Opcode::Ret, Type::void(), vec![sub.clone()]);
ret.borrow_mut().vid = 121003;
let bb = make_bb("entry", vec![add, mul, sub, ret]);
let func = make_func("pipeline_test", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 1);
assert!(result.num_instructions >= 4);
assert!(result.num_vregs > 0);
}
#[test]
fn test_full_pipeline_with_branches() {
let cond = make_ref(Value::new(Type::i1()));
cond.borrow_mut().vid = 122000;
let a = make_ref(Value::new(Type::i32()));
a.borrow_mut().vid = 122001;
let b = make_ref(Value::new(Type::i32()));
b.borrow_mut().vid = 122002;
let then_bb = make_bb("then", vec![]);
then_bb.borrow_mut().vid = 122003;
let else_bb = make_bb("else", vec![]);
else_bb.borrow_mut().vid = 122004;
let then_add = make_inst(Opcode::Add, Type::i32(), vec![a.clone(), b.clone()]);
then_add.borrow_mut().vid = 123000;
let then_ret = make_inst(Opcode::Ret, Type::void(), vec![then_add.clone()]);
then_ret.borrow_mut().vid = 123001;
then_bb.borrow_mut().instructions = vec![then_add, then_ret];
let else_sub = make_inst(Opcode::Sub, Type::i32(), vec![a.clone(), b.clone()]);
else_sub.borrow_mut().vid = 124000;
let else_ret = make_inst(Opcode::Ret, Type::void(), vec![else_sub.clone()]);
else_ret.borrow_mut().vid = 124001;
else_bb.borrow_mut().instructions = vec![else_sub, else_ret];
let br = make_inst(
Opcode::Br,
Type::void(),
vec![cond.clone(), then_bb.clone(), else_bb.clone()],
);
br.borrow_mut().vid = 125000;
let entry_bb = make_bb("entry", vec![br]);
let func = make_func(
"branch_test",
Type::i32(),
vec![],
vec![entry_bb, then_bb, else_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 3);
assert!(result.num_instructions >= 5);
}
#[test]
fn test_pipeline_function_with_locals() {
let alloca_size = make_ref(Value::new(Type::i32()));
alloca_size.borrow_mut().vid = 126000;
alloca_size.borrow_mut().subclass = SubclassKind::ConstantInt;
alloca_size.borrow_mut().name = "4".to_string();
let alloca_x = make_inst(Opcode::Alloca, Type::pointer(0), vec![alloca_size]);
alloca_x.borrow_mut().vid = 127000;
let const_5 = make_const_int(5);
let const_3 = make_const_int(3);
let store_x = make_inst(
Opcode::Store,
Type::void(),
vec![const_5.clone(), alloca_x.clone()],
);
store_x.borrow_mut().vid = 127001;
let load_x = make_inst(Opcode::Load, Type::i32(), vec![alloca_x.clone()]);
load_x.borrow_mut().vid = 127002;
let add_y = make_inst(
Opcode::Add,
Type::i32(),
vec![load_x.clone(), const_3.clone()],
);
add_y.borrow_mut().vid = 127003;
let ret = make_inst(Opcode::Ret, Type::void(), vec![add_y.clone()]);
ret.borrow_mut().vid = 127004;
let bb = make_bb("entry", vec![alloca_x, store_x, load_x, add_y, ret]);
let func = make_func("locals_test", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 5);
}
#[test]
fn test_pipeline_nested_control_flow() {
let cond1 = make_ref(Value::new(Type::i1()));
cond1.borrow_mut().vid = 128000;
let cond2 = make_ref(Value::new(Type::i1()));
cond2.borrow_mut().vid = 128001;
let inner_then = make_bb("inner_then", vec![]);
inner_then.borrow_mut().vid = 128002;
let inner_else = make_bb("inner_else", vec![]);
inner_else.borrow_mut().vid = 128003;
let outer_merge = make_bb("merge", vec![]);
outer_merge.borrow_mut().vid = 128004;
let inner_br = make_inst(
Opcode::Br,
Type::void(),
vec![cond2.clone(), inner_then.clone(), inner_else.clone()],
);
inner_br.borrow_mut().vid = 129000;
let outer_body = make_bb("outer_body", vec![inner_br]);
outer_body.borrow_mut().vid = 128005;
let outer_br = make_inst(
Opcode::Br,
Type::void(),
vec![cond1.clone(), outer_body.clone(), outer_merge.clone()],
);
outer_br.borrow_mut().vid = 130000;
let entry_bb = make_bb("entry", vec![outer_br]);
let func = make_func(
"nested_cf",
Type::void(),
vec![],
vec![entry_bb, outer_body, inner_then, inner_else, outer_merge],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.num_blocks, 5);
assert!(result.num_instructions >= 3);
}
#[test]
fn test_icmp_constants() {
let preds = [
icmp_pred::EQ,
icmp_pred::NE,
icmp_pred::UGT,
icmp_pred::UGE,
icmp_pred::ULT,
icmp_pred::ULE,
icmp_pred::SGT,
icmp_pred::SGE,
icmp_pred::SLT,
icmp_pred::SLE,
];
let mut seen = HashSet::new();
for p in preds {
assert!(seen.insert(p), "Duplicate icmp predicate: {p}");
assert!(p <= 9, "icmp predicate {p} out of range");
}
assert_eq!(seen.len(), 10);
}
#[test]
fn test_fcmp_constants() {
let preds = [
fcmp_pred::FALSE,
fcmp_pred::OEQ,
fcmp_pred::OGT,
fcmp_pred::OGE,
fcmp_pred::OLT,
fcmp_pred::OLE,
fcmp_pred::ONE,
fcmp_pred::ORD,
fcmp_pred::UNO,
fcmp_pred::UEQ,
fcmp_pred::UGT,
fcmp_pred::UGE,
fcmp_pred::ULT,
fcmp_pred::ULE,
fcmp_pred::UNE,
fcmp_pred::TRUE,
];
let mut seen = HashSet::new();
for p in preds {
assert!(seen.insert(p), "Duplicate fcmp predicate: {p}");
assert!(p <= 15, "fcmp predicate {p} out of range");
}
assert_eq!(seen.len(), 16);
}
#[test]
fn test_invariant_fast_isel_result_non_negative() {
let op = make_ref(Value::new(Type::i32()));
op.borrow_mut().vid = 131000;
let ret = make_inst(Opcode::Ret, Type::void(), vec![op]);
ret.borrow_mut().vid = 132000;
let bb = make_bb("entry", vec![ret]);
let func = make_func("inv_test", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_blocks >= 0);
assert!(result.num_vregs >= 0);
}
#[test]
fn test_invariant_reg_alloc_monotonic() {
let mut ra = FastRegAlloc::new(true);
let v0 = ra.create_vreg(0);
let v1 = ra.create_vreg(1);
let v2 = ra.create_vreg(2);
assert!(v0 < v1);
assert!(v1 < v2);
}
#[test]
fn test_invariant_const_pool_monotonic() {
let mut mat = FastMaterializer::new(true);
let l0 = mat.add_to_constant_pool(0);
let l1 = mat.add_to_constant_pool(1);
let l2 = mat.add_to_constant_pool(2);
assert!(mat.constant_pool.len() == 3);
assert_ne!(l0, l1);
assert_ne!(l1, l2);
}
#[test]
fn test_invariant_spill_slots_positive() {
let mut sm = SpillManager::new();
for i in 0..10 {
let slot = sm.allocate_slot(i);
assert!(slot >= 0);
}
assert!(sm.total_spill_size() > 0);
}
#[test]
fn test_all_supported_opcodes_dispatch() {
let all_opcodes = vec![
Opcode::Add,
Opcode::Sub,
Opcode::Mul,
Opcode::SDiv,
Opcode::UDiv,
Opcode::SRem,
Opcode::URem,
Opcode::And,
Opcode::Or,
Opcode::Xor,
Opcode::Shl,
Opcode::LShr,
Opcode::AShr,
Opcode::FAdd,
Opcode::FSub,
Opcode::FMul,
Opcode::FDiv,
Opcode::FRem,
Opcode::Alloca,
Opcode::Load,
Opcode::Store,
Opcode::GetElementPtr,
Opcode::Br,
Opcode::IndirectBr,
Opcode::Switch,
Opcode::Ret,
Opcode::Call,
Opcode::Invoke,
Opcode::Trunc,
Opcode::ZExt,
Opcode::SExt,
Opcode::FPTrunc,
Opcode::FPExt,
Opcode::FPToUI,
Opcode::FPToSI,
Opcode::UIToFP,
Opcode::SIToFP,
Opcode::PtrToInt,
Opcode::IntToPtr,
Opcode::BitCast,
Opcode::AddrSpaceCast,
Opcode::ICmp,
Opcode::FCmp,
Opcode::Select,
Opcode::Phi,
Opcode::ExtractValue,
Opcode::InsertValue,
Opcode::ExtractElement,
Opcode::InsertElement,
Opcode::ShuffleVector,
];
for &opcode in &all_opcodes {
let op0 = make_ref(Value::new(Type::i32()));
op0.borrow_mut().vid = 200000;
let op1 = make_ref(Value::new(Type::i32()));
op1.borrow_mut().vid = 200001;
let op2 = make_ref(Value::new(Type::i32()));
op2.borrow_mut().vid = 200002;
let operands =
if opcode == Opcode::Ret || opcode == Opcode::Alloca || opcode == Opcode::Load {
vec![op0.clone()]
} else if opcode == Opcode::Store {
vec![op0.clone(), op1.clone()]
} else if opcode == Opcode::Br {
let t = make_bb("t", vec![]);
t.borrow_mut().vid = 200003;
let f = make_bb("f", vec![]);
f.borrow_mut().vid = 200004;
vec![op0.clone(), t, f]
} else if opcode == Opcode::Switch {
let d = make_bb("d", vec![]);
d.borrow_mut().vid = 200003;
vec![op0.clone(), d]
} else if opcode == Opcode::InsertElement {
vec![op0.clone(), op1.clone(), op2.clone()]
} else {
vec![op0.clone(), op1.clone()]
};
let inst = make_inst(opcode, Type::i32(), operands);
inst.borrow_mut().vid = 210000;
let func_bb = make_bb("entry", vec![inst]);
let func = make_func(
&format!("test_op_{opcode:?}"),
Type::i32(),
vec![],
vec![func_bb],
);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let _result = run_x86_fast_isel(&func, None);
}
}
#[test]
fn test_run_fast_isel_with_none_subtarget() {
let op = make_ref(Value::new(Type::i32()));
op.borrow_mut().vid = 220000;
let ret = make_inst(Opcode::Ret, Type::void(), vec![op]);
ret.borrow_mut().vid = 221000;
let bb = make_bb("entry", vec![ret]);
let func = make_func("none_st", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 1);
}
#[test]
fn test_fast_isel_result_all_fields_match() {
let op = make_ref(Value::new(Type::i64()));
op.borrow_mut().vid = 230000;
let ret = make_inst(Opcode::Ret, Type::void(), vec![op]);
ret.borrow_mut().vid = 231000;
let bb = make_bb("entry", vec![ret]);
let func = make_func("fields_test", Type::i64(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert_eq!(result.mf.name, "fields_test");
assert_eq!(result.num_blocks, 1);
assert!(result.num_vregs >= 3);
}
#[test]
fn test_select_empty_operand_list() {
let inst = make_ref(Value {
name: String::new(),
ty: Type::void(),
vid: 240000,
subclass: SubclassKind::Instruction,
uses: Vec::new(),
opcode: Some(Opcode::Add),
num_operands: 0,
operands: vec![],
parent: None,
return_type: None,
successors: Vec::new(),
subclass_data: 0,
metadata: HashMap::new(),
is_used_by_md: false,
blocks: Vec::new(),
params: Vec::new(),
instructions: Vec::new(),
initializer: None,
is_constant: false,
is_vararg: false,
result: None,
});
let bb = make_bb("entry", vec![inst]);
let func = make_func("test_empty_ops", Type::void(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions <= 1);
}
#[test]
fn test_run_multiple_times_idempotence() {
let op = make_ref(Value::new(Type::i32()));
op.borrow_mut().vid = 250000;
let ret = make_inst(Opcode::Ret, Type::void(), vec![op]);
ret.borrow_mut().vid = 251000;
let bb = make_bb("entry", vec![ret]);
let func = make_func("idempotent", Type::i32(), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result1 = run_x86_fast_isel(&func, None);
let result2 = run_x86_fast_isel(&func, None);
assert_eq!(result1.num_instructions, result2.num_instructions);
}
#[test]
fn test_deeply_nested_gep() {
let ptr = make_ref(Value::new(Type::pointer(0)));
ptr.borrow_mut().vid = 260000;
let mut operands = vec![ptr];
for i in 0..10 {
let idx = make_ref(Value::new(Type::i32()));
idx.borrow_mut().vid = 260001 + i * 100;
operands.push(idx);
}
let gep = make_inst(Opcode::GetElementPtr, Type::pointer(0), operands);
gep.borrow_mut().vid = 270000;
let bb = make_bb("entry", vec![gep]);
let func = make_func("deep_gep", Type::pointer(0), vec![], vec![bb]);
let mut next_vid = 1;
assign_vids(&mut func.borrow_mut(), &mut next_vid);
let result = run_x86_fast_isel(&func, None);
assert!(result.num_instructions >= 9);
}
#[test]
fn test_x86_subtarget_32bit_mode() {
let st = X86Subtarget::new("i686-unknown-linux-gnu", "generic", "");
assert!(!st.is_64_bit);
let isel = X86FastISel::new(st);
assert!(!isel.is_64bit);
}
#[test]
fn test_get_add_sub_opcodes_consistent() {
assert_eq!(get_add_opcode_for_width(32), X86Opcode::ADD as u32);
assert_eq!(get_sub_opcode_for_width(32), X86Opcode::SUB as u32);
}
}