use crate::opcode::{FCmpPred, ICmpPred, Opcode};
use crate::selection_dag::sd_node::*;
use crate::types::Type;
use crate::value::ValueRef;
use std::collections::HashMap;
pub struct SelectionDAGBuilder {
pub dag: SelectionDAG,
value_map: HashMap<u64, SDValue>,
current_chain: SDValue,
entry_block: Option<String>,
current_block: Option<String>,
current_debug_loc: Option<String>,
target_pointer_size: u32,
}
impl Default for SelectionDAGBuilder {
fn default() -> Self {
Self::new()
}
}
impl SelectionDAGBuilder {
pub fn new() -> Self {
let dag = SelectionDAG::new();
Self {
current_chain: dag.entry_token,
dag,
value_map: HashMap::new(),
entry_block: None,
current_block: None,
current_debug_loc: None,
target_pointer_size: 64,
}
}
pub fn with_target(target: &str) -> Self {
let dag = SelectionDAG::with_target(target);
Self {
current_chain: dag.entry_token,
dag,
value_map: HashMap::new(),
entry_block: None,
current_block: None,
current_debug_loc: None,
target_pointer_size: 64,
}
}
pub fn set_target_pointer_size(&mut self, bits: u32) {
self.target_pointer_size = bits;
}
pub fn create_chain(&self) -> SDValue {
self.current_chain
}
pub fn update_chain(&mut self, chain: SDValue) {
self.current_chain = chain;
}
pub fn get_value(&mut self, val: &ValueRef) -> SDValue {
let v = val.borrow();
if let Some(&sdval) = self.value_map.get(&v.vid) {
return sdval;
}
if v.is_constant() {
if v.ty.is_integer() {
let int_val: i64 = v.name.parse().unwrap_or(0);
let bits = v.ty.integer_bit_width();
let mask = if bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
let uv = (int_val as u64) & mask;
return self.get_constant(uv, v.ty.clone());
} else if v.ty.is_floating_point() {
let fp_val: f64 = v.name.parse().unwrap_or(0.0);
return self.get_constant_fp(fp_val, v.ty.clone());
}
}
if v.subclass == crate::value::SubclassKind::Argument {
let reg = self.dag.get_register(v.ty.clone());
self.value_map.insert(v.vid, reg);
return reg;
}
let reg = self.dag.get_register(v.ty.clone());
self.value_map.insert(v.vid, reg);
reg
}
pub fn set_value(&mut self, val: &ValueRef, sdval: SDValue) {
let v = val.borrow();
self.value_map.insert(v.vid, sdval);
}
pub fn get_constant(&mut self, val: u64, ty: Type) -> SDValue {
self.dag.get_constant(val, ty)
}
pub fn get_constant_fp(&mut self, val: f64, ty: Type) -> SDValue {
let fp_bits = val.to_bits();
self.dag.get_constant_fp(fp_bits, ty)
}
pub fn build_function(&mut self, func: &ValueRef) -> Result<SelectionDAG, String> {
self.dag = SelectionDAG::new();
self.value_map.clear();
self.current_chain = self.dag.entry_token;
self.entry_block = None;
self.current_block = None;
let f = func.borrow();
if !f.is_function() {
return Err("Expected a Function value".to_string());
}
for (i, op) in f.operands.iter().enumerate() {
let bb = op.borrow();
if bb.is_basic_block() {
if i == 0 {
self.entry_block = Some(bb.name.clone());
}
self.current_block = Some(bb.name.clone());
for inst_val in bb.operands.iter() {
let inst = inst_val.borrow();
if inst.is_instruction() {
self.visit_instruction(inst_val)?;
}
}
}
}
Ok(std::mem::replace(&mut self.dag, SelectionDAG::new()))
}
pub fn build_basic_block(&mut self, bb: &ValueRef) -> Result<(), String> {
let block = bb.borrow();
if !block.is_basic_block() {
return Err("Expected a BasicBlock value".to_string());
}
self.current_block = Some(block.name.clone());
for inst_val in block.operands.iter() {
let inst = inst_val.borrow();
if inst.is_instruction() {
self.visit_instruction(inst_val)?;
}
}
Ok(())
}
fn visit_instruction(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let opcode = i
.get_opcode()
.ok_or_else(|| format!("No opcode for instruction '{}' (vid {})", i.name, i.vid))?;
match opcode {
Opcode::Ret => self.visit_ret(inst),
Opcode::Br => self.visit_br(inst),
Opcode::Switch => self.visit_switch(inst),
Opcode::Unreachable => self.visit_unreachable(inst),
Opcode::Add => self.visit_binary(inst, SDOpcode::Add),
Opcode::Sub => self.visit_binary(inst, SDOpcode::Sub),
Opcode::Mul => self.visit_binary(inst, SDOpcode::Mul),
Opcode::UDiv => self.visit_binary(inst, SDOpcode::UDiv),
Opcode::SDiv => self.visit_binary(inst, SDOpcode::SDiv),
Opcode::URem => self.visit_binary(inst, SDOpcode::URem),
Opcode::SRem => self.visit_binary(inst, SDOpcode::SRem),
Opcode::FAdd => self.visit_binary(inst, SDOpcode::FAdd),
Opcode::FSub => self.visit_binary(inst, SDOpcode::FSub),
Opcode::FMul => self.visit_binary(inst, SDOpcode::FMul),
Opcode::FDiv => self.visit_binary(inst, SDOpcode::FDiv),
Opcode::FRem => self.visit_binary(inst, SDOpcode::FRem),
Opcode::And => self.visit_binary(inst, SDOpcode::And),
Opcode::Or => self.visit_binary(inst, SDOpcode::Or),
Opcode::Xor => self.visit_binary(inst, SDOpcode::Xor),
Opcode::Shl => self.visit_binary(inst, SDOpcode::Shl),
Opcode::LShr => self.visit_binary(inst, SDOpcode::Srl),
Opcode::AShr => self.visit_binary(inst, SDOpcode::Sra),
Opcode::ICmp => self.visit_icmp(inst),
Opcode::FCmp => self.visit_fcmp(inst),
Opcode::Load => self.visit_load(inst),
Opcode::Store => self.visit_store(inst),
Opcode::Alloca => self.visit_alloca(inst),
Opcode::Call => self.visit_call(inst),
Opcode::Phi => self.visit_phi(inst),
Opcode::Select => self.visit_select(inst),
Opcode::Freeze => self.visit_freeze(inst),
Opcode::ZExt => self.visit_cast(inst, SDOpcode::ZExt),
Opcode::SExt => self.visit_cast(inst, SDOpcode::SExt),
Opcode::Trunc => self.visit_cast(inst, SDOpcode::Trunc),
Opcode::BitCast => self.visit_cast(inst, SDOpcode::Bitcast),
Opcode::FPTrunc => self.visit_cast(inst, SDOpcode::FpRound),
Opcode::FPExt => self.visit_cast(inst, SDOpcode::FpExtend),
Opcode::FPToSI => self.visit_cast(inst, SDOpcode::FPToSI),
Opcode::FPToUI => self.visit_cast(inst, SDOpcode::FPToUI),
Opcode::SIToFP => self.visit_cast(inst, SDOpcode::SIToFP),
Opcode::UIToFP => self.visit_cast(inst, SDOpcode::UIToFP),
Opcode::PtrToInt => self.visit_cast(inst, SDOpcode::Bitcast),
Opcode::IntToPtr => self.visit_cast(inst, SDOpcode::Bitcast),
Opcode::GetElementPtr => self.visit_getelementptr(inst),
Opcode::ExtractValue => self.visit_extractvalue(inst),
Opcode::InsertValue => self.visit_insertvalue(inst),
Opcode::ExtractElement | Opcode::InsertElement | Opcode::ShuffleVector => {
let reg = self.dag.get_register(i.ty.clone());
self.set_value(inst, reg);
Ok(reg)
}
Opcode::LandingPad | Opcode::CatchPad | Opcode::CleanupPad => {
let reg = self.dag.get_register(i.ty.clone());
self.set_value(inst, reg);
Ok(reg)
}
Opcode::Fence => {
let chain = self.create_chain();
let fence =
self.dag
.add_node(SDOpcode::TokenFactor, vec![Type::token()], vec![chain]);
self.update_chain(fence);
self.set_value(inst, fence);
Ok(fence)
}
Opcode::CmpXchg | Opcode::AtomicRMW => self.visit_atomic_rmw(inst, opcode),
_ => {
let reg = self.dag.get_register(i.ty.clone());
self.set_value(inst, reg);
Ok(reg)
}
}
}
fn visit_ret(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let chain_in = self.create_chain();
let mut ops = vec![chain_in];
if !i.operands.is_empty() {
let ret_val = self.get_value(&i.operands[0]);
ops.push(ret_val);
}
let ret_sd = self.dag.add_node(SDOpcode::Ret, vec![], ops);
self.dag.set_root(ret_sd);
self.set_value(inst, ret_sd);
Ok(ret_sd)
}
fn visit_br(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let chain_in = self.create_chain();
if i.operands.len() == 1 {
let br_val = self.dag.add_node(SDOpcode::Br, vec![], vec![chain_in]);
self.dag.set_root(br_val);
self.set_value(inst, br_val);
Ok(br_val)
} else if i.operands.len() == 3 {
let cond = self.get_value(&i.operands[0]);
let br_val = self
.dag
.add_node(SDOpcode::BrCond, vec![], vec![chain_in, cond]);
self.dag.set_root(br_val);
self.set_value(inst, br_val);
Ok(br_val)
} else {
Err(format!(
"Invalid br instruction with {} operands",
i.operands.len()
))
}
}
fn visit_switch(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let _i = inst.borrow();
let chain_in = self.create_chain();
let switch_val = self.dag.add_node(SDOpcode::Br, vec![], vec![chain_in]);
self.dag.set_root(switch_val);
self.set_value(inst, switch_val);
Ok(switch_val)
}
fn visit_unreachable(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let _i = inst.borrow();
let chain_in = self.create_chain();
let node = self.dag.add_node(SDOpcode::Br, vec![], vec![chain_in]);
self.dag.set_root(node);
self.set_value(inst, node);
Ok(node)
}
fn visit_binary(&mut self, inst: &ValueRef, opcode: SDOpcode) -> Result<SDValue, String> {
let i = inst.borrow();
let lhs = self.get_value(&i.operands[0]);
let rhs = self.get_value(&i.operands[1]);
let result = self.dag.get_binary_op(opcode, i.ty.clone(), lhs, rhs);
self.set_value(inst, result);
Ok(result)
}
fn visit_icmp(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let lhs = self.get_value(&i.operands[0]);
let rhs = self.get_value(&i.operands[1]);
let pred = if i.name.starts_with("icmp.") {
Self::parse_icmp_pred(&i.name[5..])
} else {
ICmpPred::Eq };
let result = self.dag.get_setcc(pred, Type::i1(), lhs, rhs);
self.set_value(inst, result);
Ok(result)
}
fn parse_icmp_pred(s: &str) -> ICmpPred {
match s {
"eq" => ICmpPred::Eq,
"ne" => ICmpPred::Ne,
"ugt" => ICmpPred::Ugt,
"uge" => ICmpPred::Uge,
"ult" => ICmpPred::Ult,
"ule" => ICmpPred::Ule,
"sgt" => ICmpPred::Sgt,
"sge" => ICmpPred::Sge,
"slt" => ICmpPred::Slt,
"sle" => ICmpPred::Sle,
_ => ICmpPred::Eq,
}
}
fn visit_fcmp(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let lhs = self.get_value(&i.operands[0]);
let rhs = self.get_value(&i.operands[1]);
let _pred = if i.name.starts_with("fcmp.") {
Self::parse_fcmp_pred(&i.name[5..])
} else {
FCmpPred::Oeq };
let result = self.dag.get_setcc(ICmpPred::Eq, Type::i1(), lhs, rhs);
self.set_value(inst, result);
Ok(result)
}
fn parse_fcmp_pred(s: &str) -> FCmpPred {
match s {
"false" => FCmpPred::False,
"oeq" => FCmpPred::Oeq,
"ogt" => FCmpPred::Ogt,
"oge" => FCmpPred::Oge,
"olt" => FCmpPred::Olt,
"ole" => FCmpPred::Ole,
"one" => FCmpPred::One,
"ord" => FCmpPred::Ord,
"ueq" => FCmpPred::Ueq,
"ugt" => FCmpPred::Ugt,
"uge" => FCmpPred::Uge,
"ult" => FCmpPred::Ult,
"ule" => FCmpPred::Ule,
"une" => FCmpPred::Une,
"uno" => FCmpPred::Uno,
"true" => FCmpPred::True,
_ => FCmpPred::Oeq,
}
}
fn visit_load(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let ptr = self.get_value(&i.operands[0]);
let chain_in = self.create_chain();
let load_val = self.dag.get_load(chain_in, ptr, i.ty.clone());
let chain_out = self.dag.get_load_chain(load_val);
self.update_chain(chain_out);
self.set_value(inst, load_val);
Ok(load_val)
}
fn visit_store(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let val = self.get_value(&i.operands[0]);
let ptr = self.get_value(&i.operands[1]);
let chain_in = self.create_chain();
let store_sd = self.dag.get_store(chain_in, val, ptr);
self.update_chain(store_sd);
self.set_value(inst, store_sd);
Ok(store_sd)
}
fn visit_alloca(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let alloca_val = self
.dag
.add_node(SDOpcode::FrameIndex, vec![i.ty.clone()], vec![]);
self.set_value(inst, alloca_val);
Ok(alloca_val)
}
fn visit_atomic_rmw(&mut self, inst: &ValueRef, _ir_opcode: Opcode) -> Result<SDValue, String> {
let i = inst.borrow();
let chain_in = self.create_chain();
let mut ops = vec![chain_in];
for op in &i.operands {
let sdv = self.get_value(op);
ops.push(sdv);
}
let at_val = self.dag.add_node_with_values(
SDOpcode::AtomicRMW,
vec![i.ty.clone(), Type::token()],
ops,
2,
);
let chain_out = SDValue::new(at_val.node_id, 1);
self.update_chain(chain_out);
self.set_value(inst, at_val);
Ok(at_val)
}
fn visit_call(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let chain_in = self.create_chain();
let mut ops = vec![chain_in];
for op in &i.operands {
let sdv = self.get_value(op);
ops.push(sdv);
}
let return_ty = if i.ty.is_void() {
Type::token()
} else {
i.ty.clone()
};
let call_val =
self.dag
.add_node_with_values(SDOpcode::Call, vec![return_ty, Type::token()], ops, 2);
let chain_out = SDValue::new(call_val.node_id, 1);
self.update_chain(chain_out);
if i.ty.is_void() {
self.set_value(inst, chain_out);
Ok(chain_out)
} else {
self.set_value(inst, call_val);
Ok(call_val)
}
}
fn visit_phi(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let mut incoming_sdvals = Vec::new();
for chunk in i.operands.chunks(2) {
if !chunk.is_empty() {
let sdv = self.get_value(&chunk[0]);
incoming_sdvals.push(sdv);
}
}
let phi_val = self.dag.get_register(i.ty.clone());
self.set_value(inst, phi_val);
Ok(phi_val)
}
fn visit_select(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let cond = self.get_value(&i.operands[0]);
let true_val = self.get_value(&i.operands[1]);
let false_val = self.get_value(&i.operands[2]);
let result = self.dag.get_select(cond, true_val, false_val, i.ty.clone());
self.set_value(inst, result);
Ok(result)
}
fn visit_freeze(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let val = self.get_value(&i.operands[0]);
let freeze_val = self
.dag
.add_node(SDOpcode::Freeze, vec![i.ty.clone()], vec![val]);
self.set_value(inst, freeze_val);
Ok(freeze_val)
}
fn visit_cast(&mut self, inst: &ValueRef, opcode: SDOpcode) -> Result<SDValue, String> {
let i = inst.borrow();
let operand = self.get_value(&i.operands[0]);
let result = self.dag.add_node(opcode, vec![i.ty.clone()], vec![operand]);
self.set_value(inst, result);
Ok(result)
}
fn visit_getelementptr(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
if i.operands.is_empty() {
return Err("GEP with no operands".to_string());
}
let base_ptr = self.get_value(&i.operands[0]);
if i.operands.len() == 1 {
self.set_value(inst, base_ptr);
return Ok(base_ptr);
}
let mut current = base_ptr;
for idx_val in &i.operands[1..] {
let idx_sd = self.get_value(idx_val);
current = self
.dag
.get_binary_op(SDOpcode::Add, i.ty.clone(), current, idx_sd);
}
self.set_value(inst, current);
Ok(current)
}
fn visit_extractvalue(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let _agg = self.get_value(&i.operands[0]);
let reg = self.dag.get_register(i.ty.clone());
self.set_value(inst, reg);
Ok(reg)
}
fn visit_insertvalue(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let _agg = self.get_value(&i.operands[0]);
let reg = self.dag.get_register(i.ty.clone());
self.set_value(inst, reg);
Ok(reg)
}
}
impl SelectionDAGBuilder {
fn visit_direct_call(
&mut self,
inst: &ValueRef,
_callee: &ValueRef,
) -> Result<SDValue, String> {
let i = inst.borrow();
let mut call_args = vec![self.current_chain];
for arg_idx in 1..i.operands.len() {
let arg_sd = self.get_value(&i.operands[arg_idx]);
call_args.push(arg_sd);
}
let call_node = self
.dag
.add_node(SDOpcode::Call, vec![i.ty.clone()], call_args);
let call_result = SDValue::new(call_node.node_id, 0);
let call_chain = SDValue::new(call_node.node_id, 1);
self.current_chain = call_chain;
self.set_value(inst, call_result);
Ok(call_result)
}
fn build_ret_sequence(&mut self, retval: Option<SDValue>) -> Result<SDValue, String> {
let chain = self.current_chain;
if let Some(val) = retval {
let copy_node =
self.dag
.add_node(SDOpcode::CopyToReg, vec![Type::void()], vec![chain, val]);
let new_chain = SDValue::new(copy_node.node_id, 0);
let ret_node = self
.dag
.add_node(SDOpcode::Ret, vec![Type::void()], vec![new_chain]);
self.current_chain = SDValue::new(ret_node.node_id, 0);
Ok(self.current_chain)
} else {
let ret_node = self
.dag
.add_node(SDOpcode::Ret, vec![Type::void()], vec![chain]);
self.current_chain = SDValue::new(ret_node.node_id, 0);
Ok(self.current_chain)
}
}
fn visit_switch_lowered(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let cond = self.get_value(&i.operands[0]);
let default_bb = i.operands[1].clone();
let mut cases: Vec<(SDValue, ValueRef)> = Vec::new();
let mut idx = 2;
while idx + 1 < i.operands.len() {
let case_val = self.get_value(&i.operands[idx]);
let case_bb = i.operands[idx + 1].clone();
cases.push((case_val, case_bb));
idx += 2;
}
let num_cases = cases.len();
if num_cases == 0 {
return Ok(cond);
}
if num_cases >= 4 {
self.lower_switch_jump_table(cond, &cases, &default_bb)
} else {
self.lower_switch_binary_tree(cond, &cases, &default_bb)
}
}
fn lower_switch_jump_table(
&mut self,
cond: SDValue,
cases: &[(SDValue, ValueRef)],
_default_bb: &ValueRef,
) -> Result<SDValue, String> {
let chain = self.current_chain;
let mut table_args = vec![chain, cond];
for (case_val, _case_bb) in cases {
table_args.push(*case_val);
}
let jt_node = self
.dag
.add_node(SDOpcode::Br, vec![Type::void()], table_args);
let result = SDValue::new(jt_node.node_id, 0);
self.current_chain = result;
Ok(result)
}
fn lower_switch_binary_tree(
&mut self,
cond: SDValue,
cases: &[(SDValue, ValueRef)],
_default_bb: &ValueRef,
) -> Result<SDValue, String> {
let chain = self.current_chain;
if cases.is_empty() {
return Ok(cond);
}
let mut current_chain = chain;
for (case_val, _case_bb) in cases.iter() {
let setcc = self.dag.add_node(
SDOpcode::SetCC,
vec![Type::i1()],
vec![cond, *case_val, *case_val],
);
let cond_sd = SDValue::new(setcc.node_id, 0);
let brcc = self.dag.add_node(
SDOpcode::BrCond,
vec![Type::void()],
vec![current_chain, cond_sd],
);
current_chain = SDValue::new(brcc.node_id, 0);
}
self.current_chain = current_chain;
Ok(current_chain)
}
fn lower_switch_bit_test(
&mut self,
cond: SDValue,
cases: &[(SDValue, ValueRef)],
) -> Result<SDValue, String> {
let chain = self.current_chain;
let mut mask = 0i64;
for (case_val, _) in cases {
let c = self.dag.get_node(*case_val);
if c.is_constant() {
mask = mask.wrapping_add(1);
}
}
let mask_sd = self.dag.get_constant(mask as u64, Type::i64());
let and_node = self
.dag
.get_binary_op(SDOpcode::And, Type::i64(), cond, mask_sd);
let zero = self.dag.get_constant(0, Type::i64());
let setcc = self
.dag
.get_binary_op(SDOpcode::SetCC, Type::i1(), and_node, zero);
let brcc = self
.dag
.add_node(SDOpcode::BrCond, vec![Type::void()], vec![chain, setcc]);
let result = SDValue::new(brcc.node_id, 0);
self.current_chain = result;
Ok(result)
}
fn visit_inline_asm(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let chain = self.current_chain;
let asm_node = self
.dag
.add_node(SDOpcode::TokenFactor, vec![Type::void()], vec![chain]);
let result = SDValue::new(asm_node.node_id, 0);
self.current_chain = result;
let reg = self.dag.get_register(i.ty.clone());
self.set_value(inst, reg);
Ok(reg)
}
fn visit_eh_label(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let chain = self.current_chain;
let eh_node = self
.dag
.add_node(SDOpcode::TokenFactor, vec![Type::void()], vec![chain]);
let result = SDValue::new(eh_node.node_id, 0);
self.current_chain = result;
self.set_value(inst, result);
Ok(result)
}
pub fn create_copy_to_reg(&mut self, val: SDValue, vreg: u32) -> Result<SDValue, String> {
let chain = self.current_chain;
let vreg_sd = self.dag.get_constant(vreg as u64, Type::i32());
let copy_node = self.dag.add_node(
SDOpcode::CopyToReg,
vec![Type::void()],
vec![chain, val, vreg_sd],
);
let result = SDValue::new(copy_node.node_id, 0);
self.current_chain = result;
Ok(result)
}
pub fn create_copy_from_reg(&mut self, vreg: u32, ty: Type) -> Result<SDValue, String> {
let chain = self.current_chain;
let vreg_sd = self.dag.get_constant(vreg as u64, Type::i32());
let copy_node = self
.dag
.add_node(SDOpcode::CopyFromReg, vec![ty], vec![chain, vreg_sd]);
let result_val = SDValue::new(copy_node.node_id, 0);
let result_chain = SDValue::new(copy_node.node_id, 1);
self.current_chain = result_chain;
Ok(result_val)
}
pub fn create_token_factor(&mut self, chains: &[SDValue]) -> SDValue {
if chains.is_empty() {
return self.current_chain;
}
if chains.len() == 1 {
return chains[0];
}
let mut factor_args = Vec::with_capacity(chains.len() + 1);
factor_args.push(self.current_chain);
factor_args.extend_from_slice(chains);
let tf_node = self
.dag
.add_node(SDOpcode::TokenFactor, vec![Type::void()], factor_args);
SDValue::new(tf_node.node_id, 0)
}
pub fn recognize_addressing_mode(
&mut self,
base: SDValue,
operands: &[SDValue],
) -> (SDValue, Option<SDValue>, u8, i64) {
let mut current_base = base;
let mut index: Option<SDValue> = None;
let mut scale: u8 = 1;
let mut offset: i64 = 0;
for op in operands {
let node = self.dag.get_node(*op);
if node.is_constant() {
offset = offset.wrapping_add(1); } else if let Some(idx_val) = self.is_scaled_index(*op) {
index = Some(idx_val.0);
scale = idx_val.1;
} else {
let ty = node.get_value_type(0).cloned().unwrap_or(Type::i64());
current_base = self.dag.get_binary_op(SDOpcode::Add, ty, current_base, *op);
}
}
(current_base, index, scale, offset)
}
fn is_scaled_index(&self, val: SDValue) -> Option<(SDValue, u8)> {
let node = self.dag.get_node(val);
if node.opcode == SDOpcode::Mul {
let left = node.operands.get(0)?;
let right = node.operands.get(1)?;
let right_is_const = self.dag.get_node(*right).is_constant();
let left_is_const = self.dag.get_node(*left).is_constant();
if right_is_const || left_is_const {
let index = if right_is_const { *left } else { *right };
return Some((index, 1));
}
}
None
}
pub fn recognize_large_offset(
&self,
base: SDValue,
offset: i64,
max_offset: i64,
) -> Option<(SDValue, i64)> {
if offset >= -max_offset && offset <= max_offset {
Some((base, offset))
} else {
None
}
}
pub fn build_addressing_load(
&mut self,
base: SDValue,
index: Option<SDValue>,
scale: u8,
offset: i64,
load_type: Type,
) -> Result<SDValue, String> {
let chain = self.current_chain;
let addr = self.build_complex_address(base, index, scale, offset);
let load_node = self.dag.add_node(
SDOpcode::Load,
vec![load_type, Type::void()],
vec![chain, addr],
);
let load_val = SDValue::new(load_node.node_id, 0);
let load_chain = SDValue::new(load_node.node_id, 1);
self.current_chain = load_chain;
Ok(load_val)
}
fn build_complex_address(
&mut self,
base: SDValue,
index: Option<SDValue>,
scale: u8,
offset: i64,
) -> SDValue {
let ty = self
.dag
.get_node(base)
.get_value_type(0)
.cloned()
.unwrap_or(Type::i64());
let mut addr = base;
if let Some(idx) = index {
if scale > 1 {
let scale_sd = self.dag.get_constant(scale as u64, ty.clone());
let scaled = self
.dag
.get_binary_op(SDOpcode::Mul, ty.clone(), idx, scale_sd);
addr = self
.dag
.get_binary_op(SDOpcode::Add, ty.clone(), addr, scaled);
} else {
addr = self.dag.get_binary_op(SDOpcode::Add, ty.clone(), addr, idx);
}
}
if offset != 0 {
let off_sd = self.dag.get_constant(offset as u64, ty.clone());
addr = self.dag.get_binary_op(SDOpcode::Add, ty, addr, off_sd);
}
addr
}
fn visit_fneg(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let op = self.get_value(&i.operands[0]);
let result = self
.dag
.add_node(SDOpcode::FNeg, vec![i.ty.clone()], vec![op]);
self.set_value(inst, result);
Ok(result)
}
fn visit_fabs(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let op = self.get_value(&i.operands[0]);
let result = self
.dag
.add_node(SDOpcode::FAbs, vec![i.ty.clone()], vec![op]);
self.set_value(inst, result);
Ok(result)
}
fn visit_fma(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let a = self.get_value(&i.operands[0]);
let b = self.get_value(&i.operands[1]);
let c = self.get_value(&i.operands[2]);
let result = self
.dag
.add_node(SDOpcode::FMA, vec![i.ty.clone()], vec![a, b, c]);
let result_sd = SDValue::new(result.node_id, 0);
self.set_value(inst, result_sd);
Ok(result_sd)
}
fn visit_vector_reduce(
&mut self,
inst: &ValueRef,
opcode: SDOpcode,
) -> Result<SDValue, String> {
let i = inst.borrow();
let vec = self.get_value(&i.operands[0]);
let result = self.dag.add_node(opcode, vec![i.ty.clone()], vec![vec]);
self.set_value(inst, result);
Ok(result)
}
fn visit_shufflevector(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let v1 = self.get_value(&i.operands[0]);
let v2 = self.get_value(&i.operands[1]);
let mask = self.get_value(&i.operands[2]);
let result = self.dag.add_node(
SDOpcode::VectorShuffle,
vec![i.ty.clone()],
vec![v1, v2, mask],
);
let result_sd = SDValue::new(result.node_id, 0);
self.set_value(inst, result_sd);
Ok(result_sd)
}
fn visit_insertelement(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let vec = self.get_value(&i.operands[0]);
let elt = self.get_value(&i.operands[1]);
let idx = self.get_value(&i.operands[2]);
let result = self.dag.add_node(
SDOpcode::InsertElement,
vec![i.ty.clone()],
vec![vec, elt, idx],
);
let result_sd = SDValue::new(result.node_id, 0);
self.set_value(inst, result_sd);
Ok(result_sd)
}
fn visit_extractelement(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let vec = self.get_value(&i.operands[0]);
let idx = self.get_value(&i.operands[1]);
let result =
self.dag
.add_node(SDOpcode::ExtractElement, vec![i.ty.clone()], vec![vec, idx]);
let result_sd = SDValue::new(result.node_id, 0);
self.set_value(inst, result_sd);
Ok(result_sd)
}
fn visit_landingpad(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let chain = self.current_chain;
let lp_node = self
.dag
.add_node(SDOpcode::TokenFactor, vec![Type::void()], vec![chain]);
let result = SDValue::new(lp_node.node_id, 0);
self.current_chain = result;
let reg = self.dag.get_register(inst.borrow().ty.clone());
self.set_value(inst, reg);
Ok(reg)
}
fn visit_resume(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let _exc = self.get_value(&i.operands[0]);
let chain = self.current_chain;
let resume_node = self
.dag
.add_node(SDOpcode::TokenFactor, vec![Type::void()], vec![chain]);
self.current_chain = SDValue::new(resume_node.node_id, 0);
Ok(self.current_chain)
}
fn visit_va_arg(&mut self, inst: &ValueRef) -> Result<SDValue, String> {
let i = inst.borrow();
let _va_list = self.get_value(&i.operands[0]);
let chain = self.current_chain;
let va_node = self
.dag
.add_node(SDOpcode::TokenFactor, vec![Type::void()], vec![chain]);
let result = SDValue::new(va_node.node_id, 0);
self.current_chain = result;
let reg = self.dag.get_register(i.ty.clone());
self.set_value(inst, reg);
Ok(reg)
}
pub fn build_build_vector(&mut self, elements: &[SDValue], vec_type: Type) -> SDValue {
let mut args = Vec::with_capacity(elements.len());
args.extend_from_slice(elements);
let node = self
.dag
.add_node(SDOpcode::BuildVector, vec![vec_type], args);
SDValue::new(node.node_id, 0)
}
pub fn build_build_pair(&mut self, lo: SDValue, hi: SDValue, result_type: Type) -> SDValue {
let node = self
.dag
.add_node(SDOpcode::BuildPair, vec![result_type], vec![lo, hi]);
SDValue::new(node.node_id, 0)
}
pub fn get_and_update_chain(&mut self, new_chain: SDValue) -> SDValue {
let old = self.current_chain;
self.current_chain = new_chain;
old
}
pub fn is_undef(&self, val: SDValue) -> bool {
let node = self.dag.get_node(val);
node.opcode == SDOpcode::Undef
}
pub fn get_dag(&self) -> &SelectionDAG {
&self.dag
}
pub fn get_dag_mut(&mut self) -> &mut SelectionDAG {
&mut self.dag
}
#[cfg(test)]
pub fn is_node_opcode(&self, val: SDValue, op: SDOpcode) -> bool {
self.dag.get_node(val).opcode == op
}
#[cfg(test)]
pub fn get_node_num_values(&self, val: SDValue) -> u32 {
self.dag.get_node(val).num_values
}
pub fn lower_indirect_call(
&mut self,
callee: SDValue,
args: &[SDValue],
return_ty: Type,
) -> SDValue {
let mut call_operands = vec![self.current_chain, callee];
for arg in args {
call_operands.push(*arg);
}
let call_node = self.dag.add_node_with_values(
SDOpcode::Call,
vec![return_ty, Type::token()],
call_operands,
2,
);
let result = SDValue::new(call_node.node_id, 0);
let chain = SDValue::new(call_node.node_id, 1);
self.current_chain = chain;
result
}
pub fn lower_tail_call(
&mut self,
callee: SDValue,
args: &[SDValue],
return_ty: Type,
) -> SDValue {
let mut call_operands = vec![self.current_chain, callee];
for arg in args {
call_operands.push(*arg);
}
let call_node = self.dag.add_node_with_values(
SDOpcode::Call,
vec![return_ty, Type::token()],
call_operands,
2,
);
let result = SDValue::new(call_node.node_id, 0);
let chain = SDValue::new(call_node.node_id, 1);
self.current_chain = chain;
self.dag.set_root(chain);
result
}
pub fn lower_sibling_call(
&mut self,
callee: SDValue,
args: &[SDValue],
return_ty: Type,
) -> SDValue {
let mut call_operands = vec![self.current_chain, callee];
for arg in args {
call_operands.push(*arg);
}
let call_node = self.dag.add_node_with_values(
SDOpcode::Call,
vec![return_ty, Type::token()],
call_operands,
2,
);
let result = SDValue::new(call_node.node_id, 0);
let chain = SDValue::new(call_node.node_id, 1);
self.current_chain = chain;
result
}
pub fn lower_icmp_setcc(
&mut self,
pred: crate::opcode::ICmpPred,
lhs: SDValue,
rhs: SDValue,
) -> SDValue {
let setcc_node = self
.dag
.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![lhs, rhs]);
let _ = pred; setcc_node
}
pub fn lower_fcmp_setcc(
&mut self,
pred: crate::opcode::FCmpPred,
lhs: SDValue,
rhs: SDValue,
) -> SDValue {
let setcc_node = self
.dag
.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![lhs, rhs]);
let _ = pred;
setcc_node
}
pub fn create_frame_index(&mut self, index: u32, ptr_ty: Type) -> SDValue {
let idx_val = self.dag.get_constant(index as u64, Type::i32());
let fi = self
.dag
.add_node(SDOpcode::FrameIndex, vec![ptr_ty], vec![idx_val]);
fi
}
pub fn create_global_address(&mut self, symbol: &str, offset: i64, ptr_ty: Type) -> SDValue {
let offset_val = self.dag.get_constant(offset as u64, Type::i64());
let ga = self
.dag
.add_node(SDOpcode::GlobalAddress, vec![ptr_ty], vec![offset_val]);
let _ = symbol; ga
}
pub fn create_constant_pool(&mut self, value: u64, ptr_ty: Type) -> SDValue {
let val_node = self.dag.get_constant(value, Type::i64());
let cp = self
.dag
.add_node(SDOpcode::ConstantPool, vec![ptr_ty], vec![val_node]);
cp
}
pub fn create_constant_pool_fp(&mut self, fp_bits: u64, ptr_ty: Type) -> SDValue {
let val_node = self.dag.get_constant(fp_bits, Type::i64());
let cp = self
.dag
.add_node(SDOpcode::ConstantPool, vec![ptr_ty], vec![val_node]);
cp
}
pub fn create_jump_table(&mut self, table_index: u32, ptr_ty: Type) -> SDValue {
let idx_val = self.dag.get_constant(table_index as u64, Type::i32());
let jt = self
.dag
.add_node(SDOpcode::JumpTable, vec![ptr_ty], vec![idx_val]);
jt
}
pub fn create_block_address(&mut self, block_name: &str, ptr_ty: Type) -> SDValue {
let ba = self
.dag
.add_node(SDOpcode::BlockAddress, vec![ptr_ty], vec![]);
let _ = block_name;
ba
}
pub fn create_external_symbol(&mut self, symbol: &str, ptr_ty: Type) -> SDValue {
let es = self
.dag
.add_node(SDOpcode::ExternalSymbol, vec![ptr_ty], vec![]);
let _ = symbol;
es
}
pub fn create_target_index(&mut self, index: u32, ptr_ty: Type) -> SDValue {
let idx_val = self.dag.get_constant(index as u64, Type::i32());
let ti = self
.dag
.add_node(SDOpcode::TargetIndex, vec![ptr_ty], vec![idx_val]);
ti
}
pub fn create_builtin_oprend(&mut self, imm_value: u64, ty: Type) -> SDValue {
let val = self.dag.get_constant(imm_value, Type::i32());
let builtin = self
.dag
.add_node(SDOpcode::TargetConstant, vec![ty], vec![val]);
builtin
}
pub fn build_copy_to_reg(&mut self, val: SDValue) -> SDValue {
let chain = self.current_chain;
let copy = self.dag.add_node_with_values(
SDOpcode::CopyToReg,
vec![Type::token()],
vec![chain, val],
1,
);
self.current_chain = copy;
copy
}
pub fn build_copy_from_reg(&mut self, ty: Type) -> SDValue {
let chain = self.current_chain;
let copy = self.dag.add_node_with_values(
SDOpcode::CopyFromReg,
vec![ty, Type::token()],
vec![chain],
2,
);
let data = SDValue::new(copy.node_id, 0);
let new_chain = SDValue::new(copy.node_id, 1);
self.current_chain = new_chain;
data
}
pub fn create_implicit_def(&mut self, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::Undef, vec![ty], vec![])
}
pub fn create_reg_sequence(&mut self, regs: &[(SDValue, u32)], result_ty: Type) -> SDValue {
let mut ops = Vec::new();
for (reg, _subreg) in regs {
ops.push(*reg);
}
self.dag.add_node(SDOpcode::BuildPair, vec![result_ty], ops)
}
pub fn create_extract_subreg(&mut self, super_reg: SDValue, subreg: u32, ty: Type) -> SDValue {
let idx = self.dag.get_constant(subreg as u64, Type::i32());
self.dag
.add_node(SDOpcode::ExtractSubvector, vec![ty], vec![super_reg, idx])
}
pub fn create_insert_subreg(
&mut self,
super_reg: SDValue,
sub_reg: SDValue,
subreg: u32,
result_ty: Type,
) -> SDValue {
let idx = self.dag.get_constant(subreg as u64, Type::i32());
self.dag.add_node(
SDOpcode::InsertSubvector,
vec![result_ty],
vec![super_reg, sub_reg, idx],
)
}
pub fn create_dbg_value(
&mut self,
value: SDValue,
variable: &str,
_expr: &str,
ty: Type,
) -> SDValue {
let dbg = self
.dag
.add_node(SDOpcode::TokenFactor, vec![ty], vec![value]);
let _ = variable;
dbg
}
pub fn create_dbg_label(&mut self, label: &str) -> SDValue {
let dbg = self.dag.add_node(
SDOpcode::TokenFactor,
vec![Type::token()],
vec![self.current_chain],
);
let _ = label;
dbg
}
pub fn create_dbg_instr_ref(&mut self, instr_number: u32, ty: Type) -> SDValue {
let num = self.dag.get_constant(instr_number as u64, Type::i32());
let dbg = self
.dag
.add_node(SDOpcode::TokenFactor, vec![ty], vec![num]);
dbg
}
pub fn lower_intrinsic_memcpy(
&mut self,
dst: SDValue,
src: SDValue,
len: SDValue,
_align: u32,
_is_volatile: bool,
) -> SDValue {
let chain = self.current_chain;
let memcpy_node = self.dag.add_node(
SDOpcode::LibCall,
vec![Type::token()],
vec![chain, dst, src, len],
);
self.current_chain = memcpy_node;
memcpy_node
}
pub fn lower_intrinsic_memset(
&mut self,
dst: SDValue,
val: SDValue,
len: SDValue,
_align: u32,
) -> SDValue {
let chain = self.current_chain;
let memset_node = self.dag.add_node(
SDOpcode::LibCall,
vec![Type::token()],
vec![chain, dst, val, len],
);
self.current_chain = memset_node;
memset_node
}
pub fn lower_intrinsic_memmove(
&mut self,
dst: SDValue,
src: SDValue,
len: SDValue,
_align: u32,
) -> SDValue {
let chain = self.current_chain;
let memmove_node = self.dag.add_node(
SDOpcode::LibCall,
vec![Type::token()],
vec![chain, dst, src, len],
);
self.current_chain = memmove_node;
memmove_node
}
pub fn lower_intrinsic_sqrt(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::FSqrt, vec![ty], vec![val])
}
pub fn lower_intrinsic_pow(&mut self, base: SDValue, exp: SDValue, ty: Type) -> SDValue {
let chain = self.current_chain;
let pow_node = self
.dag
.add_node(SDOpcode::LibCall, vec![ty], vec![chain, base, exp]);
pow_node
}
pub fn lower_intrinsic_exp(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::FExp2, vec![ty], vec![val])
}
pub fn lower_intrinsic_log(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::FLog2, vec![ty], vec![val])
}
pub fn lower_intrinsic_sin(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::FSin, vec![ty], vec![val])
}
pub fn lower_intrinsic_cos(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::FCos, vec![ty], vec![val])
}
pub fn lower_intrinsic_ctlz(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::CtLz, vec![ty], vec![val])
}
pub fn lower_intrinsic_cttz(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::CtTz, vec![ty], vec![val])
}
pub fn lower_intrinsic_ctpop(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::CtPop, vec![ty], vec![val])
}
pub fn lower_intrinsic_bswap(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::BSwap, vec![ty], vec![val])
}
pub fn lower_intrinsic_bitreverse(&mut self, val: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::BitReverse, vec![ty], vec![val])
}
pub fn lower_intrinsic_fma(&mut self, a: SDValue, b: SDValue, c: SDValue, ty: Type) -> SDValue {
self.dag.add_node(SDOpcode::FMA, vec![ty], vec![a, b, c])
}
}
pub fn make_test_function(name: &str, return_type: Type, blocks: Vec<ValueRef>) -> ValueRef {
use crate::value::{valref, SubclassKind, Value};
let mut f = Value::new(Type::void())
.named(name)
.with_subclass(SubclassKind::Function);
f.return_type = Some(return_type);
f.operands = blocks;
valref(f)
}
pub fn make_test_block(name: &str, instructions: Vec<ValueRef>) -> ValueRef {
use crate::value::{valref, SubclassKind, Value};
let mut bb = Value::new(Type::label())
.named(name)
.with_subclass(SubclassKind::BasicBlock);
bb.operands = instructions;
valref(bb)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants;
use crate::instruction;
use crate::value::{valref, SubclassKind, Value};
fn make_arg(name: &str, ty: Type) -> ValueRef {
let mut v = Value::new(ty)
.named(name)
.with_subclass(SubclassKind::Argument);
valref(v)
}
fn build(func: &ValueRef) -> (SelectionDAG, usize) {
let mut builder = SelectionDAGBuilder::new();
let dag = builder.build_function(func).unwrap();
let num_nodes = dag.num_nodes();
(dag, num_nodes)
}
#[test]
fn test_build_empty_function() {
let block = make_test_block("entry", vec![]);
let func = make_test_function("empty", Type::void(), vec![block]);
let (dag, _) = build(&func);
assert!(dag.root.is_none());
assert_eq!(dag.get_node(dag.entry_token).opcode, SDOpcode::EntryToken);
}
#[test]
fn test_build_ret_void() {
let ret = instruction::ret_void();
let block = make_test_block("entry", vec![ret]);
let func = make_test_function("test", Type::void(), vec![block]);
let (dag, num) = build(&func);
assert!(num >= 2, "Expected at least EntryToken + Ret, got {}", num);
let root = dag.get_root().unwrap();
assert_eq!(dag.get_node(root).opcode, SDOpcode::Ret);
assert!(dag.get_node(root).has_chain());
}
#[test]
fn test_build_ret_value() {
let c = constants::const_i32(42);
let ret = instruction::ret_val(c);
let block = make_test_block("entry", vec![ret]);
let func = make_test_function("test", Type::i32(), vec![block]);
let (dag, num) = build(&func);
assert!(num >= 2);
let root = dag.get_root().unwrap();
assert_eq!(dag.get_node(root).opcode, SDOpcode::Ret);
assert_eq!(dag.get_node(root).get_num_operands(), 2);
}
#[test]
fn test_build_add() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(10);
let add = instruction::add(a.clone(), b.clone());
let ret = instruction::ret_val(add.clone());
let block = make_test_block("entry", vec![add.clone(), ret]);
let func = make_test_function("add_test", Type::i32(), vec![block]);
let (dag, num) = build(&func);
assert!(num >= 4, "EntryToken + arg + const + add + ret");
let add_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Add)
.collect();
assert_eq!(add_nodes.len(), 1, "Expected exactly one Add node");
assert_eq!(add_nodes[0].get_num_operands(), 2);
assert!(add_nodes[0].is_binary_op());
}
#[test]
fn test_build_sub() {
let a = make_arg("a", Type::i64());
let b = constants::const_i64(5);
let sub = instruction::sub(a, b);
let ret = instruction::ret_val(sub.clone());
let block = make_test_block("entry", vec![sub, ret]);
let func = make_test_function("sub_test", Type::i64(), vec![block]);
let (dag, _) = build(&func);
let sub_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Sub)
.collect();
assert_eq!(sub_nodes.len(), 1);
}
#[test]
fn test_build_mul() {
let a = make_arg("a", Type::i32());
let b = make_arg("b", Type::i32());
let mul = instruction::mul(a, b);
let ret = instruction::ret_val(mul.clone());
let block = make_test_block("entry", vec![mul, ret]);
let func = make_test_function("mul_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Mul)
.collect();
assert_eq!(nodes.len(), 1);
assert!(nodes[0].is_commutative());
}
#[test]
fn test_build_sdiv() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(3);
let div = instruction::sdiv(a, b);
let ret = instruction::ret_val(div.clone());
let block = make_test_block("entry", vec![div, ret]);
let func = make_test_function("sdiv_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SDiv)
.collect();
assert_eq!(nodes.len(), 1);
assert!(!nodes[0].is_commutative());
}
#[test]
fn test_build_and() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(0xFF);
let and = instruction::and(a, b);
let ret = instruction::ret_val(and.clone());
let block = make_test_block("entry", vec![and, ret]);
let func = make_test_function("and_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::And)
.collect();
assert_eq!(nodes.len(), 1);
assert!(nodes[0].is_commutative());
}
#[test]
fn test_build_or() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(0x0F);
let or = instruction::or(a, b);
let ret = instruction::ret_val(or.clone());
let block = make_test_block("entry", vec![or, ret]);
let func = make_test_function("or_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Or)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_xor() {
let a = make_arg("a", Type::i64());
let b = constants::const_i64(-1);
let xor = instruction::xor(a, b);
let ret = instruction::ret_val(xor.clone());
let block = make_test_block("entry", vec![xor, ret]);
let func = make_test_function("xor_test", Type::i64(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Xor)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_shl() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(2);
let shl = instruction::shl(a, b);
let ret = instruction::ret_val(shl.clone());
let block = make_test_block("entry", vec![shl, ret]);
let func = make_test_function("shl_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Shl)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_lshr() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(1);
let lshr = instruction::lshr(a, b);
let ret = instruction::ret_val(lshr.clone());
let block = make_test_block("entry", vec![lshr, ret]);
let func = make_test_function("lshr_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Srl)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_ashr() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(2);
let ashr = instruction::ashr(a, b);
let ret = instruction::ret_val(ashr.clone());
let block = make_test_block("entry", vec![ashr, ret]);
let func = make_test_function("ashr_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Sra)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_fadd() {
let a = make_arg("a", Type::float());
let b = make_arg("b", Type::float());
let fadd = instruction::fadd(a, b);
let ret = instruction::ret_val(fadd.clone());
let block = make_test_block("entry", vec![fadd, ret]);
let func = make_test_function("fadd_test", Type::float(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::FAdd)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_fmul() {
let a = constants::const_float(2.0);
let b = constants::const_float(3.0);
let fmul = instruction::fmul(a, b);
let ret = instruction::ret_val(fmul.clone());
let block = make_test_block("entry", vec![fmul, ret]);
let func = make_test_function("fmul_test", Type::float(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::FMul)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_icmp_eq() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(0);
let cmp = instruction::icmp(ICmpPred::Eq, a, b);
let ret = instruction::ret_val(cmp.clone());
let block = make_test_block("entry", vec![cmp, ret]);
let func = make_test_function("icmp_test", Type::i1(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SetCC)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_icmp_sgt() {
let a = make_arg("a", Type::i64());
let b = constants::const_i64(100);
let cmp = instruction::icmp(ICmpPred::Sgt, a, b);
let ret = instruction::ret_val(cmp.clone());
let block = make_test_block("entry", vec![cmp, ret]);
let func = make_test_function("icmp_sgt", Type::i1(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SetCC)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_icmp_ult() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(255);
let cmp = instruction::icmp(ICmpPred::Ult, a, b);
let ret = instruction::ret_val(cmp.clone());
let block = make_test_block("entry", vec![cmp, ret]);
let func = make_test_function("icmp_ult", Type::i1(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SetCC)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_fcmp() {
let a = constants::const_double(1.0);
let b = constants::const_double(2.0);
let cmp = instruction::fcmp(FCmpPred::Olt, a, b);
let ret = instruction::ret_val(cmp.clone());
let block = make_test_block("entry", vec![cmp, ret]);
let func = make_test_function("fcmp_test", Type::i1(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SetCC)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_load() {
let ptr = make_arg("ptr", Type::pointer(0));
let load = instruction::load(Type::i32(), ptr);
let ret = instruction::ret_val(load.clone());
let block = make_test_block("entry", vec![load, ret]);
let func = make_test_function("load_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Load)
.collect();
assert_eq!(nodes.len(), 1);
assert!(nodes[0].has_chain());
assert_eq!(nodes[0].num_values, 2);
assert_eq!(nodes[0].get_num_operands(), 2);
}
#[test]
fn test_build_store() {
let val = constants::const_i32(42);
let ptr = make_arg("ptr", Type::pointer(0));
let store = instruction::store(val, ptr);
let ret = instruction::ret_void();
let block = make_test_block("entry", vec![store, ret]);
let func = make_test_function("store_test", Type::void(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Store)
.collect();
assert_eq!(nodes.len(), 1);
assert!(nodes[0].has_chain());
assert_eq!(nodes[0].get_num_operands(), 3); }
#[test]
fn test_build_load_store_chain() {
let ptr = make_arg("ptr", Type::pointer(0));
let val = constants::const_i32(7);
let load = instruction::load(Type::i32(), ptr.clone());
let store = instruction::store(val.clone(), ptr.clone());
let ret = instruction::ret_void();
let block = make_test_block("entry", vec![load, store, ret]);
let func = make_test_function("mem_test", Type::void(), vec![block]);
let (dag, _) = build(&func);
let load_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Load)
.collect();
let store_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Store)
.collect();
assert_eq!(load_nodes.len(), 1);
assert_eq!(store_nodes.len(), 1);
let store_chain_op = store_nodes[0].get_operand(0).unwrap();
assert_eq!(store_chain_op.node_id, load_nodes[0].id);
assert_eq!(store_chain_op.res_no, 1);
}
#[test]
fn test_build_alloca() {
let alloca = instruction::alloca(Type::i32());
let store = instruction::store(constants::const_i32(0), alloca.clone());
let ret = instruction::ret_void();
let block = make_test_block("entry", vec![alloca.clone(), store, ret]);
let func = make_test_function("alloca_test", Type::void(), vec![block]);
let (dag, _) = build(&func);
let frame_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::FrameIndex)
.collect();
assert_eq!(frame_nodes.len(), 1);
assert!(frame_nodes[0].value_types[0].is_pointer());
}
#[test]
fn test_build_unconditional_br() {
let target = make_test_block("target", vec![]);
let br = instruction::br(target);
let block = make_test_block("entry", vec![br]);
let func = make_test_function("br_test", Type::void(), vec![block]);
let (dag, _) = build(&func);
let root = dag.get_root().unwrap();
assert_eq!(dag.get_node(root).opcode, SDOpcode::Br);
}
#[test]
fn test_build_conditional_br() {
let cond = constants::const_bool(true);
let true_bb = make_test_block("if_true", vec![]);
let false_bb = make_test_block("if_false", vec![]);
let br = instruction::br_cond(cond, true_bb, false_bb);
let block = make_test_block("entry", vec![br]);
let func = make_test_function("brcond_test", Type::void(), vec![block]);
let (dag, _) = build(&func);
let root = dag.get_root().unwrap();
assert_eq!(dag.get_node(root).opcode, SDOpcode::BrCond);
}
#[test]
fn test_build_phi_simple() {
let a = make_arg("a", Type::i32());
let b = make_arg("b", Type::i32());
let bb1 = make_test_block("bb1", vec![]);
let bb2 = make_test_block("bb2", vec![]);
let phi = instruction::phi(Type::i32(), vec![(a, bb1), (b, bb2)]);
let ret = instruction::ret_val(phi.clone());
let block = make_test_block("merge", vec![phi, ret]);
let func = make_test_function("phi_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let reg_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Register)
.collect();
assert!(!reg_nodes.is_empty());
}
#[test]
fn test_build_diamond_cfg() {
let x = make_arg("x", Type::i32());
let zero = constants::const_i32(0);
let cond = instruction::icmp(ICmpPred::Sgt, x, zero);
let t_block = make_test_block(
"if_true",
vec![instruction::ret_val(constants::const_i32(1))],
);
let f_block = make_test_block(
"if_false",
vec![instruction::ret_val(constants::const_i32(0))],
);
let entry_br = instruction::br_cond(cond.clone(), t_block.clone(), f_block.clone());
let entry = make_test_block("entry", vec![cond, entry_br]);
let func = make_test_function("diamond", Type::i32(), vec![entry, t_block, f_block]);
let (dag, _) = build(&func);
let setcc_count = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SetCC)
.count();
assert_eq!(setcc_count, 1);
let brcond_count = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::BrCond)
.count();
assert_eq!(brcond_count, 1);
}
#[test]
fn test_build_call() {
let callee = make_test_function("callee", Type::i32(), vec![]);
let call = instruction::call(Type::i32(), callee, vec![]);
let ret = instruction::ret_val(call.clone());
let block = make_test_block("entry", vec![call, ret]);
let func = make_test_function("call_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let call_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Call)
.collect();
assert_eq!(call_nodes.len(), 1);
assert!(call_nodes[0].has_chain());
assert_eq!(call_nodes[0].num_values, 2);
assert!(call_nodes[0].get_num_operands() >= 2);
}
#[test]
fn test_build_call_with_args() {
let callee = make_test_function("puts", Type::void(), vec![]);
let arg = make_arg("str", Type::pointer(0));
let call = instruction::call(Type::void(), callee, vec![arg]);
let ret = instruction::ret_void();
let block = make_test_block("entry", vec![call, ret]);
let func = make_test_function("call_args", Type::void(), vec![block]);
let (dag, _) = build(&func);
let call_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Call)
.collect();
assert_eq!(call_nodes.len(), 1);
}
#[test]
fn test_build_gep_simple() {
let ptr = make_arg("ptr", Type::pointer(0));
let idx = constants::const_i32(4);
let gep = instruction::getelementptr(Type::i32(), ptr, vec![idx]);
let ret = instruction::ret_val(gep.clone());
let block = make_test_block("entry", vec![gep, ret]);
let func = make_test_function("gep_test", Type::pointer(0), vec![block]);
let (dag, _) = build(&func);
let add_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Add)
.collect();
assert_eq!(add_nodes.len(), 1, "GEP should produce an Add node");
}
#[test]
fn test_build_gep_multiple_indices() {
let ptr = make_arg("arr", Type::pointer(0));
let idx0 = constants::const_i64(0);
let idx1 = constants::const_i64(5);
let gep = instruction::getelementptr(Type::i64(), ptr, vec![idx0, idx1]);
let ret = instruction::ret_val(gep.clone());
let block = make_test_block("entry", vec![gep, ret]);
let func = make_test_function("gep_multi", Type::pointer(0), vec![block]);
let (dag, _) = build(&func);
let add_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Add)
.collect();
assert_eq!(
add_nodes.len(),
2,
"GEP with 2 indices should produce 2 Add nodes"
);
}
#[test]
fn test_build_zext() {
let val = constants::const_i32(1);
let zext = instruction::zext(val, Type::i64());
let ret = instruction::ret_val(zext.clone());
let block = make_test_block("entry", vec![zext, ret]);
let func = make_test_function("zext_test", Type::i64(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::ZExt)
.collect();
assert_eq!(nodes.len(), 1);
assert!(nodes[0].is_conversion());
}
#[test]
fn test_build_sext() {
let val = constants::const_int(Type::i16(), -1);
let sext = instruction::sext(val, Type::i64());
let ret = instruction::ret_val(sext.clone());
let block = make_test_block("entry", vec![sext, ret]);
let func = make_test_function("sext_test", Type::i64(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SExt)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_trunc() {
let val = constants::const_i64(0xFFFF);
let trunc = instruction::trunc(val, Type::i32());
let ret = instruction::ret_val(trunc.clone());
let block = make_test_block("entry", vec![trunc, ret]);
let func = make_test_function("trunc_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Trunc)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_bitcast() {
let val = constants::const_i32(0x3F800000u32 as i32); let bc = instruction::bitcast(val, Type::float());
let ret = instruction::ret_val(bc.clone());
let block = make_test_block("entry", vec![bc, ret]);
let func = make_test_function("bc_test", Type::float(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Bitcast)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_fptosi() {
let val = constants::const_double(3.14);
let cvt = instruction::fptosi(val, Type::i32());
let ret = instruction::ret_val(cvt.clone());
let block = make_test_block("entry", vec![cvt, ret]);
let func = make_test_function("fptosi_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::FPToSI)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_sitofp() {
let val = constants::const_i32(42);
let cvt = instruction::sitofp(val, Type::float());
let ret = instruction::ret_val(cvt.clone());
let block = make_test_block("entry", vec![cvt, ret]);
let func = make_test_function("sitofp_test", Type::float(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SIToFP)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_build_select() {
let cond = constants::const_bool(true);
let tval = constants::const_i32(10);
let fval = constants::const_i32(20);
let sel = instruction::select(cond, tval, fval);
let ret = instruction::ret_val(sel.clone());
let block = make_test_block("entry", vec![sel, ret]);
let func = make_test_function("sel_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Select)
.collect();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_num_operands(), 3);
}
#[test]
fn test_build_freeze() {
let val = constants::const_i32(42);
let free = valref(
Value::new(Type::i32())
.named("freeze")
.with_subclass(SubclassKind::Instruction),
);
free.borrow_mut().set_opcode(Opcode::Freeze);
free.borrow_mut().operands = vec![val];
free.borrow_mut().num_operands = 1;
let ret = instruction::ret_val(free.clone());
let block = make_test_block("entry", vec![free, ret]);
let func = make_test_function("freeze_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Freeze)
.collect();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_constant_dedup_in_builder() {
let c1 = constants::const_i32(42);
let c2 = constants::const_i32(42);
let a = make_arg("a", Type::i32());
let add1 = instruction::add(a.clone(), c1);
let add2 = instruction::add(add1.clone(), c2);
let ret = instruction::ret_val(add2.clone());
let block = make_test_block("entry", vec![add1, add2, ret]);
let func = make_test_function("dedup_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let const_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Constant)
.collect();
assert_eq!(
const_nodes.len(),
1,
"Constants should be deduplicated: expected 1, got {}",
const_nodes.len()
);
}
#[test]
fn test_value_mapping_arg() {
let a = make_arg("a", Type::i32());
let ret = instruction::ret_val(a.clone());
let block = make_test_block("entry", vec![ret]);
let func = make_test_function("arg_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let root = dag.get_root().unwrap();
let ret_node = dag.get_node(root);
assert_eq!(ret_node.get_num_operands(), 2); }
#[test]
fn test_build_multiple_blocks() {
let a = make_arg("a", Type::i32());
let add = instruction::add(a.clone(), constants::const_i32(1));
let ret1 = instruction::ret_val(add.clone());
let bb1 = make_test_block("bb1", vec![add, ret1]);
let sub = instruction::sub(a.clone(), constants::const_i32(1));
let ret2 = instruction::ret_val(sub.clone());
let bb2 = make_test_block("bb2", vec![sub, ret2]);
let func = make_test_function("multi_bb", Type::i32(), vec![bb1, bb2]);
let (dag, _) = build(&func);
assert!(dag.num_nodes() > 2);
let add_nodes = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Add)
.count();
assert_eq!(add_nodes, 1);
let sub_nodes = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Sub)
.count();
assert_eq!(sub_nodes, 1);
let ret_nodes = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Ret)
.count();
assert_eq!(ret_nodes, 2);
}
#[test]
fn test_build_complex_expression() {
let a = make_arg("a", Type::i32());
let b = make_arg("b", Type::i32());
let c = make_arg("c", Type::i32());
let d = make_arg("d", Type::i32());
let sum = instruction::add(a, b);
let diff = instruction::sub(c, d);
let prod = instruction::mul(sum.clone(), diff.clone());
let div = instruction::sdiv(prod.clone(), constants::const_i32(2));
let ret = instruction::ret_val(div.clone());
let block = make_test_block("entry", vec![sum, diff, prod, div, ret]);
let func = make_test_function("complex", Type::i32(), vec![block]);
let (dag, _) = build(&func);
assert_eq!(
dag.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Add)
.count(),
1
);
assert_eq!(
dag.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Sub)
.count(),
1
);
assert_eq!(
dag.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Mul)
.count(),
1
);
assert_eq!(
dag.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SDiv)
.count(),
1
);
}
#[test]
fn test_build_token_factor() {
let ptr1 = make_arg("p1", Type::pointer(0));
let ptr2 = make_arg("p2", Type::pointer(0));
let val1 = constants::const_i32(1);
let val2 = constants::const_i32(2);
let store1 = instruction::store(val1, ptr1);
let store2 = instruction::store(val2, ptr2);
let ret = instruction::ret_void();
let block = make_test_block("entry", vec![store1, store2, ret]);
let func = make_test_function("two_stores", Type::void(), vec![block]);
let (dag, _) = build(&func);
let store_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Store)
.collect();
assert_eq!(store_nodes.len(), 2);
let s2_chain = store_nodes[1].get_operand(0).unwrap();
assert_eq!(s2_chain.node_id, store_nodes[0].id);
}
#[test]
fn test_flags_default_on_built_nodes() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(5);
let add = instruction::add(a, b);
let ret = instruction::ret_val(add.clone());
let block = make_test_block("entry", vec![add, ret]);
let func = make_test_function("flags_default", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let add_node = dag
.nodes
.iter()
.find(|n| n.opcode == SDOpcode::Add)
.unwrap();
assert!(!add_node.flags.has_no_unsigned_wrap);
assert!(!add_node.flags.has_no_signed_wrap);
}
#[test]
fn test_debug_loc_on_nodes() {
let a = make_arg("a", Type::i32());
let ret = instruction::ret_val(a.clone());
let block = make_test_block("entry", vec![ret]);
let func = make_test_function("debug_test", Type::i32(), vec![block]);
let (dag, _) = build(&func);
let root = dag.get_root().unwrap();
let root_node = dag.get_node(root);
assert!(root_node.debug_loc.is_none());
}
#[test]
fn test_get_constant_integer() {
let mut builder = SelectionDAGBuilder::new();
let c1 = builder.get_constant(123, Type::i32());
let c2 = builder.get_constant(123, Type::i32());
assert_eq!(c1, c2, "Same constant should be deduplicated");
let c3 = builder.get_constant(456, Type::i32());
assert_ne!(c1, c3, "Different values should be different nodes");
}
#[test]
fn test_get_constant_fp() {
let mut builder = SelectionDAGBuilder::new();
let c1 = builder.get_constant_fp(3.14, Type::double());
let c2 = builder.get_constant_fp(3.14, Type::double());
assert_eq!(c1, c2, "Same FP constant should be deduplicated");
}
#[test]
fn test_build_non_function() {
let mut builder = SelectionDAGBuilder::new();
let not_func = make_arg("x", Type::i32());
let result = builder.build_function(¬_func);
assert!(result.is_err());
}
#[test]
fn test_build_basic_block() {
let mut builder = SelectionDAGBuilder::new();
let add = instruction::add(constants::const_i32(1), constants::const_i32(2));
let ret = instruction::ret_val(add.clone());
let block = make_test_block("test_bb", vec![add, ret]);
let result = builder.build_basic_block(&block);
assert!(result.is_ok());
assert!(builder.dag.num_nodes() > 1);
}
#[test]
fn test_build_unreachable() {
let ur = instruction::unreachable();
let block = make_test_block("entry", vec![ur]);
let func = make_test_function("unreach_test", Type::void(), vec![block]);
let (dag, _) = build(&func);
let root = dag.get_root().unwrap();
assert_eq!(dag.get_node(root).opcode, SDOpcode::Br);
}
#[test]
fn test_build_mixed_instructions() {
let a = make_arg("a", Type::i32());
let b = constants::const_i32(10);
let c = constants::const_i32(3);
let add_val = instruction::add(a.clone(), b);
let mul_val = instruction::mul(add_val.clone(), c);
let cmp = instruction::icmp(ICmpPred::Sgt, mul_val.clone(), constants::const_i32(0));
let ret = instruction::ret_val(cmp.clone());
let block = make_test_block("entry", vec![add_val, mul_val, cmp, ret]);
let func = make_test_function("mixed", Type::i1(), vec![block]);
let (dag, _) = build(&func);
assert_eq!(
dag.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Add)
.count(),
1
);
assert_eq!(
dag.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::Mul)
.count(),
1
);
assert_eq!(
dag.nodes
.iter()
.filter(|n| n.opcode == SDOpcode::SetCC)
.count(),
1
);
}
#[test]
fn test_chain_edges_load_then_store() {
let ptr = make_arg("ptr", Type::pointer(0));
let load = instruction::load(Type::i32(), ptr.clone());
let store = instruction::store(load.clone(), ptr.clone());
let ret = instruction::ret_void();
let block = make_test_block("entry", vec![load.clone(), store, ret]);
let func = make_test_function("chain_test", Type::void(), vec![block]);
let (dag, _) = build(&func);
let store_node = dag
.nodes
.iter()
.find(|n| n.opcode == SDOpcode::Store)
.unwrap();
let load_val = *dag
.nodes
.iter()
.find(|n| n.opcode == SDOpcode::Load)
.map(|n| n.id)
.get_or_insert(0);
let store_chain = store_node.get_operand(0).unwrap();
assert_eq!(store_chain.node_id, load_val);
assert_eq!(
store_chain.res_no, 1,
"Chain should be res_no=1 of the load node"
);
}
#[test]
fn test_builder_reuse() {
let mut builder = SelectionDAGBuilder::new();
let a = make_arg("a", Type::i32());
let add = instruction::add(a.clone(), constants::const_i32(1));
let ret = instruction::ret_val(add.clone());
let block = make_test_block("entry", vec![add, ret]);
let func = make_test_function("f1", Type::i32(), vec![block]);
let dag1 = builder.build_function(&func).unwrap();
let n1 = dag1.num_nodes();
let b = make_arg("b", Type::i64());
let ret2 = instruction::ret_val(b.clone());
let block2 = make_test_block("entry", vec![ret2]);
let func2 = make_test_function("f2", Type::i64(), vec![block2]);
let dag2 = builder.build_function(&func2).unwrap();
let n2 = dag2.num_nodes();
assert!(n1 > n2, "Function 1 has more nodes than function 2");
}
#[test]
fn test_indirect_call_lowering() {
let mut builder = SelectionDAGBuilder::new();
let callee = builder.dag.get_register(Type::pointer(0));
let args = vec![builder.dag.get_constant(42, Type::i32())];
let result = builder.lower_indirect_call(callee, &args, Type::i32());
assert!(result.node_id > 0);
let call_node = builder.dag.get_node(result);
assert_eq!(call_node.opcode, SDOpcode::Call);
}
#[test]
fn test_tail_call_lowering() {
let mut builder = SelectionDAGBuilder::new();
let callee = builder.dag.get_register(Type::pointer(0));
let args = vec![builder.dag.get_constant(1, Type::i32())];
let result = builder.lower_tail_call(callee, &args, Type::i32());
assert!(result.node_id > 0);
assert!(builder.dag.root.is_some());
}
#[test]
fn test_sibling_call_lowering() {
let mut builder = SelectionDAGBuilder::new();
let callee = builder.dag.get_register(Type::pointer(0));
let args = vec![builder.dag.get_constant(7, Type::i64())];
let result = builder.lower_sibling_call(callee, &args, Type::i64());
assert!(result.node_id > 0);
}
#[test]
fn test_icmp_setcc_lowering() {
let mut builder = SelectionDAGBuilder::new();
let lhs = builder.dag.get_register(Type::i32());
let rhs = builder.dag.get_constant(0, Type::i32());
let setcc = builder.lower_icmp_setcc(crate::opcode::ICmpPred::Eq, lhs, rhs);
let node = builder.dag.get_node(setcc);
assert_eq!(node.opcode, SDOpcode::SetCC);
assert_eq!(node.operands.len(), 2);
}
#[test]
fn test_fcmp_setcc_lowering() {
let mut builder = SelectionDAGBuilder::new();
let lhs = builder.dag.get_register(Type::float());
let rhs = builder.dag.get_constant_fp(0.0f64.to_bits(), Type::float());
let setcc = builder.lower_fcmp_setcc(crate::opcode::FCmpPred::Oeq, lhs, rhs);
let node = builder.dag.get_node(setcc);
assert_eq!(node.opcode, SDOpcode::SetCC);
}
#[test]
fn test_frame_index_creation() {
let mut builder = SelectionDAGBuilder::new();
let fi = builder.create_frame_index(3, Type::pointer(0));
let node = builder.dag.get_node(fi);
assert_eq!(node.opcode, SDOpcode::FrameIndex);
assert_eq!(node.operands.len(), 1);
}
#[test]
fn test_global_address_creation() {
let mut builder = SelectionDAGBuilder::new();
let ga = builder.create_global_address("my_global", 8, Type::pointer(0));
let node = builder.dag.get_node(ga);
assert_eq!(node.opcode, SDOpcode::GlobalAddress);
assert_eq!(node.operands.len(), 1);
}
#[test]
fn test_constant_pool_creation() {
let mut builder = SelectionDAGBuilder::new();
let cp = builder.create_constant_pool(0xDEADBEEF, Type::pointer(0));
let node = builder.dag.get_node(cp);
assert_eq!(node.opcode, SDOpcode::ConstantPool);
}
#[test]
fn test_constant_pool_fp_creation() {
let mut builder = SelectionDAGBuilder::new();
let cp = builder.create_constant_pool_fp(3.14f64.to_bits(), Type::pointer(0));
let node = builder.dag.get_node(cp);
assert_eq!(node.opcode, SDOpcode::ConstantPool);
}
#[test]
fn test_jump_table_creation() {
let mut builder = SelectionDAGBuilder::new();
let jt = builder.create_jump_table(2, Type::pointer(0));
let node = builder.dag.get_node(jt);
assert_eq!(node.opcode, SDOpcode::JumpTable);
}
#[test]
fn test_block_address_creation() {
let mut builder = SelectionDAGBuilder::new();
let ba = builder.create_block_address("label_5", Type::pointer(0));
let node = builder.dag.get_node(ba);
assert_eq!(node.opcode, SDOpcode::BlockAddress);
}
#[test]
fn test_external_symbol_creation() {
let mut builder = SelectionDAGBuilder::new();
let es = builder.create_external_symbol("printf", Type::pointer(0));
let node = builder.dag.get_node(es);
assert_eq!(node.opcode, SDOpcode::ExternalSymbol);
}
#[test]
fn test_target_index_creation() {
let mut builder = SelectionDAGBuilder::new();
let ti = builder.create_target_index(7, Type::pointer(0));
let node = builder.dag.get_node(ti);
assert_eq!(node.opcode, SDOpcode::TargetIndex);
}
#[test]
fn test_builtin_oprend_creation() {
let mut builder = SelectionDAGBuilder::new();
let bo = builder.create_builtin_oprend(0xFFFF, Type::i32());
let node = builder.dag.get_node(bo);
assert_eq!(node.opcode, SDOpcode::TargetConstant);
}
#[test]
fn test_copy_to_reg_creation() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_constant(42, Type::i32());
let copy = builder.build_copy_to_reg(val);
let node = builder.dag.get_node(copy);
assert_eq!(node.opcode, SDOpcode::CopyToReg);
}
#[test]
fn test_copy_from_reg_creation() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.build_copy_from_reg(Type::i32());
let node = builder.dag.get_node(val);
assert_eq!(node.opcode, SDOpcode::CopyFromReg);
}
#[test]
fn test_implicit_def_creation() {
let mut builder = SelectionDAGBuilder::new();
let def = builder.create_implicit_def(Type::i64());
let node = builder.dag.get_node(def);
assert_eq!(node.opcode, SDOpcode::Undef);
}
#[test]
fn test_reg_sequence_creation() {
let mut builder = SelectionDAGBuilder::new();
let lo = builder.dag.get_register(Type::i32());
let hi = builder.dag.get_register(Type::i32());
let regs = vec![(lo, 0), (hi, 1)];
let seq = builder.create_reg_sequence(®s, Type::i64());
let node = builder.dag.get_node(seq);
assert_eq!(node.opcode, SDOpcode::BuildPair);
assert_eq!(node.operands.len(), 2);
}
#[test]
fn test_extract_subreg_creation() {
let mut builder = SelectionDAGBuilder::new();
let super_reg = builder.dag.get_register(Type::i64());
let sub = builder.create_extract_subreg(super_reg, 0, Type::i32());
let node = builder.dag.get_node(sub);
assert_eq!(node.opcode, SDOpcode::ExtractSubvector);
}
#[test]
fn test_insert_subreg_creation() {
let mut builder = SelectionDAGBuilder::new();
let super_reg = builder.dag.get_register(Type::i64());
let sub_reg = builder.dag.get_register(Type::i32());
let ins = builder.create_insert_subreg(super_reg, sub_reg, 0, Type::i64());
let node = builder.dag.get_node(ins);
assert_eq!(node.opcode, SDOpcode::InsertSubvector);
}
#[test]
fn test_dbg_value_creation() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_constant(10, Type::i32());
let dbg = builder.create_dbg_value(val, "x", "", Type::i32());
let node = builder.dag.get_node(dbg);
assert_eq!(node.opcode, SDOpcode::TokenFactor);
}
#[test]
fn test_dbg_label_creation() {
let mut builder = SelectionDAGBuilder::new();
let dbg = builder.create_dbg_label("entry_label");
let node = builder.dag.get_node(dbg);
assert_eq!(node.opcode, SDOpcode::TokenFactor);
}
#[test]
fn test_dbg_instr_ref_creation() {
let mut builder = SelectionDAGBuilder::new();
let dbg = builder.create_dbg_instr_ref(42, Type::i32());
let node = builder.dag.get_node(dbg);
assert_eq!(node.opcode, SDOpcode::TokenFactor);
}
#[test]
fn test_intrinsic_memcpy_lowering() {
let mut builder = SelectionDAGBuilder::new();
let dst = builder.dag.get_register(Type::pointer(0));
let src = builder.dag.get_register(Type::pointer(0));
let len = builder.dag.get_constant(16, Type::i32());
let result = builder.lower_intrinsic_memcpy(dst, src, len, 4, false);
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::LibCall);
assert_eq!(node.operands.len(), 4);
}
#[test]
fn test_intrinsic_memset_lowering() {
let mut builder = SelectionDAGBuilder::new();
let dst = builder.dag.get_register(Type::pointer(0));
let val = builder.dag.get_constant(0, Type::i8());
let len = builder.dag.get_constant(32, Type::i32());
let result = builder.lower_intrinsic_memset(dst, val, len, 4);
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::LibCall);
}
#[test]
fn test_intrinsic_memmove_lowering() {
let mut builder = SelectionDAGBuilder::new();
let dst = builder.dag.get_register(Type::pointer(0));
let src = builder.dag.get_register(Type::pointer(0));
let len = builder.dag.get_constant(64, Type::i32());
let result = builder.lower_intrinsic_memmove(dst, src, len, 8);
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::LibCall);
}
#[test]
fn test_intrinsic_sqrt_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::double());
let result = builder.lower_intrinsic_sqrt(val, Type::double());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::FSqrt);
}
#[test]
fn test_intrinsic_pow_lowering() {
let mut builder = SelectionDAGBuilder::new();
let base = builder.dag.get_register(Type::double());
let exp = builder.dag.get_register(Type::double());
let result = builder.lower_intrinsic_pow(base, exp, Type::double());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::LibCall);
}
#[test]
fn test_intrinsic_exp_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::double());
let result = builder.lower_intrinsic_exp(val, Type::double());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::FExp2);
}
#[test]
fn test_intrinsic_log_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::float());
let result = builder.lower_intrinsic_log(val, Type::float());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::FLog2);
}
#[test]
fn test_intrinsic_sin_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::double());
let result = builder.lower_intrinsic_sin(val, Type::double());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::FSin);
}
#[test]
fn test_intrinsic_cos_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::double());
let result = builder.lower_intrinsic_cos(val, Type::double());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::FCos);
}
#[test]
fn test_intrinsic_ctlz_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::i32());
let result = builder.lower_intrinsic_ctlz(val, Type::i32());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::CtLz);
}
#[test]
fn test_intrinsic_cttz_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::i64());
let result = builder.lower_intrinsic_cttz(val, Type::i64());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::CtTz);
}
#[test]
fn test_intrinsic_ctpop_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::i32());
let result = builder.lower_intrinsic_ctpop(val, Type::i32());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::CtPop);
}
#[test]
fn test_intrinsic_bswap_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::i32());
let result = builder.lower_intrinsic_bswap(val, Type::i32());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::BSwap);
}
#[test]
fn test_intrinsic_bitreverse_lowering() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_register(Type::i8());
let result = builder.lower_intrinsic_bitreverse(val, Type::i8());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::BitReverse);
}
#[test]
fn test_intrinsic_fma_lowering() {
let mut builder = SelectionDAGBuilder::new();
let a = builder.dag.get_register(Type::double());
let b = builder.dag.get_register(Type::double());
let c = builder.dag.get_register(Type::double());
let result = builder.lower_intrinsic_fma(a, b, c, Type::double());
let node = builder.dag.get_node(result);
assert_eq!(node.opcode, SDOpcode::FMA);
assert_eq!(node.operands.len(), 3);
}
#[test]
fn test_builder_call_and_intrinsic_chain() {
let mut builder = SelectionDAGBuilder::new();
let callee = builder.dag.get_register(Type::pointer(0));
let args = vec![
builder.dag.get_constant(42, Type::i32()),
builder.dag.get_constant(7, Type::i32()),
];
let call_result = builder.lower_indirect_call(callee, &args, Type::i32());
let chain = builder.current_chain;
assert!(chain.node_id > 0);
assert_eq!(chain.node_id, call_result.node_id);
assert_eq!(chain.res_no, 1);
}
#[test]
fn test_builder_frame_index_and_global_address() {
let mut builder = SelectionDAGBuilder::new();
let fi = builder.create_frame_index(0, Type::pointer(0));
assert_eq!(builder.dag.get_node(fi).opcode, SDOpcode::FrameIndex);
let ga = builder.create_global_address("my_var", 8, Type::pointer(0));
assert_eq!(builder.dag.get_node(ga).opcode, SDOpcode::GlobalAddress);
}
#[test]
fn test_builder_constant_pool_and_jump_table() {
let mut builder = SelectionDAGBuilder::new();
let cp = builder.create_constant_pool(0xDEADBEEF, Type::pointer(0));
assert_eq!(builder.dag.get_node(cp).opcode, SDOpcode::ConstantPool);
let jt = builder.create_jump_table(3, Type::pointer(0));
assert_eq!(builder.dag.get_node(jt).opcode, SDOpcode::JumpTable);
}
#[test]
fn test_builder_block_address_and_external_symbol() {
let mut builder = SelectionDAGBuilder::new();
let ba = builder.create_block_address("label_7", Type::pointer(0));
assert_eq!(builder.dag.get_node(ba).opcode, SDOpcode::BlockAddress);
let es = builder.create_external_symbol("exit", Type::pointer(0));
assert_eq!(builder.dag.get_node(es).opcode, SDOpcode::ExternalSymbol);
}
#[test]
fn test_builder_target_index_and_builtin() {
let mut builder = SelectionDAGBuilder::new();
let ti = builder.create_target_index(15, Type::pointer(0));
assert_eq!(builder.dag.get_node(ti).opcode, SDOpcode::TargetIndex);
let bo = builder.create_builtin_oprend(0xFF, Type::i32());
assert_eq!(builder.dag.get_node(bo).opcode, SDOpcode::TargetConstant);
}
#[test]
fn test_builder_reg_sequence_multi_subreg() {
let mut builder = SelectionDAGBuilder::new();
let lo = builder.dag.get_register(Type::i32());
let hi = builder.dag.get_register(Type::i32());
let regs = vec![(lo, 0), (hi, 1)];
let seq = builder.create_reg_sequence(®s, Type::i64());
assert_eq!(builder.dag.get_node(seq).opcode, SDOpcode::BuildPair);
assert_eq!(builder.dag.get_node(seq).operands.len(), 2);
}
#[test]
fn test_builder_extract_and_insert_subreg() {
let mut builder = SelectionDAGBuilder::new();
let super_reg = builder.dag.get_register(Type::i64());
let sub = builder.create_extract_subreg(super_reg, 0, Type::i32());
assert_eq!(builder.dag.get_node(sub).opcode, SDOpcode::ExtractSubvector);
let super2 = builder.dag.get_register(Type::i64());
let sub2 = builder.dag.get_register(Type::i32());
let ins = builder.create_insert_subreg(super2, sub2, 1, Type::i64());
assert_eq!(builder.dag.get_node(ins).opcode, SDOpcode::InsertSubvector);
assert_eq!(builder.dag.get_node(ins).operands.len(), 3);
}
#[test]
fn test_builder_debug_info_nodes() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_constant(100, Type::i32());
let dbgv = builder.create_dbg_value(val, "my_var", "DW_OP_reg0", Type::i32());
assert_eq!(builder.dag.get_node(dbgv).opcode, SDOpcode::TokenFactor);
let dbgl = builder.create_dbg_label("start_label");
assert_eq!(builder.dag.get_node(dbgl).opcode, SDOpcode::TokenFactor);
let dbgi = builder.create_dbg_instr_ref(42, Type::i32());
assert_eq!(builder.dag.get_node(dbgi).opcode, SDOpcode::TokenFactor);
}
#[test]
fn test_builder_implicit_def() {
let mut builder = SelectionDAGBuilder::new();
let def = builder.create_implicit_def(Type::i64());
assert_eq!(builder.dag.get_node(def).opcode, SDOpcode::Undef);
}
#[test]
fn test_builder_copy_to_from_reg_chain() {
let mut builder = SelectionDAGBuilder::new();
let val = builder.dag.get_constant(42, Type::i32());
let old_chain = builder.current_chain;
let _copy_to = builder.build_copy_to_reg(val);
assert!(builder.current_chain != old_chain);
}
#[test]
fn test_builder_setcc_with_different_predicates() {
use crate::opcode::{FCmpPred, ICmpPred};
let mut builder = SelectionDAGBuilder::new();
let lhs = builder.dag.get_register(Type::i32());
let rhs = builder.dag.get_constant(0, Type::i32());
let preds = [
ICmpPred::Eq,
ICmpPred::Ne,
ICmpPred::Sgt,
ICmpPred::Sge,
ICmpPred::Slt,
ICmpPred::Sle,
ICmpPred::Ugt,
ICmpPred::Uge,
ICmpPred::Ult,
ICmpPred::Ule,
];
for pred in &preds {
let setcc = builder.lower_icmp_setcc(*pred, lhs, rhs);
assert_eq!(builder.dag.get_node(setcc).opcode, SDOpcode::SetCC);
}
}
#[test]
fn test_builder_fcmp_setcc_predicates() {
use crate::opcode::FCmpPred;
let mut builder = SelectionDAGBuilder::new();
let lhs = builder.dag.get_register(Type::float());
let rhs = builder.dag.get_constant_fp(0.0f64.to_bits(), Type::float());
let preds = [
FCmpPred::Oeq,
FCmpPred::One,
FCmpPred::Olt,
FCmpPred::Ole,
FCmpPred::Ogt,
FCmpPred::Oge,
];
for pred in &preds {
let setcc = builder.lower_fcmp_setcc(*pred, lhs, rhs);
assert_eq!(builder.dag.get_node(setcc).opcode, SDOpcode::SetCC);
}
}
}