use crate::global_isel::gisel_machine_ir::{
GInstruction, GMachineFunction, GOpcode, MOperand, VReg,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Type {
S1,
S8,
S16,
S32,
S64,
S128,
F16,
F32,
F64,
F128,
}
impl Type {
pub fn bit_width(self) -> u32 {
match self {
Type::S1 => 1,
Type::S8 => 8,
Type::S16 => 16,
Type::S32 => 32,
Type::S64 => 64,
Type::S128 => 128,
Type::F16 => 16,
Type::F32 => 32,
Type::F64 => 64,
Type::F128 => 128,
}
}
pub fn is_floating_point(self) -> bool {
matches!(self, Type::F16 | Type::F32 | Type::F64 | Type::F128)
}
}
#[derive(Debug, Clone)]
pub enum LegalizeCondition {
Always,
NarrowScalar(u32),
WideScalar(u32),
TypeIs(Type),
}
impl LegalizeCondition {
pub fn evaluate(&self, ty: Option<Type>) -> bool {
match self {
LegalizeCondition::Always => true,
LegalizeCondition::NarrowScalar(threshold) => {
ty.map_or(false, |t| t.bit_width() < *threshold)
}
LegalizeCondition::WideScalar(threshold) => {
ty.map_or(false, |t| t.bit_width() > *threshold)
}
LegalizeCondition::TypeIs(expected) => ty.map_or(false, |t| t == *expected),
}
}
}
#[derive(Debug, Clone)]
pub enum LegalizeAction {
Legal,
NarrowScalar { new_width: u32 },
WidenScalar { new_width: u32 },
Lower,
LibCall(String),
}
#[derive(Debug, Clone)]
pub struct LegalizeRule {
pub opcode: GOpcode,
pub condition: LegalizeCondition,
pub action: LegalizeAction,
}
impl LegalizeRule {
pub fn new(opcode: GOpcode, condition: LegalizeCondition, action: LegalizeAction) -> Self {
LegalizeRule {
opcode,
condition,
action,
}
}
}
#[derive(Debug, Clone)]
pub struct LegalizerInfo {
pub rules: Vec<LegalizeRule>,
}
impl LegalizerInfo {
pub fn new() -> Self {
LegalizerInfo { rules: Vec::new() }
}
pub fn add_rule(&mut self, rule: LegalizeRule) {
self.rules.push(rule);
}
pub fn rule_count(&self) -> usize {
self.rules.len()
}
pub fn get_action(&self, opcode: GOpcode, ty: Option<Type>) -> Option<&LegalizeAction> {
for rule in &self.rules {
if rule.opcode == opcode && rule.condition.evaluate(ty) {
return Some(&rule.action);
}
}
None
}
pub fn default_aarch64() -> Self {
let mut info = LegalizerInfo::new();
info.add_rule(LegalizeRule::new(
GOpcode::G_ADD,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
info.add_rule(LegalizeRule::new(
GOpcode::G_SUB,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
info.add_rule(LegalizeRule::new(
GOpcode::G_MUL,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
info.add_rule(LegalizeRule::new(
GOpcode::G_AND,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
info.add_rule(LegalizeRule::new(
GOpcode::G_OR,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
info.add_rule(LegalizeRule::new(
GOpcode::G_XOR,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
info.add_rule(LegalizeRule::new(
GOpcode::G_ADD,
LegalizeCondition::WideScalar(64),
LegalizeAction::Lower,
));
info.add_rule(LegalizeRule::new(
GOpcode::G_SREM,
LegalizeCondition::WideScalar(32),
LegalizeAction::LibCall("__moddi3".to_string()),
));
info.add_rule(LegalizeRule::new(
GOpcode::G_UREM,
LegalizeCondition::WideScalar(32),
LegalizeAction::LibCall("__umoddi3".to_string()),
));
info
}
pub fn default_arm32() -> Self {
let mut info = LegalizerInfo::new();
info.add_rule(LegalizeRule::new(
GOpcode::G_ADD,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
info.add_rule(LegalizeRule::new(
GOpcode::G_ADD,
LegalizeCondition::WideScalar(32),
LegalizeAction::Lower,
));
info
}
}
impl Default for LegalizerInfo {
fn default() -> Self {
Self::new()
}
}
pub struct GISelLegalizer {
pub legalized: usize,
already_legal: usize,
lowered: usize,
libcalls: usize,
max_iterations: usize,
}
impl GISelLegalizer {
pub fn new() -> Self {
GISelLegalizer {
legalized: 0,
already_legal: 0,
lowered: 0,
libcalls: 0,
max_iterations: 32,
}
}
pub fn legalize(&mut self, mf: &mut GMachineFunction, legalizer_info: &LegalizerInfo) {
for _iteration in 0..self.max_iterations {
let mut any_changed = false;
for block_idx in 0..mf.blocks.len() {
let block = &mf.blocks[block_idx];
let inst_count = block.instruction_count();
for inst_idx in 0..inst_count {
let inst = mf.blocks[block_idx].instructions[inst_idx].clone();
let changed =
self.legalize_instruction(mf, block_idx, inst_idx, &inst, legalizer_info);
if changed {
any_changed = true;
break; } else {
self.already_legal += 1;
}
}
if any_changed {
break;
}
}
if !any_changed {
break; }
}
}
pub fn legalize_instruction(
&mut self,
mf: &mut GMachineFunction,
block_idx: usize,
inst_idx: usize,
mi: &GInstruction,
legalizer_info: &LegalizerInfo,
) -> bool {
let ty: Option<Type> = None;
let action = legalizer_info.get_action(mi.opcode, ty);
match action {
Some(LegalizeAction::Legal) => {
self.legalized += 1;
false }
Some(LegalizeAction::NarrowScalar { new_width }) => {
let _ = new_width;
self.legalized += 1;
self.lowered += 1;
true
}
Some(LegalizeAction::WidenScalar { new_width }) => {
let _ = new_width;
self.legalized += 1;
self.lowered += 1;
true
}
Some(LegalizeAction::Lower) => {
self.lower_instruction(mf, block_idx, inst_idx, mi);
self.lowered += 1;
true
}
Some(LegalizeAction::LibCall(name)) => {
let _name = name.clone();
let opcode = mi.opcode;
let dst = mi.def_vreg().unwrap_or(0);
let uses = mi.uses();
let src1 = uses.first().and_then(|o| o.as_vreg()).unwrap_or(0);
let src2 = uses.get(1).and_then(|o| o.as_vreg()).unwrap_or(0);
let _ = self.libcall_replace(opcode, dst, src1, src2, name);
self.libcalls += 1;
true
}
None => {
self.legalized += 1;
false
}
}
}
fn lower_instruction(
&mut self,
mf: &mut GMachineFunction,
block_idx: usize,
inst_idx: usize,
mi: &GInstruction,
) {
match mi.opcode {
GOpcode::G_SDIV => {
let _ = mf;
let _ = block_idx;
let _ = inst_idx;
self.legalized += 1;
}
GOpcode::G_UDIV => {
let _ = mf;
let _ = block_idx;
let _ = inst_idx;
self.legalized += 1;
}
_ => {
let _ = mf;
let _ = block_idx;
let _ = inst_idx;
self.legalized += 1;
}
}
}
fn libcall_replace(
&mut self,
_opcode: GOpcode,
_dst: VReg,
_src1: VReg,
_src2: VReg,
_libcall_name: &str,
) -> LegalizeResult {
self.legalized += 1;
LegalizeResult::LibCallReplaced(Vec::new())
}
pub fn already_legal_count(&self) -> usize {
self.already_legal
}
pub fn lowered_count(&self) -> usize {
self.lowered
}
pub fn libcalls_count(&self) -> usize {
self.libcalls
}
pub fn reset(&mut self) {
self.legalized = 0;
self.already_legal = 0;
self.lowered = 0;
self.libcalls = 0;
}
pub fn max_iterations(&self) -> usize {
self.max_iterations
}
pub fn set_max_iterations(&mut self, max: usize) {
self.max_iterations = max.max(1);
}
}
impl Default for GISelLegalizer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum LegalizeResult {
AlreadyLegal,
Narrowed(Vec<GInstruction>),
Widened(Vec<GInstruction>),
Lowered(Vec<GInstruction>),
LibCallReplaced(Vec<GInstruction>),
Unsupported,
}
impl GISelLegalizer {
pub fn narrow_scalar(
&mut self,
opcode: GOpcode,
dst: VReg,
src1: VReg,
src2: VReg,
_current_width: u32,
new_width: u32,
) -> LegalizeResult {
if new_width == 0 || new_width >= _current_width {
return LegalizeResult::Unsupported;
}
let mut replacements = Vec::new();
match opcode {
GOpcode::G_ADD | GOpcode::G_SUB | GOpcode::G_AND | GOpcode::G_OR | GOpcode::G_XOR => {
let trunc_dst1 = dst + 100; let trunc_dst2 = dst + 200;
replacements.push(GInstruction::with_operands(
GOpcode::G_TRUNC,
vec![MOperand::vreg(trunc_dst1), MOperand::vreg(src1)],
));
replacements.push(GInstruction::with_operands(
GOpcode::G_TRUNC,
vec![MOperand::vreg(trunc_dst2), MOperand::vreg(src2)],
));
let narrow_dst = dst + 300;
replacements.push(GInstruction::with_operands(
opcode,
vec![
MOperand::vreg(narrow_dst),
MOperand::vreg(trunc_dst1),
MOperand::vreg(trunc_dst2),
],
));
replacements.push(GInstruction::with_operands(
GOpcode::G_ANYEXT,
vec![MOperand::vreg(dst), MOperand::vreg(narrow_dst)],
));
self.lowered += 1;
LegalizeResult::Narrowed(replacements)
}
_ => LegalizeResult::Unsupported,
}
}
pub fn widen_scalar(
&mut self,
opcode: GOpcode,
dst: VReg,
src1: VReg,
src2: VReg,
_current_width: u32,
new_width: u32,
) -> LegalizeResult {
if new_width <= _current_width {
return LegalizeResult::Unsupported;
}
let mut replacements = Vec::new();
match opcode {
GOpcode::G_ADD | GOpcode::G_SUB | GOpcode::G_AND | GOpcode::G_OR | GOpcode::G_XOR => {
let ext_dst1 = dst + 100;
let ext_dst2 = dst + 200;
replacements.push(GInstruction::with_operands(
GOpcode::G_ANYEXT,
vec![MOperand::vreg(ext_dst1), MOperand::vreg(src1)],
));
replacements.push(GInstruction::with_operands(
GOpcode::G_ANYEXT,
vec![MOperand::vreg(ext_dst2), MOperand::vreg(src2)],
));
let wide_dst = dst + 300;
replacements.push(GInstruction::with_operands(
opcode,
vec![
MOperand::vreg(wide_dst),
MOperand::vreg(ext_dst1),
MOperand::vreg(ext_dst2),
],
));
replacements.push(GInstruction::with_operands(
GOpcode::G_TRUNC,
vec![MOperand::vreg(dst), MOperand::vreg(wide_dst)],
));
self.lowered += 1;
LegalizeResult::Widened(replacements)
}
_ => LegalizeResult::Unsupported,
}
}
pub fn lower(&mut self, opcode: GOpcode, dst: VReg, src1: VReg, src2: VReg) -> LegalizeResult {
let mut replacements = Vec::new();
match opcode {
GOpcode::G_CTLZ => {
replacements.push(GInstruction::with_operands(
GOpcode::G_CONSTANT,
vec![MOperand::vreg(dst), MOperand::imm(31)],
));
self.lowered += 1;
LegalizeResult::Lowered(replacements)
}
GOpcode::G_BSWAP => {
let tmp1 = dst + 100;
let tmp2 = dst + 200;
replacements.push(GInstruction::with_operands(
GOpcode::G_SHL,
vec![
MOperand::vreg(tmp1),
MOperand::vreg(src1),
MOperand::imm(24),
],
));
replacements.push(GInstruction::with_operands(
GOpcode::G_LSHR,
vec![
MOperand::vreg(tmp2),
MOperand::vreg(src1),
MOperand::imm(24),
],
));
replacements.push(GInstruction::with_operands(
GOpcode::G_OR,
vec![
MOperand::vreg(dst),
MOperand::vreg(tmp1),
MOperand::vreg(tmp2),
],
));
self.lowered += 1;
LegalizeResult::Lowered(replacements)
}
GOpcode::G_FMA => {
let mul_dst = dst + 100;
replacements.push(GInstruction::with_operands(
GOpcode::G_FMUL,
vec![
MOperand::vreg(mul_dst),
MOperand::vreg(src1),
MOperand::vreg(src2),
],
));
replacements.push(GInstruction::with_operands(
GOpcode::G_FADD,
vec![
MOperand::vreg(dst),
MOperand::vreg(mul_dst),
MOperand::vreg(dst + 200),
],
));
self.lowered += 1;
LegalizeResult::Lowered(replacements)
}
_ => LegalizeResult::Unsupported,
}
}
pub fn fewer_elements_vector(
&mut self,
_opcode: GOpcode,
_dst: VReg,
_src: VReg,
_num_elements: usize,
) -> Vec<GInstruction> {
self.lowered += 1;
Vec::new()
}
pub fn more_elements_vector(
&mut self,
_opcode: GOpcode,
_dst: VReg,
_src: VReg,
_target_elements: usize,
) -> Vec<GInstruction> {
self.lowered += 1;
Vec::new()
}
pub fn legalize_op(&mut self, inst: &GInstruction, info: &LegalizerInfo) -> LegalizeResult {
let opcode = inst.opcode;
let action = info.get_action(opcode, None).cloned();
let dst = inst.def_vreg().unwrap_or(0);
let uses = inst.uses();
let src1 = uses.first().and_then(|o| o.as_vreg()).unwrap_or(0);
let src2 = uses.get(1).and_then(|o| o.as_vreg()).unwrap_or(0);
match action {
Some(LegalizeAction::Legal) => LegalizeResult::AlreadyLegal,
Some(LegalizeAction::NarrowScalar { new_width }) => {
self.narrow_scalar(opcode, dst, src1, src2, 64, new_width)
}
Some(LegalizeAction::WidenScalar { new_width }) => {
self.widen_scalar(opcode, dst, src1, src2, 32, new_width)
}
Some(LegalizeAction::Lower) => self.lower(opcode, dst, src1, src2),
Some(LegalizeAction::LibCall(name)) => {
self.libcall_replace(opcode, dst, src1, src2, &name)
}
None => LegalizeResult::Unsupported,
}
}
pub fn legalize_with_rules(
&mut self,
mf: &mut GMachineFunction,
info: &LegalizerInfo,
) -> usize {
self.reset();
let mut total_legalized = 0;
for _iteration in 0..self.max_iterations {
let mut changed = false;
for block_idx in 0..mf.blocks.len() {
let mut new_insts: Vec<GInstruction> = Vec::new();
let mut remove_indices: Vec<usize> = Vec::new();
for (i_idx, inst) in mf.blocks[block_idx].instructions.iter().enumerate() {
match self.legalize_op(inst, info) {
LegalizeResult::AlreadyLegal => {
self.already_legal += 1;
}
LegalizeResult::Narrowed(replacements)
| LegalizeResult::Widened(replacements)
| LegalizeResult::Lowered(replacements)
| LegalizeResult::LibCallReplaced(replacements) => {
remove_indices.push(i_idx);
new_insts.extend(replacements);
changed = true;
total_legalized += 1;
}
LegalizeResult::Unsupported => {
}
}
}
for &idx in remove_indices.iter().rev() {
if idx < mf.blocks[block_idx].instructions.len() {
mf.blocks[block_idx].instructions.remove(idx);
}
}
mf.blocks[block_idx].instructions.extend(new_insts);
}
if !changed {
break;
}
}
total_legalized
}
}
impl LegalizerInfo {
pub fn default_x86_64() -> Self {
let mut info = LegalizerInfo::new();
let scalar_ops = [
GOpcode::G_ADD,
GOpcode::G_SUB,
GOpcode::G_MUL,
GOpcode::G_AND,
GOpcode::G_OR,
GOpcode::G_XOR,
GOpcode::G_SHL,
GOpcode::G_LSHR,
GOpcode::G_ASHR,
GOpcode::G_LOAD,
GOpcode::G_STORE,
GOpcode::G_BR,
GOpcode::G_BRCOND,
GOpcode::G_RET,
GOpcode::G_CONSTANT,
GOpcode::G_FCONSTANT,
GOpcode::G_FADD,
GOpcode::G_FSUB,
GOpcode::G_FMUL,
GOpcode::G_FDIV,
GOpcode::G_ICMP,
GOpcode::G_FCMP,
GOpcode::G_SELECT,
GOpcode::G_PHI,
GOpcode::G_TRUNC,
GOpcode::G_ZEXT,
GOpcode::G_SEXT,
GOpcode::G_ANYEXT,
GOpcode::G_PTRTOINT,
GOpcode::G_INTTOPTR,
GOpcode::G_BITCAST,
GOpcode::COPY,
];
for op in &scalar_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
}
let lowering_ops = [
(GOpcode::G_CTLZ, "ctlz"),
(GOpcode::G_CTTZ, "cttz"),
(GOpcode::G_CTPOP, "ctpop"),
(GOpcode::G_BSWAP, "bswap"),
(GOpcode::G_BITREVERSE, "bitreverse"),
];
for (op, _name) in &lowering_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::LibCall(String::new()),
));
}
let widening_ops = [
GOpcode::G_SDIV,
GOpcode::G_UDIV,
GOpcode::G_SREM,
GOpcode::G_UREM,
];
for op in &widening_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::NarrowScalar(64),
LegalizeAction::WidenScalar { new_width: 64 },
));
}
info
}
pub fn default_riscv64() -> Self {
let mut info = LegalizerInfo::new();
let scalar_ops = [
GOpcode::G_ADD,
GOpcode::G_SUB,
GOpcode::G_AND,
GOpcode::G_OR,
GOpcode::G_XOR,
GOpcode::G_SHL,
GOpcode::G_LSHR,
GOpcode::G_ASHR,
GOpcode::G_LOAD,
GOpcode::G_STORE,
GOpcode::G_BR,
GOpcode::G_BRCOND,
GOpcode::G_RET,
GOpcode::G_CONSTANT,
GOpcode::G_ICMP,
GOpcode::G_SELECT,
GOpcode::G_PHI,
GOpcode::G_TRUNC,
GOpcode::G_ZEXT,
GOpcode::G_SEXT,
GOpcode::COPY,
];
for op in &scalar_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
}
let mul_div_ops = [
GOpcode::G_MUL,
GOpcode::G_SDIV,
GOpcode::G_UDIV,
GOpcode::G_SREM,
GOpcode::G_UREM,
];
for op in &mul_div_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::LibCall(String::new()),
));
}
let fp_ops = [
GOpcode::G_FADD,
GOpcode::G_FSUB,
GOpcode::G_FMUL,
GOpcode::G_FDIV,
GOpcode::G_FNEG,
GOpcode::G_FABS,
GOpcode::G_FSQRT,
GOpcode::G_FCMP,
GOpcode::G_FCONSTANT,
];
for op in &fp_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::LibCall(String::new()),
));
}
info
}
pub fn check_function_legality(&self, mf: &GMachineFunction) -> Vec<String> {
let mut illegal = Vec::new();
for (bb_idx, block) in mf.blocks.iter().enumerate() {
for (i_idx, inst) in block.instructions.iter().enumerate() {
let action = self.get_action(inst.opcode, None);
match action {
Some(LegalizeAction::Legal) => {}
Some(a) => {
illegal.push(format!(
"BB{} inst{}: {:?} requires {:?}",
bb_idx, i_idx, inst.opcode, a
));
}
None => {}
}
}
}
illegal
}
}
#[derive(Debug, Clone)]
pub struct TargetConfig {
pub max_scalar_width: u32,
pub supports_unaligned_access: bool,
pub has_hardware_fp: bool,
pub has_vector: bool,
pub max_vector_width: u32,
pub has_predication: bool,
}
impl Default for TargetConfig {
fn default() -> Self {
TargetConfig {
max_scalar_width: 64,
supports_unaligned_access: true,
has_hardware_fp: true,
has_vector: true,
max_vector_width: 256,
has_predication: false,
}
}
}
pub struct LegalizerFactory;
impl LegalizerFactory {
pub fn for_x86_64() -> LegalizerInfo {
LegalizerInfo::default_x86_64()
}
pub fn for_aarch64() -> LegalizerInfo {
LegalizerInfo::default_aarch64()
}
pub fn for_arm32() -> LegalizerInfo {
LegalizerInfo::default_arm32()
}
pub fn for_riscv64() -> LegalizerInfo {
LegalizerInfo::default_riscv64()
}
pub fn for_config(config: &TargetConfig) -> LegalizerInfo {
let mut info = LegalizerInfo::new();
let scalar_ops = [
GOpcode::G_ADD,
GOpcode::G_SUB,
GOpcode::G_AND,
GOpcode::G_OR,
GOpcode::G_XOR,
GOpcode::G_SHL,
GOpcode::G_LSHR,
GOpcode::G_ASHR,
GOpcode::G_CONSTANT,
GOpcode::G_LOAD,
GOpcode::G_STORE,
GOpcode::G_BR,
GOpcode::G_BRCOND,
GOpcode::G_RET,
GOpcode::G_ICMP,
GOpcode::G_SELECT,
GOpcode::G_PHI,
GOpcode::COPY,
];
for op in &scalar_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
}
let ext_ops = [
GOpcode::G_TRUNC,
GOpcode::G_ZEXT,
GOpcode::G_SEXT,
GOpcode::G_ANYEXT,
GOpcode::G_PTRTOINT,
GOpcode::G_INTTOPTR,
GOpcode::G_BITCAST,
];
for op in &ext_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
}
let fp_ops = [
GOpcode::G_FADD,
GOpcode::G_FSUB,
GOpcode::G_FMUL,
GOpcode::G_FDIV,
GOpcode::G_FNEG,
GOpcode::G_FABS,
GOpcode::G_FSQRT,
GOpcode::G_FCMP,
GOpcode::G_FCONSTANT,
GOpcode::G_FPTRUNC,
GOpcode::G_FPEXT,
GOpcode::G_FPTOUI,
GOpcode::G_FPTOSI,
GOpcode::G_UITOFP,
GOpcode::G_SITOFP,
];
for op in &fp_ops {
if config.has_hardware_fp {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
} else {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::LibCall(String::new()),
));
}
}
let bit_ops = [
GOpcode::G_CTLZ,
GOpcode::G_CTTZ,
GOpcode::G_CTPOP,
GOpcode::G_BSWAP,
GOpcode::G_BITREVERSE,
];
for op in &bit_ops {
info.add_rule(LegalizeRule::new(
*op,
LegalizeCondition::Always,
LegalizeAction::LibCall(String::new()),
));
}
info
}
}
#[derive(Debug, Clone, Default)]
pub struct LegalizationStats {
pub already_legal: usize,
pub narrowed: usize,
pub widened: usize,
pub lowered: usize,
pub libcalls: usize,
pub unsupported: usize,
pub total: usize,
}
impl GISelLegalizer {
pub fn legalize_with_stats(
&mut self,
mf: &mut GMachineFunction,
info: &LegalizerInfo,
) -> LegalizationStats {
self.reset();
let mut stats = LegalizationStats::default();
for _iteration in 0..self.max_iterations {
let mut changed = false;
for block in &mut mf.blocks {
let mut new_insts: Vec<GInstruction> = Vec::new();
let mut remove_indices: Vec<usize> = Vec::new();
for (i_idx, inst) in block.instructions.iter().enumerate() {
stats.total += 1;
match self.legalize_op(inst, info) {
LegalizeResult::AlreadyLegal => {
stats.already_legal += 1;
}
LegalizeResult::Narrowed(repl) => {
remove_indices.push(i_idx);
new_insts.extend(repl);
stats.narrowed += 1;
changed = true;
}
LegalizeResult::Widened(repl) => {
remove_indices.push(i_idx);
new_insts.extend(repl);
stats.widened += 1;
changed = true;
}
LegalizeResult::Lowered(repl) => {
remove_indices.push(i_idx);
new_insts.extend(repl);
stats.lowered += 1;
changed = true;
}
LegalizeResult::LibCallReplaced(repl) => {
remove_indices.push(i_idx);
new_insts.extend(repl);
stats.libcalls += 1;
changed = true;
}
LegalizeResult::Unsupported => {
stats.unsupported += 1;
}
}
}
for &idx in remove_indices.iter().rev() {
if idx < block.instructions.len() {
block.instructions.remove(idx);
}
}
block.instructions.extend(new_insts);
}
if !changed {
break;
}
}
stats
}
pub fn is_op_legal(&self, opcode: GOpcode, info: &LegalizerInfo) -> bool {
matches!(info.get_action(opcode, None), Some(LegalizeAction::Legal))
}
pub fn describe_action(&self, opcode: GOpcode, info: &LegalizerInfo) -> String {
match info.get_action(opcode, None) {
Some(LegalizeAction::Legal) => format!("{:?} is legal", opcode),
Some(LegalizeAction::NarrowScalar { new_width }) => {
format!("{:?} must be narrowed to {} bits", opcode, new_width)
}
Some(LegalizeAction::WidenScalar { new_width }) => {
format!("{:?} must be widened to {} bits", opcode, new_width)
}
Some(LegalizeAction::Lower) => format!("{:?} must be lowered", opcode),
Some(LegalizeAction::LibCall(name)) => {
format!("{:?} requires libcall '{}'", opcode, name)
}
None => format!("{:?} has no known action", opcode),
}
}
}
#[derive(Debug, Clone)]
pub struct NarrowScalarLegalizer {
pub max_width: u32,
}
impl NarrowScalarLegalizer {
pub fn new(max_width: u32) -> Self {
Self { max_width }
}
pub fn needs_narrowing(&self, width: u32) -> bool {
width > self.max_width
}
pub fn narrow_add(
&self,
a_lo: VReg,
a_hi: VReg,
b_lo: VReg,
b_hi: VReg,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let result_lo = a_lo.wrapping_add(100);
seq.push((
GOpcode::G_UADDO,
vec![
MOperand::vreg(result_lo),
MOperand::vreg(a_lo),
MOperand::vreg(b_lo),
],
));
let result_hi = a_hi.wrapping_add(101);
let carry = result_lo.wrapping_add(1);
seq.push((
GOpcode::G_ADD,
vec![
MOperand::vreg(carry),
MOperand::vreg(a_hi),
MOperand::vreg(b_hi),
],
));
seq.push((
GOpcode::G_ADD,
vec![
MOperand::vreg(result_hi),
MOperand::vreg(carry),
MOperand::vreg(result_lo),
],
));
seq
}
pub fn narrow_sub(
&self,
a_lo: VReg,
a_hi: VReg,
b_lo: VReg,
b_hi: VReg,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let result_lo = a_lo.wrapping_add(100);
seq.push((
GOpcode::G_USUBO,
vec![
MOperand::vreg(result_lo),
MOperand::vreg(a_lo),
MOperand::vreg(b_lo),
],
));
let result_hi = a_hi.wrapping_add(101);
let borrow = a_lo.wrapping_add(1);
seq.push((
GOpcode::G_SUB,
vec![
MOperand::vreg(borrow),
MOperand::vreg(a_hi),
MOperand::vreg(b_hi),
],
));
seq.push((
GOpcode::G_SUB,
vec![
MOperand::vreg(result_hi),
MOperand::vreg(borrow),
MOperand::vreg(result_lo),
],
));
seq
}
pub fn narrow_mul(
&self,
a_lo: VReg,
a_hi: VReg,
b_lo: VReg,
b_hi: VReg,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let t1 = a_lo.wrapping_add(100);
seq.push((
GOpcode::G_MUL,
vec![
MOperand::vreg(t1),
MOperand::vreg(a_lo),
MOperand::vreg(b_lo),
],
));
let t2 = a_lo.wrapping_add(101);
seq.push((
GOpcode::G_MUL,
vec![
MOperand::vreg(t2),
MOperand::vreg(a_lo),
MOperand::vreg(b_hi),
],
));
let t3 = a_lo.wrapping_add(102);
seq.push((
GOpcode::G_MUL,
vec![
MOperand::vreg(t3),
MOperand::vreg(a_hi),
MOperand::vreg(b_lo),
],
));
let lo = t1;
let hi = a_lo.wrapping_add(103);
seq.push((
GOpcode::G_ADD,
vec![MOperand::vreg(hi), MOperand::vreg(t2), MOperand::vreg(t3)],
));
seq
}
}
impl Default for NarrowScalarLegalizer {
fn default() -> Self {
Self::new(64)
}
}
#[derive(Debug, Clone)]
pub struct WidenScalarLegalizer {
pub min_width: u32,
}
impl WidenScalarLegalizer {
pub fn new(min_width: u32) -> Self {
Self { min_width }
}
pub fn needs_widening(&self, width: u32) -> bool {
width < self.min_width
}
pub fn widen_binary_op(
&self,
opcode: GOpcode,
dst: VReg,
a: VReg,
b: VReg,
original_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let a_ext = a.wrapping_add(1000);
let b_ext = b.wrapping_add(1001);
seq.push((
GOpcode::G_ANYEXT,
vec![MOperand::vreg(a_ext), MOperand::vreg(a)],
));
seq.push((
GOpcode::G_ANYEXT,
vec![MOperand::vreg(b_ext), MOperand::vreg(b)],
));
let wide_result = dst.wrapping_add(1002);
seq.push((
opcode,
vec![
MOperand::vreg(wide_result),
MOperand::vreg(a_ext),
MOperand::vreg(b_ext),
],
));
seq.push((
GOpcode::G_TRUNC,
vec![
MOperand::vreg(dst),
MOperand::vreg(wide_result),
MOperand::imm(original_width as i64),
],
));
seq
}
pub fn widen_unary_op(
&self,
opcode: GOpcode,
dst: VReg,
src: VReg,
original_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let src_ext = src.wrapping_add(1000);
seq.push((
GOpcode::G_ANYEXT,
vec![MOperand::vreg(src_ext), MOperand::vreg(src)],
));
let wide_result = dst.wrapping_add(1001);
seq.push((
opcode,
vec![MOperand::vreg(wide_result), MOperand::vreg(src_ext)],
));
seq.push((
GOpcode::G_TRUNC,
vec![
MOperand::vreg(dst),
MOperand::vreg(wide_result),
MOperand::imm(original_width as i64),
],
));
seq
}
}
impl Default for WidenScalarLegalizer {
fn default() -> Self {
Self::new(32)
}
}
#[derive(Debug, Clone)]
pub struct BitManipLowerer {
pub has_ctlz: bool,
pub has_cttz: bool,
pub has_ctpop: bool,
pub has_bswap: bool,
pub has_bitreverse: bool,
}
impl BitManipLowerer {
pub fn new() -> Self {
Self {
has_ctlz: false,
has_cttz: false,
has_ctpop: false,
has_bswap: false,
has_bitreverse: false,
}
}
pub fn lower_ctlz(
&self,
dst: VReg,
src: VReg,
bit_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let t1 = dst.wrapping_add(100);
seq.push((
GOpcode::G_LSHR,
vec![MOperand::vreg(t1), MOperand::vreg(src), MOperand::imm(1)],
));
let t2 = dst.wrapping_add(101);
seq.push((
GOpcode::G_OR,
vec![MOperand::vreg(t2), MOperand::vreg(src), MOperand::vreg(t1)],
));
for shift in &[2u32, 4, 8, 16, 32] {
if *shift >= bit_width {
break;
}
let tn = dst.wrapping_add(110 + shift);
let tm = dst.wrapping_add(120 + shift);
seq.push((
GOpcode::G_LSHR,
vec![
MOperand::vreg(tn),
MOperand::vreg(t2),
MOperand::imm(*shift as i64),
],
));
seq.push((
GOpcode::G_OR,
vec![MOperand::vreg(tm), MOperand::vreg(t2), MOperand::vreg(tn)],
));
}
let inverted = dst.wrapping_add(200);
seq.push((
GOpcode::G_XOR,
vec![
MOperand::vreg(inverted),
MOperand::vreg(t2),
MOperand::imm(-1),
],
));
seq.append(&mut self.lower_ctpop(dst, inverted, bit_width));
seq
}
pub fn lower_cttz(
&self,
dst: VReg,
src: VReg,
bit_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let reversed = dst.wrapping_add(200);
seq.append(&mut self.lower_bitreverse(reversed, src, bit_width));
seq.append(&mut self.lower_ctlz(dst, reversed, bit_width));
seq
}
pub fn lower_ctpop(
&self,
dst: VReg,
src: VReg,
bit_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let t1 = dst.wrapping_add(100);
seq.push((
GOpcode::G_LSHR,
vec![MOperand::vreg(t1), MOperand::vreg(src), MOperand::imm(1)],
));
seq.push((
GOpcode::G_AND,
vec![
MOperand::vreg(t1),
MOperand::vreg(t1),
MOperand::imm(0x5555555555555555i64),
],
));
let t2 = dst.wrapping_add(101);
seq.push((
GOpcode::G_AND,
vec![
MOperand::vreg(t2),
MOperand::vreg(src),
MOperand::imm(0x5555555555555555i64),
],
));
let sum1 = dst.wrapping_add(102);
seq.push((
GOpcode::G_ADD,
vec![MOperand::vreg(sum1), MOperand::vreg(t1), MOperand::vreg(t2)],
));
let s1 = dst.wrapping_add(103);
seq.push((
GOpcode::G_LSHR,
vec![MOperand::vreg(s1), MOperand::vreg(sum1), MOperand::imm(2)],
));
seq.push((
GOpcode::G_AND,
vec![
MOperand::vreg(s1),
MOperand::vreg(s1),
MOperand::imm(0x3333333333333333i64),
],
));
seq.push((
GOpcode::G_AND,
vec![
MOperand::vreg(dst),
MOperand::vreg(sum1),
MOperand::imm(0x3333333333333333i64),
],
));
seq.push((
GOpcode::G_ADD,
vec![MOperand::vreg(dst), MOperand::vreg(dst), MOperand::vreg(s1)],
));
seq
}
pub fn lower_bswap(
&self,
dst: VReg,
src: VReg,
bit_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let byte_count = bit_width / 8;
let mut accumulator = dst.wrapping_add(200);
for i in 0..byte_count {
let byte_src = src.wrapping_add(100 + i);
let shift_amount = (i * 8) as i64;
let target_shift = ((byte_count - 1 - i) * 8) as i64;
if i == 0 {
seq.push((
GOpcode::G_AND,
vec![
MOperand::vreg(byte_src),
MOperand::vreg(src),
MOperand::imm(0xFF),
],
));
seq.push((
GOpcode::G_SHL,
vec![
MOperand::vreg(accumulator),
MOperand::vreg(byte_src),
MOperand::imm(target_shift),
],
));
} else {
let shifted = src.wrapping_add(300 + i);
seq.push((
GOpcode::G_LSHR,
vec![
MOperand::vreg(shifted),
MOperand::vreg(src),
MOperand::imm(shift_amount),
],
));
seq.push((
GOpcode::G_AND,
vec![
MOperand::vreg(byte_src),
MOperand::vreg(shifted),
MOperand::imm(0xFF),
],
));
let shifted_byte = src.wrapping_add(400 + i);
seq.push((
GOpcode::G_SHL,
vec![
MOperand::vreg(shifted_byte),
MOperand::vreg(byte_src),
MOperand::imm(target_shift),
],
));
let new_acc = src.wrapping_add(500 + i);
seq.push((
GOpcode::G_OR,
vec![
MOperand::vreg(new_acc),
MOperand::vreg(accumulator),
MOperand::vreg(shifted_byte),
],
));
accumulator = new_acc;
}
}
if accumulator != dst {
seq.push((
GOpcode::COPY,
vec![MOperand::vreg(dst), MOperand::vreg(accumulator)],
));
}
seq
}
pub fn lower_bitreverse(
&self,
dst: VReg,
src: VReg,
bit_width: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let mut current = src;
let mut next = dst.wrapping_add(300);
let t1 = dst.wrapping_add(100);
seq.push((
GOpcode::G_AND,
vec![
MOperand::vreg(t1),
MOperand::vreg(current),
MOperand::imm(0xAAAAAAAAAAAAAAAAu64 as i64),
],
));
seq.push((
GOpcode::G_LSHR,
vec![MOperand::vreg(t1), MOperand::vreg(t1), MOperand::imm(1)],
));
let t2 = dst.wrapping_add(101);
seq.push((
GOpcode::G_AND,
vec![
MOperand::vreg(t2),
MOperand::vreg(current),
MOperand::imm(0x5555555555555555i64),
],
));
seq.push((
GOpcode::G_SHL,
vec![MOperand::vreg(t2), MOperand::vreg(t2), MOperand::imm(1)],
));
seq.push((
GOpcode::G_OR,
vec![MOperand::vreg(next), MOperand::vreg(t1), MOperand::vreg(t2)],
));
if self.has_bswap {
seq.push((
GOpcode::G_BSWAP,
vec![MOperand::vreg(dst), MOperand::vreg(next)],
));
} else {
seq.append(&mut self.lower_bswap(dst, next, bit_width));
}
seq
}
}
impl Default for BitManipLowerer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct FunnelShiftLowerer {
pub has_funnel_shift: bool,
}
impl FunnelShiftLowerer {
pub fn new() -> Self {
Self {
has_funnel_shift: false,
}
}
pub fn lower_fshl(
&self,
dst: VReg,
a: VReg,
b: VReg,
amt: VReg,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let t1 = dst.wrapping_add(100);
seq.push((
GOpcode::G_SHL,
vec![MOperand::vreg(t1), MOperand::vreg(a), MOperand::vreg(amt)],
));
let t2 = dst.wrapping_add(101);
seq.push((
GOpcode::G_CONSTANT,
vec![MOperand::vreg(t2), MOperand::imm(64)],
));
let t3 = dst.wrapping_add(102);
seq.push((
GOpcode::G_SUB,
vec![MOperand::vreg(t3), MOperand::vreg(t2), MOperand::vreg(amt)],
));
let t4 = dst.wrapping_add(103);
seq.push((
GOpcode::G_LSHR,
vec![MOperand::vreg(t4), MOperand::vreg(b), MOperand::vreg(t3)],
));
seq.push((
GOpcode::G_OR,
vec![MOperand::vreg(dst), MOperand::vreg(t1), MOperand::vreg(t4)],
));
seq
}
pub fn lower_fshr(
&self,
dst: VReg,
a: VReg,
b: VReg,
amt: VReg,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let t1 = dst.wrapping_add(100);
seq.push((
GOpcode::G_LSHR,
vec![MOperand::vreg(t1), MOperand::vreg(a), MOperand::vreg(amt)],
));
let t2 = dst.wrapping_add(101);
seq.push((
GOpcode::G_CONSTANT,
vec![MOperand::vreg(t2), MOperand::imm(64)],
));
let t3 = dst.wrapping_add(102);
seq.push((
GOpcode::G_SUB,
vec![MOperand::vreg(t3), MOperand::vreg(t2), MOperand::vreg(amt)],
));
let t4 = dst.wrapping_add(103);
seq.push((
GOpcode::G_SHL,
vec![MOperand::vreg(t4), MOperand::vreg(b), MOperand::vreg(t3)],
));
seq.push((
GOpcode::G_OR,
vec![MOperand::vreg(dst), MOperand::vreg(t1), MOperand::vreg(t4)],
));
seq
}
}
impl Default for FunnelShiftLowerer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct VectorElementLegalizer {
pub max_vector_bits: u32,
pub min_vector_bits: u32,
}
impl VectorElementLegalizer {
pub fn new(max_bits: u32, min_bits: u32) -> Self {
Self {
max_vector_bits: max_bits,
min_vector_bits: min_bits,
}
}
pub fn split_256_to_128(
&self,
dst_lo: VReg,
dst_hi: VReg,
src: VReg,
) -> Vec<(GOpcode, Vec<MOperand>)> {
vec![
(
GOpcode::G_TRUNC,
vec![
MOperand::vreg(dst_lo),
MOperand::vreg(src),
MOperand::imm(0),
],
),
(
GOpcode::G_TRUNC,
vec![
MOperand::vreg(dst_hi),
MOperand::vreg(src),
MOperand::imm(128),
],
),
]
}
pub fn split_512_to_128(&self, dsts: &[VReg], src: VReg) -> Vec<(GOpcode, Vec<MOperand>)> {
dsts.iter()
.enumerate()
.map(|(i, &dst)| {
(
GOpcode::G_TRUNC,
vec![
MOperand::vreg(dst),
MOperand::vreg(src),
MOperand::imm((i * 128) as i64),
],
)
})
.collect()
}
pub fn combine_2x64_to_128(
&self,
dst: VReg,
lo: VReg,
hi: VReg,
) -> Vec<(GOpcode, Vec<MOperand>)> {
vec![
(
GOpcode::G_ANYEXT,
vec![MOperand::vreg(dst), MOperand::vreg(lo)],
),
(
GOpcode::G_ANYEXT,
vec![MOperand::vreg(dst), MOperand::vreg(hi)],
),
]
}
pub fn fewer_elements(
&self,
src: VReg,
src_bits: u32,
num_parts: u32,
) -> Vec<(GOpcode, Vec<MOperand>)> {
let part_bits = src_bits / num_parts;
let mut seq = Vec::new();
for i in 0..num_parts {
let dst = src.wrapping_add(100 + i);
seq.push((
GOpcode::G_TRUNC,
vec![
MOperand::vreg(dst),
MOperand::vreg(src),
MOperand::imm((i * part_bits) as i64),
],
));
}
seq
}
pub fn more_elements(&self, dst: VReg, sources: &[VReg]) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let mut current = dst;
for &src in sources {
let temp = dst.wrapping_add(200 + src);
seq.push((
GOpcode::G_ANYEXT,
vec![MOperand::vreg(temp), MOperand::vreg(src)],
));
seq.push((
GOpcode::G_OR,
vec![
MOperand::vreg(current),
MOperand::vreg(current),
MOperand::vreg(temp),
],
));
}
seq
}
}
impl Default for VectorElementLegalizer {
fn default() -> Self {
Self::new(256, 64)
}
}
#[derive(Debug, Clone)]
pub struct VarargsLegalizer {
pub va_list_size: u32,
pub reg_save_area: bool,
pub pointer_size: u32,
}
impl VarargsLegalizer {
pub fn new(va_list_size: u32, pointer_size: u32) -> Self {
Self {
va_list_size,
reg_save_area: true,
pointer_size,
}
}
pub fn lower_vastart(&self, va_list_ptr: VReg) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let offset0 = va_list_ptr.wrapping_add(100);
seq.push((
GOpcode::G_CONSTANT,
vec![MOperand::vreg(offset0), MOperand::imm(0)],
));
seq.push((
GOpcode::G_STORE,
vec![MOperand::vreg(offset0), MOperand::vreg(va_list_ptr)],
));
seq
}
pub fn lower_vaarg(&self, dst: VReg, va_list_ptr: VReg) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let offset_val = dst.wrapping_add(100);
seq.push((
GOpcode::G_LOAD,
vec![MOperand::vreg(offset_val), MOperand::vreg(va_list_ptr)],
));
let overflow_ptr = dst.wrapping_add(101);
seq.push((
GOpcode::G_PTR_ADD,
vec![
MOperand::vreg(overflow_ptr),
MOperand::vreg(va_list_ptr),
MOperand::imm(0),
],
));
seq.push((
GOpcode::G_LOAD,
vec![MOperand::vreg(dst), MOperand::vreg(overflow_ptr)],
));
seq.push((
GOpcode::G_ADD,
vec![
MOperand::vreg(offset_val),
MOperand::vreg(offset_val),
MOperand::imm(8),
],
));
seq.push((
GOpcode::G_STORE,
vec![MOperand::vreg(offset_val), MOperand::vreg(va_list_ptr)],
));
seq
}
pub fn lower_vaend(&self) -> Vec<(GOpcode, Vec<MOperand>)> {
Vec::new()
}
pub fn lower_vacopy(&self, dst_ptr: VReg, src_ptr: VReg) -> Vec<(GOpcode, Vec<MOperand>)> {
let mut seq = Vec::new();
let temp = dst_ptr.wrapping_add(100);
seq.push((
GOpcode::G_LOAD,
vec![MOperand::vreg(temp), MOperand::vreg(src_ptr)],
));
seq.push((
GOpcode::G_STORE,
vec![MOperand::vreg(temp), MOperand::vreg(dst_ptr)],
));
seq
}
}
impl Default for VarargsLegalizer {
fn default() -> Self {
Self::new(24, 64)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_mf() -> GMachineFunction {
let mut mf = GMachineFunction::new("test".to_string());
let entry = mf.push_block("entry".to_string());
let inst = GInstruction::with_operands(
GOpcode::G_ADD,
vec![MOperand::vreg(0), MOperand::vreg(1), MOperand::vreg(2)],
);
mf.blocks[entry].push_instruction(inst);
let inst2 = GInstruction::with_operands(
GOpcode::G_SDIV,
vec![MOperand::vreg(3), MOperand::vreg(4), MOperand::vreg(5)],
);
mf.blocks[entry].push_instruction(inst2);
mf
}
#[test]
fn test_type_bit_width() {
assert_eq!(Type::S1.bit_width(), 1);
assert_eq!(Type::S8.bit_width(), 8);
assert_eq!(Type::S32.bit_width(), 32);
assert_eq!(Type::S64.bit_width(), 64);
assert_eq!(Type::F32.bit_width(), 32);
assert_eq!(Type::F64.bit_width(), 64);
}
#[test]
fn test_type_is_floating_point() {
assert!(Type::F32.is_floating_point());
assert!(!Type::S32.is_floating_point());
}
#[test]
fn test_condition_always() {
assert!(LegalizeCondition::Always.evaluate(None));
assert!(LegalizeCondition::Always.evaluate(Some(Type::S32)));
}
#[test]
fn test_condition_narrow_scalar() {
let cond = LegalizeCondition::NarrowScalar(32);
assert!(cond.evaluate(Some(Type::S8)));
assert!(cond.evaluate(Some(Type::S16)));
assert!(!cond.evaluate(Some(Type::S32)));
assert!(!cond.evaluate(Some(Type::S64)));
}
#[test]
fn test_condition_wide_scalar() {
let cond = LegalizeCondition::WideScalar(32);
assert!(cond.evaluate(Some(Type::S64)));
assert!(!cond.evaluate(Some(Type::S32)));
assert!(!cond.evaluate(Some(Type::S8)));
}
#[test]
fn test_condition_type_is() {
let cond = LegalizeCondition::TypeIs(Type::S32);
assert!(cond.evaluate(Some(Type::S32)));
assert!(!cond.evaluate(Some(Type::S64)));
assert!(!cond.evaluate(None));
}
#[test]
fn test_legalizer_info_new() {
let info = LegalizerInfo::new();
assert_eq!(info.rule_count(), 0);
}
#[test]
fn test_legalizer_info_add_rule() {
let mut info = LegalizerInfo::new();
info.add_rule(LegalizeRule::new(
GOpcode::G_ADD,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
assert_eq!(info.rule_count(), 1);
}
#[test]
fn test_legalizer_info_get_action() {
let mut info = LegalizerInfo::new();
info.add_rule(LegalizeRule::new(
GOpcode::G_ADD,
LegalizeCondition::Always,
LegalizeAction::Legal,
));
let action = info.get_action(GOpcode::G_ADD, None);
assert!(matches!(action, Some(LegalizeAction::Legal)));
}
#[test]
fn test_legalizer_info_get_action_no_match() {
let info = LegalizerInfo::new();
assert!(info.get_action(GOpcode::G_ADD, None).is_none());
}
#[test]
fn test_default_aarch64() {
let info = LegalizerInfo::default_aarch64();
assert!(info.rule_count() > 0);
let action = info.get_action(GOpcode::G_ADD, Some(Type::S32));
assert!(matches!(action, Some(LegalizeAction::Legal)));
}
#[test]
fn test_default_arm32() {
let info = LegalizerInfo::default_arm32();
assert!(info.rule_count() > 0);
}
#[test]
fn test_default_info() {
let info = LegalizerInfo::default();
assert_eq!(info.rule_count(), 0);
}
#[test]
fn test_legalizer_new() {
let legalizer = GISelLegalizer::new();
assert_eq!(legalizer.legalized, 0);
assert_eq!(legalizer.already_legal_count(), 0);
assert_eq!(legalizer.lowered_count(), 0);
assert_eq!(legalizer.libcalls_count(), 0);
}
#[test]
fn test_legalizer_default() {
let legalizer = GISelLegalizer::default();
assert_eq!(legalizer.legalized, 0);
}
#[test]
fn test_legalize_empty_function() {
let mut legalizer = GISelLegalizer::new();
let mut mf = GMachineFunction::new("empty".to_string());
mf.push_block("entry".to_string());
let info = LegalizerInfo::default_aarch64();
legalizer.legalize(&mut mf, &info);
assert_eq!(legalizer.legalized, 0);
}
#[test]
fn test_legalize_basic() {
let mut legalizer = GISelLegalizer::new();
let mut mf = make_test_mf();
let info = LegalizerInfo::default_aarch64();
legalizer.legalize(&mut mf, &info);
assert!(legalizer.legalized >= 1);
}
#[test]
fn test_reset_counters() {
let mut legalizer = GISelLegalizer::new();
let mut mf = make_test_mf();
let info = LegalizerInfo::default_aarch64();
legalizer.legalize(&mut mf, &info);
assert!(legalizer.legalized > 0);
legalizer.reset();
assert_eq!(legalizer.legalized, 0);
}
#[test]
fn test_max_iterations() {
let mut legalizer = GISelLegalizer::new();
assert_eq!(legalizer.max_iterations(), 32);
legalizer.set_max_iterations(10);
assert_eq!(legalizer.max_iterations(), 10);
legalizer.set_max_iterations(0); assert_eq!(legalizer.max_iterations(), 1);
}
#[test]
fn test_legalize_rule_new() {
let rule = LegalizeRule::new(
GOpcode::G_ADD,
LegalizeCondition::Always,
LegalizeAction::Legal,
);
assert_eq!(rule.opcode, GOpcode::G_ADD);
assert!(matches!(rule.action, LegalizeAction::Legal));
}
#[test]
fn test_narrow_scalar_new() {
let nl = NarrowScalarLegalizer::new(64);
assert_eq!(nl.max_width, 64);
}
#[test]
fn test_narrow_scalar_needs_narrowing() {
let nl = NarrowScalarLegalizer::new(64);
assert!(!nl.needs_narrowing(32));
assert!(nl.needs_narrowing(128));
}
#[test]
fn test_narrow_scalar_add() {
let nl = NarrowScalarLegalizer::new(64);
let seq = nl.narrow_add(10, 11, 20, 21);
assert!(!seq.is_empty());
assert!(seq.len() >= 2);
}
#[test]
fn test_narrow_scalar_sub() {
let nl = NarrowScalarLegalizer::new(64);
let seq = nl.narrow_sub(10, 11, 20, 21);
assert!(!seq.is_empty());
}
#[test]
fn test_widen_scalar_new() {
let wl = WidenScalarLegalizer::new(32);
assert_eq!(wl.min_width, 32);
}
#[test]
fn test_widen_scalar_needs_widening() {
let wl = WidenScalarLegalizer::new(32);
assert!(wl.needs_widening(8));
assert!(!wl.needs_widening(64));
}
#[test]
fn test_widen_scalar_binary_op() {
let wl = WidenScalarLegalizer::new(32);
let seq = wl.widen_binary_op(GOpcode::G_ADD, 100, 10, 20, 8);
assert!(!seq.is_empty());
assert!(seq.len() >= 3);
}
#[test]
fn test_bitmanip_lowerer_new() {
let bl = BitManipLowerer::new();
assert!(!bl.has_ctlz);
}
#[test]
fn test_lower_ctlz() {
let bl = BitManipLowerer::new();
let seq = bl.lower_ctlz(100, 10, 32);
assert!(!seq.is_empty());
}
#[test]
fn test_lower_cttz() {
let bl = BitManipLowerer::new();
let seq = bl.lower_cttz(100, 10, 32);
assert!(!seq.is_empty());
}
#[test]
fn test_lower_ctpop() {
let bl = BitManipLowerer::new();
let seq = bl.lower_ctpop(100, 10, 32);
assert!(!seq.is_empty());
}
#[test]
fn test_lower_bswap() {
let bl = BitManipLowerer::new();
let seq = bl.lower_bswap(100, 10, 32);
assert!(!seq.is_empty());
}
#[test]
fn test_funnel_shift_new() {
let fl = FunnelShiftLowerer::new();
assert!(!fl.has_funnel_shift);
}
#[test]
fn test_lower_fshl() {
let fl = FunnelShiftLowerer::new();
let seq = fl.lower_fshl(100, 10, 20, 30);
assert!(!seq.is_empty());
}
#[test]
fn test_lower_fshr() {
let fl = FunnelShiftLowerer::new();
let seq = fl.lower_fshr(100, 10, 20, 30);
assert!(!seq.is_empty());
}
#[test]
fn test_vector_element_new() {
let vl = VectorElementLegalizer::new(256, 64);
assert_eq!(vl.max_vector_bits, 256);
assert_eq!(vl.min_vector_bits, 64);
}
#[test]
fn test_fewer_elements() {
let vl = VectorElementLegalizer::new(256, 64);
let seq = vl.fewer_elements(100, 256, 2);
assert_eq!(seq.len(), 2);
}
#[test]
fn test_more_elements() {
let vl = VectorElementLegalizer::new(256, 64);
let seq = vl.more_elements(100, &[10, 20]);
assert!(!seq.is_empty());
}
#[test]
fn test_varargs_legalizer_new() {
let vl = VarargsLegalizer::new(24, 64);
assert_eq!(vl.va_list_size, 24);
assert_eq!(vl.pointer_size, 64);
}
#[test]
fn test_lower_vastart() {
let vl = VarargsLegalizer::new(24, 64);
let seq = vl.lower_vastart(100);
assert!(!seq.is_empty());
}
#[test]
fn test_lower_vaarg() {
let vl = VarargsLegalizer::new(24, 64);
let seq = vl.lower_vaarg(100, 200);
assert!(!seq.is_empty());
}
#[test]
fn test_lower_vaend() {
let vl = VarargsLegalizer::new(24, 64);
let seq = vl.lower_vaend();
assert!(seq.is_empty());
}
#[test]
fn test_lower_vacopy() {
let vl = VarargsLegalizer::new(24, 64);
let seq = vl.lower_vacopy(100, 200);
assert!(!seq.is_empty());
}
}