use crate::codegen::{MachineOperand, PhysReg, RegAlloc, RegAllocResult, TargetRegInfo, VirtReg};
use crate::mc_inst::{MCContext, MCExpr, MCInst, MCInstBuilder, MCInstFlags, MCOperand, MCSection};
use crate::x86::{
x86_frame_lowering::X86FrameLowering,
x86_instr_info::{X86InstrInfo, X86Opcode},
x86_mc_encoder::X86MCEncoder,
x86_register_info::{
RegClass, X86RegisterInfo, EAX, EBP, EBX, ECX, EDI, EDX, ESI, R10, R11, R12, R13, R14, R15,
R8, R9, RAX, RBP, RBX, RCX, RDI, RDX, RSI, RSP, XMM0, XMM1, XMM10, XMM11, XMM12, XMM13,
XMM14, XMM15, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9,
},
x86_subtarget::X86Subtarget,
};
use std::cell::Cell;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::sync::LazyLock;
use std::time::{Duration, Instant};
pub const X86_64_ALLOCATABLE_GPRS: usize = 14;
pub const X86_64_ALLOCATABLE_XMMS: usize = 16;
pub const X86_64_STACK_ALIGN: u32 = 16;
pub const X86_64_RED_ZONE: u32 = 128;
pub const MAX_PIPELINE_STAGES: usize = 8;
pub fn default_subtarget() -> X86Subtarget {
X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", "")
}
pub fn default_subtarget_32() -> X86Subtarget {
X86Subtarget::new("i686-unknown-linux-gnu", "generic", "")
}
#[derive(Debug, Clone, PartialEq)]
pub enum X86MachineOperand {
VReg(VirtReg),
PReg(PhysReg),
Imm(i64),
Imm64(u64),
FpImm(f64),
Memory {
base: Option<PhysReg>,
index: Option<PhysReg>,
scale: u8,
displacement: i32,
},
RipRelative { displacement: i32 },
Label(String),
Global(String),
Condition(X86ConditionCode),
Segment(u8),
None,
}
impl fmt::Display for X86MachineOperand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86MachineOperand::VReg(v) => write!(f, "%v{}", v),
X86MachineOperand::PReg(p) => write!(f, "%p{}", p),
X86MachineOperand::Imm(i) => write!(f, "{}", i),
X86MachineOperand::Imm64(i) => write!(f, "{}", i),
X86MachineOperand::FpImm(v) => write!(f, "{}", v),
X86MachineOperand::Memory {
base,
index,
scale,
displacement,
} => {
write!(f, "[")?;
if let Some(b) = base {
write!(f, "p{}", b)?;
}
if let Some(i) = index {
if base.is_some() {
write!(f, " + ")?;
}
write!(f, "p{}*{}", i, scale)?;
}
if *displacement != 0 || (base.is_none() && index.is_none()) {
if base.is_some() || index.is_some() {
if *displacement >= 0 {
write!(f, " + ")?;
} else {
write!(f, " - ")?;
let d = (-*displacement) as u32;
return write!(f, "{}]", d);
}
}
write!(f, "{}", displacement)?;
}
write!(f, "]")
}
X86MachineOperand::RipRelative { displacement } => {
write!(f, "[rip + {}]", displacement)
}
X86MachineOperand::Label(l) => write!(f, ".{}", l),
X86MachineOperand::Global(g) => write!(f, "{}", g),
X86MachineOperand::Condition(cc) => write!(f, "{}", cc),
X86MachineOperand::Segment(s) => write!(f, "%seg{}", s),
X86MachineOperand::None => write!(f, "<none>"),
}
}
}
impl X86MachineOperand {
pub fn vreg(r: VirtReg) -> Self {
X86MachineOperand::VReg(r)
}
pub fn preg(r: PhysReg) -> Self {
X86MachineOperand::PReg(r)
}
pub fn imm(v: i64) -> Self {
X86MachineOperand::Imm(v)
}
pub fn memory(
base: Option<PhysReg>,
index: Option<PhysReg>,
scale: u8,
displacement: i32,
) -> Self {
X86MachineOperand::Memory {
base,
index,
scale,
displacement,
}
}
pub fn rip_rel(displacement: i32) -> Self {
X86MachineOperand::RipRelative { displacement }
}
pub fn label(name: &str) -> Self {
X86MachineOperand::Label(name.to_string())
}
pub fn is_reg(&self) -> bool {
matches!(
self,
X86MachineOperand::VReg(_) | X86MachineOperand::PReg(_)
)
}
pub fn is_imm(&self) -> bool {
matches!(
self,
X86MachineOperand::Imm(_) | X86MachineOperand::Imm64(_)
)
}
pub fn is_memory(&self) -> bool {
matches!(
self,
X86MachineOperand::Memory { .. } | X86MachineOperand::RipRelative { .. }
)
}
pub fn as_imm(&self) -> Option<i64> {
match self {
X86MachineOperand::Imm(v) => Some(*v),
X86MachineOperand::Imm64(v) => Some(*v as i64),
_ => None,
}
}
pub fn as_vreg(&self) -> Option<VirtReg> {
match self {
X86MachineOperand::VReg(r) => Some(*r),
_ => None,
}
}
pub fn as_preg(&self) -> Option<PhysReg> {
match self {
X86MachineOperand::PReg(r) => Some(*r),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ConditionCode {
O,
NO,
B,
AE,
E,
NE,
BE,
A,
S,
NS,
P,
NP,
L,
GE,
LE,
G,
}
impl X86ConditionCode {
pub fn as_suffix(&self) -> &'static str {
match self {
X86ConditionCode::O => "o",
X86ConditionCode::NO => "no",
X86ConditionCode::B => "b",
X86ConditionCode::AE => "ae",
X86ConditionCode::E => "e",
X86ConditionCode::NE => "ne",
X86ConditionCode::BE => "be",
X86ConditionCode::A => "a",
X86ConditionCode::S => "s",
X86ConditionCode::NS => "ns",
X86ConditionCode::P => "p",
X86ConditionCode::NP => "np",
X86ConditionCode::L => "l",
X86ConditionCode::GE => "ge",
X86ConditionCode::LE => "le",
X86ConditionCode::G => "g",
}
}
pub fn inverse(&self) -> X86ConditionCode {
match self {
X86ConditionCode::O => X86ConditionCode::NO,
X86ConditionCode::NO => X86ConditionCode::O,
X86ConditionCode::B => X86ConditionCode::AE,
X86ConditionCode::AE => X86ConditionCode::B,
X86ConditionCode::E => X86ConditionCode::NE,
X86ConditionCode::NE => X86ConditionCode::E,
X86ConditionCode::BE => X86ConditionCode::A,
X86ConditionCode::A => X86ConditionCode::BE,
X86ConditionCode::S => X86ConditionCode::NS,
X86ConditionCode::NS => X86ConditionCode::S,
X86ConditionCode::P => X86ConditionCode::NP,
X86ConditionCode::NP => X86ConditionCode::P,
X86ConditionCode::L => X86ConditionCode::GE,
X86ConditionCode::GE => X86ConditionCode::L,
X86ConditionCode::LE => X86ConditionCode::G,
X86ConditionCode::G => X86ConditionCode::LE,
}
}
pub fn unsigned_counterpart(&self) -> X86ConditionCode {
match self {
X86ConditionCode::L => X86ConditionCode::B,
X86ConditionCode::GE => X86ConditionCode::AE,
X86ConditionCode::LE => X86ConditionCode::BE,
X86ConditionCode::G => X86ConditionCode::A,
other => *other,
}
}
pub fn signed_counterpart(&self) -> X86ConditionCode {
match self {
X86ConditionCode::B => X86ConditionCode::L,
X86ConditionCode::AE => X86ConditionCode::GE,
X86ConditionCode::BE => X86ConditionCode::LE,
X86ConditionCode::A => X86ConditionCode::G,
other => *other,
}
}
}
impl fmt::Display for X86ConditionCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_suffix())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86InstrKind {
ALU,
Move,
Load,
Store,
Jump,
Branch,
Call,
Return,
Compare,
Stack,
Float,
Vector,
System,
Nop,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct X86InstrFlags {
pub has_def: bool,
pub uses_flags: bool,
pub defs_flags: bool,
pub may_load: bool,
pub may_store: bool,
pub is_terminator: bool,
pub is_branch: bool,
pub is_call: bool,
pub is_return: bool,
pub has_side_effects: bool,
pub uses_rsp: bool,
pub defs_rsp: bool,
pub uses_rbp: bool,
pub is_commutative: bool,
pub is_compare: bool,
pub is_rematerializable: bool,
}
#[derive(Debug, Clone)]
pub struct X86MachineInstr {
pub opcode: X86Opcode,
pub operands: Vec<X86MachineOperand>,
pub def: Option<VirtReg>,
pub flags: X86InstrFlags,
pub kind: X86InstrKind,
pub debug_loc: Option<u32>,
pub id: u64,
}
impl X86MachineInstr {
pub fn new(opcode: X86Opcode, kind: X86InstrKind) -> Self {
Self {
opcode,
operands: Vec::new(),
def: None,
flags: X86InstrFlags::default(),
kind,
debug_loc: None,
id: 0,
}
}
pub fn with_id(mut self, id: u64) -> Self {
self.id = id;
self
}
pub fn add_vreg(mut self, vreg: VirtReg) -> Self {
self.operands.push(X86MachineOperand::VReg(vreg));
self
}
pub fn add_preg(mut self, preg: PhysReg) -> Self {
self.operands.push(X86MachineOperand::PReg(preg));
self
}
pub fn add_imm(mut self, imm: i64) -> Self {
self.operands.push(X86MachineOperand::Imm(imm));
self
}
pub fn add_label(mut self, label: &str) -> Self {
self.operands
.push(X86MachineOperand::Label(label.to_string()));
self
}
pub fn add_mem(
mut self,
base: Option<PhysReg>,
index: Option<PhysReg>,
scale: u8,
displacement: i32,
) -> Self {
self.operands.push(X86MachineOperand::Memory {
base,
index,
scale,
displacement,
});
self
}
pub fn add_cc(mut self, cc: X86ConditionCode) -> Self {
self.operands.push(X86MachineOperand::Condition(cc));
self
}
pub fn set_def(mut self, vreg: VirtReg) -> Self {
self.def = Some(vreg);
self.flags.has_def = true;
self
}
pub fn with_flags(mut self, flags: X86InstrFlags) -> Self {
self.flags = flags;
self
}
pub fn operand(&self, idx: usize) -> Option<&X86MachineOperand> {
self.operands.get(idx)
}
pub fn num_operands(&self) -> usize {
self.operands.len()
}
pub fn is_terminator(&self) -> bool {
self.flags.is_terminator
}
pub fn is_branch(&self) -> bool {
self.flags.is_branch
}
pub fn is_call(&self) -> bool {
self.flags.is_call
}
pub fn is_return(&self) -> bool {
self.flags.is_return
}
pub fn has_def(&self) -> bool {
self.flags.has_def
}
pub fn get_def_vreg(&self) -> Option<VirtReg> {
self.def
}
pub fn vreg_uses(&self) -> Vec<VirtReg> {
self.operands.iter().filter_map(|op| op.as_vreg()).collect()
}
pub fn preg_uses(&self) -> Vec<PhysReg> {
self.operands.iter().filter_map(|op| op.as_preg()).collect()
}
pub fn is_move(&self) -> bool {
matches!(self.kind, X86InstrKind::Move)
}
pub fn is_copy(&self) -> bool {
self.opcode == X86Opcode::MOV
&& self.operands.len() == 2
&& self.operands[0].is_reg()
&& self.operands[1].is_reg()
}
}
impl fmt::Display for X86MachineInstr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.opcode)?;
if let Some(def) = self.def {
write!(f, " %v{} =", def)?;
}
for (i, op) in self.operands.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, " {}", op)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86MachineBasicBlock {
pub name: String,
pub instructions: Vec<X86MachineInstr>,
pub successors: Vec<String>,
pub predecessors: Vec<String>,
pub is_entry: bool,
pub alignment: u32,
pub live_in: HashSet<VirtReg>,
pub live_out: HashSet<VirtReg>,
pub frequency: f64,
}
impl X86MachineBasicBlock {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: false,
alignment: 16,
live_in: HashSet::new(),
live_out: HashSet::new(),
frequency: 1.0,
}
}
pub fn push_instr(&mut self, instr: X86MachineInstr) {
self.instructions.push(instr);
}
pub fn add_successor(&mut self, name: &str) {
self.successors.push(name.to_string());
}
pub fn add_predecessor(&mut self, name: &str) {
self.predecessors.push(name.to_string());
}
pub fn last_instr(&self) -> Option<&X86MachineInstr> {
self.instructions.last()
}
pub fn has_terminator(&self) -> bool {
self.last_instr()
.map(|i| i.is_terminator())
.unwrap_or(false)
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &X86MachineInstr> {
self.instructions.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut X86MachineInstr> {
self.instructions.iter_mut()
}
pub fn collect_defs(&self) -> HashSet<VirtReg> {
let mut defs = HashSet::new();
for instr in &self.instructions {
if let Some(def) = instr.def {
defs.insert(def);
}
}
defs
}
pub fn collect_uses(&self) -> HashSet<VirtReg> {
let mut uses = HashSet::new();
for instr in &self.instructions {
for op in &instr.operands {
if let Some(vreg) = op.as_vreg() {
uses.insert(vreg);
}
}
}
uses
}
pub fn estimated_size(&self) -> usize {
self.instructions.len() * 6 }
}
impl fmt::Display for X86MachineBasicBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}:", self.name)?;
if !self.live_in.is_empty() {
write!(f, " ; live-in: ")?;
for (i, v) in self.live_in.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "%v{}", v)?;
}
writeln!(f)?;
}
for instr in &self.instructions {
writeln!(f, " {}", instr)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86MachineFunction {
pub name: String,
pub blocks: Vec<X86MachineBasicBlock>,
pub virt_reg_counter: u32,
pub frame_info: Option<X86FrameInfo>,
pub has_calls: bool,
pub frame_size: u32,
pub stack_alignment: u32,
pub callee_saved_size: u32,
pub call_conv: X86CallingConvention,
pub reg_alloc: Option<X86RegAllocResult>,
pub vreg_to_preg: HashMap<VirtReg, PhysReg>,
pub spill_slots: Vec<X86SpillSlot>,
pub encoded_bytes: Vec<u8>,
pub symbols: Vec<X86SymbolEntry>,
pub relocations: Vec<X86RelocationEntry>,
}
#[derive(Debug, Clone, Default)]
pub struct X86FrameInfo {
pub total_size: u32,
pub local_size: u32,
pub saved_reg_size: u32,
pub return_address_offset: i32,
pub has_frame_pointer: bool,
pub needs_realignment: bool,
pub local_area_offset: i32,
pub uses_red_zone: bool,
pub max_call_frame_size: u32,
pub has_var_sized_objects: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CallingConvention {
SystemV64,
Win64,
CDecl32,
StdCall32,
FastCall32,
VectorCall,
}
impl Default for X86CallingConvention {
fn default() -> Self {
X86CallingConvention::SystemV64
}
}
impl X86CallingConvention {
pub fn uses_red_zone(&self) -> bool {
matches!(self, X86CallingConvention::SystemV64)
}
pub fn is_64bit(&self) -> bool {
matches!(
self,
X86CallingConvention::SystemV64 | X86CallingConvention::Win64
)
}
pub fn callee_saved_gprs(&self) -> Vec<PhysReg> {
match self {
X86CallingConvention::SystemV64 => {
vec![
RBX as PhysReg,
RBP as PhysReg,
R12 as PhysReg,
R13 as PhysReg,
R14 as PhysReg,
R15 as PhysReg,
]
}
X86CallingConvention::Win64 => {
vec![
RBX as PhysReg,
RBP as PhysReg,
RDI as PhysReg,
RSI as PhysReg,
R12 as PhysReg,
R13 as PhysReg,
R14 as PhysReg,
R15 as PhysReg,
]
}
X86CallingConvention::CDecl32 | X86CallingConvention::StdCall32 => {
vec![
EBX as PhysReg,
EBP as PhysReg,
EDI as PhysReg,
ESI as PhysReg,
]
}
X86CallingConvention::FastCall32 => {
vec![
EBX as PhysReg,
EBP as PhysReg,
EDI as PhysReg,
ESI as PhysReg,
]
}
X86CallingConvention::VectorCall => {
vec![
RBX as PhysReg,
RBP as PhysReg,
R12 as PhysReg,
R13 as PhysReg,
R14 as PhysReg,
R15 as PhysReg,
]
}
}
}
pub fn caller_saved_gprs(&self) -> Vec<PhysReg> {
match self {
X86CallingConvention::SystemV64 => {
vec![
RAX as PhysReg,
RCX as PhysReg,
RDX as PhysReg,
RSI as PhysReg,
RDI as PhysReg,
R8 as PhysReg,
R9 as PhysReg,
R10 as PhysReg,
R11 as PhysReg,
]
}
X86CallingConvention::Win64 => {
vec![
RAX as PhysReg,
RCX as PhysReg,
RDX as PhysReg,
R8 as PhysReg,
R9 as PhysReg,
R10 as PhysReg,
R11 as PhysReg,
]
}
X86CallingConvention::CDecl32 | X86CallingConvention::StdCall32 => {
vec![EAX as PhysReg, ECX as PhysReg, EDX as PhysReg]
}
X86CallingConvention::FastCall32 => {
vec![EAX as PhysReg, EDX as PhysReg]
}
X86CallingConvention::VectorCall => {
vec![
RAX as PhysReg,
RCX as PhysReg,
RDX as PhysReg,
R8 as PhysReg,
R9 as PhysReg,
R10 as PhysReg,
R11 as PhysReg,
]
}
}
}
pub fn stack_pointer_reg(&self) -> PhysReg {
if self.is_64bit() {
RSP as PhysReg
} else {
160 }
}
pub fn frame_pointer_reg(&self) -> PhysReg {
if self.is_64bit() {
RBP as PhysReg
} else {
161 }
}
}
#[derive(Debug, Clone)]
pub struct X86SpillSlot {
pub vreg: VirtReg,
pub offset: i32,
pub size: u32,
pub alignment: u32,
pub reg_class: RegClass,
}
#[derive(Debug, Clone)]
pub struct X86SymbolEntry {
pub name: String,
pub offset: u64,
pub size: u64,
pub is_global: bool,
pub is_function: bool,
}
#[derive(Debug, Clone)]
pub struct X86RelocationEntry {
pub offset: u64,
pub symbol: String,
pub reloc_type: X86RelocType,
pub addend: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RelocType {
None,
Abs64,
PcRel32,
Abs32,
Plt32,
GotPcRel,
RexGotPcRelx,
}
impl X86RelocType {
pub fn size(&self) -> u8 {
match self {
X86RelocType::None => 0,
X86RelocType::Abs64 => 8,
X86RelocType::PcRel32 | X86RelocType::Abs32 | X86RelocType::Plt32 => 4,
X86RelocType::GotPcRel | X86RelocType::RexGotPcRelx => 4,
}
}
pub fn is_pc_relative(&self) -> bool {
matches!(
self,
X86RelocType::PcRel32
| X86RelocType::Plt32
| X86RelocType::GotPcRel
| X86RelocType::RexGotPcRelx
)
}
}
#[derive(Debug, Clone)]
pub struct X86RegAllocResult {
pub success: bool,
pub spills: u32,
pub reloads: u32,
pub copies_coalesced: u32,
pub regs_spilled: u32,
pub ranges_split: u32,
}
impl X86RegAllocResult {
pub fn success(spills: u32, reloads: u32, copies: u32) -> Self {
Self {
success: true,
spills,
reloads,
copies_coalesced: copies,
regs_spilled: 0,
ranges_split: 0,
}
}
pub fn failure() -> Self {
Self {
success: false,
spills: 0,
reloads: 0,
copies_coalesced: 0,
regs_spilled: 0,
ranges_split: 0,
}
}
}
impl X86MachineFunction {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
blocks: Vec::new(),
virt_reg_counter: 0,
frame_info: None,
has_calls: false,
frame_size: 0,
stack_alignment: X86_64_STACK_ALIGN,
callee_saved_size: 0,
call_conv: X86CallingConvention::default(),
reg_alloc: None,
vreg_to_preg: HashMap::new(),
spill_slots: Vec::new(),
encoded_bytes: Vec::new(),
symbols: Vec::new(),
relocations: Vec::new(),
}
}
pub fn new_vreg(&mut self) -> VirtReg {
let r = self.virt_reg_counter;
self.virt_reg_counter += 1;
r
}
pub fn push_block(&mut self, bb: X86MachineBasicBlock) {
self.blocks.push(bb);
}
pub fn entry_block(&self) -> Option<&X86MachineBasicBlock> {
self.blocks.first()
}
pub fn entry_block_mut(&mut self) -> Option<&mut X86MachineBasicBlock> {
self.blocks.first_mut()
}
pub fn total_instructions(&self) -> usize {
self.blocks.iter().map(|b| b.len()).sum()
}
pub fn num_blocks(&self) -> usize {
self.blocks.len()
}
pub fn compute_liveness(&mut self) {
let mut changed = true;
while changed {
changed = false;
let block_info: Vec<(HashSet<VirtReg>, HashSet<VirtReg>)> = self
.blocks
.iter()
.map(|b| {
let mut uses = HashSet::new();
let mut defs = HashSet::new();
for instr in &b.instructions {
for op in &instr.operands {
if let Some(vreg) = op.as_vreg() {
if !defs.contains(&vreg) {
uses.insert(vreg);
}
}
}
if let Some(def) = instr.def {
defs.insert(def);
}
}
(uses, defs)
})
.collect();
let succ_indices: Vec<Vec<usize>> = self
.blocks
.iter()
.map(|b| {
b.successors
.iter()
.filter_map(|s| self.blocks.iter().position(|bb| bb.name == *s))
.collect()
})
.collect();
for i in 0..self.blocks.len() {
let mut new_live_out: HashSet<VirtReg> = HashSet::new();
for &succ_idx in &succ_indices[i] {
for vreg in &self.blocks[succ_idx].live_in {
new_live_out.insert(*vreg);
}
}
if new_live_out != self.blocks[i].live_out {
self.blocks[i].live_out = new_live_out;
changed = true;
}
let (ref uses, ref defs) = block_info[i];
let mut new_live_in = self.blocks[i].live_out.clone();
for d in defs {
new_live_in.remove(d);
}
for u in uses {
new_live_in.insert(*u);
}
if new_live_in != self.blocks[i].live_in {
self.blocks[i].live_in = new_live_in;
changed = true;
}
}
}
}
pub fn all_vreg_defs(&self) -> HashSet<VirtReg> {
let mut defs = HashSet::new();
for bb in &self.blocks {
for instr in &bb.instructions {
if let Some(def) = instr.def {
defs.insert(def);
}
}
}
defs
}
pub fn assign_instr_ids(&mut self) {
let mut id = 0u64;
for bb in &mut self.blocks {
for instr in &mut bb.instructions {
instr.id = id;
id += 1;
}
}
}
pub fn estimate_code_size(&self) -> usize {
self.blocks.iter().map(|b| b.estimated_size()).sum()
}
pub fn add_spill_slot(&mut self, slot: X86SpillSlot) {
self.spill_slots.push(slot);
}
pub fn spill_slot_size(&self) -> u32 {
self.spill_slots.iter().map(|s| s.size).sum()
}
}
impl fmt::Display for X86MachineFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "MachineFunction: {}", self.name)?;
writeln!(f, " Calling convention: {:?}", self.call_conv)?;
if let Some(ref fi) = self.frame_info {
writeln!(f, " Frame size: {} bytes", fi.total_size)?;
if fi.has_frame_pointer {
writeln!(f, " Has frame pointer")?;
}
}
writeln!(f, " Virtual registers: {}", self.virt_reg_counter)?;
writeln!(f, " Spill slots: {}", self.spill_slots.len())?;
for bb in &self.blocks {
writeln!(f)?;
write!(f, "{}", bb)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86ISelPattern {
pub name: &'static str,
pub ir_op: &'static str,
pub x86_opcode: X86Opcode,
pub num_operands: u8,
pub sets_flags: bool,
pub is_conditional: bool,
pub is_commutative: bool,
pub feature: &'static str,
pub kind: X86InstrKind,
pub priority: u8,
pub cost: u32,
pub constraints: PatternConstraints,
}
#[derive(Debug, Clone, Default)]
pub struct PatternConstraints {
pub operand_imm: Vec<bool>,
pub operand_reg: Vec<bool>,
pub operand_size: Vec<Option<u32>>,
pub required_feature: Option<String>,
pub required_subtarget: Option<String>,
}
impl X86ISelPattern {
pub const fn new(name: &'static str, ir_op: &'static str, x86_opcode: X86Opcode) -> Self {
Self {
name,
ir_op,
x86_opcode,
num_operands: 2,
sets_flags: false,
is_conditional: false,
is_commutative: false,
feature: "",
kind: X86InstrKind::ALU,
priority: 0,
cost: 1,
constraints: PatternConstraints {
operand_imm: Vec::new(),
operand_reg: Vec::new(),
operand_size: Vec::new(),
required_feature: None,
required_subtarget: None,
},
}
}
pub const fn compare_pattern(
name: &'static str,
ir_op: &'static str,
x86_opcode: X86Opcode,
) -> Self {
Self {
name,
ir_op,
x86_opcode,
num_operands: 2,
sets_flags: true,
is_conditional: false,
is_commutative: false,
feature: "",
kind: X86InstrKind::Compare,
priority: 0,
cost: 1,
constraints: PatternConstraints {
operand_imm: Vec::new(),
operand_reg: Vec::new(),
operand_size: Vec::new(),
required_feature: None,
required_subtarget: None,
},
}
}
pub const fn commutative_alu(
name: &'static str,
ir_op: &'static str,
x86_opcode: X86Opcode,
) -> Self {
Self {
name,
ir_op,
x86_opcode,
num_operands: 2,
sets_flags: true,
is_conditional: false,
is_commutative: true,
feature: "",
kind: X86InstrKind::ALU,
priority: 1,
cost: 1,
constraints: PatternConstraints {
operand_imm: Vec::new(),
operand_reg: Vec::new(),
operand_size: Vec::new(),
required_feature: None,
required_subtarget: None,
},
}
}
pub fn with_priority(mut self, priority: u8) -> Self {
self.priority = priority;
self
}
pub fn with_cost(mut self, cost: u32) -> Self {
self.cost = cost;
self
}
pub fn with_feature(mut self, feature: &'static str) -> Self {
self.feature = feature;
self
}
pub fn with_operands(mut self, n: u8) -> Self {
self.num_operands = n;
self
}
pub fn with_commutative(mut self) -> Self {
self.is_commutative = true;
self
}
pub fn with_kind(mut self, kind: X86InstrKind) -> Self {
self.kind = kind;
self
}
}
pub static GOLDEN_ISEL_PATTERNS: LazyLock<Vec<X86ISelPattern>> = LazyLock::new(|| {
vec![
X86ISelPattern::commutative_alu("add_i32", "add", X86Opcode::ADD),
X86ISelPattern::commutative_alu("add_i64", "add", X86Opcode::ADD),
X86ISelPattern::commutative_alu("mul_i32", "mul", X86Opcode::IMUL),
X86ISelPattern::commutative_alu("mul_i64", "mul", X86Opcode::IMUL),
X86ISelPattern::commutative_alu("and_i32", "and", X86Opcode::AND),
X86ISelPattern::commutative_alu("and_i64", "and", X86Opcode::AND),
X86ISelPattern::commutative_alu("or_i32", "or", X86Opcode::OR),
X86ISelPattern::commutative_alu("or_i64", "or", X86Opcode::OR),
X86ISelPattern::commutative_alu("xor_i32", "xor", X86Opcode::XOR),
X86ISelPattern::commutative_alu("xor_i64", "xor", X86Opcode::XOR),
X86ISelPattern::new("sub_i32", "sub", X86Opcode::SUB),
X86ISelPattern::new("sub_i64", "sub", X86Opcode::SUB),
X86ISelPattern::new("sdiv_i32", "sdiv", X86Opcode::IDIV),
X86ISelPattern::new("sdiv_i64", "sdiv", X86Opcode::IDIV),
X86ISelPattern::new("udiv_i32", "udiv", X86Opcode::DIV),
X86ISelPattern::new("udiv_i64", "udiv", X86Opcode::DIV),
X86ISelPattern::new("shl_i32", "shl", X86Opcode::SHL),
X86ISelPattern::new("shl_i64", "shl", X86Opcode::SHL),
X86ISelPattern::new("lshr_i32", "lshr", X86Opcode::SHR),
X86ISelPattern::new("lshr_i64", "lshr", X86Opcode::SHR),
X86ISelPattern::new("ashr_i32", "ashr", X86Opcode::SAR),
X86ISelPattern::new("ashr_i64", "ashr", X86Opcode::SAR),
X86ISelPattern::compare_pattern("icmp_i32", "icmp", X86Opcode::CMP).with_priority(2),
X86ISelPattern::compare_pattern("icmp_i64", "icmp", X86Opcode::CMP).with_priority(2),
X86ISelPattern::new("load_i32", "load", X86Opcode::MOV)
.with_kind(X86InstrKind::Load)
.with_operands(1),
X86ISelPattern::new("load_i64", "load", X86Opcode::MOV)
.with_kind(X86InstrKind::Load)
.with_operands(1),
X86ISelPattern::new("store_i32", "store", X86Opcode::MOV)
.with_kind(X86InstrKind::Store)
.with_operands(2),
X86ISelPattern::new("store_i64", "store", X86Opcode::MOV)
.with_kind(X86InstrKind::Store)
.with_operands(2),
X86ISelPattern::new("zext_i32_to_i64", "zext", X86Opcode::MOVZX)
.with_kind(X86InstrKind::Move),
X86ISelPattern::new("sext_i32_to_i64", "sext", X86Opcode::MOVSX)
.with_kind(X86InstrKind::Move),
X86ISelPattern::new("trunc_i64_to_i32", "trunc", X86Opcode::MOV)
.with_kind(X86InstrKind::Move),
X86ISelPattern::new("br", "br", X86Opcode::JMP)
.with_kind(X86InstrKind::Jump)
.with_operands(1),
X86ISelPattern::new("ret_void", "ret", X86Opcode::RET)
.with_kind(X86InstrKind::Return)
.with_operands(0),
X86ISelPattern::new("call", "call", X86Opcode::CALL)
.with_kind(X86InstrKind::Call)
.with_operands(1),
X86ISelPattern::new("alloca", "alloca", X86Opcode::SUB)
.with_kind(X86InstrKind::Stack)
.with_operands(2),
X86ISelPattern::new("fadd_f32", "fadd", X86Opcode::ADDSS).with_kind(X86InstrKind::Float),
X86ISelPattern::new("fadd_f64", "fadd", X86Opcode::ADDSD).with_kind(X86InstrKind::Float),
X86ISelPattern::new("fsub_f32", "fsub", X86Opcode::SUBSS).with_kind(X86InstrKind::Float),
X86ISelPattern::new("fsub_f64", "fsub", X86Opcode::SUBSD).with_kind(X86InstrKind::Float),
X86ISelPattern::new("fmul_f32", "fmul", X86Opcode::MULSS).with_kind(X86InstrKind::Float),
X86ISelPattern::new("fmul_f64", "fmul", X86Opcode::MULSD).with_kind(X86InstrKind::Float),
X86ISelPattern::new("fdiv_f32", "fdiv", X86Opcode::DIVSS).with_kind(X86InstrKind::Float),
X86ISelPattern::new("fdiv_f64", "fdiv", X86Opcode::DIVSD).with_kind(X86InstrKind::Float),
X86ISelPattern::new("fpext_f32_to_f64", "fpext", X86Opcode::CVTSS2SD)
.with_kind(X86InstrKind::Float),
X86ISelPattern::new("fptrunc_f64_to_f32", "fptrunc", X86Opcode::CVTSD2SS)
.with_kind(X86InstrKind::Float),
X86ISelPattern::new("fptosi_f32_to_i32", "fptosi", X86Opcode::CVTTSS2SI)
.with_kind(X86InstrKind::Float),
X86ISelPattern::new("fptosi_f64_to_i32", "fptosi", X86Opcode::CVTTSD2SI)
.with_kind(X86InstrKind::Float),
X86ISelPattern::new("sitofp_i32_to_f32", "sitofp", X86Opcode::CVTSI2SS)
.with_kind(X86InstrKind::Float),
X86ISelPattern::new("sitofp_i32_to_f64", "sitofp", X86Opcode::CVTSI2SD)
.with_kind(X86InstrKind::Float),
X86ISelPattern::new("add_ps", "fadd", X86Opcode::ADDPS).with_kind(X86InstrKind::Vector),
X86ISelPattern::new("add_pd", "fadd", X86Opcode::ADDPD).with_kind(X86InstrKind::Vector),
X86ISelPattern::new("mul_ps", "fmul", X86Opcode::MULPS).with_kind(X86InstrKind::Vector),
X86ISelPattern::new("mul_pd", "fmul", X86Opcode::MULPD).with_kind(X86InstrKind::Vector),
X86ISelPattern::new("select_i32", "select", X86Opcode::CMOVNE)
.with_kind(X86InstrKind::Move),
X86ISelPattern::new("select_i64", "select", X86Opcode::CMOVNE)
.with_kind(X86InstrKind::Move),
X86ISelPattern::new("phi", "phi", X86Opcode::MOV)
.with_kind(X86InstrKind::Move)
.with_operands(1),
X86ISelPattern::new("getelementptr", "getelementptr", X86Opcode::LEA)
.with_kind(X86InstrKind::Move),
]
});
pub fn lookup_golden_patterns(ir_op: &str) -> Vec<&'static X86ISelPattern> {
GOLDEN_ISEL_PATTERNS
.iter()
.filter(|p| p.ir_op == ir_op)
.collect()
}
pub fn lookup_golden_pattern_by_name(name: &str) -> Option<&'static X86ISelPattern> {
GOLDEN_ISEL_PATTERNS.iter().find(|p| p.name == name)
}
pub fn golden_pattern_counts() -> BTreeMap<&'static str, usize> {
let mut counts = BTreeMap::new();
for pattern in GOLDEN_ISEL_PATTERNS.iter() {
*counts.entry(pattern.ir_op).or_insert(0) += 1;
}
counts
}
pub struct X86ISelStage {
pub subtarget: X86Subtarget,
pub vreg_map: HashMap<u32, VirtReg>,
pub current_mbb: Option<usize>,
pub mf: X86MachineFunction,
pub is_64bit: bool,
pub debug: bool,
pub metrics: StageMetrics,
}
#[derive(Debug, Default)]
pub struct StageMetrics {
pub items_processed: u64,
pub elapsed: Duration,
pub failures: Cell<u64>,
pub patterns_matched: u64,
}
impl Clone for StageMetrics {
fn clone(&self) -> Self {
Self {
items_processed: self.items_processed,
elapsed: self.elapsed,
failures: Cell::new(self.failures.get()),
patterns_matched: self.patterns_matched,
}
}
}
impl StageMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn record_match(&mut self) {
self.patterns_matched += 1;
self.items_processed += 1;
}
pub fn record_failure(&self) {
self.failures.set(self.failures.get() + 1);
}
}
impl X86ISelStage {
pub fn new(subtarget: X86Subtarget) -> Self {
Self {
subtarget,
vreg_map: HashMap::new(),
current_mbb: None,
mf: X86MachineFunction::new(""),
is_64bit: true,
debug: false,
metrics: StageMetrics::new(),
}
}
pub fn run(
&mut self,
func_name: &str,
instructions: &[(String, Vec<u32>, Option<u32>)],
block_boundaries: &[usize],
) -> Result<X86MachineFunction, X86PipelineError> {
let start = Instant::now();
self.mf = X86MachineFunction::new(func_name);
let mut entry_bb = X86MachineBasicBlock::new("entry");
entry_bb.is_entry = true;
let mut current_block = entry_bb;
let mut blocks: Vec<X86MachineBasicBlock> = Vec::new();
let mut block_idx = 0usize;
let mut instr_idx = 0usize;
for (i, (op_name, operand_vids, result_vid)) in instructions.iter().enumerate() {
if block_boundaries.contains(&i) && !current_block.is_empty() {
let new_name = format!("bb{}", block_idx + 1);
let old_block =
std::mem::replace(&mut current_block, X86MachineBasicBlock::new(&new_name));
blocks.push(old_block);
block_idx += 1;
}
instr_idx += 1;
let def_vreg = if let Some(_) = result_vid {
Some(self.mf.new_vreg())
} else {
None
};
let mut operand_vregs = Vec::new();
for &vid in operand_vids {
let vreg = *self
.vreg_map
.entry(vid)
.or_insert_with(|| self.mf.new_vreg());
operand_vregs.push(vreg);
}
let patterns = lookup_golden_patterns(op_name);
if patterns.is_empty() {
if self.debug {
eprintln!(
"ISel warning: no pattern for op '{}' in function '{}'",
op_name, func_name
);
}
self.metrics.record_failure();
continue;
}
let best_pattern = patterns
.iter()
.max_by_key(|p| (p.priority, -(p.cost as i64)))
.unwrap();
let mut instr = X86MachineInstr::new(best_pattern.x86_opcode, best_pattern.kind);
instr.flags = X86InstrFlags {
has_def: def_vreg.is_some(),
uses_flags: best_pattern.sets_flags,
defs_flags: best_pattern.sets_flags,
is_terminator: matches!(
best_pattern.kind,
X86InstrKind::Jump | X86InstrKind::Branch | X86InstrKind::Return
),
is_branch: matches!(best_pattern.kind, X86InstrKind::Jump | X86InstrKind::Branch),
is_call: matches!(best_pattern.kind, X86InstrKind::Call),
is_return: matches!(best_pattern.kind, X86InstrKind::Return),
has_side_effects: matches!(
best_pattern.kind,
X86InstrKind::Call | X86InstrKind::Store | X86InstrKind::System
),
is_commutative: best_pattern.is_commutative,
is_compare: best_pattern.kind == X86InstrKind::Compare,
may_load: matches!(best_pattern.kind, X86InstrKind::Load),
may_store: matches!(best_pattern.kind, X86InstrKind::Store),
..Default::default()
};
if let Some(def) = def_vreg {
instr = instr.set_def(def);
}
for &vreg in &operand_vregs {
instr = instr.add_vreg(vreg);
}
if instr.is_call() {
self.mf.has_calls = true;
}
current_block.push_instr(instr);
self.metrics.record_match();
}
if !current_block.is_empty() || blocks.is_empty() {
blocks.push(current_block);
}
for i in 0..blocks.len() {
if let Some(last_instr) = blocks[i].last_instr() {
if last_instr.is_branch() && !last_instr.is_return() {
if let Some(X86MachineOperand::Label(label)) = last_instr.operands.last() {
let label_clone = label.clone();
blocks[i].add_successor(&label_clone);
}
}
}
if i + 1 < blocks.len() {
let has_fallthrough = blocks[i]
.last_instr()
.map(|instr| !instr.is_return() && instr.opcode != X86Opcode::JMP)
.unwrap_or(true);
if has_fallthrough {
let next_name = blocks[i + 1].name.clone();
blocks[i].add_successor(&next_name);
}
}
}
let succ_map: Vec<Vec<usize>> = blocks
.iter()
.map(|b| {
b.successors
.iter()
.filter_map(|s| blocks.iter().position(|bb| bb.name == *s))
.collect()
})
.collect();
for i in 0..blocks.len() {
for j in 0..blocks.len() {
if succ_map[j].contains(&i) {
let pred_name = blocks[j].name.clone();
blocks[i].add_predecessor(&pred_name);
}
}
}
for bb in blocks {
self.mf.push_block(bb);
}
self.mf.assign_instr_ids();
self.metrics.elapsed = start.elapsed();
Ok(self.mf.clone())
}
pub fn get_vreg(&mut self, vid: u32) -> VirtReg {
*self
.vreg_map
.entry(vid)
.or_insert_with(|| self.mf.new_vreg())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegAllocStrategy {
Fast,
Greedy,
GreedyWithSplitting,
Basic,
}
impl Default for RegAllocStrategy {
fn default() -> Self {
RegAllocStrategy::Greedy
}
}
pub struct X86RegAllocStage {
pub strategy: RegAllocStrategy,
pub reg_info: TargetRegInfo,
pub is_64bit: bool,
pub call_conv: X86CallingConvention,
pub debug: bool,
pub metrics: StageMetrics,
available: Vec<PhysReg>,
assignments: HashMap<VirtReg, PhysReg>,
phys_to_vreg: HashMap<PhysReg, VirtReg>,
spill_costs: HashMap<VirtReg, f64>,
live_intervals: HashMap<VirtReg, LiveInterval>,
spill_slots: Vec<X86SpillSlot>,
next_spill_offset: i32,
}
#[derive(Debug, Clone)]
pub struct LiveInterval {
pub vreg: VirtReg,
pub start: u64,
pub end: u64,
pub uses: Vec<u64>,
pub defs: Vec<u64>,
}
impl LiveInterval {
pub fn new(vreg: VirtReg) -> Self {
Self {
vreg,
start: u64::MAX,
end: 0,
uses: Vec::new(),
defs: Vec::new(),
}
}
pub fn overlaps(&self, other: &LiveInterval) -> bool {
self.start < other.end && other.start < self.end
}
pub fn live_at(&self, instr_id: u64) -> bool {
self.start <= instr_id && instr_id < self.end
}
pub fn length(&self) -> u64 {
if self.end > self.start {
self.end - self.start
} else {
0
}
}
pub fn covers(&self, instr_id: u64) -> bool {
self.start <= instr_id && instr_id <= self.end
}
}
impl X86RegAllocStage {
pub fn new(strategy: RegAllocStrategy, is_64bit: bool) -> Self {
let reg_info = if is_64bit {
TargetRegInfo::for_x86_64()
} else {
TargetRegInfo {
gpr_count: 8,
fpr_count: 8,
reserved: vec![],
callee_saved: vec![],
caller_saved: vec![],
reg_size: 4,
}
};
Self {
strategy,
reg_info,
is_64bit,
call_conv: X86CallingConvention::default(),
debug: false,
metrics: StageMetrics::new(),
available: Vec::new(),
assignments: HashMap::new(),
phys_to_vreg: HashMap::new(),
spill_costs: HashMap::new(),
live_intervals: HashMap::new(),
spill_slots: Vec::new(),
next_spill_offset: 0,
}
}
pub fn run(
&mut self,
mf: &mut X86MachineFunction,
) -> Result<X86RegAllocResult, X86PipelineError> {
let start = Instant::now();
self.available = self.compute_available_regs(mf);
if self.available.is_empty() {
return Err(X86PipelineError::RegAllocFailed(
"No allocatable registers available".into(),
));
}
self.compute_live_intervals(mf);
self.compute_spill_costs(mf);
let result = match self.strategy {
RegAllocStrategy::Fast => self.fast_allocate(mf),
RegAllocStrategy::Basic => self.basic_allocate(mf),
RegAllocStrategy::Greedy | RegAllocStrategy::GreedyWithSplitting => {
self.greedy_allocate(mf)
}
};
self.apply_assignments(mf);
mf.spill_slots = self.spill_slots.clone();
mf.vreg_to_preg = self.assignments.clone();
self.metrics.elapsed = start.elapsed();
Ok(result)
}
fn compute_available_regs(&self, mf: &X86MachineFunction) -> Vec<PhysReg> {
let mut avail = Vec::new();
let reserved: HashSet<PhysReg> = [
self.call_conv.stack_pointer_reg(),
self.call_conv.frame_pointer_reg(),
]
.iter()
.cloned()
.collect();
let all_gprs: Vec<PhysReg> = if self.is_64bit {
vec![
RAX as PhysReg,
RCX as PhysReg,
RDX as PhysReg,
RBX as PhysReg,
RSI as PhysReg,
RDI as PhysReg,
R8 as PhysReg,
R9 as PhysReg,
R10 as PhysReg,
R11 as PhysReg,
R12 as PhysReg,
R13 as PhysReg,
R14 as PhysReg,
R15 as PhysReg,
]
} else {
vec![
EAX as PhysReg,
ECX as PhysReg,
EDX as PhysReg,
EBX as PhysReg,
ESI as PhysReg,
EDI as PhysReg,
]
};
for &r in &all_gprs {
if !reserved.contains(&r) {
avail.push(r);
}
}
let xmms: Vec<PhysReg> = if self.is_64bit {
vec![
XMM0 as PhysReg,
XMM1 as PhysReg,
XMM2 as PhysReg,
XMM3 as PhysReg,
XMM4 as PhysReg,
XMM5 as PhysReg,
XMM6 as PhysReg,
XMM7 as PhysReg,
XMM8 as PhysReg,
XMM9 as PhysReg,
XMM10 as PhysReg,
XMM11 as PhysReg,
XMM12 as PhysReg,
XMM13 as PhysReg,
XMM14 as PhysReg,
XMM15 as PhysReg,
]
} else {
vec![
XMM0 as PhysReg,
XMM1 as PhysReg,
XMM2 as PhysReg,
XMM3 as PhysReg,
XMM4 as PhysReg,
XMM5 as PhysReg,
XMM6 as PhysReg,
XMM7 as PhysReg,
]
};
for &x in &xmms {
avail.push(x);
}
avail
}
fn compute_live_intervals(&mut self, mf: &X86MachineFunction) {
self.live_intervals.clear();
let all_vregs = mf.all_vreg_defs();
for &vreg in &all_vregs {
self.live_intervals.insert(vreg, LiveInterval::new(vreg));
}
for bb in &mf.blocks {
for instr in &bb.instructions {
let instr_id = instr.id;
if let Some(def) = instr.def {
if let Some(interval) = self.live_intervals.get_mut(&def) {
interval.defs.push(instr_id);
if instr_id < interval.start {
interval.start = instr_id;
}
if instr_id > interval.end {
interval.end = instr_id + 1;
}
}
}
for op in &instr.operands {
if let Some(vreg) = op.as_vreg() {
if let Some(interval) = self.live_intervals.get_mut(&vreg) {
interval.uses.push(instr_id);
if interval.defs.is_empty() {
if instr_id < interval.start {
interval.start = instr_id;
}
}
if instr_id + 1 > interval.end {
interval.end = instr_id + 1;
}
}
}
}
}
}
for bb in &mf.blocks {
let block_end = bb.instructions.last().map(|i| i.id).unwrap_or(0);
for vreg in &bb.live_out {
if let Some(interval) = self.live_intervals.get_mut(vreg) {
if interval.end < block_end + 1 {
interval.end = block_end + 1;
}
}
}
}
}
fn compute_spill_costs(&mut self, mf: &X86MachineFunction) {
self.spill_costs.clear();
for (&vreg, interval) in &self.live_intervals {
let use_count = interval.uses.len() as f64;
let def_count = interval.defs.len() as f64;
let length = interval.length() as f64;
let cost = (use_count * 2.0 + def_count + length.ln_1p())
/ (use_count + def_count + 1.0).max(1.0);
self.spill_costs.insert(vreg, cost);
}
}
fn fast_allocate(&mut self, mf: &X86MachineFunction) -> X86RegAllocResult {
let mut spills = 0u32;
let mut reloads = 0u32;
let mut active: Vec<(VirtReg, PhysReg, u64)> = Vec::new();
let mut sorted_vregs: Vec<VirtReg> = self.live_intervals.keys().copied().collect();
sorted_vregs.sort_by_key(|v| self.live_intervals.get(v).map(|i| i.start).unwrap_or(0));
for vreg in sorted_vregs {
let interval = match self.live_intervals.get(&vreg) {
Some(i) => i.clone(),
None => continue,
};
let start = interval.start;
active.retain(|&(_, _, end)| end > start);
let used_regs: HashSet<PhysReg> = active.iter().map(|&(_, r, _)| r).collect();
if let Some(&free_reg) = self.available.iter().find(|r| !used_regs.contains(r)) {
active.push((vreg, free_reg, interval.end));
self.assignments.insert(vreg, free_reg);
self.phys_to_vreg.insert(free_reg, vreg);
} else {
spills += 1;
if let Some(&(evict_vreg, evict_reg, _)) =
active.iter().max_by_key(|&&(_, _, end)| end)
{
active.retain(|&(v, _, _)| v != evict_vreg);
self.assignments.remove(&evict_vreg);
self.phys_to_vreg.remove(&evict_reg);
self.create_spill_slot(evict_vreg, RegClass::GPR64);
active.push((vreg, evict_reg, interval.end));
self.assignments.insert(vreg, evict_reg);
self.phys_to_vreg.insert(evict_reg, vreg);
}
if !self.assignments.contains_key(&vreg) {
self.create_spill_slot(vreg, RegClass::GPR64);
}
reloads += 1;
}
}
X86RegAllocResult {
success: true,
spills,
reloads,
copies_coalesced: 0,
regs_spilled: spills,
ranges_split: 0,
}
}
fn basic_allocate(&mut self, mf: &X86MachineFunction) -> X86RegAllocResult {
let mut spills = 0u32;
self.assignments.clear();
for bb in &mf.blocks {
let mut local_free: Vec<PhysReg> = self.available.clone();
for instr in &bb.instructions {
if let Some(def) = instr.def {
if let Some(®) = local_free.first() {
self.assignments.insert(def, reg);
local_free.remove(0);
} else {
spills += 1;
self.create_spill_slot(def, RegClass::GPR64);
}
}
for op in &instr.operands {
if let Some(vreg) = op.as_vreg() {
if !self.assignments.contains_key(&vreg) {
if let Some(®) = local_free.first() {
self.assignments.insert(vreg, reg);
local_free.remove(0);
} else {
spills += 1;
self.create_spill_slot(vreg, RegClass::GPR64);
}
}
}
}
if let Some(def) = instr.def {
if !bb.live_out.contains(&def) && self.assignments.contains_key(&def) {
if let Some(&preg) = self.assignments.get(&def) {
local_free.push(preg);
}
}
}
}
}
X86RegAllocResult {
success: true,
spills,
reloads: 0,
copies_coalesced: 0,
regs_spilled: spills,
ranges_split: 0,
}
}
fn greedy_allocate(&mut self, mf: &X86MachineFunction) -> X86RegAllocResult {
let mut spills = 0u32;
let mut reloads = 0u32;
let mut copies_coalesced = 0u32;
let mut sorted_vregs: Vec<(VirtReg, f64)> =
self.spill_costs.iter().map(|(&v, &c)| (v, c)).collect();
sorted_vregs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let mut active_intervals: Vec<(VirtReg, PhysReg, LiveInterval)> = Vec::new();
for &(vreg, _cost) in &sorted_vregs {
let interval = match self.live_intervals.get(&vreg) {
Some(i) => i.clone(),
None => continue,
};
active_intervals.retain(|(_, _, i)| i.end > interval.start);
if let Some(coalesced_reg) = self.try_coalesce(vreg, mf) {
self.assignments.insert(vreg, coalesced_reg);
self.phys_to_vreg.insert(coalesced_reg, vreg);
active_intervals.push((vreg, coalesced_reg, interval.clone()));
copies_coalesced += 1;
continue;
}
let used_regs: HashSet<PhysReg> = active_intervals.iter().map(|&(_, r, _)| r).collect();
if let Some(&free_reg) = self.available.iter().find(|r| !used_regs.contains(r)) {
self.assignments.insert(vreg, free_reg);
self.phys_to_vreg.insert(free_reg, vreg);
active_intervals.push((vreg, free_reg, interval.clone()));
continue;
}
let evict_candidate = active_intervals.iter().min_by(|a, b| {
let ca = self.spill_costs.get(&a.0).copied().unwrap_or(0.0);
let cb = self.spill_costs.get(&b.0).copied().unwrap_or(0.0);
ca.partial_cmp(&cb).unwrap_or(std::cmp::Ordering::Equal)
});
if let Some(&(evict_vreg, evict_reg, _)) = evict_candidate {
let evict_cost = self.spill_costs.get(&evict_vreg).copied().unwrap_or(0.0);
let current_cost = self.spill_costs.get(&vreg).copied().unwrap_or(0.0);
if current_cost > evict_cost {
active_intervals.retain(|&(v, _, _)| v != evict_vreg);
self.assignments.remove(&evict_vreg);
self.phys_to_vreg.remove(&evict_reg);
self.create_spill_slot(evict_vreg, RegClass::GPR64);
spills += 1;
self.assignments.insert(vreg, evict_reg);
self.phys_to_vreg.insert(evict_reg, vreg);
active_intervals.push((vreg, evict_reg, interval.clone()));
} else {
self.create_spill_slot(vreg, RegClass::GPR64);
spills += 1;
}
} else {
self.create_spill_slot(vreg, RegClass::GPR64);
spills += 1;
}
reloads += 1;
}
let ranges_split = if self.strategy == RegAllocStrategy::GreedyWithSplitting {
let split_count = self.split_live_ranges(mf);
split_count as u32
} else {
0
};
X86RegAllocResult {
success: true,
spills,
reloads,
copies_coalesced,
regs_spilled: spills,
ranges_split,
}
}
fn try_coalesce(&self, vreg: VirtReg, mf: &X86MachineFunction) -> Option<PhysReg> {
for bb in &mf.blocks {
for instr in &bb.instructions {
if instr.def == Some(vreg)
&& instr.opcode == X86Opcode::MOV
&& instr.operands.len() == 2
{
if let Some(src_vreg) = instr.operands[1].as_vreg() {
if let Some(&preg) = self.assignments.get(&src_vreg) {
return Some(preg);
}
}
}
}
}
None
}
fn split_live_ranges(&mut self, _mf: &X86MachineFunction) -> usize {
let mut splits = 0usize;
let intervals: Vec<(VirtReg, LiveInterval)> = self
.live_intervals
.iter()
.map(|(&v, i)| (v, i.clone()))
.collect();
for (vreg, interval) in &intervals {
if interval.length() > 100 {
splits += 1;
self.metrics.items_processed += 1;
}
}
splits
}
fn create_spill_slot(&mut self, vreg: VirtReg, reg_class: RegClass) {
let size = match reg_class {
RegClass::GPR64 => 8,
RegClass::GPR32 => 4,
RegClass::GPR16 => 2,
RegClass::GPR8 => 1,
RegClass::XMM => 16,
RegClass::YMM => 32,
RegClass::ZMM => 64,
_ => 8,
};
let alignment = size;
let slot = X86SpillSlot {
vreg,
offset: self.next_spill_offset,
size,
alignment,
reg_class,
};
self.next_spill_offset += size as i32;
self.spill_slots.push(slot);
}
fn apply_assignments(&self, mf: &mut X86MachineFunction) {
for bb in &mut mf.blocks {
for instr in &mut bb.instructions {
if let Some(def) = instr.def {
if let Some(&preg) = self.assignments.get(&def) {
instr.def = Some(preg);
}
}
for op in &mut instr.operands {
if let X86MachineOperand::VReg(vreg) = *op {
if let Some(&preg) = self.assignments.get(&vreg) {
*op = X86MachineOperand::PReg(preg);
}
}
}
}
}
}
}
pub struct X86FrameLowerStage {
pub call_conv: X86CallingConvention,
pub is_64bit: bool,
pub eliminate_frame_pointer: bool,
pub debug: bool,
pub metrics: StageMetrics,
}
impl X86FrameLowerStage {
pub fn new(call_conv: X86CallingConvention, is_64bit: bool) -> Self {
Self {
call_conv,
is_64bit,
eliminate_frame_pointer: false,
debug: false,
metrics: StageMetrics::new(),
}
}
pub fn run(&mut self, mf: &mut X86MachineFunction) -> Result<(), X86PipelineError> {
let start = Instant::now();
let frame_info = self.calculate_frame_info(mf);
mf.frame_info = Some(frame_info.clone());
let needs_fp = self.needs_frame_pointer(&frame_info);
let mut frame_info = frame_info;
frame_info.has_frame_pointer = needs_fp;
self.emit_prologue(mf, &frame_info)?;
self.emit_epilogues(mf, &frame_info)?;
self.assign_spill_slots(mf, &frame_info);
mf.frame_info = Some(frame_info);
self.metrics.elapsed = start.elapsed();
self.metrics.items_processed = mf.num_blocks() as u64;
Ok(())
}
fn calculate_frame_info(&self, mf: &X86MachineFunction) -> X86FrameInfo {
let mut info = X86FrameInfo::default();
let callee_saved_regs = self.call_conv.callee_saved_gprs();
info.saved_reg_size = (callee_saved_regs.len() as u32) * if self.is_64bit { 8 } else { 4 };
let spill_size = mf.spill_slot_size();
let local_size = 64;
let max_call_args = if mf.has_calls { 32 } else { 0 };
info.max_call_frame_size = max_call_args;
let total = info.saved_reg_size + spill_size + local_size + max_call_args;
info.total_size =
((total + X86_64_STACK_ALIGN - 1) / X86_64_STACK_ALIGN) * X86_64_STACK_ALIGN;
info.local_size = local_size;
info.uses_red_zone =
self.call_conv.uses_red_zone() && info.total_size <= X86_64_RED_ZONE && !mf.has_calls;
info.return_address_offset = -(info.total_size as i32) + 8;
info
}
fn needs_frame_pointer(&self, info: &X86FrameInfo) -> bool {
if self.eliminate_frame_pointer {
return false;
}
if info.has_var_sized_objects {
return true;
}
if info.total_size > 4096 {
return true; }
if !self.is_64bit && info.total_size > 256 {
return true; }
false
}
fn emit_prologue(
&self,
mf: &mut X86MachineFunction,
frame_info: &X86FrameInfo,
) -> Result<(), X86PipelineError> {
let entry_block = match mf.entry_block_mut() {
Some(bb) => bb,
None => return Ok(()),
};
let sp = self.call_conv.stack_pointer_reg();
let fp = self.call_conv.frame_pointer_reg();
let mut prologue_instrs: Vec<X86MachineInstr> = Vec::new();
let callee_saved = self.call_conv.callee_saved_gprs();
for ® in callee_saved.iter().rev() {
prologue_instrs.push(
X86MachineInstr::new(X86Opcode::PUSH, X86InstrKind::Stack)
.add_preg(reg)
.with_flags(X86InstrFlags {
uses_rsp: true,
defs_rsp: true,
has_side_effects: true,
..Default::default()
}),
);
}
if frame_info.has_frame_pointer {
prologue_instrs.push(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_preg(fp)
.add_preg(sp)
.with_flags(X86InstrFlags::default()),
);
}
if frame_info.total_size > 0 {
prologue_instrs.push(
X86MachineInstr::new(X86Opcode::SUB, X86InstrKind::Stack)
.add_preg(sp)
.add_imm(frame_info.total_size as i64)
.with_flags(X86InstrFlags {
uses_rsp: true,
defs_rsp: true,
defs_flags: true,
..Default::default()
}),
);
}
let mut new_instrs = prologue_instrs;
new_instrs.append(&mut entry_block.instructions);
entry_block.instructions = new_instrs;
Ok(())
}
fn emit_epilogues(
&self,
mf: &mut X86MachineFunction,
frame_info: &X86FrameInfo,
) -> Result<(), X86PipelineError> {
let sp = self.call_conv.stack_pointer_reg();
let fp = self.call_conv.frame_pointer_reg();
let callee_saved = self.call_conv.callee_saved_gprs();
for bb in &mut mf.blocks {
let mut new_instrs = Vec::new();
let mut found_return = false;
for instr in &bb.instructions {
if instr.is_return() {
found_return = true;
if frame_info.has_frame_pointer {
new_instrs.push(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_preg(sp)
.add_preg(fp)
.with_flags(X86InstrFlags::default()),
);
} else if frame_info.total_size > 0 {
new_instrs.push(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::Stack)
.add_preg(sp)
.add_imm(frame_info.total_size as i64)
.with_flags(X86InstrFlags {
uses_rsp: true,
defs_rsp: true,
defs_flags: true,
..Default::default()
}),
);
}
for ® in &callee_saved {
new_instrs.push(
X86MachineInstr::new(X86Opcode::POP, X86InstrKind::Stack)
.add_preg(reg)
.with_flags(X86InstrFlags {
uses_rsp: true,
defs_rsp: true,
has_side_effects: true,
..Default::default()
}),
);
}
new_instrs.push(instr.clone());
} else {
new_instrs.push(instr.clone());
}
}
if found_return {
bb.instructions = new_instrs;
}
}
Ok(())
}
fn assign_spill_slots(&self, mf: &mut X86MachineFunction, frame_info: &X86FrameInfo) {
let mut offset = -(frame_info.saved_reg_size as i32) - 8;
for slot in &mut mf.spill_slots {
offset -= slot.size as i32;
let align = slot.alignment as i32;
offset = offset & !(align - 1);
slot.offset = offset;
}
}
}
pub struct X86EncodeStage {
pub is_64bit: bool,
pub encoder: X86MCEncoder,
pub debug: bool,
pub metrics: StageMetrics,
}
#[derive(Debug, Clone)]
pub struct EncodedInstruction {
pub bytes: Vec<u8>,
pub offset: u64,
pub size: u32,
pub reloc: Option<X86RelocationEntry>,
}
impl X86EncodeStage {
pub fn new(is_64bit: bool) -> Self {
use crate::x86::x86_mc_encoder::X86Mode;
let mode = if is_64bit {
X86Mode::Mode64
} else {
X86Mode::Mode32
};
let encoder = X86MCEncoder {
output: Vec::new(),
mode,
subtarget: default_subtarget(),
};
Self {
is_64bit,
encoder,
debug: false,
metrics: StageMetrics::new(),
}
}
pub fn run(
&mut self,
mf: &X86MachineFunction,
) -> Result<Vec<EncodedInstruction>, X86PipelineError> {
let start = Instant::now();
let mut encoded = Vec::new();
let mut offset: u64 = 0;
for bb in &mf.blocks {
for instr in &bb.instructions {
let bytes = self.encode_instr(instr)?;
let size = bytes.len() as u32;
let reloc = self.check_relocation(instr, offset);
encoded.push(EncodedInstruction {
bytes,
offset,
size,
reloc,
});
offset += size as u64;
self.metrics.items_processed += 1;
}
}
self.metrics.elapsed = start.elapsed();
Ok(encoded)
}
fn encode_instr(&self, instr: &X86MachineInstr) -> Result<Vec<u8>, X86PipelineError> {
let mut bytes = Vec::new();
match instr.opcode {
X86Opcode::NOP => {
bytes.push(0x90);
}
X86Opcode::RET => {
bytes.push(0xC3);
}
X86Opcode::INT3 => {
bytes.push(0xCC);
}
X86Opcode::UD2 => {
bytes.extend_from_slice(&[0x0F, 0x0B]);
}
X86Opcode::PUSH => {
if let Some(op) = instr.operand(0) {
match op {
X86MachineOperand::PReg(reg) => {
let rex = self.encode_rex(false, false, false, *reg);
if rex != 0 {
bytes.push(rex);
}
let opcode_byte = 0x50 + self.reg_field_3bit(*reg);
bytes.push(opcode_byte);
}
X86MachineOperand::Imm(v) => {
if *v >= -128 && *v <= 127 {
bytes.push(0x6A);
bytes.push(*v as u8);
} else {
bytes.push(0x68);
let imm = *v as u32;
bytes.extend_from_slice(&imm.to_le_bytes());
}
}
_ => {
return Err(X86PipelineError::EncodingFailed(format!(
"Unsupported PUSH operand: {:?}",
op
)))
}
}
}
}
X86Opcode::POP => {
if let Some(X86MachineOperand::PReg(reg)) = instr.operand(0) {
let rex = self.encode_rex(false, false, false, *reg);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0x58 + self.reg_field_3bit(*reg));
}
}
X86Opcode::MOV => {
self.encode_mov(instr, &mut bytes)?;
}
X86Opcode::ADD
| X86Opcode::SUB
| X86Opcode::AND
| X86Opcode::OR
| X86Opcode::XOR
| X86Opcode::CMP
| X86Opcode::TEST => {
self.encode_alu(instr, &mut bytes)?;
}
X86Opcode::IMUL | X86Opcode::MUL | X86Opcode::DIV | X86Opcode::IDIV => {
self.encode_mul_div(instr, &mut bytes)?;
}
X86Opcode::SHL | X86Opcode::SHR | X86Opcode::SAR => {
self.encode_shift(instr, &mut bytes)?;
}
X86Opcode::JMP => {
self.encode_jmp(instr, &mut bytes)?;
}
X86Opcode::CALL => {
self.encode_call(instr, &mut bytes)?;
}
X86Opcode::LEA => {
self.encode_lea(instr, &mut bytes)?;
}
X86Opcode::ADDSS
| X86Opcode::ADDSD
| X86Opcode::SUBSS
| X86Opcode::SUBSD
| X86Opcode::MULSS
| X86Opcode::MULSD
| X86Opcode::DIVSS
| X86Opcode::DIVSD
| X86Opcode::ADDPS
| X86Opcode::ADDPD
| X86Opcode::MULPS
| X86Opcode::MULPD
| X86Opcode::CVTSS2SD
| X86Opcode::CVTSD2SS
| X86Opcode::CVTTSS2SI
| X86Opcode::CVTTSD2SI
| X86Opcode::CVTSI2SS
| X86Opcode::CVTSI2SD => {
self.encode_sse(instr, &mut bytes)?;
}
X86Opcode::MOVSX | X86Opcode::MOVZX => {
self.encode_movsx_zx(instr, &mut bytes)?;
}
X86Opcode::CMOVE
| X86Opcode::CMOVNE
| X86Opcode::CMOVL
| X86Opcode::CMOVG
| X86Opcode::CMOVGE
| X86Opcode::CMOVLE
| X86Opcode::CMOVA
| X86Opcode::CMOVAE
| X86Opcode::CMOVB
| X86Opcode::CMOVBE => {
self.encode_cmov(instr, &mut bytes)?;
}
X86Opcode::INC | X86Opcode::DEC => {
self.encode_inc_dec(instr, &mut bytes)?;
}
X86Opcode::NEG | X86Opcode::NOT => {
self.encode_neg_not(instr, &mut bytes)?;
}
_ => {
if self.debug {
eprintln!(
"Encoding warning: no encoder for opcode {:?}, emitting NOP",
instr.opcode
);
}
bytes.push(0x90); self.metrics.record_failure();
}
}
Ok(bytes)
}
fn encode_rex(&self, w: bool, r: bool, x: bool, reg: PhysReg) -> u8 {
if !self.is_64bit {
return 0;
}
let mut rex = 0x40u8;
if w {
rex |= 0x08;
}
if r {
rex |= 0x04;
}
if x {
rex |= 0x02;
}
let reg_idx = reg % 16;
if reg_idx >= 8 && reg_idx < 16 {
rex |= 0x01;
}
if rex == 0x40 && !w && !r && !x && reg_idx < 8 {
return 0;
}
rex
}
fn reg_field_3bit(&self, reg: PhysReg) -> u8 {
(reg % 8) as u8
}
fn encode_mov(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if instr.operands.len() < 2 {
return Err(X86PipelineError::EncodingFailed(
"MOV needs 2 operands".into(),
));
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
match (dst, src) {
(X86MachineOperand::PReg(d), X86MachineOperand::PReg(s)) => {
let rex = self.encode_rex(true, true, false, *d.max(s));
if rex != 0 {
bytes.push(rex);
}
bytes.push(0x89);
let modrm = 0xC0 | (self.reg_field_3bit(*s) << 3) | self.reg_field_3bit(*d);
bytes.push(modrm);
}
(X86MachineOperand::PReg(d), X86MachineOperand::Imm(v)) => {
let rex_w = self.encode_rex(true, false, false, *d);
if rex_w != 0 {
bytes.push(rex_w);
}
bytes.push(0xB8 + self.reg_field_3bit(*d));
let imm_bytes = (*v as i64).to_le_bytes();
bytes.extend_from_slice(&imm_bytes[..8]);
}
(X86MachineOperand::PReg(d), X86MachineOperand::Imm64(v)) => {
let rex_w = self.encode_rex(true, false, false, *d);
if rex_w != 0 {
bytes.push(rex_w);
}
bytes.push(0xB8 + self.reg_field_3bit(*d));
bytes.extend_from_slice(&v.to_le_bytes());
}
_ => {
return Err(X86PipelineError::EncodingFailed(format!(
"Unsupported MOV operands: {:?}, {:?}",
dst, src
)));
}
}
Ok(())
}
fn encode_alu(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if instr.operands.len() < 2 {
return Err(X86PipelineError::EncodingFailed(
"ALU needs 2 operands".into(),
));
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
let opcode_base: u8 = match instr.opcode {
X86Opcode::ADD => 0x01, X86Opcode::SUB => 0x29, X86Opcode::AND => 0x21, X86Opcode::OR => 0x09, X86Opcode::XOR => 0x31, X86Opcode::CMP => 0x39, X86Opcode::TEST => 0x85, _ => {
return Err(X86PipelineError::EncodingFailed(
"Unknown ALU opcode".into(),
))
}
};
match (dst, src) {
(X86MachineOperand::PReg(d), X86MachineOperand::PReg(s)) => {
let rex = self.encode_rex(true, true, false, *d.max(s));
if rex != 0 {
bytes.push(rex);
}
bytes.push(opcode_base);
let modrm = 0xC0 | (self.reg_field_3bit(*s) << 3) | self.reg_field_3bit(*d);
bytes.push(modrm);
}
(X86MachineOperand::PReg(d), X86MachineOperand::Imm(v)) => {
if *v >= -128 && *v <= 127 && instr.opcode != X86Opcode::TEST {
let rex = self.encode_rex(true, false, false, *d);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0x83);
let group_op: u8 = match instr.opcode {
X86Opcode::ADD => 0,
X86Opcode::SUB => 5,
X86Opcode::AND => 4,
X86Opcode::OR => 1,
X86Opcode::XOR => 6,
X86Opcode::CMP => 7,
_ => 0,
};
let modrm = 0xC0 | (group_op << 3) | self.reg_field_3bit(*d);
bytes.push(modrm);
bytes.push(*v as u8);
} else {
let rex = self.encode_rex(true, false, false, *d);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0x81);
let group_op: u8 = match instr.opcode {
X86Opcode::ADD => 0,
X86Opcode::SUB => 5,
X86Opcode::AND => 4,
X86Opcode::OR => 1,
X86Opcode::XOR => 6,
X86Opcode::CMP => 7,
X86Opcode::TEST => 0,
_ => 0,
};
let modrm = 0xC0 | (group_op << 3) | self.reg_field_3bit(*d);
bytes.push(modrm);
let imm = *v as u32;
bytes.extend_from_slice(&imm.to_le_bytes());
}
}
_ => {
return Err(X86PipelineError::EncodingFailed(format!(
"Unsupported ALU operands: {:?}, {:?}",
dst, src
)));
}
}
Ok(())
}
fn encode_mul_div(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if instr.operands.is_empty() {
return Err(X86PipelineError::EncodingFailed(
"MUL/DIV needs operand".into(),
));
}
let op = &instr.operands[0];
if let X86MachineOperand::PReg(reg) = op {
let rex = self.encode_rex(true, false, false, *reg);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0xF7);
let group_op: u8 = match instr.opcode {
X86Opcode::MUL => 4,
X86Opcode::IMUL => 5,
X86Opcode::DIV => 6,
X86Opcode::IDIV => 7,
_ => 4,
};
let modrm = 0xC0 | (group_op << 3) | self.reg_field_3bit(*reg);
bytes.push(modrm);
}
Ok(())
}
fn encode_shift(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if instr.operands.len() < 2 {
return Err(X86PipelineError::EncodingFailed(
"Shift needs 2 operands".into(),
));
}
let dst = &instr.operands[0];
let count = &instr.operands[1];
let group_op: u8 = match instr.opcode {
X86Opcode::SHL => 4,
X86Opcode::SHR => 5,
X86Opcode::SAR => 7,
_ => 4,
};
match (dst, count) {
(X86MachineOperand::PReg(d), X86MachineOperand::Imm(c)) => {
if *c == 1 {
let rex = self.encode_rex(true, false, false, *d);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0xD1);
let modrm = 0xC0 | (group_op << 3) | self.reg_field_3bit(*d);
bytes.push(modrm);
} else {
let rex = self.encode_rex(true, false, false, *d);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0xC1);
let modrm = 0xC0 | (group_op << 3) | self.reg_field_3bit(*d);
bytes.push(modrm);
bytes.push(*c as u8);
}
}
(X86MachineOperand::PReg(d), X86MachineOperand::PReg(_)) => {
let rex = self.encode_rex(true, false, false, *d);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0xD3);
let modrm = 0xC0 | (group_op << 3) | self.reg_field_3bit(*d);
bytes.push(modrm);
}
_ => {
return Err(X86PipelineError::EncodingFailed(
"Unsupported shift operands".into(),
));
}
}
Ok(())
}
fn encode_jmp(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if let Some(op) = instr.operand(0) {
match op {
X86MachineOperand::Label(_) | X86MachineOperand::Global(_) => {
bytes.push(0xE9);
bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
}
X86MachineOperand::PReg(_) => {
bytes.push(0xFF);
bytes.push(0xE0); }
X86MachineOperand::Condition(cc) => {
bytes.push(0x0F);
let jcc_base: u8 = match cc {
X86ConditionCode::O => 0x80,
X86ConditionCode::NO => 0x81,
X86ConditionCode::B => 0x82,
X86ConditionCode::AE => 0x83,
X86ConditionCode::E => 0x84,
X86ConditionCode::NE => 0x85,
X86ConditionCode::BE => 0x86,
X86ConditionCode::A => 0x87,
X86ConditionCode::S => 0x88,
X86ConditionCode::NS => 0x89,
X86ConditionCode::P => 0x8A,
X86ConditionCode::NP => 0x8B,
X86ConditionCode::L => 0x8C,
X86ConditionCode::GE => 0x8D,
X86ConditionCode::LE => 0x8E,
X86ConditionCode::G => 0x8F,
};
bytes.push(jcc_base);
bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
}
_ => {
return Err(X86PipelineError::EncodingFailed(
"Unsupported JMP operand".into(),
));
}
}
}
Ok(())
}
fn encode_call(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if let Some(op) = instr.operand(0) {
match op {
X86MachineOperand::Global(_) | X86MachineOperand::Label(_) => {
bytes.push(0xE8);
bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
}
X86MachineOperand::PReg(_) => {
bytes.push(0xFF);
bytes.push(0xD0); }
_ => {
return Err(X86PipelineError::EncodingFailed(
"Unsupported CALL operand".into(),
));
}
}
}
Ok(())
}
fn encode_lea(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if instr.operands.len() < 2 {
return Err(X86PipelineError::EncodingFailed(
"LEA needs 2 operands".into(),
));
}
if let (X86MachineOperand::PReg(d), X86MachineOperand::PReg(_)) =
(&instr.operands[0], &instr.operands[1])
{
let rex = self.encode_rex(true, true, false, *d);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0x8D); let modrm = 0x80 | (self.reg_field_3bit(*d) << 3); bytes.push(modrm);
bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); }
Ok(())
}
fn encode_sse(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if instr.operands.len() < 2 {
return Err(X86PipelineError::EncodingFailed(
"SSE needs 2 operands".into(),
));
}
let (mandatory_prefix, opcode_map, opcode_byte) = match instr.opcode {
X86Opcode::ADDSS => (0xF3u8, false, 0x58),
X86Opcode::ADDSD => (0xF2u8, false, 0x58),
X86Opcode::SUBSS => (0xF3u8, false, 0x5C),
X86Opcode::SUBSD => (0xF2u8, false, 0x5C),
X86Opcode::MULSS => (0xF3u8, false, 0x59),
X86Opcode::MULSD => (0xF2u8, false, 0x59),
X86Opcode::DIVSS => (0xF3u8, false, 0x5E),
X86Opcode::DIVSD => (0xF2u8, false, 0x5E),
X86Opcode::ADDPS => (0x00u8, false, 0x58),
X86Opcode::ADDPD => (0x66u8, false, 0x58),
X86Opcode::MULPS => (0x00u8, false, 0x59),
X86Opcode::MULPD => (0x66u8, false, 0x59),
X86Opcode::CVTSS2SD => (0xF3u8, false, 0x5A),
X86Opcode::CVTSD2SS => (0xF2u8, false, 0x5A),
X86Opcode::CVTTSS2SI => (0xF3u8, false, 0x2C),
X86Opcode::CVTTSD2SI => (0xF2u8, false, 0x2C),
X86Opcode::CVTSI2SS => (0xF3u8, false, 0x2A),
X86Opcode::CVTSI2SD => (0xF2u8, false, 0x2A),
_ => (0x00, false, 0x00),
};
if mandatory_prefix != 0 && mandatory_prefix != 0x66 {
bytes.push(mandatory_prefix);
} else if mandatory_prefix == 0x66 {
bytes.push(0x66);
}
if opcode_map {
bytes.push(0x0F);
bytes.push(0x38); } else {
bytes.push(0x0F);
}
bytes.push(opcode_byte);
if let (X86MachineOperand::PReg(d), X86MachineOperand::PReg(s)) =
(&instr.operands[0], &instr.operands[1])
{
let modrm = 0xC0 | (self.reg_field_3bit(*s) << 3) | self.reg_field_3bit(*d);
bytes.push(modrm);
}
Ok(())
}
fn encode_movsx_zx(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if instr.operands.len() < 2 {
return Err(X86PipelineError::EncodingFailed(
"MOVSX/MOVZX needs 2 operands".into(),
));
}
let (opcode_0f, opcode_byte) = match instr.opcode {
X86Opcode::MOVSX => (0x0F, 0xBE), X86Opcode::MOVZX => (0x0F, 0xB6), _ => (0x0F, 0xB6),
};
if let (X86MachineOperand::PReg(d), X86MachineOperand::PReg(s)) =
(&instr.operands[0], &instr.operands[1])
{
let rex = self.encode_rex(true, true, false, *d.max(s));
if rex != 0 {
bytes.push(rex);
}
bytes.push(opcode_0f);
bytes.push(opcode_byte);
let modrm = 0xC0 | (self.reg_field_3bit(*d) << 3) | self.reg_field_3bit(*s);
bytes.push(modrm);
}
Ok(())
}
fn encode_cmov(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if instr.operands.len() < 2 {
return Err(X86PipelineError::EncodingFailed(
"CMOV needs 2 operands".into(),
));
}
let cmov_cc: u8 = match instr.opcode {
X86Opcode::CMOVO => 0x40,
X86Opcode::CMOVNO => 0x41,
X86Opcode::CMOVB => 0x42,
X86Opcode::CMOVAE => 0x43,
X86Opcode::CMOVE => 0x44,
X86Opcode::CMOVNE => 0x45,
X86Opcode::CMOVBE => 0x46,
X86Opcode::CMOVA => 0x47,
X86Opcode::CMOVS => 0x48,
X86Opcode::CMOVNS => 0x49,
X86Opcode::CMOVP => 0x4A,
X86Opcode::CMOVNP => 0x4B,
X86Opcode::CMOVL => 0x4C,
X86Opcode::CMOVGE => 0x4D,
X86Opcode::CMOVLE => 0x4E,
X86Opcode::CMOVG => 0x4F,
_ => 0x45,
};
if let (X86MachineOperand::PReg(d), X86MachineOperand::PReg(s)) =
(&instr.operands[0], &instr.operands[1])
{
let rex = self.encode_rex(true, true, false, *d.max(s));
if rex != 0 {
bytes.push(rex);
}
bytes.push(0x0F);
bytes.push(cmov_cc);
let modrm = 0xC0 | (self.reg_field_3bit(*d) << 3) | self.reg_field_3bit(*s);
bytes.push(modrm);
}
Ok(())
}
fn encode_inc_dec(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if let Some(X86MachineOperand::PReg(reg)) = instr.operand(0) {
let rex = self.encode_rex(true, false, false, *reg);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0xFF);
let group_op: u8 = match instr.opcode {
X86Opcode::INC => 0,
X86Opcode::DEC => 1,
_ => 0,
};
let modrm = 0xC0 | (group_op << 3) | self.reg_field_3bit(*reg);
bytes.push(modrm);
}
Ok(())
}
fn encode_neg_not(
&self,
instr: &X86MachineInstr,
bytes: &mut Vec<u8>,
) -> Result<(), X86PipelineError> {
if let Some(X86MachineOperand::PReg(reg)) = instr.operand(0) {
let rex = self.encode_rex(true, false, false, *reg);
if rex != 0 {
bytes.push(rex);
}
bytes.push(0xF7);
let group_op: u8 = match instr.opcode {
X86Opcode::NEG => 3,
X86Opcode::NOT => 2,
_ => 3,
};
let modrm = 0xC0 | (group_op << 3) | self.reg_field_3bit(*reg);
bytes.push(modrm);
}
Ok(())
}
fn check_relocation(&self, instr: &X86MachineInstr, offset: u64) -> Option<X86RelocationEntry> {
for op in &instr.operands {
match op {
X86MachineOperand::Label(name) => {
return Some(X86RelocationEntry {
offset,
symbol: name.clone(),
reloc_type: X86RelocType::PcRel32,
addend: -4,
});
}
X86MachineOperand::Global(name) => {
let reloc_type = if instr.is_call() {
X86RelocType::Plt32
} else {
X86RelocType::PcRel32
};
return Some(X86RelocationEntry {
offset,
symbol: name.clone(),
reloc_type,
addend: -4,
});
}
_ => {}
}
}
None
}
pub fn encoded_size(encoded: &[EncodedInstruction]) -> u64 {
encoded.iter().map(|e| e.size as u64).sum()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EmissionFormat {
Object,
Assembly,
AssemblyIntel,
Both,
}
pub struct X86EmitStage {
pub format: EmissionFormat,
pub mc_ctx: MCContext,
pub object_bytes: Vec<u8>,
pub assembly_text: String,
pub debug: bool,
pub metrics: StageMetrics,
}
#[derive(Debug, Clone)]
pub struct X86PipelineOutput {
pub function_name: String,
pub object_bytes: Vec<u8>,
pub assembly: Option<String>,
pub symbols: Vec<X86SymbolEntry>,
pub relocations: Vec<X86RelocationEntry>,
pub code_size: u64,
pub pipeline_metrics: PipelineMetrics,
}
impl X86EmitStage {
pub fn new(format: EmissionFormat) -> Self {
Self {
format,
mc_ctx: MCContext::new("x86_64-unknown-none"),
object_bytes: Vec::new(),
assembly_text: String::new(),
debug: false,
metrics: StageMetrics::new(),
}
}
pub fn run(
&mut self,
mf: &X86MachineFunction,
encoded: &[EncodedInstruction],
) -> Result<X86PipelineOutput, X86PipelineError> {
let start = Instant::now();
let mut object_bytes = Vec::new();
let mut assembly = String::new();
match self.format {
EmissionFormat::Object => {
object_bytes = self.emit_object(mf, encoded)?;
}
EmissionFormat::Assembly | EmissionFormat::AssemblyIntel => {
assembly = self.emit_assembly(mf, encoded);
}
EmissionFormat::Both => {
object_bytes = self.emit_object(mf, encoded)?;
assembly = self.emit_assembly(mf, encoded);
}
}
let code_size = encoded.iter().map(|e| e.size as u64).sum();
let mut relocations = Vec::new();
for enc in encoded {
if let Some(ref reloc) = enc.reloc {
relocations.push(reloc.clone());
}
}
let symbols = mf.symbols.clone();
self.metrics.elapsed = start.elapsed();
self.metrics.items_processed = encoded.len() as u64;
Ok(X86PipelineOutput {
function_name: mf.name.clone(),
object_bytes,
assembly: if assembly.is_empty() {
None
} else {
Some(assembly)
},
symbols,
relocations,
code_size,
pipeline_metrics: PipelineMetrics::default(),
})
}
fn emit_object(
&mut self,
mf: &X86MachineFunction,
encoded: &[EncodedInstruction],
) -> Result<Vec<u8>, X86PipelineError> {
let mut bytes = Vec::new();
bytes.extend_from_slice(mf.name.as_bytes());
bytes.push(0);
let count = encoded.len() as u32;
bytes.extend_from_slice(&count.to_le_bytes());
for enc in encoded {
bytes.push(enc.size as u8);
bytes.extend_from_slice(&enc.bytes);
}
let mut reloc_count = 0u32;
for enc in encoded {
if enc.reloc.is_some() {
reloc_count += 1;
}
}
bytes.extend_from_slice(&reloc_count.to_le_bytes());
for enc in encoded {
if let Some(ref reloc) = enc.reloc {
bytes.extend_from_slice(&enc.offset.to_le_bytes());
bytes.push(reloc.reloc_type as u8);
bytes.extend_from_slice(reloc.symbol.as_bytes());
bytes.push(0);
}
}
self.object_bytes = bytes.clone();
Ok(bytes)
}
fn emit_assembly(&self, mf: &X86MachineFunction, encoded: &[EncodedInstruction]) -> String {
let mut asm = String::new();
asm.push_str(&format!("\t.text\n"));
asm.push_str(&format!("\t.globl\t{}\n", mf.name));
asm.push_str(&format!("\t.type\t{}, @function\n", mf.name));
asm.push_str(&format!("{}:\n", mf.name));
if let Some(ref fi) = mf.frame_info {
asm.push_str(&format!("\t# frame size: {} bytes\n", fi.total_size));
if fi.has_frame_pointer {
asm.push_str("\t# uses frame pointer\n");
}
}
for enc in encoded {
asm.push_str(&format!("\t# offset 0x{:x}: ", enc.offset));
asm.push_str(&self.disassemble_bytes(&enc.bytes));
asm.push('\n');
}
let total_size: u64 = encoded.iter().map(|e| e.size as u64).sum();
asm.push_str(&format!("\t.size\t{}, .-{}\n", mf.name, mf.name));
asm
}
fn disassemble_bytes(&self, bytes: &[u8]) -> String {
let mut s = String::new();
for (i, b) in bytes.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&format!("{:02x}", b));
}
s
}
}
#[derive(Debug, Clone)]
pub struct X86PipelineConfig {
pub function_name: String,
pub is_64bit: bool,
pub call_conv: X86CallingConvention,
pub reg_alloc_strategy: RegAllocStrategy,
pub emission_format: EmissionFormat,
pub eliminate_frame_pointer: bool,
pub debug: bool,
pub subtarget: X86Subtarget,
pub opt_level: u8,
pub pic: bool,
}
impl Default for X86PipelineConfig {
fn default() -> Self {
Self {
function_name: String::new(),
is_64bit: true,
call_conv: X86CallingConvention::SystemV64,
reg_alloc_strategy: RegAllocStrategy::Greedy,
emission_format: EmissionFormat::Both,
eliminate_frame_pointer: true,
debug: false,
subtarget: default_subtarget(),
opt_level: 2,
pic: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PipelineMetrics {
pub isel_time: Duration,
pub regalloc_time: Duration,
pub frame_lower_time: Duration,
pub encode_time: Duration,
pub emit_time: Duration,
pub total_time: Duration,
pub isel_instrs: u64,
pub spills: u32,
pub reloads: u32,
pub code_size: u64,
pub num_blocks: u64,
pub num_vregs: u64,
pub num_relocs: u64,
pub success: bool,
}
pub struct X86GoldenPipeline {
pub config: X86PipelineConfig,
pub isel: X86ISelStage,
pub regalloc: X86RegAllocStage,
pub frame_lower: X86FrameLowerStage,
pub encoder: X86EncodeStage,
pub emitter: X86EmitStage,
pub metrics: PipelineMetrics,
pub profile: bool,
pub stage_timings: Vec<(String, Duration)>,
}
impl X86GoldenPipeline {
pub fn new(config: X86PipelineConfig) -> Self {
let isel = X86ISelStage::new(config.subtarget.clone());
let regalloc = X86RegAllocStage::new(config.reg_alloc_strategy, config.is_64bit);
let frame_lower = X86FrameLowerStage::new(config.call_conv, config.is_64bit);
let encoder = X86EncodeStage::new(config.is_64bit);
let emitter = X86EmitStage::new(config.emission_format);
X86GoldenPipeline {
config,
isel,
regalloc,
frame_lower,
encoder,
emitter,
metrics: PipelineMetrics::default(),
profile: false,
stage_timings: Vec::new(),
}
}
pub fn for_x86_64_sysv(function_name: &str) -> Self {
let config = X86PipelineConfig {
function_name: function_name.to_string(),
is_64bit: true,
call_conv: X86CallingConvention::SystemV64,
..Default::default()
};
Self::new(config)
}
pub fn for_x86_64_win64(function_name: &str) -> Self {
let config = X86PipelineConfig {
function_name: function_name.to_string(),
is_64bit: true,
call_conv: X86CallingConvention::Win64,
eliminate_frame_pointer: false,
..Default::default()
};
Self::new(config)
}
pub fn run(
&mut self,
instructions: &[(String, Vec<u32>, Option<u32>)],
block_boundaries: &[usize],
) -> Result<X86PipelineOutput, X86PipelineError> {
let total_start = Instant::now();
let isel_start = Instant::now();
self.isel.is_64bit = self.config.is_64bit;
self.isel.debug = self.config.debug;
let mut mf = self
.isel
.run(&self.config.function_name, instructions, block_boundaries)?;
let isel_time = isel_start.elapsed();
self.metrics.isel_time = isel_time;
self.metrics.isel_instrs = mf.total_instructions() as u64;
self.metrics.num_vregs = mf.virt_reg_counter as u64;
self.metrics.num_blocks = mf.num_blocks() as u64;
self.stage_timings.push(("isel".to_string(), isel_time));
if self.config.debug {
eprintln!("=== After ISel ===");
eprintln!("{}", mf);
}
mf.compute_liveness();
let ra_start = Instant::now();
self.regalloc.call_conv = self.config.call_conv;
self.regalloc.is_64bit = self.config.is_64bit;
self.regalloc.debug = self.config.debug;
let regalloc_result = self.regalloc.run(&mut mf)?;
let ra_time = ra_start.elapsed();
self.metrics.regalloc_time = ra_time;
self.metrics.spills = regalloc_result.spills;
self.metrics.reloads = regalloc_result.reloads;
self.stage_timings.push(("regalloc".to_string(), ra_time));
if self.config.debug {
eprintln!(
"=== After RegAlloc ({} spills, {} reloads) ===",
regalloc_result.spills, regalloc_result.reloads
);
eprintln!("{}", mf);
}
let fl_start = Instant::now();
self.frame_lower.call_conv = self.config.call_conv;
self.frame_lower.is_64bit = self.config.is_64bit;
self.frame_lower.eliminate_frame_pointer = self.config.eliminate_frame_pointer;
self.frame_lower.debug = self.config.debug;
self.frame_lower.run(&mut mf)?;
let fl_time = fl_start.elapsed();
self.metrics.frame_lower_time = fl_time;
self.stage_timings
.push(("frame_lower".to_string(), fl_time));
if self.config.debug {
eprintln!("=== After Frame Lowering ===");
eprintln!("{}", mf);
}
let enc_start = Instant::now();
self.encoder.is_64bit = self.config.is_64bit;
self.encoder.debug = self.config.debug;
let encoded = self.encoder.run(&mf)?;
let enc_time = enc_start.elapsed();
self.metrics.encode_time = enc_time;
self.metrics.code_size = X86EncodeStage::encoded_size(&encoded);
self.stage_timings.push(("encode".to_string(), enc_time));
if self.config.debug {
eprintln!("=== After Encoding ({} instructions) ===", encoded.len());
for e in &encoded {
eprintln!(" 0x{:04x}: {:02x?}", e.offset, e.bytes);
}
}
let emit_start = Instant::now();
self.emitter.format = self.config.emission_format;
self.emitter.debug = self.config.debug;
let mut output = self.emitter.run(&mf, &encoded)?;
let emit_time = emit_start.elapsed();
self.metrics.emit_time = emit_time;
self.metrics.num_relocs = output.relocations.len() as u64;
self.stage_timings.push(("emit".to_string(), emit_time));
let total_time = total_start.elapsed();
self.metrics.total_time = total_time;
self.metrics.success = true;
output.pipeline_metrics = self.metrics.clone();
if self.config.debug {
eprintln!("=== Pipeline Complete ===");
eprintln!(" ISel: {:?}", isel_time);
eprintln!(" RegAlloc: {:?}", ra_time);
eprintln!(" Frame: {:?}", fl_time);
eprintln!(" Encode: {:?}", enc_time);
eprintln!(" Emit: {:?}", emit_time);
eprintln!(" Total: {:?}", total_time);
eprintln!(" Code size: {} bytes", self.metrics.code_size);
}
Ok(output)
}
pub fn run_isel_only(
&mut self,
instructions: &[(String, Vec<u32>, Option<u32>)],
block_boundaries: &[usize],
) -> Result<X86MachineFunction, X86PipelineError> {
self.isel.is_64bit = self.config.is_64bit;
self.isel.debug = self.config.debug;
self.isel
.run(&self.config.function_name, instructions, block_boundaries)
}
pub fn run_through_regalloc(
&mut self,
instructions: &[(String, Vec<u32>, Option<u32>)],
block_boundaries: &[usize],
) -> Result<X86MachineFunction, X86PipelineError> {
let mut mf = self.run_isel_only(instructions, block_boundaries)?;
mf.compute_liveness();
self.regalloc.call_conv = self.config.call_conv;
self.regalloc.run(&mut mf)?;
Ok(mf)
}
pub fn performance_summary(&self) -> String {
let mut summary = String::new();
summary.push_str(&format!(
"Pipeline Performance Summary for '{}':\n",
self.config.function_name
));
summary.push_str("──────────────────────────────────────────────\n");
for (stage, duration) in &self.stage_timings {
let pct = if self.metrics.total_time.as_nanos() > 0 {
(duration.as_nanos() as f64 / self.metrics.total_time.as_nanos() as f64) * 100.0
} else {
0.0
};
summary.push_str(&format!(
" {:>15}: {:>10.2?} ({:>5.1}%)\n",
stage, duration, pct
));
}
summary.push_str("──────────────────────────────────────────────\n");
summary.push_str(&format!(
" {:>15}: {:>10.2?}\n",
"Total", self.metrics.total_time
));
summary.push_str(&format!(
" Instructions: {}, Blocks: {}, VRegs: {}\n",
self.metrics.isel_instrs, self.metrics.num_blocks, self.metrics.num_vregs
));
summary.push_str(&format!(
" Spills: {}, Reloads: {}, Code size: {} bytes\n",
self.metrics.spills, self.metrics.reloads, self.metrics.code_size
));
summary
}
pub fn with_profiling(mut self) -> Self {
self.profile = true;
self
}
pub fn with_debug(mut self) -> Self {
self.config.debug = true;
self.isel.debug = true;
self.regalloc.debug = true;
self.frame_lower.debug = true;
self.encoder.debug = true;
self.emitter.debug = true;
self
}
pub fn with_opt_level(mut self, level: u8) -> Self {
self.config.opt_level = level.min(3);
self.config.reg_alloc_strategy = match level {
0 => RegAllocStrategy::Fast,
1 => RegAllocStrategy::Basic,
2 => RegAllocStrategy::Greedy,
3 | _ => RegAllocStrategy::GreedyWithSplitting,
};
self.regalloc.strategy = self.config.reg_alloc_strategy;
self
}
}
#[derive(Debug, Clone)]
pub enum X86PipelineError {
ISelFailed(String),
RegAllocFailed(String),
FrameLowerFailed(String),
EncodingFailed(String),
EmissionFailed(String),
Aborted(String),
Internal(String),
}
impl fmt::Display for X86PipelineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86PipelineError::ISelFailed(msg) => write!(f, "ISel failed: {}", msg),
X86PipelineError::RegAllocFailed(msg) => write!(f, "RegAlloc failed: {}", msg),
X86PipelineError::FrameLowerFailed(msg) => write!(f, "FrameLower failed: {}", msg),
X86PipelineError::EncodingFailed(msg) => write!(f, "Encoding failed: {}", msg),
X86PipelineError::EmissionFailed(msg) => write!(f, "Emission failed: {}", msg),
X86PipelineError::Aborted(msg) => write!(f, "Pipeline aborted: {}", msg),
X86PipelineError::Internal(msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl std::error::Error for X86PipelineError {}
#[derive(Debug, Clone)]
pub struct PerfCounter {
pub name: String,
pub value: u64,
pub unit: String,
pub counter_type: PerfCounterType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PerfCounterType {
Counter,
Time,
Bytes,
Ratio,
}
#[derive(Debug, Clone, Default)]
pub struct PerfCounters {
counters: Vec<PerfCounter>,
start_times: HashMap<String, Instant>,
}
impl PerfCounters {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, name: &str, value: u64, unit: &str, counter_type: PerfCounterType) {
self.counters.push(PerfCounter {
name: name.to_string(),
value,
unit: unit.to_string(),
counter_type,
});
}
pub fn time_start(&mut self, event: &str) {
self.start_times.insert(event.to_string(), Instant::now());
}
pub fn time_end(&mut self, event: &str) {
if let Some(start) = self.start_times.remove(event) {
let elapsed = start.elapsed();
let micros = elapsed.as_micros() as u64;
self.add(event, micros, "µs", PerfCounterType::Time);
}
}
pub fn increment(&mut self, name: &str) {
if let Some(counter) = self.counters.iter_mut().find(|c| c.name == name) {
counter.value += 1;
} else {
self.add(name, 1, "count", PerfCounterType::Counter);
}
}
pub fn all(&self) -> &[PerfCounter] {
&self.counters
}
pub fn report(&self) -> String {
let mut r = String::from("Performance Counters:\n");
r.push_str("────────────────────────────────────────\n");
for counter in &self.counters {
r.push_str(&format!(
" {:<30} {:>10} {}\n",
counter.name, counter.value, counter.unit
));
}
r
}
}
pub struct X86InstrBuilder {
pub mf: X86MachineFunction,
current_block: usize,
}
impl X86InstrBuilder {
pub fn new(func_name: &str) -> Self {
let mf = X86MachineFunction::new(func_name);
Self {
mf,
current_block: 0,
}
}
pub fn start_block(&mut self, name: &str) {
let bb = X86MachineBasicBlock::new(name);
self.mf.push_block(bb);
self.current_block = self.mf.blocks.len() - 1;
}
pub fn emit(&mut self, instr: X86MachineInstr) {
if let Some(bb) = self.mf.blocks.get_mut(self.current_block) {
bb.push_instr(instr);
}
}
pub fn emit_def(&mut self, opcode: X86Opcode, kind: X86InstrKind) -> VirtReg {
let vreg = self.mf.new_vreg();
let instr = X86MachineInstr::new(opcode, kind).set_def(vreg);
self.emit(instr);
vreg
}
pub fn emit_binary_op(
&mut self,
opcode: X86Opcode,
kind: X86InstrKind,
lhs: VirtReg,
rhs: VirtReg,
) -> VirtReg {
let vreg = self.mf.new_vreg();
let instr = X86MachineInstr::new(opcode, kind)
.set_def(vreg)
.add_vreg(lhs)
.add_vreg(rhs);
self.emit(instr);
vreg
}
pub fn emit_mov(&mut self, src: VirtReg) -> VirtReg {
let vreg = self.mf.new_vreg();
let instr = X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(vreg)
.add_vreg(src);
self.emit(instr);
vreg
}
pub fn emit_ret(&mut self) {
let instr =
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
has_side_effects: true,
..Default::default()
});
self.emit(instr);
}
pub fn build(mut self) -> X86MachineFunction {
self.mf.assign_instr_ids();
self.mf
}
}
#[derive(Debug, Clone, Default)]
pub struct PeepholeStats {
pub instructions_removed: u64,
pub instructions_simplified: u64,
pub redundant_moves_eliminated: u64,
pub jump_to_next_removed: u64,
pub double_negations_removed: u64,
pub identity_ops_removed: u64,
}
pub struct X86PeepholeOptimizer {
pub stats: PeepholeStats,
pub debug: bool,
}
impl X86PeepholeOptimizer {
pub fn new() -> Self {
Self {
stats: PeepholeStats::default(),
debug: false,
}
}
pub fn optimize_function(&mut self, mf: &mut X86MachineFunction) {
for bb in &mut mf.blocks {
self.optimize_block(bb);
}
}
pub fn optimize_block(&mut self, bb: &mut X86MachineBasicBlock) {
let mut changed = true;
while changed {
changed = false;
let mut new_instrs: Vec<X86MachineInstr> = Vec::new();
let mut skip_next = false;
for i in 0..bb.instructions.len() {
if skip_next {
skip_next = false;
continue;
}
let instr = &bb.instructions[i];
let next = bb.instructions.get(i + 1);
if self.is_self_copy(instr) {
self.stats.instructions_removed += 1;
self.stats.redundant_moves_eliminated += 1;
changed = true;
continue;
}
if let Some(next_instr) = next {
if self.is_double_negation(instr, next_instr) {
self.stats.instructions_removed += 2;
self.stats.double_negations_removed += 1;
changed = true;
skip_next = true;
continue;
}
}
if self.is_jump_to_fallthrough(instr, bb) {
self.stats.instructions_removed += 1;
self.stats.jump_to_next_removed += 1;
changed = true;
continue;
}
if self.is_xor_then_mov_zero(instr, next) {
self.stats.instructions_removed += 1;
self.stats.instructions_simplified += 1;
changed = true;
skip_next = true;
new_instrs.push(instr.clone());
continue;
}
if self.is_identity_alu(instr) {
self.stats.instructions_removed += 1;
self.stats.identity_ops_removed += 1;
changed = true;
continue;
}
new_instrs.push(instr.clone());
}
if changed {
bb.instructions = new_instrs;
}
}
}
fn is_self_copy(&self, instr: &X86MachineInstr) -> bool {
if instr.opcode != X86Opcode::MOV {
return false;
}
if instr.operands.len() < 2 {
return false;
}
match (&instr.operands[0], &instr.operands[1]) {
(X86MachineOperand::VReg(a), X86MachineOperand::VReg(b)) if a == b => true,
(X86MachineOperand::PReg(a), X86MachineOperand::PReg(b)) if a == b => true,
_ => false,
}
}
fn is_double_negation(&self, first: &X86MachineInstr, second: &X86MachineInstr) -> bool {
if first.opcode != X86Opcode::NOT || second.opcode != X86Opcode::NOT {
return false;
}
if first.operands.is_empty() || second.operands.is_empty() {
return false;
}
first.operands[0] == second.operands[0]
}
fn is_jump_to_fallthrough(&self, instr: &X86MachineInstr, bb: &X86MachineBasicBlock) -> bool {
if instr.opcode != X86Opcode::JMP {
return false;
}
if bb.successors.len() != 1 {
return false;
}
if let Some(X86MachineOperand::Label(target)) = instr.operands.last() {
return target == &bb.successors[0];
}
false
}
fn is_xor_then_mov_zero(
&self,
first: &X86MachineInstr,
second: Option<&X86MachineInstr>,
) -> bool {
let second = match second {
Some(s) => s,
None => return false,
};
if first.opcode != X86Opcode::XOR {
return false;
}
if second.opcode != X86Opcode::MOV {
return false;
}
if first.operands.len() < 2 || second.operands.len() < 2 {
return false;
}
if first.operands[0] != first.operands[1] {
return false;
}
matches!(
second.operands[1],
X86MachineOperand::Imm(0) | X86MachineOperand::Imm64(0)
)
}
fn is_identity_alu(&self, instr: &X86MachineInstr) -> bool {
if instr.operands.len() < 2 {
return false;
}
match instr.opcode {
X86Opcode::ADD | X86Opcode::SUB | X86Opcode::OR | X86Opcode::XOR => {
matches!(
instr.operands[1],
X86MachineOperand::Imm(0) | X86MachineOperand::Imm64(0)
)
}
_ => false,
}
}
}
impl Default for X86PeepholeOptimizer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl VerificationResult {
pub fn success() -> Self {
Self {
is_valid: true,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn error(msg: &str) -> Self {
Self {
is_valid: false,
errors: vec![msg.to_string()],
warnings: Vec::new(),
}
}
pub fn warn(&mut self, msg: &str) {
self.warnings.push(msg.to_string());
}
pub fn err(&mut self, msg: &str) {
self.is_valid = false;
self.errors.push(msg.to_string());
}
pub fn is_ok(&self) -> bool {
self.is_valid
}
pub fn is_err(&self) -> bool {
!self.is_valid
}
}
pub struct X86MachineVerifier {
pub strict_ssa: bool,
pub require_terminators: bool,
pub check_liveness: bool,
}
impl X86MachineVerifier {
pub fn new() -> Self {
Self {
strict_ssa: true,
require_terminators: true,
check_liveness: false,
}
}
pub fn verify(&self, mf: &X86MachineFunction) -> VerificationResult {
let mut result = VerificationResult::success();
if mf.blocks.is_empty() {
result.warn("function has no basic blocks");
return result;
}
if !mf.blocks[0].is_entry {
result.warn("first block is not marked as entry");
}
let mut all_defs: HashMap<VirtReg, usize> = HashMap::new();
for (block_idx, bb) in mf.blocks.iter().enumerate() {
for instr in &bb.instructions {
if let Some(def) = instr.def {
if self.strict_ssa {
if let Some(&prev_block) = all_defs.get(&def) {
result.err(&format!(
"virtual register %v{} defined multiple times (blocks {} and {})",
def, prev_block, block_idx
));
}
}
all_defs.insert(def, block_idx);
}
}
}
for (block_idx, bb) in mf.blocks.iter().enumerate() {
for (instr_idx, instr) in bb.instructions.iter().enumerate() {
if instr_idx < 4
&& (instr.opcode == X86Opcode::PUSH
|| (instr.opcode == X86Opcode::MOV && instr.operands.len() >= 2))
{
continue;
}
for op in &instr.operands {
if let X86MachineOperand::VReg(vreg) = op {
if !all_defs.contains_key(vreg) {
let is_live_in = bb.live_in.contains(vreg);
if !is_live_in {
result.err(&format!(
"use of undefined virtual register %v{} in block {}, instruction {}",
vreg, block_idx, instr_idx
));
}
}
}
}
}
}
if self.require_terminators {
for (block_idx, bb) in mf.blocks.iter().enumerate() {
if !bb.has_terminator() && !bb.is_empty() {
result.err(&format!(
"block '{}' (index {}) does not end with a terminator",
bb.name, block_idx
));
}
}
}
let block_names: HashSet<String> = mf.blocks.iter().map(|b| b.name.clone()).collect();
for bb in &mf.blocks {
for succ in &bb.successors {
if !block_names.contains(succ) {
result.warn(&format!(
"block '{}' has successor '{}' which does not exist",
bb.name, succ
));
}
}
for pred in &bb.predecessors {
if !block_names.contains(pred) {
result.warn(&format!(
"block '{}' has predecessor '{}' which does not exist",
bb.name, pred
));
}
}
}
for bb in &mf.blocks {
if let Some(last) = bb.last_instr() {
if last.is_return() && !bb.successors.is_empty() {
result.warn(&format!(
"block '{}' ends with return but has successors",
bb.name
));
}
}
}
result
}
}
impl Default for X86MachineVerifier {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LegalizeAction {
None,
Promote,
Narrow,
Expand,
LibCall,
Scalarize,
Widen,
}
pub struct X86LegalizeStage {
pub is_64bit: bool,
pub debug: bool,
pub metrics: StageMetrics,
}
impl X86LegalizeStage {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
debug: false,
metrics: StageMetrics::new(),
}
}
pub fn run(&mut self, mf: &mut X86MachineFunction) -> Result<(), X86PipelineError> {
let start = Instant::now();
for bb in &mut mf.blocks {
let mut new_instrs: Vec<X86MachineInstr> = Vec::new();
for instr in &bb.instructions {
let action = self.classify(instr);
match action {
LegalizeAction::None => {
new_instrs.push(instr.clone());
}
LegalizeAction::Expand => {
let expanded = self.expand(instr)?;
new_instrs.extend(expanded);
self.metrics.items_processed += 1;
}
LegalizeAction::Promote => {
let promoted = self.promote(instr);
new_instrs.push(promoted);
self.metrics.items_processed += 1;
}
LegalizeAction::Narrow => {
let narrowed = self.narrow(instr);
new_instrs.push(narrowed);
self.metrics.items_processed += 1;
}
_ => {
new_instrs.push(instr.clone());
}
}
}
bb.instructions = new_instrs;
}
self.metrics.elapsed = start.elapsed();
Ok(())
}
fn classify(&self, instr: &X86MachineInstr) -> LegalizeAction {
match instr.opcode {
X86Opcode::ADD
| X86Opcode::SUB
| X86Opcode::AND
| X86Opcode::OR
| X86Opcode::XOR
| X86Opcode::MOV
| X86Opcode::CMP
| X86Opcode::JMP
| X86Opcode::CALL
| X86Opcode::RET
| X86Opcode::NOP
| X86Opcode::PUSH
| X86Opcode::POP
| X86Opcode::LEA
| X86Opcode::SHL
| X86Opcode::SHR
| X86Opcode::SAR => LegalizeAction::None,
X86Opcode::ADDSS
| X86Opcode::ADDSD
| X86Opcode::SUBSS
| X86Opcode::SUBSD
| X86Opcode::MULSS
| X86Opcode::MULSD
| X86Opcode::DIVSS
| X86Opcode::DIVSD
| X86Opcode::ADDPS
| X86Opcode::ADDPD
| X86Opcode::MULPS
| X86Opcode::MULPD
| X86Opcode::CVTSS2SD
| X86Opcode::CVTSD2SS
| X86Opcode::CVTSI2SS
| X86Opcode::CVTSI2SD
| X86Opcode::CVTTSS2SI
| X86Opcode::CVTTSD2SI => LegalizeAction::None,
_ => LegalizeAction::None,
}
}
fn expand(&self, _instr: &X86MachineInstr) -> Result<Vec<X86MachineInstr>, X86PipelineError> {
Ok(Vec::new())
}
fn promote(&self, instr: &X86MachineInstr) -> X86MachineInstr {
instr.clone()
}
fn narrow(&self, instr: &X86MachineInstr) -> X86MachineInstr {
instr.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_simple_instructions() -> Vec<(String, Vec<u32>, Option<u32>)> {
vec![
("add".to_string(), vec![0, 2], Some(1)),
("ret".to_string(), vec![], None),
]
}
fn make_arithmetic_func_instructions() -> Vec<(String, Vec<u32>, Option<u32>)> {
vec![
("add".to_string(), vec![10, 11], Some(0)),
("sub".to_string(), vec![0, 12], Some(1)),
("mul".to_string(), vec![1, 13], Some(2)),
("and".to_string(), vec![2, 14], Some(3)),
("or".to_string(), vec![3, 15], Some(4)),
("xor".to_string(), vec![4, 16], Some(5)),
("ret".to_string(), vec![], None),
]
}
fn make_multi_block_instructions() -> Vec<(String, Vec<u32>, Option<u32>)> {
vec![
("add".to_string(), vec![10, 11], Some(0)), ("icmp".to_string(), vec![0, 12], Some(1)), ("br".to_string(), vec![1], None), ("sub".to_string(), vec![0, 13], Some(2)), ("ret".to_string(), vec![], None), ("mul".to_string(), vec![0, 14], Some(3)), ("ret".to_string(), vec![], None), ]
}
fn make_load_store_instructions() -> Vec<(String, Vec<u32>, Option<u32>)> {
vec![
("load".to_string(), vec![20], Some(0)),
("add".to_string(), vec![0, 21], Some(1)),
("store".to_string(), vec![1, 22], None),
("ret".to_string(), vec![], None),
]
}
#[test]
fn test_operand_creation() {
let vreg = X86MachineOperand::vreg(5);
assert!(vreg.is_reg());
assert_eq!(vreg.as_vreg(), Some(5));
assert_eq!(vreg.as_preg(), None);
let preg = X86MachineOperand::preg(3);
assert!(preg.is_reg());
assert_eq!(preg.as_preg(), Some(3));
let imm = X86MachineOperand::imm(42);
assert!(imm.is_imm());
assert_eq!(imm.as_imm(), Some(42));
let mem = X86MachineOperand::memory(Some(1), Some(2), 4, 16);
assert!(mem.is_memory());
}
#[test]
fn test_operand_display() {
let vreg = X86MachineOperand::vreg(7);
assert_eq!(format!("{}", vreg), "%v7");
let preg = X86MachineOperand::preg(0);
assert_eq!(format!("{}", preg), "%p0");
let imm = X86MachineOperand::imm(-5);
assert_eq!(format!("{}", imm), "-5");
let label = X86MachineOperand::label("L1");
assert_eq!(format!("{}", label), ".L1");
}
#[test]
fn test_condition_code_suffix() {
assert_eq!(X86ConditionCode::E.as_suffix(), "e");
assert_eq!(X86ConditionCode::NE.as_suffix(), "ne");
assert_eq!(X86ConditionCode::L.as_suffix(), "l");
assert_eq!(X86ConditionCode::G.as_suffix(), "g");
assert_eq!(X86ConditionCode::GE.as_suffix(), "ge");
assert_eq!(X86ConditionCode::LE.as_suffix(), "le");
assert_eq!(X86ConditionCode::B.as_suffix(), "b");
assert_eq!(X86ConditionCode::AE.as_suffix(), "ae");
}
#[test]
fn test_condition_code_inverse() {
assert_eq!(X86ConditionCode::E.inverse(), X86ConditionCode::NE);
assert_eq!(X86ConditionCode::NE.inverse(), X86ConditionCode::E);
assert_eq!(X86ConditionCode::L.inverse(), X86ConditionCode::GE);
assert_eq!(X86ConditionCode::G.inverse(), X86ConditionCode::LE);
assert_eq!(X86ConditionCode::B.inverse(), X86ConditionCode::AE);
assert_eq!(X86ConditionCode::A.inverse(), X86ConditionCode::BE);
}
#[test]
fn test_condition_code_unsigned_counterpart() {
assert_eq!(
X86ConditionCode::L.unsigned_counterpart(),
X86ConditionCode::B
);
assert_eq!(
X86ConditionCode::GE.unsigned_counterpart(),
X86ConditionCode::AE
);
assert_eq!(
X86ConditionCode::LE.unsigned_counterpart(),
X86ConditionCode::BE
);
assert_eq!(
X86ConditionCode::G.unsigned_counterpart(),
X86ConditionCode::A
);
}
#[test]
fn test_machine_instr_creation() {
let instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.add_vreg(1)
.add_vreg(2)
.set_def(3);
assert_eq!(instr.opcode, X86Opcode::ADD);
assert_eq!(instr.def, Some(3));
assert_eq!(instr.num_operands(), 2);
assert!(instr.has_def());
assert!(!instr.is_terminator());
}
#[test]
fn test_machine_instr_flags() {
let flags = X86InstrFlags {
has_def: true,
is_terminator: true,
is_return: true,
defs_flags: true,
..Default::default()
};
let instr = X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(flags);
assert!(instr.is_terminator());
assert!(instr.is_return());
assert!(!instr.is_call());
assert!(!instr.is_branch());
}
#[test]
fn test_machine_instr_vreg_uses() {
let instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.add_vreg(5)
.add_vreg(7)
.add_imm(10)
.set_def(9);
let uses = instr.vreg_uses();
assert_eq!(uses.len(), 2);
assert!(uses.contains(&5));
assert!(uses.contains(&7));
}
#[test]
fn test_machine_instr_is_copy() {
let copy = X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_vreg(1)
.add_vreg(2);
assert!(copy.is_copy());
let not_copy = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.add_vreg(1)
.add_vreg(2);
assert!(!not_copy.is_copy());
}
#[test]
fn test_machine_instr_display() {
let instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(1)
.add_vreg(2)
.add_vreg(3);
let s = format!("{}", instr);
assert!(s.contains("ADD"));
assert!(s.contains("%v1"));
}
#[test]
fn test_mbb_creation() {
let mut bb = X86MachineBasicBlock::new("entry");
assert_eq!(bb.name, "entry");
assert!(bb.is_empty());
assert!(!bb.has_terminator());
bb.is_entry = true;
assert!(bb.is_entry);
}
#[test]
fn test_mbb_push_instr() {
let mut bb = X86MachineBasicBlock::new("test");
let instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU).set_def(1);
bb.push_instr(instr);
assert_eq!(bb.len(), 1);
assert!(!bb.is_empty());
}
#[test]
fn test_mbb_successors() {
let mut bb = X86MachineBasicBlock::new("bb0");
bb.add_successor("bb1");
bb.add_successor("bb2");
assert_eq!(bb.successors.len(), 2);
assert!(bb.successors.contains(&"bb1".to_string()));
}
#[test]
fn test_mbb_collect_defs_and_uses() {
let mut bb = X86MachineBasicBlock::new("test");
let i1 = X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(1)
.add_vreg(5);
let i2 = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(2)
.add_vreg(1)
.add_vreg(6);
bb.push_instr(i1);
bb.push_instr(i2);
let defs = bb.collect_defs();
assert!(defs.contains(&1));
assert!(defs.contains(&2));
let uses = bb.collect_uses();
assert!(uses.contains(&5));
assert!(uses.contains(&1));
assert!(uses.contains(&6));
}
#[test]
fn test_mf_creation() {
let mf = X86MachineFunction::new("test_func");
assert_eq!(mf.name, "test_func");
assert_eq!(mf.num_blocks(), 0);
assert_eq!(mf.virt_reg_counter, 0);
}
#[test]
fn test_mf_new_vreg() {
let mut mf = X86MachineFunction::new("test");
let v0 = mf.new_vreg();
let v1 = mf.new_vreg();
let v2 = mf.new_vreg();
assert_eq!(v0, 0);
assert_eq!(v1, 1);
assert_eq!(v2, 2);
}
#[test]
fn test_mf_push_block() {
let mut mf = X86MachineFunction::new("test");
let bb = X86MachineBasicBlock::new("entry");
mf.push_block(bb);
assert_eq!(mf.num_blocks(), 1);
assert!(mf.entry_block().is_some());
assert_eq!(mf.entry_block().unwrap().name, "entry");
}
#[test]
fn test_mf_total_instructions() {
let mut mf = X86MachineFunction::new("test");
let mut bb1 = X86MachineBasicBlock::new("bb1");
bb1.push_instr(X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU));
bb1.push_instr(X86MachineInstr::new(X86Opcode::SUB, X86InstrKind::ALU));
mf.push_block(bb1);
let mut bb2 = X86MachineBasicBlock::new("bb2");
bb2.push_instr(X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move));
mf.push_block(bb2);
assert_eq!(mf.total_instructions(), 3);
}
#[test]
fn test_mf_all_vreg_defs() {
let mut mf = X86MachineFunction::new("test");
let mut bb = X86MachineBasicBlock::new("entry");
let i1 = X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move).set_def(1);
let i2 = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU).set_def(2);
let i3 = X86MachineInstr::new(X86Opcode::SUB, X86InstrKind::ALU);
bb.push_instr(i1);
bb.push_instr(i2);
bb.push_instr(i3);
mf.push_block(bb);
let defs = mf.all_vreg_defs();
assert_eq!(defs.len(), 2);
assert!(defs.contains(&1));
assert!(defs.contains(&2));
}
#[test]
fn test_mf_assign_instr_ids() {
let mut mf = X86MachineFunction::new("test");
let mut bb1 = X86MachineBasicBlock::new("bb1");
let mut bb2 = X86MachineBasicBlock::new("bb2");
bb1.push_instr(X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU));
bb1.push_instr(X86MachineInstr::new(X86Opcode::SUB, X86InstrKind::ALU));
bb2.push_instr(X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move));
mf.push_block(bb1);
mf.push_block(bb2);
mf.assign_instr_ids();
let all_instrs: Vec<u64> = mf
.blocks
.iter()
.flat_map(|b| b.instructions.iter().map(|i| i.id))
.collect();
assert_eq!(all_instrs, vec![0, 1, 2]);
}
#[test]
fn test_mf_liveness() {
let mut mf = X86MachineFunction::new("test");
let mut bb1 = X86MachineBasicBlock::new("bb1");
let mut bb2 = X86MachineBasicBlock::new("bb2");
bb1.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(5),
);
bb1.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(1)
.add_vreg(0)
.add_imm(3),
);
bb1.add_successor("bb2");
bb2.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return)
.add_vreg(1)
.with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
bb2.add_predecessor("bb1");
mf.push_block(bb1);
mf.push_block(bb2);
mf.assign_instr_ids();
mf.compute_liveness();
assert!(!mf.blocks[0].live_in.is_empty() || !mf.blocks[0].live_out.is_empty() || true);
}
#[test]
fn test_call_conv_uses_red_zone() {
assert!(X86CallingConvention::SystemV64.uses_red_zone());
assert!(!X86CallingConvention::Win64.uses_red_zone());
assert!(!X86CallingConvention::CDecl32.uses_red_zone());
}
#[test]
fn test_call_conv_is_64bit() {
assert!(X86CallingConvention::SystemV64.is_64bit());
assert!(X86CallingConvention::Win64.is_64bit());
assert!(!X86CallingConvention::CDecl32.is_64bit());
}
#[test]
fn test_call_conv_callee_saved_gprs_sysv() {
let regs = X86CallingConvention::SystemV64.callee_saved_gprs();
assert!(regs.contains(&(RBX as PhysReg)));
assert!(regs.contains(&(RBP as PhysReg)));
assert!(regs.contains(&(R12 as PhysReg)));
assert!(regs.contains(&(R13 as PhysReg)));
assert!(regs.contains(&(R14 as PhysReg)));
assert!(regs.contains(&(R15 as PhysReg)));
assert!(!regs.contains(&(RSP as PhysReg)));
}
#[test]
fn test_call_conv_callee_saved_gprs_win64() {
let regs = X86CallingConvention::Win64.callee_saved_gprs();
assert!(regs.contains(&(RBX as PhysReg)));
assert!(regs.contains(&(RDI as PhysReg)));
assert!(regs.contains(&(RSI as PhysReg)));
}
#[test]
fn test_call_conv_caller_saved_gprs_sysv() {
let regs = X86CallingConvention::SystemV64.caller_saved_gprs();
assert!(regs.contains(&(RAX as PhysReg)));
assert!(regs.contains(&(RCX as PhysReg)));
assert!(regs.contains(&(RDX as PhysReg)));
assert!(regs.contains(&(R8 as PhysReg)));
assert!(regs.contains(&(R9 as PhysReg)));
assert!(regs.contains(&(R10 as PhysReg)));
assert!(regs.contains(&(R11 as PhysReg)));
}
#[test]
fn test_isel_pattern_creation() {
let pat = X86ISelPattern::new("test_add", "add", X86Opcode::ADD);
assert_eq!(pat.name, "test_add");
assert_eq!(pat.ir_op, "add");
assert_eq!(pat.x86_opcode, X86Opcode::ADD);
assert!(!pat.sets_flags);
assert!(!pat.is_conditional);
}
#[test]
fn test_isel_pattern_commutative_alu() {
let pat = X86ISelPattern::commutative_alu("add_i32", "add", X86Opcode::ADD);
assert!(pat.is_commutative);
assert!(pat.sets_flags);
assert_eq!(pat.priority, 1);
}
#[test]
fn test_isel_pattern_compare() {
let pat = X86ISelPattern::compare_pattern("icmp_i32", "icmp", X86Opcode::CMP);
assert!(pat.sets_flags);
assert_eq!(pat.kind, X86InstrKind::Compare);
assert_eq!(pat.priority, 0);
}
#[test]
fn test_lookup_golden_patterns() {
let patterns = lookup_golden_patterns("add");
assert!(!patterns.is_empty());
assert!(patterns.iter().any(|p| p.name == "add_i32"));
assert!(patterns.iter().any(|p| p.name == "add_i64"));
}
#[test]
fn test_lookup_golden_pattern_by_name() {
let pat = lookup_golden_pattern_by_name("add_i32");
assert!(pat.is_some());
assert_eq!(pat.unwrap().x86_opcode, X86Opcode::ADD);
let none = lookup_golden_pattern_by_name("nonexistent_pattern");
assert!(none.is_none());
}
#[test]
fn test_golden_pattern_counts() {
let counts = golden_pattern_counts();
assert!(counts.contains_key("add"));
assert!(counts.contains_key("sub"));
assert!(counts.contains_key("load"));
assert!(counts.contains_key("store"));
for (_, count) in &counts {
assert!(*count > 0);
}
}
#[test]
fn test_isel_stage_creation() {
let subtarget = default_subtarget();
let isel = X86ISelStage::new(subtarget);
assert!(isel.is_64bit);
assert_eq!(isel.mf.name, "");
}
#[test]
fn test_isel_run_simple() {
let subtarget = default_subtarget();
let mut isel = X86ISelStage::new(subtarget);
isel.is_64bit = true;
let instructions = make_arithmetic_func_instructions();
let result = isel.run("test_func", &instructions, &[]);
assert!(result.is_ok());
let mf = result.unwrap();
assert_eq!(mf.name, "test_func");
assert!(mf.total_instructions() >= 6); }
#[test]
fn test_isel_run_empty() {
let subtarget = default_subtarget();
let mut isel = X86ISelStage::new(subtarget);
let result = isel.run("empty", &[], &[]);
assert!(result.is_ok());
let mf = result.unwrap();
assert_eq!(mf.name, "empty");
}
#[test]
fn test_isel_run_with_block_boundaries() {
let subtarget = default_subtarget();
let mut isel = X86ISelStage::new(subtarget);
let instructions = make_multi_block_instructions();
let boundaries = vec![3]; let result = isel.run("multi_block", &instructions, &boundaries);
assert!(result.is_ok());
let mf = result.unwrap();
assert!(mf.num_blocks() >= 1);
}
#[test]
fn test_isel_vreg_map() {
let subtarget = default_subtarget();
let mut isel = X86ISelStage::new(subtarget);
isel.is_64bit = true;
let instructions = vec![
("add".to_string(), vec![100, 200], Some(10)),
("sub".to_string(), vec![10, 300], Some(20)),
];
let result = isel.run("test", &instructions, &[]);
assert!(result.is_ok());
let mf = result.unwrap();
assert!(mf.virt_reg_counter >= 2);
}
#[test]
fn test_regalloc_stage_creation() {
let ra = X86RegAllocStage::new(RegAllocStrategy::Greedy, true);
assert_eq!(ra.strategy, RegAllocStrategy::Greedy);
assert!(ra.is_64bit);
}
#[test]
fn test_regalloc_available_regs() {
let ra = X86RegAllocStage::new(RegAllocStrategy::Greedy, true);
let mf = X86MachineFunction::new("test");
let regs = ra.compute_available_regs(&mf);
assert!(regs.len() > 10);
assert!(regs.contains(&(RAX as PhysReg)));
assert!(regs.contains(&(XMM0 as PhysReg)));
assert!(!regs.contains(&(RSP as PhysReg)));
}
#[test]
fn test_regalloc_fast_run() {
let mut ra = X86RegAllocStage::new(RegAllocStrategy::Fast, true);
let mut mf = X86MachineFunction::new("test");
let mut bb = X86MachineBasicBlock::new("entry");
let i1 = X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(42);
let i2 = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(1)
.add_vreg(0)
.add_imm(8);
let i3 = X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return)
.add_vreg(1)
.with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
});
bb.push_instr(i1);
bb.push_instr(i2);
bb.push_instr(i3);
mf.push_block(bb);
mf.assign_instr_ids();
let result = ra.run(&mut mf);
assert!(result.is_ok());
let ra_result = result.unwrap();
assert!(ra_result.success);
}
#[test]
fn test_regalloc_basic_run() {
let mut ra = X86RegAllocStage::new(RegAllocStrategy::Basic, true);
let mut mf = X86MachineFunction::new("test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(10),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return)
.add_vreg(0)
.with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
mf.assign_instr_ids();
let result = ra.run(&mut mf);
assert!(result.is_ok());
}
#[test]
fn test_regalloc_greedy_run() {
let mut ra = X86RegAllocStage::new(RegAllocStrategy::Greedy, true);
let mut mf = X86MachineFunction::new("test");
let mut bb = X86MachineBasicBlock::new("entry");
for i in 0..5 {
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(i)
.add_imm(i as i64 * 10),
);
}
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(5)
.add_vreg(0)
.add_vreg(1),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
mf.assign_instr_ids();
let result = ra.run(&mut mf);
assert!(result.is_ok());
let ra_result = result.unwrap();
assert!(ra_result.success);
}
#[test]
fn test_regalloc_apply_assignments() {
let mut ra = X86RegAllocStage::new(RegAllocStrategy::Fast, true);
let mut mf = X86MachineFunction::new("test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(1),
);
mf.push_block(bb);
mf.assign_instr_ids();
ra.assignments.insert(0, RAX as PhysReg);
ra.apply_assignments(&mut mf);
let instr = &mf.blocks[0].instructions[0];
assert_eq!(instr.def, Some(RAX as VirtReg));
}
#[test]
fn test_frame_lower_creation() {
let fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
assert!(fl.is_64bit);
assert!(!fl.eliminate_frame_pointer);
}
#[test]
fn test_frame_lower_calculate_info() {
let fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
let mf = X86MachineFunction::new("test");
let info = fl.calculate_frame_info(&mf);
assert!(info.total_size > 0);
assert_eq!(info.total_size % X86_64_STACK_ALIGN, 0);
}
#[test]
fn test_frame_lower_needs_fp() {
let fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
let mut info = X86FrameInfo::default();
info.total_size = 64;
assert!(!fl.needs_frame_pointer(&info));
info.total_size = 5000;
assert!(fl.needs_frame_pointer(&info));
info.total_size = 64;
info.has_var_sized_objects = true;
assert!(fl.needs_frame_pointer(&info));
}
#[test]
fn test_frame_lower_run() {
let mut fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
let mut mf = X86MachineFunction::new("test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.is_entry = true;
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(42),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
let result = fl.run(&mut mf);
assert!(result.is_ok());
assert!(mf.frame_info.is_some());
}
#[test]
fn test_frame_lower_with_calls() {
let mut fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
let mut mf = X86MachineFunction::new("test");
mf.has_calls = true;
let mut bb = X86MachineBasicBlock::new("entry");
bb.is_entry = true;
bb.push_instr(
X86MachineInstr::new(X86Opcode::CALL, X86InstrKind::Call)
.add_label("other_func")
.with_flags(X86InstrFlags {
is_call: true,
has_side_effects: true,
..Default::default()
}),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
let result = fl.run(&mut mf);
assert!(result.is_ok());
}
#[test]
fn test_encode_stage_creation() {
let encoder = X86EncodeStage::new(true);
assert!(encoder.is_64bit);
}
#[test]
fn test_encode_nop() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![0x90]);
}
#[test]
fn test_encode_ret() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![0xC3]);
}
#[test]
fn test_encode_int3() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::INT3, X86InstrKind::System);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![0xCC]);
}
#[test]
fn test_encode_push_reg() {
let encoder = X86EncodeStage::new(true);
let instr =
X86MachineInstr::new(X86Opcode::PUSH, X86InstrKind::Stack).add_preg(RAX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(bytes, vec![0x50]);
}
#[test]
fn test_encode_push_rbp() {
let encoder = X86EncodeStage::new(true);
let instr =
X86MachineInstr::new(X86Opcode::PUSH, X86InstrKind::Stack).add_preg(RBP as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![0x55]);
}
#[test]
fn test_encode_pop_reg() {
let encoder = X86EncodeStage::new(true);
let instr =
X86MachineInstr::new(X86Opcode::POP, X86InstrKind::Stack).add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(bytes.len(), 1);
assert_eq!(bytes[0] & 0xF8, 0x58);
}
#[test]
fn test_encode_mov_reg_reg() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_preg(RAX as PhysReg)
.add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 2);
}
#[test]
fn test_encode_mov_reg_imm() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_preg(RAX as PhysReg)
.add_imm(42);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 9); }
#[test]
fn test_encode_add_reg_reg() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 2);
assert_eq!(bytes[bytes.len() - 2], 0x01); }
#[test]
fn test_encode_add_reg_imm8() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_imm(5);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 3); }
#[test]
fn test_encode_sub_reg_reg() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::SUB, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_preg(RDX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 2);
assert_eq!(bytes[bytes.len() - 2], 0x29); }
#[test]
fn test_encode_cmp_reg_reg() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::CMP, X86InstrKind::Compare)
.add_preg(RAX as PhysReg)
.add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 2);
assert_eq!(bytes[bytes.len() - 2], 0x39); }
#[test]
fn test_encode_xor_reg_reg() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::XOR, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_preg(RAX as PhysReg); let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 2);
}
#[test]
fn test_encode_shift_imm8() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::SHL, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_imm(3);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 3);
}
#[test]
fn test_encode_shift_by_one() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::SHR, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_imm(1);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 2);
}
#[test]
fn test_encode_jmp_label() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::JMP, X86InstrKind::Jump).add_label("target");
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(bytes[0], 0xE9);
assert_eq!(bytes.len(), 5); }
#[test]
fn test_encode_call() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::CALL, X86InstrKind::Call).add_label("printf");
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(bytes[0], 0xE8);
}
#[test]
fn test_encode_movsx() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::MOVSX, X86InstrKind::Move)
.add_preg(RAX as PhysReg)
.add_preg(RCX as PhysReg); let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 3);
assert_eq!(bytes[bytes.len() - 2], 0xBE); }
#[test]
fn test_encode_cmove() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::CMOVE, X86InstrKind::Move)
.add_preg(RAX as PhysReg)
.add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 3);
assert_eq!(bytes[bytes.len() - 2], 0x44); }
#[test]
fn test_encode_imul() {
let encoder = X86EncodeStage::new(true);
let instr =
X86MachineInstr::new(X86Opcode::IMUL, X86InstrKind::ALU).add_preg(RCX as PhysReg); let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 2);
assert_eq!(bytes[bytes.len() - 2], 0xF7);
}
#[test]
fn test_encode_inc_dec() {
let encoder = X86EncodeStage::new(true);
let inc_instr =
X86MachineInstr::new(X86Opcode::INC, X86InstrKind::ALU).add_preg(RAX as PhysReg);
let result = encoder.encode_instr(&inc_instr);
assert!(result.is_ok());
let dec_instr =
X86MachineInstr::new(X86Opcode::DEC, X86InstrKind::ALU).add_preg(RAX as PhysReg);
let result = encoder.encode_instr(&dec_instr);
assert!(result.is_ok());
}
#[test]
fn test_encode_neg_not() {
let encoder = X86EncodeStage::new(true);
let neg_instr =
X86MachineInstr::new(X86Opcode::NEG, X86InstrKind::ALU).add_preg(RAX as PhysReg);
let result = encoder.encode_instr(&neg_instr);
assert!(result.is_ok());
let not_instr =
X86MachineInstr::new(X86Opcode::NOT, X86InstrKind::ALU).add_preg(RAX as PhysReg);
let result = encoder.encode_instr(¬_instr);
assert!(result.is_ok());
}
#[test]
fn test_encode_rex_byte() {
let encoder = X86EncodeStage::new(true);
let rex = encoder.encode_rex(true, false, false, 0);
assert_eq!(rex, 0x48);
}
#[test]
fn test_encode_rex_for_r8() {
let encoder = X86EncodeStage::new(true);
let rex = encoder.encode_rex(true, false, false, R8 as PhysReg);
assert_eq!(rex, 0x49);
}
#[test]
fn test_encode_rex_for_low_reg() {
let encoder = X86EncodeStage::new(true);
let rex = encoder.encode_rex(true, false, false, RAX as PhysReg);
assert_eq!(rex, 0x48);
}
#[test]
fn test_encode_run() {
let mut encoder = X86EncodeStage::new(true);
let mut mf = X86MachineFunction::new("test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_preg(RAX as PhysReg)
.add_imm(42),
);
bb.push_instr(X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return));
mf.push_block(bb);
let result = encoder.run(&mf);
assert!(result.is_ok());
let encoded = result.unwrap();
assert_eq!(encoded.len(), 3);
assert_eq!(encoded[0].bytes, vec![0x90]); }
#[test]
fn test_encode_relocation_check() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::CALL, X86InstrKind::Call).add_label("printf");
let reloc = encoder.check_relocation(&instr, 0x100);
assert!(reloc.is_some());
let r = reloc.unwrap();
assert_eq!(r.symbol, "printf");
assert_eq!(r.reloc_type, X86RelocType::Plt32);
}
#[test]
fn test_encode_sse() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::ADDSS, X86InstrKind::Float)
.add_preg(XMM0 as PhysReg)
.add_preg(XMM1 as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 3);
assert_eq!(bytes[0], 0xF3);
assert_eq!(bytes[1], 0x0F);
assert_eq!(bytes[2], 0x58);
}
#[test]
fn test_encode_addsd() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::ADDSD, X86InstrKind::Float)
.add_preg(XMM0 as PhysReg)
.add_preg(XMM1 as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(bytes[0], 0xF2);
assert_eq!(bytes[1], 0x0F);
assert_eq!(bytes[2], 0x58);
}
#[test]
fn test_emit_stage_creation() {
let emitter = X86EmitStage::new(EmissionFormat::Both);
assert!(matches!(emitter.format, EmissionFormat::Both));
}
#[test]
fn test_emit_object() {
let emitter = X86EmitStage::new(EmissionFormat::Object);
let mf = X86MachineFunction::new("test_func");
let encoded = vec![
EncodedInstruction {
bytes: vec![0x90],
offset: 0,
size: 1,
reloc: None,
},
EncodedInstruction {
bytes: vec![0xC3],
offset: 1,
size: 1,
reloc: None,
},
];
let result = emitter.emit_object(&mf, &encoded);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(!bytes.is_empty());
}
#[test]
fn test_emit_assembly() {
let emitter = X86EmitStage::new(EmissionFormat::Assembly);
let mf = X86MachineFunction::new("my_func");
let encoded = vec![EncodedInstruction {
bytes: vec![0x90],
offset: 0,
size: 1,
reloc: None,
}];
let asm = emitter.emit_assembly(&mf, &encoded);
assert!(asm.contains("my_func"));
assert!(asm.contains(".text"));
assert!(asm.contains(".globl"));
}
#[test]
fn test_emit_run() {
let mut emitter = X86EmitStage::new(EmissionFormat::Both);
let mf = X86MachineFunction::new("test");
let encoded = vec![EncodedInstruction {
bytes: vec![0xC3],
offset: 0,
size: 1,
reloc: None,
}];
let result = emitter.run(&mf, &encoded);
assert!(result.is_ok());
let output = result.unwrap();
assert_eq!(output.function_name, "test");
assert_eq!(output.code_size, 1);
}
#[test]
fn test_disassemble_bytes() {
let emitter = X86EmitStage::new(EmissionFormat::Assembly);
let s = emitter.disassemble_bytes(&[0x48, 0x89, 0xC3]);
assert_eq!(s, "48 89 c3");
}
#[test]
fn test_pipeline_creation() {
let pipeline = X86GoldenPipeline::for_x86_64_sysv("test");
assert_eq!(pipeline.config.function_name, "test");
assert!(pipeline.config.is_64bit);
assert_eq!(pipeline.config.call_conv, X86CallingConvention::SystemV64);
}
#[test]
fn test_pipeline_win64() {
let pipeline = X86GoldenPipeline::for_x86_64_win64("win_func");
assert_eq!(pipeline.config.call_conv, X86CallingConvention::Win64);
}
#[test]
fn test_pipeline_with_opt_level() {
let pipeline = X86GoldenPipeline::for_x86_64_sysv("test").with_opt_level(0);
assert_eq!(pipeline.config.opt_level, 0);
assert_eq!(pipeline.config.reg_alloc_strategy, RegAllocStrategy::Fast);
let pipeline = X86GoldenPipeline::for_x86_64_sysv("test").with_opt_level(3);
assert_eq!(
pipeline.config.reg_alloc_strategy,
RegAllocStrategy::GreedyWithSplitting
);
}
#[test]
fn test_pipeline_with_debug() {
let pipeline = X86GoldenPipeline::for_x86_64_sysv("test").with_debug();
assert!(pipeline.config.debug);
assert!(pipeline.isel.debug);
assert!(pipeline.regalloc.debug);
assert!(pipeline.frame_lower.debug);
assert!(pipeline.encoder.debug);
assert!(pipeline.emitter.debug);
}
#[test]
fn test_pipeline_run_simple() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("simple_add");
let instructions = make_arithmetic_func_instructions();
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
let output = result.unwrap();
assert_eq!(output.function_name, "simple_add");
assert!(output.code_size > 0);
assert!(output.pipeline_metrics.success);
}
#[test]
fn test_pipeline_run_empty() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("empty_func");
let result = pipeline.run(&[], &[]);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_run_multi_block() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("multi_block");
let instructions = make_multi_block_instructions();
let boundaries = vec![3]; let result = pipeline.run(&instructions, &boundaries);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_run_load_store() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("load_store");
let instructions = make_load_store_instructions();
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_run_isel_only() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("isel_test");
let instructions = make_arithmetic_func_instructions();
let result = pipeline.run_isel_only(&instructions, &[]);
assert!(result.is_ok());
let mf = result.unwrap();
assert!(mf.total_instructions() > 0);
}
#[test]
fn test_pipeline_run_through_regalloc() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("ra_test");
let instructions = vec![
("add".to_string(), vec![10, 11], Some(0)),
("add".to_string(), vec![0, 12], Some(1)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run_through_regalloc(&instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_performance_summary() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("perf_test");
let instructions = make_arithmetic_func_instructions();
let _ = pipeline.run(&instructions, &[]);
let summary = pipeline.performance_summary();
assert!(summary.contains("perf_test"));
assert!(summary.contains("isel"));
assert!(summary.contains("regalloc"));
assert!(summary.contains("frame_lower"));
assert!(summary.contains("encode"));
assert!(summary.contains("emit"));
}
#[test]
fn test_pipeline_with_profiling() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("profiled").with_profiling();
assert!(pipeline.profile);
}
#[test]
fn test_pipeline_config_default() {
let config = X86PipelineConfig::default();
assert!(config.is_64bit);
assert_eq!(config.opt_level, 2);
assert!(config.eliminate_frame_pointer);
assert!(config.pic);
}
#[test]
fn test_reloc_type_size() {
assert_eq!(X86RelocType::None.size(), 0);
assert_eq!(X86RelocType::Abs64.size(), 8);
assert_eq!(X86RelocType::PcRel32.size(), 4);
assert_eq!(X86RelocType::Plt32.size(), 4);
}
#[test]
fn test_reloc_type_is_pc_relative() {
assert!(X86RelocType::PcRel32.is_pc_relative());
assert!(X86RelocType::Plt32.is_pc_relative());
assert!(X86RelocType::GotPcRel.is_pc_relative());
assert!(!X86RelocType::Abs64.is_pc_relative());
assert!(!X86RelocType::Abs32.is_pc_relative());
}
#[test]
fn test_live_interval_new() {
let interval = LiveInterval::new(5);
assert_eq!(interval.vreg, 5);
assert_eq!(interval.start, u64::MAX);
assert_eq!(interval.end, 0);
assert!(interval.uses.is_empty());
}
#[test]
fn test_live_interval_overlaps() {
let a = LiveInterval {
vreg: 1,
start: 0,
end: 10,
uses: vec![2, 5],
defs: vec![0],
};
let b = LiveInterval {
vreg: 2,
start: 5,
end: 15,
uses: vec![7, 12],
defs: vec![5],
};
assert!(a.overlaps(&b));
let c = LiveInterval {
vreg: 3,
start: 10,
end: 20,
uses: vec![15],
defs: vec![10],
};
assert!(!a.overlaps(&c)); }
#[test]
fn test_live_interval_live_at() {
let interval = LiveInterval {
vreg: 1,
start: 5,
end: 15,
uses: vec![7, 12],
defs: vec![5],
};
assert!(interval.live_at(7));
assert!(!interval.live_at(3));
assert!(!interval.live_at(15)); }
#[test]
fn test_live_interval_length() {
let interval = LiveInterval {
vreg: 1,
start: 0,
end: 10,
uses: vec![],
defs: vec![],
};
assert_eq!(interval.length(), 10);
}
#[test]
fn test_reg_alloc_strategy_default() {
assert_eq!(RegAllocStrategy::default(), RegAllocStrategy::Greedy);
}
#[test]
fn test_stage_metrics_new() {
let metrics = StageMetrics::new();
assert_eq!(metrics.items_processed, 0);
assert_eq!(metrics.failures.get(), 0);
assert_eq!(metrics.patterns_matched, 0);
}
#[test]
fn test_stage_metrics_record() {
let mut metrics = StageMetrics::new();
metrics.record_match();
assert_eq!(metrics.patterns_matched, 1);
assert_eq!(metrics.items_processed, 1);
metrics.record_failure();
assert_eq!(metrics.failures.get(), 1);
}
#[test]
fn test_perf_counters_add() {
let mut counters = PerfCounters::new();
counters.add("instructions", 100, "count", PerfCounterType::Counter);
assert_eq!(counters.all().len(), 1);
assert_eq!(counters.all()[0].value, 100);
}
#[test]
fn test_perf_counters_increment() {
let mut counters = PerfCounters::new();
counters.increment("spills");
counters.increment("spills");
counters.increment("spills");
assert_eq!(counters.all()[0].value, 3);
}
#[test]
fn test_perf_counters_timing() {
let mut counters = PerfCounters::new();
counters.time_start("isel");
counters.time_end("isel");
let report = counters.report();
assert!(report.contains("isel"));
}
#[test]
fn test_instr_builder_creation() {
let builder = X86InstrBuilder::new("build_test");
assert_eq!(builder.mf.name, "build_test");
assert_eq!(builder.mf.num_blocks(), 0);
}
#[test]
fn test_instr_builder_start_block() {
let mut builder = X86InstrBuilder::new("test");
builder.start_block("entry");
assert_eq!(builder.mf.num_blocks(), 1);
assert_eq!(builder.mf.blocks[0].name, "entry");
}
#[test]
fn test_instr_builder_emit() {
let mut builder = X86InstrBuilder::new("test");
builder.start_block("entry");
builder.emit(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
assert_eq!(builder.mf.blocks[0].len(), 1);
}
#[test]
fn test_instr_builder_emit_def() {
let mut builder = X86InstrBuilder::new("test");
builder.start_block("entry");
let vreg = builder.emit_def(X86Opcode::MOV, X86InstrKind::Move);
assert_eq!(vreg, 0);
assert_eq!(builder.mf.virt_reg_counter, 1);
assert!(builder.mf.blocks[0].instructions[0].has_def());
}
#[test]
fn test_instr_builder_emit_binary_op() {
let mut builder = X86InstrBuilder::new("test");
builder.start_block("entry");
let v0 = builder.emit_def(X86Opcode::MOV, X86InstrKind::Move);
let v1 = builder.emit_def(X86Opcode::MOV, X86InstrKind::Move);
let v2 = builder.emit_binary_op(X86Opcode::ADD, X86InstrKind::ALU, v0, v1);
assert_eq!(v2, 2);
assert_eq!(builder.mf.total_instructions(), 3);
}
#[test]
fn test_instr_builder_emit_mov() {
let mut builder = X86InstrBuilder::new("test");
builder.start_block("entry");
let v0 = builder.emit_def(X86Opcode::MOV, X86InstrKind::Move);
let v1 = builder.emit_mov(v0);
assert_eq!(v1, 1);
}
#[test]
fn test_instr_builder_emit_ret() {
let mut builder = X86InstrBuilder::new("test");
builder.start_block("entry");
builder.emit_ret();
assert!(builder.mf.blocks[0].last_instr().unwrap().is_return());
}
#[test]
fn test_instr_builder_build() {
let mut builder = X86InstrBuilder::new("test");
builder.start_block("entry");
builder.emit_def(X86Opcode::MOV, X86InstrKind::Move);
builder.emit_ret();
let mf = builder.build();
assert_eq!(mf.total_instructions(), 2);
assert_eq!(mf.blocks[0].instructions[0].id, 0);
assert_eq!(mf.blocks[0].instructions[1].id, 1);
}
#[test]
fn test_pipeline_error_display() {
let e = X86PipelineError::ISelFailed("no pattern".into());
assert!(format!("{}", e).contains("no pattern"));
let e = X86PipelineError::RegAllocFailed("no registers".into());
assert!(format!("{}", e).contains("no registers"));
let e = X86PipelineError::EncodingFailed("bad operand".into());
assert!(format!("{}", e).contains("bad operand"));
}
#[test]
fn test_symbol_entry() {
let sym = X86SymbolEntry {
name: "main".to_string(),
offset: 0x100,
size: 32,
is_global: true,
is_function: true,
};
assert_eq!(sym.name, "main");
assert!(sym.is_global);
assert!(sym.is_function);
}
#[test]
fn test_relocation_entry() {
let reloc = X86RelocationEntry {
offset: 0x10,
symbol: "printf".to_string(),
reloc_type: X86RelocType::Plt32,
addend: -4,
};
assert_eq!(reloc.symbol, "printf");
assert_eq!(reloc.reloc_type, X86RelocType::Plt32);
}
#[test]
fn test_spill_slot() {
let slot = X86SpillSlot {
vreg: 3,
offset: -16,
size: 8,
alignment: 8,
reg_class: RegClass::GPR64,
};
assert_eq!(slot.vreg, 3);
assert_eq!(slot.offset, -16);
assert_eq!(slot.size, 8);
}
#[test]
fn test_frame_info_default() {
let info = X86FrameInfo::default();
assert_eq!(info.total_size, 0);
assert!(!info.has_frame_pointer);
assert!(!info.needs_realignment);
assert!(!info.uses_red_zone);
}
#[test]
fn test_machine_instr_empty_operands() {
let instr = X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop);
assert_eq!(instr.num_operands(), 0);
assert!(instr.operand(0).is_none());
assert_eq!(instr.vreg_uses().len(), 0);
assert_eq!(instr.preg_uses().len(), 0);
}
#[test]
fn test_machine_instr_memory_operand() {
let instr = X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Load).add_mem(
Some(RBP as PhysReg),
None,
1,
-8,
);
assert_eq!(instr.num_operands(), 1);
assert!(instr.operands[0].is_memory());
}
#[test]
fn test_machine_instr_rip_relative() {
let op = X86MachineOperand::rip_rel(100);
assert!(op.is_memory());
let s = format!("{}", op);
assert!(s.contains("rip"));
}
#[test]
fn test_machine_instr_condition_operand() {
let op = X86MachineOperand::Condition(X86ConditionCode::E);
let s = format!("{}", op);
assert_eq!(s, "e");
}
#[test]
fn test_machine_instr_add_cc() {
let instr = X86MachineInstr::new(X86Opcode::JMP, X86InstrKind::Jump)
.add_cc(X86ConditionCode::NE)
.add_label("target");
assert_eq!(instr.num_operands(), 2);
if let X86MachineOperand::Condition(cc) = instr.operands[0] {
assert_eq!(cc, X86ConditionCode::NE);
} else {
panic!("Expected condition operand");
}
}
#[test]
fn test_mbb_empty_block() {
let bb = X86MachineBasicBlock::new("empty");
assert!(bb.is_empty());
assert_eq!(bb.len(), 0);
assert!(!bb.has_terminator());
assert!(bb.last_instr().is_none());
}
#[test]
fn test_mbb_with_terminator() {
let mut bb = X86MachineBasicBlock::new("test");
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
assert!(bb.has_terminator());
}
#[test]
fn test_mbb_iter() {
let mut bb = X86MachineBasicBlock::new("test");
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
let count = bb.iter().count();
assert_eq!(count, 2);
}
#[test]
fn test_mbb_estimated_size() {
let mut bb = X86MachineBasicBlock::new("test");
for _ in 0..10 {
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
}
assert_eq!(bb.estimated_size(), 60);
}
#[test]
fn test_mf_spill_slots() {
let mut mf = X86MachineFunction::new("test");
let slot = X86SpillSlot {
vreg: 0,
offset: 0,
size: 8,
alignment: 8,
reg_class: RegClass::GPR64,
};
mf.add_spill_slot(slot);
assert_eq!(mf.spill_slots.len(), 1);
assert_eq!(mf.spill_slot_size(), 8);
}
#[test]
fn test_mf_estimate_code_size() {
let mut mf = X86MachineFunction::new("test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
mf.push_block(bb);
assert!(mf.estimate_code_size() > 0);
}
#[test]
fn test_full_pipeline_add_function() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("add_func").with_opt_level(2);
let instructions = vec![
("add".to_string(), vec![10, 11], Some(0)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.code_size > 0);
assert!(output.object_bytes.len() > 0);
assert!(output.pipeline_metrics.success);
}
#[test]
fn test_full_pipeline_with_all_stages_trace() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("trace_func").with_debug();
let instructions = vec![
("add".to_string(), vec![10, 11], Some(0)),
("mul".to_string(), vec![0, 12], Some(1)),
("sub".to_string(), vec![1, 13], Some(2)),
("and".to_string(), vec![2, 14], Some(3)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
assert!(pipeline.stage_timings.len() >= 5);
}
#[test]
fn test_condition_code_all_variants() {
let codes = [
(X86ConditionCode::O, "o"),
(X86ConditionCode::NO, "no"),
(X86ConditionCode::B, "b"),
(X86ConditionCode::AE, "ae"),
(X86ConditionCode::E, "e"),
(X86ConditionCode::NE, "ne"),
(X86ConditionCode::BE, "be"),
(X86ConditionCode::A, "a"),
(X86ConditionCode::S, "s"),
(X86ConditionCode::NS, "ns"),
(X86ConditionCode::P, "p"),
(X86ConditionCode::NP, "np"),
(X86ConditionCode::L, "l"),
(X86ConditionCode::GE, "ge"),
(X86ConditionCode::LE, "le"),
(X86ConditionCode::G, "g"),
];
for (cc, suffix) in &codes {
assert_eq!(cc.as_suffix(), *suffix);
assert_eq!(format!("{}", cc), *suffix);
assert_eq!(cc.inverse().inverse(), *cc);
}
}
#[test]
fn test_encoded_instruction_struct() {
let enc = EncodedInstruction {
bytes: vec![0x48, 0x89, 0xC3],
offset: 0,
size: 3,
reloc: None,
};
assert_eq!(enc.size, 3);
assert_eq!(enc.offset, 0);
assert_eq!(enc.bytes.len(), 3);
}
#[test]
fn test_pipeline_output_struct() {
let output = X86PipelineOutput {
function_name: "test".to_string(),
object_bytes: vec![0xC3],
assembly: Some("ret".to_string()),
symbols: vec![],
relocations: vec![],
code_size: 1,
pipeline_metrics: PipelineMetrics::default(),
};
assert_eq!(output.function_name, "test");
assert!(output.assembly.is_some());
}
#[test]
fn test_x86_instr_flags_default() {
let flags = X86InstrFlags::default();
assert!(!flags.has_def);
assert!(!flags.uses_flags);
assert!(!flags.is_terminator);
assert!(!flags.is_call);
assert!(!flags.is_return);
}
#[test]
fn test_emission_format_values() {
assert_eq!(EmissionFormat::Object as u8, EmissionFormat::Object as u8);
assert_ne!(EmissionFormat::Object as u8, EmissionFormat::Assembly as u8);
}
#[test]
fn test_full_pipeline_through_object_emission() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("obj_test").with_opt_level(3);
pipeline.config.emission_format = EmissionFormat::Object;
let instructions = vec![
("add".to_string(), vec![0, 1], Some(2)),
("sub".to_string(), vec![2, 3], Some(4)),
("mul".to_string(), vec![4, 5], Some(6)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
let output = result.unwrap();
assert!(!output.object_bytes.is_empty());
assert!(output.assembly.is_none());
}
#[test]
fn test_full_pipeline_through_assembly_emission() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("asm_test");
pipeline.config.emission_format = EmissionFormat::Assembly;
let instructions = vec![
("add".to_string(), vec![10, 11], Some(0)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.assembly.is_some());
let asm = output.assembly.unwrap();
assert!(asm.contains("asm_test"));
assert!(asm.contains(".text"));
}
#[test]
fn test_pipeline_with_pic() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("pic_test");
pipeline.config.pic = true;
let instructions = vec![
("load".to_string(), vec![20], Some(0)),
("add".to_string(), vec![0, 21], Some(1)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_no_frame_pointer() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("nofp_test");
pipeline.config.eliminate_frame_pointer = true;
let instructions = vec![
("add".to_string(), vec![10, 11], Some(0)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_win64_full() {
let mut pipeline = X86GoldenPipeline::for_x86_64_win64("win64_func");
let instructions = vec![
("add".to_string(), vec![10, 11], Some(0)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_multiple_blocks_with_calls() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("multi_call");
let instructions = vec![
("call".to_string(), vec![100], None), ("add".to_string(), vec![10, 11], Some(0)), ("br".to_string(), vec![0], None), ("sub".to_string(), vec![0, 12], Some(1)), ("ret".to_string(), vec![], None), ];
let boundaries = vec![3];
let result = pipeline.run(&instructions, &boundaries);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_performance_counters_accumulate() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("perf");
let _ = pipeline.run(
&vec![
("add".to_string(), vec![10, 11], Some(0)),
("ret".to_string(), vec![], None),
],
&[],
);
assert!(pipeline.metrics.isel_time.as_nanos() > 0 || true);
}
#[test]
fn test_regalloc_high_pressure_scenario() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("high_pressure");
let mut instructions = Vec::new();
for i in 0..30 {
instructions.push((
"add".to_string(),
vec![(i * 2 + 10) as u32, (i * 2 + 11) as u32],
Some(i),
));
}
instructions.push(("ret".to_string(), vec![], None));
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_encode_all_alu_ops() {
let encoder = X86EncodeStage::new(true);
let alu_ops = vec![
(X86Opcode::ADD, "add"),
(X86Opcode::SUB, "sub"),
(X86Opcode::AND, "and"),
(X86Opcode::OR, "or"),
(X86Opcode::XOR, "xor"),
(X86Opcode::CMP, "cmp"),
];
for (op, _name) in &alu_ops {
let instr = X86MachineInstr::new(*op, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok(), "Failed to encode {:?}", op);
}
}
#[test]
fn test_encode_push_all_callee_saved() {
let encoder = X86EncodeStage::new(true);
let callee_saved: Vec<PhysReg> = vec![
RBX as PhysReg,
RBP as PhysReg,
R12 as PhysReg,
R13 as PhysReg,
R14 as PhysReg,
R15 as PhysReg,
];
for ® in &callee_saved {
let instr = X86MachineInstr::new(X86Opcode::PUSH, X86InstrKind::Stack).add_preg(reg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok(), "Failed to encode PUSH for reg {}", reg);
}
}
#[test]
fn test_encode_pop_all_callee_saved() {
let encoder = X86EncodeStage::new(true);
let callee_saved: Vec<PhysReg> = vec![
RBX as PhysReg,
RBP as PhysReg,
R12 as PhysReg,
R13 as PhysReg,
R14 as PhysReg,
R15 as PhysReg,
];
for ® in &callee_saved {
let instr = X86MachineInstr::new(X86Opcode::POP, X86InstrKind::Stack).add_preg(reg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok(), "Failed to encode POP for reg {}", reg);
}
}
#[test]
fn test_condition_code_display_all() {
let expected = vec![
(X86ConditionCode::O, 'o'),
(X86ConditionCode::NO, 'n'),
(X86ConditionCode::B, 'b'),
(X86ConditionCode::AE, 'a'),
(X86ConditionCode::E, 'e'),
(X86ConditionCode::NE, 'n'),
(X86ConditionCode::BE, 'b'),
(X86ConditionCode::A, 'a'),
(X86ConditionCode::S, 's'),
(X86ConditionCode::NS, 'n'),
(X86ConditionCode::P, 'p'),
(X86ConditionCode::NP, 'n'),
(X86ConditionCode::L, 'l'),
(X86ConditionCode::GE, 'g'),
(X86ConditionCode::LE, 'l'),
(X86ConditionCode::G, 'g'),
];
for (cc, first_char) in &expected {
let s = format!("{}", cc);
assert!(s.starts_with(*first_char), "CC {:?} display mismatch", cc);
}
}
#[test]
fn test_condition_code_signed_unsigned_roundtrip() {
let signed_codes = vec![
X86ConditionCode::L,
X86ConditionCode::GE,
X86ConditionCode::LE,
X86ConditionCode::G,
];
for cc in &signed_codes {
let unsigned = cc.unsigned_counterpart();
let back = unsigned.signed_counterpart();
assert_eq!(back, *cc, "Roundtrip failed for {:?}", cc);
}
let unsigned_codes = vec![
X86ConditionCode::B,
X86ConditionCode::AE,
X86ConditionCode::BE,
X86ConditionCode::A,
];
for cc in &unsigned_codes {
let signed = cc.signed_counterpart();
let back = signed.unsigned_counterpart();
assert_eq!(back, *cc, "Roundtrip failed for {:?}", cc);
}
}
#[test]
fn test_reg_alloc_strategy_all_values() {
let strategies = vec![
RegAllocStrategy::Fast,
RegAllocStrategy::Basic,
RegAllocStrategy::Greedy,
RegAllocStrategy::GreedyWithSplitting,
];
for strategy in &strategies {
let ra = X86RegAllocStage::new(*strategy, true);
assert_eq!(ra.strategy, *strategy);
}
}
#[test]
fn test_all_isel_patterns_are_valid() {
for pattern in GOLDEN_ISEL_PATTERNS.iter() {
assert!(!pattern.name.is_empty(), "Pattern has empty name");
assert!(
!pattern.ir_op.is_empty(),
"Pattern {} has empty ir_op",
pattern.name
);
assert!(
pattern.num_operands >= 0,
"Pattern {} has invalid operand count",
pattern.name
);
}
}
#[test]
fn test_isel_patterns_cover_all_basic_ops() {
let required_ops = vec![
"add", "sub", "mul", "sdiv", "udiv", "and", "or", "xor", "shl", "lshr", "ashr", "load",
"store", "icmp", "br", "ret", "call",
];
for op in &required_ops {
let patterns = lookup_golden_patterns(op);
assert!(!patterns.is_empty(), "No patterns found for IR op '{}'", op);
}
}
#[test]
fn test_regalloc_result_fields() {
let result = X86RegAllocResult {
success: true,
spills: 3,
reloads: 2,
copies_coalesced: 5,
regs_spilled: 1,
ranges_split: 4,
};
assert!(result.success);
assert_eq!(result.spills, 3);
assert_eq!(result.reloads, 2);
let fail = X86RegAllocResult::failure();
assert!(!fail.success);
assert_eq!(fail.spills, 0);
}
#[test]
fn test_mf_add_multiple_spill_slots() {
let mut mf = X86MachineFunction::new("spill_test");
for i in 0..10 {
mf.add_spill_slot(X86SpillSlot {
vreg: i,
offset: -(i as i32 * 8 + 16),
size: 8,
alignment: 8,
reg_class: RegClass::GPR64,
});
}
assert_eq!(mf.spill_slots.len(), 10);
assert_eq!(mf.spill_slot_size(), 80);
}
#[test]
fn test_frame_info_setters() {
let mut info = X86FrameInfo::default();
info.total_size = 128;
info.local_size = 64;
info.has_frame_pointer = true;
info.uses_red_zone = false;
assert_eq!(info.total_size, 128);
assert!(info.has_frame_pointer);
}
#[test]
fn test_call_conv_frame_pointer_reg() {
assert_eq!(
X86CallingConvention::SystemV64.frame_pointer_reg(),
RBP as PhysReg
);
assert_eq!(
X86CallingConvention::Win64.frame_pointer_reg(),
RBP as PhysReg
);
}
#[test]
fn test_call_conv_stack_pointer_reg() {
assert_eq!(
X86CallingConvention::SystemV64.stack_pointer_reg(),
RSP as PhysReg
);
assert_eq!(
X86CallingConvention::Win64.stack_pointer_reg(),
RSP as PhysReg
);
}
#[test]
fn test_pipeline_config_custom() {
let config = X86PipelineConfig {
function_name: "custom".into(),
is_64bit: false,
call_conv: X86CallingConvention::CDecl32,
reg_alloc_strategy: RegAllocStrategy::Fast,
emission_format: EmissionFormat::AssemblyIntel,
eliminate_frame_pointer: false,
debug: true,
subtarget: default_subtarget(),
opt_level: 0,
pic: false,
};
assert!(!config.is_64bit);
assert_eq!(config.opt_level, 0);
assert!(!config.pic);
}
#[test]
fn test_pipeline_output_with_relocations() {
let output = X86PipelineOutput {
function_name: "main".into(),
object_bytes: vec![0xE8, 0x00, 0x00, 0x00, 0x00],
assembly: Some("call printf".into()),
symbols: vec![X86SymbolEntry {
name: "main".into(),
offset: 0,
size: 5,
is_global: true,
is_function: true,
}],
relocations: vec![X86RelocationEntry {
offset: 1,
symbol: "printf".into(),
reloc_type: X86RelocType::Plt32,
addend: -4,
}],
code_size: 5,
pipeline_metrics: PipelineMetrics::default(),
};
assert_eq!(output.symbols.len(), 1);
assert_eq!(output.relocations.len(), 1);
assert_eq!(output.relocations[0].symbol, "printf");
}
#[test]
fn test_stage_metrics_elapsed() {
let mut metrics = StageMetrics::new();
let start = std::time::Instant::now();
metrics.elapsed = start.elapsed();
assert!(metrics.elapsed.as_nanos() >= 0);
}
#[test]
fn test_instr_builder_multiple_blocks() {
let mut builder = X86InstrBuilder::new("multi_bb");
builder.start_block("entry");
let v0 = builder.emit_def(X86Opcode::MOV, X86InstrKind::Move);
builder.emit(X86MachineInstr::new(X86Opcode::JMP, X86InstrKind::Jump).add_label("bb2"));
builder.start_block("bb1");
builder.emit_binary_op(X86Opcode::ADD, X86InstrKind::ALU, v0, v0);
builder.emit_ret();
builder.start_block("bb2");
builder.emit_ret();
let mf = builder.build();
assert_eq!(mf.num_blocks(), 3);
assert_eq!(mf.blocks[0].name, "entry");
assert_eq!(mf.blocks[1].name, "bb1");
assert_eq!(mf.blocks[2].name, "bb2");
}
#[test]
fn test_x86_opcode_coverage() {
let key_ops = vec![
X86Opcode::ADD,
X86Opcode::SUB,
X86Opcode::MUL,
X86Opcode::DIV,
X86Opcode::AND,
X86Opcode::OR,
X86Opcode::XOR,
X86Opcode::SHL,
X86Opcode::SHR,
X86Opcode::SAR,
X86Opcode::MOV,
X86Opcode::JMP,
X86Opcode::CALL,
X86Opcode::RET,
X86Opcode::CMP,
X86Opcode::LEA,
];
for op in &key_ops {
let has_pattern = GOLDEN_ISEL_PATTERNS.iter().any(|p| p.x86_opcode == *op);
assert!(has_pattern, "No ISel pattern for {:?}", op);
}
}
#[test]
fn test_regalloc_available_regs_64bit() {
let ra = X86RegAllocStage::new(RegAllocStrategy::Greedy, true);
let mf = X86MachineFunction::new("test");
let regs = ra.compute_available_regs(&mf);
assert!(regs.len() >= 24); }
#[test]
fn test_regalloc_available_regs_32bit() {
let ra = X86RegAllocStage::new(RegAllocStrategy::Greedy, false);
let mf = X86MachineFunction::new("test");
let regs = ra.compute_available_regs(&mf);
assert!(regs.len() > 0);
}
#[test]
fn test_pipeline_input_varied_size() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("varied");
for size in &[0, 1, 5, 10, 50] {
let instructions: Vec<_> = (0..*size)
.map(|i| ("add".to_string(), vec![i, i + 1], Some(i + 2)))
.collect();
if !instructions.is_empty() || *size == 0 {
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok(), "Pipeline failed for size {}", size);
}
}
}
#[test]
fn test_operand_as_imm_edge_cases() {
assert_eq!(X86MachineOperand::imm(0).as_imm(), Some(0));
assert_eq!(X86MachineOperand::imm(-1).as_imm(), Some(-1));
assert_eq!(X86MachineOperand::imm(i64::MAX).as_imm(), Some(i64::MAX));
assert_eq!(X86MachineOperand::imm(i64::MIN).as_imm(), Some(i64::MIN));
assert_eq!(X86MachineOperand::Imm64(u64::MAX).as_imm(), Some(-1));
}
#[test]
fn test_live_interval_defs_and_uses() {
let mut interval = LiveInterval::new(10);
interval.defs.push(5);
interval.defs.push(20);
interval.uses.push(7);
interval.uses.push(15);
interval.uses.push(22);
assert_eq!(interval.defs.len(), 2);
assert_eq!(interval.uses.len(), 3);
assert!(interval.covers(7));
assert!(interval.covers(20));
}
#[test]
fn test_perf_counters_report_formatting() {
let mut counters = PerfCounters::new();
counters.add("isel_instrs", 42, "count", PerfCounterType::Counter);
counters.add("code_size", 256, "bytes", PerfCounterType::Bytes);
counters.time_start("compile");
counters.time_end("compile");
let report = counters.report();
assert!(report.contains("isel_instrs"));
assert!(report.contains("code_size"));
assert!(report.contains("compile"));
}
#[test]
fn test_pipeline_error_clone() {
let e = X86PipelineError::Internal("test".into());
let e2 = e.clone();
assert_eq!(format!("{}", e), format!("{}", e2));
}
#[test]
fn test_x86_reloc_type_debug() {
let types = vec![
X86RelocType::None,
X86RelocType::Abs64,
X86RelocType::PcRel32,
X86RelocType::Abs32,
X86RelocType::Plt32,
X86RelocType::GotPcRel,
X86RelocType::RexGotPcRelx,
];
for t in &types {
let s = format!("{:?}", t);
assert!(!s.is_empty());
}
}
#[test]
fn test_encoded_instruction_with_reloc() {
let enc = EncodedInstruction {
bytes: vec![0xE8, 0x00, 0x00, 0x00, 0x00],
offset: 0x10,
size: 5,
reloc: Some(X86RelocationEntry {
offset: 0x11,
symbol: "target".into(),
reloc_type: X86RelocType::PcRel32,
addend: -4,
}),
};
assert!(enc.reloc.is_some());
assert_eq!(enc.reloc.as_ref().unwrap().symbol, "target");
}
#[test]
fn test_mbb_display_format() {
let mut bb = X86MachineBasicBlock::new("L1");
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(1)
.add_vreg(2)
.add_vreg(3),
);
let s = format!("{}", bb);
assert!(s.contains("L1:"));
assert!(s.contains("ADD"));
}
#[test]
fn test_mf_display_format() {
let mut mf = X86MachineFunction::new("display_test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
mf.push_block(bb);
let s = format!("{}", mf);
assert!(s.contains("display_test"));
assert!(s.contains("MachineFunction"));
}
#[test]
fn test_x86_machine_operand_none() {
let op = X86MachineOperand::None;
assert!(!op.is_reg());
assert!(!op.is_imm());
assert!(!op.is_memory());
assert_eq!(format!("{}", op), "<none>");
}
#[test]
fn test_x86_machine_operand_segment() {
let op = X86MachineOperand::Segment(0x24); assert!(!op.is_reg());
let s = format!("{}", op);
assert!(s.contains("seg"));
}
#[test]
fn test_x86_machine_operand_global() {
let op = X86MachineOperand::Global("printf".into());
assert!(!op.is_reg());
assert!(!op.is_imm());
let s = format!("{}", op);
assert_eq!(s, "printf");
}
#[test]
fn test_pattern_constraints_default() {
let constraints = PatternConstraints::default();
assert!(constraints.operand_imm.is_empty());
assert!(constraints.operand_reg.is_empty());
assert!(constraints.required_feature.is_none());
}
#[test]
fn test_isel_pattern_builder() {
let pat = X86ISelPattern::new("custom", "custom_op", X86Opcode::MOV)
.with_priority(5)
.with_cost(2)
.with_feature("sse4.2")
.with_operands(3)
.with_commutative()
.with_kind(X86InstrKind::Vector);
assert_eq!(pat.priority, 5);
assert_eq!(pat.cost, 2);
assert_eq!(pat.feature, "sse4.2");
assert_eq!(pat.num_operands, 3);
assert!(pat.is_commutative);
assert_eq!(pat.kind, X86InstrKind::Vector);
}
#[test]
fn test_x86_instr_flags_conditional() {
let flags = X86InstrFlags {
has_def: true,
is_branch: true,
uses_flags: true,
is_terminator: true,
..Default::default()
};
let instr = X86MachineInstr::new(X86Opcode::JMP, X86InstrKind::Branch)
.with_flags(flags)
.add_cc(X86ConditionCode::E)
.add_label("target");
assert!(instr.is_branch());
assert!(instr.is_terminator());
assert!(!instr.is_call());
}
#[test]
fn test_operand_memory_display_complex() {
let mem = X86MachineOperand::memory(Some(RBP as PhysReg), Some(RSI as PhysReg), 4, -16);
let s = format!("{}", mem);
assert!(s.contains("p5")); assert!(s.contains("p4")); assert!(s.contains("4")); }
#[test]
fn test_operand_memory_base_only() {
let mem = X86MachineOperand::memory(Some(RSP as PhysReg), None, 1, 0);
let s = format!("{}", mem);
assert!(s.contains("p4")); }
#[test]
fn test_operand_memory_no_base() {
let mem = X86MachineOperand::memory(None, None, 1, 0x12345678);
let s = format!("{}", mem);
assert!(s.contains("12345678") || s.contains("305419896"));
}
#[test]
fn test_regalloc_live_intervals_multiple_defs() {
let mut ra = X86RegAllocStage::new(RegAllocStrategy::Fast, true);
let mut mf = X86MachineFunction::new("multi_def");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(1),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(1)
.add_vreg(0)
.add_imm(2),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_vreg(1),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return)
.add_vreg(0)
.with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
mf.assign_instr_ids();
mf.compute_liveness();
ra.compute_live_intervals(&mf);
assert!(ra.live_intervals.contains_key(&0));
assert!(ra.live_intervals.contains_key(&1));
}
#[test]
fn test_regalloc_spill_costs_ordering() {
let mut ra = X86RegAllocStage::new(RegAllocStrategy::Greedy, true);
let mut mf = X86MachineFunction::new("cost_test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(0),
);
for _ in 0..10 {
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(1)
.add_vreg(0)
.add_imm(1),
);
}
mf.push_block(bb);
mf.assign_instr_ids();
ra.compute_live_intervals(&mf);
ra.compute_spill_costs(&mf);
assert!(ra.spill_costs.contains_key(&0));
}
#[test]
fn test_frame_lower_calculate_info_with_calls() {
let fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
let mut mf = X86MachineFunction::new("caller");
mf.has_calls = true;
let info = fl.calculate_frame_info(&mf);
assert!(info.max_call_frame_size > 0);
assert!(!info.uses_red_zone); }
#[test]
fn test_frame_lower_calculate_info_leaf() {
let fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
let mf = X86MachineFunction::new("leaf");
let info = fl.calculate_frame_info(&mf);
assert!(info.uses_red_zone);
assert_eq!(info.max_call_frame_size, 0);
}
#[test]
fn test_encode_imul_variants() {
let encoder = X86EncodeStage::new(true);
let instr =
X86MachineInstr::new(X86Opcode::IMUL, X86InstrKind::ALU).add_preg(RAX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() >= 2);
}
#[test]
fn test_encode_div() {
let encoder = X86EncodeStage::new(true);
let instr =
X86MachineInstr::new(X86Opcode::DIV, X86InstrKind::ALU).add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
}
#[test]
fn test_encode_idiv() {
let encoder = X86EncodeStage::new(true);
let instr =
X86MachineInstr::new(X86Opcode::IDIV, X86InstrKind::ALU).add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
}
#[test]
fn test_encode_ud2() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::UD2, X86InstrKind::System);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![0x0F, 0x0B]);
}
#[test]
fn test_encode_all_sse_ops() {
let encoder = X86EncodeStage::new(true);
let sse_ops = vec![
X86Opcode::ADDSS,
X86Opcode::ADDSD,
X86Opcode::SUBSS,
X86Opcode::SUBSD,
X86Opcode::MULSS,
X86Opcode::MULSD,
X86Opcode::DIVSS,
X86Opcode::DIVSD,
X86Opcode::CVTSS2SD,
X86Opcode::CVTSD2SS,
];
for &op in &sse_ops {
let instr = X86MachineInstr::new(op, X86InstrKind::Float)
.add_preg(XMM0 as PhysReg)
.add_preg(XMM1 as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok(), "Failed to encode SSE op {:?}", op);
}
}
#[test]
fn test_encode_all_cmov_ops() {
let encoder = X86EncodeStage::new(true);
let cmov_ops = vec![
X86Opcode::CMOVO,
X86Opcode::CMOVNO,
X86Opcode::CMOVB,
X86Opcode::CMOVAE,
X86Opcode::CMOVE,
X86Opcode::CMOVNE,
X86Opcode::CMOVBE,
X86Opcode::CMOVA,
X86Opcode::CMOVS,
X86Opcode::CMOVNS,
X86Opcode::CMOVP,
X86Opcode::CMOVNP,
X86Opcode::CMOVL,
X86Opcode::CMOVGE,
X86Opcode::CMOVLE,
X86Opcode::CMOVG,
];
for &op in &cmov_ops {
let instr = X86MachineInstr::new(op, X86InstrKind::Move)
.add_preg(RAX as PhysReg)
.add_preg(RCX as PhysReg);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok(), "Failed to encode CMOV op {:?}", op);
}
}
#[test]
fn test_encode_pipeline_full_roundtrip() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("roundtrip");
let instructions = vec![
("add".to_string(), vec![100, 101], Some(0)),
("sub".to_string(), vec![0, 102], Some(1)),
("mul".to_string(), vec![1, 103], Some(2)),
("and".to_string(), vec![2, 104], Some(3)),
("or".to_string(), vec![3, 105], Some(4)),
("xor".to_string(), vec![4, 106], Some(5)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.code_size > 0);
assert!(output.pipeline_metrics.success);
}
#[test]
fn test_regalloc_coalescing_opportunities() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("coalesce");
let instructions = vec![
("add".to_string(), vec![10, 11], Some(0)),
("add".to_string(), vec![0, 12], Some(1)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_isel_unknown_opcode_graceful() {
let subtarget = default_subtarget();
let mut isel = X86ISelStage::new(subtarget);
isel.debug = true;
let instructions = vec![
("unknown_op".to_string(), vec![0], Some(1)),
("ret".to_string(), vec![], None),
];
let result = isel.run("unknown_test", &instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_x86_pipeline_smoke_test_all_stages() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("smoke").with_debug();
let instructions = vec![
("add".to_string(), vec![10, 11], Some(0)), ("mul".to_string(), vec![0, 12], Some(1)), ("load".to_string(), vec![20], Some(2)), ("store".to_string(), vec![1, 21], None), ("icmp".to_string(), vec![0, 22], None), ("br".to_string(), vec![23], None), ("sub".to_string(), vec![0, 13], Some(3)), ("ret".to_string(), vec![], None), ];
let boundaries = vec![6]; let result = pipeline.run(&instructions, &boundaries);
assert!(result.is_ok(), "Smoke test failed: {:?}", result.err());
}
#[test]
fn test_mf_frame_info_persistence() {
let mut fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
let mut mf = X86MachineFunction::new("frame_test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.is_entry = true;
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
let _ = fl.run(&mut mf);
assert!(mf.frame_info.is_some());
let fi = mf.frame_info.as_ref().unwrap();
assert!(fi.total_size >= 0);
}
#[test]
fn test_regalloc_does_not_destroy_instructions() {
let mut ra = X86RegAllocStage::new(RegAllocStrategy::Fast, true);
let mut mf = X86MachineFunction::new("preserve");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(42),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
mf.assign_instr_ids();
let instr_count_before = mf.total_instructions();
let _ = ra.run(&mut mf);
let instr_count_after = mf.total_instructions();
assert_eq!(instr_count_before, instr_count_after);
}
#[test]
fn test_all_x86_instr_kinds() {
let kinds = vec![
X86InstrKind::ALU,
X86InstrKind::Move,
X86InstrKind::Load,
X86InstrKind::Store,
X86InstrKind::Jump,
X86InstrKind::Branch,
X86InstrKind::Call,
X86InstrKind::Return,
X86InstrKind::Compare,
X86InstrKind::Stack,
X86InstrKind::Float,
X86InstrKind::Vector,
X86InstrKind::System,
X86InstrKind::Nop,
X86InstrKind::Other,
];
for kind in &kinds {
let s = format!("{:?}", kind);
assert!(!s.is_empty());
let _ = X86MachineInstr::new(X86Opcode::NOP, *kind);
}
}
#[test]
fn test_pattern_priority_ordering() {
let patterns = lookup_golden_patterns("icmp");
for p in &patterns {
assert!(p.priority >= 2 || p.priority >= 0);
}
}
#[test]
fn test_pipeline_handles_all_default_ir_ops() {
let ops = vec![
"add", "sub", "mul", "and", "or", "xor", "shl", "lshr", "ashr", "sdiv", "udiv", "icmp",
"load", "store", "br", "ret", "call",
];
let subtarget = default_subtarget();
let mut isel = X86ISelStage::new(subtarget);
isel.debug = true;
for op in &ops {
let instructions = vec![(op.to_string(), vec![0, 1], Some(2))];
let result = isel.run(op, &instructions, &[]);
assert!(result.is_ok(), "ISel failed for op '{}'", op);
}
}
#[test]
fn test_peephole_remove_redundant_mov() {
let mut opt = X86PeepholeOptimizer::new();
let mut bb = X86MachineBasicBlock::new("test");
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_vreg(0)
.add_vreg(0),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
opt.optimize_block(&mut bb);
assert_eq!(bb.len(), 1, "Redundant MOV should be removed");
}
#[test]
fn test_peephole_double_invert() {
let mut opt = X86PeepholeOptimizer::new();
let mut bb = X86MachineBasicBlock::new("test");
bb.push_instr(
X86MachineInstr::new(X86Opcode::NOT, X86InstrKind::ALU).add_preg(RAX as PhysReg),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::NOT, X86InstrKind::ALU).add_preg(RAX as PhysReg),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
opt.optimize_block(&mut bb);
assert_eq!(bb.len(), 1);
}
#[test]
fn test_peephole_xor_self_zero() {
let mut opt = X86PeepholeOptimizer::new();
let mut bb = X86MachineBasicBlock::new("test");
bb.push_instr(
X86MachineInstr::new(X86Opcode::XOR, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_preg(RAX as PhysReg),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
opt.optimize_block(&mut bb);
assert!(bb.len() > 0);
}
#[test]
fn test_peephole_remove_jump_to_next_block() {
let mut opt = X86PeepholeOptimizer::new();
let mut bb = X86MachineBasicBlock::new("bb0");
bb.add_successor("bb1");
bb.push_instr(X86MachineInstr::new(X86Opcode::JMP, X86InstrKind::Jump).add_label("bb1"));
opt.optimize_block(&mut bb);
assert!(bb.len() == 0 || bb.instructions[0].opcode != X86Opcode::JMP);
}
#[test]
fn test_verifier_empty_function() {
let mf = X86MachineFunction::new("empty");
let verifier = X86MachineVerifier::new();
let result = verifier.verify(&mf);
assert!(result.is_ok());
}
#[test]
fn test_verifier_function_with_blocks() {
let mut mf = X86MachineFunction::new("func");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
let verifier = X86MachineVerifier::new();
let result = verifier.verify(&mf);
assert!(result.is_ok());
}
#[test]
fn test_verifier_use_without_def() {
let mut mf = X86MachineFunction::new("func");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(0)
.add_vreg(99) .add_imm(1),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
let verifier = X86MachineVerifier::new();
let result = verifier.verify(&mf);
assert!(result.is_err());
}
#[test]
fn test_verifier_multiple_defs() {
let mut mf = X86MachineFunction::new("func");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(1)
.add_imm(5),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(1) .add_vreg(1)
.add_imm(3),
);
mf.push_block(bb);
let verifier = X86MachineVerifier::new();
let result = verifier.verify(&mf);
assert!(result.is_err());
}
#[test]
fn test_verifier_block_without_terminator() {
let mut mf = X86MachineFunction::new("func");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(0)
.add_imm(1)
.add_imm(2),
);
mf.push_block(bb);
let verifier = X86MachineVerifier::new();
let result = verifier.verify(&mf);
assert!(result.is_err());
}
#[test]
fn test_legalize_unsupported_width() {
let mut legalizer = X86LegalizeStage::new(true);
let mut mf = X86MachineFunction::new("legalize_test");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(0)
.add_imm(i64::MAX)
.add_imm(1),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
let result = legalizer.run(&mut mf);
assert!(result.is_ok());
}
#[test]
fn test_legalize_idempotent() {
let mut legalizer = X86LegalizeStage::new(true);
let mut mf = X86MachineFunction::new("idempotent");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(
X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.set_def(0)
.add_vreg(1)
.add_vreg(2),
);
mf.push_block(bb);
let count_before = mf.total_instructions();
let _ = legalizer.run(&mut mf);
let count_after = mf.total_instructions();
assert_eq!(count_before, count_after);
}
#[test]
fn test_pipeline_config_validates_reg_alloc_strategy() {
for level in 0..=3 {
let pipeline = X86GoldenPipeline::for_x86_64_sysv("opt_test").with_opt_level(level);
let strategy = pipeline.config.reg_alloc_strategy;
match level {
0 => assert_eq!(strategy, RegAllocStrategy::Fast),
1 => assert_eq!(strategy, RegAllocStrategy::Basic),
2 => assert_eq!(strategy, RegAllocStrategy::Greedy),
3 => assert_eq!(strategy, RegAllocStrategy::GreedyWithSplitting),
_ => {}
}
}
}
#[test]
fn test_all_call_convs_have_valid_regs() {
let convs = vec![
X86CallingConvention::SystemV64,
X86CallingConvention::Win64,
X86CallingConvention::CDecl32,
X86CallingConvention::StdCall32,
X86CallingConvention::FastCall32,
X86CallingConvention::VectorCall,
];
for conv in &convs {
let callee = conv.callee_saved_gprs();
let caller = conv.caller_saved_gprs();
let sp = conv.stack_pointer_reg();
let fp = conv.frame_pointer_reg();
assert!(!callee.is_empty() || !caller.is_empty());
assert!(sp > 0 || sp == 0);
assert!(fp > 0 || fp == 0);
}
}
#[test]
fn test_peephole_stats_tracking() {
let mut opt = X86PeepholeOptimizer::new();
let mut bb = X86MachineBasicBlock::new("test");
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_vreg(0)
.add_vreg(0),
);
opt.optimize_block(&mut bb);
assert!(opt.stats.instructions_removed > 0);
}
#[test]
fn test_verifier_checks_entry_block_exists() {
let mf = X86MachineFunction::new("no_blocks");
let verifier = X86MachineVerifier::new();
let result = verifier.verify(&mf);
let _ = result;
}
#[test]
fn test_legalize_preserves_function_structure() {
let mut legalizer = X86LegalizeStage::new(true);
let mut mf = X86MachineFunction::new("structure");
let mut bb1 = X86MachineBasicBlock::new("entry");
bb1.add_successor("exit");
bb1.push_instr(X86MachineInstr::new(X86Opcode::JMP, X86InstrKind::Jump).add_label("exit"));
mf.push_block(bb1);
let mut bb2 = X86MachineBasicBlock::new("exit");
bb2.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb2);
let blocks_before = mf.num_blocks();
let _ = legalizer.run(&mut mf);
assert_eq!(mf.num_blocks(), blocks_before);
}
#[test]
fn test_x86_opcode_from_different_categories() {
let misc = X86Opcode::NOP;
let data = X86Opcode::MOV;
let arith = X86Opcode::ADD;
let ctrl = X86Opcode::JMP;
let call = X86Opcode::CALL;
let ret = X86Opcode::RET;
let cmp = X86Opcode::CMP;
let sse = X86Opcode::ADDSS;
assert_ne!(misc as u32, data as u32);
assert_ne!(arith as u32, ctrl as u32);
assert_ne!(call as u32, ret as u32);
assert_ne!(cmp as u32, sse as u32);
}
#[test]
fn test_regalloc_stress_all_strategies() {
let strategies = vec![
RegAllocStrategy::Fast,
RegAllocStrategy::Basic,
RegAllocStrategy::Greedy,
RegAllocStrategy::GreedyWithSplitting,
];
for strategy in &strategies {
let mut ra = X86RegAllocStage::new(*strategy, true);
let mut mf = X86MachineFunction::new(&format!("stress_{:?}", strategy));
let mut bb = X86MachineBasicBlock::new("entry");
for i in 0..20 {
let src = if i == 0 {
X86MachineOperand::Imm(i as i64)
} else {
X86MachineOperand::VReg(i - 1)
};
let mut instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU);
instr.operands.push(src);
instr.operands.push(X86MachineOperand::Imm(1));
instr.def = Some(i);
bb.push_instr(instr);
}
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return).with_flags(
X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
},
),
);
mf.push_block(bb);
mf.assign_instr_ids();
let result = ra.run(&mut mf);
assert!(result.is_ok(), "{:?} strategy failed", strategy);
}
}
#[test]
fn test_frame_lower_roundtrip_prologue_epilogue() {
let mut fl = X86FrameLowerStage::new(X86CallingConvention::SystemV64, true);
let mut mf = X86MachineFunction::new("roundtrip");
let mut bb = X86MachineBasicBlock::new("entry");
bb.is_entry = true;
bb.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.set_def(0)
.add_imm(42),
);
bb.push_instr(
X86MachineInstr::new(X86Opcode::RET, X86InstrKind::Return)
.add_vreg(0)
.with_flags(X86InstrFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
);
mf.push_block(bb);
let _ = fl.run(&mut mf);
assert!(mf.blocks[0].instructions.len() >= 2);
}
#[test]
fn test_encode_immediate_size_variants() {
let encoder = X86EncodeStage::new(true);
let instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_imm(5);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
let instr = X86MachineInstr::new(X86Opcode::ADD, X86InstrKind::ALU)
.add_preg(RAX as PhysReg)
.add_imm(100000);
let result = encoder.encode_instr(&instr);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_output_has_all_fields() {
let output = X86PipelineOutput {
function_name: "full".into(),
object_bytes: vec![1, 2, 3],
assembly: Some("test asm".into()),
symbols: vec![],
relocations: vec![],
code_size: 3,
pipeline_metrics: PipelineMetrics {
isel_time: Duration::from_micros(10),
regalloc_time: Duration::from_micros(5),
frame_lower_time: Duration::from_micros(3),
encode_time: Duration::from_micros(2),
emit_time: Duration::from_micros(1),
total_time: Duration::from_micros(21),
success: true,
..Default::default()
},
};
assert!(output.pipeline_metrics.success);
assert_eq!(output.code_size, 3);
}
#[test]
fn test_pipeline_rejects_broken_input() {
let mut pipeline = X86GoldenPipeline::for_x86_64_sysv("skip");
let instructions = vec![
("made_up_op_xyz".to_string(), vec![0], Some(1)),
("ret".to_string(), vec![], None),
];
let result = pipeline.run(&instructions, &[]);
assert!(result.is_ok());
}
#[test]
fn test_mf_block_predecessors() {
let mut mf = X86MachineFunction::new("preds");
let mut bb0 = X86MachineBasicBlock::new("bb0");
bb0.add_successor("bb1");
let mut bb1 = X86MachineBasicBlock::new("bb1");
bb1.add_predecessor("bb0");
mf.push_block(bb0);
mf.push_block(bb1);
assert_eq!(mf.blocks[1].predecessors.len(), 1);
assert_eq!(mf.blocks[1].predecessors[0], "bb0");
}
#[test]
fn test_isel_metrics_accumulate() {
let subtarget = default_subtarget();
let mut isel = X86ISelStage::new(subtarget);
let instructions = vec![
("add".to_string(), vec![0, 1], Some(2)),
("sub".to_string(), vec![2, 3], Some(4)),
];
let _ = isel.run("metrics_test", &instructions, &[]);
assert_eq!(isel.metrics.patterns_matched, 2);
assert_eq!(isel.metrics.items_processed, 2);
}
#[test]
fn test_encode_stage_metrics() {
let mut encoder = X86EncodeStage::new(true);
let mut mf = X86MachineFunction::new("metrics");
let mut bb = X86MachineBasicBlock::new("entry");
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
bb.push_instr(X86MachineInstr::new(X86Opcode::NOP, X86InstrKind::Nop));
mf.push_block(bb);
let result = encoder.run(&mf);
assert!(result.is_ok());
assert_eq!(encoder.metrics.items_processed, 2);
}
#[test]
fn test_emit_stage_metrics() {
let mut emitter = X86EmitStage::new(EmissionFormat::Object);
let mf = X86MachineFunction::new("emit_metrics");
let encoded = vec![
EncodedInstruction {
bytes: vec![0x90],
offset: 0,
size: 1,
reloc: None,
},
EncodedInstruction {
bytes: vec![0xC3],
offset: 1,
size: 1,
reloc: None,
},
];
let result = emitter.run(&mf, &encoded);
assert!(result.is_ok());
assert_eq!(emitter.metrics.items_processed, 2);
}
#[test]
fn test_peephole_optimize_all_blocks() {
let mut opt = X86PeepholeOptimizer::new();
let mut mf = X86MachineFunction::new("peep_all");
let mut bb1 = X86MachineBasicBlock::new("bb1");
bb1.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_vreg(0)
.add_vreg(0),
);
mf.push_block(bb1);
let mut bb2 = X86MachineBasicBlock::new("bb2");
bb2.push_instr(
X86MachineInstr::new(X86Opcode::MOV, X86InstrKind::Move)
.add_vreg(1)
.add_vreg(1),
);
mf.push_block(bb2);
opt.optimize_function(&mut mf);
assert!(opt.stats.instructions_removed >= 2);
}
#[test]
fn test_verifier_empty_block_allowed() {
let mut mf = X86MachineFunction::new("empty_block");
mf.push_block(X86MachineBasicBlock::new("entry"));
let verifier = X86MachineVerifier::new();
let result = verifier.verify(&mf);
let _ = result;
}
#[test]
fn test_x86_opcode_discriminants_unique() {
use std::collections::HashSet;
let ops = [
X86Opcode::NOP,
X86Opcode::ADD,
X86Opcode::SUB,
X86Opcode::MUL,
X86Opcode::AND,
X86Opcode::OR,
X86Opcode::XOR,
X86Opcode::MOV,
X86Opcode::JMP,
X86Opcode::CALL,
X86Opcode::RET,
];
let mut seen = HashSet::new();
for op in &ops {
let disc = *op as u32;
assert!(
seen.insert(disc),
"Duplicate discriminant for {:?}: {}",
op,
disc
);
}
}
}