use crate::mc_streamer::x86_opcodes;
use crate::opcode::Opcode;
use crate::value::{SubclassKind, Value, ValueRef};
use std::collections::{HashMap, HashSet};
pub type VirtReg = u32;
pub type PhysReg = u32;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MachineOperand {
Reg(VirtReg),
PhysReg(PhysReg),
Imm(i64),
Label(String),
Global(String),
}
impl MachineOperand {
pub fn new_reg(reg: VirtReg) -> Self {
Self::Reg(reg)
}
pub fn new_phys_reg(reg: PhysReg) -> Self {
Self::PhysReg(reg)
}
pub fn new_use(reg: u32) -> Self {
Self::Reg(reg)
}
pub fn new_imm(imm: i64) -> Self {
Self::Imm(imm)
}
pub fn new_label(label: &str) -> Self {
Self::Label(label.to_string())
}
pub fn new_global(name: &str) -> Self {
Self::Global(name.to_string())
}
pub fn is_reg(&self) -> bool {
matches!(self, Self::Reg(_) | Self::PhysReg(_))
}
pub fn is_imm(&self) -> bool {
matches!(self, Self::Imm(_))
}
pub fn is_label(&self) -> bool {
matches!(self, Self::Label(_))
}
pub fn is_global(&self) -> bool {
matches!(self, Self::Global(_))
}
pub fn is_def(&self) -> bool {
matches!(self, Self::Reg(_) | Self::PhysReg(_))
}
pub fn is_use(&self) -> bool {
matches!(self, Self::Reg(_) | Self::PhysReg(_))
}
pub fn reg(&self) -> u32 {
match self {
Self::Reg(r) | Self::PhysReg(r) => *r,
_ => 0,
}
}
pub fn reg_val(&self) -> Option<VirtReg> {
match self {
Self::Reg(r) | Self::PhysReg(r) => Some(*r),
_ => None,
}
}
pub fn imm_val(&self) -> i64 {
match self {
Self::Imm(i) => *i,
_ => 0,
}
}
pub fn label_str(&self) -> &str {
match self {
Self::Label(l) => l.as_str(),
_ => "",
}
}
pub fn global_str(&self) -> &str {
match self {
Self::Global(g) => g.as_str(),
_ => "",
}
}
}
#[derive(Debug, Clone)]
pub struct MachineInstr {
pub opcode: u32,
pub operands: Vec<MachineOperand>,
pub def: Option<VirtReg>,
pub size: u8,
}
impl MachineInstr {
pub fn new(opcode: u32) -> Self {
Self {
opcode,
operands: Vec::new(),
def: None,
size: 0,
}
}
pub fn with_def(mut self, reg: VirtReg) -> Self {
self.def = Some(reg);
self
}
pub fn push_reg(&mut self, reg: VirtReg) {
self.operands.push(MachineOperand::new_reg(reg));
}
pub fn push_imm(&mut self, imm: i64) {
self.operands.push(MachineOperand::new_imm(imm));
}
pub fn push_label(&mut self, label: &str) {
self.operands.push(MachineOperand::new_label(label));
}
#[allow(dead_code)]
pub fn with_flags(mut self, _flags: u32) -> Self {
self
}
#[allow(dead_code)]
pub fn with_opcode(mut self, opcode: u32) -> Self {
self.opcode = opcode;
self
}
}
impl Default for MachineInstr {
fn default() -> Self {
Self::new(0)
}
}
#[derive(Debug, Clone)]
pub struct MachineBasicBlock {
pub id: usize,
pub name: String,
pub instructions: Vec<MachineInstr>,
pub successors: Vec<usize>,
pub predecessors: Vec<usize>,
pub is_entry: bool,
}
impl Default for MachineBasicBlock {
fn default() -> Self {
Self {
id: 0,
name: String::new(),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: false,
}
}
}
impl MachineBasicBlock {
pub fn new(id: usize) -> Self {
Self {
id,
name: format!("bb{}", id),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: false,
}
}
pub fn new_named(id: usize, name: &str) -> Self {
Self {
id,
name: name.to_string(),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: false,
}
}
pub fn with_name(name: String) -> Self {
Self {
id: 0,
name,
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: false,
}
}
}
#[derive(Debug, Clone)]
pub struct MachineFunction {
pub name: String,
pub blocks: Vec<MachineBasicBlock>,
pub virt_reg_counter: u32,
pub alloca_counter: i64,
pub alloca_byte_offset: i64,
pub alloca_offsets: std::collections::HashMap<usize, i64>,
}
impl MachineFunction {
pub fn new(name: &str) -> Self {
Self {
name: name.into(),
blocks: Vec::new(),
virt_reg_counter: 0,
alloca_counter: 0,
alloca_byte_offset: 0,
alloca_offsets: std::collections::HashMap::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: MachineBasicBlock) {
self.blocks.push(bb);
}
}
pub struct InstructionSelector;
impl InstructionSelector {
fn arg_index_to_abi_reg(idx: usize) -> Option<u32> {
match idx {
0 => Some(7), 1 => Some(6), 2 => Some(2), 3 => Some(1), 4 => Some(8), 5 => Some(9), _ => None, }
}
pub fn select(mf: &mut MachineFunction, func: &ValueRef) {
let f = func.borrow();
let mut vreg_map: HashMap<usize, VirtReg> = HashMap::new();
let mut arg_vids: Vec<(usize, VirtReg, u32, bool, bool)> = Vec::new(); for block in &f.blocks {
let ir_bb = block.borrow();
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
if inst.is_argument() {
let vid = inst.vid as usize;
if !vreg_map.contains_key(&vid) {
let vr = mf.new_vreg();
vreg_map.insert(vid, vr);
let bw = inst.ty.integer_bit_width();
let is_fp = inst.ty.is_floating_point();
let is_double = inst.ty.is_double_ty();
arg_vids.push((vid, vr, bw, is_fp, is_double));
}
}
continue;
}
for operand in &inst.operands {
let op = operand.borrow();
if op.is_argument() {
let vid = op.vid as usize;
if !vreg_map.contains_key(&vid) {
let vr = mf.new_vreg();
vreg_map.insert(vid, vr);
let bw = op.ty.integer_bit_width();
let is_fp = op.ty.is_floating_point();
let is_double = op.ty.is_double_ty();
arg_vids.push((vid, vr, bw, is_fp, is_double));
}
}
}
}
}
for op in &f.operands {
let ir_bb = op.borrow();
if ir_bb.is_basic_block() {
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if inst.is_instruction() && !inst.ty.is_void() {
let vr = mf.new_vreg();
vreg_map.insert(inst.vid as usize, vr);
}
}
}
}
for block in &f.blocks {
let ir_bb = block.borrow();
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if inst.is_instruction() && !inst.ty.is_void() {
if !vreg_map.contains_key(&(inst.vid as usize)) {
let vr = mf.new_vreg();
vreg_map.insert(inst.vid as usize, vr);
}
}
}
}
for block in &f.blocks {
let ir_bb = block.borrow();
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if inst.is_instruction() {
for op in &inst.operands {
if !op.borrow().is_constant() && !op.borrow().is_function() {
let op_vid = op.borrow().vid as usize;
if !vreg_map.contains_key(&op_vid) {
let vr = mf.new_vreg();
vreg_map.insert(op_vid, vr);
}
}
}
}
}
}
let mut phi_list: Vec<(VirtReg, Vec<(Option<VirtReg>, i64, String)>)> = Vec::new();
for block in &f.blocks {
let ir_bb = block.borrow();
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
if inst.get_opcode() == Some(Opcode::Phi) {
let phi_vr = vreg_map.get(&(inst.vid as usize)).copied();
if let Some(pvr) = phi_vr {
let mut inputs = Vec::new();
let mut i = 0;
while i + 1 < inst.operands.len() {
let val = inst.operands[i].borrow();
let block_name = inst.operands[i + 1].borrow().name.clone();
if let Some(&vr) = vreg_map.get(&(val.vid as usize)) {
inputs.push((Some(vr), 0, block_name));
} else if val.is_constant() {
let imm_val: i64 = val.name.parse().unwrap_or(0);
inputs.push((None, imm_val, block_name));
}
i += 2;
}
if !inputs.is_empty() {
phi_list.push((pvr, inputs));
}
}
}
}
}
let mut has_alloca = false;
let mut has_call = false;
let mut max_alloca_vid: usize = 0;
for op in &f.operands {
let ir_bb = op.borrow();
if ir_bb.is_basic_block() {
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
match inst.get_opcode() {
Some(Opcode::Alloca) => {
has_alloca = true;
let vid = inst.vid as usize;
if vid > max_alloca_vid {
max_alloca_vid = vid;
}
}
Some(Opcode::Call) => has_call = true,
_ => {}
}
}
}
}
for block in &f.blocks {
let ir_bb = block.borrow();
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
match inst.get_opcode() {
Some(Opcode::Alloca) => {
has_alloca = true;
let vid = inst.vid as usize;
if vid > max_alloca_vid {
max_alloca_vid = vid;
}
}
Some(Opcode::Call) => has_call = true,
_ => {}
}
}
}
let total_stack_size: Option<i64> = if has_alloca && has_call {
let mut sz = (max_alloca_vid as i64 + 1) * 8 + 8;
if sz % 16 == 0 {
sz += 8;
}
if sz > 0 {
Some(sz)
} else {
None
}
} else {
None
};
let alloca_offsets: HashMap<usize, i64> = HashMap::new();
for op in &f.operands {
let ir_bb = op.borrow();
if !ir_bb.is_basic_block() {
continue;
}
let mut mbb = MachineBasicBlock {
name: ir_bb.name.clone(),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
};
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
Self::select_ir_instruction(&mut mbb, &inst, &mut vreg_map, mf, &alloca_offsets);
}
mf.push_block(mbb);
}
for (block_idx, block) in f.blocks.iter().enumerate() {
let ir_bb = block.borrow();
let mut mbb = MachineBasicBlock {
name: ir_bb.name.clone(),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
};
if block_idx == 0 {
let mut fp_arg_idx = 0usize;
for (arg_idx, &(arg_vid, arg_vr, bw, is_fp, is_double_arg)) in
arg_vids.iter().enumerate()
{
if is_fp {
if fp_arg_idx < 8 {
let xmm_reg = 64 + fp_arg_idx as u32;
let op = if is_double_arg {
x86_opcodes::MOVQ_FROM_XMM
} else {
x86_opcodes::MOVD_FROM_XMM
};
let mut mi = MachineInstr::new(op);
mi.operands.push(MachineOperand::PhysReg(xmm_reg)); mi.push_reg(arg_vr); mbb.instructions.push(mi);
fp_arg_idx += 1;
}
} else if let Some(abi_reg) = Self::arg_index_to_abi_reg(arg_idx) {
if bw == 8 || bw == 32 || bw == 1 {
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(arg_vr);
mi.size = 4;
mi.operands.push(MachineOperand::PhysReg(abi_reg));
mbb.instructions.push(mi);
} else if bw == 64 || bw == 0 {
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(arg_vr);
mi.size = 0;
mi.operands.push(MachineOperand::PhysReg(abi_reg));
mbb.instructions.push(mi);
} else {
let mut mi = MachineInstr::new(x86_opcodes::MOVSXD).with_def(arg_vr);
mi.operands.push(MachineOperand::PhysReg(abi_reg));
mbb.instructions.push(mi);
}
}
}
}
for inst_val in &ir_bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
Self::select_ir_instruction(&mut mbb, &inst, &mut vreg_map, mf, &alloca_offsets);
}
mf.push_block(mbb);
}
let block_idx_map: std::collections::HashMap<String, usize> = mf
.blocks
.iter()
.enumerate()
.map(|(i, b)| (b.name.clone(), i))
.collect();
for (phi_vr, ref inputs) in &phi_list {
for &(ref input_vr, imm_val, ref pred_name) in inputs {
if let Some(&pred_idx) = block_idx_map.get(pred_name.as_str()) {
let pred_block = &mut mf.blocks[pred_idx];
let insert_pos = if pred_block.instructions.is_empty() {
0
} else {
pred_block.instructions.len() - 1
};
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(*phi_vr);
mov.size = 4; if let Some(vr) = input_vr {
mov.push_reg(*vr);
} else {
mov.push_imm(imm_val);
}
pred_block.instructions.insert(insert_pos, mov);
}
}
}
}
fn select_ir_instruction(
mbb: &mut MachineBasicBlock,
inst: &std::cell::Ref<'_, Value>,
vreg_map: &mut HashMap<usize, VirtReg>,
mf: &mut MachineFunction,
alloca_offsets: &HashMap<usize, i64>,
) {
if mf.name.contains("__divXf3__") && inst.get_opcode() == Some(Opcode::AShr) {
let rhs = inst.operands.get(1).map(|o| o.borrow().name.clone());
eprintln!("ASHR_TRACE vid={} ty={:?} rhs={:?}", inst.vid, inst.ty, rhs);
}
let opcode = inst.get_opcode();
let num_ops = inst.operands.len();
match opcode {
Some(Opcode::Ret) => {
if num_ops == 0 {
mbb.instructions.push(MachineInstr::new(x86_opcodes::RET));
} else {
let val = &inst.operands[0];
let val_vid = val.borrow().vid as usize;
let ret_ty = val.borrow().ty.clone();
let ret_bw = ret_ty.integer_bit_width();
let size = if ret_bw == 32 || ret_bw == 1 {
4
} else if ret_ty.is_pointer() {
eprintln!(
"ISEL_RET_PTR: func={} ret_ty=pointer size=0 (64-bit)",
mf.name
);
0
} else if ret_bw == 64 || ret_bw == 0 {
0
} else {
0
};
let is_fp_ret = ret_ty.is_floating_point();
if let Some(&vr) = vreg_map.get(&val_vid) {
if is_fp_ret {
let is_double = ret_ty.is_double_ty();
let mut mi = MachineInstr::new(if is_double {
x86_opcodes::MOVQ
} else {
x86_opcodes::MOVD
});
mi.push_reg(vr); mi.operands.push(MachineOperand::PhysReg(64)); mbb.instructions.push(mi);
} else {
let mut mi = MachineInstr::new(x86_opcodes::MOV);
mi.operands.push(MachineOperand::PhysReg(0)); mi.push_reg(vr);
mi.size = size;
mbb.instructions.push(mi);
}
} else if val.borrow().is_constant() {
if is_fp_ret {
let is_double = ret_ty.is_double_ty();
let imm_val: i64 = val.borrow().name.parse().unwrap_or(0);
let mut mov = MachineInstr::new(x86_opcodes::MOV);
mov.operands.push(MachineOperand::PhysReg(0)); mov.push_imm(imm_val);
mov.size = if is_double { 0u8 } else { 4u8 };
mbb.instructions.push(mov);
let mut movd = MachineInstr::new(if is_double {
x86_opcodes::MOVQ
} else {
x86_opcodes::MOVD
});
movd.operands.push(MachineOperand::PhysReg(0)); movd.operands.push(MachineOperand::PhysReg(64)); mbb.instructions.push(movd);
} else {
let imm_val: i64 = val.borrow().name.parse().unwrap_or(0);
let mut mi = MachineInstr::new(x86_opcodes::MOV);
mi.operands.push(MachineOperand::PhysReg(0)); mi.push_imm(imm_val);
mi.size = size;
mbb.instructions.push(mi);
}
}
mbb.instructions.push(MachineInstr::new(x86_opcodes::RET));
}
}
Some(Opcode::Br) => {
if num_ops == 1 {
let dest = inst.operands[0].borrow().name.clone();
mbb.successors.push(0);
let mut mi = MachineInstr::new(x86_opcodes::JMP);
mi.push_label(&dest);
mbb.instructions.push(mi);
} else if num_ops == 3 {
let cond_vid = inst.operands[0].borrow().vid as usize;
let t_label = inst.operands[1].borrow().name.clone();
let f_label = inst.operands[2].borrow().name.clone();
mbb.successors.push(0);
mbb.successors.push(0);
if let Some(&cond_vr) = vreg_map.get(&cond_vid) {
let mut cmp = MachineInstr::new(x86_opcodes::CMP);
cmp.push_reg(cond_vr);
cmp.push_imm(0);
mbb.instructions.push(cmp);
}
let mut jne = MachineInstr::new(x86_opcodes::JNE);
jne.push_label(&t_label);
mbb.instructions.push(jne);
let mut jmp = MachineInstr::new(x86_opcodes::JMP);
jmp.push_label(&f_label);
mbb.instructions.push(jmp);
}
}
Some(Opcode::Unreachable) => {
}
Some(Opcode::Mul) => {
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
let op0_vid = inst.operands[0].borrow().vid as usize;
let op1_vid = inst.operands[1].borrow().vid as usize;
let is_64bit = inst.ty.integer_bit_width() == 64;
let mul_size: u8 = if is_64bit { 0 } else { 4 };
if let Some(def) = def_vr {
let src0_vr = if let Some(vr) = vreg_map.get(&op0_vid).copied() {
Some(vr)
} else if op0_vid > 0 {
let vr = mf.new_vreg();
let imm_val: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(vr);
mov.size = mul_size;
mov.push_imm(imm_val);
mbb.instructions.push(mov);
Some(vr)
} else {
None
};
let src1_vr = if let Some(vr) = vreg_map.get(&op1_vid).copied() {
Some(vr)
} else if op1_vid > 0 {
let vr = mf.new_vreg();
let imm_val: i64 = inst.operands[1].borrow().name.parse().unwrap_or(0);
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(vr);
mov.size = mul_size;
mov.push_imm(imm_val);
mbb.instructions.push(mov);
Some(vr)
} else {
None
};
let op0_is_const = inst.operands[0].borrow().is_constant();
let op1_is_const = inst.operands[1].borrow().is_constant();
if op0_is_const && op1_is_const {
let v0: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
let v1: i64 = inst.operands[1].borrow().name.parse().unwrap_or(0);
let result = v0.wrapping_mul(v1);
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.size = mul_size;
mi.push_imm(result);
mbb.instructions.push(mi);
} else if let (Some(s0), Some(s1)) = (src0_vr, src1_vr) {
let mut mov_src0 = MachineInstr::new(x86_opcodes::MOV);
mov_src0.size = mul_size;
mov_src0.push_reg(def); mov_src0.push_reg(s0); mbb.instructions.push(mov_src0);
let mut imul = MachineInstr::new(x86_opcodes::IMUL).with_def(def);
imul.size = mul_size;
imul.push_reg(s1); mbb.instructions.push(imul);
}
}
}
Some(Opcode::Add) | Some(Opcode::Sub) | Some(Opcode::And) | Some(Opcode::Or)
| Some(Opcode::Xor) | Some(Opcode::Shl) | Some(Opcode::LShr) | Some(Opcode::AShr) => {
let mc_opcode = ir_binop_to_x86(opcode.unwrap());
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
let op_size = if inst.ty.is_pointer() || inst.get_opcode() == Some(Opcode::Alloca) {
let mut fallback_size = 0u8;
if let Some(op0) = inst.operands.first() {
let op0_ty = &op0.borrow().ty;
if !op0_ty.is_pointer() {
fallback_size = match op0_ty.integer_bit_width() {
32 | 1 => 4,
64 => 0,
_ => 0,
};
}
}
if fallback_size == 0 && inst.operands.len() > 1 {
let first_is_ptr = inst
.operands
.first()
.map_or(false, |op| op.borrow().ty.is_pointer());
if first_is_ptr {
fallback_size = 0; } else {
let op1_ty = &inst.operands[1].borrow().ty;
if !op1_ty.is_pointer() {
fallback_size = match op1_ty.integer_bit_width() {
32 | 1 => 4,
64 => 0,
_ => 0,
};
}
}
}
let is_shift_op =
matches!(opcode.unwrap(), Opcode::Shl | Opcode::LShr | Opcode::AShr);
if is_shift_op {
if fallback_size != 8 {
if let Some(op0) = inst.operands.first() {
let op0_ty = &op0.borrow().ty;
if op0_ty.is_pointer() {
fallback_size = 8;
} else if op0_ty.integer_bit_width() == 64 {
fallback_size = 8;
}
}
}
}
fallback_size
} else {
let is_shift_op =
matches!(opcode.unwrap(), Opcode::Shl | Opcode::LShr | Opcode::AShr);
if is_shift_op {
let mut shift_size = 4u8;
if let Some(op0) = inst.operands.first() {
let op0_bw = op0.borrow().ty.integer_bit_width();
if op0_bw == 64 {
shift_size = 8;
}
}
if shift_size == 4 && inst.operands.len() > 1 {
if let Ok(imm) = inst.operands[1].borrow().name.parse::<u64>() {
if imm >= 32 {
shift_size = 8;
}
}
}
shift_size
} else {
match inst.ty.integer_bit_width() {
32 | 1 => 4,
64 => 0,
_ => 0,
}
}
};
let op0_vid = inst.operands[0].borrow().vid as usize;
let op1 = &inst.operands[1];
let src_vr = vreg_map.get(&op0_vid).copied();
if let Some(def) = def_vr {
if src_vr.is_none() && op1.borrow().is_constant() {
let src0: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
let src1: i64 = op1.borrow().name.parse().unwrap_or(0);
let result = match opcode.unwrap() {
Opcode::Add => src0 + src1,
Opcode::Sub => src0 - src1,
Opcode::Mul => src0 * src1,
Opcode::And => src0 & src1,
Opcode::Or => src0 | src1,
Opcode::Xor => src0 ^ src1,
Opcode::Shl => src0 << src1,
Opcode::LShr => ((src0 as u64) >> (src1 as u64)) as i64,
Opcode::AShr => src0 >> src1,
_ => src1,
};
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.size = op_size;
mi.push_imm(result);
mbb.instructions.push(mi);
} else {
if let Some(src) = src_vr {
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov.size = op_size;
mov.push_reg(src);
mbb.instructions.push(mov);
} else if !op1.borrow().is_constant() {
let src0: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov.size = op_size;
mov.push_imm(src0);
mbb.instructions.push(mov);
}
if op1.borrow().is_constant() {
let imm_val: i64 = op1.borrow().name.parse().unwrap_or(0);
let fits_in_signed32 =
imm_val >= (-0x8000_0000i64) && imm_val <= 0x7FFF_FFFF;
if op_size == 0 && !fits_in_signed32 {
let tmp = mf.new_vreg();
let mut load = MachineInstr::new(x86_opcodes::MOV).with_def(tmp);
load.size = 0; load.push_imm(imm_val);
mbb.instructions.push(load);
let mut mi = MachineInstr::new(mc_opcode);
mi.size = op_size;
mi.push_reg(tmp); mi.push_reg(def); mbb.instructions.push(mi);
} else {
let mut mi = MachineInstr::new(mc_opcode).with_def(def);
mi.size = op_size;
mi.push_imm(imm_val);
mbb.instructions.push(mi);
}
} else {
let op1_vid = op1.borrow().vid as usize;
if let Some(op1r) = vreg_map.get(&op1_vid).copied() {
let is_shift = matches!(
opcode.unwrap(),
Opcode::Shl | Opcode::LShr | Opcode::AShr
);
if is_shift {
let rcx_save = mf.new_vreg();
let mut save_rcx =
MachineInstr::new(x86_opcodes::MOV).with_def(rcx_save);
save_rcx.size = 0; save_rcx.operands.push(MachineOperand::PhysReg(1)); mbb.instructions.push(save_rcx);
let mut mov_to_cl = MachineInstr::new(x86_opcodes::MOV);
mov_to_cl.size = op_size;
mov_to_cl.operands.push(MachineOperand::PhysReg(1)); mov_to_cl.push_reg(op1r);
mbb.instructions.push(mov_to_cl);
let mut mi = MachineInstr::new(mc_opcode);
mi.size = op_size;
mi.push_reg(def); mi.operands.push(MachineOperand::PhysReg(1)); mbb.instructions.push(mi);
let mut restore_rcx = MachineInstr::new(x86_opcodes::MOV);
restore_rcx.size = 0; restore_rcx.operands.push(MachineOperand::PhysReg(1)); restore_rcx.push_reg(rcx_save);
mbb.instructions.push(restore_rcx);
} else {
let mut mi = MachineInstr::new(mc_opcode);
mi.size = op_size;
mi.push_reg(op1r); mi.push_reg(def); mbb.instructions.push(mi);
}
}
}
}
}
}
Some(Opcode::ICmp) => {
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
let op0_vid = inst.operands[0].borrow().vid as usize;
let op1 = &inst.operands[1];
let pred = if inst.name.starts_with("icmp.") {
let after_prefix = &inst.name[5..]; if let Some(dot) = after_prefix.find('.') {
&after_prefix[..dot]
} else {
after_prefix
}
} else if inst.name == "tobool" {
"ne"
} else {
"eq"
};
let cmp_op_bw = inst.operands[0].borrow().ty.integer_bit_width();
let cmp_size = if cmp_op_bw == 32 || cmp_op_bw == 1 {
4u8
} else {
0u8
};
let cmp_src_vr = if let Some(vr) = vreg_map.get(&op0_vid).copied() {
vr
} else if op0_vid > 0 {
let vr = mf.new_vreg();
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(vr);
mov.size = cmp_size;
let imm_val: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
mov.push_imm(imm_val);
mbb.instructions.push(mov);
vr
} else {
mf.new_vreg()
};
{
let mut cmp = MachineInstr::new(x86_opcodes::CMP);
cmp.size = cmp_size;
cmp.push_reg(cmp_src_vr);
let fits_in_i32 = |v: i64| v >= i32::MIN as i64 && v <= i32::MAX as i64;
if op1.borrow().is_constant() {
let imm_val: i64 = op1.borrow().name.parse().unwrap_or(0);
if fits_in_i32(imm_val) {
cmp.push_imm(imm_val);
} else {
let vr = mf.new_vreg();
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(vr);
mov.size = cmp_size;
mov.push_imm(imm_val);
mbb.instructions.push(mov);
cmp.push_reg(vr);
}
} else if let Some(&op1_vr) = vreg_map.get(&(op1.borrow().vid as usize)) {
cmp.push_reg(op1_vr);
} else {
let imm_val: i64 = op1.borrow().name.parse().unwrap_or(0);
if fits_in_i32(imm_val) {
cmp.push_imm(imm_val);
} else {
let vr = mf.new_vreg();
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(vr);
mov.size = cmp_size;
mov.push_imm(imm_val);
mbb.instructions.push(mov);
cmp.push_reg(vr);
}
}
mbb.instructions.push(cmp);
}
let setcc_op = match pred {
"eq" => x86_opcodes::SETE,
"ne" => x86_opcodes::SETNE,
"sgt" => x86_opcodes::SETG,
"sge" => x86_opcodes::SETGE,
"slt" => x86_opcodes::SETL,
"sle" => x86_opcodes::SETLE,
"ugt" => x86_opcodes::SETA,
"uge" => x86_opcodes::SETAE,
"ult" => x86_opcodes::SETB,
"ule" => x86_opcodes::SETBE,
_ => x86_opcodes::SETE,
};
if let Some(def) = def_vr {
let mut set = MachineInstr::new(setcc_op).with_def(def);
mbb.instructions.push(set);
let mut zx = MachineInstr::new(x86_opcodes::MOVZX).with_def(def);
zx.push_reg(def);
mbb.instructions.push(zx);
}
}
Some(Opcode::Alloca) => {
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
if let Some(def) = def_vr {
let ptr_size: u8 = 0;
let byte_size: i64 = inst
.operands
.first()
.and_then(|v| v.borrow().name.parse::<i64>().ok())
.unwrap_or(8)
.max(1);
let aligned_size = ((byte_size + 7) / 8) * 8;
mf.alloca_byte_offset += aligned_size;
let offset = mf.alloca_byte_offset;
mf.alloca_offsets.insert(inst.vid as usize, offset);
eprintln!(
"ALLOCA_SLOT func={} ir_vid={} name='{}' size={} aligned={} offset=rbp-0x{:x}",
mf.name,
inst.vid,
inst.name,
byte_size,
aligned_size,
offset
);
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov.size = ptr_size;
mov.operands.push(MachineOperand::PhysReg(5)); mbb.instructions.push(mov);
let mut sub = MachineInstr::new(x86_opcodes::SUB).with_def(def);
sub.size = ptr_size;
sub.push_imm(offset);
mbb.instructions.push(sub);
}
}
Some(Opcode::Load) => {
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
let ptr_vid = inst.operands[0].borrow().vid as usize;
let load_size = if inst.ty.is_floating_point() {
if inst.ty.is_double_ty() {
0u8
} else {
4u8
} } else if inst.ty.integer_bit_width() == 8 {
1u8 } else if inst.ty.integer_bit_width() == 32 || inst.ty.integer_bit_width() == 1 {
4u8 } else {
0u8
};
if let Some(def) = def_vr {
if let Some(&offset) = mf.alloca_offsets.get(&ptr_vid) {
eprintln!(
"DIRECT_ALLOCA_LOAD func={} ptr_vid={} offset=rbp-0x{:x} size={}",
mf.name, ptr_vid, offset, load_size
);
let mut mov_addr = MachineInstr::new(x86_opcodes::MOV);
mov_addr.operands.push(MachineOperand::PhysReg(12)); mov_addr.operands.push(MachineOperand::PhysReg(5)); mov_addr.size = 0;
mbb.instructions.push(mov_addr);
let mut sub_addr = MachineInstr::new(x86_opcodes::SUB);
sub_addr.operands.push(MachineOperand::PhysReg(12));
sub_addr.push_imm(offset);
sub_addr.size = 0;
mbb.instructions.push(sub_addr);
let mut mi = MachineInstr::new(x86_opcodes::LOAD).with_def(def);
mi.size = load_size;
mi.operands.push(MachineOperand::PhysReg(12));
mbb.instructions.push(mi);
} else if let Some(&ptr_vr) = vreg_map.get(&ptr_vid) {
let ptr_val = inst.operands[0].borrow();
let is_global = ptr_val.is_global_variable()
|| ptr_val.subclass == SubclassKind::GlobalVariable;
if is_global {
let gv_name = ptr_val.name.clone();
let mut lea = MachineInstr::new(x86_opcodes::LEA);
lea.operands.push(MachineOperand::PhysReg(10));
lea.operands.push(MachineOperand::Global(gv_name));
mbb.instructions.push(lea);
let mut mi = MachineInstr::new(x86_opcodes::LOAD).with_def(def);
mi.size = load_size;
mi.operands.push(MachineOperand::PhysReg(10));
mbb.instructions.push(mi);
} else {
let mut mi = MachineInstr::new(x86_opcodes::LOAD).with_def(def);
mi.size = load_size;
mi.push_reg(ptr_vr);
mbb.instructions.push(mi);
}
}
}
}
Some(Opcode::Store) => {
let val_vid = inst.operands[0].borrow().vid as usize;
let ptr_vid = inst.operands[1].borrow().vid as usize;
let val_ty = inst.operands[0].borrow().ty.clone();
let store_size = if val_ty.is_floating_point() {
if val_ty.is_double_ty() {
0u8
} else {
4u8
}
} else if val_ty.integer_bit_width() == 8 {
1u8 } else if val_ty.integer_bit_width() == 32 || val_ty.integer_bit_width() == 1 {
4u8
} else {
0u8
};
if let Some(&offset) = mf.alloca_offsets.get(&ptr_vid) {
eprintln!(
"DIRECT_ALLOCA_STORE func={} ptr_vid={} offset=rbp-0x{:x} size={}",
mf.name, ptr_vid, offset, store_size
);
let mut mov_addr = MachineInstr::new(x86_opcodes::MOV);
mov_addr.operands.push(MachineOperand::PhysReg(12)); mov_addr.operands.push(MachineOperand::PhysReg(5)); mov_addr.size = 0;
mbb.instructions.push(mov_addr);
let mut sub_addr = MachineInstr::new(x86_opcodes::SUB);
sub_addr.operands.push(MachineOperand::PhysReg(12));
sub_addr.push_imm(offset);
sub_addr.size = 0;
mbb.instructions.push(sub_addr);
if let Some(&val_vr) = vreg_map.get(&val_vid) {
let mut mi = MachineInstr::new(x86_opcodes::STORE);
mi.size = store_size;
mi.operands.push(MachineOperand::PhysReg(12)); mi.push_reg(val_vr); mbb.instructions.push(mi);
} else if inst.operands[0].borrow().subclass == SubclassKind::Function {
let func_name = inst.operands[0].borrow().name.clone();
let mut lea = MachineInstr::new(x86_opcodes::LEA);
lea.operands.push(MachineOperand::PhysReg(10));
lea.operands.push(MachineOperand::Global(func_name));
mbb.instructions.push(lea);
let mut mi = MachineInstr::new(x86_opcodes::STORE);
mi.size = 0; mi.operands.push(MachineOperand::PhysReg(12)); mi.operands.push(MachineOperand::PhysReg(10)); mbb.instructions.push(mi);
} else if inst.operands[0].borrow().is_global_variable() {
let gv_name = inst.operands[0].borrow().name.clone();
let mut lea = MachineInstr::new(x86_opcodes::LEA);
lea.operands.push(MachineOperand::PhysReg(10));
lea.operands.push(MachineOperand::Global(gv_name));
mbb.instructions.push(lea);
let mut mi = MachineInstr::new(x86_opcodes::STORE);
mi.size = 0; mi.operands.push(MachineOperand::PhysReg(12)); mi.operands.push(MachineOperand::PhysReg(10)); mbb.instructions.push(mi);
} else if inst.operands[0].borrow().is_constant() {
let imm_val: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
let tmp_vr = mf.new_vreg();
let mov_size: u8 = if store_size == 1 { 4 } else { store_size };
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(tmp_vr);
mov.size = mov_size;
mov.push_imm(imm_val);
mbb.instructions.push(mov);
let mut mi = MachineInstr::new(x86_opcodes::STORE);
mi.size = store_size;
mi.operands.push(MachineOperand::PhysReg(12)); mi.push_reg(tmp_vr); mbb.instructions.push(mi);
}
} else if let Some(&ptr_vr) = vreg_map.get(&ptr_vid) {
if let Some(&val_vr) = vreg_map.get(&val_vid) {
let mut mi = MachineInstr::new(x86_opcodes::STORE);
mi.size = store_size;
mi.push_reg(ptr_vr);
mi.push_reg(val_vr);
mbb.instructions.push(mi);
} else if inst.operands[0].borrow().subclass == SubclassKind::Function {
let func_name = inst.operands[0].borrow().name.clone();
let mut lea = MachineInstr::new(x86_opcodes::LEA);
lea.operands.push(MachineOperand::PhysReg(10));
lea.operands.push(MachineOperand::Global(func_name));
mbb.instructions.push(lea);
let mut mi = MachineInstr::new(x86_opcodes::STORE);
mi.size = 0; mi.push_reg(ptr_vr);
mi.operands.push(MachineOperand::PhysReg(10));
mbb.instructions.push(mi);
} else if inst.operands[0].borrow().is_global_variable() {
let gv_name = inst.operands[0].borrow().name.clone();
let mut lea = MachineInstr::new(x86_opcodes::LEA);
lea.operands.push(MachineOperand::PhysReg(10));
lea.operands.push(MachineOperand::Global(gv_name));
mbb.instructions.push(lea);
let mut mi = MachineInstr::new(x86_opcodes::STORE);
mi.size = 0; mi.push_reg(ptr_vr);
mi.operands.push(MachineOperand::PhysReg(10));
mbb.instructions.push(mi);
} else if inst.operands[0].borrow().is_constant() {
let imm_val: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
let tmp_vr = mf.new_vreg();
let mov_size: u8 = if store_size == 1 { 4 } else { store_size };
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(tmp_vr);
mov.size = mov_size;
mov.push_imm(imm_val);
mbb.instructions.push(mov);
let mut mi = MachineInstr::new(x86_opcodes::STORE);
mi.size = store_size;
mi.push_reg(ptr_vr);
mi.push_reg(tmp_vr);
mbb.instructions.push(mi);
}
}
}
Some(Opcode::Call) => {
if num_ops >= 1 {
let func_name = inst.operands[0].borrow().name.clone();
eprintln!(
"ISel Call: func='{}' num_ops={}",
func_name,
inst.operands.len()
);
let int_abi_regs: [PhysReg; 6] = [7, 6, 2, 1, 8, 9];
let mut int_arg_idx = 0usize;
let mut fp_arg_idx = 0usize;
let mut num_int_arg_captures = 0usize;
for op in inst.operands.iter().skip(1) {
let ty = op.borrow().ty.clone();
if ty.is_floating_point() {
if fp_arg_idx >= 8 {
break; }
let xmm_reg = 64 + fp_arg_idx as u32;
let is_double = ty.is_double_ty();
let mut mov = MachineInstr::new(if is_double {
x86_opcodes::MOVQ
} else {
x86_opcodes::MOVD
});
let arg_vid = op.borrow().vid as usize;
if let Some(&arg_vr) = vreg_map.get(&arg_vid) {
mov.push_reg(arg_vr); mov.operands.push(MachineOperand::PhysReg(xmm_reg));
} else {
let imm_val: i64 = op.borrow().name.parse().unwrap_or(0);
let tmp_vr = mf.new_vreg();
let mut load = MachineInstr::new(x86_opcodes::MOV).with_def(tmp_vr);
load.size = if is_double { 0 } else { 4 };
load.push_imm(imm_val);
mbb.instructions.push(load);
mov.push_reg(tmp_vr); mov.operands.push(MachineOperand::PhysReg(xmm_reg));
}
mbb.instructions.push(mov);
fp_arg_idx += 1;
} else {
if int_arg_idx >= int_abi_regs.len() {
break; }
let dst_phys = int_abi_regs[int_arg_idx];
let arg_vid = op.borrow().vid as usize;
let arg_bw = ty.integer_bit_width();
let arg_size: u8 = if arg_bw > 0 && arg_bw <= 32 { 4 } else { 0 };
let arg_vid = op.borrow().vid as usize;
if op.borrow().is_global_variable() {
let gv_name = op.borrow().name.clone();
let mut lea = MachineInstr::new(x86_opcodes::LEA);
lea.operands.push(MachineOperand::PhysReg(10));
lea.operands.push(MachineOperand::Global(gv_name));
mbb.instructions.push(lea);
} else if !mf.name.starts_with("__") {
if let Some(&alloca_off) = mf.alloca_offsets.get(&arg_vid) {
let mut mov = MachineInstr::new(x86_opcodes::MOV);
mov.operands.push(MachineOperand::PhysReg(10));
mov.operands.push(MachineOperand::PhysReg(5)); mov.size = 0; mbb.instructions.push(mov);
let mut sub = MachineInstr::new(x86_opcodes::SUB);
sub.operands.push(MachineOperand::PhysReg(10));
sub.push_imm(alloca_off);
sub.size = 0; mbb.instructions.push(sub);
} else if let Some(&arg_vr) = vreg_map.get(&arg_vid) {
let mut to_r10 = MachineInstr::new(x86_opcodes::MOV);
to_r10.operands.push(MachineOperand::PhysReg(10));
to_r10.push_reg(arg_vr);
to_r10.size = arg_size;
mbb.instructions.push(to_r10);
} else if op.borrow().is_constant() {
let imm_val: i64 = op.borrow().name.parse().unwrap_or(0);
let mut to_r10 = MachineInstr::new(x86_opcodes::MOV);
to_r10.operands.push(MachineOperand::PhysReg(10));
to_r10.push_imm(imm_val);
to_r10.size = arg_size;
mbb.instructions.push(to_r10);
} else {
let imm_val: i64 = op.borrow().name.parse().unwrap_or(0);
let mut to_r10 = MachineInstr::new(x86_opcodes::MOV);
to_r10.operands.push(MachineOperand::PhysReg(10));
to_r10.push_imm(imm_val);
to_r10.size = arg_size;
mbb.instructions.push(to_r10);
}
} else if let Some(&arg_vr) = vreg_map.get(&arg_vid) {
let mut to_r10 = MachineInstr::new(x86_opcodes::MOV);
to_r10.operands.push(MachineOperand::PhysReg(10));
to_r10.push_reg(arg_vr);
to_r10.size = arg_size;
mbb.instructions.push(to_r10);
} else if op.borrow().is_constant() {
let imm_val: i64 = op.borrow().name.parse().unwrap_or(0);
let mut to_r10 = MachineInstr::new(x86_opcodes::MOV);
to_r10.operands.push(MachineOperand::PhysReg(10));
to_r10.push_imm(imm_val);
to_r10.size = arg_size;
mbb.instructions.push(to_r10);
} else {
let imm_val: i64 = op.borrow().name.parse().unwrap_or(0);
let mut to_r10 = MachineInstr::new(x86_opcodes::MOV);
to_r10.operands.push(MachineOperand::PhysReg(10));
to_r10.push_imm(imm_val);
to_r10.size = arg_size;
mbb.instructions.push(to_r10);
}
let mut sub_rsp = MachineInstr::new(x86_opcodes::SUB);
sub_rsp.operands.push(MachineOperand::PhysReg(4)); sub_rsp.push_imm(8);
sub_rsp.size = 0; mbb.instructions.push(sub_rsp);
let mut store = MachineInstr::new(x86_opcodes::STORE);
store.operands.push(MachineOperand::PhysReg(4)); store.operands.push(MachineOperand::PhysReg(10)); store.size = 0; mbb.instructions.push(store);
num_int_arg_captures += 1;
int_arg_idx += 1;
}
}
for i in (0..num_int_arg_captures).rev() {
let dst_phys = int_abi_regs[i];
let mut load = MachineInstr::new(x86_opcodes::LOAD);
load.operands.push(MachineOperand::PhysReg(10)); load.operands.push(MachineOperand::PhysReg(4)); load.size = 0; mbb.instructions.push(load);
let mut to_abi = MachineInstr::new(x86_opcodes::MOV);
to_abi.operands.push(MachineOperand::PhysReg(dst_phys));
to_abi.operands.push(MachineOperand::PhysReg(10));
to_abi.size = 0;
mbb.instructions.push(to_abi);
let mut add_rsp = MachineInstr::new(x86_opcodes::ADD);
add_rsp.operands.push(MachineOperand::PhysReg(4)); add_rsp.push_imm(8);
add_rsp.size = 0; mbb.instructions.push(add_rsp);
}
let callee_val = inst.operands[0].borrow();
let is_direct = callee_val.subclass == SubclassKind::Function;
drop(callee_val);
if is_direct {
let mut mi = MachineInstr::new(x86_opcodes::CALL);
mi.operands.push(MachineOperand::Global(func_name));
mbb.instructions.push(mi);
} else {
let callee_vid = inst.operands[0].borrow().vid as usize;
if let Some(&callee_vr) = vreg_map.get(&callee_vid) {
let mut mi = MachineInstr::new(x86_opcodes::CALL);
mi.operands.push(MachineOperand::Reg(callee_vr));
mbb.instructions.push(mi);
} else {
let mut mi = MachineInstr::new(x86_opcodes::CALL);
mi.operands.push(MachineOperand::Global(func_name));
mbb.instructions.push(mi);
}
}
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
if let Some(def) = def_vr {
if !inst.ty.is_void() {
if inst.ty.is_floating_point() {
let is_double = inst.ty.is_double_ty();
let mut mov = MachineInstr::new(if is_double {
x86_opcodes::MOVQ_FROM_XMM
} else {
x86_opcodes::MOVD_FROM_XMM
});
mov.operands.push(MachineOperand::PhysReg(64)); mov.push_reg(def); mbb.instructions.push(mov);
} else {
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov.size = if inst.ty.integer_bit_width() == 32
|| inst.ty.integer_bit_width() == 1
{
4
} else {
0
};
mov.operands.push(MachineOperand::PhysReg(0)); mbb.instructions.push(mov);
}
}
}
}
}
Some(Opcode::GetElementPtr) => {
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
let ptr_val = inst.operands[0].borrow();
if !ptr_val.name.is_empty() && ptr_val.is_global_variable() {
if let Some(def) = def_vr {
let mut mi = MachineInstr::new(x86_opcodes::LEA);
mi.operands.push(MachineOperand::PhysReg(10));
mi.operands
.push(MachineOperand::Global(ptr_val.name.clone()));
mbb.instructions.push(mi);
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov.operands.push(MachineOperand::PhysReg(10));
mbb.instructions.push(mov);
}
} else if let Some(def) = def_vr {
let ptr_vid = ptr_val.vid as usize;
if let Some(&ptr_vr) = vreg_map.get(&ptr_vid) {
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.push_reg(ptr_vr);
mbb.instructions.push(mi);
}
}
}
Some(Opcode::Phi) => {
}
Some(Opcode::ZExt)
| Some(Opcode::SExt)
| Some(Opcode::Trunc)
| Some(Opcode::BitCast)
| Some(Opcode::PtrToInt)
| Some(Opcode::IntToPtr) => {
let src_vid = inst.operands[0].borrow().vid as usize;
let src_ty = inst.operands[0].borrow().ty.clone();
let dst_ty = inst.ty.clone();
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
if let Some(def) = def_vr {
if let Some(&src_vr) = vreg_map.get(&src_vid) {
let src_bits = src_ty.integer_bit_width();
let dst_bits = dst_ty.integer_bit_width();
match opcode.unwrap() {
Opcode::ZExt => {
if dst_bits == 64 && src_bits > 0 && src_bits <= 32 {
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.size = 4;
mi.push_reg(src_vr);
mbb.instructions.push(mi);
} else {
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.size = match dst_bits {
32 | 1 => 4,
64 => 0,
_ => 0,
};
mi.push_reg(src_vr);
mbb.instructions.push(mi);
}
}
Opcode::SExt => {
if dst_bits == 64 && src_bits == 32 {
let mut mi =
MachineInstr::new(x86_opcodes::MOVSXD).with_def(def);
mi.push_reg(src_vr);
mbb.instructions.push(mi);
} else if src_bits == 8 && (dst_bits == 32 || dst_bits == 64) {
let mut mi =
MachineInstr::new(x86_opcodes::MOVSX).with_def(def);
mi.size = match dst_bits {
32 => 4,
64 => 0,
_ => 0,
};
mi.push_reg(src_vr);
mbb.instructions.push(mi);
} else {
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.size = match dst_bits {
32 | 1 => 4,
64 => 0,
_ => 0,
};
mi.push_reg(src_vr);
mbb.instructions.push(mi);
}
}
Opcode::Trunc => {
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.size = match dst_bits {
32 | 1 => 4,
64 => 0,
_ => 4,
};
mi.push_reg(src_vr);
mbb.instructions.push(mi);
}
Opcode::BitCast | Opcode::PtrToInt | Opcode::IntToPtr => {
let emitted = if opcode == Some(Opcode::PtrToInt)
|| opcode == Some(Opcode::BitCast)
{
let src_val = inst.operands[0].borrow();
let src_vid = src_val.vid as usize;
if src_val.is_global_variable()
|| src_val.subclass == SubclassKind::Function
{
let gv_name = src_val.name.clone();
let mut mi = MachineInstr::new(x86_opcodes::LEA);
mi.operands.push(MachineOperand::PhysReg(10));
mi.operands.push(MachineOperand::Global(gv_name));
mbb.instructions.push(mi);
let mut mov =
MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov.operands.push(MachineOperand::PhysReg(10));
mbb.instructions.push(mov);
true
} else if mf.alloca_offsets.contains_key(&src_vid)
&& !mf.name.starts_with("__")
{
let offset = mf.alloca_offsets[&src_vid];
let mut mov_rbp = MachineInstr::new(x86_opcodes::MOV);
mov_rbp.operands.push(MachineOperand::PhysReg(10));
mov_rbp.operands.push(MachineOperand::PhysReg(5));
mov_rbp.size = 0; mbb.instructions.push(mov_rbp);
let mut sub = MachineInstr::new(x86_opcodes::SUB);
sub.operands.push(MachineOperand::PhysReg(10));
sub.push_imm(offset);
sub.size = 0; mbb.instructions.push(sub);
let mut mov =
MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov.operands.push(MachineOperand::PhysReg(10));
mbb.instructions.push(mov);
true
} else {
false
}
} else {
false
};
if !emitted {
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.size = match dst_bits {
32 | 1 => 4,
64 => 0,
_ => 0,
};
mi.push_reg(src_vr);
mbb.instructions.push(mi);
}
}
_ => unreachable!(),
}
} else if inst.operands[0].borrow().is_constant() {
let imm_val: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
let mut mi = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mi.size = match dst_ty.integer_bit_width() {
32 | 1 => 4,
64 => 0,
_ => 0,
};
mi.push_imm(imm_val);
mbb.instructions.push(mi);
}
}
}
Some(Opcode::UDiv) | Some(Opcode::SDiv) | Some(Opcode::URem) | Some(Opcode::SRem) => {
let is_rem = matches!(opcode, Some(Opcode::URem) | Some(Opcode::SRem));
let is_signed = matches!(opcode, Some(Opcode::SDiv) | Some(Opcode::SRem));
let is_unsigned = matches!(opcode, Some(Opcode::UDiv) | Some(Opcode::URem));
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
let op0_vid = inst.operands[0].borrow().vid as usize;
let op0_ty = inst.operands[0].borrow().ty.clone();
let op_size = match op0_ty.integer_bit_width() {
32 | 1 => 4u8,
64 => 0u8,
_ => 0u8,
};
if let Some(def) = def_vr {
let src0_vr = if let Some(vr) = vreg_map.get(&op0_vid).copied() {
vr
} else if op0_vid > 0 {
let vr = mf.new_vreg();
let imm_val: i64 = inst.operands[0].borrow().name.parse().unwrap_or(0);
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(vr);
mov.size = op_size;
mov.push_imm(imm_val);
mbb.instructions.push(mov);
vr
} else {
return;
};
let src1_vr = if inst.operands.len() > 1 {
let op1_vid = inst.operands[1].borrow().vid as usize;
if let Some(vr) = vreg_map.get(&op1_vid).copied() {
vr
} else if op1_vid > 0 {
let vr = mf.new_vreg();
let imm_val: i64 = inst.operands[1].borrow().name.parse().unwrap_or(0);
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(vr);
mov.size = op_size;
mov.push_imm(imm_val);
mbb.instructions.push(mov);
vr
} else {
return;
}
} else {
return;
};
let mut mov_eax = MachineInstr::new(x86_opcodes::MOV);
mov_eax.operands.push(MachineOperand::PhysReg(0)); mov_eax.size = op_size;
mov_eax.push_reg(src0_vr);
mbb.instructions.push(mov_eax);
if is_unsigned {
let mut xor_edx = MachineInstr::new(x86_opcodes::XOR);
xor_edx.operands.push(MachineOperand::PhysReg(2)); xor_edx.operands.push(MachineOperand::PhysReg(2));
xor_edx.size = op_size;
mbb.instructions.push(xor_edx);
} else {
let ext_op = if op_size == 0 {
x86_opcodes::CQO
} else {
x86_opcodes::CDQ
};
mbb.instructions.push(MachineInstr::new(ext_op));
}
let div_opcode = if is_unsigned {
x86_opcodes::DIV
} else {
x86_opcodes::IDIV
};
let mut div = MachineInstr::new(div_opcode);
div.size = op_size;
div.push_reg(src1_vr);
mbb.instructions.push(div);
let src_reg = if is_rem {
2 } else {
0 };
let mut mov_def = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov_def.size = op_size;
mov_def.operands.push(MachineOperand::PhysReg(src_reg));
mbb.instructions.push(mov_def);
}
}
Some(Opcode::Select) => {
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
if def_vr.is_none() {
return;
}
let def = def_vr.unwrap();
let cond_vid = inst.operands[0].borrow().vid as usize;
let true_vid = inst.operands[1].borrow().vid as usize;
let false_vid = inst.operands[2].borrow().vid as usize;
let data_ty = inst.operands[1].borrow().ty.clone();
let op_size = match data_ty.integer_bit_width() {
32 | 1 => 4u8,
64 => 0u8,
_ => 0u8,
};
let cond_vr = vreg_map.get(&cond_vid).copied();
let true_vr = vreg_map.get(&true_vid).copied();
let false_vr = vreg_map.get(&false_vid).copied();
if let (Some(cond_vr), Some(true_vr), Some(false_vr)) = (cond_vr, true_vr, false_vr)
{
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov.size = op_size;
mov.push_reg(false_vr);
mbb.instructions.push(mov);
let mut cmp = MachineInstr::new(x86_opcodes::CMP);
cmp.size = 4; cmp.push_reg(cond_vr);
cmp.push_imm(0);
mbb.instructions.push(cmp);
let mut cmov = MachineInstr::new(x86_opcodes::CMOVNE).with_def(def);
cmov.size = op_size;
cmov.push_reg(true_vr);
mbb.instructions.push(cmov);
}
}
Some(Opcode::FAdd) | Some(Opcode::FSub) | Some(Opcode::FMul) | Some(Opcode::FDiv)
| Some(Opcode::FRem) => {
let is_double = inst.ty.is_double_ty();
let mc_opcode = ir_fp_binop_to_x86(opcode.unwrap(), is_double);
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
let op0_vid = inst.operands[0].borrow().vid as usize;
if let Some(def) = def_vr {
let xmm_def = Self::ensure_fp_vreg(mbb, def, is_double, mf);
let src0_xmm = if let Some(&src0) = vreg_map.get(&op0_vid) {
Self::ensure_fp_vreg(mbb, src0, is_double, mf)
} else if inst.operands[0].borrow().is_constant() {
let tmp = mf.new_vreg();
let op0_borrowed = inst.operands[0].borrow();
Self::materialize_fp_constant(mbb, tmp, &op0_borrowed, mf);
tmp
} else {
def
};
if src0_xmm != def {
let mut mov = MachineInstr::new(if is_double {
x86_opcodes::MOVSD
} else {
x86_opcodes::MOVSS
});
mov.push_reg(src0_xmm); mov.push_reg(xmm_def); mbb.instructions.push(mov);
}
let op1 = &inst.operands[1];
let op1_borrowed = op1.borrow();
let src1_xmm = if op1_borrowed.is_constant() {
let tmp = mf.new_vreg();
Self::materialize_fp_constant(mbb, tmp, &op1_borrowed, mf);
tmp
} else {
let op1_vid = op1.borrow().vid as usize;
if let Some(&src1) = vreg_map.get(&op1_vid) {
Self::ensure_fp_vreg(mbb, src1, is_double, mf)
} else {
xmm_def
}
};
if opcode.unwrap() != Opcode::FRem {
let mut mi = MachineInstr::new(mc_opcode);
mi.push_reg(src1_xmm); mi.push_reg(xmm_def); mbb.instructions.push(mi);
}
let gpr_vreg = mf.new_vreg();
let mut extract = MachineInstr::new(if is_double {
x86_opcodes::MOVQ_FROM_XMM
} else {
x86_opcodes::MOVD_FROM_XMM
});
extract.push_reg(xmm_def);
extract.push_reg(gpr_vreg);
mbb.instructions.push(extract);
vreg_map.insert(inst.vid as usize, gpr_vreg);
}
}
Some(Opcode::FCmp) => {
let def_vr = vreg_map.get(&(inst.vid as usize)).copied();
let op0_vid = inst.operands[0].borrow().vid as usize;
let op1 = &inst.operands[1];
let pred = if inst.name.starts_with("fcmp.") {
let after = &inst.name[5..];
if let Some(dot) = after.find('.') {
&after[..dot]
} else {
after
}
} else {
"oeq"
};
let is_double = inst.operands[0].borrow().ty.is_double_ty();
let cmp_opcode = if is_double {
x86_opcodes::UCOMISD
} else {
x86_opcodes::UCOMISS
};
fn gpr_to_xmm(
mbb: &mut MachineBasicBlock,
mf: &mut MachineFunction,
gpr_vr: VirtReg,
is_double: bool,
) -> VirtReg {
let xmm_vr = mf.new_vreg();
let movx_op = if is_double {
x86_opcodes::MOVQ
} else {
x86_opcodes::MOVD
};
let mut movx = MachineInstr::new(movx_op);
movx.operands.push(MachineOperand::Reg(gpr_vr));
movx.operands.push(MachineOperand::Reg(xmm_vr));
mbb.instructions.push(movx);
xmm_vr
}
let src0_vreg = if let Some(&vr) = vreg_map.get(&op0_vid) {
gpr_to_xmm(mbb, mf, vr, is_double)
} else if inst.operands[0].borrow().is_constant() {
let gpr_tmp = mf.new_vreg();
let op0_bor = inst.operands[0].borrow();
Self::materialize_fp_constant(mbb, gpr_tmp, &op0_bor, mf);
drop(op0_bor);
gpr_to_xmm(mbb, mf, gpr_tmp, is_double)
} else {
return;
};
let src1_vreg = if let Some(&vr) = vreg_map.get(&(op1.borrow().vid as usize)) {
gpr_to_xmm(mbb, mf, vr, is_double)
} else if op1.borrow().is_constant() {
let gpr_tmp = mf.new_vreg();
let op1_bor = op1.borrow();
Self::materialize_fp_constant(mbb, gpr_tmp, &op1_bor, mf);
drop(op1_bor);
gpr_to_xmm(mbb, mf, gpr_tmp, is_double)
} else {
return;
};
let mut cmp = MachineInstr::new(cmp_opcode);
cmp.push_reg(src1_vreg); cmp.push_reg(src0_vreg); mbb.instructions.push(cmp);
let setcc_op = fp_pred_to_setcc(pred);
if let Some(def) = def_vr {
let mut set = MachineInstr::new(setcc_op).with_def(def);
mbb.instructions.push(set);
let mut zx = MachineInstr::new(x86_opcodes::MOVZX).with_def(def);
zx.push_reg(def);
mbb.instructions.push(zx);
}
}
_ => {
}
}
}
fn ensure_fp_vreg(
mbb: &mut MachineBasicBlock,
vreg: VirtReg,
_is_double: bool,
_mf: &mut MachineFunction,
) -> VirtReg {
vreg
}
fn materialize_fp_constant(
mbb: &mut MachineBasicBlock,
def: VirtReg,
operand: &std::cell::Ref<'_, Value>,
mf: &mut MachineFunction,
) {
let val_str = operand.name.clone();
let is_double = operand.ty.is_double_ty();
let bits: i64 = val_str.parse().unwrap_or(0);
let mut mov = MachineInstr::new(x86_opcodes::MOV);
mov.operands.push(MachineOperand::PhysReg(0)); mov.size = if is_double { 0u8 } else { 4u8 };
mov.push_imm(bits);
mbb.instructions.push(mov);
let mut mov_def = MachineInstr::new(x86_opcodes::MOV).with_def(def);
mov_def.operands.push(MachineOperand::PhysReg(0)); mbb.instructions.push(mov_def);
}
}
fn ir_fp_binop_to_x86(op: Opcode, is_double: bool) -> u32 {
match op {
Opcode::FAdd => {
if is_double {
x86_opcodes::ADDSD
} else {
x86_opcodes::ADDSS
}
}
Opcode::FSub => {
if is_double {
x86_opcodes::SUBSD
} else {
x86_opcodes::SUBSS
}
}
Opcode::FMul => {
if is_double {
x86_opcodes::MULSD
} else {
x86_opcodes::MULSS
}
}
Opcode::FDiv => {
if is_double {
x86_opcodes::DIVSD
} else {
x86_opcodes::DIVSS
}
}
_ => {
if is_double {
x86_opcodes::ADDSD
} else {
x86_opcodes::ADDSS
}
}
}
}
fn fp_pred_to_setcc(pred: &str) -> u32 {
match pred {
"oeq" | "ueq" => x86_opcodes::SETE,
"one" | "une" => x86_opcodes::SETNE,
"olt" | "ult" => x86_opcodes::SETB,
"ole" | "ule" => x86_opcodes::SETBE,
"ogt" | "ugt" => x86_opcodes::SETA,
"oge" | "uge" => x86_opcodes::SETAE,
"ord" => x86_opcodes::SETNP, "uno" => x86_opcodes::SETP, _ => x86_opcodes::SETE,
}
}
fn ir_binop_to_x86(op: Opcode) -> u32 {
match op {
Opcode::Add => x86_opcodes::ADD,
Opcode::Sub => x86_opcodes::SUB,
Opcode::Mul => x86_opcodes::MUL, Opcode::And => x86_opcodes::AND,
Opcode::Or => x86_opcodes::OR,
Opcode::Xor => x86_opcodes::XOR,
Opcode::Shl => x86_opcodes::SHL,
Opcode::LShr => x86_opcodes::SHR,
Opcode::AShr => x86_opcodes::SAR,
_ => x86_opcodes::ADD, }
}
pub struct RegisterAllocator {
pub assignments: HashMap<VirtReg, PhysReg>,
available: Vec<PhysReg>,
}
impl RegisterAllocator {
pub fn new() -> Self {
Self {
assignments: HashMap::new(),
available: vec![3, 12, 13, 14, 15, 0, 1, 2, 6, 7, 8, 9, 10, 11],
}
}
pub fn allocate(&mut self, mf: &mut MachineFunction) {
let mut all_vregs: Vec<VirtReg> = Vec::new();
for bb in &mf.blocks {
for mi in &bb.instructions {
if let Some(def) = mi.def {
if !all_vregs.contains(&def) {
all_vregs.push(def);
}
}
for op in &mi.operands {
if let MachineOperand::Reg(vr) = *op {
if !all_vregs.contains(&vr) {
all_vregs.push(vr);
}
}
}
}
}
let mut next_phys = 0usize;
for &vr in &all_vregs {
if !self.assignments.contains_key(&vr) {
let p = if next_phys < self.available.len() {
self.available[next_phys]
} else {
self.available[(vr.wrapping_mul(7) as usize) % self.available.len()]
};
self.assignments.insert(vr, p);
next_phys += 1;
}
}
for bb in &mut mf.blocks {
for mi in &mut bb.instructions {
if let Some(def) = mi.def {
if let Some(&phys) = self.assignments.get(&def) {
mi.operands.insert(0, MachineOperand::PhysReg(phys));
}
}
for op in &mut mi.operands {
if let MachineOperand::Reg(vr) = *op {
if let Some(&phys) = self.assignments.get(&vr) {
*op = MachineOperand::PhysReg(phys);
}
}
}
}
}
}
}
impl Default for RegisterAllocator {
fn default() -> Self {
Self::new()
}
}
pub struct AsmPrinter {
pub output: String,
}
impl AsmPrinter {
pub fn new() -> Self {
Self {
output: String::new(),
}
}
pub fn print_function(&mut self, mf: &MachineFunction) {
self.output.push_str(&format!("\t.globl {}\n", mf.name));
self.output
.push_str(&format!("\t.type {}, @function\n", mf.name));
self.output.push_str(&format!("{}:\n", mf.name));
for bb in &mf.blocks {
if !bb.name.is_empty() && bb.name != "entry" {
self.output.push_str(&format!(".L{}:\n", bb.name));
}
for mi in &bb.instructions {
self.print_instr(mi);
}
}
}
fn print_instr(&mut self, mi: &MachineInstr) {
let mnemonic = crate::mc_streamer::x86_mnemonic(mi.opcode);
self.output.push_str(&format!("\t{}", mnemonic));
let mut first = true;
for op in &mi.operands {
if first {
self.output.push('\t');
first = false;
} else {
self.output.push_str(", ");
}
match op {
MachineOperand::PhysReg(r) => self
.output
.push_str(&format!("%{}", crate::mc_streamer::x86_reg_name(*r))),
MachineOperand::Reg(r) => self.output.push_str(&format!("%%v{}", r)),
MachineOperand::Imm(i) => self.output.push_str(&format!("${}", i)),
MachineOperand::Label(l) => self.output.push_str(&format!(".L{}", l)),
MachineOperand::Global(g) => self.output.push_str(g),
}
}
self.output.push('\n');
}
}
impl Default for AsmPrinter {
fn default() -> Self {
Self::new()
}
}
pub fn compile_function(func: &ValueRef) -> String {
let f = func.borrow();
let mut mf = MachineFunction::new(&f.name);
InstructionSelector::select(&mut mf, func);
let mut ra = RegisterAllocator::new();
ra.allocate(&mut mf);
let mut printer = AsmPrinter::new();
printer.print_function(&mf);
printer.output
}
#[derive(Debug, Clone)]
pub struct TargetRegInfo {
pub gpr_count: u32,
pub fpr_count: u32,
pub reserved: Vec<PhysReg>,
pub callee_saved: Vec<PhysReg>,
pub caller_saved: Vec<PhysReg>,
pub reg_size: u32,
}
impl TargetRegInfo {
pub fn for_x86_64() -> Self {
Self {
gpr_count: 16,
fpr_count: 16,
reserved: vec![4], callee_saved: vec![3, 5, 12, 13, 14, 15], caller_saved: vec![0, 1, 2, 6, 7, 8, 9, 10, 11], reg_size: 8,
}
}
pub fn for_aarch64() -> Self {
Self {
gpr_count: 32,
fpr_count: 32,
reserved: vec![31], callee_saved: (19..=29).collect(), caller_saved: {
let mut v: Vec<u32> = (0..=18).collect();
v.push(30); v
},
reg_size: 8,
}
}
pub fn for_riscv64() -> Self {
Self {
gpr_count: 32,
fpr_count: 32,
reserved: vec![0, 2, 3, 4], callee_saved: {
let mut v: Vec<u32> = (8..=9).collect(); v.extend(18..=27); v
},
caller_saved: {
let mut v: Vec<u32> = vec![1]; v.extend(5..=7); v.extend(10..=17); v.extend(28..=31); v
},
reg_size: 8,
}
}
pub fn is_allocatable(&self, reg: PhysReg) -> bool {
!self.reserved.contains(®)
}
pub fn allocatable_regs(&self) -> Vec<PhysReg> {
let gprs: Vec<PhysReg> = (0..self.gpr_count)
.filter(|r| self.is_allocatable(*r))
.collect();
let xmms: Vec<PhysReg> = (64..(64 + self.fpr_count))
.filter(|r| self.is_allocatable(*r))
.collect();
[gprs.as_slice(), xmms.as_slice()].concat()
}
}
#[derive(Debug, Clone)]
pub struct LiveInterval {
pub vreg: VirtReg,
pub start: u32,
pub end: u32,
pub uses: Vec<u32>,
pub defs: Vec<u32>,
}
impl LiveInterval {
pub fn new(vreg: VirtReg) -> Self {
Self {
vreg,
start: u32::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 covers(&self, pos: u32) -> bool {
self.start <= pos && pos <= self.end
}
pub fn live_at(&self, pos: u32) -> bool {
self.covers(pos)
}
pub fn length(&self) -> u32 {
if self.end >= self.start {
self.end - self.start
} else {
0
}
}
pub fn recompute_bounds(&mut self) {
if let Some(&min_pos) = self.defs.iter().chain(self.uses.iter()).min() {
self.start = min_pos;
}
if let Some(&max_pos) = self.defs.iter().chain(self.uses.iter()).max() {
self.end = max_pos;
}
}
}
#[derive(Debug, Clone)]
pub struct RegAllocResult {
pub success: bool,
pub spills: usize,
pub reloads: usize,
pub copies_coalesced: usize,
}
impl RegAllocResult {
pub fn success() -> Self {
Self {
success: true,
spills: 0,
reloads: 0,
copies_coalesced: 0,
}
}
pub fn failure() -> Self {
Self {
success: false,
spills: 0,
reloads: 0,
copies_coalesced: 0,
}
}
}
pub struct RegAlloc {
pub assignments: HashMap<VirtReg, PhysReg>,
pub spilled: Vec<VirtReg>,
pub spill_slots: HashMap<VirtReg, i64>,
pub available: Vec<PhysReg>,
pub frame_offset: i64,
pub target: TargetRegInfo,
pub physreg_uses: Vec<(PhysReg, u32, u32)>,
pub vreg_classes: HashMap<VirtReg, RegClass>,
}
impl RegAlloc {
pub fn new(target: TargetRegInfo) -> Self {
Self::new_with_frame_offset(target, 0)
}
pub fn new_with_frame_offset(target: TargetRegInfo, initial_frame_offset: i64) -> Self {
let available = target.allocatable_regs();
Self {
assignments: HashMap::new(),
spilled: Vec::new(),
spill_slots: HashMap::new(),
available,
frame_offset: initial_frame_offset,
target,
physreg_uses: Vec::new(),
vreg_classes: HashMap::new(),
}
}
pub fn allocate(&mut self, mf: &mut MachineFunction) -> RegAllocResult {
eprintln!("RA_DEBUG: func={} Step 0 compute_vreg_classes", mf.name);
self.compute_vreg_classes(mf);
eprintln!("RA_DEBUG: func={} Step 1 compute_live_intervals", mf.name);
let mut intervals = self.compute_live_intervals(mf);
eprintln!(
"RA_DEBUG: func={} intervals count={}",
mf.name,
intervals.len()
);
{
let mut global_idx: u32 = 0;
let mut block_limits: Vec<(u32, u32)> = Vec::new();
for bb in &mf.blocks {
let start = global_idx;
global_idx += bb.instructions.len() as u32;
let end = global_idx - 1;
block_limits.push((start, end));
}
let mut pointer_vregs: std::collections::HashSet<VirtReg> =
std::collections::HashSet::new();
for bb in &mf.blocks {
for mi in &bb.instructions {
match mi.opcode {
x86_opcodes::LOAD | x86_opcodes::STORE => {
if let Some(MachineOperand::Reg(vr)) = mi.operands.first() {
pointer_vregs.insert(*vr);
}
}
_ => {}
}
}
}
let func_start = block_limits.first().map(|&(fs, _)| fs).unwrap_or(0);
let func_end = block_limits.last().map(|&(_, le)| le).unwrap_or(0);
if func_start != 0 || func_end != 0 {
let func_start = func_start;
let func_end = func_end;
for li in intervals.iter_mut() {
if pointer_vregs.contains(&li.vreg) {
if func_start < li.start {
li.start = func_start;
}
if func_end > li.end {
li.end = func_end;
}
} else {
let mut in_blocks = Vec::new();
for (bi, bb) in mf.blocks.iter().enumerate() {
for mi in &bb.instructions {
if mi.def == Some(li.vreg)
|| mi
.operands
.iter()
.any(|op| matches!(op, MachineOperand::Reg(vr) if *vr == li.vreg))
{
if !in_blocks.contains(&bi) {
in_blocks.push(bi);
}
}
}
}
if in_blocks.len() >= 2 {
let first = in_blocks.iter().min().copied().unwrap_or(0);
let last = in_blocks.iter().max().copied().unwrap_or(0);
if let Some(&(fs, _)) = block_limits.get(first) {
if let Some(&(_, le)) = block_limits.get(last) {
if fs < li.start {
li.start = fs;
}
if le > li.end {
li.end = le;
}
}
}
}
}
}
}
}
eprintln!("RA_DEBUG: func={} Step 2 linear_scan", mf.name);
let success = self.linear_scan_allocate(mf, &mut intervals);
if !success {
return RegAllocResult::failure();
}
eprintln!("RA_DEBUG: func={} Step 2.5 verify", mf.name);
self.verify_allocation(&intervals);
eprintln!("RA_DEBUG: func={} Step 4 coalesce", mf.name);
let coalesced = self.coalesce_copies(mf);
eprintln!("RA_DEBUG: func={} Step 5 rewrite_vregs", mf.name);
self.rewrite_vregs(mf);
eprintln!("RA_DEBUG: func={} Step 7 frame_indices", mf.name);
let fo = self.frame_offset;
self.rewrite_frame_indices(mf, fo);
eprintln!(
"RA_DEBUG: func={} DONE (spills={})",
mf.name,
self.spilled.len()
);
let spills = self.spilled.len();
let reloads = self.spilled.len();
RegAllocResult {
success: true,
spills,
reloads,
copies_coalesced: coalesced,
}
}
pub fn compute_live_intervals(&mut self, mf: &MachineFunction) -> Vec<LiveInterval> {
self.physreg_uses.clear();
let mut intervals: HashMap<VirtReg, LiveInterval> = HashMap::new();
let mut global_idx: u32 = 0;
for bb in &mf.blocks {
for mi in &bb.instructions {
for op in &mi.operands {
if let MachineOperand::Reg(vr) = *op {
let li = intervals.entry(vr).or_insert_with(|| LiveInterval::new(vr));
li.uses.push(global_idx);
if global_idx < li.start {
li.start = global_idx;
}
if global_idx > li.end {
li.end = global_idx;
}
}
}
if let Some(def) = mi.def {
let li = intervals
.entry(def)
.or_insert_with(|| LiveInterval::new(def));
li.defs.push(global_idx);
if global_idx < li.start {
li.start = global_idx;
}
if global_idx > li.end {
li.end = global_idx;
}
}
if mi.opcode == x86_opcodes::CALL {
let caller_saved: [PhysReg; 9] = [0, 1, 2, 6, 7, 8, 9, 10, 11];
for &pr in &caller_saved {
self.physreg_uses.push((pr, global_idx, global_idx));
}
for xmm in 64..80u32 {
self.physreg_uses.push((xmm, global_idx, global_idx));
}
let callee_saved: [PhysReg; 4] = [3, 12, 13, 14]; for &pr in &callee_saved {
self.physreg_uses.push((pr, global_idx, global_idx));
}
}
for op in &mi.operands {
if let MachineOperand::PhysReg(pr) = *op {
self.physreg_uses.push((pr, global_idx, global_idx));
}
}
global_idx += 1;
}
}
for bb in &mf.blocks {
for mi in &bb.instructions {
if let Some(def) = mi.def {
intervals
.entry(def)
.or_insert_with(|| LiveInterval::new(def));
}
}
}
intervals.into_values().collect()
}
pub fn linear_scan_allocate(
&mut self,
_mf: &mut MachineFunction,
intervals: &mut Vec<LiveInterval>,
) -> bool {
intervals.sort_by_key(|li| (li.start, li.end, li.vreg));
let mut active: Vec<(PhysReg, usize)> = Vec::new();
for i in 0..intervals.len() {
let li = &intervals[i];
active.retain(|&(_, active_idx)| {
let active_li = &intervals[active_idx];
active_li.end >= li.start
});
let mut used_regs: HashSet<PhysReg> = active.iter().map(|&(r, _)| r).collect();
for &(pr, use_start, use_end) in &self.physreg_uses {
if use_start <= li.end && use_end >= li.start {
used_regs.insert(pr);
}
}
let free_reg = self.find_free_reg_for(li.vreg, &used_regs);
if let Some(reg) = free_reg {
self.assignments.insert(li.vreg, reg);
active.push((reg, i));
} else {
if !self.spill_slots.contains_key(&li.vreg) {
self.frame_offset -= 8;
self.spill_slots.insert(li.vreg, self.frame_offset);
self.spilled.push(li.vreg);
}
}
}
true
}
fn verify_allocation(&self, intervals: &[LiveInterval]) {
for i in 0..intervals.len() {
for j in (i + 1)..intervals.len() {
let a = &intervals[i];
let b = &intervals[j];
let overlap = a.start <= b.end && b.start <= a.end;
if !overlap {
continue;
}
let Some(ra) = self.assignments.get(&a.vreg) else {
continue;
};
let Some(rb) = self.assignments.get(&b.vreg) else {
continue;
};
if ra == rb {
panic!(
"RA interference: vregs {:?} and {:?} overlap ({}..{}/{}..{}) both assigned {:?}",
a.vreg, b.vreg, a.start, a.end, b.start, b.end, ra
);
}
}
}
}
pub fn spill_vreg(
&mut self,
mf: &mut MachineFunction,
vreg: VirtReg,
intervals: &[LiveInterval],
) -> i64 {
if self.spill_slots.contains_key(&vreg) {
return self.spill_slots[&vreg];
}
self.frame_offset -= self.target.reg_size as i64;
let offset = self.frame_offset;
self.spill_slots.insert(vreg, offset);
self.spilled.push(vreg);
for li in intervals {
if li.vreg == vreg {
for &def_pos in &li.defs {
self.insert_spill(mf, vreg, def_pos as usize + 1);
}
for &use_pos in &li.uses {
if !li.defs.contains(&use_pos) {
self.insert_reload(mf, vreg, use_pos as usize);
}
}
break;
}
}
offset
}
pub fn insert_spill(&mut self, mf: &mut MachineFunction, vreg: VirtReg, before_inst: usize) {
let mut global_idx: usize = 0;
let offset = self
.spill_slots
.get(&vreg)
.copied()
.unwrap_or(self.frame_offset);
let tmp = mf.new_vreg();
self.assignments.insert(tmp, 11);
for bb in &mut mf.blocks {
let mut insert_idx = None;
for (i, _) in bb.instructions.iter().enumerate() {
if global_idx == before_inst {
insert_idx = Some(i);
break;
}
global_idx += 1;
}
if let Some(pos) = insert_idx {
let mut push = MachineInstr::new(x86_opcodes::PUSH);
push.push_reg(vreg);
bb.instructions.insert(pos, push);
let mut base = MachineInstr::new(x86_opcodes::MOV).with_def(vreg);
base.operands.push(MachineOperand::PhysReg(5));
bb.instructions.insert(pos + 1, base);
let mut adj = MachineInstr::new(x86_opcodes::ADD).with_def(vreg);
adj.push_imm(offset);
bb.instructions.insert(pos + 2, adj);
let mut pop = MachineInstr::new(x86_opcodes::POP);
pop.operands.push(MachineOperand::Reg(tmp));
bb.instructions.insert(pos + 3, pop);
let mut store = MachineInstr::new(x86_opcodes::STORE);
store.push_reg(vreg); store.push_reg(tmp); bb.instructions.insert(pos + 4, store);
return;
}
global_idx += bb.instructions.len();
}
}
pub fn insert_reload(&mut self, mf: &mut MachineFunction, vreg: VirtReg, before_inst: usize) {
let mut global_idx: usize = 0;
let offset = self
.spill_slots
.get(&vreg)
.copied()
.unwrap_or(self.frame_offset);
for bb in &mut mf.blocks {
let mut insert_idx = None;
for (i, _) in bb.instructions.iter().enumerate() {
if global_idx == before_inst {
insert_idx = Some(i);
break;
}
global_idx += 1;
}
if let Some(pos) = insert_idx {
let mut p = pos;
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(vreg);
mov.operands.push(MachineOperand::PhysReg(5)); bb.instructions.insert(p, mov);
p += 1;
let mut add = MachineInstr::new(x86_opcodes::ADD).with_def(vreg);
add.push_imm(offset);
bb.instructions.insert(p, add);
p += 1;
let mut load = MachineInstr::new(x86_opcodes::LOAD).with_def(vreg);
load.push_reg(vreg); bb.instructions.insert(p, load);
return;
}
global_idx += bb.instructions.len();
}
}
fn find_free_reg_in(
&self,
_intervals: &[LiveInterval],
_at_pos: u32,
used: &HashSet<PhysReg>,
) -> Option<PhysReg> {
let candidates = self.regs_for_class(RegClass::Gpr64);
candidates.iter().copied().find(|r| !used.contains(r))
}
fn find_free_reg_for(&self, vreg: VirtReg, used: &HashSet<PhysReg>) -> Option<PhysReg> {
let class = self
.vreg_classes
.get(&vreg)
.copied()
.unwrap_or(RegClass::Gpr64);
let candidates = self.regs_for_class(class);
candidates.iter().copied().find(|r| !used.contains(r))
}
fn regs_for_class(&self, class: RegClass) -> Vec<PhysReg> {
match class {
RegClass::Xmm => self
.available
.iter()
.copied()
.filter(|r| *r >= 64 && *r < 80)
.collect(),
_ => self.available.iter().copied().filter(|r| *r < 16).collect(),
}
}
pub fn compute_vreg_classes(&mut self, mf: &MachineFunction) {
let sse_binary_ops: std::collections::HashSet<u32> = [
x86_opcodes::MOVSS,
x86_opcodes::MOVSD,
x86_opcodes::ADDSS,
x86_opcodes::ADDSD,
x86_opcodes::SUBSS,
x86_opcodes::SUBSD,
x86_opcodes::MULSS,
x86_opcodes::MULSD,
x86_opcodes::DIVSS,
x86_opcodes::DIVSD,
x86_opcodes::UCOMISS,
x86_opcodes::UCOMISD,
x86_opcodes::XORPS,
x86_opcodes::XORPD,
]
.iter()
.copied()
.collect();
let sse_mem_ops: std::collections::HashSet<u32> = [
x86_opcodes::MOVSS_LOAD,
x86_opcodes::MOVSD_LOAD,
x86_opcodes::MOVSS_STORE,
x86_opcodes::MOVSD_STORE,
]
.iter()
.copied()
.collect();
let sse_gpr_to_xmm_ops: std::collections::HashSet<u32> =
[x86_opcodes::CVTSI2SS, x86_opcodes::CVTSI2SD]
.iter()
.copied()
.collect();
let sse_xmm_to_gpr_ops: std::collections::HashSet<u32> =
[x86_opcodes::CVTTSS2SI, x86_opcodes::CVTTSD2SI]
.iter()
.copied()
.collect();
let movd_from_xmm_ops: std::collections::HashSet<u32> =
[x86_opcodes::MOVD_FROM_XMM, x86_opcodes::MOVQ_FROM_XMM]
.iter()
.copied()
.collect();
let movd_to_xmm_ops: std::collections::HashSet<u32> =
[x86_opcodes::MOVD, x86_opcodes::MOVQ]
.iter()
.copied()
.collect();
for bb in &mf.blocks {
for mi in &bb.instructions {
let opcode = mi.opcode;
if sse_binary_ops.contains(&opcode) {
for op in &mi.operands {
if let MachineOperand::Reg(vr) = *op {
self.vreg_classes.insert(vr, RegClass::Xmm);
}
}
if let Some(def) = mi.def {
self.vreg_classes.insert(def, RegClass::Xmm);
}
} else if sse_mem_ops.contains(&opcode) {
for (i, op) in mi.operands.iter().enumerate() {
if i == 0 {
if let MachineOperand::Reg(vr) = *op {
self.vreg_classes.insert(vr, RegClass::Xmm);
}
}
}
if let Some(def) = mi.def {
self.vreg_classes.insert(def, RegClass::Xmm);
}
} else if sse_gpr_to_xmm_ops.contains(&opcode) {
for (i, op) in mi.operands.iter().enumerate() {
if i == 0 {
if let MachineOperand::Reg(vr) = *op {
self.vreg_classes.insert(vr, RegClass::Xmm);
}
}
}
if let Some(def) = mi.def {
self.vreg_classes.insert(def, RegClass::Xmm);
}
} else if sse_xmm_to_gpr_ops.contains(&opcode) {
for (i, op) in mi.operands.iter().enumerate() {
if i == 1 {
if let MachineOperand::Reg(vr) = *op {
self.vreg_classes.insert(vr, RegClass::Xmm);
}
}
}
} else if movd_from_xmm_ops.contains(&opcode) {
for (i, op) in mi.operands.iter().enumerate() {
if i == 0 {
if let MachineOperand::Reg(vr) = *op {
self.vreg_classes.insert(vr, RegClass::Xmm);
}
}
}
} else if movd_to_xmm_ops.contains(&opcode) {
for (i, op) in mi.operands.iter().enumerate() {
if i == 1 {
if let MachineOperand::Reg(vr) = *op {
self.vreg_classes.insert(vr, RegClass::Xmm);
}
}
}
if let Some(def) = mi.def {
self.vreg_classes.insert(def, RegClass::Xmm);
}
}
}
}
}
pub fn find_free_reg(&self, intervals: &[LiveInterval], at_pos: u32) -> Option<PhysReg> {
let used: HashSet<PhysReg> = intervals
.iter()
.filter(|li| li.live_at(at_pos))
.filter_map(|li| self.assignments.get(&li.vreg))
.copied()
.collect();
self.find_free_reg_in(intervals, at_pos, &used)
}
pub fn evict_register(&self, intervals: &[LiveInterval], at_pos: u32) -> VirtReg {
intervals
.iter()
.filter(|li| li.live_at(at_pos))
.max_by_key(|li| li.end)
.map(|li| li.vreg)
.unwrap_or(0)
}
pub fn coalesce_copies(&mut self, mf: &mut MachineFunction) -> usize {
let mut coalesced = 0;
for bb in &mut mf.blocks {
let mut to_remove: HashSet<usize> = HashSet::new();
for i in 0..bb.instructions.len() {
let mi = &bb.instructions[i];
if mi.opcode == x86_opcodes::MOV {
if let Some(def) = mi.def {
if mi.operands.len() == 1 {
if let MachineOperand::Reg(src) = mi.operands[0] {
let def_phys = self.assignments.get(&def);
let src_phys = self.assignments.get(&src);
if let (Some(&dp), Some(&sp)) = (def_phys, src_phys) {
if dp == sp {
to_remove.insert(i);
}
}
}
}
}
}
}
let mut indices: Vec<usize> = to_remove.into_iter().collect();
indices.sort_by(|a, b| b.cmp(a));
for idx in indices {
bb.instructions.remove(idx);
coalesced += 1;
}
}
coalesced
}
pub fn rewrite_frame_indices(&mut self, mf: &mut MachineFunction, frame_offset: i64) {
for bb in &mut mf.blocks {
for _mi in &mut bb.instructions {
let _ = frame_offset;
}
}
}
fn rewrite_vregs(&self, mf: &mut MachineFunction) {
const SPILL_ADDR: PhysReg = 12; const SCRATCH_POOL: [PhysReg; 2] = [10, 11];
for bb in &mut mf.blocks {
let mut new_insts: Vec<MachineInstr> = Vec::new();
for mi in &bb.instructions {
let mut spilled_to_scratch: std::collections::HashMap<VirtReg, PhysReg> =
std::collections::HashMap::new();
let mut scratch_idx = 0usize;
for op in &mi.operands {
if let MachineOperand::Reg(vr) = *op {
if !self.assignments.contains_key(&vr)
&& self.spill_slots.contains_key(&vr)
&& !spilled_to_scratch.contains_key(&vr)
{
if scratch_idx >= SCRATCH_POOL.len() {
panic!(
"rewrite_vregs: ran out of scratch regs at idx {} (need {}), max {}",
scratch_idx, scratch_idx + 1, SCRATCH_POOL.len()
);
}
let scratch = SCRATCH_POOL[scratch_idx];
scratch_idx += 1;
spilled_to_scratch.insert(vr, scratch);
}
}
}
let def_scratch = mi.def.and_then(|def| {
if !self.assignments.contains_key(&def) && self.spill_slots.contains_key(&def) {
if !spilled_to_scratch.contains_key(&def) {
if scratch_idx >= SCRATCH_POOL.len() {
panic!(
"rewrite_vregs: ran out of scratch regs for def {} (need {}), max {}",
def, scratch_idx + 1, SCRATCH_POOL.len()
);
}
let scratch = SCRATCH_POOL[scratch_idx];
scratch_idx += 1;
spilled_to_scratch.insert(def, scratch);
}
spilled_to_scratch.get(&def).copied()
} else {
None
}
});
let implicit_def: Option<VirtReg> = if mi.def.is_none() {
let has_physreg_dst = mi
.operands
.first()
.map_or(false, |op| matches!(op, MachineOperand::PhysReg(_)));
if has_physreg_dst {
None
} else {
mi.operands.iter().rev().find_map(|op| {
if let MachineOperand::Reg(vr) = *op {
Some(vr)
} else {
None
}
})
}
} else {
None
};
let implicit_def_is_spilled: bool = implicit_def.is_some_and(|def| {
!self.assignments.contains_key(&def) && self.spill_slots.contains_key(&def)
});
for op in &mi.operands {
if let MachineOperand::Reg(vr) = *op {
if let Some(&scratch) = spilled_to_scratch.get(&vr) {
let offset = self.spill_slots[&vr];
let mut mov = MachineInstr::new(x86_opcodes::MOV);
mov.operands.push(MachineOperand::PhysReg(scratch));
mov.operands.push(MachineOperand::PhysReg(5)); mov.size = 0;
new_insts.push(mov);
let mut add = MachineInstr::new(x86_opcodes::ADD);
add.operands.push(MachineOperand::PhysReg(scratch));
add.push_imm(offset);
add.size = 0;
new_insts.push(add);
let mut load = MachineInstr::new(x86_opcodes::LOAD);
load.operands.push(MachineOperand::PhysReg(scratch)); load.operands.push(MachineOperand::PhysReg(scratch)); new_insts.push(load);
}
}
}
let mut rewritten = mi.clone();
for op in &mut rewritten.operands {
if let MachineOperand::Reg(vr) = *op {
if let Some(&phys) = self.assignments.get(&vr) {
*op = MachineOperand::PhysReg(phys);
} else if let Some(&scratch) = spilled_to_scratch.get(&vr) {
*op = MachineOperand::PhysReg(scratch);
}
}
}
let mut emitted = false;
if implicit_def_is_spilled {
if let Some(def) = implicit_def {
let scratch = spilled_to_scratch[&def];
let offset = self.spill_slots[&def];
let mut mov = MachineInstr::new(x86_opcodes::MOV);
mov.operands.push(MachineOperand::PhysReg(SPILL_ADDR)); mov.operands.push(MachineOperand::PhysReg(5)); mov.size = 0;
let mut add = MachineInstr::new(x86_opcodes::ADD);
add.operands.push(MachineOperand::PhysReg(SPILL_ADDR));
add.push_imm(offset);
add.size = 0;
let mut store = MachineInstr::new(x86_opcodes::STORE);
store.operands.push(MachineOperand::PhysReg(SPILL_ADDR)); store.operands.push(MachineOperand::PhysReg(scratch));
new_insts.push(rewritten.clone());
new_insts.push(mov);
new_insts.push(add);
new_insts.push(store);
emitted = true;
}
}
if let Some(def) = rewritten.def {
if let Some(&phys) = self.assignments.get(&def) {
rewritten.operands.insert(0, MachineOperand::PhysReg(phys));
} else if self.spill_slots.contains_key(&def) {
let scratch = def_scratch.unwrap_or(0);
rewritten
.operands
.insert(0, MachineOperand::PhysReg(scratch));
rewritten.def = None;
let offset = self.spill_slots[&def];
let mut mov = MachineInstr::new(x86_opcodes::MOV);
mov.operands.push(MachineOperand::PhysReg(SPILL_ADDR)); mov.operands.push(MachineOperand::PhysReg(5)); mov.size = 0;
let mut add = MachineInstr::new(x86_opcodes::ADD);
add.operands.push(MachineOperand::PhysReg(SPILL_ADDR));
add.push_imm(offset);
add.size = 0;
let mut store = MachineInstr::new(x86_opcodes::STORE);
store.operands.push(MachineOperand::PhysReg(SPILL_ADDR)); store.operands.push(MachineOperand::PhysReg(scratch));
if !emitted {
new_insts.push(rewritten);
new_insts.push(mov);
new_insts.push(add);
new_insts.push(store);
}
continue; }
}
if !emitted {
new_insts.push(rewritten);
}
}
bb.instructions = new_insts;
}
}
pub fn add_callee_saved_spills(&mut self, mf: &mut MachineFunction) {
let used_callee_saved: HashSet<PhysReg> = self
.target
.callee_saved
.iter()
.copied()
.filter(|r| {
for bb in &mf.blocks {
for mi in &bb.instructions {
for op in &mi.operands {
if *op == MachineOperand::PhysReg(*r) {
return true;
}
}
}
}
false
})
.collect();
if used_callee_saved.is_empty() || mf.blocks.is_empty() {
return;
}
let mut sorted_regs: Vec<PhysReg> = used_callee_saved.into_iter().collect();
sorted_regs.sort();
for ® in sorted_regs.iter().rev() {
let mut push = MachineInstr::new(x86_opcodes::PUSH);
push.operands.push(MachineOperand::PhysReg(reg));
mf.blocks[0].instructions.insert(0, push);
}
for bb in &mut mf.blocks {
let mut pop_indices = Vec::new();
for (i, mi) in bb.instructions.iter().enumerate() {
if mi.opcode == x86_opcodes::RET {
pop_indices.push(i);
}
}
for &ret_idx in pop_indices.iter().rev() {
for ® in &sorted_regs {
let mut pop = MachineInstr::new(x86_opcodes::POP);
pop.operands.push(MachineOperand::PhysReg(reg));
bb.instructions.insert(ret_idx, pop);
}
}
}
}
pub fn eliminate_dead_code(&mut self, mf: &mut MachineFunction) {
for bb in &mut mf.blocks {
let mut used_vregs: HashSet<VirtReg> = HashSet::new();
for mi in &bb.instructions {
for op in &mi.operands {
if let MachineOperand::Reg(vr) = *op {
used_vregs.insert(vr);
}
}
}
bb.instructions.retain(|mi| {
if let Some(def) = mi.def {
used_vregs.contains(&def)
|| mi.opcode == x86_opcodes::RET
|| mi.opcode == x86_opcodes::JMP
|| mi.opcode == x86_opcodes::JNE
|| mi.opcode == x86_opcodes::CALL
|| mi.opcode == x86_opcodes::PUSH
|| mi.opcode == x86_opcodes::POP
} else {
true
}
});
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LiveRangeSegment {
pub start: u32,
pub end: u32,
}
impl LiveRangeSegment {
pub fn new(start: u32, end: u32) -> Self {
Self { start, end }
}
pub fn overlaps(&self, other: &LiveRangeSegment) -> bool {
self.start <= other.end && other.start <= self.end
}
pub fn contains(&self, pos: u32) -> bool {
self.start <= pos && pos <= self.end
}
pub fn is_valid(&self) -> bool {
self.start <= self.end
}
pub fn length(&self) -> u32 {
self.end.saturating_sub(self.start) + 1
}
pub fn try_merge(a: &LiveRangeSegment, b: &LiveRangeSegment) -> Option<LiveRangeSegment> {
if a.end + 1 >= b.start && b.end + 1 >= a.start {
Some(LiveRangeSegment {
start: a.start.min(b.start),
end: a.end.max(b.end),
})
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct LiveRange {
pub vreg: VirtReg,
pub segments: Vec<LiveRangeSegment>,
pub value_numbers: Vec<u32>,
pub computed: bool,
}
impl LiveRange {
pub fn new(vreg: VirtReg) -> Self {
Self {
vreg,
segments: Vec::new(),
value_numbers: Vec::new(),
computed: false,
}
}
pub fn add_segment(&mut self, seg: LiveRangeSegment, vn: u32) {
self.segments.push(seg);
self.value_numbers.push(vn);
self.sort_and_merge();
}
pub fn sort_and_merge(&mut self) {
if self.segments.len() <= 1 {
return;
}
let mut pairs: Vec<(LiveRangeSegment, u32)> = self
.segments
.iter()
.zip(self.value_numbers.iter())
.map(|(s, v)| (*s, *v))
.collect();
pairs.sort_by_key(|(s, _)| s.start);
let mut merged_segments = Vec::new();
let mut merged_vns = Vec::new();
let mut cur_seg = pairs[0].0;
let mut cur_vn = pairs[0].1;
for (seg, vn) in pairs.into_iter().skip(1) {
if vn == cur_vn {
if let Some(merged) = LiveRangeSegment::try_merge(&cur_seg, &seg) {
cur_seg = merged;
continue;
}
}
merged_segments.push(cur_seg);
merged_vns.push(cur_vn);
cur_seg = seg;
cur_vn = vn;
}
merged_segments.push(cur_seg);
merged_vns.push(cur_vn);
self.segments = merged_segments;
self.value_numbers = merged_vns;
}
pub fn live_at(&self, pos: u32) -> bool {
self.segments.iter().any(|seg| seg.contains(pos))
}
pub fn overlaps(&self, other: &LiveRange) -> bool {
for a in &self.segments {
for b in &other.segments {
if a.overlaps(b) {
return true;
}
}
}
false
}
pub fn begin_index(&self) -> Option<u32> {
self.segments.first().map(|s| s.start)
}
pub fn end_index(&self) -> Option<u32> {
self.segments.last().map(|s| s.end)
}
pub fn value_number_at(&self, pos: u32) -> Option<u32> {
self.segments
.iter()
.zip(self.value_numbers.iter())
.find(|(seg, _)| seg.contains(pos))
.map(|(_, vn)| *vn)
}
}
pub struct LiveRangeCalc {
pub ranges: HashMap<VirtReg, LiveRange>,
pub next_value_number: u32,
}
impl LiveRangeCalc {
pub fn new() -> Self {
Self {
ranges: HashMap::new(),
next_value_number: 0,
}
}
pub fn reset(&mut self) {
self.ranges.clear();
self.next_value_number = 0;
}
pub fn get_next_value_number(&mut self) -> u32 {
let vn = self.next_value_number;
self.next_value_number += 1;
vn
}
pub fn create_dead_def(&mut self, vreg: VirtReg, def_pos: u32) {
let vn = self.get_next_value_number();
let range = self
.ranges
.entry(vreg)
.or_insert_with(|| LiveRange::new(vreg));
range.add_segment(LiveRangeSegment::new(def_pos, def_pos), vn);
}
pub fn extend_to_use(&mut self, vreg: VirtReg, def_pos: u32, use_pos: u32) {
let existing_vn = self.ranges.get(&vreg).and_then(|r| {
r.segments
.iter()
.zip(r.value_numbers.iter())
.find(|(s, _)| s.start <= def_pos && def_pos <= s.end)
.map(|(_, vn)| *vn)
});
let vn = if let Some(vn) = existing_vn {
vn
} else {
self.get_next_value_number()
};
let range = self
.ranges
.entry(vreg)
.or_insert_with(|| LiveRange::new(vreg));
range.segments.push(LiveRangeSegment::new(def_pos, use_pos));
range.value_numbers.push(vn);
range.sort_and_merge();
}
pub fn extend_to_block_end(&mut self, vreg: VirtReg, def_pos: u32, block_end: u32) {
self.extend_to_use(vreg, def_pos, block_end);
}
pub fn add_use_only(&mut self, vreg: VirtReg, use_pos: u32) {
let vn = self.get_next_value_number();
let range = self
.ranges
.entry(vreg)
.or_insert_with(|| LiveRange::new(vreg));
range.add_segment(LiveRangeSegment::new(use_pos, use_pos), vn);
}
pub fn get_range(&self, vreg: VirtReg) -> Option<&LiveRange> {
self.ranges.get(&vreg)
}
pub fn get_range_mut(&mut self, vreg: VirtReg) -> Option<&mut LiveRange> {
self.ranges.get_mut(&vreg)
}
pub fn compute_all(&mut self, mf: &MachineFunction) {
self.reset();
for (block_idx, block) in mf.blocks.iter().enumerate() {
let block_pos = block_idx as u32;
for (instr_idx, instr) in block.instructions.iter().enumerate() {
let instr_pos = (block_idx as u32) * 10000 + instr_idx as u32;
if let Some(def_vreg) = instr.def {
self.create_dead_def(def_vreg, instr_pos);
}
for op in &instr.operands {
if let MachineOperand::Reg(vreg) = *op {
self.extend_to_use(vreg, instr_pos, instr_pos);
}
}
}
let block_end = (block_idx as u32) * 10000 + 9999;
for instr in &block.instructions {
if let Some(def_vreg) = instr.def {
if block.successors.len() == 1 {
self.extend_to_block_end(def_vreg, (block_idx as u32) * 10000, block_end);
}
if !block.successors.is_empty() {
self.extend_to_block_end(def_vreg, (block_idx as u32) * 10000, block_end);
}
}
}
}
}
pub fn prune_dead(&mut self) {
for range in self.ranges.values_mut() {
range.segments.retain(|s| s.is_valid());
}
}
}
impl Default for LiveRangeCalc {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ConnectedVNInfo {
pub vreg: VirtReg,
pub value_number: u32,
pub connected: HashSet<VirtReg>,
pub is_constant: bool,
pub constant_value: Option<i64>,
pub resolved: bool,
}
impl ConnectedVNInfo {
pub fn new(vreg: VirtReg, value_number: u32) -> Self {
Self {
vreg,
value_number,
connected: HashSet::new(),
is_constant: false,
constant_value: None,
resolved: false,
}
}
pub fn connect(&mut self, other: VirtReg) {
self.connected.insert(other);
}
pub fn is_connected_to(&self, other: VirtReg) -> bool {
self.connected.contains(&other)
}
pub fn set_constant(&mut self, value: i64) {
self.is_constant = true;
self.constant_value = Some(value);
self.resolved = true;
}
pub fn get_constant(&self) -> Option<i64> {
if self.is_constant {
self.constant_value
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct ValueNumberMap {
pub entries: HashMap<VirtReg, ConnectedVNInfo>,
pub next_vn: u32,
pub parent: HashMap<u32, u32>,
}
impl ValueNumberMap {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
next_vn: 0,
parent: HashMap::new(),
}
}
pub fn get_value_number(&mut self, vreg: VirtReg) -> u32 {
if let Some(entry) = self.entries.get(&vreg) {
self.find_canonical(entry.value_number)
} else {
let vn = self.next_vn;
self.next_vn += 1;
let entry = ConnectedVNInfo::new(vreg, vn);
self.entries.insert(vreg, entry);
self.parent.insert(vn, vn);
vn
}
}
pub fn union_values(&mut self, a: VirtReg, b: VirtReg) {
let vn_a = self.get_value_number(a);
let vn_b = self.get_value_number(b);
self.union(vn_a, vn_b);
if let Some(entry) = self.entries.get_mut(&a) {
entry.connect(b);
}
if let Some(entry) = self.entries.get_mut(&b) {
entry.connect(a);
}
}
fn find_canonical(&self, vn: u32) -> u32 {
let mut current = vn;
while let Some(&parent) = self.parent.get(¤t) {
if parent == current {
break;
}
current = parent;
}
current
}
fn union(&mut self, a: u32, b: u32) {
let root_a = self.find_canonical(a);
let root_b = self.find_canonical(b);
if root_a != root_b {
self.parent.insert(root_b, root_a);
}
}
pub fn same_value(&self, a: VirtReg, b: VirtReg) -> bool {
if let (Some(ea), Some(eb)) = (self.entries.get(&a), self.entries.get(&b)) {
self.find_canonical(ea.value_number) == self.find_canonical(eb.value_number)
} else {
false
}
}
pub fn set_constant(&mut self, vreg: VirtReg, value: i64) {
let vn = self.get_value_number(vreg);
if let Some(entry) = self.entries.get_mut(&vreg) {
entry.set_constant(value);
}
}
pub fn get_constant(&self, vreg: VirtReg) -> Option<i64> {
self.entries.get(&vreg)?.get_constant()
}
}
impl Default for ValueNumberMap {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SlotIndex {
pub instr: u32,
pub slot: u32,
}
impl SlotIndex {
pub fn new(instr: u32, slot: u32) -> Self {
Self { instr, slot }
}
pub fn base(instr: u32) -> Self {
Self { instr, slot: 0 }
}
pub fn def(instr: u32) -> Self {
Self { instr, slot: 1 }
}
pub fn uses(instr: u32) -> Self {
Self { instr, slot: 2 }
}
pub fn is_def(&self) -> bool {
self.slot == 1
}
pub fn is_use(&self) -> bool {
self.slot == 2
}
pub fn is_base(&self) -> bool {
self.slot == 0
}
pub fn prev(&self) -> Self {
if self.slot > 0 {
Self {
instr: self.instr,
slot: self.slot - 1,
}
} else if self.instr > 0 {
Self {
instr: self.instr - 1,
slot: 3,
}
} else {
*self
}
}
pub fn next(&self) -> Self {
if self.slot < 3 {
Self {
instr: self.instr,
slot: self.slot + 1,
}
} else {
Self {
instr: self.instr + 1,
slot: 0,
}
}
}
}
#[derive(Debug, Clone)]
pub struct SlotIndexes {
pub next_index: u32,
pub index_map: HashMap<(usize, usize), SlotIndex>,
pub instruction_count: u32,
}
impl SlotIndexes {
pub fn new() -> Self {
Self {
next_index: 0,
index_map: HashMap::new(),
instruction_count: 0,
}
}
pub fn index_function(&mut self, mf: &MachineFunction) {
self.next_index = 0;
self.index_map.clear();
self.instruction_count = 0;
for (block_idx, block) in mf.blocks.iter().enumerate() {
for (instr_idx, _instr) in block.instructions.iter().enumerate() {
let idx = self.next_index;
let si = SlotIndex::base(idx);
self.index_map.insert((block_idx, instr_idx), si);
self.next_index += 1;
self.instruction_count += 1;
}
}
}
pub fn get_index(&self, block_idx: usize, instr_idx: usize) -> Option<SlotIndex> {
self.index_map.get(&(block_idx, instr_idx)).copied()
}
pub fn get_def_index(&self, block_idx: usize, instr_idx: usize) -> Option<SlotIndex> {
self.index_map
.get(&(block_idx, instr_idx))
.map(|si| SlotIndex::def(si.instr))
}
pub fn get_use_index(&self, block_idx: usize, instr_idx: usize) -> Option<SlotIndex> {
self.index_map
.get(&(block_idx, instr_idx))
.map(|si| SlotIndex::uses(si.instr))
}
pub fn is_valid(&self, si: SlotIndex) -> bool {
si.instr < self.next_index
}
pub fn total_slots(&self) -> u32 {
self.next_index * 4 }
}
impl Default for SlotIndexes {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct BlockLiveness {
pub live_in: bool,
pub live_out: bool,
pub killed: bool,
pub first_use: Option<u32>,
pub last_use: Option<u32>,
pub defs: Vec<u32>,
}
impl Default for BlockLiveness {
fn default() -> Self {
Self {
live_in: false,
live_out: false,
killed: false,
first_use: None,
last_use: None,
defs: Vec::new(),
}
}
}
pub struct LiveVariables {
pub block_liveness: Vec<HashMap<VirtReg, BlockLiveness>>,
pub ranges: HashMap<VirtReg, LiveRange>,
pub computed: bool,
pub slot_indexes: SlotIndexes,
pub value_numbers: ValueNumberMap,
}
impl LiveVariables {
pub fn new() -> Self {
Self {
block_liveness: Vec::new(),
ranges: HashMap::new(),
computed: false,
slot_indexes: SlotIndexes::new(),
value_numbers: ValueNumberMap::new(),
}
}
pub fn run_on_function(&mut self, mf: &MachineFunction) {
self.block_liveness = vec![HashMap::new(); mf.blocks.len()];
self.ranges.clear();
self.slot_indexes.index_function(mf);
for (block_idx, block) in mf.blocks.iter().enumerate() {
for (instr_idx, instr) in block.instructions.iter().enumerate() {
for op in &instr.operands {
if let MachineOperand::Reg(vreg) = *op {
let entry = self.block_liveness[block_idx].entry(vreg).or_default();
let pos = instr_idx as u32;
if entry.first_use.is_none() {
entry.first_use = Some(pos);
}
entry.last_use = Some(pos);
}
}
if let Some(def_vreg) = instr.def {
let entry = self.block_liveness[block_idx].entry(def_vreg).or_default();
entry.defs.push(instr_idx as u32);
}
}
}
let mut changed = true;
let mut iterations = 0;
while changed && iterations < 100 {
changed = false;
iterations += 1;
for block_idx in (0..mf.blocks.len()).rev() {
let block = &mf.blocks[block_idx];
let mut live_out: HashSet<VirtReg> = HashSet::new();
for &succ_idx in &block.successors {
if succ_idx < self.block_liveness.len() {
for (&vreg, info) in &self.block_liveness[succ_idx] {
if info.live_in {
live_out.insert(vreg);
}
}
}
}
let block_info = &mut self.block_liveness[block_idx];
for &vreg in &live_out {
let entry = block_info.entry(vreg).or_default();
if !entry.live_out {
entry.live_out = true;
changed = true;
}
}
for (&vreg, info) in block_info.clone().iter() {
let was_live_in = info.live_in;
let has_use = info.first_use.is_some();
let has_def = !info.defs.is_empty();
let is_live_out = info.live_out;
let new_live_in = has_use || (is_live_out && !has_def);
let entry = block_info.entry(vreg).or_default();
if entry.live_in != new_live_in {
entry.live_in = new_live_in;
changed = true;
}
}
for &vreg in &live_out {
block_info.entry(vreg).or_default();
}
}
}
for block_idx in 0..mf.blocks.len() {
let mut last_use_by_reg: HashMap<VirtReg, u32> = HashMap::new();
for (instr_idx, instr) in mf.blocks[block_idx].instructions.iter().enumerate() {
for op in &instr.operands {
if let MachineOperand::Reg(vreg) = *op {
last_use_by_reg.insert(vreg, instr_idx as u32);
}
}
}
for (&vreg, &last_pos) in &last_use_by_reg {
if let Some(entry) = self.block_liveness[block_idx].get_mut(&vreg) {
if entry.last_use == Some(last_pos) && !entry.live_out {
entry.killed = true;
}
}
}
}
self.build_global_ranges(mf);
self.computed = true;
}
fn build_global_ranges(&mut self, mf: &MachineFunction) {
let mut range_calc = LiveRangeCalc::new();
range_calc.compute_all(mf);
self.ranges = range_calc.ranges;
}
pub fn is_live_in(&self, block_idx: usize, vreg: VirtReg) -> bool {
self.block_liveness
.get(block_idx)
.and_then(|map| map.get(&vreg))
.map(|info| info.live_in)
.unwrap_or(false)
}
pub fn is_live_out(&self, block_idx: usize, vreg: VirtReg) -> bool {
self.block_liveness
.get(block_idx)
.and_then(|map| map.get(&vreg))
.map(|info| info.live_out)
.unwrap_or(false)
}
pub fn is_killed(&self, block_idx: usize, vreg: VirtReg) -> bool {
self.block_liveness
.get(block_idx)
.and_then(|map| map.get(&vreg))
.map(|info| info.killed)
.unwrap_or(false)
}
pub fn get_range(&self, vreg: VirtReg) -> Option<&LiveRange> {
self.ranges.get(&vreg)
}
pub fn live_out_set(&self, block_idx: usize) -> Vec<VirtReg> {
self.block_liveness
.get(block_idx)
.map(|map| {
map.iter()
.filter(|(_, info)| info.live_out)
.map(|(&vreg, _)| vreg)
.collect()
})
.unwrap_or_default()
}
pub fn live_in_set(&self, block_idx: usize) -> Vec<VirtReg> {
self.block_liveness
.get(block_idx)
.map(|map| {
map.iter()
.filter(|(_, info)| info.live_in)
.map(|(&vreg, _)| vreg)
.collect()
})
.unwrap_or_default()
}
}
impl Default for LiveVariables {
fn default() -> Self {
Self::new()
}
}
pub trait TargetISel {
fn select(&self, mf: &mut MachineFunction, func: &ValueRef);
fn get_target_reg_info(&self) -> TargetRegInfo;
fn get_frame_lowering(&self) -> Box<dyn FrameLowering>;
}
pub trait FrameLowering {
fn emit_prologue(&self, mf: &mut MachineFunction) -> Vec<MachineInstr>;
fn emit_epilogue(&self, mf: &mut MachineFunction) -> Vec<MachineInstr>;
fn needs_frame_pointer(&self, func: &ValueRef) -> bool;
}
pub struct X86FrameLowering;
impl FrameLowering for X86FrameLowering {
fn emit_prologue(&self, _mf: &mut MachineFunction) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let mut push_rbp = MachineInstr::new(x86_opcodes::PUSH);
push_rbp.operands.push(MachineOperand::PhysReg(5));
instrs.push(push_rbp);
let mut mov = MachineInstr::new(x86_opcodes::MOV);
mov.operands.push(MachineOperand::PhysReg(5));
mov.operands.push(MachineOperand::PhysReg(4));
instrs.push(mov);
instrs
}
fn emit_epilogue(&self, _mf: &mut MachineFunction) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
let mut pop_rbp = MachineInstr::new(x86_opcodes::POP);
pop_rbp.operands.push(MachineOperand::PhysReg(5));
instrs.push(pop_rbp);
instrs
}
fn needs_frame_pointer(&self, func: &ValueRef) -> bool {
let f = func.borrow();
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if let Some(Opcode::Alloca) = inst.get_opcode() {
return true;
}
}
}
}
false
}
}
pub struct X86TargetISel;
impl TargetISel for X86TargetISel {
fn select(&self, mf: &mut MachineFunction, func: &ValueRef) {
InstructionSelector::select(mf, func);
}
fn get_target_reg_info(&self) -> TargetRegInfo {
TargetRegInfo::for_x86_64()
}
fn get_frame_lowering(&self) -> Box<dyn FrameLowering> {
Box::new(X86FrameLowering)
}
}
pub fn compile_function_with_pipeline(func: &ValueRef, isel: &dyn TargetISel) -> String {
let f = func.borrow();
let mut mf = MachineFunction::new(&f.name);
isel.select(&mut mf, func);
let target_info = isel.get_target_reg_info();
let mut regalloc = RegAlloc::new(target_info);
let result = regalloc.allocate(&mut mf);
let frame_lowering = isel.get_frame_lowering();
let prologue = frame_lowering.emit_prologue(&mut mf);
let epilogue = frame_lowering.emit_epilogue(&mut mf);
if !mf.blocks.is_empty() {
for instr in prologue.into_iter().rev() {
mf.blocks[0].instructions.insert(0, instr);
}
}
for bb in &mut mf.blocks {
let ret_indices: Vec<usize> = bb
.instructions
.iter()
.enumerate()
.filter(|(_, mi)| mi.opcode == x86_opcodes::RET)
.map(|(i, _)| i)
.collect();
for &ret_idx in ret_indices.iter().rev() {
for instr in epilogue.iter().rev() {
bb.instructions.insert(ret_idx, instr.clone());
}
}
}
let mut printer = AsmPrinter::new();
printer.print_function(&mf);
let _ = result; printer.output
}
pub struct GreedyRegAlloc {
pub assignments: HashMap<VirtReg, PhysReg>,
pub available: Vec<PhysReg>,
pub phys_to_vreg: HashMap<PhysReg, VirtReg>,
pub spill_costs: HashMap<VirtReg, f64>,
pub interference: InterferenceGraph,
pub intervals: Vec<LiveInterval>,
pub frame_offset: i64,
pub spill_slots: HashMap<VirtReg, i64>,
pub copies_coalesced: usize,
pub ranges_split: usize,
pub regs_spilled: usize,
}
#[derive(Debug, Clone)]
pub struct InterferenceGraph {
pub edges: HashMap<VirtReg, HashSet<VirtReg>>,
pub degree: HashMap<VirtReg, usize>,
pub built: bool,
}
impl InterferenceGraph {
pub fn new() -> Self {
InterferenceGraph {
edges: HashMap::new(),
degree: HashMap::new(),
built: false,
}
}
pub fn add_interference(&mut self, a: VirtReg, b: VirtReg) {
if a == b {
return;
}
self.edges.entry(a).or_default().insert(b);
self.edges.entry(b).or_default().insert(a);
*self.degree.entry(a).or_default() += 1;
*self.degree.entry(b).or_default() += 1;
}
pub fn interfere(&self, a: VirtReg, b: VirtReg) -> bool {
self.edges
.get(&a)
.map(|neighbors| neighbors.contains(&b))
.unwrap_or(false)
}
pub fn build_from_intervals(&mut self, intervals: &[LiveInterval]) {
self.edges.clear();
self.degree.clear();
for i in 0..intervals.len() {
let li_a = &intervals[i];
for j in (i + 1)..intervals.len() {
let li_b = &intervals[j];
if li_a.overlaps(li_b) {
self.add_interference(li_a.vreg, li_b.vreg);
}
}
}
self.built = true;
}
pub fn get_degree(&self, vreg: VirtReg) -> usize {
self.degree.get(&vreg).copied().unwrap_or(0)
}
pub fn remove_register(&mut self, vreg: VirtReg) {
if let Some(neighbors) = self.edges.remove(&vreg) {
for &neighbor in &neighbors {
if let Some(neighbor_edges) = self.edges.get_mut(&neighbor) {
neighbor_edges.remove(&vreg);
}
if let Some(deg) = self.degree.get_mut(&neighbor) {
*deg = deg.saturating_sub(1);
}
}
}
self.degree.remove(&vreg);
}
pub fn neighbors(&self, vreg: VirtReg) -> Vec<VirtReg> {
self.edges
.get(&vreg)
.map(|set| set.iter().copied().collect())
.unwrap_or_default()
}
}
impl Default for InterferenceGraph {
fn default() -> Self {
InterferenceGraph::new()
}
}
impl GreedyRegAlloc {
pub fn new(target: TargetRegInfo) -> Self {
let available = target.allocatable_regs();
GreedyRegAlloc {
assignments: HashMap::new(),
available,
phys_to_vreg: HashMap::new(),
spill_costs: HashMap::new(),
interference: InterferenceGraph::new(),
intervals: Vec::new(),
frame_offset: 0,
spill_slots: HashMap::new(),
copies_coalesced: 0,
ranges_split: 0,
regs_spilled: 0,
}
}
pub fn allocate(&mut self, mf: &mut MachineFunction) -> RegAllocResult {
let mut regalloc = RegAlloc::new(TargetRegInfo::for_x86_64());
self.intervals = regalloc.compute_live_intervals(mf);
self.interference.build_from_intervals(&self.intervals);
self.compute_spill_costs(mf);
let mut sorted: Vec<usize> = (0..self.intervals.len()).collect();
sorted.sort_by(|&a, &b| {
let cost_a = self
.spill_costs
.get(&self.intervals[a].vreg)
.unwrap_or(&0.0);
let cost_b = self
.spill_costs
.get(&self.intervals[b].vreg)
.unwrap_or(&0.0);
cost_b
.partial_cmp(cost_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
for idx in sorted {
let vreg = self.intervals[idx].vreg;
if self.assignments.contains_key(&vreg) {
continue;
}
let li_copy = self.intervals[idx].clone();
self.try_assign_with_split_and_evict(&li_copy, mf);
}
self.coalesce_copies_greedy(mf);
let spills = self.regs_spilled;
RegAllocResult {
success: true,
spills,
reloads: spills,
copies_coalesced: self.copies_coalesced,
}
}
fn try_assign_with_split_and_evict(&mut self, li: &LiveInterval, mf: &mut MachineFunction) {
if let Some(phys) = self.find_best_free_reg(li) {
self.assign(li.vreg, phys);
return;
}
if self.should_split(li) {
let split_points = self.find_split_points(li);
if !split_points.is_empty() {
if self.split_and_assign(li, &split_points, mf) {
self.ranges_split += 1;
return;
}
}
}
if let Some(phys) = self.evict_cheapest(li) {
self.assign(li.vreg, phys);
return;
}
self.spill_register(li, mf);
}
fn find_best_free_reg(&self, li: &LiveInterval) -> Option<PhysReg> {
let interfering: HashSet<PhysReg> = self
.interference
.neighbors(li.vreg)
.iter()
.filter_map(|&vreg| self.assignments.get(&vreg))
.copied()
.collect();
self.available
.iter()
.filter(|r| !interfering.contains(r))
.copied()
.next()
}
fn assign(&mut self, vreg: VirtReg, phys: PhysReg) {
self.assignments.insert(vreg, phys);
self.phys_to_vreg.insert(phys, vreg);
}
fn unassign(&mut self, phys: PhysReg) {
if let Some(vreg) = self.phys_to_vreg.remove(&phys) {
self.assignments.remove(&vreg);
}
}
fn should_split(&self, li: &LiveInterval) -> bool {
let length = li.length();
length > 50 && li.uses.len() >= 3
}
fn find_split_points(&self, li: &LiveInterval) -> Vec<u32> {
let mut points = Vec::new();
if li.uses.len() < 2 {
return points;
}
let mut sorted_uses = li.uses.clone();
sorted_uses.sort();
sorted_uses.dedup();
for window in sorted_uses.windows(2) {
let gap = window[1] - window[0];
if gap > 10 {
points.push(window[0] + gap / 2);
}
}
points
}
fn split_and_assign(
&mut self,
li: &LiveInterval,
split_points: &[u32],
mf: &mut MachineFunction,
) -> bool {
if split_points.is_empty() {
return false;
}
let split_at = split_points[0];
let new_vreg = self.make_new_vreg(mf);
let first_half = LiveInterval {
vreg: li.vreg,
start: li.start,
end: split_at,
uses: li
.uses
.iter()
.filter(|&&u| u <= split_at)
.copied()
.collect(),
defs: li
.defs
.iter()
.filter(|&&d| d <= split_at)
.copied()
.collect(),
};
let second_half = LiveInterval {
vreg: new_vreg,
start: split_at + 1,
end: li.end,
uses: li.uses.iter().filter(|&&u| u > split_at).copied().collect(),
defs: li.defs.iter().filter(|&&d| d > split_at).copied().collect(),
};
let first_phys = self.find_best_free_reg_for_part(&first_half);
let second_phys = self.find_best_free_reg_for_part(&second_half);
if let (Some(p1), Some(p2)) = (first_phys, second_phys) {
self.assign(li.vreg, p1);
self.assign(new_vreg, p2);
self.insert_split_copy(li.vreg, new_vreg, split_at, mf);
return true;
}
false
}
fn find_best_free_reg_for_part(&self, li: &LiveInterval) -> Option<PhysReg> {
let mut live_regs = HashSet::new();
for (&vreg, &phys) in &self.assignments {
for interval in &self.intervals {
if interval.vreg == vreg && interval.overlaps(li) {
live_regs.insert(phys);
break;
}
}
}
self.available
.iter()
.filter(|r| !live_regs.contains(r))
.copied()
.next()
}
fn make_new_vreg(&self, mf: &mut MachineFunction) -> VirtReg {
mf.virt_reg_counter += 1;
mf.virt_reg_counter
}
fn insert_split_copy(&self, src: VirtReg, dst: VirtReg, at_pos: u32, mf: &mut MachineFunction) {
let mut copy = MachineInstr::new(x86_opcodes::MOV);
copy.operands.push(MachineOperand::Reg(src));
copy.def = Some(dst);
for bb in &mut mf.blocks {
if bb.instructions.len() as u32 > at_pos {
bb.instructions.insert(at_pos as usize, copy.clone());
break;
}
}
}
fn evict_cheapest(&mut self, li: &LiveInterval) -> Option<PhysReg> {
let neighbors = self.interference.neighbors(li.vreg);
let mut best_phys: Option<PhysReg> = None;
let mut best_cost = f64::MAX;
for &neighbor in &neighbors {
if let Some(&phys) = self.assignments.get(&neighbor) {
let cost = self.spill_costs.get(&neighbor).unwrap_or(&f64::MAX);
if *cost < best_cost {
best_cost = *cost;
best_phys = Some(phys);
}
}
}
if let Some(phys) = best_phys {
self.unassign(phys);
}
best_phys
}
fn spill_register(&mut self, li: &LiveInterval, mf: &mut MachineFunction) {
let slot_offset = self.frame_offset;
self.spill_slots.insert(li.vreg, slot_offset);
self.frame_offset -= 8;
for &def_pos in &li.defs {
self.insert_spill_at(li.vreg, slot_offset, def_pos, mf);
}
for &use_pos in &li.uses {
self.insert_reload_at(li.vreg, slot_offset, use_pos, mf);
}
self.regs_spilled += 1;
}
fn insert_spill_at(&self, vreg: VirtReg, offset: i64, pos: u32, mf: &mut MachineFunction) {
let mut spill = MachineInstr::new(x86_opcodes::MOV);
spill.operands.push(MachineOperand::PhysReg(5)); spill.operands.push(MachineOperand::Imm(offset));
spill.operands.push(MachineOperand::Reg(vreg));
for bb in &mut mf.blocks {
let len = bb.instructions.len() as u32;
if pos < len {
bb.instructions.insert(pos as usize, spill);
break;
}
}
}
fn insert_reload_at(&self, vreg: VirtReg, offset: i64, pos: u32, mf: &mut MachineFunction) {
let mut reload = MachineInstr::new(x86_opcodes::MOV);
reload.operands.push(MachineOperand::PhysReg(5)); reload.operands.push(MachineOperand::Imm(offset));
reload.def = Some(vreg);
for bb in &mut mf.blocks {
let len = bb.instructions.len() as u32;
if pos < len {
bb.instructions.insert(pos as usize, reload);
break;
}
}
}
}
impl GreedyRegAlloc {
pub fn compute_spill_costs(&mut self, mf: &MachineFunction) {
let total_blocks = mf.blocks.len() as f64;
for li in &self.intervals {
let use_count = li.uses.len() as f64;
let def_count = li.defs.len() as f64;
let range_length = li.length() as f64;
let base_cost = (use_count * 2.0 + def_count) * (1.0 + range_length / 100.0);
let position_ratio = li.start as f64 / (mf.blocks.len() as f64 * 10.0).max(1.0);
let _ = total_blocks;
let is_rematerializable = self.is_rematerializable(li.vreg, mf);
let remat_factor = if is_rematerializable { 0.3 } else { 1.0 };
let cost = base_cost * (1.0 + position_ratio) * remat_factor;
self.spill_costs.insert(li.vreg, cost);
}
}
fn is_rematerializable(&self, vreg: VirtReg, mf: &MachineFunction) -> bool {
for bb in &mf.blocks {
for mi in &bb.instructions {
if mi.def == Some(vreg) {
return mi.opcode == x86_opcodes::MOV
&& mi.operands.len() >= 2
&& matches!(mi.operands[1], MachineOperand::Imm(_));
}
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct LiveRangeSplit {
pub vreg: VirtReg,
pub split_points: Vec<u32>,
pub new_vregs: Vec<VirtReg>,
}
impl LiveRangeSplit {
pub fn new(vreg: VirtReg) -> Self {
LiveRangeSplit {
vreg,
split_points: Vec::new(),
new_vregs: Vec::new(),
}
}
pub fn compute_split_points(&mut self, interval: &LiveInterval, min_gap: u32) {
let mut uses = interval.uses.clone();
uses.sort();
uses.dedup();
if uses.len() < 2 {
return;
}
let mut gaps: Vec<(u32, u32)> = Vec::new(); for w in uses.windows(2) {
let gap_size = w[1] - w[0];
if gap_size >= min_gap {
gaps.push((w[0], gap_size));
}
}
gaps.sort_by(|a, b| b.1.cmp(&a.1));
for &(gap_start, _) in &gaps {
let split_point = gap_start + min_gap / 2;
self.split_points.push(split_point);
if self.split_points.len() >= 3 {
break;
}
}
self.split_points.sort();
}
pub fn is_profitable(&self, interval: &LiveInterval) -> bool {
if self.split_points.is_empty() {
return false;
}
interval.length() > 20 && !self.split_points.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct RegisterPriority {
pub priorities: HashMap<VirtReg, f64>,
}
impl RegisterPriority {
pub fn new() -> Self {
RegisterPriority {
priorities: HashMap::new(),
}
}
pub fn compute(&mut self, intervals: &[LiveInterval], spill_costs: &HashMap<VirtReg, f64>) {
for li in intervals {
let cost = spill_costs.get(&li.vreg).copied().unwrap_or(1.0);
let use_factor = 1.0 + li.uses.len() as f64 / 10.0;
let range_penalty = 1.0 + li.length() as f64 / 100.0;
let priority = cost * use_factor / range_penalty;
self.priorities.insert(li.vreg, priority);
}
}
pub fn get_priority(&self, vreg: VirtReg) -> f64 {
self.priorities.get(&vreg).copied().unwrap_or(0.0)
}
pub fn sort_by_priority(&self, intervals: &mut [LiveInterval]) {
intervals.sort_by(|a, b| {
let pa = self.get_priority(a.vreg);
let pb = self.get_priority(b.vreg);
pb.partial_cmp(&pa).unwrap_or(std::cmp::Ordering::Equal)
});
}
}
impl Default for RegisterPriority {
fn default() -> Self {
RegisterPriority::new()
}
}
#[derive(Debug, Clone)]
pub struct EvictionCost {
pub costs: HashMap<VirtReg, f64>,
}
impl EvictionCost {
pub fn new() -> Self {
EvictionCost {
costs: HashMap::new(),
}
}
pub fn compute(
&mut self,
assigned: &HashMap<VirtReg, PhysReg>,
intervals: &[LiveInterval],
spill_costs: &HashMap<VirtReg, f64>,
current_position: u32,
) {
self.costs.clear();
for (&vreg, _) in assigned {
let interval = intervals.iter().find(|li| li.vreg == vreg);
if let Some(li) = interval {
let base_cost = spill_costs.get(&vreg).copied().unwrap_or(100.0);
let remaining_uses = li
.uses
.iter()
.filter(|&&pos| pos >= current_position)
.count() as f64;
let adjusted = base_cost * (remaining_uses / li.uses.len().max(1) as f64);
self.costs.insert(vreg, adjusted);
}
}
}
pub fn cheapest_to_evict(&self) -> Option<VirtReg> {
self.costs
.iter()
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map(|(&vreg, _)| vreg)
}
}
impl Default for EvictionCost {
fn default() -> Self {
EvictionCost::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegClass {
Gpr64,
Gpr32,
Gpr16,
Gpr8,
Xmm,
Ymm,
Zmm,
}
#[derive(Debug, Clone)]
pub struct RegClassAssignment {
pub class: RegClass,
pub needs_copy: bool,
pub copy_src: Option<VirtReg>,
}
#[derive(Debug, Clone)]
pub struct CrossClassCopyHandler {
pub copies: Vec<(VirtReg, VirtReg, RegClass, RegClass)>,
}
impl CrossClassCopyHandler {
pub fn new() -> Self {
CrossClassCopyHandler { copies: Vec::new() }
}
pub fn needs_cross_class_copy(from: RegClass, to: RegClass) -> bool {
use RegClass::*;
match (from, to) {
(Gpr64, Gpr32) | (Gpr64, Gpr16) | (Gpr64, Gpr8) => false,
(Gpr32, Gpr16) | (Gpr32, Gpr8) => false,
(Gpr16, Gpr8) => false,
(Gpr32, Gpr64) | (Gpr16, Gpr64) | (Gpr8, Gpr64) => false,
(Gpr64, Xmm) | (Gpr32, Xmm) => true,
(Xmm, Gpr64) | (Xmm, Gpr32) => true,
(Xmm, Ymm) | (Ymm, Xmm) => false, (Ymm, Zmm) | (Zmm, Ymm) => false,
_ => from != to,
}
}
pub fn add_copy(
&mut self,
src: VirtReg,
dst: VirtReg,
from_class: RegClass,
to_class: RegClass,
) {
if Self::needs_cross_class_copy(from_class, to_class) {
self.copies.push((src, dst, from_class, to_class));
}
}
pub fn emit_copies(&self, mf: &mut MachineFunction) {
for &(src, dst, from_class, to_class) in &self.copies {
let opcode = match (from_class, to_class) {
(RegClass::Gpr64, RegClass::Xmm) => x86_opcodes::MOV, (RegClass::Xmm, RegClass::Gpr64) => x86_opcodes::MOV, _ => x86_opcodes::MOV,
};
let mut copy_instr = MachineInstr::new(opcode);
copy_instr.operands.push(MachineOperand::Reg(src));
copy_instr.def = Some(dst);
if let Some(bb) = mf.blocks.first_mut() {
bb.instructions.push(copy_instr);
}
}
}
}
impl Default for CrossClassCopyHandler {
fn default() -> Self {
CrossClassCopyHandler::new()
}
}
impl GreedyRegAlloc {
fn coalesce_copies_greedy(&mut self, mf: &mut MachineFunction) {
let mut coalesced = 0usize;
let mut copy_pairs: Vec<(VirtReg, VirtReg)> = Vec::new();
for bb in &mf.blocks {
for mi in &bb.instructions {
if mi.opcode == x86_opcodes::MOV && mi.operands.len() >= 2 {
if let (MachineOperand::Reg(src), Some(dst)) = (mi.operands[0].clone(), mi.def)
{
copy_pairs.push((src, dst));
}
}
}
}
for (src, dst) in copy_pairs {
let src_phys = self.assignments.get(&src).copied();
let dst_phys = self.assignments.get(&dst).copied();
match (src_phys, dst_phys) {
(Some(sp), Some(dp)) if sp != dp => {
if !self.interference.interfere(src, dst) {
self.assignments.insert(dst, sp);
self.phys_to_vreg.insert(sp, dst);
coalesced += 1;
}
}
(Some(sp), None) => {
let used_by_src: HashSet<PhysReg> = {
let mut s = HashSet::new();
s.insert(sp);
s
};
let free = self.available.iter().find(|r| !used_by_src.contains(r));
if let Some(&free_reg) = free {
self.assign(dst, free_reg);
coalesced += 1;
} else {
self.assign(dst, sp);
coalesced += 1;
}
}
(None, Some(dp)) => {
self.assign(src, dp);
coalesced += 1;
}
(None, None) => {
if let Some(®) = self.available.first() {
self.assign(src, reg);
self.assign(dst, reg);
coalesced += 1;
}
}
(Some(_), Some(_)) => {
}
}
}
self.copies_coalesced = coalesced;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::LLVMContext;
use crate::function;
use crate::instruction;
fn build_simple_func() -> ValueRef {
let mut ctx = LLVMContext::new();
let func = function::new_function("test", ctx.i32(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
let ret = instruction::ret_val(crate::constants::const_i32(42));
entry.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry);
func
}
#[test]
fn test_compile_simple_function() {
let func = build_simple_func();
let asm = compile_function(&func);
assert!(asm.contains(".globl test"));
assert!(asm.contains("ret"));
}
#[test]
fn test_compile_empty_function() {
let mut ctx = LLVMContext::new();
let func = function::new_function("empty", ctx.void_ty(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
let ret = instruction::ret_void();
entry.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry);
let asm = compile_function(&func);
assert!(asm.contains("ret"));
}
#[test]
fn test_compile_function_with_add() {
let mut ctx = LLVMContext::new();
let func = function::new_function("add_test", ctx.i32(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
let a = crate::constants::const_i32(10);
let b = crate::constants::const_i32(20);
let add = instruction::add(a, b);
add.borrow_mut().name = "sum".into();
entry.borrow_mut().push_operand(add.clone());
let ret = instruction::ret_val(add);
entry.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry);
let asm = compile_function(&func);
assert!(asm.contains("add"), "Should contain add: {}", asm);
assert!(asm.contains("ret"));
}
#[test]
fn test_compile_branch() {
let mut ctx = LLVMContext::new();
let func = function::new_function("branch_test", ctx.void_ty(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
let then_bb = crate::basic_block::new_basic_block("then");
let else_bb = crate::basic_block::new_basic_block("else");
let cond = crate::constants::const_bool(true);
entry.borrow_mut().push_operand(instruction::br_cond(
cond,
then_bb.clone(),
else_bb.clone(),
));
then_bb.borrow_mut().push_operand(instruction::ret_void());
else_bb.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry);
func.borrow_mut().push_operand(then_bb);
func.borrow_mut().push_operand(else_bb);
let asm = compile_function(&func);
assert!(asm.contains("jmp") || asm.contains("jne") || asm.contains("je"));
}
#[test]
fn test_selector_handles_alloca() {
let mut ctx = LLVMContext::new();
let func = function::new_function("alloca_test", ctx.void_ty(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
let alloca = instruction::alloca(ctx.i32());
alloca.borrow_mut().name = "p".into();
entry.borrow_mut().push_operand(alloca);
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry);
let asm = compile_function(&func);
assert!(asm.len() > 0);
}
#[test]
fn test_compile_with_icmp() {
let mut ctx = LLVMContext::new();
let func = function::new_function("cmp_test", ctx.i1(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
let a = crate::constants::const_i32(5);
let b = crate::constants::const_i32(10);
let icmp = instruction::icmp(crate::opcode::ICmpPred::Eq, a, b);
icmp.borrow_mut().name = "cmp".into();
entry.borrow_mut().push_operand(icmp.clone());
entry.borrow_mut().push_operand(instruction::ret_val(icmp));
func.borrow_mut().push_operand(entry);
let asm = compile_function(&func);
assert!(asm.contains("cmp"));
}
}
#[cfg(test)]
mod regalloc_tests {
use super::*;
use crate::context::LLVMContext;
use crate::function;
use crate::instruction;
fn build_add_func() -> ValueRef {
let mut ctx = LLVMContext::new();
let func = function::new_function("add_func", ctx.i32(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
let a = crate::constants::const_i32(10);
let b = crate::constants::const_i32(20);
let add = instruction::add(a, b);
add.borrow_mut().name = "result".into();
entry.borrow_mut().push_operand(add.clone());
entry.borrow_mut().push_operand(instruction::ret_val(add));
func.borrow_mut().push_operand(entry);
func
}
fn build_multi_block_func() -> ValueRef {
let mut ctx = LLVMContext::new();
let func = function::new_function("multi", ctx.i32(), &[ctx.i32()]);
let entry = crate::basic_block::new_basic_block("entry");
let then_bb = crate::basic_block::new_basic_block("then");
let else_bb = crate::basic_block::new_basic_block("else");
let cond = crate::constants::const_bool(true);
entry.borrow_mut().push_operand(instruction::br_cond(
cond,
then_bb.clone(),
else_bb.clone(),
));
let a = crate::constants::const_i32(1);
let b = crate::constants::const_i32(2);
let add = instruction::add(a, b);
then_bb.borrow_mut().push_operand(add.clone());
then_bb.borrow_mut().push_operand(instruction::ret_val(add));
let c = crate::constants::const_i32(3);
else_bb.borrow_mut().push_operand(instruction::ret_val(c));
func.borrow_mut().push_operand(entry);
func.borrow_mut().push_operand(then_bb);
func.borrow_mut().push_operand(else_bb);
func
}
#[test]
fn test_target_reg_info_for_x86_64() {
let info = TargetRegInfo::for_x86_64();
assert_eq!(info.gpr_count, 16);
assert_eq!(info.fpr_count, 16);
assert_eq!(info.reg_size, 8);
assert!(info.reserved.contains(&4)); assert!(info.callee_saved.contains(&3)); }
#[test]
fn test_target_reg_info_for_aarch64() {
let info = TargetRegInfo::for_aarch64();
assert_eq!(info.gpr_count, 32);
assert_eq!(info.reg_size, 8);
assert!(info.reserved.contains(&31)); assert!(info.callee_saved.contains(&19));
}
#[test]
fn test_target_reg_info_for_riscv64() {
let info = TargetRegInfo::for_riscv64();
assert_eq!(info.gpr_count, 32);
assert_eq!(info.reg_size, 8);
assert!(info.reserved.contains(&0)); assert!(info.callee_saved.contains(&8)); }
#[test]
fn test_target_reg_info_is_allocatable() {
let info = TargetRegInfo::for_x86_64();
assert!(!info.is_allocatable(4)); assert!(info.is_allocatable(0)); }
#[test]
fn test_target_reg_info_allocatable_regs() {
let info = TargetRegInfo::for_x86_64();
let regs = info.allocatable_regs();
assert!(!regs.contains(&4)); assert!(!regs.is_empty());
}
#[test]
fn test_live_interval_new() {
let li = LiveInterval::new(0);
assert_eq!(li.vreg, 0);
assert_eq!(li.start, u32::MAX);
assert_eq!(li.end, 0);
assert!(li.uses.is_empty());
assert!(li.defs.is_empty());
}
#[test]
fn test_live_interval_overlaps() {
let a = LiveInterval {
vreg: 0,
start: 0,
end: 10,
uses: vec![5],
defs: vec![0],
};
let b = LiveInterval {
vreg: 1,
start: 5,
end: 15,
uses: vec![10],
defs: vec![5],
};
assert!(a.overlaps(&b));
}
#[test]
fn test_live_interval_no_overlap() {
let a = LiveInterval {
vreg: 0,
start: 0,
end: 5,
uses: vec![3],
defs: vec![0],
};
let b = LiveInterval {
vreg: 1,
start: 10,
end: 15,
uses: vec![12],
defs: vec![10],
};
assert!(!a.overlaps(&b));
}
#[test]
fn test_live_interval_live_at() {
let li = LiveInterval {
vreg: 0,
start: 5,
end: 20,
uses: vec![10],
defs: vec![5],
};
assert!(li.live_at(10));
assert!(!li.live_at(0));
assert!(li.live_at(5));
assert!(li.live_at(20));
}
#[test]
fn test_live_interval_recompute_bounds() {
let mut li = LiveInterval::new(7);
li.defs = vec![5, 15];
li.uses = vec![10, 20];
li.recompute_bounds();
assert_eq!(li.start, 5);
assert_eq!(li.end, 20);
}
#[test]
fn test_regalloc_new() {
let target = TargetRegInfo::for_x86_64();
let ra = RegAlloc::new(target);
assert!(ra.assignments.is_empty());
assert!(ra.spilled.is_empty());
assert_eq!(ra.frame_offset, 0);
}
#[test]
fn test_regalloc_allocate_simple() {
let func = build_add_func();
let mut mf = MachineFunction::new("test");
InstructionSelector::select(&mut mf, &func);
let target = TargetRegInfo::for_x86_64();
let mut ra = RegAlloc::new(target);
let result = ra.allocate(&mut mf);
assert!(result.success);
assert!(!ra.assignments.is_empty());
}
#[test]
fn test_regalloc_compute_live_intervals() {
let func = build_add_func();
let mut mf = MachineFunction::new("test");
InstructionSelector::select(&mut mf, &func);
let target = TargetRegInfo::for_x86_64();
let mut ra = RegAlloc::new(target);
let intervals = ra.compute_live_intervals(&mf);
assert!(!intervals.is_empty());
}
#[test]
fn test_regalloc_coalesce_copies() {
let target = TargetRegInfo::for_x86_64();
let mut ra = RegAlloc::new(target);
let mut mf = MachineFunction::new("coalesce_test");
let v0 = mf.new_vreg();
let v1 = mf.new_vreg();
ra.assignments.insert(v0, 0);
ra.assignments.insert(v1, 0);
let mut mov = MachineInstr::new(x86_opcodes::MOV).with_def(v1);
mov.push_reg(v0);
let bb = MachineBasicBlock {
name: "entry".into(),
instructions: vec![mov],
successors: vec![],
..Default::default()
};
mf.push_block(bb);
let coalesced = ra.coalesce_copies(&mut mf);
assert_eq!(coalesced, 1);
assert!(mf.blocks[0].instructions.is_empty());
}
#[test]
fn test_regalloc_eliminate_dead_code() {
let target = TargetRegInfo::for_x86_64();
let mut ra = RegAlloc::new(target);
let mut mf = MachineFunction::new("dead_test");
let v0 = mf.new_vreg();
let v1 = mf.new_vreg();
let dead_mov = MachineInstr::new(x86_opcodes::MOV).with_def(v0);
let live_mov = MachineInstr::new(x86_opcodes::MOV).with_def(v1);
let mut use_mov = MachineInstr::new(x86_opcodes::ADD).with_def(mf.new_vreg());
use_mov.push_reg(v1);
let bb = MachineBasicBlock {
name: "entry".into(),
instructions: vec![dead_mov, live_mov, use_mov],
successors: vec![],
..Default::default()
};
mf.push_block(bb);
ra.eliminate_dead_code(&mut mf);
assert_eq!(mf.blocks[0].instructions.len(), 2);
}
#[test]
fn test_regalloc_result_success() {
let result = RegAllocResult::success();
assert!(result.success);
assert_eq!(result.spills, 0);
assert_eq!(result.reloads, 0);
assert_eq!(result.copies_coalesced, 0);
}
#[test]
fn test_regalloc_result_failure() {
let result = RegAllocResult::failure();
assert!(!result.success);
}
#[test]
fn test_x86_target_isel_select() {
let isel = X86TargetISel;
let func = build_add_func();
let mut mf = MachineFunction::new("isel_test");
isel.select(&mut mf, &func);
assert!(!mf.blocks.is_empty());
}
#[test]
fn test_x86_target_isel_reg_info() {
let isel = X86TargetISel;
let info = isel.get_target_reg_info();
assert_eq!(info.gpr_count, 16);
}
#[test]
fn test_x86_target_isel_frame_lowering() {
let isel = X86TargetISel;
let fl = isel.get_frame_lowering();
let func = build_add_func();
assert!(!fl.needs_frame_pointer(&func));
}
#[test]
fn test_x86_frame_lowering_prologue() {
let fl = X86FrameLowering;
let mut mf = MachineFunction::new("test");
let prologue = fl.emit_prologue(&mut mf);
assert!(!prologue.is_empty());
assert_eq!(prologue[0].opcode, x86_opcodes::PUSH);
}
#[test]
fn test_x86_frame_lowering_epilogue() {
let fl = X86FrameLowering;
let mut mf = MachineFunction::new("test");
let epilogue = fl.emit_epilogue(&mut mf);
assert!(!epilogue.is_empty());
}
#[test]
fn test_x86_frame_lowering_needs_frame_pointer_with_alloca() {
let mut ctx = LLVMContext::new();
let func = function::new_function("alloca_func", ctx.void_ty(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
let alloca = instruction::alloca(ctx.i32());
entry.borrow_mut().push_operand(alloca);
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry);
let fl = X86FrameLowering;
assert!(fl.needs_frame_pointer(&func));
}
#[test]
fn test_compile_function_with_pipeline() {
let func = build_add_func();
let isel = X86TargetISel;
let asm = compile_function_with_pipeline(&func, &isel);
assert!(asm.contains(".globl add_func"));
assert!(asm.contains("ret"));
}
#[test]
fn test_compile_function_with_pipeline_multi_block() {
let func = build_multi_block_func();
let isel = X86TargetISel;
let asm = compile_function_with_pipeline(&func, &isel);
assert!(asm.contains(".globl multi"));
assert!(asm.len() > 0);
}
}