#![allow(non_camel_case_types)]
use std::collections::HashMap;
pub type VReg = u32;
pub type MBBIndex = usize;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MOperand {
VReg(VReg),
PhysReg(u32),
Imm(i64),
Label(String),
Global(String),
}
impl MOperand {
pub fn vreg(v: VReg) -> Self {
MOperand::VReg(v)
}
pub fn imm(v: i64) -> Self {
MOperand::Imm(v)
}
pub fn label(name: &str) -> Self {
MOperand::Label(name.to_string())
}
pub fn as_vreg(&self) -> Option<VReg> {
match self {
MOperand::VReg(v) => Some(*v),
_ => None,
}
}
pub fn as_imm(&self) -> Option<i64> {
match self {
MOperand::Imm(v) => Some(*v),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum GOpcode {
G_BR = 1,
G_BRCOND = 2,
G_RET = 3,
G_CONSTANT = 10,
G_FCONSTANT = 11,
G_FRAME_INDEX = 12,
G_GLOBAL_VALUE = 13,
G_ADD = 20,
G_SUB = 21,
G_MUL = 22,
G_SDIV = 23,
G_UDIV = 24,
G_SREM = 25,
G_UREM = 26,
G_AND = 27,
G_OR = 28,
G_XOR = 29,
G_SHL = 30,
G_LSHR = 31,
G_ASHR = 32,
G_FADD = 40,
G_FSUB = 41,
G_FMUL = 42,
G_FDIV = 43,
G_FREM = 44,
G_FNEG = 45,
G_FABS = 46,
G_TRUNC = 50,
G_ZEXT = 51,
G_SEXT = 52,
G_ANYEXT = 53,
G_FPTRUNC = 54,
G_FPEXT = 55,
G_FPTOUI = 56,
G_FPTOSI = 57,
G_UITOFP = 58,
G_SITOFP = 59,
G_ICMP = 60,
G_FCMP = 61,
G_LOAD = 70,
G_STORE = 71,
G_PTR_ADD = 72,
G_PHI = 80,
G_SELECT = 81,
COPY = 90,
G_PTRTOINT = 100,
G_INTTOPTR = 101,
G_BITCAST = 102,
G_ADDRSPACE_CAST = 103,
G_CTLZ = 110,
G_CTTZ = 111,
G_CTPOP = 112,
G_BSWAP = 113,
G_BITREVERSE = 114,
G_FCEIL = 120,
G_FFLOOR = 121,
G_FSQRT = 122,
G_FMA = 123,
G_FPOW = 124,
G_FEXP = 125,
G_FLOG = 126,
G_UADDO = 130,
G_SADDO = 131,
G_USUBO = 132,
G_SSUBO = 133,
}
impl GOpcode {
pub fn as_u32(self) -> u32 {
self as u32
}
pub fn is_terminator(self) -> bool {
matches!(self, GOpcode::G_BR | GOpcode::G_BRCOND | GOpcode::G_RET)
}
pub fn is_binary_op(self) -> bool {
matches!(
self,
GOpcode::G_ADD
| GOpcode::G_SUB
| GOpcode::G_MUL
| GOpcode::G_SDIV
| GOpcode::G_UDIV
| GOpcode::G_SREM
| GOpcode::G_UREM
| GOpcode::G_AND
| GOpcode::G_OR
| GOpcode::G_XOR
| GOpcode::G_SHL
| GOpcode::G_LSHR
| GOpcode::G_ASHR
| GOpcode::G_FADD
| GOpcode::G_FSUB
| GOpcode::G_FMUL
| GOpcode::G_FDIV
| GOpcode::G_FREM
)
}
pub fn is_commutative(self) -> bool {
matches!(
self,
GOpcode::G_ADD
| GOpcode::G_MUL
| GOpcode::G_AND
| GOpcode::G_OR
| GOpcode::G_XOR
| GOpcode::G_FADD
| GOpcode::G_FMUL
)
}
}
#[derive(Debug, Clone)]
pub struct GInstruction {
pub opcode: GOpcode,
pub operands: Vec<MOperand>,
}
impl GInstruction {
pub fn new(opcode: GOpcode) -> Self {
GInstruction {
opcode,
operands: Vec::new(),
}
}
pub fn with_operands(opcode: GOpcode, operands: Vec<MOperand>) -> Self {
GInstruction { opcode, operands }
}
pub fn def_vreg(&self) -> Option<VReg> {
self.operands.first().and_then(|op| op.as_vreg())
}
pub fn uses(&self) -> &[MOperand] {
if self.operands.is_empty() {
&[]
} else {
&self.operands[1..]
}
}
pub fn all_operands(&self) -> &[MOperand] {
&self.operands
}
}
#[derive(Debug, Clone)]
pub struct GMachineBasicBlock {
pub name: String,
pub instructions: Vec<GInstruction>,
pub successors: Vec<MBBIndex>,
}
impl GMachineBasicBlock {
pub fn new(name: String) -> Self {
GMachineBasicBlock {
name,
instructions: Vec::new(),
successors: Vec::new(),
}
}
pub fn push_instruction(&mut self, inst: GInstruction) {
self.instructions.push(inst);
}
pub fn add_successor(&mut self, bb: MBBIndex) {
if !self.successors.contains(&bb) {
self.successors.push(bb);
}
}
pub fn instruction_count(&self) -> usize {
self.instructions.len()
}
}
#[derive(Debug, Clone)]
pub struct GMachineFunction {
pub name: String,
pub blocks: Vec<GMachineBasicBlock>,
pub next_vreg: VReg,
}
impl GMachineFunction {
pub fn new(name: String) -> Self {
GMachineFunction {
name,
blocks: Vec::new(),
next_vreg: 0,
}
}
pub fn new_vreg(&mut self) -> VReg {
let v = self.next_vreg;
self.next_vreg += 1;
v
}
pub fn push_block(&mut self, name: String) -> MBBIndex {
let idx = self.blocks.len();
self.blocks.push(GMachineBasicBlock::new(name));
idx
}
pub fn block_count(&self) -> usize {
self.blocks.len()
}
pub fn instruction_count(&self) -> usize {
self.blocks.iter().map(|b| b.instruction_count()).sum()
}
}
pub struct MachineIRBuilder {
pub mf: GMachineFunction,
pub current_block: MBBIndex,
pub insert_point: usize,
}
impl MachineIRBuilder {
pub fn new(func_name: &str) -> Self {
let mut mf = GMachineFunction::new(func_name.to_string());
let entry = mf.push_block("entry".to_string());
MachineIRBuilder {
mf,
current_block: entry,
insert_point: 0,
}
}
pub fn set_insert_point(&mut self, bb: MBBIndex, pos: usize) {
self.current_block = bb;
self.insert_point = pos;
}
pub fn set_insert_point_to_end(&mut self) {
let len = self.mf.blocks[self.current_block].instruction_count();
self.insert_point = len;
}
pub fn new_vreg(&mut self) -> VReg {
self.mf.new_vreg()
}
pub fn new_block(&mut self, name: &str) -> MBBIndex {
self.mf.push_block(name.to_string())
}
pub fn build_copy(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::COPY,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_constant(&mut self, val: i64) -> VReg {
let dst = self.new_vreg();
let inst = GInstruction::with_operands(
GOpcode::G_CONSTANT,
vec![MOperand::vreg(dst), MOperand::imm(val)],
);
self.insert_instruction(inst);
dst
}
pub fn build_add(&mut self, dst: VReg, src1: VReg, src2: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_ADD,
vec![
MOperand::vreg(dst),
MOperand::vreg(src1),
MOperand::vreg(src2),
],
);
self.insert_instruction(inst);
}
pub fn build_sub(&mut self, dst: VReg, src1: VReg, src2: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_SUB,
vec![
MOperand::vreg(dst),
MOperand::vreg(src1),
MOperand::vreg(src2),
],
);
self.insert_instruction(inst);
}
pub fn build_mul(&mut self, dst: VReg, src1: VReg, src2: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_MUL,
vec![
MOperand::vreg(dst),
MOperand::vreg(src1),
MOperand::vreg(src2),
],
);
self.insert_instruction(inst);
}
pub fn build_and(&mut self, dst: VReg, src1: VReg, src2: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_AND,
vec![
MOperand::vreg(dst),
MOperand::vreg(src1),
MOperand::vreg(src2),
],
);
self.insert_instruction(inst);
}
pub fn build_or(&mut self, dst: VReg, src1: VReg, src2: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_OR,
vec![
MOperand::vreg(dst),
MOperand::vreg(src1),
MOperand::vreg(src2),
],
);
self.insert_instruction(inst);
}
pub fn build_xor(&mut self, dst: VReg, src1: VReg, src2: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_XOR,
vec![
MOperand::vreg(dst),
MOperand::vreg(src1),
MOperand::vreg(src2),
],
);
self.insert_instruction(inst);
}
pub fn build_load(&mut self, dst: VReg, addr: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_LOAD,
vec![MOperand::vreg(dst), MOperand::vreg(addr)],
);
self.insert_instruction(inst);
}
pub fn build_store(&mut self, val: VReg, addr: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_STORE,
vec![MOperand::vreg(val), MOperand::vreg(addr)],
);
self.insert_instruction(inst);
}
pub fn build_br(&mut self, target: MBBIndex) {
let label = self.mf.blocks[target].name.clone();
let inst = GInstruction::with_operands(GOpcode::G_BR, vec![MOperand::label(&label)]);
self.insert_instruction(inst);
self.mf.blocks[self.current_block].add_successor(target);
}
pub fn build_cond_br(&mut self, cond: VReg, t_bb: MBBIndex, f_bb: MBBIndex) {
let t_label = self.mf.blocks[t_bb].name.clone();
let f_label = self.mf.blocks[f_bb].name.clone();
let inst = GInstruction::with_operands(
GOpcode::G_BRCOND,
vec![
MOperand::vreg(cond),
MOperand::label(&t_label),
MOperand::label(&f_label),
],
);
self.insert_instruction(inst);
self.mf.blocks[self.current_block].add_successor(t_bb);
self.mf.blocks[self.current_block].add_successor(f_bb);
}
pub fn build_ret(&mut self, val: VReg) {
let inst = GInstruction::with_operands(GOpcode::G_RET, vec![MOperand::vreg(val)]);
self.insert_instruction(inst);
}
pub fn build_call(&mut self, _callee: VReg, _args: &[VReg]) -> VReg {
let dst = self.new_vreg();
let inst = GInstruction::new(GOpcode::G_ADD); self.insert_instruction(inst);
dst
}
fn insert_instruction(&mut self, inst: GInstruction) {
let block = &mut self.mf.blocks[self.current_block];
if self.insert_point >= block.instruction_count() {
block.push_instruction(inst);
} else {
block.instructions.insert(self.insert_point, inst);
}
self.insert_point += 1;
}
pub fn get_mf(&self) -> &GMachineFunction {
&self.mf
}
pub fn get_mf_mut(&mut self) -> &mut GMachineFunction {
&mut self.mf
}
pub fn total_instructions(&self) -> usize {
self.mf.instruction_count()
}
}
impl GOpcode {
pub fn is_extension(self) -> bool {
matches!(
self,
GOpcode::G_ANYEXT | GOpcode::G_ZEXT | GOpcode::G_SEXT | GOpcode::G_FPEXT
)
}
pub fn is_truncation(self) -> bool {
matches!(self, GOpcode::G_TRUNC | GOpcode::G_FPTRUNC)
}
pub fn is_int_ptr_conversion(self) -> bool {
matches!(self, GOpcode::G_PTRTOINT | GOpcode::G_INTTOPTR)
}
pub fn is_bitwise_manipulation(self) -> bool {
matches!(
self,
GOpcode::G_CTLZ
| GOpcode::G_CTTZ
| GOpcode::G_CTPOP
| GOpcode::G_BSWAP
| GOpcode::G_BITREVERSE
)
}
pub fn is_fp_unary(self) -> bool {
matches!(
self,
GOpcode::G_FNEG
| GOpcode::G_FABS
| GOpcode::G_FCEIL
| GOpcode::G_FFLOOR
| GOpcode::G_FSQRT
| GOpcode::G_FEXP
| GOpcode::G_FLOG
)
}
pub fn is_fp_ternary(self) -> bool {
matches!(self, GOpcode::G_FMA | GOpcode::G_FPOW)
}
pub fn is_overflow_op(self) -> bool {
matches!(
self,
GOpcode::G_UADDO | GOpcode::G_SADDO | GOpcode::G_USUBO | GOpcode::G_SSUBO
)
}
pub fn num_defs(self) -> usize {
match self {
GOpcode::COPY
| GOpcode::G_CONSTANT
| GOpcode::G_FCONSTANT
| GOpcode::G_FRAME_INDEX
| GOpcode::G_GLOBAL_VALUE
| GOpcode::G_LOAD
| GOpcode::G_ANYEXT
| GOpcode::G_ZEXT
| GOpcode::G_SEXT
| GOpcode::G_TRUNC
| GOpcode::G_FPTRUNC
| GOpcode::G_FPEXT
| GOpcode::G_FPTOUI
| GOpcode::G_FPTOSI
| GOpcode::G_UITOFP
| GOpcode::G_SITOFP
| GOpcode::G_PTRTOINT
| GOpcode::G_INTTOPTR
| GOpcode::G_BITCAST
| GOpcode::G_ADDRSPACE_CAST
| GOpcode::G_CTLZ
| GOpcode::G_CTTZ
| GOpcode::G_CTPOP
| GOpcode::G_BSWAP
| GOpcode::G_BITREVERSE
| GOpcode::G_FNEG
| GOpcode::G_FABS
| GOpcode::G_FCEIL
| GOpcode::G_FFLOOR
| GOpcode::G_FSQRT
| GOpcode::G_FEXP
| GOpcode::G_FLOG
| GOpcode::G_ICMP
| GOpcode::G_FCMP
| GOpcode::G_SELECT
| GOpcode::G_PHI
| GOpcode::G_PTR_ADD => 1,
GOpcode::G_UADDO | GOpcode::G_SADDO | GOpcode::G_USUBO | GOpcode::G_SSUBO => 2,
_ => 1,
}
}
pub fn num_uses(self) -> usize {
match self {
GOpcode::G_BR => 1,
GOpcode::G_BRCOND => 3,
GOpcode::G_RET => 1,
GOpcode::G_STORE => 2,
GOpcode::G_FMA => 3,
GOpcode::G_FPOW => 2,
GOpcode::G_SELECT => 3,
_ => {
if self.is_binary_op() || self.is_overflow_op() {
2
} else if self.is_fp_unary()
|| self.is_extension()
|| self.is_truncation()
|| self.is_bitwise_manipulation()
|| self.is_int_ptr_conversion()
{
1
} else if self.is_fp_ternary() {
3
} else {
2
}
}
}
}
pub fn verify_operands(&self, operands: &[MOperand]) -> Result<(), String> {
let expected_uses = self.num_uses();
let expected_defs = self.num_defs();
let total = expected_defs + expected_uses;
if operands.len() < total {
return Err(format!(
"{:?}: expected {} operands ({} defs + {} uses), got {}",
self,
total,
expected_defs,
expected_uses,
operands.len()
));
}
for i in 0..expected_defs {
if !matches!(operands[i], MOperand::VReg(_)) {
return Err(format!("{:?}: def operand {} must be a vreg", self, i));
}
}
Ok(())
}
}
impl MachineIRBuilder {
pub fn build_anyext(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_ANYEXT,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_zext(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_ZEXT,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_sext(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_SEXT,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_trunc(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_TRUNC,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_ptrtoint(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_PTRTOINT,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_inttoptr(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_INTTOPTR,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_bitcast(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_BITCAST,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_addrspace_cast(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_ADDRSPACE_CAST,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_ctlz(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_CTLZ,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_cttz(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_CTTZ,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_ctpop(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_CTPOP,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_bswap(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_BSWAP,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_bitreverse(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_BITREVERSE,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_fceil(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FCEIL,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_ffloor(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FFLOOR,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_fabs(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FABS,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_fneg(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FNEG,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_fsqrt(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FSQRT,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_fma(&mut self, dst: VReg, a: VReg, b: VReg, c: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FMA,
vec![
MOperand::vreg(dst),
MOperand::vreg(a),
MOperand::vreg(b),
MOperand::vreg(c),
],
);
self.insert_instruction(inst);
dst
}
pub fn build_fpow(&mut self, dst: VReg, base: VReg, exp: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FPOW,
vec![
MOperand::vreg(dst),
MOperand::vreg(base),
MOperand::vreg(exp),
],
);
self.insert_instruction(inst);
dst
}
pub fn build_fexp(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FEXP,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_flog(&mut self, dst: VReg, src: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_FLOG,
vec![MOperand::vreg(dst), MOperand::vreg(src)],
);
self.insert_instruction(inst);
dst
}
pub fn build_uaddo(&mut self, dst: VReg, overflow: VReg, a: VReg, b: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_UADDO,
vec![
MOperand::vreg(dst),
MOperand::vreg(overflow),
MOperand::vreg(a),
MOperand::vreg(b),
],
);
self.insert_instruction(inst);
}
pub fn build_saddo(&mut self, dst: VReg, overflow: VReg, a: VReg, b: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_SADDO,
vec![
MOperand::vreg(dst),
MOperand::vreg(overflow),
MOperand::vreg(a),
MOperand::vreg(b),
],
);
self.insert_instruction(inst);
}
pub fn build_usubo(&mut self, dst: VReg, overflow: VReg, a: VReg, b: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_USUBO,
vec![
MOperand::vreg(dst),
MOperand::vreg(overflow),
MOperand::vreg(a),
MOperand::vreg(b),
],
);
self.insert_instruction(inst);
}
pub fn build_ssubo(&mut self, dst: VReg, overflow: VReg, a: VReg, b: VReg) {
let inst = GInstruction::with_operands(
GOpcode::G_SSUBO,
vec![
MOperand::vreg(dst),
MOperand::vreg(overflow),
MOperand::vreg(a),
MOperand::vreg(b),
],
);
self.insert_instruction(inst);
}
pub fn build_select(&mut self, dst: VReg, cond: VReg, tval: VReg, fval: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_SELECT,
vec![
MOperand::vreg(dst),
MOperand::vreg(cond),
MOperand::vreg(tval),
MOperand::vreg(fval),
],
);
self.insert_instruction(inst);
dst
}
pub fn build_ptr_add(&mut self, dst: VReg, base: VReg, offset: VReg) -> VReg {
let inst = GInstruction::with_operands(
GOpcode::G_PTR_ADD,
vec![
MOperand::vreg(dst),
MOperand::vreg(base),
MOperand::vreg(offset),
],
);
self.insert_instruction(inst);
dst
}
pub fn verify_function(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
for (bb_idx, block) in self.mf.blocks.iter().enumerate() {
for (i_idx, inst) in block.instructions.iter().enumerate() {
if let Err(e) = inst.opcode.verify_operands(&inst.operands) {
errors.push(format!("BB{} inst{}: {}", bb_idx, i_idx, e));
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
#[derive(Debug)]
pub struct MachineVerificationResult {
pub passed: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl GMachineFunction {
pub fn verify(&self) -> MachineVerificationResult {
let mut result = MachineVerificationResult {
passed: true,
errors: Vec::new(),
warnings: Vec::new(),
};
for (bb_idx, block) in self.blocks.iter().enumerate() {
for &succ in &block.successors {
if succ >= self.blocks.len() {
result.errors.push(format!(
"BB{}: successor {} is out of range (max {})",
bb_idx,
succ,
self.blocks.len()
));
result.passed = false;
}
}
for (i_idx, inst) in block.instructions.iter().enumerate() {
if inst.opcode.is_terminator() {
if i_idx != block.instructions.len() - 1 {
result.errors.push(format!(
"BB{}: terminator at position {}, not end of block",
bb_idx, i_idx
));
result.passed = false;
}
}
}
if !block.instructions.is_empty() {
let last = block.instructions.last().unwrap();
if !last.opcode.is_terminator() {
result.warnings.push(format!(
"BB{}: block does not end with a terminator",
bb_idx
));
}
}
}
result
}
pub fn dump(&self) -> String {
let mut out = String::new();
out.push_str(&format!("MachineFunction: {}\n", self.name));
for (bb_idx, block) in self.blocks.iter().enumerate() {
out.push_str(&format!("bb.{} ({})\n", bb_idx, block.name));
if !block.successors.is_empty() {
out.push_str(" successors: ");
for (i, &succ) in block.successors.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
if succ < self.blocks.len() {
out.push_str(&format!("bb.{}", succ));
} else {
out.push_str(&format!("<invalid {}>", succ));
}
}
out.push('\n');
}
for inst in &block.instructions {
out.push_str(&format!(" {:?}\n", inst.opcode));
for (i, op) in inst.operands.iter().enumerate() {
match op {
MOperand::VReg(v) => {
out.push_str(&format!(" op{}: %vreg{}\n", i, v));
}
MOperand::PhysReg(r) => {
out.push_str(&format!(" op{}: $physreg{}\n", i, r));
}
MOperand::Imm(v) => {
out.push_str(&format!(" op{}: #imm {}\n", i, v));
}
MOperand::Label(name) => {
out.push_str(&format!(" op{}: label \"{}\"\n", i, name));
}
MOperand::Global(name) => {
out.push_str(&format!(" op{}: @{}\n", i, name));
}
}
}
}
}
out
}
pub fn find_block(&self, name: &str) -> Option<MBBIndex> {
self.blocks.iter().position(|b| b.name == name)
}
pub fn remove_instruction(&mut self, bb_idx: MBBIndex, inst_idx: usize) -> bool {
if bb_idx >= self.blocks.len() {
return false;
}
let block = &mut self.blocks[bb_idx];
if inst_idx >= block.instructions.len() {
return false;
}
block.instructions.remove(inst_idx);
true
}
pub fn replace_instruction(
&mut self,
bb_idx: MBBIndex,
inst_idx: usize,
new_inst: GInstruction,
) -> bool {
if bb_idx >= self.blocks.len() {
return false;
}
let block = &mut self.blocks[bb_idx];
if inst_idx >= block.instructions.len() {
return false;
}
block.instructions[inst_idx] = new_inst;
true
}
pub fn count_vregs(&self) -> usize {
let mut max_vreg = 0;
for block in &self.blocks {
for inst in &block.instructions {
for op in &inst.operands {
if let MOperand::VReg(v) = op {
max_vreg = max_vreg.max(*v as usize + 1);
}
}
}
}
max_vreg
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegBank {
GPR,
FPR,
CCR,
VecR,
}
impl RegBank {
pub fn default_size(self) -> u32 {
match self {
RegBank::GPR => 64,
RegBank::FPR => 128,
RegBank::CCR => 1,
RegBank::VecR => 128,
}
}
pub fn name(self) -> &'static str {
match self {
RegBank::GPR => "GPR",
RegBank::FPR => "FPR",
RegBank::CCR => "CCR",
RegBank::VecR => "VecR",
}
}
}
#[derive(Debug, Clone)]
pub struct RegBankSelect {
pub assignments: HashMap<VReg, RegBank>,
pub rules: Vec<BankRule>,
}
#[derive(Debug, Clone)]
pub struct BankRule {
pub opcode: GOpcode,
pub operand_idx: u32,
pub bank: RegBank,
}
impl RegBankSelect {
pub fn new() -> Self {
Self {
assignments: HashMap::new(),
rules: Vec::new(),
}
}
pub fn add_rule(&mut self, opcode: GOpcode, operand_idx: u32, bank: RegBank) {
self.rules.push(BankRule {
opcode,
operand_idx,
bank,
});
}
pub fn assign_banks(&mut self, mf: &GMachineFunction) {
self.assignments.clear();
for block in &mf.blocks {
for inst in &block.instructions {
for rule in &self.rules {
if rule.opcode == inst.opcode {
if rule.operand_idx == 0 {
if let Some(def) = inst.def_vreg() {
self.assignments.insert(def, rule.bank);
}
} else {
let use_idx = (rule.operand_idx - 1) as usize;
if use_idx < inst.operands.len() {
if let Some(vreg) = inst.operands[use_idx].as_vreg() {
self.assignments.insert(vreg, rule.bank);
}
}
}
}
}
if inst.def_vreg().is_some() {
let bank = self.default_bank_for_opcode(inst.opcode);
if let Some(def) = inst.def_vreg() {
self.assignments.entry(def).or_insert(bank);
}
}
}
}
}
fn default_bank_for_opcode(&self, opcode: GOpcode) -> RegBank {
match opcode {
GOpcode::G_FADD
| GOpcode::G_FSUB
| GOpcode::G_FMUL
| GOpcode::G_FDIV
| GOpcode::G_FREM
| GOpcode::G_FNEG
| GOpcode::G_FABS
| GOpcode::G_FCEIL
| GOpcode::G_FFLOOR
| GOpcode::G_FSQRT
| GOpcode::G_FMA
| GOpcode::G_FPOW
| GOpcode::G_FEXP
| GOpcode::G_FLOG => RegBank::FPR,
GOpcode::G_ICMP | GOpcode::G_FCMP => RegBank::CCR,
_ => RegBank::GPR,
}
}
pub fn get_bank(&self, vreg: VReg) -> Option<RegBank> {
self.assignments.get(&vreg).copied()
}
pub fn default_config() -> Self {
let mut sel = Self::new();
sel.add_rule(GOpcode::G_FADD, 0, RegBank::FPR);
sel.add_rule(GOpcode::G_FMUL, 0, RegBank::FPR);
sel.add_rule(GOpcode::G_FADD, 1, RegBank::FPR);
sel.add_rule(GOpcode::G_FADD, 2, RegBank::FPR);
sel.add_rule(GOpcode::G_ADD, 0, RegBank::GPR);
sel.add_rule(GOpcode::G_ADD, 1, RegBank::GPR);
sel.add_rule(GOpcode::G_ADD, 2, RegBank::GPR);
sel.add_rule(GOpcode::G_ICMP, 0, RegBank::CCR);
sel
}
}
impl Default for RegBankSelect {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TargetInstr {
pub opcode: u32,
pub name: String,
pub num_operands: u32,
pub is_commutable: bool,
pub operand_constraints: Vec<(bool, u32)>,
}
#[derive(Debug, Clone)]
pub struct InstructionSelect {
pub selection_table: HashMap<GOpcode, TargetInstr>,
pub fallback: Option<TargetInstr>,
pub selected: usize,
}
impl InstructionSelect {
pub fn new() -> Self {
Self {
selection_table: HashMap::new(),
fallback: None,
selected: 0,
}
}
pub fn add_selection(&mut self, generic: GOpcode, target: TargetInstr) {
self.selection_table.insert(generic, target);
}
pub fn select(&mut self, mf: &mut GMachineFunction) -> usize {
self.selected = 0;
for block in &mut mf.blocks {
for inst in &mut block.instructions {
if let Some(target) = self.selection_table.get(&inst.opcode) {
self.selected += 1;
}
}
}
self.selected
}
pub fn has_selection(&self, opcode: GOpcode) -> bool {
self.selection_table.contains_key(&opcode)
}
pub fn x86_64_default() -> Self {
let mut isel = Self::new();
isel.add_selection(
GOpcode::G_ADD,
TargetInstr {
opcode: 0x01,
name: "ADD".to_string(),
num_operands: 3,
is_commutable: true,
operand_constraints: vec![(true, 0), (false, 0), (false, 0)],
},
);
isel.add_selection(
GOpcode::G_LOAD,
TargetInstr {
opcode: 0x8B,
name: "MOVrm".to_string(),
num_operands: 2,
is_commutable: false,
operand_constraints: vec![(true, 0), (false, 1)],
},
);
isel.add_selection(
GOpcode::G_STORE,
TargetInstr {
opcode: 0x89,
name: "MOVmr".to_string(),
num_operands: 2,
is_commutable: false,
operand_constraints: vec![(false, 1), (false, 0)],
},
);
isel
}
}
impl Default for InstructionSelect {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GIMatchEntry {
pub match_opcode: GOpcode,
pub emit_opcode: u32,
pub predicate: Option<MatchPredicate>,
pub operand_remap: Vec<(u32, u32)>,
}
#[derive(Debug, Clone)]
pub enum MatchPredicate {
IsConstant(u32),
IsBank(u32, RegBank),
TypeSize(u32, u32),
Always,
}
#[derive(Debug, Clone)]
pub struct GIMatchTable {
pub entries: Vec<GIMatchEntry>,
pub matches: usize,
}
impl GIMatchTable {
pub fn new() -> Self {
Self {
entries: Vec::new(),
matches: 0,
}
}
pub fn add_entry(&mut self, entry: GIMatchEntry) {
self.entries.push(entry);
}
pub fn try_match(&self, opcode: GOpcode, operands: &[MOperand]) -> Option<u32> {
for entry in &self.entries {
if entry.match_opcode != opcode {
continue;
}
if let Some(ref pred) = entry.predicate {
if !self.evaluate_predicate(pred, operands) {
continue;
}
}
return Some(entry.emit_opcode);
}
None
}
fn evaluate_predicate(&self, pred: &MatchPredicate, operands: &[MOperand]) -> bool {
match pred {
MatchPredicate::IsConstant(idx) => {
if (*idx as usize) < operands.len() {
operands[*idx as usize].as_imm().is_some()
} else {
false
}
}
MatchPredicate::IsBank(_idx, _bank) => {
true
}
MatchPredicate::TypeSize(_idx, _size) => {
true
}
MatchPredicate::Always => true,
}
}
pub fn apply(&mut self, mf: &GMachineFunction) -> usize {
self.matches = 0;
for block in &mf.blocks {
for inst in &block.instructions {
if self.try_match(inst.opcode, &inst.operands).is_some() {
self.matches += 1;
}
}
}
self.matches
}
pub fn default_table() -> Self {
let mut table = Self::new();
table.add_entry(GIMatchEntry {
match_opcode: GOpcode::G_ADD,
emit_opcode: 0x8D, predicate: Some(MatchPredicate::IsConstant(1)),
operand_remap: vec![(0, 0), (1, 1), (2, 2)],
});
table.add_entry(GIMatchEntry {
match_opcode: GOpcode::G_LOAD,
emit_opcode: 0x8B, predicate: None,
operand_remap: vec![(0, 0), (1, 1)],
});
table.add_entry(GIMatchEntry {
match_opcode: GOpcode::G_STORE,
emit_opcode: 0x89, predicate: None,
operand_remap: vec![(0, 1), (1, 0)],
});
table.add_entry(GIMatchEntry {
match_opcode: GOpcode::G_BR,
emit_opcode: 0xE9, predicate: None,
operand_remap: vec![(0, 0)],
});
table
}
}
impl Default for GIMatchTable {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ConstrainRegClass {
pub constraints: HashMap<VReg, u32>,
pub reg_classes: Vec<RegClass>,
}
#[derive(Debug, Clone)]
pub struct RegClass {
pub id: u32,
pub name: String,
pub registers: Vec<u32>,
pub reg_size: u32,
pub spill_size: u32,
}
impl ConstrainRegClass {
pub fn new() -> Self {
Self {
constraints: HashMap::new(),
reg_classes: Vec::new(),
}
}
pub fn add_reg_class(&mut self, rc: RegClass) {
self.reg_classes.push(rc);
}
pub fn constrain(&mut self, vreg: VReg, class_id: u32) {
self.constraints.insert(vreg, class_id);
}
pub fn get_constraint(&self, vreg: VReg) -> Option<u32> {
self.constraints.get(&vreg).copied()
}
pub fn get_reg_class(&self, id: u32) -> Option<&RegClass> {
self.reg_classes.iter().find(|rc| rc.id == id)
}
pub fn x86_64_default() -> Self {
let mut crc = Self::new();
crc.add_reg_class(RegClass {
id: 0,
name: "GR64".to_string(),
registers: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
reg_size: 64,
spill_size: 64,
});
crc.add_reg_class(RegClass {
id: 1,
name: "GR32".to_string(),
registers: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
reg_size: 32,
spill_size: 32,
});
crc.add_reg_class(RegClass {
id: 2,
name: "FR64".to_string(),
registers: vec![
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
],
reg_size: 64,
spill_size: 64,
});
crc
}
}
impl Default for ConstrainRegClass {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PostISelCombiner {
pub num_combined: usize,
pub max_iterations: usize,
}
impl PostISelCombiner {
pub fn new() -> Self {
Self {
num_combined: 0,
max_iterations: 4,
}
}
pub fn combine(&mut self, _mf: &mut GMachineFunction) -> usize {
self.num_combined = 0;
self.num_combined
}
pub fn try_combine_adjacent(
&self,
first: &GInstruction,
second: &GInstruction,
) -> Option<GInstruction> {
if first.opcode == GOpcode::G_ADD {
if let Some(MOperand::Imm(0)) = first.operands.get(1) {
if let Some(src) = first.operands.first() {
let mut instr = GInstruction::new(GOpcode::COPY);
if let Some(def) = first.def_vreg() {
instr.operands.push(MOperand::vreg(def));
}
instr.operands.push(src.clone());
return Some(instr);
}
}
}
if first.opcode == GOpcode::G_MUL {
if let Some(MOperand::Imm(1)) = first.operands.get(1) {
if let Some(src) = first.operands.first() {
let mut instr = GInstruction::new(GOpcode::COPY);
if let Some(def) = first.def_vreg() {
instr.operands.push(MOperand::vreg(def));
}
instr.operands.push(src.clone());
return Some(instr);
}
}
}
None
}
}
impl Default for PostISelCombiner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct LoadStoreLegalizer {
pub max_load_width: u32,
pub unaligned_access: bool,
pub little_endian: bool,
}
impl LoadStoreLegalizer {
pub fn new(max_load_width: u32, unaligned_access: bool) -> Self {
Self {
max_load_width,
unaligned_access,
little_endian: true,
}
}
pub fn needs_legalization(&self, bit_width: u32) -> bool {
!self.is_legal_width(bit_width)
}
fn is_legal_width(&self, bit_width: u32) -> bool {
bit_width == 8
|| bit_width == 16
|| bit_width == 32
|| bit_width == 64
|| (bit_width <= self.max_load_width && bit_width.is_power_of_two())
}
pub fn legalize_narrow_load(
&self,
addr: VReg,
bit_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
match bit_width {
1 => {
seq.push((
GOpcode::G_LOAD,
vec![MOperand::vreg(addr), MOperand::imm(8)],
));
}
w if w < 8 => {
seq.push((
GOpcode::G_LOAD,
vec![MOperand::vreg(addr), MOperand::imm(8)],
));
}
_ => {
seq.push((
GOpcode::G_LOAD,
vec![MOperand::vreg(addr), MOperand::imm(bit_width as i64)],
));
}
}
seq
}
pub fn legalize_wide_load(&self, addr: VReg, bit_width: u32) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let mut remaining = bit_width;
let mut offset: i64 = 0;
while remaining > 0 {
let chunk = remaining.min(self.max_load_width);
let chunk = chunk.next_power_of_two() / 2;
let chunk = chunk.min(remaining);
let mut operands = vec![MOperand::vreg(addr)];
if offset != 0 {
operands.push(MOperand::imm(offset));
}
seq.push((GOpcode::G_LOAD, operands));
offset += (chunk / 8) as i64;
remaining -= chunk;
}
seq
}
pub fn legalize_unaligned_load(
&self,
addr: VReg,
bit_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
if self.unaligned_access {
return vec![(
GOpcode::G_LOAD,
vec![MOperand::vreg(addr), MOperand::imm(bit_width as i64)],
)];
}
let num_bytes = (bit_width + 7) / 8;
let mut seq = Vec::new();
for i in 0..num_bytes {
let mut operands = vec![MOperand::vreg(addr)];
if self.little_endian {
operands.push(MOperand::imm(i as i64));
} else {
operands.push(MOperand::imm((num_bytes - 1 - i) as i64));
}
seq.push((GOpcode::G_LOAD, operands));
}
seq
}
}
impl Default for LoadStoreLegalizer {
fn default() -> Self {
Self::new(64, true)
}
}
#[derive(Debug, Clone)]
pub struct ConstantMaterializer {
pub max_immediate_bits: u32,
pub has_imm64: bool,
}
impl ConstantMaterializer {
pub fn new(max_immediate_bits: u32) -> Self {
Self {
max_immediate_bits,
has_imm64: false,
}
}
pub fn can_materialize(&self, value: i64) -> bool {
let abs_val = value.unsigned_abs();
abs_val < (1u64 << self.max_immediate_bits) || (value == 0) || (self.has_imm64)
}
pub fn decompose(&self, value: i64) -> Vec<(GOpcode, i64)> {
if self.can_materialize(value) {
return vec![(GOpcode::G_CONSTANT, value)];
}
let mut seq = Vec::new();
let mut remaining = value as u64;
let mut shift = 0u32;
while remaining != 0 {
let chunk = (remaining & ((1u64 << self.max_immediate_bits) - 1)) as i64;
if chunk != 0 {
if shift == 0 {
seq.push((GOpcode::G_CONSTANT, chunk));
} else {
seq.push((GOpcode::G_CONSTANT, chunk));
seq.push((GOpcode::G_SHL, shift as i64));
seq.push((GOpcode::G_OR, 0));
}
}
remaining >>= self.max_immediate_bits;
shift += self.max_immediate_bits;
}
if seq.is_empty() {
seq.push((GOpcode::G_CONSTANT, 0));
}
seq
}
pub fn materialization_cost(&self, value: i64) -> usize {
self.decompose(value).len()
}
}
impl Default for ConstantMaterializer {
fn default() -> Self {
Self::new(16)
}
}
#[derive(Debug, Clone)]
pub struct GEPLowerer {
pub pointer_size: u32,
}
impl GEPLowerer {
pub fn new(pointer_size: u32) -> Self {
Self { pointer_size }
}
pub fn decompose_gep(
&self,
base_ptr: VReg,
indices: &[(VReg, u32)],
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let mut current_ptr = base_ptr;
for &(idx_reg, elem_size) in indices {
let offset_vreg = if elem_size > 1 {
let temp = idx_reg.wrapping_add(1000); seq.push((
GOpcode::G_MUL,
vec![
MOperand::vreg(temp),
MOperand::vreg(idx_reg),
MOperand::imm(elem_size as i64),
],
));
temp
} else {
idx_reg
};
let new_ptr = current_ptr.wrapping_add(1); seq.push((
GOpcode::G_PTR_ADD,
vec![
MOperand::vreg(new_ptr),
MOperand::vreg(current_ptr),
MOperand::vreg(offset_vreg),
],
));
current_ptr = new_ptr;
}
seq
}
pub fn lower_gep(&self, base_ptr: VReg, offset_bytes: i64) -> (GOpcode, Vec<MOperand>) {
let result = base_ptr.wrapping_add(1);
(
GOpcode::G_PTR_ADD,
vec![
MOperand::vreg(result),
MOperand::vreg(base_ptr),
MOperand::imm(offset_bytes),
],
)
}
}
impl Default for GEPLowerer {
fn default() -> Self {
Self::new(64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_moperand_vreg() {
let op = MOperand::vreg(5);
assert_eq!(op.as_vreg(), Some(5));
assert_eq!(op.as_imm(), None);
}
#[test]
fn test_moperand_imm() {
let op = MOperand::imm(42);
assert_eq!(op.as_imm(), Some(42));
assert_eq!(op.as_vreg(), None);
}
#[test]
fn test_moperand_label() {
let op = MOperand::label("loop");
assert_eq!(op.as_vreg(), None);
}
#[test]
fn test_gopcode_terminator() {
assert!(GOpcode::G_BR.is_terminator());
assert!(GOpcode::G_BRCOND.is_terminator());
assert!(GOpcode::G_RET.is_terminator());
assert!(!GOpcode::G_ADD.is_terminator());
}
#[test]
fn test_gopcode_binary_op() {
assert!(GOpcode::G_ADD.is_binary_op());
assert!(GOpcode::G_MUL.is_binary_op());
assert!(!GOpcode::G_BR.is_binary_op());
assert!(!GOpcode::COPY.is_binary_op());
}
#[test]
fn test_gopcode_commutative() {
assert!(GOpcode::G_ADD.is_commutative());
assert!(GOpcode::G_MUL.is_commutative());
assert!(GOpcode::G_AND.is_commutative());
assert!(!GOpcode::G_SUB.is_commutative());
assert!(!GOpcode::G_SDIV.is_commutative());
}
#[test]
fn test_ginstruction_def_and_uses() {
let inst = GInstruction::with_operands(
GOpcode::G_ADD,
vec![MOperand::vreg(0), MOperand::vreg(1), MOperand::vreg(2)],
);
assert_eq!(inst.def_vreg(), Some(0));
assert_eq!(inst.uses().len(), 2);
assert_eq!(inst.all_operands().len(), 3);
}
#[test]
fn test_ginstruction_no_def() {
let inst = GInstruction::with_operands(GOpcode::G_BR, vec![MOperand::label("exit")]);
assert_eq!(inst.def_vreg(), None);
assert_eq!(inst.uses().len(), 0);
}
#[test]
fn test_gmachine_basic_block() {
let mut bb = GMachineBasicBlock::new("test".to_string());
assert_eq!(bb.instruction_count(), 0);
bb.push_instruction(GInstruction::new(GOpcode::G_ADD));
assert_eq!(bb.instruction_count(), 1);
}
#[test]
fn test_gmachine_basic_block_successors() {
let mut bb = GMachineBasicBlock::new("test".to_string());
bb.add_successor(1);
bb.add_successor(1); bb.add_successor(2);
assert_eq!(bb.successors.len(), 2);
}
#[test]
fn test_gmachine_function_new() {
let mf = GMachineFunction::new("func".to_string());
assert_eq!(mf.name, "func");
assert_eq!(mf.block_count(), 0);
assert_eq!(mf.instruction_count(), 0);
}
#[test]
fn test_gmachine_function_new_vreg() {
let mut mf = GMachineFunction::new("func".to_string());
assert_eq!(mf.new_vreg(), 0);
assert_eq!(mf.new_vreg(), 1);
assert_eq!(mf.new_vreg(), 2);
}
#[test]
fn test_gmachine_function_push_block() {
let mut mf = GMachineFunction::new("func".to_string());
let idx0 = mf.push_block("entry".to_string());
let idx1 = mf.push_block("loop".to_string());
assert_eq!(idx0, 0);
assert_eq!(idx1, 1);
assert_eq!(mf.block_count(), 2);
}
#[test]
fn test_builder_new() {
let builder = MachineIRBuilder::new("test_func");
assert_eq!(builder.mf.name, "test_func");
assert_eq!(builder.mf.block_count(), 1); assert_eq!(builder.current_block, 0);
assert_eq!(builder.insert_point, 0);
}
#[test]
fn test_builder_set_insert_point() {
let mut builder = MachineIRBuilder::new("test_func");
let bb1 = builder.new_block("bb1");
builder.set_insert_point(bb1, 0);
assert_eq!(builder.current_block, 1);
}
#[test]
fn test_builder_new_vreg() {
let mut builder = MachineIRBuilder::new("test_func");
assert_eq!(builder.new_vreg(), 0);
assert_eq!(builder.new_vreg(), 1);
}
#[test]
fn test_builder_new_block() {
let mut builder = MachineIRBuilder::new("test_func");
let bb1 = builder.new_block("loop");
assert_eq!(bb1, 1);
assert_eq!(builder.mf.block_count(), 2);
}
#[test]
fn test_build_constant() {
let mut builder = MachineIRBuilder::new("test_func");
let dst = builder.build_constant(42);
assert_eq!(builder.total_instructions(), 1);
assert!(dst >= 0);
}
#[test]
fn test_build_add() {
let mut builder = MachineIRBuilder::new("test_func");
let dst = builder.new_vreg();
let s1 = builder.build_constant(10);
let s2 = builder.build_constant(20);
builder.build_add(dst, s1, s2);
assert_eq!(builder.total_instructions(), 3);
}
#[test]
fn test_build_sub() {
let mut builder = MachineIRBuilder::new("test_func");
let dst = builder.new_vreg();
builder.build_sub(dst, 1, 2);
assert_eq!(builder.total_instructions(), 1);
}
#[test]
fn test_build_mul() {
let mut builder = MachineIRBuilder::new("test_func");
let dst = builder.new_vreg();
builder.build_mul(dst, 1, 2);
assert_eq!(builder.total_instructions(), 1);
}
#[test]
fn test_build_and_or_xor() {
let mut builder = MachineIRBuilder::new("test_func");
let d1 = builder.new_vreg();
let d2 = builder.new_vreg();
let d3 = builder.new_vreg();
builder.build_and(d1, 1, 2);
builder.build_or(d2, 1, 2);
builder.build_xor(d3, 1, 2);
assert_eq!(builder.total_instructions(), 3);
}
#[test]
fn test_build_load_store() {
let mut builder = MachineIRBuilder::new("test_func");
let addr = builder.build_constant(0);
let dst = builder.new_vreg();
builder.build_load(dst, addr);
let val = builder.build_constant(99);
builder.build_store(val, addr);
assert_eq!(builder.total_instructions(), 4);
}
#[test]
fn test_build_br() {
let mut builder = MachineIRBuilder::new("test_func");
let target = builder.new_block("target");
builder.build_br(target);
assert!(builder.mf.blocks[0].successors.contains(&target));
}
#[test]
fn test_build_cond_br() {
let mut builder = MachineIRBuilder::new("test_func");
let cond = builder.build_constant(1);
let t_bb = builder.new_block("then");
let f_bb = builder.new_block("else");
builder.build_cond_br(cond, t_bb, f_bb);
let block = &builder.mf.blocks[0];
assert!(block.successors.contains(&t_bb));
assert!(block.successors.contains(&f_bb));
}
#[test]
fn test_build_ret() {
let mut builder = MachineIRBuilder::new("test_func");
let val = builder.build_constant(0);
builder.build_ret(val);
assert_eq!(builder.total_instructions(), 2);
}
#[test]
fn test_set_insert_point_to_end() {
let mut builder = MachineIRBuilder::new("test_func");
let _ = builder.build_constant(1);
let _ = builder.build_constant(2);
assert_eq!(builder.insert_point, 2);
builder.set_insert_point_to_end();
assert_eq!(builder.insert_point, 2); }
#[test]
fn test_get_mf() {
let builder = MachineIRBuilder::new("test_func");
let mf = builder.get_mf();
assert_eq!(mf.name, "test_func");
}
#[test]
fn test_get_mf_mut() {
let mut builder = MachineIRBuilder::new("test_func");
builder.get_mf_mut().next_vreg = 100;
assert_eq!(builder.new_vreg(), 100);
}
#[test]
fn test_total_instructions() {
let mut builder = MachineIRBuilder::new("test_func");
assert_eq!(builder.total_instructions(), 0);
let _ = builder.build_constant(1);
let _ = builder.build_constant(2);
let _ = builder.build_constant(3);
assert_eq!(builder.total_instructions(), 3);
}
#[test]
fn test_reg_bank_default_size() {
assert_eq!(RegBank::GPR.default_size(), 64);
assert_eq!(RegBank::FPR.default_size(), 128);
assert_eq!(RegBank::CCR.default_size(), 1);
}
#[test]
fn test_reg_bank_select_new() {
let sel = RegBankSelect::new();
assert!(sel.assignments.is_empty());
assert!(sel.rules.is_empty());
}
#[test]
fn test_reg_bank_select_default_config() {
let sel = RegBankSelect::default_config();
assert!(!sel.rules.is_empty());
}
#[test]
fn test_instruction_select_new() {
let isel = InstructionSelect::new();
assert_eq!(isel.selected, 0);
assert!(isel.selection_table.is_empty());
}
#[test]
fn test_instruction_select_has_selection() {
let isel = InstructionSelect::x86_64_default();
assert!(isel.has_selection(GOpcode::G_ADD));
assert!(!isel.has_selection(GOpcode::G_FSQRT));
}
#[test]
fn test_match_table_new() {
let table = GIMatchTable::new();
assert!(table.entries.is_empty());
}
#[test]
fn test_match_table_try_match() {
let table = GIMatchTable::default_table();
let result = table.try_match(GOpcode::G_ADD, &[MOperand::vreg(0), MOperand::imm(5)]);
assert!(result.is_some());
}
#[test]
fn test_match_table_no_match() {
let table = GIMatchTable::default_table();
let result = table.try_match(GOpcode::G_FSQRT, &[]);
assert!(result.is_none());
}
#[test]
fn test_constrain_reg_class_new() {
let crc = ConstrainRegClass::new();
assert!(crc.reg_classes.is_empty());
}
#[test]
fn test_constrain_reg_class_x86_64() {
let crc = ConstrainRegClass::x86_64_default();
assert_eq!(crc.reg_classes.len(), 3);
assert_eq!(crc.reg_classes[0].name, "GR64");
assert_eq!(crc.reg_classes[2].name, "FR64");
}
#[test]
fn test_post_isel_combiner_new() {
let combiner = PostISelCombiner::new();
assert_eq!(combiner.num_combined, 0);
assert_eq!(combiner.max_iterations, 4);
}
#[test]
fn test_post_isel_combine_add_zero() {
let combiner = PostISelCombiner::new();
let first = GInstruction::new(
GOpcode::G_ADD,
10,
vec![MOperand::vreg(5), MOperand::imm(0)],
);
let second = GInstruction::new(GOpcode::G_STORE, 0, vec![]);
let result = combiner.try_combine_adjacent(&first, &second);
assert!(result.is_some());
}
#[test]
fn test_ls_legalizer_new() {
let lsl = LoadStoreLegalizer::new(64, true);
assert_eq!(lsl.max_load_width, 64);
assert!(lsl.unaligned_access);
}
#[test]
fn test_ls_legalizer_needs_legalization() {
let lsl = LoadStoreLegalizer::new(64, true);
assert!(!lsl.needs_legalization(32));
assert!(lsl.needs_legalization(1));
assert!(lsl.needs_legalization(128));
}
#[test]
fn test_constant_materializer_new() {
let cm = ConstantMaterializer::new(16);
assert_eq!(cm.max_immediate_bits, 16);
}
#[test]
fn test_constant_materializer_can_materialize() {
let cm = ConstantMaterializer::new(16);
assert!(cm.can_materialize(0));
assert!(cm.can_materialize(65535));
assert!(!cm.can_materialize(65536));
}
#[test]
fn test_constant_materializer_decompose() {
let cm = ConstantMaterializer::new(16);
let seq = cm.decompose(0x12345678);
assert!(seq.len() >= 1);
}
#[test]
fn test_gep_lowerer_new() {
let gl = GEPLowerer::new(64);
assert_eq!(gl.pointer_size, 64);
}
#[test]
fn test_gep_lowerer_lower_gep() {
let gl = GEPLowerer::new(64);
let (opcode, operands) = gl.lower_gep(100, 16);
assert_eq!(opcode, GOpcode::G_PTR_ADD);
assert_eq!(operands.len(), 3);
}
#[test]
fn test_gep_lowerer_decompose_gep() {
let gl = GEPLowerer::new(64);
let indices = vec![(200, 4), (201, 8)];
let seq = gl.decompose_gep(100, &indices);
assert!(!seq.is_empty());
}
}