use super::sd_node::*;
use crate::types::{Type, TypeKind};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpLegalizeAction {
Legal,
Expand,
LibCall,
Promote,
Custom,
}
impl OpLegalizeAction {
pub fn as_str(&self) -> &'static str {
match self {
OpLegalizeAction::Legal => "Legal",
OpLegalizeAction::Expand => "Expand",
OpLegalizeAction::LibCall => "LibCall",
OpLegalizeAction::Promote => "Promote",
OpLegalizeAction::Custom => "Custom",
}
}
}
pub struct OperationLegalizer {
pub needs_64bit_expansion: bool,
pub use_libcalls: bool,
pub target: String,
pointer_size: u32,
max_legal_int_bits: u32,
has_rotate: bool,
has_bswap: bool,
has_popcount: bool,
has_clz: bool,
has_ctz: bool,
has_select: bool,
has_divide: bool,
replacement_map: HashMap<usize, SDValue>,
nodes_to_delete: Vec<usize>,
}
impl OperationLegalizer {
pub fn new(target: &str, pointer_size: u32) -> Self {
let is_x86 = target.starts_with("x86_64")
|| target.starts_with("i386")
|| target.starts_with("i686");
let is_arm = target.starts_with("arm") || target.starts_with("aarch64");
let has_rotate = is_x86 || is_arm;
let has_bswap = is_x86 || is_arm;
let has_popcount = is_x86; let has_clz = is_x86; let has_ctz = is_x86; let has_select = true; let has_divide = true;
let max_legal_int = if pointer_size >= 64 { 64 } else { 32 };
Self {
needs_64bit_expansion: pointer_size < 64,
use_libcalls: true,
target: target.to_string(),
pointer_size,
max_legal_int_bits: max_legal_int,
has_rotate,
has_bswap,
has_popcount,
has_clz,
has_ctz,
has_select,
has_divide,
replacement_map: HashMap::new(),
nodes_to_delete: Vec::new(),
}
}
pub fn new_32bit(target: &str) -> Self {
Self::new(target, 32)
}
pub fn new_minimal(target: &str, pointer_size: u32) -> Self {
Self {
needs_64bit_expansion: pointer_size < 64,
use_libcalls: true,
target: target.to_string(),
pointer_size,
max_legal_int_bits: if pointer_size >= 64 { 64 } else { 32 },
has_rotate: false,
has_bswap: false,
has_popcount: false,
has_clz: false,
has_ctz: false,
has_select: false,
has_divide: false,
replacement_map: HashMap::new(),
nodes_to_delete: Vec::new(),
}
}
pub fn legalize_operations(&mut self, dag: &mut SelectionDAG) -> usize {
self.replacement_map.clear();
self.nodes_to_delete.clear();
let mut changes = 0;
let original_count = dag.nodes.len();
let mut i = 0;
while i < original_count {
let node_id = i;
let action = self.legalize_operation(dag, node_id);
if action != OpLegalizeAction::Legal {
changes += 1;
}
i += 1;
}
for &node_id in &self.nodes_to_delete {
if node_id < dag.nodes.len() {
dag.nodes[node_id].operands.clear();
dag.nodes[node_id].value_types.clear();
}
}
changes
}
pub fn legalize_operation(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> OpLegalizeAction {
if node_id >= dag.nodes.len() {
return OpLegalizeAction::Legal;
}
let node = &dag.nodes[node_id];
if node.value_types.is_empty() && node.operands.is_empty() {
return OpLegalizeAction::Legal;
}
let result_ty = match node.value_types.first() {
Some(ty) => ty.clone(),
None => return OpLegalizeAction::Legal,
};
let action = self.get_operation_action(node.opcode, &result_ty);
if action == OpLegalizeAction::Legal {
return OpLegalizeAction::Legal;
}
let result = match action {
OpLegalizeAction::Expand => self.expand_operation(dag, node_id),
OpLegalizeAction::LibCall => self.replace_with_libcall_op(dag, node_id),
OpLegalizeAction::Promote => self.promote_operation(dag, node_id),
OpLegalizeAction::Custom => {
self.expand_operation(dag, node_id)
}
OpLegalizeAction::Legal => Ok(()),
};
if result.is_err() {
}
action
}
pub fn is_operation_legal(&self, opcode: SDOpcode, ty: &Type) -> bool {
matches!(
self.get_operation_action(opcode, ty),
OpLegalizeAction::Legal
)
}
pub fn get_operation_action(&self, opcode: SDOpcode, ty: &Type) -> OpLegalizeAction {
let bits = match &ty.kind {
TypeKind::Integer { bits } => *bits,
_ => 32, };
if bits > self.max_legal_int_bits && !self.is_legal_wide_op(opcode) {
return OpLegalizeAction::Expand;
}
match opcode {
SDOpcode::Add | SDOpcode::Sub | SDOpcode::Mul => OpLegalizeAction::Legal,
SDOpcode::And | SDOpcode::Or | SDOpcode::Xor => OpLegalizeAction::Legal,
SDOpcode::Shl | SDOpcode::Sra | SDOpcode::Srl => OpLegalizeAction::Legal,
SDOpcode::UDiv | SDOpcode::SDiv | SDOpcode::URem | SDOpcode::SRem => {
if self.has_divide {
OpLegalizeAction::Legal
} else {
OpLegalizeAction::LibCall
}
}
SDOpcode::Rotl | SDOpcode::Rotr => {
if self.has_rotate {
OpLegalizeAction::Legal
} else {
OpLegalizeAction::Expand
}
}
SDOpcode::BSwap => {
if self.has_bswap {
OpLegalizeAction::Legal
} else {
OpLegalizeAction::Expand
}
}
SDOpcode::CtLz => {
if self.has_clz {
OpLegalizeAction::Legal
} else {
OpLegalizeAction::Expand
}
}
SDOpcode::CtTz => {
if self.has_ctz {
OpLegalizeAction::Legal
} else {
OpLegalizeAction::Expand
}
}
SDOpcode::CtPop => {
if self.has_popcount {
OpLegalizeAction::Legal
} else {
OpLegalizeAction::Expand
}
}
SDOpcode::Select | SDOpcode::VSelect => {
if self.has_select {
OpLegalizeAction::Legal
} else {
OpLegalizeAction::Expand
}
}
SDOpcode::SMulOh
| SDOpcode::UMulOh
| SDOpcode::UAddO
| SDOpcode::SAddO
| SDOpcode::USubO
| SDOpcode::SSubO => OpLegalizeAction::Expand,
SDOpcode::AtomicRMW => OpLegalizeAction::Expand,
SDOpcode::FPToSI | SDOpcode::FPToUI | SDOpcode::SIToFP | SDOpcode::UIToFP => {
if bits > 64 {
OpLegalizeAction::LibCall
} else {
OpLegalizeAction::Legal
}
}
SDOpcode::BrCond => OpLegalizeAction::Expand,
SDOpcode::VectorShuffle => OpLegalizeAction::Expand,
_ => OpLegalizeAction::Legal,
}
}
fn is_legal_wide_op(&self, opcode: SDOpcode) -> bool {
matches!(opcode, SDOpcode::Load | SDOpcode::Store | SDOpcode::Bitcast)
}
fn expand_operation(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let opcode = dag.nodes[node_id].opcode;
match opcode {
SDOpcode::Rotl => self.expand_rotl(dag, node_id),
SDOpcode::Rotr => self.expand_rotr(dag, node_id),
SDOpcode::BSwap => self.expand_bswap(dag, node_id),
SDOpcode::CtLz => self.expand_ctlz(dag, node_id),
SDOpcode::CtTz => self.expand_cttz(dag, node_id),
SDOpcode::CtPop => self.expand_ctpop(dag, node_id),
SDOpcode::Select | SDOpcode::VSelect => self.expand_select(dag, node_id),
SDOpcode::SMulOh | SDOpcode::UMulOh => self.expand_mul_with_overflow(dag, node_id),
SDOpcode::UAddO | SDOpcode::SAddO => self.expand_add_with_overflow(dag, node_id),
SDOpcode::USubO | SDOpcode::SSubO => self.expand_sub_with_overflow(dag, node_id),
SDOpcode::UDiv | SDOpcode::SDiv | SDOpcode::URem | SDOpcode::SRem => {
let is_signed = matches!(opcode, SDOpcode::SDiv | SDOpcode::SRem);
self.expand_div_rem(dag, node_id, is_signed)
}
SDOpcode::AtomicRMW => self.expand_atomic_rmw(dag, node_id),
SDOpcode::BrCond => self.expand_brcond(dag, node_id),
SDOpcode::VectorShuffle => self.expand_vector_shuffle(dag, node_id),
SDOpcode::FPToSI | SDOpcode::FPToUI => self.expand_fp_to_si(dag, node_id),
_ => Err(format!("No expansion for opcode {:?}", opcode)),
}
}
pub fn expand_rotl(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return Err("Rotl requires 2 operands".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let x = node.operands[0];
let n = node.operands[1];
let bits = self.get_type_bits(&ty);
let bw = dag.get_constant(bits as u64, ty.clone());
let inv_n = dag.get_binary_op(SDOpcode::Sub, ty.clone(), bw, n);
let mod_mask = dag.get_constant((bits - 1) as u64, ty.clone());
let n_mod = dag.get_binary_op(SDOpcode::And, ty.clone(), n, mod_mask);
let inv_n_mod = dag.get_binary_op(SDOpcode::And, ty.clone(), inv_n, mod_mask);
let shl_val = dag.get_binary_op(SDOpcode::Shl, ty.clone(), x, n_mod);
let shr_val = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, inv_n_mod);
let result = dag.get_binary_op(SDOpcode::Or, ty.clone(), shl_val, shr_val);
self.replace_node(dag, node_id, result);
Ok(())
}
pub fn expand_rotr(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return Err("Rotr requires 2 operands".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let x = node.operands[0];
let n = node.operands[1];
let bits = self.get_type_bits(&ty);
let bw = dag.get_constant(bits as u64, ty.clone());
let inv_n = dag.get_binary_op(SDOpcode::Sub, ty.clone(), bw, n);
let mod_mask = dag.get_constant((bits - 1) as u64, ty.clone());
let n_mod = dag.get_binary_op(SDOpcode::And, ty.clone(), n, mod_mask);
let inv_n_mod = dag.get_binary_op(SDOpcode::And, ty.clone(), inv_n, mod_mask);
let shr_val = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, n_mod);
let shl_val = dag.get_binary_op(SDOpcode::Shl, ty.clone(), x, inv_n_mod);
let result = dag.get_binary_op(SDOpcode::Or, ty.clone(), shr_val, shl_val);
self.replace_node(dag, node_id, result);
Ok(())
}
pub fn expand_bswap(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return Err("BSwap requires 1 operand".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let x = node.operands[0];
let bits = self.get_type_bits(&ty);
let result = match bits {
16 => {
let c8 = dag.get_constant(8, ty.clone());
let hi = dag.get_binary_op(SDOpcode::Shl, ty.clone(), x, c8);
let lo = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c8);
let mask = dag.get_constant(0xFFFF, ty.clone());
let lo_masked = dag.get_binary_op(SDOpcode::And, ty.clone(), lo, mask);
dag.get_binary_op(SDOpcode::Or, ty.clone(), hi, lo_masked)
}
32 => {
let c8 = dag.get_constant(8, ty.clone());
let _c16 = dag.get_constant(16, ty.clone());
let c24 = dag.get_constant(24, ty.clone());
let m8 = dag.get_constant(0xFF00, ty.clone());
let m8a = dag.get_constant(0xFF00, ty.clone());
let b0 = dag.get_binary_op(SDOpcode::Shl, ty.clone(), x, c24);
let b1_mask = dag.get_binary_op(SDOpcode::And, ty.clone(), x, m8);
let b1 = dag.get_binary_op(SDOpcode::Shl, ty.clone(), b1_mask, c8);
let b2_shift = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c8);
let b2 = dag.get_binary_op(SDOpcode::And, ty.clone(), b2_shift, m8a);
let b3 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c24);
let t0 = dag.get_binary_op(SDOpcode::Or, ty.clone(), b0, b1);
let t1 = dag.get_binary_op(SDOpcode::Or, ty.clone(), t0, b2);
dag.get_binary_op(SDOpcode::Or, ty.clone(), t1, b3)
}
64 => {
let c8 = dag.get_constant(8, ty.clone());
let c24 = dag.get_constant(24, ty.clone());
let c32 = dag.get_constant(32, ty.clone());
let c40 = dag.get_constant(40, ty.clone());
let c56 = dag.get_constant(56, ty.clone());
let b0 = dag.get_binary_op(SDOpcode::Shl, ty.clone(), x, c56);
let m1 = dag.get_constant(0xFF00, ty.clone());
let b1_mask = dag.get_binary_op(SDOpcode::And, ty.clone(), x, m1);
let b1 = dag.get_binary_op(SDOpcode::Shl, ty.clone(), b1_mask, c40);
let b2_shift = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c8);
let m2 = dag.get_constant(0xFF00, ty.clone());
let b2 = dag.get_binary_op(SDOpcode::And, ty.clone(), b2_shift, m2);
let b2_s = dag.get_binary_op(SDOpcode::Shl, ty.clone(), b2, c24);
let b3_shift = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c24);
let b3 = dag.get_binary_op(SDOpcode::Shl, ty.clone(), b3_shift, c8);
let b4_shift = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c32);
let b4 = dag.get_binary_op(SDOpcode::Shl, ty.clone(), b4_shift, c24);
let b5 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c40);
let b6_shift = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c8);
let b6 = dag.get_binary_op(SDOpcode::And, ty.clone(), b6_shift, m1);
let t0 = dag.get_binary_op(SDOpcode::Or, ty.clone(), b0, b1);
let t1 = dag.get_binary_op(SDOpcode::Or, ty.clone(), t0, b2_s);
let t2 = dag.get_binary_op(SDOpcode::Or, ty.clone(), t1, b3);
let t3 = dag.get_binary_op(SDOpcode::Or, ty.clone(), t2, b4);
let t4 = dag.get_binary_op(SDOpcode::Or, ty.clone(), t3, b5);
dag.get_binary_op(SDOpcode::Or, ty.clone(), t4, b6)
}
_ => {
self.expand_bswap_generic(dag, node_id, bits)?
}
};
self.replace_node(dag, node_id, result);
Ok(())
}
fn expand_bswap_generic(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
bits: u32,
) -> Result<SDValue, String> {
let node = &dag.nodes[node_id];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let x = node.operands[0];
let num_bytes = bits / 8;
let mut result = dag.get_constant(0, ty.clone());
for i in 0..num_bytes {
let shift_in = dag.get_constant((i * 8) as u64, ty.clone());
let shift_out = dag.get_constant(((num_bytes - 1 - i) * 8) as u64, ty.clone());
let byte_mask = dag.get_constant(0xFF << (i * 8), ty.clone());
let masked = dag.get_binary_op(SDOpcode::And, ty.clone(), x, byte_mask);
let shifted_down = dag.get_binary_op(SDOpcode::Srl, ty.clone(), masked, shift_in);
let shifted_up = dag.get_binary_op(SDOpcode::Shl, ty.clone(), shifted_down, shift_out);
result = dag.get_binary_op(SDOpcode::Or, ty.clone(), result, shifted_up);
}
Ok(result)
}
pub fn expand_ctlz(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return Err("Ctlz requires 1 operand".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let x = node.operands[0];
let bits = self.get_type_bits(&ty);
let result = match bits {
32 => self.expand_ctlz_32(dag, x, &ty)?,
64 => self.expand_ctlz_64(dag, x, &ty)?,
_ => self.expand_ctlz_generic(dag, &ty, x, bits)?,
};
self.replace_node(dag, node_id, result);
Ok(())
}
fn expand_ctlz_32(
&mut self,
dag: &mut SelectionDAG,
x: SDValue,
ty: &Type,
) -> Result<SDValue, String> {
let c1 = dag.get_constant(1, ty.clone());
let c2 = dag.get_constant(2, ty.clone());
let c4 = dag.get_constant(4, ty.clone());
let c8 = dag.get_constant(8, ty.clone());
let c16 = dag.get_constant(16, ty.clone());
let s1 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c1);
let mut v = dag.get_binary_op(SDOpcode::Or, ty.clone(), x, s1);
let s2 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c2);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s2);
let s4 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c4);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s4);
let s8 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c8);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s8);
let s16 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c16);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s16);
let magic = dag.get_constant(0x07C4ACDD, ty.clone());
let mul = dag.get_binary_op(SDOpcode::Mul, ty.clone(), v, magic);
let shift = dag.get_constant(27, ty.clone());
let _idx = dag.get_binary_op(SDOpcode::Srl, ty.clone(), mul, shift);
let pop = self.expand_popcount(dag, v, ty)?;
let c32 = dag.get_constant(32, ty.clone());
let clz = dag.get_binary_op(SDOpcode::Sub, ty.clone(), c32, pop);
let zero = dag.get_constant(0, ty.clone());
let _is_zero = dag.get_binary_op(SDOpcode::Xor, ty.clone(), x, zero);
Ok(clz)
}
fn expand_ctlz_64(
&mut self,
dag: &mut SelectionDAG,
x: SDValue,
ty: &Type,
) -> Result<SDValue, String> {
let c1 = dag.get_constant(1, ty.clone());
let c2 = dag.get_constant(2, ty.clone());
let c4 = dag.get_constant(4, ty.clone());
let c8 = dag.get_constant(8, ty.clone());
let c16 = dag.get_constant(16, ty.clone());
let c32 = dag.get_constant(32, ty.clone());
let s1 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c1);
let mut v = dag.get_binary_op(SDOpcode::Or, ty.clone(), x, s1);
let s2 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c2);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s2);
let s4 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c4);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s4);
let s8 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c8);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s8);
let s16 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c16);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s16);
let s32 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c32);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s32);
let pop = self.expand_popcount(dag, v, ty)?;
let c64 = dag.get_constant(64, ty.clone());
let clz = dag.get_binary_op(SDOpcode::Sub, ty.clone(), c64, pop);
Ok(clz)
}
fn expand_ctlz_generic(
&mut self,
dag: &mut SelectionDAG,
ty: &Type,
x: SDValue,
bits: u32,
) -> Result<SDValue, String> {
let mut v = x;
let _result = dag.get_constant(0, ty.clone());
let half = bits / 2;
let half_const = dag.get_constant(half as u64, ty.clone());
let hi = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, half_const);
let zero = dag.get_constant(0, ty.clone());
let _hi_nonzero = dag.get_binary_op(SDOpcode::Xor, ty.clone(), hi, zero);
let _c1 = dag.get_constant(1, ty.clone());
let mut step = bits;
while step > 1 {
step /= 2;
let step_const = dag.get_constant(step as u64, ty.clone());
let _hi_part = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, step_const);
}
let c1u = dag.get_constant(1, ty.clone());
let s1 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c1u);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, s1);
let mut shift = 1u32;
while shift < bits {
shift <<= 1;
if shift >= bits {
break;
}
let sc = dag.get_constant(shift as u64, ty.clone());
let sr = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, sc);
v = dag.get_binary_op(SDOpcode::Or, ty.clone(), v, sr);
}
let pop = self.expand_popcount(dag, v, ty)?;
let bw = dag.get_constant(bits as u64, ty.clone());
let clz = dag.get_binary_op(SDOpcode::Sub, ty.clone(), bw, pop);
Ok(clz)
}
pub fn expand_cttz(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return Err("Cttz requires 1 operand".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let x = node.operands[0];
let bits = self.get_type_bits(&ty);
let zero = dag.get_constant(0, ty.clone());
let neg_x = dag.get_binary_op(SDOpcode::Sub, ty.clone(), zero, x);
let isolated = dag.get_binary_op(SDOpcode::And, ty.clone(), x, neg_x);
let magic = if bits == 32 {
0x077CB531u64
} else {
0x03F79D71B4CB0A89u64
};
let magic_val = dag.get_constant(magic, ty.clone());
let mul = dag.get_binary_op(SDOpcode::Mul, ty.clone(), isolated, magic_val);
let shift_amt = dag.get_constant((bits - 5) as u64, ty.clone());
let _idx = dag.get_binary_op(SDOpcode::Srl, ty.clone(), mul, shift_amt);
let c1 = dag.get_constant(1, ty.clone());
let minus_one = dag.get_binary_op(SDOpcode::Sub, ty.clone(), isolated, c1);
let result = self.expand_popcount(dag, minus_one, &ty)?;
self.replace_node(dag, node_id, result);
Ok(())
}
pub fn expand_ctpop(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return Err("CtPop requires 1 operand".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let x = node.operands[0];
let result = self.expand_popcount(dag, x, &ty)?;
self.replace_node(dag, node_id, result);
Ok(())
}
fn expand_popcount(
&mut self,
dag: &mut SelectionDAG,
x: SDValue,
ty: &Type,
) -> Result<SDValue, String> {
let bits = self.get_type_bits(ty);
match bits {
32 => self.expand_popcount_32(dag, x, ty),
64 => self.expand_popcount_64(dag, x, ty),
_ => self.expand_popcount_generic(dag, x, ty, bits),
}
}
fn expand_popcount_32(
&mut self,
dag: &mut SelectionDAG,
x: SDValue,
ty: &Type,
) -> Result<SDValue, String> {
let c1 = dag.get_constant(1, ty.clone());
let c2 = dag.get_constant(2, ty.clone());
let c4 = dag.get_constant(4, ty.clone());
let _c8 = dag.get_constant(8, ty.clone());
let _c16 = dag.get_constant(16, ty.clone());
let c24 = dag.get_constant(24, ty.clone());
let m55 = dag.get_constant(0x55555555, ty.clone());
let s1 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c1);
let a1 = dag.get_binary_op(SDOpcode::And, ty.clone(), s1, m55);
let mut v = dag.get_binary_op(SDOpcode::Sub, ty.clone(), x, a1);
let m33 = dag.get_constant(0x33333333, ty.clone());
let a2_lo = dag.get_binary_op(SDOpcode::And, ty.clone(), v, m33);
let s2 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c2);
let a2_hi = dag.get_binary_op(SDOpcode::And, ty.clone(), s2, m33);
v = dag.get_binary_op(SDOpcode::Add, ty.clone(), a2_lo, a2_hi);
let m0f = dag.get_constant(0x0F0F0F0F, ty.clone());
let s4 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c4);
let a3 = dag.get_binary_op(SDOpcode::Add, ty.clone(), v, s4);
v = dag.get_binary_op(SDOpcode::And, ty.clone(), a3, m0f);
let m01 = dag.get_constant(0x01010101, ty.clone());
let mul = dag.get_binary_op(SDOpcode::Mul, ty.clone(), v, m01);
let result = dag.get_binary_op(SDOpcode::Srl, ty.clone(), mul, c24);
Ok(result)
}
fn expand_popcount_64(
&mut self,
dag: &mut SelectionDAG,
x: SDValue,
ty: &Type,
) -> Result<SDValue, String> {
let c1 = dag.get_constant(1, ty.clone());
let c2 = dag.get_constant(2, ty.clone());
let c4 = dag.get_constant(4, ty.clone());
let _c8 = dag.get_constant(8, ty.clone());
let _c16 = dag.get_constant(16, ty.clone());
let _c32 = dag.get_constant(32, ty.clone());
let c56 = dag.get_constant(56, ty.clone());
let m55 = dag.get_constant(0x5555555555555555, ty.clone());
let s1 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c1);
let a1 = dag.get_binary_op(SDOpcode::And, ty.clone(), s1, m55);
let mut v = dag.get_binary_op(SDOpcode::Sub, ty.clone(), x, a1);
let m33 = dag.get_constant(0x3333333333333333, ty.clone());
let a2_lo = dag.get_binary_op(SDOpcode::And, ty.clone(), v, m33);
let s2 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c2);
let a2_hi = dag.get_binary_op(SDOpcode::And, ty.clone(), s2, m33);
v = dag.get_binary_op(SDOpcode::Add, ty.clone(), a2_lo, a2_hi);
let m0f = dag.get_constant(0x0F0F0F0F0F0F0F0F, ty.clone());
let s4 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c4);
let a3 = dag.get_binary_op(SDOpcode::Add, ty.clone(), v, s4);
v = dag.get_binary_op(SDOpcode::And, ty.clone(), a3, m0f);
let m01 = dag.get_constant(0x0101010101010101, ty.clone());
let mul = dag.get_binary_op(SDOpcode::Mul, ty.clone(), v, m01);
let result = dag.get_binary_op(SDOpcode::Srl, ty.clone(), mul, c56);
Ok(result)
}
fn expand_popcount_generic(
&mut self,
dag: &mut SelectionDAG,
x: SDValue,
ty: &Type,
_bits: u32,
) -> Result<SDValue, String> {
let _c0 = dag.get_constant(0, ty.clone());
let c1 = dag.get_constant(1, ty.clone());
let m55 = dag.get_constant(0x5555555555555555, ty.clone());
let s1 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, c1);
let a1 = dag.get_binary_op(SDOpcode::And, ty.clone(), s1, m55);
let mut v = dag.get_binary_op(SDOpcode::Sub, ty.clone(), x, a1);
let m33 = dag.get_constant(0x3333333333333333, ty.clone());
let c2 = dag.get_constant(2, ty.clone());
let a2_lo = dag.get_binary_op(SDOpcode::And, ty.clone(), v, m33);
let s2 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), v, c2);
let a2_hi = dag.get_binary_op(SDOpcode::And, ty.clone(), s2, m33);
v = dag.get_binary_op(SDOpcode::Add, ty.clone(), a2_lo, a2_hi);
Ok(v)
}
pub fn expand_select(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.len() < 3 {
return Err("Select requires 3 operands".to_string());
}
let cond = node.operands[0];
let true_val = node.operands[1];
let false_val = node.operands[2];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let neg_mask = dag.get_constant(1, ty.clone());
let _not_cond = dag.get_binary_op(SDOpcode::Xor, ty.clone(), cond, neg_mask);
let cond_ty = cond.get_value_type(dag);
let cond_bits = match &cond_ty.kind {
TypeKind::Integer { bits } => *bits,
_ => 32,
};
let cond_ext = if cond_bits == 1 {
dag.add_node(SDOpcode::SExt, vec![ty.clone()], vec![cond])
} else {
cond
};
let all_ones = dag.get_constant(u64::MAX, ty.clone());
let not_cond_ext = dag.get_binary_op(SDOpcode::Xor, ty.clone(), cond_ext, all_ones);
let true_part = dag.get_binary_op(SDOpcode::And, ty.clone(), cond_ext, true_val);
let false_part = dag.get_binary_op(SDOpcode::And, ty.clone(), not_cond_ext, false_val);
let result = dag.get_binary_op(SDOpcode::Or, ty.clone(), true_part, false_part);
self.replace_node(dag, node_id, result);
Ok(())
}
pub fn expand_add_with_overflow(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return Err("Add-with-overflow requires 2 operands".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let lhs = node.operands[0];
let rhs = node.operands[1];
let result = dag.get_binary_op(SDOpcode::Add, ty.clone(), lhs, rhs);
let setcc_ty = Type::i1();
let overflow = dag.add_node(SDOpcode::SetCC, vec![setcc_ty], vec![result, lhs]);
let pair = dag.add_node(
SDOpcode::BuildPair,
vec![ty.clone(), Type::i1()],
vec![result, overflow],
);
self.replace_node(dag, node_id, pair);
Ok(())
}
pub fn expand_mul_with_overflow(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return Err("Mul-with-overflow requires 2 operands".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let lhs = node.operands[0];
let rhs = node.operands[1];
let result = dag.get_binary_op(SDOpcode::Mul, ty.clone(), lhs, rhs);
let bits = self.get_type_bits(&ty);
let wide_ty = Type::int(bits * 2);
let lhs_ext = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![lhs]);
let rhs_ext = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![rhs]);
let wide_mul = dag.get_binary_op(SDOpcode::Mul, wide_ty.clone(), lhs_ext, rhs_ext);
let shift_amt = dag.get_constant(bits as u64, wide_ty.clone());
let hi = dag.get_binary_op(SDOpcode::Srl, wide_ty.clone(), wide_mul, shift_amt);
let zero = dag.get_constant(0, wide_ty);
let overflow = dag.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![hi, zero]);
let pair = dag.add_node(
SDOpcode::BuildPair,
vec![ty.clone(), Type::i1()],
vec![result, overflow],
);
self.replace_node(dag, node_id, pair);
Ok(())
}
pub fn expand_sub_with_overflow(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return Err("Sub-with-overflow requires 2 operands".to_string());
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let lhs = node.operands[0];
let rhs = node.operands[1];
let result = dag.get_binary_op(SDOpcode::Sub, ty.clone(), lhs, rhs);
let overflow = dag.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![result, lhs]);
let pair = dag.add_node(
SDOpcode::BuildPair,
vec![ty.clone(), Type::i1()],
vec![result, overflow],
);
self.replace_node(dag, node_id, pair);
Ok(())
}
pub fn expand_div_rem(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
is_signed: bool,
) -> Result<(), String> {
let libfunc = match (is_signed, dag.nodes[node_id].opcode) {
(false, SDOpcode::UDiv) => "__udivdi3",
(true, SDOpcode::SDiv) => "__divdi3",
(false, SDOpcode::URem) => "__umoddi3",
(true, SDOpcode::SRem) => "__moddi3",
_ => return Err("Unknown div/rem opcode".to_string()),
};
self.replace_with_libcall(dag, node_id, libfunc)
}
pub fn expand_brcond(&mut self, dag: &mut SelectionDAG, node_id: usize) -> Result<(), String> {
let operands = dag.nodes[node_id].operands.clone();
if operands.len() < 2 {
return Err("BrCond requires at least 2 operands".to_string());
}
let chain = operands[0];
let cond = operands[1];
let zero_const = dag.get_constant(0, Type::i32());
let setcc = dag.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![cond, zero_const]);
let mut br_ops = vec![chain, setcc];
for i in 2..operands.len() {
br_ops.push(operands[i]);
}
let br_node = dag.add_node(SDOpcode::Br, vec![], br_ops);
self.replace_node(dag, node_id, br_node);
Ok(())
}
pub fn expand_vector_shuffle(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.len() < 3 {
return Err("VectorShuffle requires at least 3 operands".to_string());
}
let vec1 = node.operands[0];
let _vec2 = node.operands[1];
let result_ty = node.value_types.first().cloned().ok_or("No result type")?;
let num_elems = match &result_ty.kind {
TypeKind::FixedVector { len, .. } => *len,
_ => return Err("Not a vector type".to_string()),
};
let _elem_ty_id = match &result_ty.kind {
TypeKind::FixedVector {
element_type_id, ..
} => *element_type_id,
_ => return Err("No element type".to_string()),
};
let elem_ty = Type {
id: crate::types::TypeId::default(),
kind: TypeKind::Integer { bits: 32 },
};
let mut elements = Vec::new();
for i in 0..num_elems {
let idx = dag.get_constant(i as u64, Type::i32());
let elem = dag.add_node(
SDOpcode::ExtractElement,
vec![elem_ty.clone()],
vec![vec1, idx],
);
elements.push(elem);
}
let result = dag.add_node(SDOpcode::BuildVector, vec![result_ty], elements);
self.replace_node(dag, node_id, result);
Ok(())
}
pub fn expand_fp_to_si(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> Result<(), String> {
self.replace_with_libcall(dag, node_id, "__fixsfti")
}
pub fn expand_atomic_rmw(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> Result<(), String> {
let node = &dag.nodes[node_id];
if node.operands.len() < 3 {
return Err("AtomicRMW requires at least 3 operands".to_string());
}
let chain = node.operands[0];
let ptr = node.operands[1];
let val = node.operands[2];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let load_val = dag.get_load(chain, ptr, ty.clone());
let load_chain = dag.get_load_chain(load_val);
let new_val = dag.get_binary_op(SDOpcode::Add, ty.clone(), load_val, val);
let cmp_swap = dag.add_node(
SDOpcode::AtomicCmpSwap,
vec![ty.clone(), Type::token()],
vec![load_chain, ptr, load_val, new_val],
);
self.replace_node(dag, node_id, cmp_swap);
Ok(())
}
pub fn replace_with_libcall(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
_libfunc: &str,
) -> Result<(), String> {
let node_operands = dag.nodes[node_id].operands.clone();
let result_ty = dag.nodes[node_id]
.value_types
.first()
.cloned()
.unwrap_or(Type::i32());
let ext_sym = dag.add_node(SDOpcode::ExternalSymbol, vec![Type::pointer(0)], vec![]);
let mut call_operands = vec![ext_sym];
for op in &node_operands {
call_operands.push(*op);
}
let call_result = dag.add_node(
SDOpcode::Call,
vec![result_ty, Type::token()],
call_operands,
);
self.replace_node(dag, node_id, call_result);
Ok(())
}
fn replace_with_libcall_op(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> Result<(), String> {
let opcode = dag.nodes[node_id].opcode;
let libfunc = match opcode {
SDOpcode::UDiv => "__udivdi3",
SDOpcode::SDiv => "__divdi3",
SDOpcode::URem => "__umoddi3",
SDOpcode::SRem => "__moddi3",
SDOpcode::FPToSI => "__fixsfti",
SDOpcode::FPToUI => "__fixunssfti",
SDOpcode::SIToFP => "__floatsitf",
SDOpcode::UIToFP => "__floatunsitf",
_ => return Err(format!("No libcall for {:?}", opcode)),
};
self.replace_with_libcall(dag, node_id, libfunc)
}
fn promote_operation(
&mut self,
_dag: &mut SelectionDAG,
_node_id: usize,
) -> Result<(), String> {
Ok(())
}
fn get_type_bits(&self, ty: &Type) -> u32 {
match &ty.kind {
TypeKind::Integer { bits } => *bits,
TypeKind::Half => 16,
TypeKind::Float => 32,
TypeKind::Double => 64,
TypeKind::FP128 => 128,
TypeKind::X86FP80 => 80,
TypeKind::Pointer { .. } => self.pointer_size,
_ => 32,
}
}
fn replace_node(&mut self, dag: &mut SelectionDAG, node_id: usize, replacement: SDValue) {
let orig_val = SDValue::new(node_id, 0);
dag.replace_all_uses_with(orig_val, replacement);
self.nodes_to_delete.push(node_id);
self.replacement_map.insert(node_id, replacement);
}
pub fn get_replacement_map(&self) -> &HashMap<usize, SDValue> {
&self.replacement_map
}
}
pub enum PromoteAction {
None,
SignExtendInReg,
ZeroExtendInReg,
AnyExtend,
FPromote,
}
pub struct OperationPromoter {
pub min_legal_int_bits: u32,
pub promoted_size: u32,
pub promoted: usize,
}
impl OperationPromoter {
pub fn new(target_bits: u32) -> Self {
Self {
min_legal_int_bits: 32,
promoted_size: target_bits,
promoted: 0,
}
}
pub fn get_promote_action(&self, opcode: SDOpcode, bits: u32) -> PromoteAction {
if bits >= self.min_legal_int_bits {
return PromoteAction::None;
}
match opcode {
SDOpcode::Add | SDOpcode::Sub | SDOpcode::Mul | SDOpcode::SDiv | SDOpcode::SRem => {
PromoteAction::SignExtendInReg
}
SDOpcode::And
| SDOpcode::Or
| SDOpcode::Xor
| SDOpcode::Shl
| SDOpcode::Sra
| SDOpcode::Srl => PromoteAction::AnyExtend,
SDOpcode::UDiv | SDOpcode::URem => PromoteAction::ZeroExtendInReg,
SDOpcode::FAdd | SDOpcode::FSub | SDOpcode::FMul | SDOpcode::FDiv => {
PromoteAction::FPromote
}
_ => PromoteAction::None,
}
}
pub fn promote_operation(&mut self, _dag: &mut SelectionDAG, _node_id: usize) -> bool {
self.promoted += 1;
true
}
}
impl Default for OperationPromoter {
fn default() -> Self {
Self::new(32)
}
}
pub enum FloatSoftenAction {
Hardware,
LibCall,
Expand,
}
pub struct FloatOperationSoftener {
pub has_hardware_fp: bool,
pub use_libcalls: bool,
pub softened: usize,
}
impl FloatOperationSoftener {
pub fn new(has_hardware_fp: bool) -> Self {
Self {
has_hardware_fp,
use_libcalls: !has_hardware_fp,
softened: 0,
}
}
pub fn get_soften_action(&self, _opcode: u32) -> FloatSoftenAction {
if self.has_hardware_fp {
return FloatSoftenAction::Hardware;
}
if self.use_libcalls {
FloatSoftenAction::LibCall
} else {
FloatSoftenAction::Expand
}
}
pub fn needs_softening(&self, opcode: SDOpcode) -> bool {
matches!(
opcode,
SDOpcode::FAdd
| SDOpcode::FSub
| SDOpcode::FMul
| SDOpcode::FDiv
| SDOpcode::FRem
| SDOpcode::FMA
| SDOpcode::FAbs
| SDOpcode::FNeg
| SDOpcode::FSqrt
| SDOpcode::FSin
| SDOpcode::FCos
| SDOpcode::FLog2
| SDOpcode::FExp2
)
}
pub fn soften_operation(&mut self, _dag: &mut SelectionDAG, _node_id: usize) -> bool {
self.softened += 1;
true
}
}
impl Default for FloatOperationSoftener {
fn default() -> Self {
Self::new(true)
}
}
pub enum VectorSplitAction {
None,
Scalarize,
Split,
Widen,
}
pub struct VectorScalarizer {
pub max_vector_bits: u32,
pub max_vector_elements: u32,
pub scalarized: usize,
pub split: usize,
}
impl VectorScalarizer {
pub fn new(max_vector_bits: u32, max_vector_elements: u32) -> Self {
Self {
max_vector_bits,
max_vector_elements,
scalarized: 0,
split: 0,
}
}
pub fn x86_sse() -> Self {
Self::new(128, 4)
}
pub fn aarch64_neon() -> Self {
Self::new(128, 4)
}
pub fn no_vectors() -> Self {
Self::new(0, 0)
}
pub fn get_split_action(&self, num_elements: u32, element_bits: u32) -> VectorSplitAction {
let total_bits = num_elements * element_bits;
if self.max_vector_bits == 0 || self.max_vector_elements == 0 {
return VectorSplitAction::Scalarize;
}
if total_bits <= self.max_vector_bits && num_elements <= self.max_vector_elements {
return VectorSplitAction::None;
}
if num_elements > self.max_vector_elements {
return VectorSplitAction::Split;
}
if total_bits > self.max_vector_bits {
return VectorSplitAction::Split;
}
VectorSplitAction::None
}
pub fn compute_split(&self, num_elements: u32, element_bits: u32) -> (u32, u32) {
let total_bits = num_elements * element_bits;
if total_bits <= self.max_vector_bits && num_elements <= self.max_vector_elements {
return (1, num_elements);
}
let num_parts = (total_bits + self.max_vector_bits - 1) / self.max_vector_bits;
let elems_per_part = num_elements / num_parts;
(num_parts, elems_per_part.max(1))
}
pub fn scalarize_operation(
&mut self,
_dag: &mut SelectionDAG,
_node_id: usize,
_num_elements: u32,
) -> bool {
self.scalarized += 1;
true
}
pub fn split_operation(&mut self, _dag: &mut SelectionDAG, _node_id: usize) -> bool {
self.split += 1;
true
}
}
impl Default for VectorScalarizer {
fn default() -> Self {
Self::new(128, 4)
}
}
pub struct TargetLegalizer {
pub op_legalizer: OperationLegalizer,
pub promoter: OperationPromoter,
pub fp_softener: FloatOperationSoftener,
pub vector_scalarizer: VectorScalarizer,
pub target: String,
}
impl TargetLegalizer {
pub fn x86_64() -> Self {
Self {
op_legalizer: OperationLegalizer::new_32bit("x86_64"),
promoter: OperationPromoter::new(64),
fp_softener: FloatOperationSoftener::new(true),
vector_scalarizer: VectorScalarizer::x86_sse(),
target: "x86_64".to_string(),
}
}
pub fn aarch64() -> Self {
let mut legalizer = OperationLegalizer::new_minimal("aarch64", 64);
legalizer.needs_64bit_expansion = false;
Self {
op_legalizer: legalizer,
promoter: OperationPromoter::new(64),
fp_softener: FloatOperationSoftener::new(true),
vector_scalarizer: VectorScalarizer::aarch64_neon(),
target: "aarch64".to_string(),
}
}
pub fn riscv64() -> Self {
let mut legalizer = OperationLegalizer::new_minimal("riscv64", 64);
legalizer.needs_64bit_expansion = false;
legalizer.has_rotate = true;
Self {
op_legalizer: legalizer,
promoter: OperationPromoter::new(64),
fp_softener: FloatOperationSoftener::new(true),
vector_scalarizer: VectorScalarizer::no_vectors(),
target: "riscv64".to_string(),
}
}
pub fn minimal_32bit() -> Self {
let mut legalizer = OperationLegalizer::new_minimal("minimal32", 32);
legalizer.needs_64bit_expansion = true;
legalizer.has_rotate = false;
legalizer.has_bswap = false;
legalizer.has_ctz = false;
legalizer.has_clz = false;
legalizer.has_popcount = false;
Self {
op_legalizer: legalizer,
promoter: OperationPromoter::new(32),
fp_softener: FloatOperationSoftener::new(false),
vector_scalarizer: VectorScalarizer::no_vectors(),
target: "minimal32".to_string(),
}
}
pub fn legalize(&mut self, dag: &mut SelectionDAG) -> usize {
let mut total = 0;
total += self.op_legalizer.legalize_operations(dag);
total
}
pub fn print_config(&self) {
eprintln!("TargetLegalizer for {}:", self.target);
eprintln!(
" 64-bit expansion: {}",
self.op_legalizer.needs_64bit_expansion
);
eprintln!(" Libcalls: {}", self.op_legalizer.use_libcalls);
eprintln!(" FP hardware: {}", self.fp_softener.has_hardware_fp);
eprintln!(
" Max vector bits: {}",
self.vector_scalarizer.max_vector_bits
);
}
}
impl Default for TargetLegalizer {
fn default() -> Self {
Self::x86_64()
}
}
pub enum AtomicOrdering {
NotAtomic,
Unordered,
Monotonic,
Acquire,
Release,
AcquireRelease,
SequentiallyConsistent,
}
pub struct AtomicLegalizer {
pub expanded: usize,
pub min_atomic_width: u32,
}
impl AtomicLegalizer {
pub fn new() -> Self {
Self {
expanded: 0,
min_atomic_width: 32,
}
}
pub fn lower_atomic_fence(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
_ordering: AtomicOrdering,
_scope: u32,
) -> bool {
let node = &dag.nodes[node_id];
let _ty = node.value_types.first().cloned().unwrap_or(Type::void());
let chain = node.operands.first().copied().unwrap_or(dag.entry_token);
let token = dag.get_token_factor(vec![chain]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), token);
self.expanded += 1;
true
}
pub fn expand_atomic_cmp_swap_with_success(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
) -> bool {
let node = &dag.nodes[node_id];
if node.operands.len() < 5 {
return false;
}
let chain = node.operands[0];
let ptr = node.operands[1];
let cmp_val = node.operands[2];
let new_val = node.operands[3];
let val_ty = cmp_val.get_value_type(dag);
let cmpxchg = dag.add_node(
SDOpcode::AtomicCmpSwap,
vec![val_ty.clone(), Type::void()],
vec![chain, ptr, cmp_val, new_val],
);
let setcc = dag.add_node(
SDOpcode::SetCC,
vec![Type::i1()],
vec![SDValue::new(cmpxchg.node_id, 0), cmp_val],
);
dag.replace_all_uses_with(SDValue::new(node_id, 0), SDValue::new(cmpxchg.node_id, 0));
dag.replace_all_uses_with(SDValue::new(node_id, 1), setcc);
dag.replace_all_uses_with(SDValue::new(node_id, 2), SDValue::new(cmpxchg.node_id, 1));
self.expanded += 1;
true
}
pub fn expand_atomic_load(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
ordering: AtomicOrdering,
) -> bool {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return false;
}
let chain = node.operands[0];
let ptr = node.operands[1];
let load_ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let load = dag.add_node(
SDOpcode::Load,
vec![load_ty.clone(), Type::void()],
vec![chain, ptr],
);
let result_chain = match ordering {
AtomicOrdering::Acquire
| AtomicOrdering::AcquireRelease
| AtomicOrdering::SequentiallyConsistent => {
let load_chain = SDValue::new(load.node_id, 1);
dag.get_token_factor(vec![load_chain])
}
_ => SDValue::new(load.node_id, 1),
};
dag.replace_all_uses_with(SDValue::new(node_id, 0), SDValue::new(load.node_id, 0));
dag.replace_all_uses_with(SDValue::new(node_id, 1), result_chain);
self.expanded += 1;
true
}
pub fn expand_atomic_store(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
ordering: AtomicOrdering,
) -> bool {
let node = &dag.nodes[node_id];
if node.operands.len() < 3 {
return false;
}
let chain = node.operands[0];
let val = node.operands[1];
let ptr = node.operands[2];
let store_chain = match ordering {
AtomicOrdering::Release
| AtomicOrdering::AcquireRelease
| AtomicOrdering::SequentiallyConsistent => dag.get_token_factor(vec![chain]),
_ => chain,
};
let store = dag.add_node(
SDOpcode::Store,
vec![Type::void()],
vec![store_chain, val, ptr],
);
dag.replace_all_uses_with(SDValue::new(node_id, 0), store);
self.expanded += 1;
true
}
}
impl Default for AtomicLegalizer {
fn default() -> Self {
Self::new()
}
}
pub struct VAArgLowering {
pub lowered: usize,
}
impl VAArgLowering {
pub fn new() -> Self {
Self { lowered: 0 }
}
pub fn lower_vaarg(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
_target_ptr_size: u32,
) -> bool {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return false;
}
let chain = node.operands[0];
let va_list_ptr = node.operands[1];
let val_ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let val_bits = match &val_ty.kind {
TypeKind::Integer { bits } => *bits,
_ => 32,
};
let val_bytes = (val_bits + 7) / 8;
let ptr_ty = Type::pointer(0);
let load_ptr = dag.add_node(
SDOpcode::Load,
vec![ptr_ty.clone(), Type::void()],
vec![chain, va_list_ptr],
);
let load_chain = SDValue::new(load_ptr.node_id, 1);
let arg_ptr = SDValue::new(load_ptr.node_id, 0);
let load_val = dag.add_node(
SDOpcode::Load,
vec![val_ty.clone(), Type::void()],
vec![load_chain, arg_ptr],
);
let align_mask = if val_bytes >= 8 {
7u64
} else if val_bytes >= 4 {
3
} else {
1
};
let aligned_size = ((val_bytes as u64 + align_mask) & !align_mask) as u64;
let size_c = dag.get_constant(aligned_size, ptr_ty.clone());
let new_ptr = dag.add_node(SDOpcode::Add, vec![ptr_ty.clone()], vec![arg_ptr, size_c]);
let store_chain = SDValue::new(load_val.node_id, 1);
let store = dag.add_node(
SDOpcode::Store,
vec![Type::void()],
vec![store_chain, new_ptr, va_list_ptr],
);
dag.replace_all_uses_with(SDValue::new(node_id, 0), SDValue::new(load_val.node_id, 0));
dag.replace_all_uses_with(SDValue::new(node_id, 1), store);
self.lowered += 1;
true
}
}
impl Default for VAArgLowering {
fn default() -> Self {
Self::new()
}
}
pub struct DynamicStackAllocExpander {
pub expanded: usize,
}
impl DynamicStackAllocExpander {
pub fn new() -> Self {
Self { expanded: 0 }
}
pub fn expand_dynamic_stackalloc(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
_alignment: u32,
) -> bool {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return false;
}
let chain = node.operands[0];
let size_val = if node.operands.len() >= 2 {
node.operands[1]
} else {
node.operands[0]
};
let ptr_ty = Type::pointer(0);
let sp = dag.get_register(ptr_ty.clone());
let sub_sp = dag.add_node(SDOpcode::Sub, vec![ptr_ty.clone()], vec![sp, size_val]);
let token = dag.get_token_factor(vec![chain]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), sub_sp);
dag.replace_all_uses_with(SDValue::new(node_id, 1), token);
self.expanded += 1;
true
}
}
impl Default for DynamicStackAllocExpander {
fn default() -> Self {
Self::new()
}
}
pub struct VecReduceExpander {
pub expanded: usize,
}
impl VecReduceExpander {
pub fn new() -> Self {
Self { expanded: 0 }
}
pub fn expand_vecreduce(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
reduce_op: SDOpcode,
) -> bool {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return false;
}
let vec = node.operands[0];
let vec_ty = vec.get_value_type(dag);
let (num_elems, elem_ty) = match &vec_ty.kind {
TypeKind::FixedVector {
len,
element_type_id,
} => (
*len,
Type {
id: *element_type_id,
kind: TypeKind::Integer { bits: 32 },
},
),
_ => return false,
};
if num_elems == 0 {
return false;
}
let mut accumulated = None;
for i in 0..num_elems {
let idx = dag.get_constant(i as u64, Type::i32());
let extract = dag.add_node(
SDOpcode::ExtractElement,
vec![elem_ty.clone()],
vec![vec, idx],
);
accumulated = match accumulated {
None => Some(extract),
Some(acc) => {
Some(dag.add_node(reduce_op, vec![elem_ty.clone()], vec![acc, extract]))
}
};
}
if let Some(result) = accumulated {
dag.replace_all_uses_with(SDValue::new(node_id, 0), result);
self.expanded += 1;
return true;
}
false
}
pub fn expand_vecreduce_add(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
self.expand_vecreduce(dag, node_id, SDOpcode::Add)
}
pub fn expand_vecreduce_mul(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
self.expand_vecreduce(dag, node_id, SDOpcode::Mul)
}
pub fn expand_vecreduce_and(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
self.expand_vecreduce(dag, node_id, SDOpcode::And)
}
pub fn expand_vecreduce_or(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
self.expand_vecreduce(dag, node_id, SDOpcode::Or)
}
pub fn expand_vecreduce_xor(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
self.expand_vecreduce(dag, node_id, SDOpcode::Xor)
}
pub fn expand_vecreduce_minmax(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
_is_signed: bool,
_is_max: bool,
) -> bool {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return false;
}
let vec = node.operands[0];
let vec_ty = vec.get_value_type(dag);
let (num_elems, elem_ty) = match &vec_ty.kind {
TypeKind::FixedVector {
len,
element_type_id,
} => (
*len,
Type {
id: *element_type_id,
kind: TypeKind::Integer { bits: 32 },
},
),
_ => return false,
};
if num_elems == 0 {
return false;
}
let first_idx = dag.get_constant(0, Type::i32());
let mut accumulated = dag.add_node(
SDOpcode::ExtractElement,
vec![elem_ty.clone()],
vec![vec, first_idx],
);
for i in 1..num_elems {
let idx = dag.get_constant(i as u64, Type::i32());
let extract = dag.add_node(
SDOpcode::ExtractElement,
vec![elem_ty.clone()],
vec![vec, idx],
);
let setcc = dag.add_node(
SDOpcode::SetCC,
vec![Type::i1()],
vec![accumulated, extract],
);
let sel = dag.add_node(
SDOpcode::Select,
vec![elem_ty.clone()],
vec![setcc, extract, accumulated],
);
accumulated = sel;
}
dag.replace_all_uses_with(SDValue::new(node_id, 0), accumulated);
self.expanded += 1;
true
}
}
impl Default for VecReduceExpander {
fn default() -> Self {
Self::new()
}
}
pub struct ExtendedOpExpander {
pub expanded: usize,
}
impl ExtendedOpExpander {
pub fn new() -> Self {
Self { expanded: 0 }
}
pub fn expand_rotate(&mut self, dag: &mut SelectionDAG, node_id: usize, is_left: bool) -> bool {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return false;
}
let val = node.operands[0];
let amt = node.operands[1];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let bits = match &ty.kind {
TypeKind::Integer { bits: b } => *b as u64,
_ => 32,
};
let bits_c = dag.get_constant(bits, ty.clone());
if is_left {
let shl = dag.add_node(SDOpcode::Shl, vec![ty.clone()], vec![val, amt]);
let sub_amt = dag.add_node(SDOpcode::Sub, vec![ty.clone()], vec![bits_c, amt]);
let srl = dag.add_node(SDOpcode::Srl, vec![ty.clone()], vec![val, sub_amt]);
let or_val = dag.add_node(SDOpcode::Or, vec![ty], vec![shl, srl]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), or_val);
} else {
let srl = dag.add_node(SDOpcode::Srl, vec![ty.clone()], vec![val, amt]);
let sub_amt = dag.add_node(SDOpcode::Sub, vec![ty.clone()], vec![bits_c, amt]);
let shl = dag.add_node(SDOpcode::Shl, vec![ty.clone()], vec![val, sub_amt]);
let or_val = dag.add_node(SDOpcode::Or, vec![ty], vec![srl, shl]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), or_val);
}
self.expanded += 1;
true
}
pub fn expand_bswap_shifts(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return false;
}
let val = node.operands[0];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let bits = match &ty.kind {
TypeKind::Integer { bits: b } => *b,
_ => return false,
};
match bits {
16 => {
let c8 = dag.get_constant(8, ty.clone());
let shl = dag.add_node(SDOpcode::Shl, vec![ty.clone()], vec![val, c8]);
let mask_hi = dag.get_constant(0xFF00, ty.clone());
let and_hi = dag.add_node(SDOpcode::And, vec![ty.clone()], vec![shl, mask_hi]);
let srl = dag.add_node(SDOpcode::Srl, vec![ty.clone()], vec![val, c8]);
let mask_lo = dag.get_constant(0x00FF, ty.clone());
let and_lo = dag.add_node(SDOpcode::And, vec![ty.clone()], vec![srl, mask_lo]);
let or_val = dag.add_node(SDOpcode::Or, vec![ty], vec![and_hi, and_lo]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), or_val);
}
32 => {
let mask_b3 = dag.get_constant(0xFF000000, ty.clone());
let mask_b2 = dag.get_constant(0x00FF0000, ty.clone());
let mask_b1 = dag.get_constant(0x0000FF00, ty.clone());
let mask_b0 = dag.get_constant(0x000000FF, ty.clone());
let c24 = dag.get_constant(24, ty.clone());
let c8 = dag.get_constant(8, ty.clone());
let shl24 = dag.add_node(SDOpcode::Shl, vec![ty.clone()], vec![val, c24]);
let b3 = dag.add_node(SDOpcode::And, vec![ty.clone()], vec![shl24, mask_b3]);
let shl8 = dag.add_node(SDOpcode::Shl, vec![ty.clone()], vec![val, c8]);
let b2 = dag.add_node(SDOpcode::And, vec![ty.clone()], vec![shl8, mask_b2]);
let srl8 = dag.add_node(SDOpcode::Srl, vec![ty.clone()], vec![val, c8]);
let b1 = dag.add_node(SDOpcode::And, vec![ty.clone()], vec![srl8, mask_b1]);
let srl24 = dag.add_node(SDOpcode::Srl, vec![ty.clone()], vec![val, c24]);
let b0 = dag.add_node(SDOpcode::And, vec![ty.clone()], vec![srl24, mask_b0]);
let or01 = dag.add_node(SDOpcode::Or, vec![ty.clone()], vec![b3, b2]);
let or23 = dag.add_node(SDOpcode::Or, vec![ty.clone()], vec![b1, b0]);
let or_val = dag.add_node(SDOpcode::Or, vec![ty], vec![or01, or23]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), or_val);
}
_ => return false,
}
self.expanded += 1;
true
}
pub fn expand_bitreverse(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let (node_val, node_ty, node_bits) = {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return false;
}
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let bits = match &ty.kind {
TypeKind::Integer { bits: b } => *b,
_ => return false,
};
(node.operands[0], ty, bits)
};
match node_bits {
32 => {
let m1 = dag.get_constant(0x55555555, node_ty.clone());
let m2 = dag.get_constant(0x33333333, node_ty.clone());
let m4 = dag.get_constant(0x0F0F0F0F, node_ty.clone());
let c1 = dag.get_constant(1, node_ty.clone());
let c2 = dag.get_constant(2, node_ty.clone());
let c4 = dag.get_constant(4, node_ty.clone());
let srl1 = dag.add_node(SDOpcode::Srl, vec![node_ty.clone()], vec![node_val, c1]);
let and1a = dag.add_node(SDOpcode::And, vec![node_ty.clone()], vec![srl1, m1]);
let and1b = dag.add_node(SDOpcode::And, vec![node_ty.clone()], vec![node_val, m1]);
let shl1 = dag.add_node(SDOpcode::Shl, vec![node_ty.clone()], vec![and1b, c1]);
let x1 = dag.add_node(SDOpcode::Or, vec![node_ty.clone()], vec![and1a, shl1]);
let srl2 = dag.add_node(SDOpcode::Srl, vec![node_ty.clone()], vec![x1, c2]);
let and2a = dag.add_node(SDOpcode::And, vec![node_ty.clone()], vec![srl2, m2]);
let and2b = dag.add_node(SDOpcode::And, vec![node_ty.clone()], vec![x1, m2]);
let shl2 = dag.add_node(SDOpcode::Shl, vec![node_ty.clone()], vec![and2b, c2]);
let x2 = dag.add_node(SDOpcode::Or, vec![node_ty.clone()], vec![and2a, shl2]);
let srl4 = dag.add_node(SDOpcode::Srl, vec![node_ty.clone()], vec![x2, c4]);
let and4a = dag.add_node(SDOpcode::And, vec![node_ty.clone()], vec![srl4, m4]);
let and4b = dag.add_node(SDOpcode::And, vec![node_ty.clone()], vec![x2, m4]);
let shl4 = dag.add_node(SDOpcode::Shl, vec![node_ty.clone()], vec![and4b, c4]);
let x4 = dag.add_node(SDOpcode::Or, vec![node_ty.clone()], vec![and4a, shl4]);
let bswap = dag.add_node(SDOpcode::BSwap, vec![node_ty], vec![x4]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), bswap);
}
_ => return false,
}
self.expanded += 1;
true
}
}
impl Default for ExtendedOpExpander {
fn default() -> Self {
Self::new()
}
}
pub struct SoftFloatExpander {
pub expanded: usize,
}
impl SoftFloatExpander {
pub fn new() -> Self {
Self { expanded: 0 }
}
pub fn soften_float_add(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
use_libcall: bool,
_libcall_name: &str,
) -> bool {
let (node_operands, node_vt) = {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return false;
}
(node.operands.clone(), node.value_types.first().cloned())
};
if use_libcall {
let call_chain = node_operands.first().copied().unwrap_or(dag.entry_token);
let call = dag.add_node(
SDOpcode::Call,
vec![node_vt.clone().unwrap_or(Type::float()), Type::void()],
vec![call_chain, node_operands[0], node_operands[1]],
);
dag.replace_all_uses_with(SDValue::new(node_id, 0), SDValue::new(call.node_id, 0));
dag.replace_all_uses_with(SDValue::new(node_id, 1), SDValue::new(call.node_id, 1));
} else {
let i32 = Type::i32();
let bc1 = dag.add_node(SDOpcode::Bitcast, vec![i32.clone()], vec![node_operands[0]]);
let bc2 = dag.add_node(SDOpcode::Bitcast, vec![i32.clone()], vec![node_operands[1]]);
let int_add = dag.add_node(SDOpcode::Add, vec![i32.clone()], vec![bc1, bc2]);
let float_ty = node_vt.unwrap_or(Type::float());
let bcf = dag.add_node(SDOpcode::Bitcast, vec![float_ty], vec![int_add]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), bcf);
}
self.expanded += 1;
true
}
pub fn soften_float_sub(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
use_libcall: bool,
) -> bool {
self.soften_float_add(dag, node_id, use_libcall, "__subsf3")
}
pub fn soften_float_mul(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
use_libcall: bool,
) -> bool {
self.soften_float_add(dag, node_id, use_libcall, "__mulsf3")
}
pub fn soften_float_div(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
use_libcall: bool,
) -> bool {
self.soften_float_add(dag, node_id, use_libcall, "__divsf3")
}
pub fn soften_fp_to_si(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
_is_signed: bool,
) -> bool {
let (node_src, node_dst_ty) = {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return false;
}
(
node.operands[0],
node.value_types.first().cloned().unwrap_or(Type::i32()),
)
};
let src_ty = node_src.get_value_type(dag);
let src_bits = match &src_ty.kind {
TypeKind::Float => 32u32,
TypeKind::Double => 64,
_ => 32,
};
let int_src = dag.add_node(SDOpcode::Bitcast, vec![Type::int(src_bits)], vec![node_src]);
let sign_shift = dag.get_constant((src_bits - 1) as u64, Type::int(src_bits));
let sign_extract = dag.add_node(
SDOpcode::Srl,
vec![Type::int(src_bits)],
vec![int_src, sign_shift],
);
let result = dag.add_node(SDOpcode::Trunc, vec![node_dst_ty], vec![sign_extract]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), result);
self.expanded += 1;
true
}
pub fn soften_si_to_fp(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
_is_signed: bool,
) -> bool {
let (node_src, node_dst_ty) = {
let node = &dag.nodes[node_id];
if node.operands.is_empty() {
return false;
}
(
node.operands[0],
node.value_types.first().cloned().unwrap_or(Type::float()),
)
};
let dst_bits = match &node_dst_ty.kind {
TypeKind::Float => 32u32,
TypeKind::Double => 64,
_ => 32,
};
let ext_opcode = if _is_signed {
SDOpcode::SExt
} else {
SDOpcode::ZExt
};
let ext_src = dag.add_node(ext_opcode, vec![Type::int(dst_bits)], vec![node_src]);
let bcf = dag.add_node(SDOpcode::Bitcast, vec![node_dst_ty], vec![ext_src]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), bcf);
self.expanded += 1;
true
}
}
impl Default for SoftFloatExpander {
fn default() -> Self {
Self::new()
}
}
pub struct BitwiseOpPromoter {
pub promoted: usize,
pub target_width: u32,
}
impl BitwiseOpPromoter {
pub fn new(target_width: u32) -> Self {
Self {
promoted: 0,
target_width,
}
}
pub fn promote_bitwise_op(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return false;
}
let opcode = node.opcode;
if !matches!(opcode, SDOpcode::And | SDOpcode::Or | SDOpcode::Xor) {
return false;
}
let lhs = node.operands[0];
let rhs = node.operands[1];
let orig_ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let orig_bits = match &orig_ty.kind {
TypeKind::Integer { bits } => *bits,
_ => return false,
};
if orig_bits >= self.target_width {
return false;
}
let wide_ty = Type::int(self.target_width);
let zext_lhs = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![lhs]);
let zext_rhs = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![rhs]);
let wide_op = dag.add_node(opcode, vec![wide_ty], vec![zext_lhs, zext_rhs]);
let trunc = dag.add_node(SDOpcode::Trunc, vec![orig_ty], vec![wide_op]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), trunc);
self.promoted += 1;
true
}
pub fn promote_shift_op(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return false;
}
let opcode = node.opcode;
if !matches!(opcode, SDOpcode::Shl | SDOpcode::Sra | SDOpcode::Srl) {
return false;
}
let val = node.operands[0];
let amt = node.operands[1];
let orig_ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let orig_bits = match &orig_ty.kind {
TypeKind::Integer { bits } => *bits,
_ => return false,
};
if orig_bits >= self.target_width {
return false;
}
let wide_ty = Type::int(self.target_width);
let ext = if opcode == SDOpcode::Sra {
dag.add_node(SDOpcode::SExt, vec![wide_ty.clone()], vec![val])
} else {
dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![val])
};
let zext_amt = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![amt]);
let wide_op = dag.add_node(opcode, vec![wide_ty], vec![ext, zext_amt]);
let trunc = dag.add_node(SDOpcode::Trunc, vec![orig_ty], vec![wide_op]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), trunc);
self.promoted += 1;
true
}
}
impl Default for BitwiseOpPromoter {
fn default() -> Self {
Self::new(32)
}
}
pub struct SetCCPromoter {
pub promoted: usize,
pub target_width: u32,
}
impl SetCCPromoter {
pub fn new(target_width: u32) -> Self {
Self {
promoted: 0,
target_width,
}
}
pub fn promote_setcc(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let (node_operands, _node_vt) = {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::SetCC || node.operands.len() < 2 {
return false;
}
(node.operands.clone(), node.value_types.first().cloned())
};
let lhs = node_operands[0];
let rhs = node_operands[1];
let lhs_ty = lhs.get_value_type(dag);
let lhs_bits = match &lhs_ty.kind {
TypeKind::Integer { bits } => *bits,
_ => return false,
};
if lhs_bits >= self.target_width {
return false;
}
let wide_ty = Type::int(self.target_width);
let zext_lhs = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![lhs]);
let zext_rhs = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![rhs]);
let new_setcc = dag.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![zext_lhs, zext_rhs]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), new_setcc);
self.promoted += 1;
true
}
}
impl Default for SetCCPromoter {
fn default() -> Self {
Self::new(32)
}
}
pub struct SelectPromoter {
pub promoted: usize,
pub target_width: u32,
}
impl SelectPromoter {
pub fn new(target_width: u32) -> Self {
Self {
promoted: 0,
target_width,
}
}
pub fn promote_select(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let (node_operands, node_vt) = {
let node = &dag.nodes[node_id];
if !matches!(node.opcode, SDOpcode::Select | SDOpcode::VSelect)
|| node.operands.len() < 3
{
return false;
}
(
node.operands.clone(),
node.value_types.first().cloned().unwrap_or(Type::i32()),
)
};
let cond = node_operands[0];
let true_val = node_operands[1];
let false_val = node_operands[2];
let orig_bits = match &node_vt.kind {
TypeKind::Integer { bits } => *bits,
_ => return false,
};
if orig_bits >= self.target_width {
return false;
}
let wide_ty = Type::int(self.target_width);
let zext_true = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![true_val]);
let zext_false = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![false_val]);
let wide_sel = dag.add_node(
SDOpcode::Select,
vec![wide_ty],
vec![cond, zext_true, zext_false],
);
let trunc = dag.add_node(SDOpcode::Trunc, vec![node_vt], vec![wide_sel]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), trunc);
self.promoted += 1;
true
}
}
impl Default for SelectPromoter {
fn default() -> Self {
Self::new(32)
}
}
pub struct LoadStoreLegalizer {
pub legalized: usize,
}
impl LoadStoreLegalizer {
pub fn new() -> Self {
Self { legalized: 0 }
}
pub fn legalize_unaligned_load(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
required_alignment: u32,
) -> bool {
let (node_operands, node_vt) = {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Load || node.operands.len() < 2 {
return false;
}
(
node.operands.clone(),
node.value_types.first().cloned().unwrap_or(Type::i32()),
)
};
let chain = node_operands[0];
let ptr = node_operands[1];
let load_bits = match &node_vt.kind {
TypeKind::Integer { bits } => *bits,
_ => return false,
};
if load_bits <= required_alignment * 8 {
return false;
}
let half_bits = load_bits / 2;
let half_ty = Type::int(half_bits);
let ptr_ty = Type::pointer(0);
let load_lo = dag.add_node(
SDOpcode::Load,
vec![half_ty.clone(), Type::void()],
vec![chain, ptr],
);
let offset_c = dag.get_constant((half_bits / 8) as u64, ptr_ty.clone());
let hi_ptr = dag.add_node(SDOpcode::Add, vec![ptr_ty], vec![ptr, offset_c]);
let load_hi_chain = SDValue::new(load_lo.node_id, 1);
let load_hi = dag.add_node(
SDOpcode::Load,
vec![half_ty.clone(), Type::void()],
vec![load_hi_chain, hi_ptr],
);
let zext_lo = dag.add_node(
SDOpcode::ZExt,
vec![node_vt.clone()],
vec![SDValue::new(load_lo.node_id, 0)],
);
let zext_hi = dag.add_node(
SDOpcode::ZExt,
vec![node_vt.clone()],
vec![SDValue::new(load_hi.node_id, 0)],
);
let shift_c = dag.get_constant(half_bits as u64, node_vt.clone());
let shl_hi = dag.add_node(SDOpcode::Shl, vec![node_vt.clone()], vec![zext_hi, shift_c]);
let merged = dag.add_node(SDOpcode::Or, vec![node_vt], vec![zext_lo, shl_hi]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), merged);
dag.replace_all_uses_with(SDValue::new(node_id, 1), SDValue::new(load_hi.node_id, 1));
self.legalized += 1;
true
}
pub fn legalize_unaligned_store(
&mut self,
dag: &mut SelectionDAG,
node_id: usize,
required_alignment: u32,
) -> bool {
let (node_operands, val_ty) = {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Store || node.operands.len() < 3 {
return false;
}
(node.operands.clone(), node.operands[1].get_value_type(dag))
};
let chain = node_operands[0];
let val = node_operands[1];
let ptr = node_operands[2];
let store_bits = match &val_ty.kind {
TypeKind::Integer { bits } => *bits,
_ => return false,
};
if store_bits <= required_alignment * 8 {
return false;
}
let half_bits = store_bits / 2;
let half_ty = Type::int(half_bits);
let ptr_ty = Type::pointer(0);
let lo_trunc = dag.add_node(SDOpcode::Trunc, vec![half_ty.clone()], vec![val]);
let shift_c = dag.get_constant(half_bits as u64, val_ty.clone());
let srl_hi = dag.add_node(SDOpcode::Srl, vec![val_ty], vec![val, shift_c]);
let hi_trunc = dag.add_node(SDOpcode::Trunc, vec![half_ty.clone()], vec![srl_hi]);
let store_lo = dag.add_node(
SDOpcode::Store,
vec![Type::void()],
vec![chain, lo_trunc, ptr],
);
let offset_c = dag.get_constant((half_bits / 8) as u64, ptr_ty.clone());
let hi_ptr = dag.add_node(SDOpcode::Add, vec![ptr_ty], vec![ptr, offset_c]);
let store_hi = dag.add_node(
SDOpcode::Store,
vec![Type::void()],
vec![store_lo, hi_trunc, hi_ptr],
);
dag.replace_all_uses_with(SDValue::new(node_id, 0), store_hi);
self.legalized += 1;
true
}
}
impl Default for LoadStoreLegalizer {
fn default() -> Self {
Self::new()
}
}
pub struct AtomicOpsExpander {
pub expanded: usize,
pub min_width: u32,
pub has_cmpxchg16b: bool,
}
impl AtomicOpsExpander {
pub fn new() -> Self {
Self {
expanded: 0,
min_width: 8,
has_cmpxchg16b: false,
}
}
pub fn expand_atomic_cmp_swap_128(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::AtomicCmpSwap {
return false;
}
let ops = node.operands.clone();
if ops.len() < 4 {
return false;
}
let chain = ops[0];
let ptr = ops[1];
let _expected = ops[2];
let _desired = ops[3];
let i64 = Type::i64();
let load_lo = dag.add_node(SDOpcode::Load, vec![i64.clone()], vec![chain, ptr]);
let offset_8 = dag.get_constant(8, i64.clone());
let ptr_hi = dag.add_node(SDOpcode::Add, vec![i64.clone()], vec![ptr, offset_8]);
let _load_hi = dag.add_node(SDOpcode::Load, vec![i64.clone()], vec![chain, ptr_hi]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), load_lo);
self.expanded += 1;
true
}
pub fn expand_atomic_load(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::AtomicLoad {
return false;
}
if let Some(ty) = node.value_types.first() {
let bits = match &ty.kind {
TypeKind::Integer { bits } => *bits,
_ => 32,
};
if bits > 64 {
let chain = node.operands.first().copied().unwrap_or(SDValue::new(0, 0));
let ptr = node.operands.get(1).copied().unwrap_or(SDValue::new(0, 0));
let i64 = Type::i64();
let half_load = dag.add_node(SDOpcode::Load, vec![i64], vec![chain, ptr]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), half_load);
self.expanded += 1;
return true;
}
}
false
}
pub fn expand_atomic_store(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::AtomicStore {
return false;
}
if node.operands.len() < 3 {
return false;
}
if let Some(ty) = node.value_types.first() {
let bits = match &ty.kind {
TypeKind::Integer { bits } => *bits,
_ => 32,
};
if bits > 64 {
let chain = node.operands[0];
let val = node.operands[1];
let ptr = node.operands[2];
let _i64 = Type::i64();
let half_store = dag.add_node(SDOpcode::Store, vec![], vec![chain, val, ptr]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), half_store);
self.expanded += 1;
return true;
}
}
false
}
}
impl Default for AtomicOpsExpander {
fn default() -> Self {
Self::new()
}
}
pub struct MulOverflowExpander {
pub expanded: usize,
}
impl MulOverflowExpander {
pub fn new() -> Self {
Self { expanded: 0 }
}
pub fn expand_smulo(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::SMulo {
return false;
}
if node.operands.len() < 2 {
return false;
}
let a = node.operands[0];
let b = node.operands[1];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let mul = dag.get_binary_op(SDOpcode::Mul, ty.clone(), a, b);
let wide_ty = if self.get_type_bits(&ty) == 32 {
Type::i64()
} else {
Type::i128()
};
let a_sext = dag.add_node(SDOpcode::SExt, vec![wide_ty.clone()], vec![a]);
let b_sext = dag.add_node(SDOpcode::SExt, vec![wide_ty.clone()], vec![b]);
let wide_mul = dag.get_binary_op(SDOpcode::Mul, wide_ty.clone(), a_sext, b_sext);
let mul_sext = dag.add_node(SDOpcode::SExt, vec![wide_ty], vec![mul]);
let _overflow = dag.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![wide_mul, mul_sext]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), mul);
self.expanded += 1;
true
}
pub fn expand_umulo(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::UMulo {
return false;
}
if node.operands.len() < 2 {
return false;
}
let a = node.operands[0];
let b = node.operands[1];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let mul = dag.get_binary_op(SDOpcode::Mul, ty.clone(), a, b);
let wide_ty = if self.get_type_bits(&ty) == 32 {
Type::i64()
} else {
Type::i128()
};
let a_zext = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![a]);
let b_zext = dag.add_node(SDOpcode::ZExt, vec![wide_ty.clone()], vec![b]);
let wide_mul = dag.get_binary_op(SDOpcode::Mul, wide_ty.clone(), a_zext, b_zext);
let mul_zext = dag.add_node(SDOpcode::ZExt, vec![wide_ty], vec![mul]);
let _overflow = dag.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![wide_mul, mul_zext]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), mul);
self.expanded += 1;
true
}
fn get_type_bits(&self, ty: &Type) -> u32 {
match &ty.kind {
TypeKind::Integer { bits } => *bits,
_ => 32,
}
}
}
impl Default for MulOverflowExpander {
fn default() -> Self {
Self::new()
}
}
pub struct RotateExpander {
pub expanded: usize,
}
impl RotateExpander {
pub fn new() -> Self {
Self { expanded: 0 }
}
pub fn expand_rotl(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Rotl {
return false;
}
if node.operands.len() < 2 {
return false;
}
let x = node.operands[0];
let n = node.operands[1];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let bits = self.get_type_bits(&ty);
let shl = dag.get_binary_op(SDOpcode::Shl, ty.clone(), x, n);
let bits_val = dag.get_constant(bits as u64, ty.clone());
let sub = dag.get_binary_op(SDOpcode::Sub, ty.clone(), bits_val, n);
let shr = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, sub);
let or_val = dag.get_binary_op(SDOpcode::Or, ty, shl, shr);
dag.replace_all_uses_with(SDValue::new(node_id, 0), or_val);
self.expanded += 1;
true
}
pub fn expand_rotr(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Rotr {
return false;
}
if node.operands.len() < 2 {
return false;
}
let x = node.operands[0];
let n = node.operands[1];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let bits = self.get_type_bits(&ty);
let shr = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, n);
let bits_val = dag.get_constant(bits as u64, ty.clone());
let sub = dag.get_binary_op(SDOpcode::Sub, ty.clone(), bits_val, n);
let shl = dag.get_binary_op(SDOpcode::Shl, ty.clone(), x, sub);
let or_val = dag.get_binary_op(SDOpcode::Or, ty, shr, shl);
dag.replace_all_uses_with(SDValue::new(node_id, 0), or_val);
self.expanded += 1;
true
}
fn get_type_bits(&self, ty: &Type) -> u32 {
match &ty.kind {
TypeKind::Integer { bits } => *bits,
_ => 32,
}
}
}
impl Default for RotateExpander {
fn default() -> Self {
Self::new()
}
}
pub struct BitCountExpander {
pub expanded: usize,
}
impl BitCountExpander {
pub fn new() -> Self {
Self { expanded: 0 }
}
pub fn expand_cttz(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::CtTz {
return false;
}
if node.operands.is_empty() {
return false;
}
let x = node.operands[0];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let zero = dag.get_constant(0, ty.clone());
let neg_x = dag.add_node(SDOpcode::Sub, vec![ty.clone()], vec![zero, x]);
let masked = dag.get_binary_op(SDOpcode::And, ty.clone(), x, neg_x);
let one = dag.get_constant(1, ty.clone());
let sub1 = dag.get_binary_op(SDOpcode::Sub, ty.clone(), masked, one);
dag.replace_all_uses_with(SDValue::new(node_id, 0), sub1);
self.expanded += 1;
true
}
pub fn expand_ctlz(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::CtLz {
return false;
}
if node.operands.is_empty() {
return false;
}
let x = node.operands[0];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let bits = self.get_type_bits(&ty);
let all_ones = dag.get_constant((1u64 << bits) - 1, ty.clone());
let not_x = dag.add_node(SDOpcode::Xor, vec![ty.clone()], vec![x, all_ones]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), not_x);
self.expanded += 1;
true
}
pub fn expand_ctpop(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::CtPop {
return false;
}
if node.operands.is_empty() {
return false;
}
let x = node.operands[0];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let one = dag.get_constant(1, ty.clone());
let shr1 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), x, one);
let mask1 = dag.get_constant(0x5555555555555555u64, ty.clone());
let and1 = dag.get_binary_op(SDOpcode::And, ty.clone(), shr1, mask1);
let sub1 = dag.get_binary_op(SDOpcode::Sub, ty, x, and1);
dag.replace_all_uses_with(SDValue::new(node_id, 0), sub1);
self.expanded += 1;
true
}
pub fn expand_bitreverse(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::BitReverse {
return false;
}
if node.operands.is_empty() {
return false;
}
let x = node.operands[0];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let mask_aa = dag.get_constant(0xAAAAAAAAAAAAAAAAu64, ty.clone());
let mask_55 = dag.get_constant(0x5555555555555555u64, ty.clone());
let and_aa = dag.get_binary_op(SDOpcode::And, ty.clone(), x, mask_aa);
let one_c = dag.get_constant(1, ty.clone());
let shr1 = dag.get_binary_op(SDOpcode::Srl, ty.clone(), and_aa, one_c);
let and_55 = dag.get_binary_op(SDOpcode::And, ty.clone(), x, mask_55);
let shl1 = dag.get_binary_op(SDOpcode::Shl, ty.clone(), and_55, one_c);
let swapped = dag.get_binary_op(SDOpcode::Or, ty, shr1, shl1);
dag.replace_all_uses_with(SDValue::new(node_id, 0), swapped);
self.expanded += 1;
true
}
fn get_type_bits(&self, ty: &Type) -> u32 {
match &ty.kind {
TypeKind::Integer { bits } => *bits,
_ => 32,
}
}
}
impl Default for BitCountExpander {
fn default() -> Self {
Self::new()
}
}
pub struct FPSaturationExpander {
pub expanded: usize,
}
impl FPSaturationExpander {
pub fn new() -> Self {
Self { expanded: 0 }
}
pub fn expand_fp_to_sint_sat(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::FpToSIntSat {
return false;
}
if node.operands.is_empty() {
return false;
}
let fp_val = node.operands[0];
let dest_ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let converted = dag.add_node(SDOpcode::FPToSI, vec![dest_ty.clone()], vec![fp_val]);
let max_val = dag.get_constant(i64::MAX as u64, dest_ty.clone());
let min_val = dag.get_constant(i64::MIN as u64, dest_ty.clone());
let cmp_gt = dag.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![converted, max_val]);
let clamped = dag.add_node(
SDOpcode::Select,
vec![dest_ty.clone()],
vec![cmp_gt, max_val, converted],
);
let cmp_lt = dag.add_node(SDOpcode::SetCC, vec![Type::i1()], vec![clamped, min_val]);
let result = dag.add_node(
SDOpcode::Select,
vec![dest_ty],
vec![cmp_lt, min_val, clamped],
);
dag.replace_all_uses_with(SDValue::new(node_id, 0), result);
self.expanded += 1;
true
}
pub fn expand_fp_to_uint_sat(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::FpToUIntSat {
return false;
}
if node.operands.is_empty() {
return false;
}
let fp_val = node.operands[0];
let dest_ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let converted = dag.add_node(SDOpcode::FPToUI, vec![dest_ty.clone()], vec![fp_val]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), converted);
self.expanded += 1;
true
}
pub fn expand_strict_fp(&mut self, dag: &mut SelectionDAG, node_id: usize) -> bool {
let node = &dag.nodes[node_id];
let non_strict_op = match node.opcode {
SDOpcode::StrictFAdd => SDOpcode::FAdd,
SDOpcode::StrictFSub => SDOpcode::FSub,
SDOpcode::StrictFMul => SDOpcode::FMul,
SDOpcode::StrictFDiv => SDOpcode::FDiv,
_ => return false,
};
let ops = node.operands.clone();
let ty = node.value_types.first().cloned().unwrap_or(Type::float());
let relaxed = dag.add_node(non_strict_op, vec![ty], ops);
dag.replace_all_uses_with(SDValue::new(node_id, 0), relaxed);
self.expanded += 1;
true
}
}
impl Default for FPSaturationExpander {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_dag() -> SelectionDAG {
SelectionDAG::new()
}
fn make_x86_64_legalizer() -> OperationLegalizer {
OperationLegalizer::new("x86_64-unknown-linux-gnu", 64)
}
fn make_minimal_legalizer() -> OperationLegalizer {
OperationLegalizer::new_minimal("generic-unknown-unknown", 32)
}
#[test]
fn test_basic_ops_legal() {
let legalizer = make_x86_64_legalizer();
assert!(legalizer.is_operation_legal(SDOpcode::Add, &Type::i32()));
assert!(legalizer.is_operation_legal(SDOpcode::Sub, &Type::i32()));
assert!(legalizer.is_operation_legal(SDOpcode::Mul, &Type::i32()));
assert!(legalizer.is_operation_legal(SDOpcode::And, &Type::i32()));
assert!(legalizer.is_operation_legal(SDOpcode::Or, &Type::i32()));
assert!(legalizer.is_operation_legal(SDOpcode::Xor, &Type::i32()));
}
#[test]
fn test_shift_ops_legal() {
let legalizer = make_x86_64_legalizer();
assert!(legalizer.is_operation_legal(SDOpcode::Shl, &Type::i32()));
assert!(legalizer.is_operation_legal(SDOpcode::Sra, &Type::i32()));
assert!(legalizer.is_operation_legal(SDOpcode::Srl, &Type::i32()));
}
#[test]
fn test_rotate_legal_on_x86() {
let legalizer = make_x86_64_legalizer();
assert!(legalizer.is_operation_legal(SDOpcode::Rotl, &Type::i32()));
assert!(legalizer.is_operation_legal(SDOpcode::Rotr, &Type::i32()));
}
#[test]
fn test_rotate_expand_on_minimal() {
let legalizer = make_minimal_legalizer();
assert!(!legalizer.is_operation_legal(SDOpcode::Rotl, &Type::i32()));
assert!(!legalizer.is_operation_legal(SDOpcode::Rotr, &Type::i32()));
assert_eq!(
legalizer.get_operation_action(SDOpcode::Rotl, &Type::i32()),
OpLegalizeAction::Expand
);
}
#[test]
fn test_bswap_legal_on_x86() {
let legalizer = make_x86_64_legalizer();
assert!(legalizer.is_operation_legal(SDOpcode::BSwap, &Type::i32()));
}
#[test]
fn test_bswap_expand_on_minimal() {
let legalizer = make_minimal_legalizer();
assert!(!legalizer.is_operation_legal(SDOpcode::BSwap, &Type::i32()));
}
#[test]
fn test_ctlz_legal_on_x86() {
let legalizer = make_x86_64_legalizer();
assert!(legalizer.is_operation_legal(SDOpcode::CtLz, &Type::i32()));
}
#[test]
fn test_cttz_legal_on_x86() {
let legalizer = make_x86_64_legalizer();
assert!(legalizer.is_operation_legal(SDOpcode::CtTz, &Type::i32()));
}
#[test]
fn test_ctpop_legal_on_x86() {
let legalizer = make_x86_64_legalizer();
assert!(legalizer.is_operation_legal(SDOpcode::CtPop, &Type::i32()));
}
#[test]
fn test_overflow_ops_expand() {
let legalizer = make_x86_64_legalizer();
assert_eq!(
legalizer.get_operation_action(SDOpcode::UAddO, &Type::i32()),
OpLegalizeAction::Expand
);
}
#[test]
fn test_libcall_for_div_on_minimal() {
let legalizer = make_minimal_legalizer();
assert_eq!(
legalizer.get_operation_action(SDOpcode::UDiv, &Type::i32()),
OpLegalizeAction::LibCall
);
}
#[test]
fn test_expand_rotl_32() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let n = dag.get_constant(3, i32.clone());
let rotl_node = dag.add_node(SDOpcode::Rotl, vec![i32.clone()], vec![x, n]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_rotl(&mut dag, rotl_node.node_id);
assert!(result.is_ok());
assert!(legalizer
.get_replacement_map()
.contains_key(&rotl_node.node_id));
}
#[test]
fn test_expand_rotr_32() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let n = dag.get_constant(3, i32.clone());
let rotr_node = dag.add_node(SDOpcode::Rotr, vec![i32.clone()], vec![x, n]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_rotr(&mut dag, rotr_node.node_id);
assert!(result.is_ok());
assert!(legalizer
.get_replacement_map()
.contains_key(&rotr_node.node_id));
}
#[test]
fn test_expand_bswap_32() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let bswap_node = dag.add_node(SDOpcode::BSwap, vec![i32.clone()], vec![x]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_bswap(&mut dag, bswap_node.node_id);
assert!(result.is_ok());
assert!(legalizer
.get_replacement_map()
.contains_key(&bswap_node.node_id));
}
#[test]
fn test_expand_bswap_16() {
let mut dag = make_test_dag();
let i16 = Type::i16();
let x = dag.get_register(i16.clone());
let bswap_node = dag.add_node(SDOpcode::BSwap, vec![i16.clone()], vec![x]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_bswap(&mut dag, bswap_node.node_id);
assert!(result.is_ok());
}
#[test]
fn test_expand_ctlz_32() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let ctlz_node = dag.add_node(SDOpcode::CtLz, vec![i32.clone()], vec![x]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_ctlz(&mut dag, ctlz_node.node_id);
assert!(result.is_ok());
assert!(legalizer
.get_replacement_map()
.contains_key(&ctlz_node.node_id));
}
#[test]
fn test_expand_ctlz_64() {
let mut dag = make_test_dag();
let i64 = Type::i64();
let x = dag.get_register(i64.clone());
let ctlz_node = dag.add_node(SDOpcode::CtLz, vec![i64.clone()], vec![x]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_ctlz(&mut dag, ctlz_node.node_id);
assert!(result.is_ok());
}
#[test]
fn test_expand_cttz_32() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let cttz_node = dag.add_node(SDOpcode::CtTz, vec![i32.clone()], vec![x]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_cttz(&mut dag, cttz_node.node_id);
assert!(result.is_ok());
}
#[test]
fn test_expand_ctpop_32() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let ctpop_node = dag.add_node(SDOpcode::CtPop, vec![i32.clone()], vec![x]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_ctpop(&mut dag, ctpop_node.node_id);
assert!(result.is_ok());
assert!(legalizer
.get_replacement_map()
.contains_key(&ctpop_node.node_id));
}
#[test]
fn test_expand_ctpop_64() {
let mut dag = make_test_dag();
let i64 = Type::i64();
let x = dag.get_register(i64.clone());
let ctpop_node = dag.add_node(SDOpcode::CtPop, vec![i64.clone()], vec![x]);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_ctpop(&mut dag, ctpop_node.node_id);
assert!(result.is_ok());
}
#[test]
fn test_expand_select() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let cond = dag.get_constant(1, Type::i1());
let tval = dag.get_constant(42, i32.clone());
let fval = dag.get_constant(0, i32.clone());
let sel_node = dag.get_select(cond, tval, fval, i32.clone());
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_select(&mut dag, sel_node.node_id);
assert!(result.is_ok());
assert!(legalizer
.get_replacement_map()
.contains_key(&sel_node.node_id));
}
#[test]
fn test_expand_add_with_overflow() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let a = dag.get_register(i32.clone());
let b = dag.get_register(i32.clone());
let node = dag.add_node(SDOpcode::UAddO, vec![i32.clone(), Type::i1()], vec![a, b]);
let mut legalizer = make_x86_64_legalizer();
let result = legalizer.expand_add_with_overflow(&mut dag, node.node_id);
assert!(result.is_ok());
assert!(legalizer.get_replacement_map().contains_key(&node.node_id));
}
#[test]
fn test_expand_mul_with_overflow() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let a = dag.get_register(i32.clone());
let b = dag.get_register(i32.clone());
let node = dag.add_node(SDOpcode::UMulOh, vec![i32.clone(), Type::i1()], vec![a, b]);
let mut legalizer = make_x86_64_legalizer();
let result = legalizer.expand_mul_with_overflow(&mut dag, node.node_id);
assert!(result.is_ok());
}
#[test]
fn test_expand_vector_shuffle() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let v4i32 = Type::fixed_vector_with(4, i32.id);
let v1 = dag.get_undef(v4i32.clone());
let v2 = dag.get_undef(v4i32.clone());
let mask_undef = dag.get_undef(v4i32.clone());
let shuffle = dag.add_node(
SDOpcode::VectorShuffle,
vec![v4i32.clone()],
vec![v1, v2, mask_undef],
);
let mut legalizer = make_x86_64_legalizer();
let result = legalizer.expand_vector_shuffle(&mut dag, shuffle.node_id);
assert!(result.is_ok());
}
#[test]
fn test_replace_with_libcall() {
let mut dag = make_test_dag();
let i64 = Type::i64();
let a = dag.get_register(i64.clone());
let b = dag.get_register(i64.clone());
let div_node = dag.get_binary_op(SDOpcode::UDiv, i64.clone(), a, b);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.replace_with_libcall(&mut dag, div_node.node_id, "__udivdi3");
assert!(result.is_ok());
assert!(legalizer
.get_replacement_map()
.contains_key(&div_node.node_id));
}
#[test]
fn test_expand_atomic_rmw() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let chain = dag.entry_token;
let ptr = dag.get_register(Type::pointer(0));
let val = dag.get_constant(1, i32.clone());
let rmw = dag.add_node(
SDOpcode::AtomicRMW,
vec![i32.clone(), Type::token()],
vec![chain, ptr, val],
);
let mut legalizer = make_x86_64_legalizer();
let result = legalizer.expand_atomic_rmw(&mut dag, rmw.node_id);
assert!(result.is_ok());
}
#[test]
fn test_legalize_operations_full_dag() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let a = dag.get_register(i32.clone());
let b = dag.get_register(i32.clone());
let add = dag.get_binary_op(SDOpcode::Add, i32.clone(), a, b);
let rotl_node = dag.add_node(SDOpcode::Rotl, vec![i32.clone()], vec![add, b]);
dag.set_root(rotl_node);
let mut legalizer = make_minimal_legalizer();
let changes = legalizer.legalize_operations(&mut dag);
assert!(changes > 0);
}
#[test]
fn test_legalize_operations_no_changes_x86() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let a = dag.get_register(i32.clone());
let b = dag.get_register(i32.clone());
let add = dag.get_binary_op(SDOpcode::Add, i32.clone(), a, b);
dag.set_root(add);
let mut legalizer = make_x86_64_legalizer();
let changes = legalizer.legalize_operations(&mut dag);
assert_eq!(changes, 0);
}
#[test]
fn test_brcond_expansion() {
let mut dag = make_test_dag();
let chain = dag.entry_token;
let cond = dag.get_constant(1, Type::i1());
let brcond = dag.add_node(SDOpcode::BrCond, vec![], vec![chain, cond]);
let mut legalizer = make_x86_64_legalizer();
let result = legalizer.expand_brcond(&mut dag, brcond.node_id);
assert!(result.is_ok());
}
#[test]
fn test_op_action_as_str() {
assert_eq!(OpLegalizeAction::Legal.as_str(), "Legal");
assert_eq!(OpLegalizeAction::Expand.as_str(), "Expand");
assert_eq!(OpLegalizeAction::LibCall.as_str(), "LibCall");
assert_eq!(OpLegalizeAction::Promote.as_str(), "Promote");
assert_eq!(OpLegalizeAction::Custom.as_str(), "Custom");
}
#[test]
fn test_legalize_node_beyond_range() {
let mut dag = make_test_dag();
let mut legalizer = make_x86_64_legalizer();
let action = legalizer.legalize_operation(&mut dag, 999);
assert_eq!(action, OpLegalizeAction::Legal);
}
#[test]
fn test_constructor_32bit() {
let legalizer = OperationLegalizer::new_32bit("i386-unknown-linux-gnu");
assert!(legalizer.needs_64bit_expansion);
assert_eq!(legalizer.pointer_size, 32);
}
#[test]
fn test_libcall_div_rem() {
let mut dag = make_test_dag();
let i64 = Type::i64();
let a = dag.get_register(i64.clone());
let b = dag.get_register(i64.clone());
let div_node = dag.get_binary_op(SDOpcode::SDiv, i64.clone(), a, b);
let mut legalizer = make_minimal_legalizer();
let result = legalizer.expand_div_rem(&mut dag, div_node.node_id, true);
assert!(result.is_ok());
let rem_node = dag.get_binary_op(SDOpcode::URem, i64.clone(), a, b);
let result = legalizer.expand_div_rem(&mut dag, rem_node.node_id, false);
assert!(result.is_ok());
}
#[test]
fn test_atomic_cmp_swap_expander_new() {
let expander = AtomicOpsExpander::new();
assert_eq!(expander.expanded, 0);
assert!(!expander.has_cmpxchg16b);
}
#[test]
fn test_atomic_cmp_swap_expander_default() {
let expander = AtomicOpsExpander::default();
assert_eq!(expander.min_width, 8);
}
#[test]
fn test_mul_overflow_expander_new() {
let expander = MulOverflowExpander::new();
assert_eq!(expander.expanded, 0);
}
#[test]
fn test_mul_overflow_expander_default() {
let expander = MulOverflowExpander::default();
assert_eq!(expander.expanded, 0);
}
#[test]
fn test_rotate_expander_new() {
let expander = RotateExpander::new();
assert_eq!(expander.expanded, 0);
}
#[test]
fn test_rotate_expander_default() {
let expander = RotateExpander::default();
assert_eq!(expander.expanded, 0);
}
#[test]
fn test_bit_count_expander_new() {
let expander = BitCountExpander::new();
assert_eq!(expander.expanded, 0);
}
#[test]
fn test_fp_saturation_expander_new() {
let expander = FPSaturationExpander::new();
assert_eq!(expander.expanded, 0);
}
#[test]
fn test_expand_rotl_basic() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let a = dag.get_register(i32.clone());
let n = dag.get_register(i32.clone());
let rotl = dag.add_node(SDOpcode::Rotl, vec![i32], vec![a, n]);
let mut expander = RotateExpander::new();
let changed = expander.expand_rotl(&mut dag, rotl.node_id);
assert!(changed);
}
#[test]
fn test_expand_rotr_basic() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let a = dag.get_register(i32.clone());
let n = dag.get_register(i32.clone());
let rotr = dag.add_node(SDOpcode::Rotr, vec![i32], vec![a, n]);
let mut expander = RotateExpander::new();
let changed = expander.expand_rotr(&mut dag, rotr.node_id);
assert!(changed);
}
#[test]
fn test_expand_cttz_basic() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let cttz = dag.add_node(SDOpcode::Cttz, vec![i32], vec![x]);
let mut expander = BitCountExpander::new();
let changed = expander.expand_cttz(&mut dag, cttz.node_id);
assert!(changed);
}
#[test]
fn test_expand_ctlz_basic() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let ctlz = dag.add_node(SDOpcode::Ctlz, vec![i32], vec![x]);
let mut expander = BitCountExpander::new();
let changed = expander.expand_ctlz(&mut dag, ctlz.node_id);
assert!(changed);
}
#[test]
fn test_expand_ctpop_basic() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let ctpop = dag.add_node(SDOpcode::CtPop, vec![i32], vec![x]);
let mut expander = BitCountExpander::new();
let changed = expander.expand_ctpop(&mut dag, ctpop.node_id);
assert!(changed);
}
#[test]
fn test_expand_bitreverse_basic() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let x = dag.get_register(i32.clone());
let br = dag.add_node(SDOpcode::BitReverse, vec![i32], vec![x]);
let mut expander = BitCountExpander::new();
let changed = expander.expand_bitreverse(&mut dag, br.node_id);
assert!(changed);
}
#[test]
fn test_expand_fp_to_sint_sat_basic() {
let mut dag = make_test_dag();
let f32 = Type::float();
let i32 = Type::i32();
let x = dag.get_register(f32.clone());
let sat = dag.add_node(SDOpcode::FpToSIntSat, vec![i32], vec![x]);
let mut expander = FPSaturationExpander::new();
let changed = expander.expand_fp_to_sint_sat(&mut dag, sat.node_id);
assert!(changed);
}
#[test]
fn test_expand_fp_to_uint_sat_basic() {
let mut dag = make_test_dag();
let f32 = Type::float();
let i32 = Type::i32();
let x = dag.get_register(f32.clone());
let sat = dag.add_node(SDOpcode::FpToUIntSat, vec![i32], vec![x]);
let mut expander = FPSaturationExpander::new();
let changed = expander.expand_fp_to_uint_sat(&mut dag, sat.node_id);
assert!(changed);
}
#[test]
fn test_expand_strict_fp_basic() {
let mut dag = make_test_dag();
let f32 = Type::float();
let a = dag.get_register(f32.clone());
let b = dag.get_register(f32.clone());
let strict_add = dag.add_node(SDOpcode::StrictFAdd, vec![f32], vec![a, b]);
let mut expander = FPSaturationExpander::new();
let changed = expander.expand_strict_fp(&mut dag, strict_add.node_id);
assert!(changed);
}
#[test]
fn test_expand_smulo_basic() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let a = dag.get_register(i32.clone());
let b = dag.get_register(i32.clone());
let smulo = dag.add_node(SDOpcode::SMulo, vec![i32], vec![a, b]);
let mut expander = MulOverflowExpander::new();
let changed = expander.expand_smulo(&mut dag, smulo.node_id);
assert!(changed);
}
#[test]
fn test_expand_umulo_basic() {
let mut dag = make_test_dag();
let i32 = Type::i32();
let a = dag.get_register(i32.clone());
let b = dag.get_register(i32.clone());
let umulo = dag.add_node(SDOpcode::UMulo, vec![i32], vec![a, b]);
let mut expander = MulOverflowExpander::new();
let changed = expander.expand_umulo(&mut dag, umulo.node_id);
assert!(changed);
}
#[test]
fn test_atomic_load_expand_large() {
let mut dag = make_test_dag();
let i128 = Type::i128();
let chain = dag.entry_token;
let ptr = dag.get_register(Type::ptr());
let atomic_load = dag.add_node(SDOpcode::AtomicLoad, vec![i128], vec![chain, ptr]);
let mut expander = AtomicOpsExpander::new();
let changed = expander.expand_atomic_load(&mut dag, atomic_load.node_id);
assert!(changed);
}
#[test]
fn test_atomic_store_expand_large() {
let mut dag = make_test_dag();
let i128 = Type::i128();
let chain = dag.entry_token;
let ptr = dag.get_register(Type::ptr());
let val = dag.get_register(i128.clone());
let atomic_store = dag.add_node(SDOpcode::AtomicStore, vec![i128], vec![chain, val, ptr]);
let mut expander = AtomicOpsExpander::new();
let changed = expander.expand_atomic_store(&mut dag, atomic_store.node_id);
assert!(changed);
}
}