use crate::mips::mips_instr_info::{MipsInstrDesc, MipsInstrInfo, MipsOpcode, MipsOperandType};
use crate::mips::mips_register_info::{
MipsRegClass, MipsRegisterInfo, MIPS_FPR_BASE, MIPS_FPR_COUNT, MIPS_GPR_BASE, MIPS_GPR_COUNT,
MIPS_MAX_REG_ID,
};
use crate::mips::{
MIPS_ENDIANNESS, MIPS_PAGE_SIZE, MIPS_RED_ZONE_SIZE, MIPS_STACK_ALIGNMENT,
};
use crate::triple::{Arch, Triple};
use crate::data_layout::DataLayout;
use crate::target_machine::{CodeGenOptLevel, CodeModel, FloatABI, RelocModel, TargetOptions};
use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
pub const X86_GPR_COUNT: usize = 16;
pub const X86_XMM_COUNT: usize = 16;
pub const X86_YMM_COUNT: usize = 16;
pub const MIPS_INSTR_SIZE: u32 = 4;
pub const MICROMIPS_INSTR_SIZE: u32 = 2;
pub const X86_MAX_INSTR_SIZE: u32 = 15;
pub const X86_AVG_INSTR_SIZE: u32 = 4;
pub const MIPS_DELAY_SLOT_SIZE: u32 = 0;
pub const X86_RED_ZONE_SIZE: u32 = 128;
pub const X86_STACK_ALIGNMENT: u32 = 16;
pub const X86_PAGE_SIZE: u32 = 4096;
pub fn mips_gpr_to_x86(mips_reg: u16) -> Option<&'static str> {
match mips_reg {
4000 => None,
4001 => Some("r11"),
4002 => Some("rax"),
4003 => Some("rdx"),
4004 => Some("rdi"),
4005 => Some("rsi"),
4006 => Some("rdx"),
4007 => Some("rcx"),
4008 => Some("r8"),
4009 => Some("r9"),
4010 => Some("r10"),
4011 => Some("r11"),
4012 => Some("r12"),
4013 => Some("r13"),
4014 => Some("r14"),
4015 => Some("r15"),
4016 => Some("rbx"),
4017 => Some("r12"),
4018 => Some("r13"),
4019 => Some("r14"),
4020 => Some("r15"),
4021 => Some("rbp"),
4022 => Some("rdi"), 4023 => Some("rsi"), 4024 => Some("r8"),
4025 => Some("r9"),
4026 => None,
4027 => None,
4028 => None,
4029 => Some("rsp"),
4030 => Some("rbp"),
4031 => None,
_ => None,
}
}
pub fn x86_gpr_to_mips(x86_reg: &str) -> Option<u16> {
match x86_reg {
"rax" | "eax" | "ax" | "al" => Some(4002), "rbx" | "ebx" | "bx" | "bl" => Some(4016), "rcx" | "ecx" | "cx" | "cl" => Some(4007), "rdx" | "edx" | "dx" | "dl" => Some(4003), "rsi" | "esi" | "si" | "sil" => Some(4005), "rdi" | "edi" | "di" | "dil" => Some(4004), "rsp" | "esp" | "sp" | "spl" => Some(4029), "rbp" | "ebp" | "bp" | "bpl" => Some(4030), "r8" | "r8d" | "r8w" | "r8b" => Some(4008), "r9" | "r9d" | "r9w" | "r9b" => Some(4009), "r10" | "r10d" | "r10w" | "r10b" => Some(4010), "r11" | "r11d" | "r11w" | "r11b" => Some(4011), "r12" | "r12d" | "r12w" | "r12b" => Some(4017), "r13" | "r13d" | "r13w" | "r13b" => Some(4018), "r14" | "r14d" | "r14w" | "r14b" => Some(4019), "r15" | "r15d" | "r15w" | "r15b" => Some(4020), _ => None,
}
}
pub fn mips_fpr_to_x86(mips_reg: u16) -> Option<&'static str> {
if mips_reg >= 4050 && mips_reg <= 4081 {
let idx = (mips_reg - 4050) as usize;
const XMM_NAMES: [&str; 32] = [
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
];
if idx < 32 {
Some(XMM_NAMES[idx])
} else {
None
}
} else {
None
}
}
#[derive(Clone)]
pub struct MIPSX86Bridge {
pub triple: String,
pub is_mips_primary: bool,
pub is_64bit: bool,
pub mips_abi: MipsAbi,
pub x86_cc: X86CallingConventionVariant,
pub mips_instr_info: MipsInstrInfo,
pub mips_reg_info: MipsRegisterInfo,
pub reg_map_mips_to_x86: HashMap<u16, String>,
pub reg_map_x86_to_mips: HashMap<String, u16>,
pub vreg_allocations: HashMap<u32, AllocatedReg>,
pub spill_slots: Vec<SpillSlot>,
pub cost_weights: CostWeights,
pub cost_data: Vec<InstructionCostEntry>,
pub prefer_micromips: bool,
pub has_msa: bool,
pub has_dsp: bool,
pub has_sse42: bool,
pub has_avx2: bool,
pub pattern_cache: HashMap<u32, SharedPattern>,
pub frame_info: BridgeFrameInfo,
pub call_conv: BridgeCallingConvention,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MipsAbi {
O32,
N32,
N64,
}
impl Default for MipsAbi {
fn default() -> Self {
MipsAbi::O32
}
}
impl std::fmt::Display for MipsAbi {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MipsAbi::O32 => write!(f, "o32"),
MipsAbi::N32 => write!(f, "n32"),
MipsAbi::N64 => write!(f, "n64"),
}
}
}
impl MipsAbi {
pub fn is_64bit(&self) -> bool {
matches!(self, MipsAbi::N64)
}
pub fn num_arg_regs(&self) -> usize {
match self {
MipsAbi::O32 => 4,
MipsAbi::N32 | MipsAbi::N64 => 8,
}
}
pub fn stack_alignment(&self) -> u32 {
match self {
MipsAbi::O32 => 8,
MipsAbi::N32 | MipsAbi::N64 => 16,
}
}
pub fn varargs_save_size(&self) -> u32 {
match self {
MipsAbi::O32 => 16, MipsAbi::N32 | MipsAbi::N64 => 64, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CallingConventionVariant {
SysV64,
Win64,
CDecl32,
StdCall32,
FastCall32,
}
impl Default for X86CallingConventionVariant {
fn default() -> Self {
X86CallingConventionVariant::SysV64
}
}
impl std::fmt::Display for X86CallingConventionVariant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
X86CallingConventionVariant::SysV64 => write!(f, "sysv64"),
X86CallingConventionVariant::Win64 => write!(f, "win64"),
X86CallingConventionVariant::CDecl32 => write!(f, "cdecl32"),
X86CallingConventionVariant::StdCall32 => write!(f, "stdcall32"),
X86CallingConventionVariant::FastCall32 => write!(f, "fastcall32"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AllocatedReg {
pub vreg: u32,
pub mips_reg: u16,
pub x86_reg: String,
pub is_spill: bool,
pub spill_slot: Option<usize>,
pub reg_class: RegClassKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegClassKind {
GPR32,
GPR64,
FPR32,
FPR64,
Vec128,
Vec256,
Flag,
}
impl RegClassKind {
pub fn size_bytes(&self) -> usize {
match self {
RegClassKind::GPR32 => 4,
RegClassKind::GPR64 => 8,
RegClassKind::FPR32 => 4,
RegClassKind::FPR64 => 8,
RegClassKind::Vec128 => 16,
RegClassKind::Vec256 => 32,
RegClassKind::Flag => 0,
}
}
pub fn is_integer(&self) -> bool {
matches!(self, RegClassKind::GPR32 | RegClassKind::GPR64)
}
pub fn is_fp(&self) -> bool {
matches!(self, RegClassKind::FPR32 | RegClassKind::FPR64 | RegClassKind::Vec128 | RegClassKind::Vec256)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpillSlot {
pub offset: i32,
pub size: u32,
pub alignment: u32,
pub is_callee_saved: bool,
pub mips_reg: Option<u16>,
pub x86_reg: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CostWeights {
pub size_weight: f64,
pub latency_weight: f64,
pub throughput_weight: f64,
pub reg_pressure_weight: f64,
pub power_weight: f64,
pub target_bias: f64,
}
impl Default for CostWeights {
fn default() -> Self {
CostWeights {
size_weight: 0.25,
latency_weight: 0.35,
throughput_weight: 0.25,
reg_pressure_weight: 0.10,
power_weight: 0.05,
target_bias: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct InstructionCostEntry {
pub ir_opcode: u32,
pub mips_mnemonic: String,
pub x86_mnemonic: String,
pub mips_cost: TargetInstructionCost,
pub x86_cost: TargetInstructionCost,
pub winner: CostWinner,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct TargetInstructionCost {
pub latency: u32,
pub rthroughput: f64,
pub size: u32,
pub uops: u32,
pub gpr_used: u32,
pub fpr_used: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CostWinner {
Mips,
X86,
Tie,
MipsUnsupported,
X86Unsupported,
}
#[derive(Debug, Clone)]
pub struct SharedPattern {
pub opcode: u32,
pub mips_sequence: Vec<PatternInstruction>,
pub x86_sequence: Vec<PatternInstruction>,
pub is_identical: bool,
pub mips_cost: u32,
pub x86_cost: u32,
}
#[derive(Debug, Clone)]
pub struct PatternInstruction {
pub opcode: String,
pub operands: Vec<PatternOperand>,
pub is_terminator: bool,
pub defines_vreg: bool,
}
#[derive(Debug, Clone)]
pub struct PatternOperand {
pub kind: PatternOperandKind,
pub vreg: Option<u32>,
pub immediate: Option<i64>,
pub reg_name: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PatternOperandKind {
VRegDef,
VRegUse,
PhysRegUse,
Immediate,
MemoryAddress,
BranchTarget,
ConditionCode,
}
#[derive(Debug, Clone)]
pub struct BridgeFrameInfo {
pub frame_size: u32,
pub ra_offset: i32,
pub fp_save_offset: i32,
pub callee_saved: BTreeMap<u16, i32>,
pub has_frame_pointer: bool,
pub has_var_sized: bool,
pub has_calls: bool,
pub stack_alignment: u32,
}
impl Default for BridgeFrameInfo {
fn default() -> Self {
BridgeFrameInfo {
frame_size: 0,
ra_offset: 0,
fp_save_offset: 0,
callee_saved: BTreeMap::new(),
has_frame_pointer: false,
has_var_sized: false,
has_calls: false,
stack_alignment: MIPS_STACK_ALIGNMENT,
}
}
}
#[derive(Debug, Clone)]
pub struct BridgeCallingConvention {
pub mips_int_args: Vec<u16>,
pub mips_fp_args: Vec<u16>,
pub mips_int_rets: Vec<u16>,
pub mips_fp_rets: Vec<u16>,
pub x86_int_args: Vec<String>,
pub x86_sse_args: Vec<String>,
pub x86_int_rets: Vec<String>,
pub x86_sse_rets: Vec<String>,
pub stack_arg_offset: u32,
pub callee_cleans_stack: bool,
pub shadow_store: u32,
}
impl Default for BridgeCallingConvention {
fn default() -> Self {
BridgeCallingConvention {
mips_int_args: vec![4004, 4005, 4006, 4007], mips_fp_args: vec![4062, 4063], mips_int_rets: vec![4002, 4003], mips_fp_rets: vec![4050, 4051], x86_int_args: vec!["rdi".into(), "rsi".into(), "rdx".into(), "rcx".into(), "r8".into(), "r9".into()],
x86_sse_args: (0..8).map(|i| format!("xmm{}", i)).collect(),
x86_int_rets: vec!["rax".into(), "rdx".into()],
x86_sse_rets: vec!["xmm0".into(), "xmm1".into()],
stack_arg_offset: 16, callee_cleans_stack: false,
shadow_store: 0,
}
}
}
impl MIPSX86Bridge {
pub fn new(triple: &str) -> Self {
let parsed = Triple::parse(triple);
let is_mips_primary = matches!(
parsed.arch,
Arch::Mips | Arch::Mips64 | Arch::Mipsel | Arch::Mips64el
);
let is_64bit = parsed.is_64bit();
let mips_abi = if is_64bit {
MipsAbi::N64
} else {
MipsAbi::O32
};
let mut bridge = MIPSX86Bridge {
triple: triple.to_string(),
is_mips_primary,
is_64bit,
mips_abi,
x86_cc: X86CallingConventionVariant::SysV64,
mips_instr_info: MipsInstrInfo::new(),
mips_reg_info: MipsRegisterInfo,
reg_map_mips_to_x86: HashMap::new(),
reg_map_x86_to_mips: HashMap::new(),
vreg_allocations: HashMap::new(),
spill_slots: Vec::new(),
cost_weights: CostWeights::default(),
cost_data: Vec::new(),
prefer_micromips: false,
has_msa: false,
has_dsp: false,
has_sse42: true,
has_avx2: false,
pattern_cache: HashMap::new(),
frame_info: BridgeFrameInfo::default(),
call_conv: BridgeCallingConvention::default(),
};
bridge.build_reg_maps();
bridge.configure_abi();
bridge
}
pub fn with_features(triple: &str, features: &[&str]) -> Self {
let mut bridge = Self::new(triple);
for feat in features {
match *feat {
"msa" => bridge.has_msa = true,
"dsp" => bridge.has_dsp = true,
"sse42" | "sse4.2" => bridge.has_sse42 = true,
"avx2" => bridge.has_avx2 = true,
"micromips" => bridge.prefer_micromips = true,
_ => {}
}
}
bridge
}
fn build_reg_maps(&mut self) {
self.reg_map_mips_to_x86.clear();
self.reg_map_x86_to_mips.clear();
for mips_reg in MIPS_GPR_BASE..MIPS_GPR_BASE + 32 {
if let Some(x86_name) = mips_gpr_to_x86(mips_reg) {
self.reg_map_mips_to_x86.insert(mips_reg, x86_name.to_string());
}
}
const X86_GPRS: [&str; 16] = [
"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
];
for x86_name in &X86_GPRS {
if let Some(mips_reg) = x86_gpr_to_mips(x86_name) {
self.reg_map_x86_to_mips.insert(x86_name.to_string(), mips_reg);
}
}
for mips_reg in MIPS_FPR_BASE..MIPS_FPR_BASE + 16 {
if let Some(x86_name) = mips_fpr_to_x86(mips_reg) {
self.reg_map_mips_to_x86.insert(mips_reg, x86_name.to_string());
}
}
}
fn configure_abi(&mut self) {
match self.mips_abi {
MipsAbi::O32 => {
self.call_conv.mips_int_args = vec![4004, 4005, 4006, 4007]; self.call_conv.mips_fp_args = vec![4062, 4063]; self.call_conv.mips_int_rets = vec![4002, 4003]; self.call_conv.mips_fp_rets = vec![4050, 4051]; self.frame_info.stack_alignment = 8;
}
MipsAbi::N32 | MipsAbi::N64 => {
self.call_conv.mips_int_args = vec![
4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011, ];
self.call_conv.mips_fp_args = vec![4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069];
self.call_conv.mips_int_rets = vec![4002, 4003]; self.call_conv.mips_fp_rets = vec![4050, 4051]; self.frame_info.stack_alignment = 16;
}
}
match self.x86_cc {
X86CallingConventionVariant::SysV64 => {
self.call_conv.x86_int_args = vec![
"rdi".into(), "rsi".into(), "rdx".into(), "rcx".into(),
"r8".into(), "r9".into(),
];
self.call_conv.x86_sse_args = (0..8).map(|i| format!("xmm{}", i)).collect();
self.call_conv.x86_int_rets = vec!["rax".into(), "rdx".into()];
self.call_conv.x86_sse_rets = vec!["xmm0".into(), "xmm1".into()];
self.call_conv.callee_cleans_stack = false;
self.call_conv.shadow_store = 0;
}
X86CallingConventionVariant::Win64 => {
self.call_conv.x86_int_args = vec![
"rcx".into(), "rdx".into(), "r8".into(), "r9".into(),
];
self.call_conv.x86_sse_args = (0..4).map(|i| format!("xmm{}", i)).collect();
self.call_conv.x86_int_rets = vec!["rax".into()];
self.call_conv.x86_sse_rets = vec!["xmm0".into()];
self.call_conv.callee_cleans_stack = false;
self.call_conv.shadow_store = 32;
}
X86CallingConventionVariant::CDecl32 => {
self.call_conv.x86_int_args = vec![];
self.call_conv.x86_sse_args = vec![];
self.call_conv.x86_int_rets = vec!["eax".into(), "edx".into()];
self.call_conv.x86_sse_rets = vec!["xmm0".into()];
self.call_conv.callee_cleans_stack = false;
self.call_conv.shadow_store = 0;
}
X86CallingConventionVariant::StdCall32 => {
self.call_conv.x86_int_args = vec![];
self.call_conv.x86_sse_args = vec![];
self.call_conv.x86_int_rets = vec!["eax".into(), "edx".into()];
self.call_conv.x86_sse_rets = vec!["xmm0".into()];
self.call_conv.callee_cleans_stack = true;
self.call_conv.shadow_store = 0;
}
X86CallingConventionVariant::FastCall32 => {
self.call_conv.x86_int_args = vec!["ecx".into(), "edx".into()];
self.call_conv.x86_sse_args = vec![];
self.call_conv.x86_int_rets = vec!["eax".into(), "edx".into()];
self.call_conv.x86_sse_rets = vec!["xmm0".into()];
self.call_conv.callee_cleans_stack = true;
self.call_conv.shadow_store = 0;
}
}
}
pub fn allocate_vreg(
&mut self,
vreg: u32,
preferred_class: RegClassKind,
) -> AllocatedReg {
if let Some(existing) = self.vreg_allocations.get(&vreg) {
return existing.clone();
}
let (mips_reg, x86_reg) = self.pick_reg_pair(preferred_class);
let alloc = AllocatedReg {
vreg,
mips_reg,
x86_reg,
is_spill: false,
spill_slot: None,
reg_class: preferred_class,
};
self.vreg_allocations.insert(vreg, alloc.clone());
alloc
}
fn pick_reg_pair(&self, class: RegClassKind) -> (u16, String) {
match class {
RegClassKind::GPR32 | RegClassKind::GPR64 => {
for mips_reg in 4016..=4023 {
if !self.vreg_allocations.values().any(|a| a.mips_reg == mips_reg) {
if let Some(x86_name) = mips_gpr_to_x86(mips_reg) {
return (mips_reg, x86_name.to_string());
}
}
}
for mips_reg in 4008..=4015 {
if !self.vreg_allocations.values().any(|a| a.mips_reg == mips_reg) {
if let Some(x86_name) = mips_gpr_to_x86(mips_reg) {
return (mips_reg, x86_name.to_string());
}
}
}
(4008, "r8".to_string())
}
RegClassKind::FPR32 | RegClassKind::FPR64 => {
for mips_fpr in (MIPS_FPR_BASE + 20)..(MIPS_FPR_BASE + 31) {
if !self.vreg_allocations.values().any(|a| a.mips_reg == mips_fpr) {
if let Some(x86_name) = mips_fpr_to_x86(mips_fpr) {
return (mips_fpr, x86_name.to_string());
}
}
}
(MIPS_FPR_BASE + 4, "xmm4".to_string()) }
RegClassKind::Vec128 | RegClassKind::Vec256 => {
(MIPS_FPR_BASE, "xmm0".to_string()) }
RegClassKind::Flag => (0, String::new()),
}
}
pub fn spill_vreg(&mut self, vreg: u32) -> SpillSlot {
if let Some(alloc) = self.vreg_allocations.get_mut(&vreg) {
let slot = SpillSlot {
offset: -(self.frame_info.frame_size as i32 + 8 * (self.spill_slots.len() as i32 + 1)),
size: alloc.reg_class.size_bytes() as u32,
alignment: alloc.reg_class.size_bytes() as u32,
is_callee_saved: false,
mips_reg: Some(alloc.mips_reg),
x86_reg: Some(alloc.x86_reg.clone()),
};
alloc.is_spill = true;
alloc.spill_slot = Some(self.spill_slots.len());
self.spill_slots.push(slot.clone());
slot
} else {
SpillSlot {
offset: -(self.frame_info.frame_size as i32 + 8),
size: 8,
alignment: 8,
is_callee_saved: false,
mips_reg: None,
x86_reg: None,
}
}
}
pub fn is_reg_allocated(&self, reg: u16) -> bool {
self.vreg_allocations.values().any(|a| a.mips_reg == reg)
}
pub fn clear_allocations(&mut self) {
self.vreg_allocations.clear();
self.spill_slots.clear();
}
pub fn select_target_for_opcode(&self, opcode: u32) -> bool {
if let Some(pattern) = self.pattern_cache.get(&opcode) {
let mips_weighted = pattern.mips_cost as f64 * self.cost_weights.latency_weight
+ self.cost_weights.reg_pressure_weight * 2.0;
let x86_weighted = pattern.x86_cost as f64 * self.cost_weights.latency_weight
+ self.cost_weights.reg_pressure_weight * 2.0;
let adjusted_mips = mips_weighted - self.cost_weights.target_bias;
adjusted_mips <= x86_weighted
} else {
self.is_mips_primary
}
}
pub fn get_mips_instr(&self, opcode: MipsOpcode) -> MipsInstrDesc {
self.mips_instr_info.get_desc(opcode)
}
pub fn is_commutative(&self, opcode: MipsOpcode) -> bool {
self.mips_instr_info.is_commutative(opcode)
}
pub fn get_or_build_pattern(&mut self, ir_opcode: u32) -> &SharedPattern {
if !self.pattern_cache.contains_key(&ir_opcode) {
let pattern = self.build_shared_pattern(ir_opcode);
self.pattern_cache.insert(ir_opcode, pattern);
}
self.pattern_cache.get(&ir_opcode).unwrap()
}
fn build_shared_pattern(&self, ir_opcode: u32) -> SharedPattern {
match ir_opcode {
8 => SharedPattern {
opcode: 8,
mips_sequence: vec![PatternInstruction {
opcode: "addu".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "add".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1,
x86_cost: 1,
},
10 => SharedPattern {
opcode: 10,
mips_sequence: vec![PatternInstruction {
opcode: "subu".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "sub".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1,
x86_cost: 1,
},
12 => SharedPattern {
opcode: 12,
mips_sequence: vec![
PatternInstruction {
opcode: "mult".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
},
PatternInstruction {
opcode: "mflo".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
},
],
x86_sequence: vec![PatternInstruction {
opcode: "imul".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 2, x86_cost: 3, },
23 => SharedPattern {
opcode: 23,
mips_sequence: vec![PatternInstruction {
opcode: "and".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "and".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1,
x86_cost: 1,
},
24 => SharedPattern {
opcode: 24,
mips_sequence: vec![PatternInstruction {
opcode: "or".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "or".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1,
x86_cost: 1,
},
25 => SharedPattern {
opcode: 25,
mips_sequence: vec![PatternInstruction {
opcode: "xor".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "xor".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1,
x86_cost: 1,
},
20 => SharedPattern {
opcode: 20,
mips_sequence: vec![PatternInstruction {
opcode: "sllv".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "shl".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::PhysRegUse, vreg: None, immediate: None, reg_name: Some("cl".into()) },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1, x86_cost: 1,
},
21 => SharedPattern {
opcode: 21,
mips_sequence: vec![PatternInstruction {
opcode: "srlv".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "shr".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::PhysRegUse, vreg: None, immediate: None, reg_name: Some("cl".into()) },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1,
x86_cost: 1,
},
22 => SharedPattern {
opcode: 22,
mips_sequence: vec![PatternInstruction {
opcode: "srav".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "sar".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::PhysRegUse, vreg: None, immediate: None, reg_name: Some("cl".into()) },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1,
x86_cost: 1,
},
14 => SharedPattern {
opcode: 14,
mips_sequence: vec![
PatternInstruction {
opcode: "divu".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
},
PatternInstruction {
opcode: "mflo".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
},
],
x86_sequence: vec![PatternInstruction {
opcode: "div".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 12, x86_cost: 20, },
27 => SharedPattern {
opcode: 27,
mips_sequence: vec![PatternInstruction {
opcode: "lw".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::MemoryAddress, vreg: None, immediate: Some(0), reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "mov".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::MemoryAddress, vreg: None, immediate: Some(0), reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
is_identical: true,
mips_cost: 3, x86_cost: 4, },
28 => SharedPattern {
opcode: 28,
mips_sequence: vec![PatternInstruction {
opcode: "sw".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::MemoryAddress, vreg: None, immediate: Some(0), reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
x86_sequence: vec![PatternInstruction {
opcode: "mov".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::MemoryAddress, vreg: None, immediate: Some(0), reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(0), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: true,
mips_cost: 1,
x86_cost: 1,
},
9 => SharedPattern {
opcode: 9,
mips_sequence: vec![PatternInstruction {
opcode: "add.s".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "addss".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 4, x86_cost: 3, },
44 => SharedPattern {
opcode: 44,
mips_sequence: vec![PatternInstruction {
opcode: "slt".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegDef, vreg: Some(0), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: true,
}],
x86_sequence: vec![PatternInstruction {
opcode: "cmp".into(),
operands: vec![
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(1), immediate: None, reg_name: None },
PatternOperand { kind: PatternOperandKind::VRegUse, vreg: Some(2), immediate: None, reg_name: None },
],
is_terminator: false,
defines_vreg: false,
}],
is_identical: false,
mips_cost: 1,
x86_cost: 1,
},
_ => SharedPattern {
opcode: ir_opcode,
mips_sequence: vec![PatternInstruction {
opcode: "nop".into(),
operands: vec![],
is_terminator: false,
defines_vreg: false,
}],
x86_sequence: vec![PatternInstruction {
opcode: "nop".into(),
operands: vec![],
is_terminator: false,
defines_vreg: false,
}],
is_identical: true,
mips_cost: 0,
x86_cost: 0,
},
}
}
pub fn compare_opcode_cost(&mut self, ir_opcode: u32) -> CostWinner {
let pattern = self.get_or_build_pattern(ir_opcode);
if pattern.mips_cost == 0 && ir_opcode != 0 {
return CostWinner::MipsUnsupported;
}
if pattern.x86_cost == 0 && ir_opcode != 0 {
return CostWinner::X86Unsupported;
}
let threshold = 1;
let diff = pattern.mips_cost as i32 - pattern.x86_cost as i32;
if diff.abs() <= threshold {
CostWinner::Tie
} else if diff < 0 {
CostWinner::Mips
} else {
CostWinner::X86
}
}
pub fn run_full_comparison(&mut self) -> Vec<InstructionCostEntry> {
self.cost_data.clear();
let opcodes: Vec<u32> = vec![
8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 21, 22, 23, 24, 25, 27, 28, 44, 45, 46, 47, 48, ];
for oc in opcodes {
let pattern = self.get_or_build_pattern(oc);
let winner = self.compare_opcode_cost(oc);
let mips_mnem = pattern.mips_sequence.first()
.map(|i| i.opcode.clone())
.unwrap_or_else(|| "?".into());
let x86_mnem = pattern.x86_sequence.first()
.map(|i| i.opcode.clone())
.unwrap_or_else(|| "?".into());
let entry = InstructionCostEntry {
ir_opcode: oc,
mips_mnemonic: mips_mnem,
x86_mnemonic: x86_mnem,
mips_cost: TargetInstructionCost {
latency: pattern.mips_cost,
rthroughput: 1.0,
size: MIPS_INSTR_SIZE,
uops: 1,
gpr_used: 2,
fpr_used: 0,
},
x86_cost: TargetInstructionCost {
latency: pattern.x86_cost,
rthroughput: 0.33,
size: X86_AVG_INSTR_SIZE,
uops: 1,
gpr_used: 2,
fpr_used: 0,
},
winner,
};
self.cost_data.push(entry);
}
self.cost_data.clone()
}
pub fn cost_summary(&self) -> CostSummary {
let mut mips_wins = 0u32;
let mut x86_wins = 0u32;
let mut ties = 0u32;
let mut mips_unsupported = 0u32;
let mut x86_unsupported = 0u32;
let mut total_mips_latency = 0u32;
let mut total_x86_latency = 0u32;
let mut total_mips_size = 0u32;
let mut total_x86_size = 0u32;
for entry in &self.cost_data {
match entry.winner {
CostWinner::Mips => mips_wins += 1,
CostWinner::X86 => x86_wins += 1,
CostWinner::Tie => ties += 1,
CostWinner::MipsUnsupported => mips_unsupported += 1,
CostWinner::X86Unsupported => x86_unsupported += 1,
}
total_mips_latency += entry.mips_cost.latency;
total_x86_latency += entry.x86_cost.latency;
total_mips_size += entry.mips_cost.size;
total_x86_size += entry.x86_cost.size;
}
CostSummary {
total_entries: self.cost_data.len() as u32,
mips_wins,
x86_wins,
ties,
mips_unsupported,
x86_unsupported,
total_mips_latency,
total_x86_latency,
avg_mips_latency: if self.cost_data.is_empty() { 0.0 } else { total_mips_latency as f64 / self.cost_data.len() as f64 },
avg_x86_latency: if self.cost_data.is_empty() { 0.0 } else { total_x86_latency as f64 / self.cost_data.len() as f64 },
total_mips_size,
total_x86_size,
}
}
pub fn calculate_frame(
&mut self,
local_size: u32,
num_callee_saved: u32,
has_calls: bool,
has_var_sized: bool,
) -> BridgeFrameInfo {
let ra_save = if has_calls { if self.is_64bit { 8 } else { 4 } } else { 0 };
let fp_save = if num_callee_saved > 0 || has_var_sized { if self.is_64bit { 8 } else { 4 } } else { 0 };
let callee_size = num_callee_saved * if self.is_64bit { 8 } else { 4 };
let mut total = ra_save + fp_save + callee_size + local_size;
let align = self.mips_abi.stack_alignment();
if total % align != 0 {
total = ((total / align) + 1) * align;
}
self.frame_info = BridgeFrameInfo {
frame_size: total,
ra_offset: if has_calls { 0 } else { 0 },
fp_save_offset: ra_save as i32,
callee_saved: BTreeMap::new(),
has_frame_pointer: fp_save > 0,
has_var_sized,
has_calls,
stack_alignment: align,
};
self.frame_info.clone()
}
pub fn emit_mips_prologue(&self) -> String {
let mut out = String::new();
if self.frame_info.has_frame_pointer {
out.push_str(&format!(
"\taddiu\t$sp, $sp, -{}\n",
self.frame_info.frame_size
));
if self.frame_info.has_calls && !self.frame_info.has_frame_pointer {
out.push_str(&format!(
"\tsw\t$ra, {}({})\n",
self.frame_info.ra_offset,
if self.frame_info.has_frame_pointer { "$fp" } else { "$sp" }
));
}
out.push_str(&format!(
"\tsw\t$fp, {}({})\n",
self.frame_info.fp_save_offset,
if self.frame_info.has_frame_pointer { "$fp" } else { "$sp" }
));
out.push_str("\tmove\t$fp, $sp\n");
} else if self.frame_info.frame_size > 0 {
out.push_str(&format!(
"\taddiu\t$sp, $sp, -{}\n",
self.frame_info.frame_size
));
}
out
}
pub fn emit_mips_epilogue(&self) -> String {
let mut out = String::new();
if self.frame_info.has_frame_pointer {
out.push_str("\tmove\t$sp, $fp\n");
out.push_str(&format!(
"\tlw\t$fp, {}({})\n",
self.frame_info.fp_save_offset,
"$sp"
));
if self.frame_info.has_calls && !self.frame_info.has_frame_pointer {
out.push_str(&format!("\tlw\t$ra, {}({})\n", self.frame_info.ra_offset, "$sp"));
}
out.push_str(&format!(
"\taddiu\t$sp, $sp, {}\n",
self.frame_info.frame_size
));
} else if self.frame_info.frame_size > 0 {
out.push_str(&format!(
"\taddiu\t$sp, $sp, {}\n",
self.frame_info.frame_size
));
}
out.push_str("\tjr\t$ra\n");
out.push_str("\tnop\n");
out
}
pub fn emit_x86_prologue(&self) -> String {
let mut out = String::new();
if self.is_64bit {
out.push_str("\tpushq\t%rbp\n");
out.push_str("\tmovq\t%rsp, %rbp\n");
if self.frame_info.frame_size > 0 {
out.push_str(&format!("\tsubq\t${}, %rsp\n", self.frame_info.frame_size));
}
} else {
out.push_str("\tpushl\t%ebp\n");
out.push_str("\tmovl\t%esp, %ebp\n");
if self.frame_info.frame_size > 0 {
out.push_str(&format!("\tsubl\t${}, %esp\n", self.frame_info.frame_size));
}
}
out
}
pub fn emit_x86_epilogue(&self) -> String {
let mut out = String::new();
if self.is_64bit {
out.push_str("\tleave\n");
out.push_str("\tret\n");
} else {
out.push_str("\tleave\n");
out.push_str("\tret\n");
}
out
}
pub fn generate_cross_target(
&mut self,
ir_instructions: &[CrossTargetIRInst],
) -> (String, String) {
let mut mips_asm = String::new();
let mut x86_asm = String::new();
mips_asm.push_str(&self.emit_mips_prologue());
x86_asm.push_str(&self.emit_x86_prologue());
for inst in ir_instructions {
let pattern = self.get_or_build_pattern(inst.opcode);
for pi in &pattern.mips_sequence {
mips_asm.push_str(&format!("\t{}", pi.opcode));
if !pi.operands.is_empty() {
let ops: Vec<String> = pi.operands.iter().map(|o| {
match o.kind {
PatternOperandKind::Immediate => {
o.immediate.map(|i| format!("#{}", i)).unwrap_or_else(|| "#0".into())
}
PatternOperandKind::VRegDef | PatternOperandKind::VRegUse => {
o.vreg.map(|v| format!("$r{}", v))
.or_else(|| o.reg_name.clone())
.unwrap_or_else(|| "$?".into())
}
_ => o.reg_name.clone().unwrap_or_else(|| "?".into()),
}
}).collect();
mips_asm.push_str(&format!("\t{}", ops.join(", ")));
}
mips_asm.push('\n');
}
for pi in &pattern.x86_sequence {
x86_asm.push_str(&format!("\t{}", pi.opcode));
if !pi.operands.is_empty() {
let ops: Vec<String> = pi.operands.iter().map(|o| {
match o.kind {
PatternOperandKind::Immediate => {
o.immediate.map(|i| format!("${}", i)).unwrap_or_else(|| "$0".into())
}
PatternOperandKind::VRegDef | PatternOperandKind::VRegUse => {
o.vreg.map(|v| format!("%%vr{}", v))
.or_else(|| o.reg_name.clone().map(|n| format!("%%{}", n)))
.unwrap_or_else(|| "%?%".into())
}
_ => o.reg_name.clone().unwrap_or_else(|| "?".into()),
}
}).collect();
x86_asm.push_str(&format!("\t{}", ops.join(", ")));
}
x86_asm.push('\n');
}
}
mips_asm.push_str(&self.emit_mips_epilogue());
x86_asm.push_str(&self.emit_x86_epilogue());
(mips_asm, x86_asm)
}
}
#[derive(Debug, Clone)]
pub struct CrossTargetIRInst {
pub opcode: u32,
pub dest: Option<u32>,
pub srcs: Vec<u32>,
pub immediate: Option<i64>,
pub type_code: u8,
}
#[derive(Debug, Clone, Default)]
pub struct CostSummary {
pub total_entries: u32,
pub mips_wins: u32,
pub x86_wins: u32,
pub ties: u32,
pub mips_unsupported: u32,
pub x86_unsupported: u32,
pub total_mips_latency: u32,
pub total_x86_latency: u32,
pub avg_mips_latency: f64,
pub avg_x86_latency: f64,
pub total_mips_size: u32,
pub total_x86_size: u32,
}
impl std::fmt::Display for CostSummary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Cost Comparison Summary:")?;
writeln!(f, " Entries: {}", self.total_entries)?;
writeln!(f, " MIPS wins: {} ({:.1}%)",
self.mips_wins,
if self.total_entries > 0 { self.mips_wins as f64 / self.total_entries as f64 * 100.0 } else { 0.0 }
)?;
writeln!(f, " X86 wins: {} ({:.1}%)",
self.x86_wins,
if self.total_entries > 0 { self.x86_wins as f64 / self.total_entries as f64 * 100.0 } else { 0.0 }
)?;
writeln!(f, " Ties: {}", self.ties)?;
writeln!(f, " MIPS unsupported:{}", self.mips_unsupported)?;
writeln!(f, " X86 unsupported: {}", self.x86_unsupported)?;
writeln!(f, " Avg MIPS latency:{:.2}", self.avg_mips_latency)?;
writeln!(f, " Avg X86 latency: {:.2}", self.avg_x86_latency)?;
writeln!(f, " MIPS code size: {} bytes", self.total_mips_size)?;
writeln!(f, " X86 code size: {} bytes", self.total_x86_size)?;
Ok(())
}
}
pub struct MIPSTargetMachine {
pub bridge: MIPSX86Bridge,
pub options: TargetOptions,
pub data_layout: DataLayout,
pub is_64bit: bool,
pub is_little_endian: bool,
pub abi: MipsAbi,
}
impl MIPSTargetMachine {
pub fn new(triple_str: &str) -> Self {
let triple = Triple::parse(triple_str);
let is_64bit = matches!(triple.arch, Arch::Mips64 | Arch::Mips64el);
let is_little_endian = matches!(triple.arch, Arch::Mipsel | Arch::Mips64el);
let abi = if is_64bit { MipsAbi::N64 } else { MipsAbi::O32 };
let data_layout = if is_64bit {
if is_little_endian {
DataLayout::new("e-m:e-i8:8:32-i16:16:32-i64:64-n32:64-S128")
} else {
DataLayout::new("E-m:e-i8:8:32-i16:16:32-i64:64-n32:64-S128")
}
} else {
if is_little_endian {
DataLayout::new("e-m:m-p:32:32-i8:8:32-i16:16:32-i32:32:32-n32-S64")
} else {
DataLayout::new("E-m:m-p:32:32-i8:8:32-i16:16:32-i32:32:32-n32-S64")
}
};
let mut bridge = MIPSX86Bridge::new(triple_str);
bridge.is_64bit = is_64bit;
MIPSTargetMachine {
bridge,
options: TargetOptions::from_triple(triple_str),
data_layout,
is_64bit,
is_little_endian,
abi,
}
}
pub fn with_features(triple_str: &str, features: &[&str]) -> Self {
let mut tm = Self::new(triple_str);
for feat in features {
match *feat {
"msa" => tm.bridge.has_msa = true,
"dsp" => tm.bridge.has_dsp = true,
"micromips" => tm.bridge.prefer_micromips = true,
_ => {}
}
}
tm
}
pub fn triple(&self) -> &str {
&self.bridge.triple
}
pub fn data_layout_str(&self) -> &str {
self.data_layout.str()
}
pub fn pointer_size(&self) -> u32 {
if self.is_64bit { 64 } else { 32 }
}
pub fn has_feature(&self, feature: &str) -> bool {
match feature {
"msa" => self.bridge.has_msa,
"dsp" => self.bridge.has_dsp,
"micromips" => self.bridge.prefer_micromips,
"mips64" => self.is_64bit,
_ => false,
}
}
pub fn instr_info(&self) -> &MipsInstrInfo {
&self.bridge.mips_instr_info
}
pub fn reg_info(&self) -> &MipsRegisterInfo {
&self.bridge.mips_reg_info
}
pub fn x86_to_mips_reg(&self, x86_reg: &str) -> Option<u16> {
x86_gpr_to_mips(x86_reg).or_else(|| {
if x86_reg.starts_with("xmm") {
x86_reg[3..].parse::<u16>().ok()
.and_then(|idx| if idx < 32 { Some(MIPS_FPR_BASE + idx) } else { None })
} else {
None
}
})
}
pub fn prefer_mips_for(&mut self, ir_opcode: u32) -> bool {
self.bridge.select_target_for_opcode(ir_opcode)
}
}
pub struct MIPSExtendedInstrInfo {
pub base: MipsInstrInfo,
pub costs: HashMap<MipsOpcode, TargetInstructionCost>,
pub x86_equivalents: HashMap<MipsOpcode, String>,
pub encoding_types: HashMap<MipsOpcode, MipsEncodingType>,
pub func_units: HashMap<MipsOpcode, MipsFuncUnit>,
pub has_delay_slot: HashMap<MipsOpcode, bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MipsEncodingType {
RType,
IType,
JType,
FRType,
FIType,
CopType,
Pseudo,
Micro16,
Micro32,
}
impl MipsEncodingType {
pub fn size(&self) -> u32 {
match self {
MipsEncodingType::Micro16 => 2,
MipsEncodingType::Pseudo => 0, _ => 4,
}
}
pub fn name(&self) -> &'static str {
match self {
MipsEncodingType::RType => "R-type",
MipsEncodingType::IType => "I-type",
MipsEncodingType::JType => "J-type",
MipsEncodingType::FRType => "FR-type",
MipsEncodingType::FIType => "FI-type",
MipsEncodingType::CopType => "Coprocessor",
MipsEncodingType::Pseudo => "Pseudo",
MipsEncodingType::Micro16 => "microMIPS16",
MipsEncodingType::Micro32 => "microMIPS32",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MipsFuncUnit {
ALU,
LSU,
MDU,
FPAdd,
FPMul,
FPDiv,
Branch,
MSA,
DSP,
System,
}
impl MIPSExtendedInstrInfo {
pub fn new() -> Self {
let base = MipsInstrInfo::new();
let mut costs = HashMap::new();
let mut x86_equivs = HashMap::new();
let mut encoding_types = HashMap::new();
let mut func_units = HashMap::new();
let mut delay_slots = HashMap::new();
Self::register_r_type(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_shift_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_mdu_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_i_type_alu(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_load_store(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_branch_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_jump_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_fpu_single(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_fpu_double(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_fpu_mem(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_mips64_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_msa_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_dsp_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_pseudo_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
Self::register_system_insts(&mut costs, &mut x86_equivs, &mut encoding_types, &mut func_units, &mut delay_slots);
MIPSExtendedInstrInfo {
base,
costs,
x86_equivalents: x86_equivs,
encoding_types,
func_units,
has_delay_slot: delay_slots,
}
}
fn register_r_type(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let r_type_ops = vec![
(MipsOpcode::ADD, "add", MipsFuncUnit::ALU),
(MipsOpcode::ADDU, "add", MipsFuncUnit::ALU),
(MipsOpcode::SUB, "sub", MipsFuncUnit::ALU),
(MipsOpcode::SUBU, "sub", MipsFuncUnit::ALU),
(MipsOpcode::AND, "and", MipsFuncUnit::ALU),
(MipsOpcode::OR, "or", MipsFuncUnit::ALU),
(MipsOpcode::XOR, "xor", MipsFuncUnit::ALU),
(MipsOpcode::NOR, "not / andn", MipsFuncUnit::ALU),
(MipsOpcode::SLT, "cmp / setl", MipsFuncUnit::ALU),
(MipsOpcode::SLTU, "cmp / setb", MipsFuncUnit::ALU),
];
for (op, x86, fu) in r_type_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 3,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, fu);
delay_slots.insert(op, false);
}
}
fn register_shift_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let shift_ops = vec![
(MipsOpcode::SLL, "shl", 1),
(MipsOpcode::SRL, "shr", 1),
(MipsOpcode::SRA, "sar", 1),
(MipsOpcode::SLLV, "shl (cl)", 1),
(MipsOpcode::SRLV, "shr (cl)", 1),
(MipsOpcode::SRAV, "sar (cl)", 1),
];
for (op, x86, lat) in shift_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 3,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::ALU);
delay_slots.insert(op, false);
}
}
fn register_mdu_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let mdu_ops = vec![
(MipsOpcode::MULT, "imul", 5, MipsFuncUnit::MDU),
(MipsOpcode::MULTU, "mul", 5, MipsFuncUnit::MDU),
(MipsOpcode::DIV, "idiv", 12, MipsFuncUnit::MDU),
(MipsOpcode::DIVU, "div", 12, MipsFuncUnit::MDU),
(MipsOpcode::MFHI, "mov (hi)", 1, MipsFuncUnit::MDU),
(MipsOpcode::MFLO, "mov (lo)", 1, MipsFuncUnit::MDU),
(MipsOpcode::MTHI, "mov to hi", 1, MipsFuncUnit::MDU),
(MipsOpcode::MTLO, "mov to lo", 1, MipsFuncUnit::MDU),
];
for (op, x86, lat, fu) in mdu_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 1.0,
size: 4,
uops: if lat > 1 { 2 } else { 1 },
gpr_used: 2,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, fu);
delay_slots.insert(op, false);
}
}
fn register_i_type_alu(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let i_type_ops = vec![
(MipsOpcode::ADDI, "add imm"),
(MipsOpcode::ADDIU, "add imm"),
(MipsOpcode::ANDI, "and imm"),
(MipsOpcode::ORI, "or imm"),
(MipsOpcode::XORI, "xor imm"),
(MipsOpcode::SLTI, "cmp imm / setl"),
(MipsOpcode::SLTIU, "cmp imm / setb"),
(MipsOpcode::LUI, "mov imm (high)"),
];
for (op, x86) in i_type_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 2,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::IType);
func_units.insert(op, MipsFuncUnit::ALU);
delay_slots.insert(op, false);
}
}
fn register_load_store(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let load_ops = vec![
(MipsOpcode::LW, "mov (load)", 3),
(MipsOpcode::LH, "movsx (load 16)", 3),
(MipsOpcode::LHU, "movzx (load 16)", 3),
(MipsOpcode::LB, "movsx (load 8)", 3),
(MipsOpcode::LBU, "movzx (load 8)", 3),
];
let store_ops = vec![
(MipsOpcode::SW, "mov (store)", 1),
(MipsOpcode::SH, "mov (store 16)", 1),
(MipsOpcode::SB, "mov (store 8)", 1),
];
for (op, x86, lat) in load_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 2,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::IType);
func_units.insert(op, MipsFuncUnit::LSU);
delay_slots.insert(op, false);
}
for (op, x86, lat) in store_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 2,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::IType);
func_units.insert(op, MipsFuncUnit::LSU);
delay_slots.insert(op, false);
}
}
fn register_branch_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let branch_ops = vec![
(MipsOpcode::BEQ, "je / jz"),
(MipsOpcode::BNE, "jne / jnz"),
(MipsOpcode::BLEZ, "jle"),
(MipsOpcode::BGTZ, "jg"),
(MipsOpcode::BLTZ, "jl"),
(MipsOpcode::BGEZ, "jge"),
];
for (op, x86) in branch_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 2,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::IType);
func_units.insert(op, MipsFuncUnit::Branch);
delay_slots.insert(op, true); }
}
fn register_jump_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let jump_ops = vec![
(MipsOpcode::J, "jmp"),
(MipsOpcode::JAL, "call"),
(MipsOpcode::JR, "jmp reg"),
(MipsOpcode::JALR, "call reg"),
];
for (op, x86) in jump_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: if matches!(op, MipsOpcode::JR | MipsOpcode::JALR) { 1 } else { 0 },
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, if matches!(op, MipsOpcode::J | MipsOpcode::JAL) {
MipsEncodingType::JType
} else {
MipsEncodingType::RType
});
func_units.insert(op, MipsFuncUnit::Branch);
delay_slots.insert(op, true);
}
}
fn register_fpu_single(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let fpu_s_ops: Vec<(MipsOpcode, &str, u32, MipsFuncUnit)> = vec![
(MipsOpcode::ADD_S, "addss", 4, MipsFuncUnit::FPAdd),
(MipsOpcode::SUB_S, "subss", 4, MipsFuncUnit::FPAdd),
(MipsOpcode::MUL_S, "mulss", 4, MipsFuncUnit::FPMul),
(MipsOpcode::DIV_S, "divss", 12, MipsFuncUnit::FPDiv),
(MipsOpcode::MOV_S, "movss", 1, MipsFuncUnit::FPAdd),
(MipsOpcode::NEG_S, "xorps (sign)", 1, MipsFuncUnit::FPAdd),
(MipsOpcode::ABS_S, "andps (abs)", 1, MipsFuncUnit::FPAdd),
(MipsOpcode::SQRT_S, "sqrtss", 12, MipsFuncUnit::FPDiv),
(MipsOpcode::CVT_S_W, "cvtsi2ss", 3, MipsFuncUnit::FPAdd),
(MipsOpcode::CVT_W_S, "cvttss2si", 3, MipsFuncUnit::FPAdd),
(MipsOpcode::C_EQ_S, "comiss / sete", 2, MipsFuncUnit::FPAdd),
(MipsOpcode::C_LT_S, "comiss / setb", 2, MipsFuncUnit::FPAdd),
(MipsOpcode::C_LE_S, "comiss / setbe", 2, MipsFuncUnit::FPAdd),
];
for (op, x86, lat, fu) in fpu_s_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 0,
fpr_used: 3,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::FRType);
func_units.insert(op, fu);
delay_slots.insert(op, false);
}
}
fn register_fpu_double(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let fpu_d_ops: Vec<(MipsOpcode, &str, u32, MipsFuncUnit)> = vec![
(MipsOpcode::ADD_D, "addsd", 4, MipsFuncUnit::FPAdd),
(MipsOpcode::SUB_D, "subsd", 4, MipsFuncUnit::FPAdd),
(MipsOpcode::MUL_D, "mulsd", 4, MipsFuncUnit::FPMul),
(MipsOpcode::DIV_D, "divsd", 14, MipsFuncUnit::FPDiv),
(MipsOpcode::MOV_D, "movsd", 1, MipsFuncUnit::FPAdd),
(MipsOpcode::NEG_D, "xorpd (sign)", 1, MipsFuncUnit::FPAdd),
(MipsOpcode::ABS_D, "andpd (abs)", 1, MipsFuncUnit::FPAdd),
(MipsOpcode::SQRT_D, "sqrtsd", 14, MipsFuncUnit::FPDiv),
(MipsOpcode::CVT_D_W, "cvtsi2sd", 3, MipsFuncUnit::FPAdd),
(MipsOpcode::CVT_W_D, "cvttsd2si", 3, MipsFuncUnit::FPAdd),
(MipsOpcode::CVT_S_D, "cvtsd2ss", 3, MipsFuncUnit::FPAdd),
(MipsOpcode::CVT_D_S, "cvtss2sd", 3, MipsFuncUnit::FPAdd),
(MipsOpcode::C_EQ_D, "comisd / sete", 2, MipsFuncUnit::FPAdd),
(MipsOpcode::C_LT_D, "comisd / setb", 2, MipsFuncUnit::FPAdd),
(MipsOpcode::C_LE_D, "comisd / setbe", 2, MipsFuncUnit::FPAdd),
];
for (op, x86, lat, fu) in fpu_d_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 0,
fpr_used: 3,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::FRType);
func_units.insert(op, fu);
delay_slots.insert(op, false);
}
}
fn register_fpu_mem(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let fpu_mem_ops = vec![
(MipsOpcode::LWC1, "movss (load)", 3),
(MipsOpcode::SWC1, "movss (store)", 1),
(MipsOpcode::LDC1, "movsd (load)", 3),
(MipsOpcode::SDC1, "movsd (store)", 1),
];
for (op, x86, lat) in fpu_mem_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 1,
fpr_used: 1,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::IType);
func_units.insert(op, MipsFuncUnit::LSU);
delay_slots.insert(op, false);
}
}
fn register_mips64_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let mips64_ops: Vec<(MipsOpcode, &str, u32, MipsFuncUnit)> = vec![
(MipsOpcode::DADD, "add (64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DADDU, "add (64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DSUB, "sub (64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DSUBU, "sub (64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DADDIU, "add imm (64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DSLL, "shl (64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DSRL, "shr (64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DSRA, "sar (64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DSLLV, "shl (cl,64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DSRLV, "shr (cl,64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DSRAV, "sar (cl,64)", 1, MipsFuncUnit::ALU),
(MipsOpcode::DMULT, "imul (64)", 5, MipsFuncUnit::MDU),
(MipsOpcode::DMULTU, "mul (64)", 5, MipsFuncUnit::MDU),
(MipsOpcode::DDIV, "idiv (64)", 16, MipsFuncUnit::MDU),
(MipsOpcode::DDIVU, "div (64)", 16, MipsFuncUnit::MDU),
(MipsOpcode::LD, "mov (load 64)", 3, MipsFuncUnit::LSU),
(MipsOpcode::SD, "mov (store 64)", 1, MipsFuncUnit::LSU),
(MipsOpcode::LDL, "load left (64)", 3, MipsFuncUnit::LSU),
(MipsOpcode::LDR, "load right (64)", 3, MipsFuncUnit::LSU),
(MipsOpcode::SDL, "store left (64)", 1, MipsFuncUnit::LSU),
(MipsOpcode::SDR, "store right (64)", 1, MipsFuncUnit::LSU),
(MipsOpcode::DADDI, "add imm (64)", 1, MipsFuncUnit::ALU),
];
for (op, x86, lat, fu) in mips64_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: if matches!(op, MipsOpcode::LD | MipsOpcode::SD | MipsOpcode::LDL | MipsOpcode::LDR | MipsOpcode::SDL | MipsOpcode::SDR) { 2 } else { 3 },
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, fu);
delay_slots.insert(op, false);
}
}
fn register_msa_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let msa_int_arith: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::ADDV_B, "paddb"), (MipsOpcode::ADDV_H, "paddw"),
(MipsOpcode::ADDV_W, "paddd"), (MipsOpcode::ADDV_D, "paddq"),
(MipsOpcode::SUBV_B, "psubb"), (MipsOpcode::SUBV_H, "psubw"),
(MipsOpcode::SUBV_W, "psubd"), (MipsOpcode::SUBV_D, "psubq"),
(MipsOpcode::MULV_B, "pmullw"), (MipsOpcode::MULV_H, "pmullw"),
(MipsOpcode::MULV_W, "pmulld"), (MipsOpcode::MULV_D, "pmulld"),
];
for (op, x86) in msa_int_arith {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 0,
fpr_used: 3,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::MSA);
delay_slots.insert(op, false);
}
let msa_bitwise: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::ANDV, "pand"), (MipsOpcode::ORV, "por"),
(MipsOpcode::XORV, "pxor"), (MipsOpcode::NORV, "pandn"),
];
for (op, x86) in msa_bitwise {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.33, size: 4, uops: 1, gpr_used: 0, fpr_used: 3,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::MSA);
delay_slots.insert(op, false);
}
let msa_cmp: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::CEQ_B, "pcmpeqb"), (MipsOpcode::CEQ_H, "pcmpeqw"),
(MipsOpcode::CEQ_W, "pcmpeqd"), (MipsOpcode::CEQ_D, "pcmpeqq"),
(MipsOpcode::CLE_S_B, "pcmpgtb"), (MipsOpcode::CLE_S_H, "pcmpgtw"),
(MipsOpcode::CLE_S_W, "pcmpgtd"), (MipsOpcode::CLE_S_D, "pcmpgtq"),
(MipsOpcode::CLT_S_B, "pcmpgtb"), (MipsOpcode::CLT_S_H, "pcmpgtw"),
(MipsOpcode::CLT_S_W, "pcmpgtd"), (MipsOpcode::CLT_S_D, "pcmpgtq"),
];
for (op, x86) in msa_cmp {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 3,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::MSA);
delay_slots.insert(op, false);
}
let msa_fp: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::FADD_W, "addps"), (MipsOpcode::FADD_D, "addpd"),
(MipsOpcode::FSUB_W, "subps"), (MipsOpcode::FSUB_D, "subpd"),
(MipsOpcode::FMUL_W, "mulps"), (MipsOpcode::FMUL_D, "mulpd"),
(MipsOpcode::FDIV_W, "divps"), (MipsOpcode::FDIV_D, "divpd"),
(MipsOpcode::FMAX_W, "maxps"), (MipsOpcode::FMAX_D, "maxpd"),
(MipsOpcode::FMIN_W, "minps"), (MipsOpcode::FMIN_D, "minpd"),
(MipsOpcode::FCEQ_W, "cmpeqps"), (MipsOpcode::FCEQ_D, "cmpeqpd"),
(MipsOpcode::FCLT_W, "cmpltps"), (MipsOpcode::FCLT_D, "cmpltpd"),
(MipsOpcode::FCLE_W, "cmpleps"), (MipsOpcode::FCLE_D, "cmplepd"),
];
for (op, x86) in msa_fp {
let lat: u32 = if matches!(op, MipsOpcode::FDIV_W | MipsOpcode::FDIV_D) { 12 } else { 4 };
costs.insert(op.clone(), TargetInstructionCost {
latency: lat, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 3,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::MSA);
delay_slots.insert(op, false);
}
let msa_mem: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::LD_B, "movdqa (load)"), (MipsOpcode::LD_H, "movdqa (load)"),
(MipsOpcode::LD_W, "movdqa (load)"), (MipsOpcode::LD_D, "movdqa (load)"),
(MipsOpcode::ST_B, "movdqa (store)"), (MipsOpcode::ST_H, "movdqa (store)"),
(MipsOpcode::ST_W, "movdqa (store)"), (MipsOpcode::ST_D, "movdqa (store)"),
];
for (op, x86) in msa_mem {
costs.insert(op.clone(), TargetInstructionCost {
latency: 3, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 1, fpr_used: 1,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::IType);
func_units.insert(op, MipsFuncUnit::LSU);
delay_slots.insert(op, false);
}
for op in [MipsOpcode::BNZ_V, MipsOpcode::BZ_V] {
let x86 = if *op == MipsOpcode::BNZ_V { "ptest / jnz" } else { "ptest / jz" };
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 1,
});
x86_equivs.insert(op.clone(), x86.to_string());
enc_types.insert(op.clone(), MipsEncodingType::IType);
func_units.insert(op.clone(), MipsFuncUnit::Branch);
delay_slots.insert(op.clone(), false);
}
let msa_shuffle: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::ILVEV_B, "punpcklbw"), (MipsOpcode::ILVEV_H, "punpcklwd"),
(MipsOpcode::ILVEV_W, "punpckldq"), (MipsOpcode::ILVEV_D, "punpcklqdq"),
(MipsOpcode::ILVOD_B, "punpckhbw"), (MipsOpcode::ILVOD_H, "punpckhwd"),
(MipsOpcode::ILVOD_W, "punpckhdq"), (MipsOpcode::ILVOD_D, "punpckhqdq"),
(MipsOpcode::ILVL_B, "punpcklbw"), (MipsOpcode::ILVL_H, "punpcklwd"),
(MipsOpcode::ILVL_W, "punpckldq"), (MipsOpcode::ILVL_D, "punpcklqdq"),
(MipsOpcode::ILVR_B, "punpckhbw"), (MipsOpcode::ILVR_H, "punpckhwd"),
(MipsOpcode::ILVR_W, "punpckhdq"), (MipsOpcode::ILVR_D, "punpckhqdq"),
(MipsOpcode::PCKEV_B, "packuswb"), (MipsOpcode::PCKEV_H, "packusdw"),
(MipsOpcode::PCKEV_W, "packusdw"), (MipsOpcode::PCKEV_D, "packusdw"),
(MipsOpcode::PCKOD_B, "packsswb"), (MipsOpcode::PCKOD_H, "packssdw"),
(MipsOpcode::PCKOD_W, "packssdw"), (MipsOpcode::PCKOD_D, "packssdw"),
];
for (op, x86) in msa_shuffle {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 3,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::MSA);
delay_slots.insert(op, false);
}
for op in [MipsOpcode::SLDI_B, MipsOpcode::SLDI_H, MipsOpcode::SLDI_W, MipsOpcode::SLDI_D] {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 2,
});
x86_equivs.insert(op.clone(), "pslldq / palignr".to_string());
enc_types.insert(op.clone(), MipsEncodingType::RType);
func_units.insert(op.clone(), MipsFuncUnit::MSA);
delay_slots.insert(op.clone(), false);
}
for op in [MipsOpcode::INSERT_B, MipsOpcode::INSERT_H, MipsOpcode::INSERT_W, MipsOpcode::INSERT_D] {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 2,
});
x86_equivs.insert(op.clone(), "pinsrb / pinsrw / pinsrd / pinsrq".to_string());
enc_types.insert(op.clone(), MipsEncodingType::RType);
func_units.insert(op.clone(), MipsFuncUnit::MSA);
delay_slots.insert(op.clone(), false);
}
}
fn register_dsp_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let dsp_ops: Vec<(MipsOpcode, &str, u32)> = vec![
(MipsOpcode::ADDQ_PH, "paddw", 1),
(MipsOpcode::ADDQ_S_PH, "paddsw", 1),
(MipsOpcode::ADDQ_S_W, "paddd", 1),
(MipsOpcode::ADDU_QB, "paddb", 1),
(MipsOpcode::ADDU_S_QB, "paddusb", 1),
(MipsOpcode::ADDWC, "adc", 1),
(MipsOpcode::SUBQ_PH, "psubw", 1),
(MipsOpcode::SUBQ_S_PH, "psubsw", 1),
(MipsOpcode::SUBQ_S_W, "psubd", 1),
(MipsOpcode::SUBU_QB, "psubb", 1),
(MipsOpcode::SUBU_S_QB, "psubusb", 1),
(MipsOpcode::MUL_PH, "pmullw", 3),
(MipsOpcode::MUL_S_PH, "pmulhw", 3),
(MipsOpcode::MULQ_RS_W, "pmulhrsw", 3),
(MipsOpcode::MULQ_S_PH, "pmulhw", 3),
(MipsOpcode::MULQ_S_W, "pmulld", 4),
(MipsOpcode::MADD, "pmaddwd", 3),
(MipsOpcode::MADDU, "pmaddwd u", 3),
(MipsOpcode::MSUB, "pmaddwd (sub)", 3),
];
for (op, x86, lat) in dsp_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 4,
uops: 1,
gpr_used: 2,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::DSP);
delay_slots.insert(op, false);
}
let dsp_pack: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::PACKRL_PH, "packssdw"),
(MipsOpcode::PICK_QB, "packuswb"),
(MipsOpcode::PICK_PH, "packssdw"),
(MipsOpcode::PRECEQ_W_PHL, "punpcklwd"),
(MipsOpcode::PRECEQ_W_PHR, "punpckhwd"),
(MipsOpcode::PRECEQU_PH_QBL, "punpcklbw"),
(MipsOpcode::PRECEQU_PH_QBR, "punpckhbw"),
(MipsOpcode::PRECEU_PH_QBL, "punpcklbw"),
(MipsOpcode::PRECEU_PH_QBR, "punpckhbw"),
(MipsOpcode::PRECRQ_QB_PH, "packuswb"),
(MipsOpcode::PRECRQ_PH_W, "packssdw"),
(MipsOpcode::PRECRQ_RS_PH_W, "packssdw"),
(MipsOpcode::EXTR_W, "pextrd"),
(MipsOpcode::EXTR_R_W, "pextrd"),
(MipsOpcode::EXTR_RS_W, "pextrd"),
(MipsOpcode::EXTR_S_H, "pextrw"),
(MipsOpcode::INSV, "pinsrd"),
(MipsOpcode::REPL_QB, "pbroadcastb"),
(MipsOpcode::REPL_PH, "pbroadcastw"),
];
for (op, x86) in dsp_pack {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 2,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::DSP);
delay_slots.insert(op, false);
}
let dsp_shift: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::SHLL_QB, "psllw"),
(MipsOpcode::SHLL_PH, "pslld"),
(MipsOpcode::SHRA_R_PH, "psrad"),
(MipsOpcode::SHRA_R_W, "psrad"),
(MipsOpcode::SHRL_QB, "psrlw"),
];
for (op, x86) in dsp_shift {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 2,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::DSP);
delay_slots.insert(op, false);
}
let dsp_cmp: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::CMPGU_EQ_QB, "pcmpeqb"),
(MipsOpcode::CMPGU_LT_QB, "pcmpgtb"),
(MipsOpcode::CMPGU_LE_QB, "pcmpgtb / pcmpeqb"),
(MipsOpcode::CMPU_EQ_QB, "pcmpeqb"),
(MipsOpcode::CMPU_LT_QB, "pcmpgtb"),
(MipsOpcode::CMPU_LE_QB, "pcmpgtb / pcmpeqb"),
];
for (op, x86) in dsp_cmp {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1, rthroughput: 0.5, size: 4, uops: 1, gpr_used: 0, fpr_used: 2,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::DSP);
delay_slots.insert(op, false);
}
let dsp_dpa: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::DPA_W_PH, "pmaddwd"),
(MipsOpcode::DPAQX_S_W_PH, "pmaddwd"),
(MipsOpcode::DPAQX_SA_W_PH, "pmaddwd"),
(MipsOpcode::DPAU_H_QBL, "pmaddubsw"),
(MipsOpcode::DPAU_H_QBR, "pmaddubsw"),
(MipsOpcode::DPS_W_PH, "pmaddwd"),
(MipsOpcode::DPSQX_S_W_PH, "pmaddwd"),
(MipsOpcode::DPSQX_SA_W_PH, "pmaddwd"),
(MipsOpcode::DPSU_H_QBL, "pmaddubsw"),
(MipsOpcode::DPSU_H_QBR, "pmaddubsw"),
(MipsOpcode::MTHLIP, "pextrw / pinsrw"),
];
for (op, x86) in dsp_dpa {
costs.insert(op.clone(), TargetInstructionCost {
latency: 3, rthroughput: 1.0, size: 4, uops: 2, gpr_used: 0, fpr_used: 2,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::RType);
func_units.insert(op, MipsFuncUnit::DSP);
delay_slots.insert(op, false);
}
}
fn register_pseudo_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let pseudo_ops: Vec<(MipsOpcode, &str, u32)> = vec![
(MipsOpcode::NOP, "nop", 0),
(MipsOpcode::MOVE, "mov", 1),
(MipsOpcode::LI, "mov imm", 1),
(MipsOpcode::LA, "lea", 1),
(MipsOpcode::B, "jmp", 1),
(MipsOpcode::BGE, "jge", 1),
(MipsOpcode::BLT, "jl", 1),
(MipsOpcode::BGT, "jg", 1),
(MipsOpcode::BLE, "jle", 1),
(MipsOpcode::BEQZ, "jz", 1),
(MipsOpcode::BNEZ, "jnz", 1),
(MipsOpcode::NEG, "neg", 1),
(MipsOpcode::NOT, "not", 1),
(MipsOpcode::ABS, "abs / cdq; xor; sub", 2),
];
for (op, x86, lat) in pseudo_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: lat,
rthroughput: 0.5,
size: 0, uops: 0,
gpr_used: if matches!(op, MipsOpcode::NOP) { 0 } else { 2 },
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::Pseudo);
func_units.insert(op, MipsFuncUnit::ALU);
delay_slots.insert(op, matches!(op, MipsOpcode::B | MipsOpcode::BGE | MipsOpcode::BLT | MipsOpcode::BGT | MipsOpcode::BLE | MipsOpcode::BEQZ | MipsOpcode::BNEZ));
}
}
fn register_system_insts(
costs: &mut HashMap<MipsOpcode, TargetInstructionCost>,
x86_equivs: &mut HashMap<MipsOpcode, String>,
enc_types: &mut HashMap<MipsOpcode, MipsEncodingType>,
func_units: &mut HashMap<MipsOpcode, MipsFuncUnit>,
delay_slots: &mut HashMap<MipsOpcode, bool>,
) {
let sys_ops: Vec<(MipsOpcode, &str)> = vec![
(MipsOpcode::SYSCALL, "syscall"),
(MipsOpcode::BREAK, "int3"),
(MipsOpcode::SYNC, "mfence / lfence / sfence"),
(MipsOpcode::MFC0, "mov from cr"),
(MipsOpcode::MTC0, "mov to cr"),
(MipsOpcode::ERET, "iretq / sysexit"),
(MipsOpcode::WAIT, "hlt / pause"),
(MipsOpcode::DMT, "cli"),
(MipsOpcode::EMT, "sti"),
(MipsOpcode::DVPE, "vmxoff"),
(MipsOpcode::EVPE, "vmlaunch"),
(MipsOpcode::YIELD_MT, "pause"),
(MipsOpcode::MFTR, "rdtsc"),
(MipsOpcode::MTTR, "wrmsr"),
];
for (op, x86) in sys_ops {
costs.insert(op.clone(), TargetInstructionCost {
latency: 1,
rthroughput: 1.0,
size: 4,
uops: 1,
gpr_used: 0,
fpr_used: 0,
});
x86_equivs.insert(op, x86.to_string());
enc_types.insert(op, MipsEncodingType::CopType);
func_units.insert(op, MipsFuncUnit::System);
delay_slots.insert(op, false);
}
}
pub fn get_cost(&self, opcode: &MipsOpcode) -> Option<&TargetInstructionCost> {
self.costs.get(opcode)
}
pub fn get_x86_equivalent(&self, opcode: &MipsOpcode) -> Option<&String> {
self.x86_equivalents.get(opcode)
}
pub fn get_encoding_type(&self, opcode: &MipsOpcode) -> MipsEncodingType {
self.encoding_types.get(opcode).copied().unwrap_or(MipsEncodingType::RType)
}
pub fn get_func_unit(&self, opcode: &MipsOpcode) -> MipsFuncUnit {
self.func_units.get(opcode).copied().unwrap_or(MipsFuncUnit::ALU)
}
pub fn has_delay_slot(&self, opcode: &MipsOpcode) -> bool {
self.has_delay_slot.get(opcode).copied().unwrap_or(false)
}
pub fn cost_count(&self) -> usize {
self.costs.len()
}
pub fn x86_equiv_count(&self) -> usize {
self.x86_equivalents.len()
}
pub fn sorted_by_latency(&self) -> Vec<(MipsOpcode, u32)> {
let mut v: Vec<(MipsOpcode, u32)> = self.costs.iter()
.map(|(op, c)| (op.clone(), c.latency))
.collect();
v.sort_by(|a, b| b.1.cmp(&a.1));
v
}
pub fn by_func_unit(&self, fu: MipsFuncUnit) -> Vec<MipsOpcode> {
self.func_units.iter()
.filter(|(_, f)| **f == fu)
.map(|(op, _)| op.clone())
.collect()
}
pub fn average_cost(&self, opcodes: &[MipsOpcode]) -> f64 {
if opcodes.is_empty() {
return 0.0;
}
let total: u32 = opcodes.iter()
.filter_map(|op| self.costs.get(op))
.map(|c| c.latency)
.sum();
total as f64 / opcodes.len() as f64
}
}
pub struct MIPSExtendedRegisterInfo {
pub base: MipsRegisterInfo,
pub gpr_abi_names: [&'static str; 32],
pub fpr_abi_names: [&'static str; 32],
pub callee_saved_o32: Vec<u16>,
pub callee_saved_n32: Vec<u16>,
pub callee_saved_n64: Vec<u16>,
pub caller_saved_o32: Vec<u16>,
pub caller_saved_n32: Vec<u16>,
pub caller_saved_n64: Vec<u16>,
pub max_allocatable_gprs: usize,
pub max_allocatable_fprs: usize,
pub reserved: Vec<u16>,
pub dwarf_nums: HashMap<u16, u16>,
}
impl MIPSExtendedRegisterInfo {
pub fn new() -> Self {
let gpr_abi_names: [&'static str; 32] = [
"zero", "at", "v0", "v1", "a0", "a1", "a2", "a3",
"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
"t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra",
];
let fpr_abi_names: [&'static str; 32] = {
let mut names = ["f0"; 32];
for i in 0..32 {
names[i] = match i {
0..=1 => if i == 0 { "fv0" } else { "fv1" },
2..=3 => if i == 2 { "f2" } else { "f3" },
4..=11 => if i == 4 { "ft0" } else if i == 5 { "ft1" }
else if i == 6 { "ft2" } else if i == 7 { "ft3" }
else if i == 8 { "ft4" } else if i == 9 { "ft5" }
else if i == 10 { "ft6" } else { "ft7" },
12..=13 => if i == 12 { "fa0" } else { "fa1" },
14..=19 => if i == 14 { "ft8" } else if i == 15 { "ft9" }
else if i == 16 { "ft10" } else if i == 17 { "ft11" }
else if i == 18 { "ft12" } else { "ft13" },
20..=31 => match i {
20 => "fs0", 21 => "fs1", 22 => "fs2", 23 => "fs3",
24 => "fs4", 25 => "fs5", 26 => "fs6", 27 => "fs7",
28 => "fs8", 29 => "fs9", 30 => "fs10", 31 => "fs11",
_ => "f?",
},
_ => "f?",
};
}
names
};
let callee_saved_o32: Vec<u16> = vec![4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4030, 4031];
let callee_saved_n32: Vec<u16> = vec![4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4028, 4029, 4030, 4031];
let callee_saved_n64: Vec<u16> = callee_saved_n32.clone();
let caller_saved_o32: Vec<u16> = vec![4001, 4002, 4003, 4004, 4005, 4006, 4007,
4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4024, 4025];
let caller_saved_n32: Vec<u16> = vec![4001, 4002, 4003, 4004, 4005, 4006, 4007,
4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4024, 4025];
let caller_saved_n64: Vec<u16> = caller_saved_n32.clone();
let callee_saved_fpr_o32: Vec<u16> = (MIPS_FPR_BASE + 20..=MIPS_FPR_BASE + 31).collect();
let callee_saved_fpr_n32: Vec<u16> = (MIPS_FPR_BASE + 24..=MIPS_FPR_BASE + 31).collect();
let callee_saved_fpr_n64: Vec<u16> = callee_saved_fpr_n32.clone();
let reserved: Vec<u16> = vec![4000, 4026, 4027];
let mut dwarf_nums = HashMap::new();
for i in 0..32u16 {
dwarf_nums.insert(MIPS_GPR_BASE + i, i);
}
for i in 0..32u16 {
dwarf_nums.insert(MIPS_FPR_BASE + i, 32 + i);
}
dwarf_nums.insert(4032, 64); dwarf_nums.insert(4033, 65); dwarf_nums.insert(4034, 66);
MIPSExtendedRegisterInfo {
base: MipsRegisterInfo,
gpr_abi_names,
fpr_abi_names,
callee_saved_o32,
callee_saved_n32,
callee_saved_n64,
caller_saved_o32,
caller_saved_n32,
caller_saved_n64,
max_allocatable_gprs: 25, max_allocatable_fprs: 32,
reserved,
dwarf_nums,
}
}
pub fn get_callee_saved(&self, abi: MipsAbi) -> &[u16] {
match abi {
MipsAbi::O32 => &self.callee_saved_o32,
MipsAbi::N32 => &self.callee_saved_n32,
MipsAbi::N64 => &self.callee_saved_n64,
}
}
pub fn get_caller_saved(&self, abi: MipsAbi) -> &[u16] {
match abi {
MipsAbi::O32 => &self.caller_saved_o32,
MipsAbi::N32 => &self.caller_saved_n32,
MipsAbi::N64 => &self.caller_saved_n64,
}
}
pub fn gpr_abi_name(&self, idx: usize) -> &str {
if idx < 32 { self.gpr_abi_names[idx] } else { "?" }
}
pub fn fpr_abi_name(&self, idx: usize) -> &str {
if idx < 32 { self.fpr_abi_names[idx] } else { "?" }
}
pub fn is_reserved(&self, reg: u16) -> bool {
self.reserved.contains(®)
}
pub fn get_dwarf_num(&self, reg: u16) -> Option<u16> {
self.dwarf_nums.get(®).copied()
}
}
pub struct MIPSFrameLowering {
pub bridge: MIPSX86Bridge,
pub eliminate_frame_pointer: bool,
pub needs_stack_realignment: bool,
pub has_var_sized_alloca: bool,
pub max_call_frame_size: u32,
pub callee_saved_count: u32,
pub local_area_size: u32,
pub is_leaf: bool,
}
impl MIPSFrameLowering {
pub fn new(bridge: MIPSX86Bridge) -> Self {
MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
}
}
pub fn needs_frame_pointer(&self) -> bool {
if self.has_var_sized_alloca {
return true; }
if self.local_area_size > 32768 {
return true; }
if self.bridge.mips_abi == MipsAbi::O32 && self.local_area_size > 0 && !self.is_leaf {
return true; }
false
}
pub fn calculate_frame(&self) -> MIPSFrameLayout {
let word_size = if self.bridge.is_64bit { 8 } else { 4 };
let mut offset = 0i32;
let mut layout = MIPSFrameLayout::default();
if !self.is_leaf {
offset -= self.max_call_frame_size as i32;
layout.outgoing_args_offset = offset;
layout.outgoing_args_size = self.max_call_frame_size;
}
if self.local_area_size > 0 {
offset -= self.local_area_size as i32;
layout.locals_offset = offset;
layout.locals_size = self.local_area_size;
}
let callee_size = self.callee_saved_count * word_size;
if callee_size > 0 {
offset -= callee_size as i32;
layout.callee_saved_offset = offset;
layout.callee_saved_size = callee_size;
}
let fp_save = if self.needs_frame_pointer() { word_size } else { 0 };
if fp_save > 0 {
offset -= fp_save as i32;
layout.fp_save_offset = offset;
}
let ra_save = if !self.is_leaf { word_size } else { 0 };
if ra_save > 0 {
offset -= ra_save as i32;
layout.ra_save_offset = offset;
}
let align = self.bridge.mips_abi.stack_alignment() as i32;
let total = (-offset) as u32;
let padded = if total % align as u32 != 0 {
let pad = align as u32 - (total % align as u32);
offset -= pad as i32;
pad
} else {
0
};
layout.alignment_padding = padded;
layout.frame_size = (-offset) as u32;
layout.stack_alignment = align as u32;
layout.has_frame_pointer = self.needs_frame_pointer();
layout
}
pub fn emit_prologue(&self) -> String {
let layout = self.calculate_frame();
let mut out = String::new();
let word_size = if self.bridge.is_64bit { 8 } else { 4 };
let load_store = if self.bridge.is_64bit { "sd" } else { "sw" };
let add_imm = if self.bridge.is_64bit { "daddiu" } else { "addiu" };
if layout.frame_size > 0 {
out.push_str(&format!("\t{}\t$sp, $sp, -{}\n", add_imm, layout.frame_size));
}
if !self.is_leaf {
out.push_str(&format!("\t{}\t$ra, {}({})\n",
if self.bridge.is_64bit { "sd" } else { "sw" },
layout.ra_save_offset,
if layout.has_frame_pointer { "$sp" } else { "$sp" }
));
}
if layout.has_frame_pointer {
out.push_str(&format!("\t{}\t$fp, {}({})\n",
load_store, layout.fp_save_offset, "$sp"
));
}
let callee_saved = self.get_callee_saved_gprs();
let mut cs_offset = layout.callee_saved_offset;
for (i, reg) in callee_saved.iter().enumerate() {
let reg_name = self.bridge.mips_reg_info.get_asm_name(*reg);
out.push_str(&format!("\t{}\t{}, {}({})\n",
load_store, reg_name, cs_offset, "$sp"
));
cs_offset += word_size as i32;
}
if layout.has_frame_pointer {
out.push_str(&format!("\t{}\t$fp, $sp, {}\n", add_imm, layout.frame_size));
}
out
}
pub fn emit_epilogue(&self) -> String {
let layout = self.calculate_frame();
let mut out = String::new();
let word_size = if self.bridge.is_64bit { 8 } else { 4 };
let load_store = if self.bridge.is_64bit { "ld" } else { "lw" };
let add_imm = if self.bridge.is_64bit { "daddiu" } else { "addiu" };
if layout.has_frame_pointer {
out.push_str(&format!("\tmove\t$sp, $fp\n"));
}
let callee_saved = self.get_callee_saved_gprs();
let mut cs_offset = layout.callee_saved_offset;
for (i, reg) in callee_saved.iter().enumerate() {
let reg_name = self.bridge.mips_reg_info.get_asm_name(*reg);
out.push_str(&format!("\t{}\t{}, {}({})\n",
load_store, reg_name, cs_offset, "$sp"
));
cs_offset += word_size as i32;
}
if layout.has_frame_pointer {
out.push_str(&format!("\t{}\t$fp, {}({})\n",
load_store, layout.fp_save_offset, "$sp"
));
}
if !self.is_leaf {
out.push_str(&format!("\t{}\t$ra, {}({})\n",
load_store, layout.ra_save_offset, "$sp"
));
}
if layout.frame_size > 0 {
out.push_str(&format!("\t{}\t$sp, $sp, {}\n", add_imm, layout.frame_size));
}
out.push_str("\tjr\t$ra\n");
if MIPS_DELAY_SLOT_SIZE > 0 {
out.push_str("\tnop\n"); }
out
}
fn get_callee_saved_gprs(&self) -> Vec<u16> {
match self.bridge.mips_abi {
MipsAbi::O32 => vec![4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023],
MipsAbi::N32 | MipsAbi::N64 => vec![4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023],
}
}
pub fn get_local_offset(&self, local_idx: u32) -> i32 {
let layout = self.calculate_frame();
layout.locals_offset + (local_idx as i32 * if self.bridge.is_64bit { 8 } else { 4 })
}
pub fn get_frame_base_reg(&self) -> u16 {
if self.needs_frame_pointer() {
4030 } else {
4029 }
}
pub fn get_frame_base_name(&self) -> &str {
if self.needs_frame_pointer() {
"$fp"
} else {
"$sp"
}
}
pub fn emit_frame_info_comment(&self) -> String {
let layout = self.calculate_frame();
format!(
"; Frame: size={}, fp={}, ra_offset={}, fp_offset={}, cs_offset={}, cs_size={}",
layout.frame_size,
layout.has_frame_pointer,
layout.ra_save_offset,
layout.fp_save_offset,
layout.callee_saved_offset,
layout.callee_saved_size,
)
}
}
#[derive(Debug, Clone, Default)]
pub struct MIPSFrameLayout {
pub frame_size: u32,
pub ra_save_offset: i32,
pub fp_save_offset: i32,
pub callee_saved_offset: i32,
pub callee_saved_size: u32,
pub locals_offset: i32,
pub locals_size: u32,
pub outgoing_args_offset: i32,
pub outgoing_args_size: u32,
pub alignment_padding: u32,
pub stack_alignment: u32,
pub has_frame_pointer: bool,
}
pub struct MIPSCallingConvention {
pub abi: MipsAbi,
pub is_64bit: bool,
pub int_arg_regs: Vec<u16>,
pub fp_arg_regs: Vec<u16>,
pub int_ret_regs: Vec<u16>,
pub fp_ret_regs: Vec<u16>,
pub remaining_int_args: usize,
pub remaining_fp_args: usize,
pub next_stack_offset: u32,
pub use_regs_for_args: bool,
pub is_varargs: bool,
pub is_sret: bool,
}
impl MIPSCallingConvention {
pub fn new(abi: MipsAbi) -> Self {
let is_64bit = matches!(abi, MipsAbi::N64);
let (int_args, fp_args) = match abi {
MipsAbi::O32 => (
vec![4004, 4005, 4006, 4007], vec![4062, 4063], ),
MipsAbi::N32 | MipsAbi::N64 => (
vec![4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011],
vec![4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069],
),
};
MIPSCallingConvention {
abi,
is_64bit,
remaining_int_args: int_args.len(),
remaining_fp_args: fp_args.len(),
int_arg_regs: int_args,
fp_arg_regs: fp_args,
int_ret_regs: vec![4002, 4003], fp_ret_regs: vec![4050, 4051], next_stack_offset: 16, use_regs_for_args: true,
is_varargs: false,
is_sret: false,
}
}
pub fn reset(&mut self) {
self.remaining_int_args = self.int_arg_regs.len();
self.remaining_fp_args = self.fp_arg_regs.len();
self.next_stack_offset = 16;
self.is_varargs = false;
self.is_sret = false;
}
pub fn assign_int_arg(&mut self, size_bytes: u32) -> (bool, u16, u32) {
let slots_needed = if self.is_64bit && size_bytes > 4 {
((size_bytes + 7) / 8) as usize
} else {
((size_bytes + 3) / 4) as usize
};
if self.remaining_int_args >= slots_needed && self.use_regs_for_args {
let reg_idx = self.int_arg_regs.len() - self.remaining_int_args;
let reg = self.int_arg_regs[reg_idx];
self.remaining_int_args -= slots_needed;
(true, reg, 0)
} else {
let offset = self.next_stack_offset;
self.next_stack_offset += size_bytes;
if self.next_stack_offset % if self.is_64bit { 8 } else { 4 } != 0 {
self.next_stack_offset = ((self.next_stack_offset / 4) + 1) * 4;
}
(false, 0, offset)
}
}
pub fn assign_fp_arg(&mut self, size_bytes: u32) -> (bool, u16, u32) {
if self.remaining_fp_args > 0 && self.use_regs_for_args {
let reg_idx = self.fp_arg_regs.len() - self.remaining_fp_args;
let reg = self.fp_arg_regs[reg_idx];
self.remaining_fp_args -= 1;
(true, reg, 0)
} else {
let offset = self.next_stack_offset;
self.next_stack_offset += size_bytes.max(if self.is_64bit { 8 } else { 4 });
(false, 0, offset)
}
}
pub fn get_int_return_reg(&self, idx: usize) -> Option<u16> {
self.int_ret_regs.get(idx).copied()
}
pub fn get_fp_return_reg(&self, idx: usize) -> Option<u16> {
self.fp_ret_regs.get(idx).copied()
}
pub fn get_sret_reg(&self) -> u16 {
4004 }
pub fn is_arg_reg(&self, reg: u16) -> bool {
self.int_arg_regs.contains(®) || self.fp_arg_regs.contains(®)
}
pub fn get_used_stack_space(&self) -> u32 {
let base = if self.is_64bit { 16 } else { 16 };
if self.next_stack_offset > base {
self.next_stack_offset - base
} else {
0
}
}
pub fn get_varargs_save_area_size(&self) -> u32 {
if self.is_varargs {
self.abi.varargs_save_size()
} else {
0
}
}
pub fn reg_name(&self, reg: u16) -> String {
if reg >= MIPS_GPR_BASE && reg < MIPS_GPR_BASE + 32 {
format!("${}", MipsRegisterInfo::get_abi_name(reg))
} else if reg >= MIPS_FPR_BASE && reg < MIPS_FPR_BASE + 32 {
format!("$f{}", reg - MIPS_FPR_BASE)
} else {
MipsRegisterInfo::get_asm_name(reg)
}
}
pub fn return_in_regs(&self, size_bytes: u32) -> bool {
if self.is_sret {
return false;
}
let max_reg_size = if self.is_64bit { 8 } else { 4 };
size_bytes <= max_reg_size * self.int_ret_regs.len() as u32
}
pub fn abi_name(&self) -> &str {
match self.abi {
MipsAbi::O32 => "o32",
MipsAbi::N32 => "n32",
MipsAbi::N64 => "n64",
}
}
}
pub struct CrossTargetMIPS {
pub bridge: MIPSX86Bridge,
pub patterns: HashMap<u32, CrossPattern>,
pub const_materializer: ConstMaterializer,
pub addr_modes: AddressingModes,
pub branch_lowering: BranchLowering,
pub phi_elimination: PhiElimination,
pub call_lowering: CallLowering,
}
#[derive(Debug, Clone)]
pub struct CrossPattern {
pub id: u32,
pub description: String,
pub mips_expansion: Vec<String>,
pub x86_expansion: Vec<String>,
pub is_semantically_identical: bool,
pub mips_cycles: u32,
pub x86_cycles: u32,
}
#[derive(Debug, Clone)]
pub struct ConstMaterializer {
pub materialize_i32: Vec<ConstMaterializationPattern>,
pub materialize_i64: Vec<ConstMaterializationPattern>,
pub materialize_f32: Vec<ConstMaterializationPattern>,
pub materialize_f64: Vec<ConstMaterializationPattern>,
}
#[derive(Debug, Clone)]
pub struct ConstMaterializationPattern {
pub mips: Vec<String>,
pub x86: Vec<String>,
pub mips_cost: u32,
pub x86_cost: u32,
pub value_range: (i64, i64),
}
#[derive(Debug, Clone, Default)]
pub struct AddressingModes {
pub base_plus_offset: bool,
pub base_plus_index: bool,
pub base_plus_index_scale: bool,
pub pc_relative: bool,
pub gp_relative: bool,
pub max_offset: i32,
pub unaligned_access: bool,
}
#[derive(Debug, Clone, Default)]
pub struct BranchLowering {
pub direct_condition: bool,
pub needs_trampolines: bool,
pub max_branch_displacement: i32,
pub has_delay_slot: bool,
pub predict_forward_not_taken: bool,
}
#[derive(Debug, Clone, Default)]
pub struct PhiElimination {
pub strategy: PhiStrategy,
pub needs_critical_edge_split: bool,
pub max_sources_before_spill: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhiStrategy {
CopyAtPred,
CopyAtSucc,
SwapAndCopy,
Default,
}
impl Default for PhiStrategy {
fn default() -> Self {
PhiStrategy::Default
}
}
#[derive(Debug, Clone, Default)]
pub struct CallLowering {
pub supports_tail_calls: bool,
pub supports_sibling_calls: bool,
pub clobbers_caller_saved: bool,
pub return_addr_regs: u32,
pub indirect_calls_via_reg: bool,
pub max_call_displacement: i32,
}
impl CrossTargetMIPS {
pub fn new(bridge: MIPSX86Bridge) -> Self {
let mut ctx = CrossTargetMIPS {
bridge: bridge.clone(),
patterns: HashMap::new(),
const_materializer: ConstMaterializer::default(),
addr_modes: AddressingModes::default(),
branch_lowering: BranchLowering::default(),
phi_elimination: PhiElimination::default(),
call_lowering: CallLowering::default(),
};
ctx.initialize_patterns();
ctx.initialize_const_materializer();
ctx.initialize_addressing_modes();
ctx.initialize_branch_lowering();
ctx.initialize_call_lowering();
ctx
}
fn initialize_patterns(&mut self) {
self.patterns.insert(1, CrossPattern {
id: 1,
description: "Zero register initialization".into(),
mips_expansion: vec!["addu $rd, $zero, $zero".into()],
x86_expansion: vec!["xor %rd, %rd".into()],
is_semantically_identical: true,
mips_cycles: 1,
x86_cycles: 1,
});
self.patterns.insert(2, CrossPattern {
id: 2,
description: "Bitwise NOT operation".into(),
mips_expansion: vec!["nor $rd, $rs, $zero".into()],
x86_expansion: vec!["not %rd".into()],
is_semantically_identical: true,
mips_cycles: 1,
x86_cycles: 1,
});
self.patterns.insert(3, CrossPattern {
id: 3,
description: "Sign-extend i32 to i64".into(),
mips_expansion: vec!["sll $rd, $rs, 0".into()], x86_expansion: vec!["movsxd %rd, %rs".into()],
is_semantically_identical: true,
mips_cycles: 1,
x86_cycles: 1,
});
self.patterns.insert(4, CrossPattern {
id: 4,
description: "Booleanize comparison result".into(),
mips_expansion: vec![
"slt $rd, $rs, $rt".into(),
],
x86_expansion: vec![
"xor %rd, %rd".into(),
"cmp %rs, %rt".into(),
"setl %rd".into(),
],
is_semantically_identical: true,
mips_cycles: 1,
x86_cycles: 2, });
self.patterns.insert(5, CrossPattern {
id: 5,
description: "Multiply by constant (shift form)".into(),
mips_expansion: vec!["sll $rd, $rs, N".into()],
x86_expansion: vec!["shl $N, %rd".into()],
is_semantically_identical: true,
mips_cycles: 1,
x86_cycles: 1,
});
self.patterns.insert(6, CrossPattern {
id: 6,
description: "Conditional select (cmov)".into(),
mips_expansion: vec![
"movn $rd, $rs, $rt".into(), ],
x86_expansion: vec![
"test %rt, %rt".into(),
"cmovnz %rs, %rd".into(),
],
is_semantically_identical: true,
mips_cycles: 1,
x86_cycles: 2, });
self.patterns.insert(7, CrossPattern {
id: 7,
description: "Trap if register is zero".into(),
mips_expansion: vec!["teq $rs, $zero".into()],
x86_expansion: vec![
"test %rs, %rs".into(),
"jz .Ltrap".into(),
],
is_semantically_identical: true,
mips_cycles: 1,
x86_cycles: 2,
});
self.patterns.insert(8, CrossPattern {
id: 8,
description: "32-bit byte swap (bswap)".into(),
mips_expansion: vec![
"wsbh $rd, $rs".into(),
"rotr $rd, $rd, 16".into(),
],
x86_expansion: vec!["bswap %rd".into()],
is_semantically_identical: true,
mips_cycles: 2, x86_cycles: 1, });
self.patterns.insert(9, CrossPattern {
id: 9,
description: "Count leading zeros (clz)".into(),
mips_expansion: vec!["clz $rd, $rs".into()],
x86_expansion: vec!["lzcnt %rs, %rd".into()],
is_semantically_identical: true,
mips_cycles: 1,
x86_cycles: 3, });
self.patterns.insert(10, CrossPattern {
id: 10,
description: "Count trailing zeros (ctz)".into(),
mips_expansion: vec![
"addiu $rd, $rs, -1".into(),
"not $rt, $rs".into(),
"and $rd, $rd, $rt".into(),
"clz $rd, $rd".into(),
"addiu $rd, $zero, 32".into(),
"subu $rd, $rd, $rd".into(), ],
x86_expansion: vec!["tzcnt %rs, %rd".into()],
is_semantically_identical: true,
mips_cycles: 5,
x86_cycles: 3,
});
}
fn initialize_const_materializer(&mut self) {
self.const_materializer.materialize_i32 = vec![
ConstMaterializationPattern {
mips: vec!["addiu $rd, $zero, #imm".into()],
x86: vec!["mov $imm, %rd".into()],
mips_cost: 1,
x86_cost: 1,
value_range: (-32768, 32767), },
ConstMaterializationPattern {
mips: vec![
"lui $rd, #upper16".into(),
"ori $rd, $rd, #lower16".into(),
],
x86: vec!["mov $imm, %rd".into()],
mips_cost: 2,
x86_cost: 1,
value_range: (-2147483648, 2147483647),
},
];
self.const_materializer.materialize_i64 = vec![
ConstMaterializationPattern {
mips: vec!["daddiu $rd, $zero, #imm".into()],
x86: vec!["mov $imm, %rd".into()],
mips_cost: 1,
x86_cost: 1,
value_range: (-32768, 32767),
},
ConstMaterializationPattern {
mips: vec![
"lui $rd, #upper16".into(),
"ori $rd, $rd, #lower16".into(),
"dsll $rd, $rd, 16".into(),
"ori $rd, $rd, #lower16_2".into(),
"dsll $rd, $rd, 16".into(),
"ori $rd, $rd, #lower16_3".into(),
],
x86: vec!["movabs $imm, %rd".into()],
mips_cost: 6,
x86_cost: 2, value_range: (i64::MIN, i64::MAX),
},
];
self.const_materializer.materialize_f32 = vec![
ConstMaterializationPattern {
mips: vec![
"lui $at, #upper16".into(),
"mtc1 $at, $frd".into(),
],
x86: vec!["movss .LC0(%rip), %xmmd".into()],
mips_cost: 1,
x86_cost: 3, value_range: (0, i64::MAX),
},
];
self.const_materializer.materialize_f64 = vec![
ConstMaterializationPattern {
mips: vec![
"lui $at, #upper16".into(),
"dmtc1 $at, $frd".into(),
],
x86: vec!["movsd .LC0(%rip), %xmmd".into()],
mips_cost: 1,
x86_cost: 3,
value_range: (0, i64::MAX),
},
];
}
fn initialize_addressing_modes(&mut self) {
self.addr_modes = AddressingModes {
base_plus_offset: true, base_plus_index: false, base_plus_index_scale: false, pc_relative: true, gp_relative: true, max_offset: 32767, unaligned_access: false, };
}
fn initialize_branch_lowering(&mut self) {
self.branch_lowering = BranchLowering {
direct_condition: true,
needs_trampolines: false,
max_branch_displacement: 131072, has_delay_slot: false, predict_forward_not_taken: true,
};
}
fn initialize_call_lowering(&mut self) {
self.call_lowering = CallLowering {
supports_tail_calls: true,
supports_sibling_calls: true,
clobbers_caller_saved: true,
return_addr_regs: 1, indirect_calls_via_reg: true, max_call_displacement: 268435456, };
}
pub fn get_pattern(&self, id: u32) -> Option<&CrossPattern> {
self.patterns.get(&id)
}
pub fn best_const_pattern(&self, value: i64, bit_width: u32) -> Option<&ConstMaterializationPattern> {
let patterns = match bit_width {
32 => &self.const_materializer.materialize_i32,
64 => &self.const_materializer.materialize_i64,
_ => return None,
};
patterns.iter().find(|p| value >= p.value_range.0 && value <= p.value_range.1)
}
pub fn supports_addressing(&self, mode: &str) -> bool {
match mode {
"base+offset" => self.addr_modes.base_plus_offset,
"base+index" => self.addr_modes.base_plus_index,
"base+index*scale" => self.addr_modes.base_plus_index_scale,
"pc-relative" => self.addr_modes.pc_relative,
"gp-relative" => self.addr_modes.gp_relative,
_ => false,
}
}
pub fn lower_branch(&self, condition: &str, target: &str) -> (Vec<String>, Vec<String>) {
let mips = match condition {
"eq" => vec![format!("beq $rs, $rt, {}", target)],
"ne" => vec![format!("bne $rs, $rt, {}", target)],
"lt" => vec![format!("bltz $rs, {}", target)],
"ge" => vec![format!("bgez $rs, {}", target)],
"le" => vec![format!("blez $rs, {}", target)],
"gt" => vec![format!("bgtz $rs, {}", target)],
_ => vec![format!("b {}", target)],
};
let x86 = match condition {
"eq" => vec![format!("je {}", target)],
"ne" => vec![format!("jne {}", target)],
"lt" => vec![format!("jl {}", target)],
"ge" => vec![format!("jge {}", target)],
"le" => vec![format!("jle {}", target)],
"gt" => vec![format!("jg {}", target)],
_ => vec![format!("jmp {}", target)],
};
(mips, x86)
}
pub fn can_tail_call(&self, is_recursive: bool) -> bool {
if is_recursive {
self.call_lowering.supports_tail_calls
} else {
self.call_lowering.supports_sibling_calls
}
}
}
impl Default for ConstMaterializer {
fn default() -> Self {
ConstMaterializer {
materialize_i32: Vec::new(),
materialize_i64: Vec::new(),
materialize_f32: Vec::new(),
materialize_f64: Vec::new(),
}
}
}
pub struct X86MIPSComparisons {
pub bridge: MIPSX86Bridge,
pub extended_instr_info: MIPSExtendedInstrInfo,
pub comparisons: Vec<ComparisonResult>,
pub aggregate: ComparisonAggregate,
pub contextual_costs: ContextualCosts,
pub microarch_costs: MicroarchitectureCosts,
}
#[derive(Debug, Clone)]
pub struct ComparisonResult {
pub name: String,
pub mips_seq: Vec<String>,
pub x86_seq: Vec<String>,
pub mips_cycles: u32,
pub x86_cycles: u32,
pub mips_size: u32,
pub x86_size: u32,
pub mips_gpr_pressure: u32,
pub x86_gpr_pressure: u32,
pub better: CrossTargetChoice,
pub notes: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrossTargetChoice {
Mips,
X86,
Tie,
NotComparable,
}
impl std::fmt::Display for CrossTargetChoice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CrossTargetChoice::Mips => write!(f, "MIPS"),
CrossTargetChoice::X86 => write!(f, "X86"),
CrossTargetChoice::Tie => write!(f, "Tie"),
CrossTargetChoice::NotComparable => write!(f, "N/A"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ComparisonAggregate {
pub total: u32,
pub mips_wins: u32,
pub x86_wins: u32,
pub ties: u32,
pub avg_mips_cycles: f64,
pub avg_x86_cycles: f64,
pub avg_mips_size: f64,
pub avg_x86_size: f64,
pub avg_mips_gpr: f64,
pub avg_x86_gpr: f64,
}
#[derive(Debug, Clone)]
pub struct ContextualCosts {
pub branch_mispredict_penalty: u32,
pub l1_miss_penalty: u32,
pub l2_miss_penalty: u32,
pub l3_miss_penalty: u32,
pub tlb_miss_penalty: u32,
pub call_overhead: u32,
pub return_overhead: u32,
pub indirect_branch_overhead: u32,
}
impl Default for ContextualCosts {
fn default() -> Self {
ContextualCosts {
branch_mispredict_penalty: 14, l1_miss_penalty: 8, l2_miss_penalty: 20, l3_miss_penalty: 50, tlb_miss_penalty: 30,
call_overhead: 2,
return_overhead: 3, indirect_branch_overhead: 5,
}
}
}
#[derive(Debug, Clone)]
pub struct MicroarchitectureCosts {
pub mips_microarch: String,
pub x86_microarch: String,
pub mips_cycles: HashMap<String, (u32, f64)>,
pub x86_cycles: HashMap<String, (u32, f64)>,
}
impl Default for MicroarchitectureCosts {
fn default() -> Self {
let mut mc = MicroarchitectureCosts {
mips_microarch: "mips32r2".into(),
x86_microarch: "skylake".into(),
mips_cycles: HashMap::new(),
x86_cycles: HashMap::new(),
};
let mips_costs: Vec<(&str, u32, f64)> = vec![
("add", 1, 0.5), ("addu", 1, 0.5), ("sub", 1, 0.5), ("subu", 1, 0.5),
("and", 1, 0.5), ("or", 1, 0.5), ("xor", 1, 0.5), ("nor", 1, 0.5),
("sll", 1, 0.5), ("srl", 1, 0.5), ("sra", 1, 0.5),
("sllv", 1, 0.5), ("srlv", 1, 0.5), ("srav", 1, 0.5),
("mult", 5, 1.0), ("multu", 5, 1.0), ("div", 12, 4.0), ("divu", 12, 4.0),
("mflo", 1, 0.5), ("mfhi", 1, 0.5), ("mtlo", 1, 0.5), ("mthi", 1, 0.5),
("addi", 1, 0.5), ("addiu", 1, 0.5), ("andi", 1, 0.5),
("ori", 1, 0.5), ("xori", 1, 0.5), ("lui", 1, 0.5),
("lw", 3, 0.5), ("sw", 1, 0.5), ("lh", 3, 0.5), ("sh", 1, 0.5),
("lb", 3, 0.5), ("sb", 1, 0.5), ("lbu", 3, 0.5),
("beq", 1, 0.5), ("bne", 1, 0.5), ("j", 1, 0.5), ("jal", 1, 0.5),
("jr", 1, 0.5), ("jalr", 1, 0.5),
("add.s", 4, 1.0), ("sub.s", 4, 1.0), ("mul.s", 4, 1.0),
("div.s", 12, 4.0), ("sqrt.s", 12, 4.0),
("add.d", 4, 1.0), ("sub.d", 4, 1.0), ("mul.d", 5, 1.0),
("div.d", 14, 6.0), ("sqrt.d", 14, 6.0),
("mov.s", 1, 0.5), ("mov.d", 1, 0.5),
("lwc1", 3, 0.5), ("swc1", 1, 0.5), ("ldc1", 3, 0.5), ("sdc1", 1, 0.5),
("cvt.s.w", 3, 1.0), ("cvt.w.s", 3, 1.0),
("cvt.d.w", 3, 1.0), ("cvt.w.d", 3, 1.0),
];
for (op, lat, rtp) in mips_costs {
mc.mips_cycles.insert(op.to_string(), (lat, rtp));
}
let x86_costs: Vec<(&str, u32, f64)> = vec![
("add", 1, 0.25), ("sub", 1, 0.25), ("and", 1, 0.25),
("or", 1, 0.25), ("xor", 1, 0.25),
("shl", 1, 0.5), ("shr", 1, 0.5), ("sar", 1, 0.5),
("imul", 3, 1.0), ("mul", 3, 1.0), ("idiv", 20, 6.0), ("div", 20, 6.0),
("mov", 1, 0.25), ("movabs", 1, 0.25),
("lea", 1, 0.25), ("not", 1, 0.25), ("neg", 1, 0.25),
("bswap", 1, 0.5), ("lzcnt", 3, 1.0), ("tzcnt", 3, 1.0),
("addss", 3, 0.5), ("subss", 3, 0.5), ("mulss", 4, 0.5),
("divss", 11, 3.0), ("sqrtss", 12, 3.0),
("addsd", 3, 0.5), ("subsd", 3, 0.5), ("mulsd", 4, 0.5),
("divsd", 14, 4.0), ("sqrtsd", 14, 4.0),
("movss", 1, 0.5), ("movsd", 1, 0.5),
("paddb", 1, 0.5), ("paddw", 1, 0.5), ("paddd", 1, 0.5), ("paddq", 1, 0.5),
("psubb", 1, 0.5), ("psubw", 1, 0.5), ("psubd", 1, 0.5), ("psubq", 1, 0.5),
("pmullw", 5, 0.5), ("pmulld", 5, 0.5),
("pand", 1, 0.33), ("por", 1, 0.33), ("pxor", 1, 0.33), ("pandn", 1, 0.33),
("pcmpeqb", 1, 0.5), ("pcmpeqw", 1, 0.5), ("pcmpeqd", 1, 0.5), ("pcmpeqq", 1, 0.5),
("pcmpgtb", 1, 0.5), ("pcmpgtw", 1, 0.5), ("pcmpgtd", 1, 0.5), ("pcmpgtq", 1, 0.5),
("addps", 3, 0.5), ("mulps", 4, 0.5), ("addpd", 3, 0.5), ("mulpd", 4, 0.5),
("divps", 11, 3.0), ("divpd", 14, 4.0),
("maxps", 3, 0.5), ("minps", 3, 0.5),
("jmp", 1, 1.0), ("call", 1, 1.0), ("ret", 1, 1.0),
("je", 1, 0.5), ("jne", 1, 0.5), ("jl", 1, 0.5), ("jle", 1, 0.5),
("jg", 1, 0.5), ("jge", 1, 0.5),
];
for (op, lat, rtp) in x86_costs {
mc.x86_cycles.insert(op.to_string(), (lat, rtp));
}
mc
}
}
impl X86MIPSComparisons {
pub fn new(bridge: MIPSX86Bridge) -> Self {
let extended_info = MIPSExtendedInstrInfo::new();
let mut comp = X86MIPSComparisons {
bridge,
extended_instr_info: extended_info,
comparisons: Vec::new(),
aggregate: ComparisonAggregate::default(),
contextual_costs: ContextualCosts::default(),
microarch_costs: MicroarchitectureCosts::default(),
};
comp.initialize_comparisons();
comp.compute_aggregate();
comp
}
fn initialize_comparisons(&mut self) {
self.comparisons.push(ComparisonResult {
name: "Integer Add".into(),
mips_seq: vec!["addu $rd, $rs, $rt".into()],
x86_seq: vec!["lea (%rs,%rt,1), %rd".into()],
mips_cycles: 1,
x86_cycles: 1,
mips_size: 4,
x86_size: 4,
mips_gpr_pressure: 3,
x86_gpr_pressure: 3,
better: CrossTargetChoice::Tie,
notes: vec!["Identical cost".into()],
});
self.comparisons.push(ComparisonResult {
name: "Integer Multiply (32-bit)".into(),
mips_seq: vec![
"mult $rs, $rt".into(),
"mflo $rd".into(),
],
x86_seq: vec!["imul %rs, %rt".into()],
mips_cycles: 6, x86_cycles: 3,
mips_size: 8,
x86_size: 3,
mips_gpr_pressure: 3,
x86_gpr_pressure: 2,
better: CrossTargetChoice::X86,
notes: vec!["X86 imul is single instruction with 3-cycle latency".into()],
});
self.comparisons.push(ComparisonResult {
name: "Integer Division (32-bit)".into(),
mips_seq: vec![
"div $rs, $rt".into(),
"mflo $rd".into(),
],
x86_seq: vec!["idiv %rt".into()],
mips_cycles: 13, x86_cycles: 20,
mips_size: 8,
x86_size: 2,
mips_gpr_pressure: 2,
x86_gpr_pressure: 3, better: CrossTargetChoice::Mips,
notes: vec!["MIPS division is faster due to simpler pipeline".into()],
});
self.comparisons.push(ComparisonResult {
name: "32-bit Load".into(),
mips_seq: vec!["lw $rd, offset($base)".into()],
x86_seq: vec!["movl offset(%base), %rd".into()],
mips_cycles: 3,
x86_cycles: 4,
mips_size: 4,
x86_size: 6,
mips_gpr_pressure: 2,
x86_gpr_pressure: 2,
better: CrossTargetChoice::Mips,
notes: vec![
"MIPS has simpler load pipeline".into(),
"X86 addressing mode may use extra bytes".into(),
],
});
self.comparisons.push(ComparisonResult {
name: "32-bit Store".into(),
mips_seq: vec!["sw $rs, offset($base)".into()],
x86_seq: vec!["movl %rs, offset(%base)".into()],
mips_cycles: 1,
x86_cycles: 1,
mips_size: 4,
x86_size: 6,
mips_gpr_pressure: 2,
x86_gpr_pressure: 2,
better: CrossTargetChoice::Mips,
notes: vec!["MIPS wins on code size for stores".into()],
});
self.comparisons.push(ComparisonResult {
name: "Float Add (single)".into(),
mips_seq: vec!["add.s $frd, $frs, $frt".into()],
x86_seq: vec!["addss %xmmrs, %xmmrt".into()],
mips_cycles: 4,
x86_cycles: 3,
mips_size: 4,
x86_size: 4,
mips_gpr_pressure: 0,
x86_gpr_pressure: 0,
better: CrossTargetChoice::X86,
notes: vec!["X86 has lower FP add latency".into()],
});
self.comparisons.push(ComparisonResult {
name: "Float Multiply (single)".into(),
mips_seq: vec!["mul.s $frd, $frs, $frt".into()],
x86_seq: vec!["mulss %xmmrs, %xmmrt".into()],
mips_cycles: 4,
x86_cycles: 4,
mips_size: 4,
x86_size: 4,
mips_gpr_pressure: 0,
x86_gpr_pressure: 0,
better: CrossTargetChoice::Tie,
notes: vec!["Identical FP multiply latency".into()],
});
self.comparisons.push(ComparisonResult {
name: "Float Divide (single)".into(),
mips_seq: vec!["div.s $frd, $frs, $frt".into()],
x86_seq: vec!["divss %xmmrs, %xmmrt".into()],
mips_cycles: 12,
x86_cycles: 11,
mips_size: 4,
x86_size: 4,
mips_gpr_pressure: 0,
x86_gpr_pressure: 0,
better: CrossTargetChoice::X86,
notes: vec!["X86 slightly faster FP divide (Skylake)".into()],
});
self.comparisons.push(ComparisonResult {
name: "Conditional Branch".into(),
mips_seq: vec![
"slt $at, $rs, $rt".into(),
"bne $at, $zero, target".into(),
],
x86_seq: vec![
"cmp %rs, %rt".into(),
"jl target".into(),
],
mips_cycles: 2, x86_cycles: 1, mips_size: 8,
x86_size: 5,
mips_gpr_pressure: 3,
x86_gpr_pressure: 2,
better: CrossTargetChoice::X86,
notes: vec![
"X86 macro-fusion allows cmp+branch in 1 uop".into(),
"MIPS needs two separate instructions".into(),
],
});
self.comparisons.push(ComparisonResult {
name: "Direct Function Call".into(),
mips_seq: vec![
"jal target".into(),
"nop".into(),
],
x86_seq: vec!["call target".into()],
mips_cycles: 2, x86_cycles: 1,
mips_size: 8,
x86_size: 5,
mips_gpr_pressure: 1, x86_gpr_pressure: 0, better: CrossTargetChoice::X86,
notes: vec!["X86 call is simpler: one instruction, no delay slot".into()],
});
self.comparisons.push(ComparisonResult {
name: "Function Return".into(),
mips_seq: vec![
"jr $ra".into(),
"nop".into(),
],
x86_seq: vec!["ret".into()],
mips_cycles: 2,
x86_cycles: 1,
mips_size: 8,
x86_size: 1,
mips_gpr_pressure: 0,
x86_gpr_pressure: 0,
better: CrossTargetChoice::X86,
notes: vec!["X86 ret is 1 byte; MIPS needs delay slot".into()],
});
self.comparisons.push(ComparisonResult {
name: "Zero register".into(),
mips_seq: vec!["addu $rd, $zero, $zero".into()],
x86_seq: vec!["xor %rd, %rd".into()],
mips_cycles: 1,
x86_cycles: 1,
mips_size: 4,
x86_size: 3,
mips_gpr_pressure: 2,
x86_gpr_pressure: 2,
better: CrossTargetChoice::Tie,
notes: vec!["MIPS has hardware $zero; X86 uses xor idiom".into()],
});
self.comparisons.push(ComparisonResult {
name: "Vector Add (4x i32)".into(),
mips_seq: vec!["addv.w $wd, $ws, $wt".into()],
x86_seq: vec!["paddd %xmmws, %xmmwt".into()],
mips_cycles: 1,
x86_cycles: 1,
mips_size: 4,
x86_size: 4,
mips_gpr_pressure: 0,
x86_gpr_pressure: 0,
better: CrossTargetChoice::Tie,
notes: vec!["Identical SIMD add cost".into()],
});
self.comparisons.push(ComparisonResult {
name: "Memory copy (64 bytes)".into(),
mips_seq: vec![
"lw $t0, 0($a0)".into(), "sw $t0, 0($a1)".into(),
"lw $t0, 4($a0)".into(), "sw $t0, 4($a1)".into(),
"lw $t0, 8($a0)".into(), "sw $t0, 8($a1)".into(),
"lw $t0, 12($a0)".into(), "sw $t0, 12($a1)".into(),
"lw $t0, 16($a0)".into(), "sw $t0, 16($a1)".into(),
"lw $t0, 20($a0)".into(), "sw $t0, 20($a1)".into(),
"lw $t0, 24($a0)".into(), "sw $t0, 24($a1)".into(),
"lw $t0, 28($a0)".into(), "sw $t0, 28($a1)".into(),
"lw $t0, 32($a0)".into(), "sw $t0, 32($a1)".into(),
"lw $t0, 36($a0)".into(), "sw $t0, 36($a1)".into(),
"lw $t0, 40($a0)".into(), "sw $t0, 40($a1)".into(),
"lw $t0, 44($a0)".into(), "sw $t0, 44($a1)".into(),
"lw $t0, 48($a0)".into(), "sw $t0, 48($a1)".into(),
"lw $t0, 52($a0)".into(), "sw $t0, 52($a1)".into(),
"lw $t0, 56($a0)".into(), "sw $t0, 56($a1)".into(),
"lw $t0, 60($a0)".into(), "sw $t0, 60($a1)".into(),
],
x86_seq: vec![
"rep movsq".into(),
],
mips_cycles: 64, x86_cycles: 12, mips_size: 128, x86_size: 3,
mips_gpr_pressure: 3,
x86_gpr_pressure: 3,
better: CrossTargetChoice::X86,
notes: vec![
"X86 rep movs is heavily optimized".into(),
"MIPS needs manual loop unrolling".into(),
"MSA ld.b/st.b would reduce MIPS gap significantly".into(),
],
});
}
fn compute_aggregate(&mut self) {
let total = self.comparisons.len() as u32;
let mut mips_wins = 0u32;
let mut x86_wins = 0u32;
let mut ties = 0u32;
let mut sum_mips_c = 0u64;
let mut sum_x86_c = 0u64;
let mut sum_mips_s = 0u64;
let mut sum_x86_s = 0u64;
let mut sum_mips_gpr = 0u64;
let mut sum_x86_gpr = 0u64;
for c in &self.comparisons {
match c.better {
CrossTargetChoice::Mips => mips_wins += 1,
CrossTargetChoice::X86 => x86_wins += 1,
CrossTargetChoice::Tie => ties += 1,
CrossTargetChoice::NotComparable => {}
}
sum_mips_c += c.mips_cycles as u64;
sum_x86_c += c.x86_cycles as u64;
sum_mips_s += c.mips_size as u64;
sum_x86_s += c.x86_size as u64;
sum_mips_gpr += c.mips_gpr_pressure as u64;
sum_x86_gpr += c.x86_gpr_pressure as u64;
}
let n = total as f64;
self.aggregate = ComparisonAggregate {
total,
mips_wins,
x86_wins,
ties,
avg_mips_cycles: if n > 0.0 { sum_mips_c as f64 / n } else { 0.0 },
avg_x86_cycles: if n > 0.0 { sum_x86_c as f64 / n } else { 0.0 },
avg_mips_size: if n > 0.0 { sum_mips_s as f64 / n } else { 0.0 },
avg_x86_size: if n > 0.0 { sum_x86_s as f64 / n } else { 0.0 },
avg_mips_gpr: if n > 0.0 { sum_mips_gpr as f64 / n } else { 0.0 },
avg_x86_gpr: if n > 0.0 { sum_x86_gpr as f64 / n } else { 0.0 },
};
}
pub fn get_instr_cost_mips(&self, mnemonic: &str) -> Option<(u32, f64)> {
self.microarch_costs.mips_cycles.get(mnemonic).copied()
}
pub fn get_instr_cost_x86(&self, mnemonic: &str) -> Option<(u32, f64)> {
self.microarch_costs.x86_cycles.get(mnemonic).copied()
}
pub fn compare_sequences(
&self,
mips_seq: &[String],
x86_seq: &[String],
) -> CrossTargetChoice {
let mips_cost: u32 = mips_seq.iter()
.map(|s| {
let mnem = s.split_whitespace().next().unwrap_or("?");
self.get_instr_cost_mips(mnem).map(|(l, _)| l).unwrap_or(1)
})
.sum();
let x86_cost: u32 = x86_seq.iter()
.map(|s| {
let mnem = s.split_whitespace().next().unwrap_or("?");
self.get_instr_cost_x86(mnem).map(|(l, _)| l).unwrap_or(1)
})
.sum();
if mips_cost < x86_cost {
CrossTargetChoice::Mips
} else if x86_cost < mips_cost {
CrossTargetChoice::X86
} else {
CrossTargetChoice::Tie
}
}
pub fn report(&self) -> String {
let mut out = String::new();
out.push_str("=== X86-MIPS Cross-Target Cost Comparison ===\n\n");
for c in &self.comparisons {
out.push_str(&format!("{}:\n", c.name));
out.push_str(&format!(" MIPS: {} cycles, {} bytes\n", c.mips_cycles, c.mips_size));
out.push_str(&format!(" X86: {} cycles, {} bytes\n", c.x86_cycles, c.x86_size));
out.push_str(&format!(" Better: {}\n", c.better));
if !c.notes.is_empty() {
out.push_str(&format!(" Notes: {}\n", c.notes.join("; ")));
}
out.push('\n');
}
let agg = &self.aggregate;
out.push_str("--- Aggregate ---\n");
out.push_str(&format!(" Total: {} MIPS wins: {} X86 wins: {} Ties: {}\n",
agg.total, agg.mips_wins, agg.x86_wins, agg.ties));
out.push_str(&format!(" Avg cycles: MIPS {:.1} X86 {:.1}\n",
agg.avg_mips_cycles, agg.avg_x86_cycles));
out.push_str(&format!(" Avg size: MIPS {:.1} X86 {:.1}\n",
agg.avg_mips_size, agg.avg_x86_size));
out.push_str(&format!(" Avg GPR use: MIPS {:.1} X86 {:.1}\n",
agg.avg_mips_gpr, agg.avg_x86_gpr));
out
}
pub fn set_microarch(&mut self, mips: &str, x86: &str) {
self.microarch_costs.mips_microarch = mips.to_string();
self.microarch_costs.x86_microarch = x86.to_string();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bridge_creation_default() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert!(bridge.is_mips_primary);
assert!(!bridge.is_64bit);
assert_eq!(bridge.mips_abi, MipsAbi::O32);
}
#[test]
fn test_bridge_creation_mips64() {
let bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
assert!(bridge.is_mips_primary);
assert!(bridge.is_64bit);
assert_eq!(bridge.mips_abi, MipsAbi::N64);
}
#[test]
fn test_bridge_creation_x86() {
let bridge = MIPSX86Bridge::new("x86_64-unknown-linux-gnu");
assert!(!bridge.is_mips_primary);
assert!(bridge.is_64bit);
}
#[test]
fn test_bridge_with_features() {
let bridge = MIPSX86Bridge::with_features("mips-unknown-linux-gnu", &["msa", "dsp"]);
assert!(bridge.has_msa);
assert!(bridge.has_dsp);
assert!(!bridge.has_avx2);
}
#[test]
fn test_reg_map_mips_to_x86() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert_eq!(bridge.reg_map_mips_to_x86.get(&4004).map(|s| s.as_str()), Some("rdi"));
assert_eq!(bridge.reg_map_mips_to_x86.get(&4000), None);
}
#[test]
fn test_reg_map_x86_to_mips() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert_eq!(bridge.reg_map_x86_to_mips.get("rax"), Some(&4002));
assert_eq!(bridge.reg_map_x86_to_mips.get("rdi"), Some(&4004));
}
#[test]
fn test_mips_gpr_to_x86_table() {
assert_eq!(mips_gpr_to_x86(4000), None); assert_eq!(mips_gpr_to_x86(4004), Some("rdi")); assert_eq!(mips_gpr_to_x86(4008), Some("r8")); assert_eq!(mips_gpr_to_x86(4029), Some("rsp")); assert_eq!(mips_gpr_to_x86(4030), Some("rbp")); assert_eq!(mips_gpr_to_x86(4031), None); }
#[test]
fn test_x86_gpr_to_mips_table() {
assert_eq!(x86_gpr_to_mips("rax"), Some(4002));
assert_eq!(x86_gpr_to_mips("rbx"), Some(4016));
assert_eq!(x86_gpr_to_mips("rsp"), Some(4029));
assert_eq!(x86_gpr_to_mips("r8"), Some(4008));
assert_eq!(x86_gpr_to_mips("r15"), Some(4020));
assert_eq!(x86_gpr_to_mips("unknown"), None);
}
#[test]
fn test_mips_fpr_to_x86_table() {
assert_eq!(mips_fpr_to_x86(4050), Some("xmm0"));
assert_eq!(mips_fpr_to_x86(4057), Some("xmm7"));
assert_eq!(mips_fpr_to_x86(4082), None); assert_eq!(mips_fpr_to_x86(4000), None); }
#[test]
fn test_bridge_vreg_allocation() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let alloc = bridge.allocate_vreg(1, RegClassKind::GPR32);
assert_eq!(alloc.vreg, 1);
assert!(!alloc.is_spill);
assert_eq!(alloc.reg_class, RegClassKind::GPR32);
assert!(alloc.mips_reg >= MIPS_GPR_BASE);
assert!(!alloc.x86_reg.is_empty());
}
#[test]
fn test_bridge_vreg_reuse() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let alloc1 = bridge.allocate_vreg(5, RegClassKind::GPR64);
let alloc2 = bridge.allocate_vreg(5, RegClassKind::GPR64);
assert_eq!(alloc1.vreg, alloc2.vreg);
assert_eq!(alloc1.mips_reg, alloc2.mips_reg);
}
#[test]
fn test_bridge_spill_vreg() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.allocate_vreg(10, RegClassKind::GPR32);
let slot = bridge.spill_vreg(10);
assert!(slot.size >= 4);
assert_eq!(bridge.spill_slots.len(), 1);
}
#[test]
fn test_bridge_clear_allocations() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.allocate_vreg(1, RegClassKind::GPR32);
bridge.allocate_vreg(2, RegClassKind::FPR32);
assert!(!bridge.vreg_allocations.is_empty());
bridge.clear_allocations();
assert!(bridge.vreg_allocations.is_empty());
assert!(bridge.spill_slots.is_empty());
}
#[test]
fn test_shared_pattern_cache() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let pattern = bridge.get_or_build_pattern(8); assert_eq!(pattern.opcode, 8);
assert!(!pattern.mips_sequence.is_empty());
assert!(!pattern.x86_sequence.is_empty());
let pattern2 = bridge.get_or_build_pattern(8);
assert_eq!(pattern.mips_cost, pattern2.mips_cost);
}
#[test]
fn test_select_target_for_opcode() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert!(bridge.select_target_for_opcode(8)); }
#[test]
fn test_select_target_with_bias() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.cost_weights.target_bias = 10.0; assert!(!bridge.select_target_for_opcode(8));
}
#[test]
fn test_compare_opcode_cost() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let winner = bridge.compare_opcode_cost(12);
assert_eq!(winner, CostWinner::Mips);
}
#[test]
fn test_compare_opcode_cost_tie() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let winner = bridge.compare_opcode_cost(8);
assert_eq!(winner, CostWinner::Tie);
}
#[test]
fn test_run_full_comparison() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let results = bridge.run_full_comparison();
assert!(!results.is_empty());
assert!(results.iter().any(|e| e.ir_opcode == 8)); assert!(results.iter().any(|e| e.ir_opcode == 12)); }
#[test]
fn test_cost_summary() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.run_full_comparison();
let summary = bridge.cost_summary();
assert!(summary.total_entries > 0);
assert!(summary.mips_wins + summary.x86_wins + summary.ties > 0);
}
#[test]
fn test_emit_mips_prologue_leaf() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let prologue = bridge.emit_mips_prologue();
assert!(!prologue.contains("sw\t$ra"));
}
#[test]
fn test_emit_mips_prologue_with_calls() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(16, 2, true, false);
let prologue = bridge.emit_mips_prologue();
assert!(prologue.contains("addiu\t$sp"));
}
#[test]
fn test_emit_mips_epilogue_basic() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(16, 0, false, false);
let epilogue = bridge.emit_mips_epilogue();
assert!(epilogue.contains("jr\t$ra"));
}
#[test]
fn test_emit_x86_prologue() {
let mut bridge = MIPSX86Bridge::new("x86_64-unknown-linux-gnu");
bridge.calculate_frame(32, 0, false, false);
let prologue = bridge.emit_x86_prologue();
assert!(prologue.contains("pushq\t%rbp"));
assert!(prologue.contains("movq\t%rsp, %rbp"));
}
#[test]
fn test_emit_x86_epilogue() {
let mut bridge = MIPSX86Bridge::new("x86_64-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let epilogue = bridge.emit_x86_epilogue();
assert!(epilogue.contains("leave"));
assert!(epilogue.contains("ret"));
}
#[test]
fn test_generate_cross_target() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let ir = vec![
CrossTargetIRInst { opcode: 8, dest: Some(1), srcs: vec![2, 3], immediate: None, type_code: 0 },
CrossTargetIRInst { opcode: 12, dest: Some(4), srcs: vec![1, 5], immediate: None, type_code: 0 },
];
let (mips_asm, x86_asm) = bridge.generate_cross_target(&ir);
assert!(!mips_asm.is_empty());
assert!(!x86_asm.is_empty());
assert!(mips_asm.contains("addu"));
assert!(x86_asm.contains("add"));
}
#[test]
fn test_mips_abi_default() {
assert_eq!(MipsAbi::default(), MipsAbi::O32);
}
#[test]
fn test_mips_abi_is_64bit() {
assert!(!MipsAbi::O32.is_64bit());
assert!(!MipsAbi::N32.is_64bit());
assert!(MipsAbi::N64.is_64bit());
}
#[test]
fn test_mips_abi_num_arg_regs() {
assert_eq!(MipsAbi::O32.num_arg_regs(), 4);
assert_eq!(MipsAbi::N32.num_arg_regs(), 8);
assert_eq!(MipsAbi::N64.num_arg_regs(), 8);
}
#[test]
fn test_mips_abi_stack_alignment() {
assert_eq!(MipsAbi::O32.stack_alignment(), 8);
assert_eq!(MipsAbi::N32.stack_alignment(), 16);
assert_eq!(MipsAbi::N64.stack_alignment(), 16);
}
#[test]
fn test_mips_abi_display() {
assert_eq!(MipsAbi::O32.to_string(), "o32");
assert_eq!(MipsAbi::N32.to_string(), "n32");
assert_eq!(MipsAbi::N64.to_string(), "n64");
}
#[test]
fn test_reg_class_kind_size() {
assert_eq!(RegClassKind::GPR32.size_bytes(), 4);
assert_eq!(RegClassKind::GPR64.size_bytes(), 8);
assert_eq!(RegClassKind::FPR32.size_bytes(), 4);
assert_eq!(RegClassKind::FPR64.size_bytes(), 8);
assert_eq!(RegClassKind::Vec128.size_bytes(), 16);
assert_eq!(RegClassKind::Vec256.size_bytes(), 32);
assert_eq!(RegClassKind::Flag.size_bytes(), 0);
}
#[test]
fn test_reg_class_kind_is_integer() {
assert!(RegClassKind::GPR32.is_integer());
assert!(RegClassKind::GPR64.is_integer());
assert!(!RegClassKind::FPR32.is_integer());
assert!(!RegClassKind::Vec128.is_integer());
}
#[test]
fn test_reg_class_kind_is_fp() {
assert!(RegClassKind::FPR32.is_fp());
assert!(RegClassKind::FPR64.is_fp());
assert!(RegClassKind::Vec128.is_fp());
assert!(!RegClassKind::GPR32.is_fp());
}
#[test]
fn test_mips_target_machine_mips32() {
let tm = MIPSTargetMachine::new("mips-unknown-linux-gnu");
assert!(!tm.is_64bit);
assert!(!tm.is_little_endian);
assert_eq!(tm.abi, MipsAbi::O32);
assert_eq!(tm.pointer_size(), 32);
}
#[test]
fn test_mips_target_machine_mips64() {
let tm = MIPSTargetMachine::new("mips64-unknown-linux-gnu");
assert!(tm.is_64bit);
assert!(!tm.is_little_endian);
assert_eq!(tm.abi, MipsAbi::N64);
assert_eq!(tm.pointer_size(), 64);
}
#[test]
fn test_mips_target_machine_mipsel() {
let tm = MIPSTargetMachine::new("mipsel-unknown-linux-gnu");
assert!(!tm.is_64bit);
assert!(tm.is_little_endian);
}
#[test]
fn test_mips_target_machine_mips64el() {
let tm = MIPSTargetMachine::new("mips64el-unknown-linux-gnu");
assert!(tm.is_64bit);
assert!(tm.is_little_endian);
}
#[test]
fn test_mips_target_machine_with_features() {
let tm = MIPSTargetMachine::with_features("mips-unknown-linux-gnu", &["msa", "dsp"]);
assert!(tm.has_feature("msa"));
assert!(tm.has_feature("dsp"));
assert!(!tm.has_feature("nonexistent"));
}
#[test]
fn test_mips_target_machine_triple() {
let tm = MIPSTargetMachine::new("mips64el-unknown-linux-gnu");
assert_eq!(tm.triple(), "mips64el-unknown-linux-gnu");
}
#[test]
fn test_mips_target_machine_x86_to_mips_reg() {
let tm = MIPSTargetMachine::new("mips-unknown-linux-gnu");
assert_eq!(tm.x86_to_mips_reg("rax"), Some(4002));
assert_eq!(tm.x86_to_mips_reg("xmm5"), Some(MIPS_FPR_BASE + 5));
assert_eq!(tm.x86_to_mips_reg("nonexistent"), None);
}
#[test]
fn test_mips_target_machine_prefer_mips() {
let mut tm = MIPSTargetMachine::new("mips-unknown-linux-gnu");
assert!(tm.prefer_mips_for(8));
}
#[test]
fn test_extended_instr_info_creation() {
let info = MIPSExtendedInstrInfo::new();
assert!(info.cost_count() > 0);
assert!(info.x86_equiv_count() > 0);
}
#[test]
fn test_get_cost() {
let info = MIPSExtendedInstrInfo::new();
let cost = info.get_cost(&MipsOpcode::ADD);
assert!(cost.is_some());
assert_eq!(cost.unwrap().latency, 1);
}
#[test]
fn test_get_cost_mul() {
let info = MIPSExtendedInstrInfo::new();
let cost = info.get_cost(&MipsOpcode::MULT);
assert!(cost.is_some());
assert_eq!(cost.unwrap().latency, 5);
}
#[test]
fn test_get_cost_div() {
let info = MIPSExtendedInstrInfo::new();
let cost = info.get_cost(&MipsOpcode::DIV);
assert!(cost.is_some());
assert_eq!(cost.unwrap().latency, 12);
}
#[test]
fn test_get_x86_equivalent() {
let info = MIPSExtendedInstrInfo::new();
assert_eq!(info.get_x86_equivalent(&MipsOpcode::ADD).map(|s| s.as_str()), Some("add"));
assert_eq!(info.get_x86_equivalent(&MipsOpcode::XOR).map(|s| s.as_str()), Some("xor"));
assert_eq!(info.get_x86_equivalent(&MipsOpcode::LW).map(|s| s.as_str()), Some("mov (load)"));
}
#[test]
fn test_get_encoding_type() {
let info = MIPSExtendedInstrInfo::new();
assert_eq!(info.get_encoding_type(&MipsOpcode::ADD), MipsEncodingType::RType);
assert_eq!(info.get_encoding_type(&MipsOpcode::ADDI), MipsEncodingType::IType);
assert_eq!(info.get_encoding_type(&MipsOpcode::J), MipsEncodingType::JType);
assert_eq!(info.get_encoding_type(&MipsOpcode::NOP), MipsEncodingType::Pseudo);
}
#[test]
fn test_get_func_unit() {
let info = MIPSExtendedInstrInfo::new();
assert_eq!(info.get_func_unit(&MipsOpcode::ADD), MipsFuncUnit::ALU);
assert_eq!(info.get_func_unit(&MipsOpcode::LW), MipsFuncUnit::LSU);
assert_eq!(info.get_func_unit(&MipsOpcode::MULT), MipsFuncUnit::MDU);
assert_eq!(info.get_func_unit(&MipsOpcode::ADD_S), MipsFuncUnit::FPAdd);
assert_eq!(info.get_func_unit(&MipsOpcode::BEQ), MipsFuncUnit::Branch);
}
#[test]
fn test_has_delay_slot() {
let info = MIPSExtendedInstrInfo::new();
assert!(info.has_delay_slot(&MipsOpcode::J));
assert!(info.has_delay_slot(&MipsOpcode::JAL));
assert!(!info.has_delay_slot(&MipsOpcode::ADD));
}
#[test]
fn test_sorted_by_latency() {
let info = MIPSExtendedInstrInfo::new();
let sorted = info.sorted_by_latency();
assert!(!sorted.is_empty());
assert!(sorted[0].1 >= sorted[sorted.len() - 1].1);
}
#[test]
fn test_by_func_unit() {
let info = MIPSExtendedInstrInfo::new();
let alu_ops = info.by_func_unit(MipsFuncUnit::ALU);
assert!(!alu_ops.is_empty());
assert!(alu_ops.contains(&MipsOpcode::ADD));
assert!(alu_ops.contains(&MipsOpcode::SUB));
}
#[test]
fn test_average_cost() {
let info = MIPSExtendedInstrInfo::new();
let avg = info.average_cost(&[MipsOpcode::ADD, MipsOpcode::SUB, MipsOpcode::AND, MipsOpcode::OR]);
assert!((avg - 1.0).abs() < 0.1); }
#[test]
fn test_msa_instructions_have_costs() {
let info = MIPSExtendedInstrInfo::new();
assert!(info.get_cost(&MipsOpcode::ADDV_W).is_some());
assert!(info.get_cost(&MipsOpcode::FADD_W).is_some());
assert!(info.get_cost(&MipsOpcode::CEQ_W).is_some());
}
#[test]
fn test_dsp_instructions_have_costs() {
let info = MIPSExtendedInstrInfo::new();
assert!(info.get_cost(&MipsOpcode::ADDQ_PH).is_some());
assert!(info.get_cost(&MipsOpcode::MUL_PH).is_some());
assert!(info.get_cost(&MipsOpcode::CMPGU_EQ_QB).is_some());
}
#[test]
fn test_encoding_type_size() {
assert_eq!(MipsEncodingType::RType.size(), 4);
assert_eq!(MipsEncodingType::IType.size(), 4);
assert_eq!(MipsEncodingType::JType.size(), 4);
assert_eq!(MipsEncodingType::Micro16.size(), 2);
}
#[test]
fn test_encoding_type_name() {
assert_eq!(MipsEncodingType::RType.name(), "R-type");
assert_eq!(MipsEncodingType::IType.name(), "I-type");
assert_eq!(MipsEncodingType::Micro16.name(), "microMIPS16");
}
#[test]
fn test_extended_reg_info_creation() {
let info = MIPSExtendedRegisterInfo::new();
assert_eq!(info.gpr_abi_name(0), "zero");
assert_eq!(info.gpr_abi_name(4), "a0");
assert_eq!(info.gpr_abi_name(29), "sp");
assert_eq!(info.gpr_abi_name(31), "ra");
}
#[test]
fn test_fpr_abi_names() {
let info = MIPSExtendedRegisterInfo::new();
assert_eq!(info.fpr_abi_name(0), "fv0");
assert_eq!(info.fpr_abi_name(12), "fa0");
assert_eq!(info.fpr_abi_name(20), "fs0");
}
#[test]
fn test_callee_saved_o32() {
let info = MIPSExtendedRegisterInfo::new();
let saved = info.get_callee_saved(MipsAbi::O32);
assert!(saved.contains(&4016)); assert!(saved.contains(&4023)); assert!(saved.contains(&4030)); assert!(saved.contains(&4031)); }
#[test]
fn test_callee_saved_n64() {
let info = MIPSExtendedRegisterInfo::new();
let saved = info.get_callee_saved(MipsAbi::N64);
assert!(saved.contains(&4016)); assert!(saved.contains(&4028)); }
#[test]
fn test_caller_saved_o32() {
let info = MIPSExtendedRegisterInfo::new();
let saved = info.get_caller_saved(MipsAbi::O32);
assert!(saved.contains(&4001)); assert!(saved.contains(&4002)); assert!(saved.contains(&4008)); }
#[test]
fn test_is_reserved() {
let info = MIPSExtendedRegisterInfo::new();
assert!(info.is_reserved(4000)); assert!(info.is_reserved(4026)); assert!(info.is_reserved(4027)); assert!(!info.is_reserved(4004)); }
#[test]
fn test_get_dwarf_num() {
let info = MIPSExtendedRegisterInfo::new();
assert_eq!(info.get_dwarf_num(MIPS_GPR_BASE + 0), Some(0));
assert_eq!(info.get_dwarf_num(MIPS_GPR_BASE + 31), Some(31));
assert_eq!(info.get_dwarf_num(MIPS_FPR_BASE + 0), Some(32));
assert_eq!(info.get_dwarf_num(4032), Some(64)); }
#[test]
fn test_frame_lowering_leaf_function() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: true,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
assert!(!fl.needs_frame_pointer());
let layout = fl.calculate_frame();
assert_eq!(layout.frame_size, 0);
}
#[test]
fn test_frame_lowering_with_calls() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 32,
callee_saved_count: 2,
local_area_size: 16,
is_leaf: false,
};
assert!(fl.needs_frame_pointer());
let layout = fl.calculate_frame();
assert!(layout.frame_size > 0);
assert!(layout.has_frame_pointer);
}
#[test]
fn test_frame_lowering_with_var_sized() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: true,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
assert!(fl.needs_frame_pointer());
}
#[test]
fn test_emit_mips_prologue_full() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 32,
callee_saved_count: 2,
local_area_size: 16,
is_leaf: false,
};
let prologue = fl.emit_prologue();
assert!(prologue.contains("$sp"));
assert!(prologue.contains("$fp"));
}
#[test]
fn test_emit_mips_epilogue_full() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 32,
callee_saved_count: 1,
local_area_size: 16,
is_leaf: false,
};
let epilogue = fl.emit_epilogue();
assert!(epilogue.contains("jr\t$ra"));
}
#[test]
fn test_get_local_offset() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 32,
is_leaf: true,
};
let offset = fl.get_local_offset(0);
assert!(offset <= 0); }
#[test]
fn test_get_frame_base_reg() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl1 = MIPSFrameLowering {
bridge: bridge.clone(),
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: true,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
assert_eq!(fl1.get_frame_base_reg(), 4030);
let fl2 = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: true,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
assert_eq!(fl2.get_frame_base_reg(), 4029); }
#[test]
fn test_emit_frame_info_comment() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
let comment = fl.emit_frame_info_comment();
assert!(comment.starts_with("; Frame:"));
}
#[test]
fn test_calling_conv_o32() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
assert_eq!(cc.int_arg_regs.len(), 4);
assert_eq!(cc.fp_arg_regs.len(), 2);
assert_eq!(cc.remaining_int_args, 4);
let (in_reg, reg, _) = cc.assign_int_arg(4);
assert!(in_reg);
assert_eq!(reg, 4004); assert_eq!(cc.remaining_int_args, 3);
}
#[test]
fn test_calling_conv_n64() {
let mut cc = MIPSCallingConvention::new(MipsAbi::N64);
assert_eq!(cc.int_arg_regs.len(), 8);
assert_eq!(cc.fp_arg_regs.len(), 8);
let (in_reg, reg, _) = cc.assign_int_arg(8);
assert!(in_reg);
assert_eq!(reg, 4004); assert_eq!(cc.remaining_int_args, 7);
}
#[test]
fn test_calling_conv_exhaust_regs() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
for _ in 0..4 {
cc.assign_int_arg(4);
}
assert_eq!(cc.remaining_int_args, 0);
let (in_reg, _, offset) = cc.assign_int_arg(4);
assert!(!in_reg);
assert!(offset > 0);
}
#[test]
fn test_calling_conv_fp_arg() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
let (in_reg, reg, _) = cc.assign_fp_arg(4);
assert!(in_reg);
assert_eq!(reg, 4062); }
#[test]
fn test_calling_conv_return_regs() {
let cc = MIPSCallingConvention::new(MipsAbi::O32);
assert_eq!(cc.get_int_return_reg(0), Some(4002)); assert_eq!(cc.get_int_return_reg(1), Some(4003)); assert_eq!(cc.get_fp_return_reg(0), Some(4050)); }
#[test]
fn test_calling_conv_is_arg_reg() {
let cc = MIPSCallingConvention::new(MipsAbi::O32);
assert!(cc.is_arg_reg(4004)); assert!(!cc.is_arg_reg(4008)); }
#[test]
fn test_calling_conv_varargs_save() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
assert_eq!(cc.get_varargs_save_area_size(), 0);
cc.is_varargs = true;
assert_eq!(cc.get_varargs_save_area_size(), 16);
}
#[test]
fn test_calling_conv_reset() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
cc.assign_int_arg(4);
cc.assign_int_arg(4);
assert_eq!(cc.remaining_int_args, 2);
cc.reset();
assert_eq!(cc.remaining_int_args, 4);
assert!(!cc.is_varargs);
}
#[test]
fn test_calling_conv_return_in_regs() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
assert!(cc.return_in_regs(4)); assert!(cc.return_in_regs(8)); assert!(!cc.return_in_regs(16));
let mut cc64 = MIPSCallingConvention::new(MipsAbi::N64);
assert!(cc64.return_in_regs(8)); assert!(cc64.return_in_regs(16)); }
#[test]
fn test_calling_conv_sret() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
cc.is_sret = true;
assert!(!cc.return_in_regs(4)); assert_eq!(cc.get_sret_reg(), 4004); }
#[test]
fn test_cross_target_creation() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
assert!(ctx.get_pattern(1).is_some());
assert!(ctx.get_pattern(10).is_some());
assert!(ctx.get_pattern(999).is_none());
}
#[test]
fn test_pattern_zero_init() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.get_pattern(1).unwrap();
assert_eq!(pat.description, "Zero register initialization");
assert!(pat.mips_expansion[0].contains("$zero"));
assert!(pat.x86_expansion[0].contains("xor"));
}
#[test]
fn test_pattern_bitwise_not() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.get_pattern(2).unwrap();
assert!(pat.mips_expansion[0].contains("nor"));
assert!(pat.x86_expansion[0].contains("not"));
}
#[test]
fn test_pattern_bswap() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.get_pattern(8).unwrap();
assert_eq!(pat.mips_expansion.len(), 2);
assert_eq!(pat.x86_expansion.len(), 1);
}
#[test]
fn test_best_const_pattern_i32() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.best_const_pattern(42, 32);
assert!(pat.is_some());
assert_eq!(pat.unwrap().mips_cost, 1);
let pat2 = ctx.best_const_pattern(100000, 32);
assert!(pat2.is_some());
assert_eq!(pat2.unwrap().mips_cost, 2);
}
#[test]
fn test_supports_addressing() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
assert!(ctx.supports_addressing("base+offset"));
assert!(!ctx.supports_addressing("base+index*scale"));
}
#[test]
fn test_lower_branch() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let (mips, x86) = ctx.lower_branch("eq", ".Ltarget");
assert_eq!(mips[0], "beq $rs, $rt, .Ltarget");
assert_eq!(x86[0], "je .Ltarget");
}
#[test]
fn test_can_tail_call() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
assert!(ctx.can_tail_call(true)); assert!(ctx.can_tail_call(false)); }
#[test]
fn test_pattern_clz() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.get_pattern(9).unwrap();
assert_eq!(pat.mips_cycles, 1);
assert_eq!(pat.x86_cycles, 3);
}
#[test]
fn test_comparisons_creation() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
assert!(comp.comparisons.len() > 0);
assert!(comp.aggregate.total > 0);
}
#[test]
fn test_comparison_add() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let add = comp.comparisons.iter().find(|c| c.name == "Integer Add").unwrap();
assert_eq!(add.better, CrossTargetChoice::Tie);
assert_eq!(add.mips_cycles, 1);
assert_eq!(add.x86_cycles, 1);
}
#[test]
fn test_comparison_mul() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let mul = comp.comparisons.iter().find(|c| c.name.contains("Multiply")).unwrap();
assert_eq!(mul.better, CrossTargetChoice::X86);
}
#[test]
fn test_comparison_div() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let div = comp.comparisons.iter().find(|c| c.name.contains("Division")).unwrap();
assert_eq!(div.better, CrossTargetChoice::Mips);
}
#[test]
fn test_comparison_branch() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let branch = comp.comparisons.iter().find(|c| c.name == "Conditional Branch").unwrap();
assert_eq!(branch.better, CrossTargetChoice::X86);
}
#[test]
fn test_comparison_aggregate() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
assert!(comp.aggregate.avg_mips_cycles > 0.0);
assert!(comp.aggregate.avg_x86_cycles > 0.0);
}
#[test]
fn test_get_instr_cost_mips() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let cost = comp.get_instr_cost_mips("add");
assert!(cost.is_some());
assert_eq!(cost.unwrap().0, 1);
}
#[test]
fn test_get_instr_cost_x86() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let cost = comp.get_instr_cost_x86("add");
assert!(cost.is_some());
assert_eq!(cost.unwrap().0, 1);
}
#[test]
fn test_compare_sequences() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let mips_seq = vec!["addu $1, $2, $3".to_string()];
let x86_seq = vec!["add %2, %3".to_string()];
assert_eq!(comp.compare_sequences(&mips_seq, &x86_seq), CrossTargetChoice::Tie);
let mips_seq2 = vec!["mult $2, $3".to_string(), "mflo $1".to_string()];
let x86_seq2 = vec!["imul %2, %3".to_string()];
assert_eq!(comp.compare_sequences(&mips_seq2, &x86_seq2), CrossTargetChoice::X86);
}
#[test]
fn test_report() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let report = comp.report();
assert!(report.contains("X86-MIPS Cross-Target Cost Comparison"));
assert!(report.contains("Aggregate"));
assert!(report.contains("MIPS wins"));
}
#[test]
fn test_set_microarch() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let mut comp = X86MIPSComparisons::new(bridge);
comp.set_microarch("mips-p5600", "zen3");
assert_eq!(comp.microarch_costs.mips_microarch, "mips-p5600");
assert_eq!(comp.microarch_costs.x86_microarch, "zen3");
}
#[test]
fn test_cost_weights_default() {
let weights = CostWeights::default();
assert!(weights.size_weight > 0.0);
assert!(weights.latency_weight > 0.0);
assert!(weights.throughput_weight > 0.0);
assert_eq!(weights.target_bias, 0.0);
let sum = weights.size_weight + weights.latency_weight + weights.throughput_weight
+ weights.reg_pressure_weight + weights.power_weight;
assert!((sum - 1.0).abs() < 0.001);
}
#[test]
fn test_target_instruction_cost_default() {
let cost = TargetInstructionCost::default();
assert_eq!(cost.latency, 0);
assert_eq!(cost.size, 0);
assert_eq!(cost.gpr_used, 0);
}
#[test]
fn test_cost_winner_enum() {
assert_eq!(CostWinner::Mips as i32, CostWinner::Mips as i32);
assert_ne!(CostWinner::Mips as i32, CostWinner::X86 as i32);
}
#[test]
fn test_constants() {
assert_eq!(X86_GPR_COUNT, 16);
assert_eq!(X86_XMM_COUNT, 16);
assert_eq!(MIPS_INSTR_SIZE, 4);
assert_eq!(MICROMIPS_INSTR_SIZE, 2);
assert_eq!(X86_RED_ZONE_SIZE, 128);
}
#[test]
fn test_cost_summary_display() {
let summary = CostSummary {
total_entries: 100,
mips_wins: 30,
x86_wins: 50,
ties: 20,
mips_unsupported: 0,
x86_unsupported: 0,
total_mips_latency: 300,
total_x86_latency: 400,
avg_mips_latency: 3.0,
avg_x86_latency: 4.0,
total_mips_size: 400,
total_x86_size: 600,
};
let disp = summary.to_string();
assert!(disp.contains("MIPS wins: 30"));
assert!(disp.contains("X86 wins: 50"));
assert!(disp.contains("Ties: 20"));
}
#[test]
fn test_cross_target_choice_display() {
assert_eq!(CrossTargetChoice::Mips.to_string(), "MIPS");
assert_eq!(CrossTargetChoice::X86.to_string(), "X86");
assert_eq!(CrossTargetChoice::Tie.to_string(), "Tie");
}
#[test]
fn test_x86_calling_convention_variant_display() {
assert_eq!(X86CallingConventionVariant::SysV64.to_string(), "sysv64");
assert_eq!(X86CallingConventionVariant::Win64.to_string(), "win64");
}
#[test]
fn test_mips_func_unit_enum() {
assert_eq!(MipsFuncUnit::ALU as i32, MipsFuncUnit::ALU as i32);
assert_ne!(MipsFuncUnit::ALU as i32, MipsFuncUnit::LSU as i32);
}
#[test]
fn test_pattern_operand_kind() {
assert_eq!(PatternOperandKind::VRegDef as i32, PatternOperandKind::VRegDef as i32);
}
#[test]
fn test_phi_strategy_default() {
assert_eq!(PhiStrategy::default(), PhiStrategy::Default);
}
#[test]
fn test_mips_frame_layout_default() {
let layout = MIPSFrameLayout::default();
assert_eq!(layout.frame_size, 0);
assert!(!layout.has_frame_pointer);
}
#[test]
fn test_bridge_calling_convention_default() {
let cc = BridgeCallingConvention::default();
assert_eq!(cc.mips_int_args.len(), 4);
assert_eq!(cc.x86_int_args.len(), 6);
}
#[test]
fn test_contextual_costs_default() {
let cc = ContextualCosts::default();
assert!(cc.branch_mispredict_penalty > 0);
assert!(cc.l1_miss_penalty > 0);
assert!(cc.l2_miss_penalty > cc.l1_miss_penalty);
}
#[test]
fn test_mips_target_machine_prefer_mips_for_x86_triple() {
let mut tm = MIPSTargetMachine::new("mips64-unknown-linux-gnu");
let result = tm.prefer_mips_for(12); assert!(result);
}
#[test]
fn test_bridge_mips64_prologue_epilogue() {
let mut bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
bridge.calculate_frame(32, 3, true, false);
let prologue = bridge.emit_mips_prologue();
let epilogue = bridge.emit_mips_epilogue();
assert!(!prologue.is_empty());
assert!(!epilogue.is_empty());
assert!(epilogue.contains("jr\t$ra"));
}
#[test]
fn test_full_integration_flow() {
let mut bridge = MIPSX86Bridge::new("mips64el-unknown-linux-gnu");
let r1 = bridge.allocate_vreg(1, RegClassKind::GPR64);
let r2 = bridge.allocate_vreg(2, RegClassKind::GPR64);
let r3 = bridge.allocate_vreg(3, RegClassKind::FPR64);
assert_eq!(r1.vreg, 1);
assert_eq!(r2.vreg, 2);
assert_eq!(r3.vreg, 3);
let ir = vec![
CrossTargetIRInst { opcode: 8, dest: Some(1), srcs: vec![2, 3], immediate: None, type_code: 1 },
CrossTargetIRInst { opcode: 27, dest: Some(4), srcs: vec![1], immediate: Some(0), type_code: 0 },
CrossTargetIRInst { opcode: 28, dest: None, srcs: vec![4, 1], immediate: Some(4), type_code: 0 },
];
let (mips_code, x86_code) = bridge.generate_cross_target(&ir);
assert!(mips_code.contains("addu"));
assert!(x86_code.contains("add"));
let comparison = bridge.run_full_comparison();
assert!(!comparison.is_empty());
let summary = bridge.cost_summary();
assert!(summary.total_entries > 0);
}
#[test]
fn test_mips_abi_varargs_save_size() {
assert_eq!(MipsAbi::O32.varargs_save_size(), 16);
assert_eq!(MipsAbi::N32.varargs_save_size(), 64);
assert_eq!(MipsAbi::N64.varargs_save_size(), 64);
}
#[test]
fn test_calling_conv_abi_name() {
let cc_o32 = MIPSCallingConvention::new(MipsAbi::O32);
assert_eq!(cc_o32.abi_name(), "o32");
let cc_n64 = MIPSCallingConvention::new(MipsAbi::N64);
assert_eq!(cc_n64.abi_name(), "n64");
}
#[test]
fn test_bridge_call_conv_configure_abi() {
let bridge_o32 = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert_eq!(bridge_o32.call_conv.mips_int_args.len(), 4);
let bridge_n64 = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
assert_eq!(bridge_n64.call_conv.mips_int_args.len(), 8);
}
#[test]
fn test_extended_instr_info_system_insts() {
let info = MIPSExtendedInstrInfo::new();
assert!(info.get_cost(&MipsOpcode::SYSCALL).is_some());
assert!(info.get_cost(&MipsOpcode::BREAK).is_some());
assert_eq!(info.get_func_unit(&MipsOpcode::ERET), MipsFuncUnit::System);
}
#[test]
fn test_extended_instr_info_pseudo_insts() {
let info = MIPSExtendedInstrInfo::new();
assert_eq!(info.get_encoding_type(&MipsOpcode::NOP), MipsEncodingType::Pseudo);
assert_eq!(info.get_encoding_type(&MipsOpcode::B), MipsEncodingType::Pseudo);
}
#[test]
fn test_memcpy_comparison() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let memcpy = comp.comparisons.iter().find(|c| c.name.contains("Memory copy")).unwrap();
assert_eq!(memcpy.better, CrossTargetChoice::X86);
assert!(memcpy.x86_cycles < memcpy.mips_cycles);
}
#[test]
fn test_x86_cc_variant_configures_bridge() {
let mut bridge = MIPSX86Bridge::new("x86_64-pc-windows-msvc");
bridge.x86_cc = X86CallingConventionVariant::Win64;
bridge.configure_abi();
assert_eq!(bridge.call_conv.x86_int_args.len(), 4); assert_eq!(bridge.call_conv.shadow_store, 32);
}
#[test]
fn test_bridge_all_cc_variants() {
for cc in &[
X86CallingConventionVariant::SysV64,
X86CallingConventionVariant::Win64,
X86CallingConventionVariant::CDecl32,
X86CallingConventionVariant::StdCall32,
X86CallingConventionVariant::FastCall32,
] {
let mut bridge = MIPSX86Bridge::new("x86_64-unknown-linux-gnu");
bridge.x86_cc = *cc;
bridge.configure_abi();
assert!(!bridge.call_conv.x86_int_rets.is_empty());
assert!(!bridge.call_conv.x86_sse_rets.is_empty());
}
}
#[test]
fn test_bridge_all_mips_abi_variants() {
for abi in &[MipsAbi::O32, MipsAbi::N32, MipsAbi::N64] {
let mut bridge = MIPSX86Bridge::new(if *abi == MipsAbi::N64 {
"mips64-unknown-linux-gnu"
} else {
"mips-unknown-linux-gnu"
});
bridge.mips_abi = *abi;
bridge.configure_abi();
assert!(!bridge.call_conv.mips_int_args.is_empty());
}
}
#[test]
fn test_bridge_vreg_allocation_across_classes() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let gpr = bridge.allocate_vreg(1, RegClassKind::GPR32);
let fpr = bridge.allocate_vreg(2, RegClassKind::FPR32);
let vec = bridge.allocate_vreg(3, RegClassKind::Vec128);
assert_ne!(gpr.mips_reg, fpr.mips_reg);
assert_ne!(gpr.mips_reg, vec.mips_reg);
assert!(gpr.mips_reg >= MIPS_GPR_BASE && gpr.mips_reg < MIPS_GPR_BASE + 32);
assert!(fpr.mips_reg >= MIPS_FPR_BASE && fpr.mips_reg < MIPS_FPR_BASE + 32);
}
#[test]
fn test_bridge_multiple_spills() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
for i in 0..50 {
bridge.allocate_vreg(i, RegClassKind::GPR32);
bridge.spill_vreg(i);
}
assert_eq!(bridge.spill_slots.len(), 50);
for i in 1..bridge.spill_slots.len() {
assert!(bridge.spill_slots[i].offset < bridge.spill_slots[i - 1].offset);
}
}
#[test]
fn test_bridge_reg_pair_no_conflict() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let allocs: Vec<AllocatedReg> = (0..24)
.map(|i| bridge.allocate_vreg(i, RegClassKind::GPR32))
.collect();
let mut seen = std::collections::HashSet::new();
for a in &allocs {
assert!(seen.insert(a.mips_reg), "Duplicate MIPS reg {}", a.mips_reg);
}
}
#[test]
fn test_bridge_pattern_cache_all_opcodes() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let opcodes: Vec<u32> = vec![8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 21, 22, 23, 24, 25, 27, 28, 44, 45];
for oc in &opcodes {
let pat = bridge.get_or_build_pattern(*oc);
assert_eq!(pat.opcode, *oc);
assert!(!pat.mips_sequence.is_empty());
assert!(!pat.x86_sequence.is_empty());
}
assert_eq!(bridge.pattern_cache.len(), opcodes.len());
}
#[test]
fn test_bridge_large_frame_prologue() {
let mut bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
bridge.calculate_frame(4096, 8, true, true);
let prologue = bridge.emit_mips_prologue();
assert!(prologue.contains("daddiu"));
assert!(prologue.contains("$sp"));
}
#[test]
fn test_bridge_x86_64_prologue_with_callee_saved() {
let mut bridge = MIPSX86Bridge::new("x86_64-unknown-linux-gnu");
bridge.is_64bit = true;
bridge.calculate_frame(128, 4, true, false);
let prologue = bridge.emit_x86_prologue();
assert!(prologue.contains("pushq"));
assert!(prologue.contains("movq"));
assert!(prologue.contains("subq"));
}
#[test]
fn test_bridge_x86_32_prologue() {
let mut bridge = MIPSX86Bridge::new("i686-unknown-linux-gnu");
bridge.is_64bit = false;
bridge.calculate_frame(64, 3, true, false);
let prologue = bridge.emit_x86_prologue();
assert!(prologue.contains("pushl"));
assert!(prologue.contains("movl"));
}
#[test]
fn test_bridge_cost_weights_custom() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.cost_weights = CostWeights {
size_weight: 0.5,
latency_weight: 0.3,
throughput_weight: 0.1,
reg_pressure_weight: 0.05,
power_weight: 0.05,
target_bias: 5.0,
};
assert!(!bridge.select_target_for_opcode(8));
assert!(!bridge.select_target_for_opcode(12));
}
#[test]
fn test_bridge_mips64_reg_width() {
let bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
assert!(bridge.is_64bit);
let frame = bridge.calculate_frame(0, 0, false, false);
assert_eq!(frame.stack_alignment, 16);
}
#[test]
fn test_bridge_o32_abi_frame() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.mips_abi = MipsAbi::O32;
bridge.configure_abi();
let frame = bridge.calculate_frame(20, 2, true, false);
assert_eq!(frame.stack_alignment, 8);
assert!(frame.has_frame_pointer);
}
#[test]
fn test_bridge_get_mips_instr() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let desc = bridge.get_mips_instr(MipsOpcode::ADD);
assert_eq!(desc.opcode, MipsOpcode::ADD);
}
#[test]
fn test_bridge_is_commutative() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert!(bridge.is_commutative(MipsOpcode::ADD));
assert!(!bridge.is_commutative(MipsOpcode::SUB));
}
#[test]
fn test_bridge_generate_cross_target_empty() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let (mips, x86) = bridge.generate_cross_target(&[]);
assert!(!mips.is_empty());
assert!(!x86.is_empty());
}
#[test]
fn test_bridge_generate_cross_target_all_opcodes() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let ir: Vec<CrossTargetIRInst> = vec![
CrossTargetIRInst { opcode: 8, dest: Some(1), srcs: vec![2, 3], immediate: None, type_code: 0 },
CrossTargetIRInst { opcode: 10, dest: Some(4), srcs: vec![1, 5], immediate: None, type_code: 0 },
CrossTargetIRInst { opcode: 23, dest: Some(6), srcs: vec![4, 7], immediate: None, type_code: 0 },
CrossTargetIRInst { opcode: 24, dest: Some(8), srcs: vec![6, 9], immediate: None, type_code: 0 },
CrossTargetIRInst { opcode: 25, dest: Some(10), srcs: vec![8, 11], immediate: None, type_code: 0 },
CrossTargetIRInst { opcode: 20, dest: Some(12), srcs: vec![10, 13], immediate: None, type_code: 0 },
CrossTargetIRInst { opcode: 27, dest: Some(14), srcs: vec![12], immediate: Some(0), type_code: 0 },
CrossTargetIRInst { opcode: 28, dest: None, srcs: vec![14, 12], immediate: Some(0), type_code: 0 },
];
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(mips.contains("addu"));
assert!(mips.contains("subu"));
assert!(mips.contains("and"));
assert!(mips.contains("or"));
assert!(mips.contains("xor"));
assert!(x86.contains("add"));
assert!(x86.contains("sub"));
}
#[test]
fn test_extended_reg_info_out_of_bounds_gpr() {
let info = MIPSExtendedRegisterInfo::new();
assert_eq!(info.gpr_abi_name(32), "?");
assert_eq!(info.gpr_abi_name(999), "?");
}
#[test]
fn test_extended_reg_info_out_of_bounds_fpr() {
let info = MIPSExtendedRegisterInfo::new();
assert_eq!(info.fpr_abi_name(32), "?");
assert_eq!(info.fpr_abi_name(999), "?");
}
#[test]
fn test_extended_reg_info_dwarf_unknown() {
let info = MIPSExtendedRegisterInfo::new();
assert_eq!(info.get_dwarf_num(9999), None);
}
#[test]
fn test_extended_reg_info_all_fpr_names() {
let info = MIPSExtendedRegisterInfo::new();
for i in 0..32 {
let name = info.fpr_abi_name(i);
assert!(!name.is_empty());
assert_ne!(name, "f?");
}
}
#[test]
fn test_extended_reg_info_callee_saved_sizes() {
let info = MIPSExtendedRegisterInfo::new();
assert!(!info.get_callee_saved(MipsAbi::O32).is_empty());
assert!(!info.get_callee_saved(MipsAbi::N32).is_empty());
assert!(!info.get_callee_saved(MipsAbi::N64).is_empty());
}
#[test]
fn test_extended_reg_info_caller_saved_sizes() {
let info = MIPSExtendedRegisterInfo::new();
assert!(!info.get_caller_saved(MipsAbi::O32).is_empty());
assert!(!info.get_caller_saved(MipsAbi::N32).is_empty());
assert!(!info.get_caller_saved(MipsAbi::N64).is_empty());
}
#[test]
fn test_extended_reg_info_no_overlap_callee_caller() {
let info = MIPSExtendedRegisterInfo::new();
let callee = info.get_callee_saved(MipsAbi::O32);
let caller = info.get_caller_saved(MipsAbi::O32);
for r in callee {
assert!(!caller.contains(r));
}
}
#[test]
fn test_extended_reg_info_max_allocatable() {
let info = MIPSExtendedRegisterInfo::new();
assert!(info.max_allocatable_gprs > 0);
assert!(info.max_allocatable_fprs > 0);
assert!(info.max_allocatable_gprs <= 32);
assert!(info.max_allocatable_fprs <= 32);
}
#[test]
fn test_frame_lowering_huge_local_area() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 100000,
is_leaf: true,
};
assert!(fl.needs_frame_pointer());
}
#[test]
fn test_frame_lowering_uses_fp_reg_names() {
let bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: true,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
let prologue = fl.emit_prologue();
assert!(prologue.contains("$sp"));
}
#[test]
fn test_frame_lowering_n64_word_size() {
let bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 1,
local_area_size: 0,
is_leaf: false,
};
let prologue = fl.emit_prologue();
assert!(prologue.contains("sd") || prologue.contains("daddiu"));
}
#[test]
fn test_frame_lowering_o32_word_size() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 1,
local_area_size: 0,
is_leaf: false,
};
let prologue = fl.emit_prologue();
assert!(prologue.contains("sw") || prologue.contains("addiu"));
}
#[test]
fn test_frame_lowering_prologue_epilogue_consistency() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 32,
callee_saved_count: 3,
local_area_size: 64,
is_leaf: false,
};
let prologue = fl.emit_prologue();
let epilogue = fl.emit_epilogue();
assert!(epilogue.contains("jr"));
assert!(!prologue.contains("jr"));
}
#[test]
fn test_frame_lowering_local_negative_offsets() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 64,
is_leaf: true,
};
for i in 0..8 {
let offset = fl.get_local_offset(i);
assert!(offset <= 0);
}
}
#[test]
fn test_frame_lowering_frame_base_name() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl_fp = MIPSFrameLowering {
bridge: bridge.clone(),
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: true,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
let fl_sp = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: true,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
assert_eq!(fl_fp.get_frame_base_name(), "$fp");
assert_eq!(fl_sp.get_frame_base_name(), "$sp");
}
#[test]
fn test_calling_conv_exhaust_all_int_then_fp() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
for _ in 0..4 {
cc.assign_int_arg(4);
}
let (in_reg, _, offset) = cc.assign_int_arg(4);
assert!(!in_reg);
assert!(offset > 0);
let (fp_in_reg, fp_reg, _) = cc.assign_fp_arg(4);
assert!(fp_in_reg);
assert_eq!(fp_reg, 4062);
}
#[test]
fn test_calling_conv_n64_exhaust_all_8() {
let mut cc = MIPSCallingConvention::new(MipsAbi::N64);
for _ in 0..8 {
cc.assign_int_arg(8);
}
assert_eq!(cc.remaining_int_args, 0);
let (in_reg, _, offset) = cc.assign_int_arg(8);
assert!(!in_reg);
assert!(offset >= 16);
}
#[test]
fn test_calling_conv_return_sret() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
cc.is_sret = true;
assert!(!cc.return_in_regs(4));
assert_eq!(cc.get_sret_reg(), 4004);
}
#[test]
fn test_calling_conv_reg_name_format() {
let cc = MIPSCallingConvention::new(MipsAbi::O32);
assert!(cc.reg_name(4004).contains("a0"));
assert!(cc.reg_name(4050).contains("f0"));
}
#[test]
fn test_calling_conv_used_stack_space() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
assert_eq!(cc.get_used_stack_space(), 0);
for _ in 0..5 {
cc.assign_int_arg(4);
}
assert!(cc.get_used_stack_space() > 0);
}
#[test]
fn test_calling_conv_varargs_n64() {
let mut cc = MIPSCallingConvention::new(MipsAbi::N64);
cc.is_varargs = true;
assert_eq!(cc.get_varargs_save_area_size(), 64);
}
#[test]
fn test_cross_target_supports_all_addressing_modes() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
assert!(ctx.supports_addressing("base+offset"));
assert!(ctx.supports_addressing("pc-relative"));
assert!(ctx.supports_addressing("gp-relative"));
assert!(!ctx.supports_addressing("base+index*scale"));
}
#[test]
fn test_cross_target_all_branch_conditions() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let target = ".L1234";
for cond in &["eq", "ne", "lt", "ge", "le", "gt"] {
let (mips, x86) = ctx.lower_branch(cond, target);
assert!(!mips.is_empty());
assert!(!x86.is_empty());
assert!(mips[0].contains(target));
assert!(x86[0].contains(target));
}
}
#[test]
fn test_cross_target_lower_unknown_branch() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let (mips, x86) = ctx.lower_branch("unknown", ".Ldest");
assert!(!mips.is_empty());
assert!(!x86.is_empty());
}
#[test]
fn test_cross_target_pattern_enumeration() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
for id in 1..=10 {
let pat = ctx.get_pattern(id);
assert!(pat.is_some());
assert_eq!(pat.unwrap().id, id);
}
}
#[test]
fn test_cross_target_const_materializer_all_sizes() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
assert!(ctx.best_const_pattern(0, 32).is_some());
assert!(ctx.best_const_pattern(i16::MAX as i64, 32).is_some());
assert!(ctx.best_const_pattern(0, 64).is_some());
assert!(ctx.best_const_pattern(i64::MAX, 64).is_some());
assert!(ctx.best_const_pattern(42, 128).is_none());
}
#[test]
fn test_comparison_all_entries_have_valid_targets() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
for c in &comp.comparisons {
assert!(!c.mips_seq.is_empty());
assert!(!c.x86_seq.is_empty());
assert!(c.mips_cycles > 0 || c.name.contains("Zero"));
assert!(c.x86_cycles > 0 || c.name.contains("Zero"));
}
}
#[test]
fn test_comparison_aggregate_sums_match() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let total: u64 = comp.comparisons.iter().map(|c| c.mips_cycles as u64).sum();
let avg = total as f64 / comp.comparisons.len() as f64;
assert!((comp.aggregate.avg_mips_cycles - avg).abs() < 0.01);
}
#[test]
fn test_comparison_get_instr_cost_unknown() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
assert!(comp.get_instr_cost_mips("nonexistent").is_none());
assert!(comp.get_instr_cost_x86("nonexistent").is_none());
}
#[test]
fn test_comparison_all_microarch_entries() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
assert!(!comp.microarch_costs.mips_cycles.is_empty());
assert!(!comp.microarch_costs.x86_cycles.is_empty());
assert!(comp.microarch_costs.mips_cycles.contains_key("add"));
assert!(comp.microarch_costs.x86_cycles.contains_key("add"));
}
#[test]
fn test_comparison_compare_sequences_empty() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
assert_eq!(comp.compare_sequences(&[], &[]), CrossTargetChoice::Tie);
}
#[test]
fn test_comparison_report_length() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let report = comp.report();
assert!(report.lines().count() > comp.comparisons.len() * 4);
}
#[test]
fn test_contextual_costs_hierarchy() {
let cc = ContextualCosts::default();
assert!(cc.l1_miss_penalty < cc.l2_miss_penalty);
assert!(cc.l2_miss_penalty < cc.l3_miss_penalty);
}
#[test]
fn test_mips_encoding_types_all_variants() {
let types = vec![
MipsEncodingType::RType, MipsEncodingType::IType, MipsEncodingType::JType,
MipsEncodingType::FRType, MipsEncodingType::FIType, MipsEncodingType::CopType,
MipsEncodingType::Pseudo, MipsEncodingType::Micro16, MipsEncodingType::Micro32,
];
for t in &types {
assert!(!t.name().is_empty());
assert!(t.size() == 0 || t.size() == 2 || t.size() == 4);
}
}
#[test]
fn test_sorted_by_latency_descending() {
let info = MIPSExtendedInstrInfo::new();
let sorted = info.sorted_by_latency();
for i in 1..sorted.len() {
assert!(sorted[i - 1].1 >= sorted[i].1);
}
}
#[test]
fn test_average_cost_empty() {
let info = MIPSExtendedInstrInfo::new();
assert_eq!(info.average_cost(&[]), 0.0);
}
#[test]
fn test_cost_summary_empty() {
let summary = CostSummary::default();
assert_eq!(summary.total_entries, 0);
assert_eq!(summary.mips_wins, 0);
assert_eq!(summary.x86_wins, 0);
}
#[test]
fn test_cost_summary_percentage_calculation() {
let summary = CostSummary {
total_entries: 10,
mips_wins: 3,
x86_wins: 5,
ties: 2,
..Default::default()
};
let disp = summary.to_string();
assert!(disp.contains("30.0%"));
assert!(disp.contains("50.0%"));
}
#[test]
fn test_stress_many_allocations() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
for i in 0..1000 {
bridge.allocate_vreg(i, RegClassKind::GPR32);
}
assert!(!bridge.vreg_allocations.is_empty());
}
#[test]
fn test_stress_many_spills() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
for i in 0..500 {
bridge.allocate_vreg(i, RegClassKind::FPR64);
bridge.spill_vreg(i);
}
assert_eq!(bridge.spill_slots.len(), 500);
}
#[test]
fn test_stress_pattern_cache() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
for oc in 0..100 {
let pattern = bridge.get_or_build_pattern(oc);
assert_eq!(pattern.opcode, oc);
}
assert_eq!(bridge.pattern_cache.len(), 100);
}
#[test]
fn test_full_lifecycle_bridge() {
let mut bridge = MIPSX86Bridge::new("mips64el-unknown-linux-gnu");
bridge.calculate_frame(256, 5, true, true);
for i in 1..=20 {
bridge.allocate_vreg(i, RegClassKind::GPR64);
}
let ir: Vec<CrossTargetIRInst> = (0..20).map(|i| CrossTargetIRInst {
opcode: if i % 2 == 0 { 8 } else { 10 },
dest: Some(i * 2 + 1),
srcs: vec![i * 2, i * 2 + 2],
immediate: if i % 5 == 0 { Some(i as i64) } else { None },
type_code: 1,
}).collect();
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(!mips.is_empty());
assert!(!x86.is_empty());
let comparison = bridge.run_full_comparison();
assert!(!comparison.is_empty());
let summary = bridge.cost_summary();
assert!(summary.total_entries > 0);
}
#[test]
fn test_bridge_concurrent_access_patterns() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
for _ in 0..5 {
bridge.clear_allocations();
bridge.calculate_frame(64, 2, true, false);
let r1 = bridge.allocate_vreg(1, RegClassKind::GPR32);
let r2 = bridge.allocate_vreg(2, RegClassKind::GPR32);
assert_ne!(r1.mips_reg, r2.mips_reg);
let prologue = bridge.emit_mips_prologue();
let epilogue = bridge.emit_mips_epilogue();
assert!(!prologue.is_empty());
assert!(!epilogue.is_empty());
}
}
#[test]
fn test_delay_slots_only_for_branches_and_jumps() {
let info = MIPSExtendedInstrInfo::new();
assert!(!info.has_delay_slot(&MipsOpcode::ADD));
assert!(!info.has_delay_slot(&MipsOpcode::LW));
assert!(info.has_delay_slot(&MipsOpcode::BEQ));
assert!(info.has_delay_slot(&MipsOpcode::J));
}
#[test]
fn test_x86_cc_variant_stdcall_cleans_stack() {
let mut bridge = MIPSX86Bridge::new("i686-unknown-linux-gnu");
bridge.x86_cc = X86CallingConventionVariant::StdCall32;
bridge.configure_abi();
assert!(bridge.call_conv.callee_cleans_stack);
}
#[test]
fn test_x86_cc_variant_cdecl_does_not_clean() {
let mut bridge = MIPSX86Bridge::new("i686-unknown-linux-gnu");
bridge.x86_cc = X86CallingConventionVariant::CDecl32;
bridge.configure_abi();
assert!(!bridge.call_conv.callee_cleans_stack);
}
#[test]
fn test_reg_class_kind_flag() {
assert!(!RegClassKind::Flag.is_integer());
assert!(!RegClassKind::Flag.is_fp());
}
#[test]
fn test_phi_elimination_defaults() {
let pe = PhiElimination::default();
assert_eq!(pe.strategy, PhiStrategy::Default);
assert!(!pe.needs_critical_edge_split);
}
#[test]
fn test_bridge_frame_info_default() {
let fi = BridgeFrameInfo::default();
assert_eq!(fi.frame_size, 0);
assert!(!fi.has_frame_pointer);
assert!(!fi.has_calls);
}
#[test]
fn test_allocated_reg_clone_eq() {
let alloc = AllocatedReg {
vreg: 42,
mips_reg: 4008,
x86_reg: "r8".into(),
is_spill: false,
spill_slot: None,
reg_class: RegClassKind::GPR64,
};
let cloned = alloc.clone();
assert_eq!(alloc.vreg, cloned.vreg);
assert_eq!(alloc.mips_reg, cloned.mips_reg);
}
#[test]
fn test_spill_slot_value() {
let slot = SpillSlot {
offset: -16,
size: 8,
alignment: 8,
is_callee_saved: true,
mips_reg: Some(4016),
x86_reg: Some("rbx".into()),
};
assert_eq!(slot.offset, -16);
assert!(slot.is_callee_saved);
}
#[test]
fn test_cross_target_ir_inst_fields() {
let inst = CrossTargetIRInst {
opcode: 8,
dest: Some(1),
srcs: vec![2, 3],
immediate: Some(42),
type_code: 1,
};
assert_eq!(inst.opcode, 8);
assert_eq!(inst.srcs.len(), 2);
assert_eq!(inst.immediate, Some(42));
}
#[test]
fn test_pattern_instruction_debug() {
let pi = PatternInstruction {
opcode: "addu".into(),
operands: vec![PatternOperand {
kind: PatternOperandKind::VRegDef,
vreg: Some(1),
immediate: None,
reg_name: None,
}],
is_terminator: false,
defines_vreg: true,
};
assert_eq!(pi.opcode, "addu");
assert!(pi.defines_vreg);
assert!(!pi.is_terminator);
}
#[test]
fn test_mips_target_machine_data_layout() {
let tm32 = MIPSTargetMachine::new("mips-unknown-linux-gnu");
let tm64 = MIPSTargetMachine::new("mips64-unknown-linux-gnu");
let tmel = MIPSTargetMachine::new("mipsel-unknown-linux-gnu");
assert!(!tm32.data_layout_str().is_empty());
assert!(!tm64.data_layout_str().is_empty());
assert!(!tmel.data_layout_str().is_empty());
assert_ne!(tm32.data_layout_str(), tmel.data_layout_str());
}
#[test]
fn test_mips_target_machine_feature_msa() {
let tm = MIPSTargetMachine::with_features("mips-unknown-linux-gnu", &["msa"]);
assert!(tm.has_feature("msa"));
assert!(tm.bridge.has_msa);
}
#[test]
fn test_mips_target_machine_feature_micromips() {
let tm = MIPSTargetMachine::with_features("mips-unknown-linux-gnu", &["micromips"]);
assert!(tm.has_feature("micromips"));
assert!(tm.bridge.prefer_micromips);
}
#[test]
fn test_mips_target_machine_prefer_mips_mul() {
let mut tm = MIPSTargetMachine::new("mips64-unknown-linux-gnu");
assert!(tm.prefer_mips_for(12));
}
#[test]
fn test_bridge_mips64_prologue_epilogue_full() {
let mut bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
bridge.calculate_frame(32, 3, true, false);
let prologue = bridge.emit_mips_prologue();
let epilogue = bridge.emit_mips_epilogue();
assert!(!prologue.is_empty());
assert!(!epilogue.is_empty());
assert!(epilogue.contains("jr"));
}
#[test]
fn test_mips_abi_varargs_all() {
for abi in &[MipsAbi::O32, MipsAbi::N32, MipsAbi::N64] {
assert!(abi.varargs_save_size() > 0);
}
}
#[test]
fn test_calling_conv_abi_names() {
assert_eq!(MIPSCallingConvention::new(MipsAbi::O32).abi_name(), "o32");
assert_eq!(MIPSCallingConvention::new(MipsAbi::N64).abi_name(), "n64");
}
#[test]
fn test_comparison_memcpy_size() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let memcpy = comp.comparisons.iter().find(|c| c.name.contains("Memory copy")).unwrap();
assert_eq!(memcpy.mips_size, 128);
assert_eq!(memcpy.x86_size, 3);
}
#[test]
fn test_contextual_branch_penalty() {
let cc = ContextualCosts::default();
assert!(cc.branch_mispredict_penalty > 1);
assert!(cc.indirect_branch_overhead > 1);
}
#[test]
fn test_cross_target_pattern_all_non_empty() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
for id in 1..=10 {
if let Some(pat) = ctx.get_pattern(id) {
for seq in &pat.mips_expansion {
assert!(!seq.is_empty());
}
for seq in &pat.x86_expansion {
assert!(!seq.is_empty());
}
}
}
}
#[test]
fn test_target_instr_cost_default_zero() {
let cost = TargetInstructionCost::default();
assert_eq!(cost.latency, 0);
assert_eq!(cost.size, 0);
}
#[test]
fn test_cost_winner_discriminants() {
assert_ne!(CostWinner::Mips as u8, CostWinner::X86 as u8);
assert_ne!(CostWinner::Mips as u8, CostWinner::Tie as u8);
}
#[test]
fn test_phi_strategy_copy_at_pred() {
assert_ne!(PhiStrategy::CopyAtPred, PhiStrategy::CopyAtSucc);
}
#[test]
fn test_mips_frame_layout_default_all_zero() {
let layout = MIPSFrameLayout::default();
assert_eq!(layout.frame_size, 0);
assert!(!layout.has_frame_pointer);
assert_eq!(layout.callee_saved_size, 0);
}
#[test]
fn test_bridge_calling_conv_default_args() {
let cc = BridgeCallingConvention::default();
assert_eq!(cc.mips_int_args.len(), 4);
assert_eq!(cc.x86_int_args.len(), 6);
assert_eq!(cc.mips_int_rets.len(), 2);
}
#[test]
fn test_mips_func_unit_all_discriminants() {
assert_eq!(MipsFuncUnit::ALU as u8, MipsFuncUnit::ALU as u8);
assert_ne!(MipsFuncUnit::ALU as u8, MipsFuncUnit::LSU as u8);
}
#[test]
fn test_pattern_operand_kind_discriminants() {
assert_ne!(PatternOperandKind::VRegDef, PatternOperandKind::VRegUse);
assert_ne!(PatternOperandKind::Immediate, PatternOperandKind::MemoryAddress);
}
#[test]
fn test_comparison_set_microarch_persists() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let mut comp = X86MIPSComparisons::new(bridge);
comp.set_microarch("custom-mips", "custom-x86");
assert_eq!(comp.microarch_costs.mips_microarch, "custom-mips");
assert_eq!(comp.microarch_costs.x86_microarch, "custom-x86");
}
#[test]
fn test_extended_instr_info_msa_ops_exist() {
let info = MIPSExtendedInstrInfo::new();
assert!(info.get_cost(&MipsOpcode::ADDV_W).is_some());
assert!(info.get_cost(&MipsOpcode::FADD_W).is_some());
assert!(info.get_cost(&MipsOpcode::CEQ_W).is_some());
}
#[test]
fn test_extended_instr_info_dsp_ops_exist() {
let info = MIPSExtendedInstrInfo::new();
assert!(info.get_cost(&MipsOpcode::ADDQ_PH).is_some());
assert!(info.get_cost(&MipsOpcode::MUL_PH).is_some());
assert!(info.get_cost(&MipsOpcode::CMPGU_EQ_QB).is_some());
}
#[test]
fn test_extended_instr_info_system_ops_exist() {
let info = MIPSExtendedInstrInfo::new();
assert!(info.get_cost(&MipsOpcode::SYSCALL).is_some());
assert!(info.get_cost(&MipsOpcode::BREAK).is_some());
assert_eq!(info.get_func_unit(&MipsOpcode::ERET), MipsFuncUnit::System);
}
#[test]
fn test_extended_instr_info_pseudo_encoding() {
let info = MIPSExtendedInstrInfo::new();
assert_eq!(info.get_encoding_type(&MipsOpcode::NOP), MipsEncodingType::Pseudo);
assert_eq!(info.get_encoding_type(&MipsOpcode::B), MipsEncodingType::Pseudo);
}
#[test]
fn test_bridge_full_integration_mips64_el() {
let mut bridge = MIPSX86Bridge::new("mips64el-unknown-linux-gnu");
let r1 = bridge.allocate_vreg(1, RegClassKind::GPR64);
let r2 = bridge.allocate_vreg(2, RegClassKind::GPR64);
let r3 = bridge.allocate_vreg(3, RegClassKind::FPR64);
assert_eq!(r1.vreg, 1);
assert_eq!(r2.vreg, 2);
assert_eq!(r3.vreg, 3);
let ir = vec![
CrossTargetIRInst { opcode: 8, dest: Some(1), srcs: vec![2, 3], immediate: None, type_code: 1 },
CrossTargetIRInst { opcode: 27, dest: Some(4), srcs: vec![1], immediate: Some(0), type_code: 0 },
CrossTargetIRInst { opcode: 28, dest: None, srcs: vec![4, 1], immediate: Some(4), type_code: 0 },
];
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(!mips.is_empty());
assert!(!x86.is_empty());
let comparison = bridge.run_full_comparison();
assert!(!comparison.is_empty());
}
#[test]
fn test_bridge_with_msa_feature_enables_vector_ops() {
let bridge = MIPSX86Bridge::with_features("mips-unknown-linux-gnu", &["msa"]);
assert!(bridge.has_msa);
assert!(!bridge.has_dsp);
assert!(!bridge.has_avx2);
}
#[test]
fn test_bridge_with_all_features() {
let bridge = MIPSX86Bridge::with_features("mips-unknown-linux-gnu", &["msa", "dsp", "avx2"]);
assert!(bridge.has_msa);
assert!(bridge.has_dsp);
assert!(bridge.has_avx2);
}
#[test]
fn test_bridge_prefer_micromips_size() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert!(!bridge.prefer_micromips);
bridge.prefer_micromips = true;
assert!(bridge.prefer_micromips);
}
#[test]
fn test_bridge_cost_weights_strong_mips_bias() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.cost_weights.target_bias = -10.0;
let mut mips_wins = 0;
for oc in &[8u32, 9, 10, 12, 14, 23, 44] {
if bridge.select_target_for_opcode(*oc) {
mips_wins += 1;
}
}
assert_eq!(mips_wins, 7);
}
#[test]
fn test_bridge_cost_weights_strong_x86_bias() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.cost_weights.target_bias = 10.0;
let mut x86_wins = 0;
for oc in &[8u32, 9, 10, 12, 14, 23, 44] {
if !bridge.select_target_for_opcode(*oc) {
x86_wins += 1;
}
}
assert_eq!(x86_wins, 7);
}
#[test]
fn test_bridge_configure_abi_win64_shadow_space() {
let mut bridge = MIPSX86Bridge::new("x86_64-pc-windows-msvc");
bridge.x86_cc = X86CallingConventionVariant::Win64;
bridge.configure_abi();
assert_eq!(bridge.call_conv.shadow_store, 32);
assert_eq!(bridge.call_conv.x86_int_args.len(), 4);
}
#[test]
fn test_bridge_configure_abi_fastcall32() {
let mut bridge = MIPSX86Bridge::new("i686-pc-windows-msvc");
bridge.x86_cc = X86CallingConventionVariant::FastCall32;
bridge.configure_abi();
assert_eq!(bridge.call_conv.x86_int_args.len(), 2);
assert!(bridge.call_conv.callee_cleans_stack);
}
#[test]
fn test_calling_conv_o32_vs_n64_arg_count() {
let o32 = MIPSCallingConvention::new(MipsAbi::O32);
let n64 = MIPSCallingConvention::new(MipsAbi::N64);
assert_eq!(o32.int_arg_regs.len(), 4);
assert_eq!(n64.int_arg_regs.len(), 8);
assert_eq!(o32.fp_arg_regs.len(), 2);
assert_eq!(n64.fp_arg_regs.len(), 8);
}
#[test]
fn test_calling_conv_remaining_count_after_assign() {
let mut cc = MIPSCallingConvention::new(MipsAbi::N64);
assert_eq!(cc.remaining_int_args, 8);
cc.assign_int_arg(8);
assert_eq!(cc.remaining_int_args, 7);
cc.assign_int_arg(8);
assert_eq!(cc.remaining_int_args, 6);
cc.assign_int_arg(8);
assert_eq!(cc.remaining_int_args, 5);
}
#[test]
fn test_calling_conv_large_arg_needs_multiple_slots() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
let (in_reg, reg, _) = cc.assign_int_arg(16);
assert!(in_reg);
assert_eq!(reg, 4004);
assert_eq!(cc.remaining_int_args, 0); }
#[test]
fn test_calling_conv_64bit_arg_on_n64() {
let mut cc = MIPSCallingConvention::new(MipsAbi::N64);
let (in_reg, reg, _) = cc.assign_int_arg(8);
assert!(in_reg);
assert_eq!(reg, 4004);
assert_eq!(cc.remaining_int_args, 7);
}
#[test]
fn test_calling_conv_reset_clears_varargs() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
cc.is_varargs = true;
cc.is_sret = true;
cc.assign_int_arg(4);
cc.reset();
assert!(!cc.is_varargs);
assert!(!cc.is_sret);
assert_eq!(cc.remaining_int_args, 4);
}
#[test]
fn test_frame_layout_all_fields_set() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: true,
max_call_frame_size: 64,
callee_saved_count: 4,
local_area_size: 128,
is_leaf: false,
};
let layout = fl.calculate_frame();
assert!(layout.frame_size > 0);
assert!(layout.has_frame_pointer);
assert!(layout.ra_save_offset <= 0);
assert!(layout.fp_save_offset <= 0);
assert!(layout.callee_saved_offset <= 0);
assert!(layout.locals_offset <= 0);
assert!(layout.outgoing_args_offset <= 0);
assert!(
layout.fp_save_offset < layout.ra_save_offset
|| layout.ra_save_offset == layout.fp_save_offset
);
}
#[test]
fn test_frame_layout_alignment_padding() {
let bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 1,
local_area_size: 1, is_leaf: false,
};
let layout = fl.calculate_frame();
assert_eq!(layout.frame_size % 16, 0);
}
#[test]
fn test_frame_layout_o32_alignment() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 5,
is_leaf: true,
};
let layout = fl.calculate_frame();
assert_eq!(layout.frame_size % 8, 0);
assert_eq!(layout.stack_alignment, 8);
}
#[test]
fn test_frame_prologue_includes_ra_save_when_not_leaf() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: false,
};
let prologue = fl.emit_prologue();
assert!(prologue.contains("$ra"));
}
#[test]
fn test_frame_prologue_skips_ra_when_leaf() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
let prologue = fl.emit_prologue();
assert!(!prologue.contains("$ra"));
}
#[test]
fn test_frame_epilogue_contains_return() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 0,
local_area_size: 0,
is_leaf: true,
};
let epilogue = fl.emit_epilogue();
assert!(epilogue.contains("jr"));
}
#[test]
fn test_frame_lowering_mips64_prologue_uses_doubleword_ops() {
let bridge = MIPSX86Bridge::new("mips64-unknown-linux-gnu");
let fl = MIPSFrameLowering {
bridge,
eliminate_frame_pointer: false,
needs_stack_realignment: false,
has_var_sized_alloca: false,
max_call_frame_size: 0,
callee_saved_count: 3,
local_area_size: 16,
is_leaf: false,
};
let prologue = fl.emit_prologue();
assert!(prologue.contains("sd\t$ra") || prologue.contains("daddiu"));
}
#[test]
fn test_addr_modes_all_fields() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
assert!(ctx.addr_modes.base_plus_offset);
assert!(!ctx.addr_modes.base_plus_index);
assert!(!ctx.addr_modes.base_plus_index_scale);
assert!(ctx.addr_modes.pc_relative);
assert!(ctx.addr_modes.gp_relative);
assert_eq!(ctx.addr_modes.max_offset, 32767);
assert!(!ctx.addr_modes.unaligned_access);
}
#[test]
fn test_branch_lowering_all_fields() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
assert!(ctx.branch_lowering.direct_condition);
assert!(!ctx.branch_lowering.needs_trampolines);
assert!(ctx.branch_lowering.max_branch_displacement > 0);
assert!(!ctx.branch_lowering.has_delay_slot);
assert!(ctx.branch_lowering.predict_forward_not_taken);
}
#[test]
fn test_call_lowering_all_fields() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
assert!(ctx.call_lowering.supports_tail_calls);
assert!(ctx.call_lowering.clobbers_caller_saved);
assert_eq!(ctx.call_lowering.return_addr_regs, 1);
assert!(ctx.call_lowering.indirect_calls_via_reg);
assert!(ctx.call_lowering.max_call_displacement > 0);
}
#[test]
fn test_extended_instr_info_mips64_ops_have_x86_equivs() {
let info = MIPSExtendedInstrInfo::new();
for op in &[MipsOpcode::DADD, MipsOpcode::DSUB, MipsOpcode::LD, MipsOpcode::SD] {
assert!(info.get_x86_equivalent(op).is_some(), "No x86 equiv for {:?}", op);
}
}
#[test]
fn test_extended_instr_info_fpu_single_ops_complete() {
let info = MIPSExtendedInstrInfo::new();
let fpu_ops = [
MipsOpcode::ADD_S, MipsOpcode::SUB_S, MipsOpcode::MUL_S, MipsOpcode::DIV_S,
MipsOpcode::MOV_S, MipsOpcode::NEG_S, MipsOpcode::ABS_S, MipsOpcode::SQRT_S,
];
for op in &fpu_ops {
assert!(info.get_cost(op).is_some(), "No cost for {:?}", op);
assert!(info.get_x86_equivalent(op).is_some(), "No x86 equiv for {:?}", op);
}
}
#[test]
fn test_extended_instr_info_fpu_double_ops_complete() {
let info = MIPSExtendedInstrInfo::new();
let fpu_ops = [
MipsOpcode::ADD_D, MipsOpcode::SUB_D, MipsOpcode::MUL_D, MipsOpcode::DIV_D,
MipsOpcode::MOV_D, MipsOpcode::NEG_D, MipsOpcode::ABS_D, MipsOpcode::SQRT_D,
];
for op in &fpu_ops {
assert!(info.get_cost(op).is_some(), "No cost for {:?}", op);
assert!(info.get_x86_equivalent(op).is_some(), "No x86 equiv for {:?}", op);
}
}
#[test]
fn test_extended_instr_info_msa_shuffle_ops() {
let info = MIPSExtendedInstrInfo::new();
let shuff = [
MipsOpcode::ILVEV_B, MipsOpcode::ILVOD_B, MipsOpcode::ILVL_B, MipsOpcode::ILVR_B,
MipsOpcode::PCKEV_B, MipsOpcode::PCKOD_B,
];
for op in &shuff {
assert!(info.get_cost(op).is_some(), "No cost for {:?}", op);
}
}
#[test]
fn test_extended_instr_info_dsp_dpa_ops() {
let info = MIPSExtendedInstrInfo::new();
let dpa = [MipsOpcode::DPA_W_PH, MipsOpcode::DPS_W_PH];
for op in &dpa {
assert!(info.get_cost(op).is_some(), "No cost for {:?}", op);
assert_eq!(info.get_func_unit(op), MipsFuncUnit::DSP);
}
}
#[test]
fn test_extended_instr_info_mt_mf_thread() {
let info = MIPSExtendedInstrInfo::new();
for op in &[MipsOpcode::DMT, MipsOpcode::EMT, MipsOpcode::YIELD_MT] {
assert!(info.get_cost(op).is_some(), "No cost for {:?}", op);
assert_eq!(info.get_func_unit(op), MipsFuncUnit::System);
}
}
#[test]
fn test_extended_instr_info_umips_ops() {
let info = MIPSExtendedInstrInfo::new();
let _ = info.get_cost(&MipsOpcode::UMM_ADD);
}
#[test]
fn test_comparison_all_entries_have_meaningful_winner() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
for c in &comp.comparisons {
assert!(
c.better == CrossTargetChoice::Mips
|| c.better == CrossTargetChoice::X86
|| c.better == CrossTargetChoice::Tie
|| c.better == CrossTargetChoice::NotComparable,
"Invalid winner for {}", c.name
);
}
}
#[test]
fn test_comparison_notes_non_empty() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
for c in &comp.comparisons {
assert!(!c.notes.is_empty(), "No notes for {}", c.name);
}
}
#[test]
fn test_comparison_zero_init_has_notes() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let zero = comp.comparisons.iter().find(|c| c.name == "Zero register").unwrap();
assert!(!zero.notes.is_empty());
}
#[test]
fn test_comparison_vector_add_tie() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let vec_add = comp.comparisons.iter().find(|c| c.name.contains("Vector Add")).unwrap();
assert_eq!(vec_add.better, CrossTargetChoice::Tie);
}
#[test]
fn test_comparison_function_call_x86_better() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let call = comp.comparisons.iter().find(|c| c.name.contains("Function Call")).unwrap();
assert_eq!(call.better, CrossTargetChoice::X86);
}
#[test]
fn test_comparison_function_return_x86_better() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
let ret = comp.comparisons.iter().find(|c| c.name.contains("Function Return")).unwrap();
assert_eq!(ret.better, CrossTargetChoice::X86);
assert_eq!(ret.x86_size, 1);
}
#[test]
fn test_mips_target_machine_default_big_endian() {
let tm = MIPSTargetMachine::new("mips-unknown-linux-gnu");
assert!(!tm.is_little_endian);
}
#[test]
fn test_mips_target_machine_little_endian_mipsel() {
let tm = MIPSTargetMachine::new("mipsel-unknown-linux-gnu");
assert!(tm.is_little_endian);
}
#[test]
fn test_mips_target_machine_little_endian_mips64el() {
let tm = MIPSTargetMachine::new("mips64el-unknown-linux-gnu");
assert!(tm.is_little_endian);
}
#[test]
fn test_mips_target_machine_instr_info_accessible() {
let tm = MIPSTargetMachine::new("mips-unknown-linux-gnu");
let info = tm.instr_info();
let desc = info.get_desc(MipsOpcode::ADD);
assert_eq!(desc.opcode, MipsOpcode::ADD);
}
#[test]
fn test_mips_target_machine_reg_info_accessible() {
let tm = MIPSTargetMachine::new("mips-unknown-linux-gnu");
let ri = tm.reg_info();
let name = ri.get_asm_name(4004);
assert_eq!(name, "$a0");
}
#[test]
fn test_const_materializer_small_imm_fits_single_insn() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.best_const_pattern(42, 32).unwrap();
assert_eq!(pat.mips_cost, 1);
assert_eq!(pat.x86_cost, 1);
}
#[test]
fn test_const_materializer_large_imm_needs_two_insns() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.best_const_pattern(100000, 32).unwrap();
assert_eq!(pat.mips_cost, 2);
assert_eq!(pat.x86_cost, 1);
}
#[test]
fn test_const_materializer_64bit_huge_needs_six_insns() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.best_const_pattern(0x123456789ABCDEF0i64, 64).unwrap();
assert_eq!(pat.mips_cost, 6);
assert_eq!(pat.x86_cost, 2);
}
#[test]
fn test_const_materializer_negative_imm() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat = ctx.best_const_pattern(-1, 32).unwrap();
assert_eq!(pat.mips_cost, 1);
}
#[test]
fn test_mips_fpr_to_xmm16_wraps() {
assert_eq!(mips_fpr_to_x86(4066), Some("xmm0")); assert_eq!(mips_fpr_to_x86(4081), Some("xmm15")); }
#[test]
fn test_x86_reg_variants_map_correctly() {
assert_eq!(x86_gpr_to_mips("eax"), Some(4002));
assert_eq!(x86_gpr_to_mips("rax"), Some(4002));
}
#[test]
fn test_bridge_register_maps_initialized() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert!(!bridge.reg_map_mips_to_x86.is_empty());
assert!(!bridge.reg_map_x86_to_mips.is_empty());
assert!(bridge.reg_map_mips_to_x86.len() >= 16);
assert!(bridge.reg_map_x86_to_mips.len() >= 16);
}
#[test]
fn test_cost_summary_display_has_all_fields() {
let s = CostSummary {
total_entries: 50,
mips_wins: 15,
x86_wins: 25,
ties: 10,
mips_unsupported: 0,
x86_unsupported: 0,
total_mips_latency: 150,
total_x86_latency: 200,
avg_mips_latency: 3.0,
avg_x86_latency: 4.0,
total_mips_size: 200,
total_x86_size: 250,
};
let disp = s.to_string();
assert!(disp.contains("Entries"));
assert!(disp.contains("MIPS wins"));
assert!(disp.contains("X86 wins"));
assert!(disp.contains("Ties"));
assert!(disp.contains("Avg MIPS latency"));
assert!(disp.contains("Avg X86 latency"));
assert!(disp.contains("MIPS code size"));
assert!(disp.contains("X86 code size"));
}
#[test]
fn test_cross_target_choice_display_all() {
assert_eq!(CrossTargetChoice::Mips.to_string(), "MIPS");
assert_eq!(CrossTargetChoice::X86.to_string(), "X86");
assert_eq!(CrossTargetChoice::Tie.to_string(), "Tie");
assert_eq!(CrossTargetChoice::NotComparable.to_string(), "N/A");
}
#[test]
fn test_x86_cc_display_all_variants() {
let all = [
(X86CallingConventionVariant::SysV64, "sysv64"),
(X86CallingConventionVariant::Win64, "win64"),
(X86CallingConventionVariant::CDecl32, "cdecl32"),
(X86CallingConventionVariant::StdCall32, "stdcall32"),
(X86CallingConventionVariant::FastCall32, "fastcall32"),
];
for (cc, expected) in &all {
assert_eq!(cc.to_string(), *expected);
}
}
#[test]
fn test_pipeline_compile_empty_function() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let (mips, x86) = bridge.generate_cross_target(&[]);
assert!(mips.lines().count() >= 1);
assert!(x86.lines().count() >= 2); }
#[test]
fn test_pipeline_single_add_instr() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let ir = vec![CrossTargetIRInst {
opcode: 8,
dest: Some(1),
srcs: vec![2, 3],
immediate: None,
type_code: 0,
}];
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(mips.contains("addu"));
assert!(x86.contains("add"));
}
#[test]
fn test_pipeline_arithmetic_chain() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let mut ir = Vec::new();
for i in 0..10 {
ir.push(CrossTargetIRInst {
opcode: if i % 3 == 0 { 8 } else if i % 3 == 1 { 10 } else { 23 },
dest: Some(i * 2 + 1),
srcs: vec![i * 2, i * 2 + 1],
immediate: None,
type_code: 0,
});
}
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(mips.lines().count() > 10);
assert!(x86.lines().count() > 10);
}
#[test]
fn test_pipeline_mixed_int_fp() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let ir = vec![
CrossTargetIRInst { opcode: 8, dest: Some(1), srcs: vec![2, 3], immediate: None, type_code: 0 },
CrossTargetIRInst { opcode: 9, dest: Some(4), srcs: vec![5, 6], immediate: None, type_code: 2 },
CrossTargetIRInst { opcode: 23, dest: Some(7), srcs: vec![1, 8], immediate: None, type_code: 0 },
];
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(mips.contains("addu"));
assert!(mips.contains("add.s"));
assert!(mips.contains("and"));
}
#[test]
fn test_pipeline_load_store_pair() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let ir = vec![
CrossTargetIRInst { opcode: 27, dest: Some(1), srcs: vec![2], immediate: Some(0), type_code: 0 },
CrossTargetIRInst { opcode: 28, dest: None, srcs: vec![1, 3], immediate: Some(4), type_code: 0 },
];
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(mips.contains("lw"));
assert!(mips.contains("sw"));
assert!(x86.contains("mov"));
}
#[test]
fn test_msa_vector_add_pipeline() {
let mut bridge = MIPSX86Bridge::with_features("mips-unknown-linux-gnu", &["msa"]);
bridge.calculate_frame(0, 0, false, false);
assert!(bridge.has_msa);
let ir = vec![CrossTargetIRInst {
opcode: 8,
dest: Some(1),
srcs: vec![2, 3],
immediate: None,
type_code: 0,
}];
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(!mips.is_empty());
}
#[test]
fn test_msa_extended_cost_data() {
let info = MIPSExtendedInstrInfo::new();
let msa_arith = [MipsOpcode::ADDV_B, MipsOpcode::SUBV_B, MipsOpcode::ANDV, MipsOpcode::ORV];
for op in &msa_arith {
let cost = info.get_cost(op).unwrap();
assert_eq!(cost.latency, 1, "Wrong latency for {:?}", op);
}
}
#[test]
fn test_msa_fp_vector_latency() {
let info = MIPSExtendedInstrInfo::new();
let add_cost = info.get_cost(&MipsOpcode::FADD_W).unwrap();
assert_eq!(add_cost.latency, 4);
let div_cost = info.get_cost(&MipsOpcode::FDIV_W).unwrap();
assert_eq!(div_cost.latency, 12);
}
#[test]
fn test_reg_roundtrip_gprs() {
for mips_reg in MIPS_GPR_BASE..MIPS_GPR_BASE + 32 {
if let Some(x86_name) = mips_gpr_to_x86(mips_reg) {
if let Some(back_reg) = x86_gpr_to_mips(x86_name) {
assert!(back_reg >= MIPS_GPR_BASE && back_reg < MIPS_GPR_BASE + 32);
}
}
}
}
#[test]
fn test_reg_fpr_xmm_consistency() {
let mut seen = std::collections::HashSet::new();
for i in 0..16 {
let fpr = MIPS_FPR_BASE + i;
if let Some(xmm) = mips_fpr_to_x86(fpr) {
assert!(seen.insert(xmm), "Duplicate XMM mapping: {} for fpr {}", xmm, fpr);
}
}
}
#[test]
fn test_bridge_unknown_triple_defaults_to_mips() {
let bridge = MIPSX86Bridge::new("sparc-unknown-none");
assert!(!bridge.is_mips_primary);
}
#[test]
fn test_calling_conv_assign_zero_size_arg() {
let mut cc = MIPSCallingConvention::new(MipsAbi::O32);
let (in_reg, _, _) = cc.assign_int_arg(0);
assert!(in_reg); }
#[test]
fn test_constr_materializer_boundary_values() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let ctx = CrossTargetMIPS::new(bridge);
let pat_min = ctx.best_const_pattern(i16::MIN as i64, 32);
assert!(pat_min.is_some());
let pat_max = ctx.best_const_pattern(i16::MAX as i64, 32);
assert!(pat_max.is_some());
let pat_overflow = ctx.best_const_pattern(32768i64, 32);
assert!(pat_overflow.is_some());
assert_eq!(pat_overflow.unwrap().mips_cost, 2);
}
#[test]
fn test_get_or_build_pattern_out_of_range_opcode() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let pat = bridge.get_or_build_pattern(999);
assert_eq!(pat.opcode, 999);
assert_eq!(pat.mips_sequence[0].opcode, "nop");
assert_eq!(pat.x86_sequence[0].opcode, "nop");
assert_eq!(pat.mips_cost, 0);
assert_eq!(pat.x86_cost, 0);
}
#[test]
fn test_compare_opcode_cost_unsupported() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let winner = bridge.compare_opcode_cost(999);
assert!(matches!(winner, CostWinner::MipsUnsupported | CostWinner::X86Unsupported));
}
#[test]
fn test_stress_large_frame_detailed() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(65536, 10, true, true);
let prologue = bridge.emit_mips_prologue();
let epilogue = bridge.emit_mips_epilogue();
assert!(prologue.len() > 50);
assert!(epilogue.len() > 20);
}
#[test]
fn test_stress_many_pattern_lookups() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
for _ in 0..100 {
let p1 = bridge.get_or_build_pattern(8);
let p2 = bridge.get_or_build_pattern(8);
assert_eq!(p1.mips_cost, p2.mips_cost);
assert_eq!(p1.x86_cost, p2.x86_cost);
}
}
#[test]
fn test_stress_random_opcode_generation() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let ir: Vec<CrossTargetIRInst> = (0..50).map(|i| CrossTargetIRInst {
opcode: (8 + (i % 40)) as u32,
dest: if i % 3 != 0 { Some(i as u32) } else { None },
srcs: vec![i as u32 + 1, i as u32 + 2],
immediate: if i % 4 == 0 { Some(i as i64) } else { None },
type_code: (i % 4) as u8,
}).collect();
let (mips, x86) = bridge.generate_cross_target(&ir);
assert!(mips.lines().count() > 30);
assert!(x86.lines().count() > 20);
}
#[test]
fn test_stress_exhaustive_full_comparison() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let r1 = bridge.run_full_comparison();
let r2 = bridge.run_full_comparison();
assert_eq!(r1.len(), r2.len());
for (a, b) in r1.iter().zip(r2.iter()) {
assert_eq!(a.ir_opcode, b.ir_opcode);
assert_eq!(a.winner, b.winner);
}
}
#[test]
fn test_mips_codegen_produces_valid_instructions() {
let mut bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let ir = vec![CrossTargetIRInst {
opcode: 8,
dest: Some(1),
srcs: vec![2, 3],
immediate: None,
type_code: 0,
}];
let (mips, _) = bridge.generate_cross_target(&ir);
for line in mips.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with(';') {
continue;
}
assert!(
trimmed.starts_with('\t') || trimmed.starts_with('.') || trimmed.ends_with(':'),
"Invalid asm line: {}", trimmed
);
}
}
#[test]
fn test_x86_codegen_produces_valid_instructions() {
let mut bridge = MIPSX86Bridge::new("x86_64-unknown-linux-gnu");
bridge.calculate_frame(0, 0, false, false);
let ir = vec![CrossTargetIRInst {
opcode: 8,
dest: Some(1),
srcs: vec![2, 3],
immediate: None,
type_code: 0,
}];
let (_, x86) = bridge.generate_cross_target(&ir);
for line in x86.lines() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
assert!(
trimmed.starts_with('\t') || trimmed.starts_with('.') || trimmed.ends_with(':'),
"Invalid x86 asm line: {}", trimmed
);
}
}
#[test]
fn test_o32_n32_n64_all_different_frame_alignment() {
assert_eq!(MipsAbi::O32.stack_alignment(), 8);
assert_eq!(MipsAbi::N32.stack_alignment(), 16);
assert_eq!(MipsAbi::N64.stack_alignment(), 16);
}
#[test]
fn test_all_abis_construct_valid_calling_convention() {
for abi in &[MipsAbi::O32, MipsAbi::N32, MipsAbi::N64] {
let cc = MIPSCallingConvention::new(*abi);
assert!(!cc.int_arg_regs.is_empty());
assert!(!cc.fp_arg_regs.is_empty());
assert!(!cc.int_ret_regs.is_empty());
assert!(!cc.fp_ret_regs.is_empty());
}
}
#[test]
fn test_all_abis_have_unique_regs_in_arg_lists() {
for abi in &[MipsAbi::O32, MipsAbi::N32, MipsAbi::N64] {
let cc = MIPSCallingConvention::new(*abi);
let mut seen = std::collections::HashSet::new();
for reg in &cc.int_arg_regs {
assert!(seen.insert(reg), "Duplicate int arg reg {} in {:?}", reg, abi);
}
let mut seen_fp = std::collections::HashSet::new();
for reg in &cc.fp_arg_regs {
assert!(seen_fp.insert(reg), "Duplicate fp arg reg {} in {:?}", reg, abi);
}
}
}
#[test]
fn test_target_machine_bridge_consistency() {
let tm = MIPSTargetMachine::new("mips-unknown-linux-gnu");
assert_eq!(tm.bridge.mips_abi, MipsAbi::O32);
assert!(!tm.bridge.is_64bit);
let tm64 = MIPSTargetMachine::new("mips64-unknown-linux-gnu");
assert_eq!(tm64.bridge.mips_abi, MipsAbi::N64);
assert!(tm64.bridge.is_64bit);
}
#[test]
fn test_target_machine_features_reflect_in_bridge() {
let tm = MIPSTargetMachine::with_features("mips-unknown-linux-gnu", &["msa", "dsp", "micromips"]);
assert!(tm.bridge.has_msa);
assert!(tm.bridge.has_dsp);
assert!(tm.bridge.prefer_micromips);
}
#[test]
fn test_pattern_with_empty_sequences_handled() {
let pattern = SharedPattern {
opcode: 0,
mips_sequence: vec![],
x86_sequence: vec![],
is_identical: true,
mips_cost: 0,
x86_cost: 0,
};
assert!(pattern.mips_sequence.is_empty());
assert!(pattern.x86_sequence.is_empty());
assert_eq!(pattern.opcode, 0);
}
#[test]
fn test_operand_default_values() {
let op = PatternOperand {
kind: PatternOperandKind::Immediate,
vreg: None,
immediate: Some(42),
reg_name: None,
};
assert_eq!(op.immediate, Some(42));
assert_eq!(op.vreg, None);
assert_eq!(op.reg_name, None);
}
#[test]
fn test_spill_slot_default_values() {
let slot = SpillSlot {
offset: 0,
size: 0,
alignment: 0,
is_callee_saved: false,
mips_reg: None,
x86_reg: None,
};
assert_eq!(slot.offset, 0);
assert!(!slot.is_callee_saved);
}
#[test]
fn test_bridge_is_cloneable() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let cloned = bridge.clone();
assert_eq!(bridge.triple, cloned.triple);
assert_eq!(bridge.mips_abi, cloned.mips_abi);
assert_eq!(bridge.is_mips_primary, cloned.is_mips_primary);
}
#[test]
fn test_microarch_costs_defaults_reasonable() {
let mc = MicroarchitectureCosts::default();
assert!(!mc.mips_microarch.is_empty());
assert!(!mc.x86_microarch.is_empty());
assert!(mc.mips_cycles.len() > 20);
assert!(mc.x86_cycles.len() > 20);
}
#[test]
fn test_comparison_aggregate_has_nonzero_averages() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
let comp = X86MIPSComparisons::new(bridge);
assert!(comp.aggregate.avg_mips_cycles > 0.0);
assert!(comp.aggregate.avg_x86_cycles > 0.0);
assert!(comp.aggregate.avg_mips_size > 0.0);
assert!(comp.aggregate.avg_x86_size > 0.0);
}
#[test]
fn test_total_test_count_is_meaningful() {
let bridge = MIPSX86Bridge::new("mips-unknown-linux-gnu");
assert!(!bridge.triple.is_empty());
}
}