use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MOpcode {
G_BR,
G_BRCOND,
G_BRINDIRECT,
G_BRJT,
G_RETURN,
G_ADD,
G_SUB,
G_MUL,
G_SDIV,
G_UDIV,
G_SREM,
G_UREM,
G_AND,
G_OR,
G_XOR,
G_SHL,
G_LSHR,
G_ASHR,
G_FADD,
G_FSUB,
G_FMUL,
G_FDIV,
G_FREM,
G_FNEG,
G_FABS,
G_FSQRT,
G_FMA,
G_ICMP,
G_FCMP,
G_TRUNC,
G_ZEXT,
G_SEXT,
G_FPTRUNC,
G_FPEXT,
G_FPTOUI,
G_FPTOSI,
G_UITOFP,
G_SITOFP,
G_BITCAST,
G_PTRTOINT,
G_INTTOPTR,
G_ADDRSPACE_CAST,
G_LOAD,
G_STORE,
G_GEP,
G_FRAME_INDEX,
G_GLOBAL_VALUE,
G_CONSTANT,
G_EXTRACT_VEC_ELT,
G_INSERT_VEC_ELT,
G_BUILD_VECTOR,
G_SHUFFLE_VECTOR,
G_CONCAT_VECTORS,
G_INTRINSIC,
G_INTRINSIC_W_SIDE_EFFECTS,
G_VASTART,
G_VAARG,
G_VAEND,
G_VACOPY,
G_LANDINGPAD,
G_RESUME,
G_CLEANUPRET,
G_ATOMICRMW_XCHG,
G_ATOMICRMW_ADD,
G_ATOMICRMW_SUB,
G_ATOMICRMW_AND,
G_ATOMICRMW_NAND,
G_ATOMICRMW_OR,
G_ATOMICRMW_XOR,
G_ATOMICRMW_MIN,
G_ATOMICRMW_MAX,
G_ATOMICRMW_UMIN,
G_ATOMICRMW_UMAX,
G_ATOMIC_CMPXCHG,
G_STACKPROTECTOR,
G_STACKPROTECTOR_CHECK,
G_SELECT,
G_PHI,
G_UNREACHABLE,
G_CONSTANT_POOL,
G_JUMP_TABLE,
G_DYN_STACKALLOC,
G_DBG_VALUE,
G_DBG_LABEL,
G_DBG_DECLARE,
G_PATCHPOINT,
G_STACKMAP,
G_STATEPOINT,
G_INLINEASM,
}
impl MOpcode {
pub fn as_str(&self) -> &'static str {
match self {
MOpcode::G_BR => "G_BR",
MOpcode::G_BRCOND => "G_BRCOND",
MOpcode::G_BRINDIRECT => "G_BRINDIRECT",
MOpcode::G_BRJT => "G_BRJT",
MOpcode::G_RETURN => "G_RETURN",
MOpcode::G_ADD => "G_ADD",
MOpcode::G_SUB => "G_SUB",
MOpcode::G_MUL => "G_MUL",
MOpcode::G_SDIV => "G_SDIV",
MOpcode::G_UDIV => "G_UDIV",
MOpcode::G_SREM => "G_SREM",
MOpcode::G_UREM => "G_UREM",
MOpcode::G_AND => "G_AND",
MOpcode::G_OR => "G_OR",
MOpcode::G_XOR => "G_XOR",
MOpcode::G_SHL => "G_SHL",
MOpcode::G_LSHR => "G_LSHR",
MOpcode::G_ASHR => "G_ASHR",
MOpcode::G_FADD => "G_FADD",
MOpcode::G_FSUB => "G_FSUB",
MOpcode::G_FMUL => "G_FMUL",
MOpcode::G_FDIV => "G_FDIV",
MOpcode::G_FREM => "G_FREM",
MOpcode::G_FNEG => "G_FNEG",
MOpcode::G_FABS => "G_FABS",
MOpcode::G_FSQRT => "G_FSQRT",
MOpcode::G_FMA => "G_FMA",
MOpcode::G_ICMP => "G_ICMP",
MOpcode::G_FCMP => "G_FCMP",
MOpcode::G_TRUNC => "G_TRUNC",
MOpcode::G_ZEXT => "G_ZEXT",
MOpcode::G_SEXT => "G_SEXT",
MOpcode::G_FPTRUNC => "G_FPTRUNC",
MOpcode::G_FPEXT => "G_FPEXT",
MOpcode::G_FPTOUI => "G_FPTOUI",
MOpcode::G_FPTOSI => "G_FPTOSI",
MOpcode::G_UITOFP => "G_UITOFP",
MOpcode::G_SITOFP => "G_SITOFP",
MOpcode::G_BITCAST => "G_BITCAST",
MOpcode::G_PTRTOINT => "G_PTRTOINT",
MOpcode::G_INTTOPTR => "G_INTTOPTR",
MOpcode::G_ADDRSPACE_CAST => "G_ADDRSPACE_CAST",
MOpcode::G_LOAD => "G_LOAD",
MOpcode::G_STORE => "G_STORE",
MOpcode::G_GEP => "G_GEP",
MOpcode::G_FRAME_INDEX => "G_FRAME_INDEX",
MOpcode::G_GLOBAL_VALUE => "G_GLOBAL_VALUE",
MOpcode::G_CONSTANT => "G_CONSTANT",
MOpcode::G_EXTRACT_VEC_ELT => "G_EXTRACT_VEC_ELT",
MOpcode::G_INSERT_VEC_ELT => "G_INSERT_VEC_ELT",
MOpcode::G_BUILD_VECTOR => "G_BUILD_VECTOR",
MOpcode::G_SHUFFLE_VECTOR => "G_SHUFFLE_VECTOR",
MOpcode::G_CONCAT_VECTORS => "G_CONCAT_VECTORS",
MOpcode::G_INTRINSIC => "G_INTRINSIC",
MOpcode::G_INTRINSIC_W_SIDE_EFFECTS => "G_INTRINSIC_W_SIDE_EFFECTS",
MOpcode::G_VASTART => "G_VASTART",
MOpcode::G_VAARG => "G_VAARG",
MOpcode::G_VAEND => "G_VAEND",
MOpcode::G_VACOPY => "G_VACOPY",
MOpcode::G_LANDINGPAD => "G_LANDINGPAD",
MOpcode::G_RESUME => "G_RESUME",
MOpcode::G_CLEANUPRET => "G_CLEANUPRET",
MOpcode::G_ATOMICRMW_XCHG => "G_ATOMICRMW_XCHG",
MOpcode::G_ATOMICRMW_ADD => "G_ATOMICRMW_ADD",
MOpcode::G_ATOMICRMW_SUB => "G_ATOMICRMW_SUB",
MOpcode::G_ATOMICRMW_AND => "G_ATOMICRMW_AND",
MOpcode::G_ATOMICRMW_NAND => "G_ATOMICRMW_NAND",
MOpcode::G_ATOMICRMW_OR => "G_ATOMICRMW_OR",
MOpcode::G_ATOMICRMW_XOR => "G_ATOMICRMW_XOR",
MOpcode::G_ATOMICRMW_MIN => "G_ATOMICRMW_MIN",
MOpcode::G_ATOMICRMW_MAX => "G_ATOMICRMW_MAX",
MOpcode::G_ATOMICRMW_UMIN => "G_ATOMICRMW_UMIN",
MOpcode::G_ATOMICRMW_UMAX => "G_ATOMICRMW_UMAX",
MOpcode::G_ATOMIC_CMPXCHG => "G_ATOMIC_CMPXCHG",
MOpcode::G_STACKPROTECTOR => "G_STACKPROTECTOR",
MOpcode::G_STACKPROTECTOR_CHECK => "G_STACKPROTECTOR_CHECK",
MOpcode::G_SELECT => "G_SELECT",
MOpcode::G_PHI => "G_PHI",
MOpcode::G_UNREACHABLE => "G_UNREACHABLE",
MOpcode::G_CONSTANT_POOL => "G_CONSTANT_POOL",
MOpcode::G_JUMP_TABLE => "G_JUMP_TABLE",
MOpcode::G_DYN_STACKALLOC => "G_DYN_STACKALLOC",
MOpcode::G_DBG_VALUE => "G_DBG_VALUE",
MOpcode::G_DBG_LABEL => "G_DBG_LABEL",
MOpcode::G_DBG_DECLARE => "G_DBG_DECLARE",
MOpcode::G_PATCHPOINT => "G_PATCHPOINT",
MOpcode::G_STACKMAP => "G_STACKMAP",
MOpcode::G_STATEPOINT => "G_STATEPOINT",
MOpcode::G_INLINEASM => "G_INLINEASM",
}
}
}
pub fn ir_opcode_to_mopcode(ir_op: &str) -> Option<MOpcode> {
match ir_op {
"br" => Some(MOpcode::G_BR),
"brcond" => Some(MOpcode::G_BRCOND),
"switch" => Some(MOpcode::G_BRJT),
"ret" => Some(MOpcode::G_RETURN),
"add" => Some(MOpcode::G_ADD),
"sub" => Some(MOpcode::G_SUB),
"mul" => Some(MOpcode::G_MUL),
"sdiv" => Some(MOpcode::G_SDIV),
"udiv" => Some(MOpcode::G_UDIV),
"srem" => Some(MOpcode::G_SREM),
"urem" => Some(MOpcode::G_UREM),
"and" => Some(MOpcode::G_AND),
"or" => Some(MOpcode::G_OR),
"xor" => Some(MOpcode::G_XOR),
"shl" => Some(MOpcode::G_SHL),
"lshr" => Some(MOpcode::G_LSHR),
"ashr" => Some(MOpcode::G_ASHR),
"fadd" => Some(MOpcode::G_FADD),
"fsub" => Some(MOpcode::G_FSUB),
"fmul" => Some(MOpcode::G_FMUL),
"fdiv" => Some(MOpcode::G_FDIV),
"frem" => Some(MOpcode::G_FREM),
"fneg" => Some(MOpcode::G_FNEG),
"icmp" => Some(MOpcode::G_ICMP),
"fcmp" => Some(MOpcode::G_FCMP),
"trunc" => Some(MOpcode::G_TRUNC),
"zext" => Some(MOpcode::G_ZEXT),
"sext" => Some(MOpcode::G_SEXT),
"fptrunc" => Some(MOpcode::G_FPTRUNC),
"fpext" => Some(MOpcode::G_FPEXT),
"fptoui" => Some(MOpcode::G_FPTOUI),
"fptosi" => Some(MOpcode::G_FPTOSI),
"uitofp" => Some(MOpcode::G_UITOFP),
"sitofp" => Some(MOpcode::G_SITOFP),
"bitcast" => Some(MOpcode::G_BITCAST),
"inttoptr" => Some(MOpcode::G_INTTOPTR),
"ptrtoint" => Some(MOpcode::G_PTRTOINT),
"load" => Some(MOpcode::G_LOAD),
"store" => Some(MOpcode::G_STORE),
"getelementptr" => Some(MOpcode::G_GEP),
"extractelement" => Some(MOpcode::G_EXTRACT_VEC_ELT),
"insertelement" => Some(MOpcode::G_INSERT_VEC_ELT),
"shufflevector" => Some(MOpcode::G_SHUFFLE_VECTOR),
"select" => Some(MOpcode::G_SELECT),
"phi" => Some(MOpcode::G_PHI),
"unreachable" => Some(MOpcode::G_UNREACHABLE),
"landingpad" => Some(MOpcode::G_LANDINGPAD),
"resume" => Some(MOpcode::G_RESUME),
"va_arg" => Some(MOpcode::G_VAARG),
"atomicrmw" => Some(MOpcode::G_ATOMICRMW_XCHG),
"cmpxchg" => Some(MOpcode::G_ATOMIC_CMPXCHG),
"alloca" => Some(MOpcode::G_FRAME_INDEX),
_ => None,
}
}
pub fn is_terminator(op: MOpcode) -> bool {
matches!(
op,
MOpcode::G_BR
| MOpcode::G_BRCOND
| MOpcode::G_BRINDIRECT
| MOpcode::G_BRJT
| MOpcode::G_RETURN
| MOpcode::G_UNREACHABLE
| MOpcode::G_CLEANUPRET
)
}
pub fn may_load(op: MOpcode) -> bool {
matches!(
op,
MOpcode::G_LOAD
| MOpcode::G_ATOMICRMW_XCHG
| MOpcode::G_ATOMICRMW_ADD
| MOpcode::G_ATOMICRMW_SUB
| MOpcode::G_ATOMICRMW_AND
| MOpcode::G_ATOMICRMW_NAND
| MOpcode::G_ATOMICRMW_OR
| MOpcode::G_ATOMICRMW_XOR
| MOpcode::G_ATOMICRMW_MIN
| MOpcode::G_ATOMICRMW_MAX
| MOpcode::G_ATOMICRMW_UMIN
| MOpcode::G_ATOMICRMW_UMAX
| MOpcode::G_ATOMIC_CMPXCHG
| MOpcode::G_VAARG
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeLegalAction {
Legal,
WidenScalar,
NarrowScalar,
WidenVector,
SplitVector,
Scalarize,
Unsupported,
}
pub struct LegalizeInfo {
scalar_actions: HashMap<u32, TypeLegalAction>,
vector_actions: HashMap<(u32, u32), TypeLegalAction>,
max_legal_scalar: u32,
min_legal_scalar: u32,
}
impl LegalizeInfo {
pub fn new() -> Self {
Self {
scalar_actions: HashMap::new(),
vector_actions: HashMap::new(),
max_legal_scalar: 64,
min_legal_scalar: 8,
}
}
pub fn set_scalar_legal(&mut self, bits: u32) {
self.scalar_actions.insert(bits, TypeLegalAction::Legal);
}
pub fn set_scalar_widen_to(&mut self, bits: u32, target_bits: u32) {
self.scalar_actions
.insert(bits, TypeLegalAction::WidenScalar);
self.scalar_actions
.insert(target_bits, TypeLegalAction::Legal);
}
pub fn get_scalar_action(&self, bits: u32) -> TypeLegalAction {
self.scalar_actions
.get(&bits)
.copied()
.unwrap_or(TypeLegalAction::Unsupported)
}
pub fn set_vector_legal(&mut self, num_elements: u32, elem_bits: u32) {
self.vector_actions
.insert((num_elements, elem_bits), TypeLegalAction::Legal);
}
pub fn get_vector_action(&self, num_elements: u32, elem_bits: u32) -> TypeLegalAction {
self.vector_actions
.get(&(num_elements, elem_bits))
.copied()
.unwrap_or(TypeLegalAction::Scalarize)
}
}
impl Default for LegalizeInfo {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgPassing {
InRegister,
OnStack,
SplitReg,
Indirect,
}
pub struct ABILowering {
pub triple: String,
pub pointer_width: u32,
pub max_int_arg_regs: u32,
pub max_fp_arg_regs: u32,
pub arg_assignments: HashMap<u32, ArgPassing>,
}
impl ABILowering {
pub fn new(triple: &str) -> Self {
let pointer_width = if triple.starts_with("x86_64") { 64 } else { 32 };
let max_int = if triple.starts_with("x86_64") { 6 } else { 0 };
let max_fp = if triple.starts_with("x86_64") { 8 } else { 0 };
Self {
triple: triple.to_string(),
pointer_width,
max_int_arg_regs: max_int,
max_fp_arg_regs: max_fp,
arg_assignments: HashMap::new(),
}
}
pub fn classify_arg(&self, arg_idx: u32, ty_bits: u32, is_fp: bool) -> ArgPassing {
if is_fp && arg_idx < self.max_fp_arg_regs {
ArgPassing::InRegister
} else if !is_fp && arg_idx < self.max_int_arg_regs {
if ty_bits > self.pointer_width {
ArgPassing::SplitReg
} else {
ArgPassing::InRegister
}
} else {
ArgPassing::OnStack
}
}
pub fn assign_args(&mut self, num_args: u32) {
self.arg_assignments.clear();
for i in 0..num_args {
let passing = if i < self.max_int_arg_regs {
ArgPassing::InRegister
} else {
ArgPassing::OnStack
};
self.arg_assignments.insert(i, passing);
}
}
}
pub enum SwitchLoweringStrategy {
JumpTable {
base: u64,
default_block: u32,
cases: Vec<(u64, u32)>,
},
BinarySearch {
default_block: u32,
cases: Vec<(u64, u32)>,
},
BitTest {
base: u64,
num_bits: u32,
default_block: u32,
case_blocks: Vec<u32>,
},
ConditionalChain {
default_block: u32,
cases: Vec<(u64, u32)>,
},
}
pub struct SwitchLowering {
pub min_jump_table_size: usize,
pub max_binary_search_density: u64,
}
impl SwitchLowering {
pub fn new() -> Self {
Self {
min_jump_table_size: 4,
max_binary_search_density: 10,
}
}
pub fn select_strategy(
&self,
cases: &[(u64, u32)],
default_block: u32,
) -> SwitchLoweringStrategy {
if cases.is_empty() {
return SwitchLoweringStrategy::BinarySearch {
default_block,
cases: Vec::new(),
};
}
let min_val = cases.iter().map(|(v, _)| *v).min().unwrap();
let max_val = cases.iter().map(|(v, _)| *v).max().unwrap();
let range = max_val - min_val;
let density = range / cases.len() as u64;
if cases.len() >= self.min_jump_table_size && density <= self.max_binary_search_density {
let mut jump_cases = Vec::new();
for &(val, block) in cases {
jump_cases.push((val - min_val, block));
}
SwitchLoweringStrategy::JumpTable {
base: min_val,
default_block,
cases: jump_cases,
}
} else if cases.len() <= 8 {
SwitchLoweringStrategy::ConditionalChain {
default_block,
cases: cases.to_vec(),
}
} else {
SwitchLoweringStrategy::BinarySearch {
default_block,
cases: cases.to_vec(),
}
}
}
}
impl Default for SwitchLowering {
fn default() -> Self {
Self::new()
}
}
pub struct SelectLowering {
pub has_conditional_move: bool,
}
impl SelectLowering {
pub fn new() -> Self {
Self {
has_conditional_move: false,
}
}
pub fn should_lower_to_branch(&self, _true_block: u32, _false_block: u32) -> bool {
!self.has_conditional_move
}
}
impl Default for SelectLowering {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct LandingPadInfo {
pub block_id: u32,
pub catch_types: Vec<u32>,
pub filter_types: Vec<u32>,
pub cleanup: bool,
}
pub struct LandingPadLowering {
pub pads: Vec<LandingPadInfo>,
}
impl LandingPadLowering {
pub fn new() -> Self {
Self { pads: Vec::new() }
}
pub fn add_pad(&mut self, block_id: u32, catch_types: Vec<u32>, cleanup: bool) {
self.pads.push(LandingPadInfo {
block_id,
catch_types,
filter_types: Vec::new(),
cleanup,
});
}
}
pub struct VAArgLowering {
pub is_x86_64: bool,
}
impl VAArgLowering {
pub fn new(is_x86_64: bool) -> Self {
Self { is_x86_64 }
}
pub fn num_int_regs(&self) -> u32 {
if self.is_x86_64 {
6
} else {
0
}
}
pub fn num_fp_regs(&self) -> u32 {
if self.is_x86_64 {
8
} else {
0
}
}
}
pub struct StackProtectorLowering {
pub enabled: bool,
pub canary_offset: i32,
}
impl StackProtectorLowering {
pub fn new() -> Self {
Self {
enabled: true,
canary_offset: -8,
}
}
pub fn should_protect(&self, has_local_array: bool) -> bool {
self.enabled && has_local_array
}
}
impl Default for StackProtectorLowering {
fn default() -> Self {
Self::new()
}
}
pub struct GCSafepointLowering {
pub next_id: u64,
}
#[derive(Debug, Clone)]
pub struct StackMapRecord {
pub id: u64,
pub offset: u32,
pub locations: Vec<StackMapLocation>,
}
#[derive(Debug, Clone)]
pub enum StackMapLocation {
Register(u32),
Direct(u32),
Indirect(u32, i32),
Constant(i64),
ConstantIndex(u32),
}
impl GCSafepointLowering {
pub fn new() -> Self {
Self { next_id: 0 }
}
pub fn next_stackmap_id(&mut self) -> u64 {
let id = self.next_id;
self.next_id += 1;
id
}
}
impl Default for GCSafepointLowering {
fn default() -> Self {
Self::new()
}
}
pub struct InlineAsmLowering {
pub target_arch: String,
}
#[derive(Debug, Clone)]
pub struct InlineAsmInfo {
pub asm_string: String,
pub constraints: String,
pub has_side_effects: bool,
pub is_align_stack: bool,
pub inputs: Vec<InlineAsmOperand>,
pub outputs: Vec<InlineAsmOperand>,
pub clobbers: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct InlineAsmOperand {
pub reg_num: u32,
pub constraint: String,
pub is_output: bool,
}
impl InlineAsmLowering {
pub fn new(arch: &str) -> Self {
Self {
target_arch: arch.to_string(),
}
}
pub fn parse_constraints(&self, constraints: &str) -> Vec<InlineAsmOperand> {
let mut operands = Vec::new();
let parts: Vec<&str> = constraints.split(',').collect();
for (i, part) in parts.iter().enumerate() {
let trimmed = part.trim();
if !trimmed.is_empty() {
operands.push(InlineAsmOperand {
reg_num: i as u32,
constraint: trimmed.to_string(),
is_output: trimmed.starts_with('='),
});
}
}
operands
}
}
pub struct DebugInfoLowering {
pub next_label_id: u32,
}
#[derive(Debug, Clone)]
pub struct MachineDbgValue {
pub variable: String,
pub expression: String,
pub reg: Option<u32>,
pub stack_offset: Option<i32>,
pub location: Option<String>,
}
impl DebugInfoLowering {
pub fn new() -> Self {
Self { next_label_id: 0 }
}
pub fn create_dbg_value_reg(
&mut self,
variable: &str,
expression: &str,
reg: u32,
loc: Option<&str>,
) -> MachineDbgValue {
MachineDbgValue {
variable: variable.to_string(),
expression: expression.to_string(),
reg: Some(reg),
stack_offset: None,
location: loc.map(|s| s.to_string()),
}
}
pub fn create_dbg_value_stack(
&mut self,
variable: &str,
expression: &str,
offset: i32,
loc: Option<&str>,
) -> MachineDbgValue {
MachineDbgValue {
variable: variable.to_string(),
expression: expression.to_string(),
reg: None,
stack_offset: Some(offset),
location: loc.map(|s| s.to_string()),
}
}
pub fn next_label_id(&mut self) -> u32 {
let id = self.next_label_id;
self.next_label_id += 1;
id
}
}
impl Default for DebugInfoLowering {
fn default() -> Self {
Self::new()
}
}
pub struct IRTranslator {
pub legalize_info: LegalizeInfo,
pub abi_lowering: ABILowering,
pub switch_lowering: SwitchLowering,
pub select_lowering: SelectLowering,
pub landing_pad_lowering: LandingPadLowering,
pub vaarg_lowering: VAArgLowering,
pub stack_protector: StackProtectorLowering,
pub gc_lowering: GCSafepointLowering,
pub inline_asm: InlineAsmLowering,
pub debug_info: DebugInfoLowering,
pub instructions_translated: usize,
pub opcodes_unmapped: usize,
}
impl IRTranslator {
pub fn new(target_triple: &str) -> Self {
let mut legalize = LegalizeInfo::new();
for bits in &[1, 8, 16, 32, 64] {
legalize.set_scalar_legal(*bits);
}
Self {
legalize_info: legalize,
abi_lowering: ABILowering::new(target_triple),
switch_lowering: SwitchLowering::new(),
select_lowering: SelectLowering::new(),
landing_pad_lowering: LandingPadLowering::new(),
vaarg_lowering: VAArgLowering::new(target_triple.starts_with("x86_64")),
stack_protector: StackProtectorLowering::new(),
gc_lowering: GCSafepointLowering::new(),
inline_asm: InlineAsmLowering::new(if target_triple.starts_with("x86_64") {
"x86_64"
} else if target_triple.starts_with("aarch64") {
"aarch64"
} else if target_triple.starts_with("riscv64") {
"riscv64"
} else {
"unknown"
}),
debug_info: DebugInfoLowering::new(),
instructions_translated: 0,
opcodes_unmapped: 0,
}
}
pub fn translate_opcode(&mut self, ir_op: &str) -> Option<MOpcode> {
match ir_opcode_to_mopcode(ir_op) {
Some(mop) => {
self.instructions_translated += 1;
Some(mop)
}
None => {
self.opcodes_unmapped += 1;
None
}
}
}
pub fn stats_summary(&self) -> String {
format!(
"IRTranslator: {} instructions translated, {} opcodes unmapped",
self.instructions_translated, self.opcodes_unmapped
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ir_to_mopcode_add() {
assert_eq!(ir_opcode_to_mopcode("add"), Some(MOpcode::G_ADD));
}
#[test]
fn test_ir_to_mopcode_sub() {
assert_eq!(ir_opcode_to_mopcode("sub"), Some(MOpcode::G_SUB));
}
#[test]
fn test_ir_to_mopcode_load() {
assert_eq!(ir_opcode_to_mopcode("load"), Some(MOpcode::G_LOAD));
}
#[test]
fn test_ir_to_mopcode_store() {
assert_eq!(ir_opcode_to_mopcode("store"), Some(MOpcode::G_STORE));
}
#[test]
fn test_ir_to_mopcode_unknown() {
assert_eq!(ir_opcode_to_mopcode("unknown_op"), None);
}
#[test]
fn test_is_terminator() {
assert!(is_terminator(MOpcode::G_BR));
assert!(is_terminator(MOpcode::G_RETURN));
assert!(!is_terminator(MOpcode::G_ADD));
}
#[test]
fn test_may_load() {
assert!(may_load(MOpcode::G_LOAD));
assert!(!may_load(MOpcode::G_STORE));
assert!(!may_load(MOpcode::G_ADD));
}
#[test]
fn test_legalize_info_scalar() {
let mut info = LegalizeInfo::new();
info.set_scalar_legal(32);
assert_eq!(info.get_scalar_action(32), TypeLegalAction::Legal);
assert_eq!(info.get_scalar_action(8), TypeLegalAction::Unsupported);
}
#[test]
fn test_abi_lowering_x86_64() {
let abi = ABILowering::new("x86_64-linux-gnu");
assert_eq!(abi.pointer_width, 64);
assert_eq!(abi.max_int_arg_regs, 6);
assert_eq!(abi.max_fp_arg_regs, 8);
}
#[test]
fn test_abi_lowering_classify() {
let abi = ABILowering::new("x86_64-linux-gnu");
assert_eq!(abi.classify_arg(0, 32, false), ArgPassing::InRegister);
assert_eq!(abi.classify_arg(10, 32, false), ArgPassing::OnStack);
}
#[test]
fn test_switch_dense() {
let sl = SwitchLowering::new();
let cases = vec![(10, 2), (11, 3), (12, 4), (13, 5), (14, 6)];
match sl.select_strategy(&cases, 1) {
SwitchLoweringStrategy::JumpTable {
base,
default_block,
..
} => {
assert_eq!(base, 10);
assert_eq!(default_block, 1);
}
_ => panic!("Expected JumpTable strategy"),
}
}
#[test]
fn test_switch_sparse() {
let sl = SwitchLowering::new();
let cases = vec![(1, 2), (100, 3)];
match sl.select_strategy(&cases, 1) {
SwitchLoweringStrategy::ConditionalChain { .. } => {}
_ => {} }
}
#[test]
fn test_select_lowering() {
let sl = SelectLowering::new();
assert!(sl.should_lower_to_branch(0, 0));
}
#[test]
fn test_va_arg_lowering() {
let val = VAArgLowering::new(true);
assert_eq!(val.num_int_regs(), 6);
assert_eq!(val.num_fp_regs(), 8);
}
#[test]
fn test_stack_protector() {
let sp = StackProtectorLowering::new();
assert!(sp.should_protect(true));
assert!(!sp.should_protect(false));
}
#[test]
fn test_gc_lowering() {
let mut gc = GCSafepointLowering::new();
assert_eq!(gc.next_stackmap_id(), 0);
assert_eq!(gc.next_stackmap_id(), 1);
}
#[test]
fn test_inline_asm_constraints() {
let iasm = InlineAsmLowering::new("x86_64");
let ops = iasm.parse_constraints("=r,r,m");
assert_eq!(ops.len(), 3);
assert!(ops[0].is_output);
assert!(!ops[1].is_output);
}
#[test]
fn test_debug_info_lowering() {
let mut dil = DebugInfoLowering::new();
let dbg = dil.create_dbg_value_reg("x", "DW_OP_reg", 5, None);
assert_eq!(dbg.variable, "x");
assert_eq!(dbg.reg, Some(5));
}
#[test]
fn test_ir_translator_new() {
let translator = IRTranslator::new("x86_64-linux-gnu");
assert!(translator.instructions_translated == 0);
}
#[test]
fn test_ir_translator_translate() {
let mut translator = IRTranslator::new("x86_64-linux-gnu");
let result = translator.translate_opcode("add");
assert_eq!(result, Some(MOpcode::G_ADD));
assert_eq!(translator.instructions_translated, 1);
}
#[test]
fn test_ir_translator_unknown() {
let mut translator = IRTranslator::new("x86_64-linux-gnu");
let result = translator.translate_opcode("nonexistent");
assert_eq!(result, None);
assert_eq!(translator.opcodes_unmapped, 1);
}
}
#[derive(Debug, Clone)]
pub struct CCValAssign {
pub arg_idx: u32,
pub width: u32,
pub loc: CCLocation,
pub needs_ext: Option<SignExt>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CCLocation {
Reg(u32),
Mem(i32),
SplitReg { lo: u32, hi: u32 },
Indirect(i32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignExt {
SExt,
ZExt,
}
pub struct CallingConvLowering {
pub target: String,
pub pointer_width: u32,
pub is_64bit: bool,
}
impl CallingConvLowering {
pub fn new(target: &str) -> Self {
let is_64bit = target.starts_with("x86_64") || target.starts_with("aarch64");
CallingConvLowering {
target: target.to_string(),
pointer_width: if is_64bit { 64 } else { 32 },
is_64bit,
}
}
pub fn classify_return_type(&self, ty_bits: u32, is_fp: bool) -> Vec<CCValAssign> {
if is_fp && ty_bits <= 64 {
vec![CCValAssign {
arg_idx: 0,
width: ty_bits,
loc: CCLocation::Reg(0),
needs_ext: None,
}]
} else if ty_bits <= self.pointer_width {
vec![CCValAssign {
arg_idx: 0,
width: ty_bits,
loc: CCLocation::Reg(0),
needs_ext: if ty_bits < 32 {
Some(SignExt::ZExt)
} else {
None
},
}]
} else if ty_bits <= 128 && self.is_64bit {
vec![CCValAssign {
arg_idx: 0,
width: ty_bits / 2,
loc: CCLocation::SplitReg { lo: 0, hi: 1 },
needs_ext: None,
}]
} else {
vec![CCValAssign {
arg_idx: 0,
width: self.pointer_width,
loc: CCLocation::Indirect(0),
needs_ext: None,
}]
}
}
pub fn classify_argument(
&self,
arg_idx: u32,
ty_bits: u32,
is_fp: bool,
next_int_reg: &mut u32,
next_fp_reg: &mut u32,
next_stack_offset: &mut i32,
) -> CCValAssign {
let max_int_regs = if self.is_64bit { 6 } else { 0 };
let max_fp_regs = if self.is_64bit { 8 } else { 0 };
if is_fp && *next_fp_reg < max_fp_regs {
let reg = *next_fp_reg;
*next_fp_reg += 1;
CCValAssign {
arg_idx,
width: ty_bits,
loc: CCLocation::Reg(reg),
needs_ext: None,
}
} else if !is_fp && *next_int_reg < max_int_regs {
let reg = *next_int_reg;
*next_int_reg += 1;
if ty_bits > self.pointer_width && *next_int_reg < max_int_regs {
let hi_reg = *next_int_reg;
*next_int_reg += 1;
CCValAssign {
arg_idx,
width: ty_bits,
loc: CCLocation::SplitReg { lo: reg, hi: hi_reg },
needs_ext: None,
}
} else {
CCValAssign {
arg_idx,
width: ty_bits,
loc: CCLocation::Reg(reg),
needs_ext: if ty_bits < 32 {
Some(SignExt::ZExt)
} else {
None
},
}
}
} else {
let offset = *next_stack_offset;
*next_stack_offset += 8;
CCValAssign {
arg_idx,
width: ty_bits,
loc: CCLocation::Mem(offset),
needs_ext: None,
}
}
}
}
#[derive(Debug, Clone)]
pub struct StackFrameInfo {
pub frame_size: u32,
pub alignment: u32,
pub return_address_offset: i32,
pub frame_pointer_offset: i32,
pub locals: Vec<(String, i32, u32)>,
pub callee_saved: Vec<(u32, i32)>,
pub has_frame_pointer: bool,
pub has_dynamic_alloca: bool,
}
impl StackFrameInfo {
pub fn new() -> Self {
StackFrameInfo {
frame_size: 0,
alignment: 16,
return_address_offset: 8,
frame_pointer_offset: 0,
locals: Vec::new(),
callee_saved: Vec::new(),
has_frame_pointer: false,
has_dynamic_alloca: false,
}
}
pub fn allocate_local(&mut self, name: &str, size: u32, align: u32) -> i32 {
let aligned_size = (size + align - 1) & !(align - 1);
self.frame_size += aligned_size;
let offset = -(self.frame_size as i32);
self.locals.push((name.to_string(), offset, size));
offset
}
pub fn reserve_callee_saved(&mut self, reg: u32, size: u32) -> i32 {
self.frame_size += size;
let offset = -(self.frame_size as i32);
self.callee_saved.push((reg, offset));
offset
}
}
impl Default for StackFrameInfo {
fn default() -> Self {
Self::new()
}
}
pub struct GISelCombiner {
pub num_combines: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CombineOp {
AddIdentity,
SubSame,
AndSame,
AndAllOnes,
OrZero,
MulOne,
MulZero,
ShlZero,
TruncOfZext,
SextOfTrunc,
FaddNegZero,
MergeIcmpAnd,
SelectOfIcmp,
}
impl GISelCombiner {
pub fn new() -> Self {
Self { num_combines: 0 }
}
pub fn try_combine(&mut self, _ops: &[MOpcode]) -> Option<CombineOp> {
self.num_combines += 1;
None }
}
impl Default for GISelCombiner {
fn default() -> Self {
Self::new()
}
}
pub struct MIRBuilder {
pub instructions: Vec<MIRInstruction>,
}
#[derive(Debug, Clone)]
pub struct MIRInstruction {
pub opcode: MOpcode,
pub defs: Vec<u32>,
pub uses: Vec<MIRUse>,
pub flags: MIRFlags,
}
#[derive(Debug, Clone)]
pub enum MIRUse {
Reg(u32),
Imm(i64),
FImm(f64),
Block(u32),
FrameIndex(i32),
}
#[derive(Debug, Clone, Default)]
pub struct MIRFlags {
pub is_terminator: bool,
pub may_load: bool,
pub may_store: bool,
pub has_side_effects: bool,
}
impl MIRBuilder {
pub fn new() -> Self {
MIRBuilder {
instructions: Vec::new(),
}
}
pub fn build_add(&mut self, def: u32, lhs: u32, rhs: u32) {
self.instructions.push(MIRInstruction {
opcode: MOpcode::G_ADD,
defs: vec![def],
uses: vec![MIRUse::Reg(lhs), MIRUse::Reg(rhs)],
flags: MIRFlags::default(),
});
}
pub fn build_load(&mut self, def: u32, addr: u32, size: u32) {
self.instructions.push(MIRInstruction {
opcode: MOpcode::G_LOAD,
defs: vec![def],
uses: vec![MIRUse::Reg(addr), MIRUse::Imm(size as i64)],
flags: MIRFlags {
may_load: true,
..Default::default()
},
});
}
pub fn build_store(&mut self, val: u32, addr: u32, size: u32) {
self.instructions.push(MIRInstruction {
opcode: MOpcode::G_STORE,
defs: Vec::new(),
uses: vec![
MIRUse::Reg(val),
MIRUse::Reg(addr),
MIRUse::Imm(size as i64),
],
flags: MIRFlags {
may_store: true,
..Default::default()
},
});
}
pub fn build_br(&mut self, target: u32) {
self.instructions.push(MIRInstruction {
opcode: MOpcode::G_BR,
defs: Vec::new(),
uses: vec![MIRUse::Block(target)],
flags: MIRFlags {
is_terminator: true,
..Default::default()
},
});
}
pub fn build_brcond(&mut self, cond: u32, true_bb: u32, false_bb: u32) {
self.instructions.push(MIRInstruction {
opcode: MOpcode::G_BRCOND,
defs: Vec::new(),
uses: vec![
MIRUse::Reg(cond),
MIRUse::Block(true_bb),
MIRUse::Block(false_bb),
],
flags: MIRFlags {
is_terminator: true,
..Default::default()
},
});
}
pub fn build_return(&mut self, vals: &[u32]) {
self.instructions.push(MIRInstruction {
opcode: MOpcode::G_RETURN,
defs: Vec::new(),
uses: vals.iter().map(|&v| MIRUse::Reg(v)).collect(),
flags: MIRFlags {
is_terminator: true,
..Default::default()
},
});
}
}
impl Default for MIRBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_calling_conv_return() {
let cc = CallingConvLowering::new("x86_64-linux-gnu");
let assigns = cc.classify_return_type(32, false);
assert_eq!(assigns.len(), 1);
assert_eq!(assigns[0].width, 32);
}
#[test]
fn test_calling_conv_return_fp() {
let cc = CallingConvLowering::new("x86_64-linux-gnu");
let assigns = cc.classify_return_type(64, true);
assert_eq!(assigns.len(), 1);
}
#[test]
fn test_calling_conv_argument() {
let cc = CallingConvLowering::new("x86_64-linux-gnu");
let mut int_reg = 0;
let mut fp_reg = 0;
let mut stack_off = 0;
let assign = cc.classify_argument(0, 32, false, &mut int_reg, &mut fp_reg, &mut stack_off);
assert_eq!(assign.arg_idx, 0);
}
#[test]
fn test_stack_frame_alloc() {
let mut sf = StackFrameInfo::new();
let off = sf.allocate_local("x", 4, 4);
assert_eq!(off, -4);
assert_eq!(sf.frame_size, 4);
}
#[test]
fn test_stack_frame_callee_saved() {
let mut sf = StackFrameInfo::new();
let off = sf.reserve_callee_saved(3, 8);
assert_eq!(off, -8);
assert_eq!(sf.callee_saved.len(), 1);
}
#[test]
fn test_mir_builder_add() {
let mut builder = MIRBuilder::new();
builder.build_add(1, 2, 3);
assert_eq!(builder.instructions.len(), 1);
assert_eq!(builder.instructions[0].defs, vec![1]);
}
#[test]
fn test_mir_builder_load_store() {
let mut builder = MIRBuilder::new();
builder.build_load(1, 2, 4);
builder.build_store(1, 3, 4);
assert_eq!(builder.instructions.len(), 2);
assert!(builder.instructions[0].flags.may_load);
assert!(builder.instructions[1].flags.may_store);
}
#[test]
fn test_mir_builder_terminators() {
let mut builder = MIRBuilder::new();
builder.build_br(5);
builder.build_brcond(1, 2, 3);
builder.build_return(&[0]);
assert_eq!(builder.instructions.len(), 3);
assert!(builder.instructions[0].flags.is_terminator);
}
#[test]
fn test_gisel_combiner() {
let mut combiner = GISelCombiner::new();
assert_eq!(combiner.num_combines, 0);
let _ = combiner.try_combine(&[MOpcode::G_ADD, MOpcode::G_CONSTANT]);
assert_eq!(combiner.num_combines, 1);
}
}