use crate::arm::arm_calling_convention::ArmCallingConvention;
use crate::arm::arm_frame_lowering::{ArmFrameInfo, ArmFrameLowering};
use crate::arm::arm_instr_info::{ArmInstrDesc, ArmInstrInfo, ArmOpcode, ArmOperandType};
use crate::arm::arm_register_info::{
ArmRegClass, ArmRegisterInfo, AARCH64_REG_COUNT, ARM_MAX_REG_ID,
};
use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, VirtReg};
use crate::opcode::Opcode;
use crate::target_machine::{CodeGenOptLevel, CodeModel, RelocModel, TargetMachine, TargetOptions};
use crate::triple::{Arch, Triple};
use crate::types::Type;
use crate::value::ValueRef;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt;
pub const AARCH64_DATA_LAYOUT: &str = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
pub const AARCH64BE_DATA_LAYOUT: &str = "E-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
pub const X86_64_DATA_LAYOUT: &str =
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128";
pub const AARCH64_ABI_NAMES: &[&str] = &["aapcs", "aapcs-soft", "darwinpcs"];
pub const X86_ABI_NAMES: &[&str] = &["sysv", "win64", "msvc"];
pub const AARCH64_GPR_COUNT: usize = 31;
pub const AARCH64_FPR_COUNT: usize = 32;
pub const AARCH64_STACK_ALIGNMENT: u32 = 16;
pub const AARCH64_RED_ZONE_DARWIN: u32 = 128;
pub const AARCH64_PAGE_SIZE: u64 = 4096;
pub const AARCH64_INSTR_SIZE: u32 = 4;
pub const AARCH64_MAX_IMM: u64 = 4095;
pub const AARCH64_MAX_LDST_OFFSET: i64 = 32760;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BridgeArch {
X86_64,
X86_32,
AArch64,
ARM32,
}
impl fmt::Display for BridgeArch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BridgeArch::X86_64 => write!(f, "x86_64"),
BridgeArch::X86_32 => write!(f, "i386"),
BridgeArch::AArch64 => write!(f, "aarch64"),
BridgeArch::ARM32 => write!(f, "arm"),
}
}
}
impl BridgeArch {
pub fn is_64bit(&self) -> bool {
matches!(self, BridgeArch::X86_64 | BridgeArch::AArch64)
}
pub fn is_arm_family(&self) -> bool {
matches!(self, BridgeArch::AArch64 | BridgeArch::ARM32)
}
pub fn is_x86_family(&self) -> bool {
matches!(self, BridgeArch::X86_64 | BridgeArch::X86_32)
}
pub fn pointer_width(&self) -> u32 {
match self {
BridgeArch::X86_64 | BridgeArch::AArch64 => 64,
BridgeArch::X86_32 | BridgeArch::ARM32 => 32,
}
}
pub fn data_layout(&self) -> &'static str {
match self {
BridgeArch::AArch64 | BridgeArch::ARM32 => AARCH64_DATA_LAYOUT,
BridgeArch::X86_64 | BridgeArch::X86_32 => X86_64_DATA_LAYOUT,
}
}
}
pub struct X86AArch64Bridge {
pub source_arch: BridgeArch,
pub target_arch: BridgeArch,
pub isel: CrossTargetISel,
pub regalloc: CrossTargetRegAlloc,
pub frame_lowering: CrossTargetFrameLowering,
pub cost_model: X86AArch64CostModel,
pub abi: CrossTargetABI,
pub optimizer: CrossTargetOptimization,
pub opt_level: CodeGenOptLevel,
pub debug_info: bool,
pub features: BridgeFeatures,
pub stats: BridgeStats,
}
impl X86AArch64Bridge {
pub fn new(source_arch: BridgeArch, target_arch: BridgeArch) -> Self {
let isel = CrossTargetISel::new(target_arch);
let regalloc = CrossTargetRegAlloc::new(target_arch);
let frame_lowering = CrossTargetFrameLowering::new(target_arch);
let cost_model = X86AArch64CostModel::new(source_arch, target_arch);
let abi = CrossTargetABI::new(target_arch);
let optimizer = CrossTargetOptimization::new(target_arch);
Self {
source_arch,
target_arch,
isel,
regalloc,
frame_lowering,
cost_model,
abi,
optimizer,
opt_level: CodeGenOptLevel::Default,
debug_info: false,
features: BridgeFeatures::default(),
stats: BridgeStats::default(),
}
}
pub fn from_triples(source: &str, target: &str) -> Self {
let src = Self::parse_arch(source);
let tgt = Self::parse_arch(target);
Self::new(src, tgt)
}
fn parse_arch(triple_str: &str) -> BridgeArch {
let t = Triple::parse(triple_str);
match t.arch {
Arch::X86_64 => BridgeArch::X86_64,
Arch::X86 => BridgeArch::X86_32,
Arch::AArch64 => BridgeArch::AArch64,
Arch::ARM | Arch::Thumb => BridgeArch::ARM32,
_ => BridgeArch::AArch64, }
}
pub fn run_pipeline(&mut self, mf: &mut MachineFunction) -> Result<BridgeOutput, BridgeError> {
self.stats.functions_processed += 1;
self.isel.select_instructions(mf)?;
self.stats.isel_cycles += 1;
if self.opt_level.should_optimize() {
self.optimizer.optimize(mf)?;
self.stats.opt_cycles += 1;
}
self.regalloc.allocate_registers(mf)?;
self.stats.ra_cycles += 1;
self.frame_lowering.lower_frame(mf)?;
self.stats.frame_cycles += 1;
if self.opt_level.should_optimize() {
self.optimizer.post_ra_optimize(mf)?;
self.stats.post_ra_opt_cycles += 1;
}
Ok(BridgeOutput {
instructions_emitted: mf.blocks.iter().map(|bb| bb.instructions.len()).sum(),
basic_blocks: mf.blocks.len(),
target_arch: self.target_arch,
})
}
pub fn estimate_cost(&self, opcode: Opcode, operand_count: usize) -> CostEstimate {
self.cost_model.estimate(opcode, operand_count)
}
pub fn describe(&self) -> String {
format!(
"X86AArch64Bridge: {} → {} (opt={:?}, features={:?})",
self.source_arch, self.target_arch, self.opt_level, self.features
)
}
pub fn set_opt_level(&mut self, level: CodeGenOptLevel) {
self.opt_level = level;
}
pub fn enable_debug_info(&mut self) {
self.debug_info = true;
}
pub fn enable_feature(&mut self, feature: BridgeFeature) {
self.features.enable(feature);
}
pub fn has_feature(&self, feature: BridgeFeature) -> bool {
self.features.has(feature)
}
}
impl Default for X86AArch64Bridge {
fn default() -> Self {
Self::new(BridgeArch::X86_64, BridgeArch::AArch64)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BridgeFeature {
Neon,
V8A,
V81A,
V82A,
V83A,
V84A,
V85A,
V86A,
SVE,
SVE2,
SSE,
SSE2,
AVX,
AVX2,
AVX512,
FMA,
CRC,
Crypto,
LSE,
RCPC,
PGO,
}
impl fmt::Display for BridgeFeature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
BridgeFeature::Neon => "neon",
BridgeFeature::V8A => "v8a",
BridgeFeature::V81A => "v8.1a",
BridgeFeature::V82A => "v8.2a",
BridgeFeature::V83A => "v8.3a",
BridgeFeature::V84A => "v8.4a",
BridgeFeature::V85A => "v8.5a",
BridgeFeature::V86A => "v8.6a",
BridgeFeature::SVE => "sve",
BridgeFeature::SVE2 => "sve2",
BridgeFeature::SSE => "sse",
BridgeFeature::SSE2 => "sse2",
BridgeFeature::AVX => "avx",
BridgeFeature::AVX2 => "avx2",
BridgeFeature::AVX512 => "avx512",
BridgeFeature::FMA => "fma",
BridgeFeature::CRC => "crc",
BridgeFeature::Crypto => "crypto",
BridgeFeature::LSE => "lse",
BridgeFeature::RCPC => "rcpc",
BridgeFeature::PGO => "pgo",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Default)]
pub struct BridgeFeatures {
flags: HashSet<BridgeFeature>,
}
impl BridgeFeatures {
pub fn enable(&mut self, feature: BridgeFeature) {
self.flags.insert(feature);
}
pub fn disable(&mut self, feature: BridgeFeature) {
self.flags.remove(&feature);
}
pub fn has(&self, feature: BridgeFeature) -> bool {
self.flags.contains(&feature)
}
pub fn enable_armv8a(&mut self) {
self.flags.insert(BridgeFeature::V8A);
self.flags.insert(BridgeFeature::Neon);
self.flags.insert(BridgeFeature::FMA);
self.flags.insert(BridgeFeature::CRC);
self.flags.insert(BridgeFeature::Crypto);
}
pub fn enable_armv81a(&mut self) {
self.enable_armv8a();
self.flags.insert(BridgeFeature::V81A);
self.flags.insert(BridgeFeature::LSE);
self.flags.insert(BridgeFeature::RCPC);
}
pub fn enable_armv82a(&mut self) {
self.enable_armv81a();
self.flags.insert(BridgeFeature::V82A);
}
pub fn enable_armv83a(&mut self) {
self.enable_armv82a();
self.flags.insert(BridgeFeature::V83A);
}
pub fn enable_armv84a(&mut self) {
self.enable_armv83a();
self.flags.insert(BridgeFeature::V84A);
}
pub fn enable_armv85a(&mut self) {
self.enable_armv84a();
self.flags.insert(BridgeFeature::V85A);
}
pub fn enable_armv86a(&mut self) {
self.enable_armv85a();
self.flags.insert(BridgeFeature::V86A);
}
pub fn enable_sve(&mut self) {
self.flags.insert(BridgeFeature::SVE);
}
pub fn enable_sve2(&mut self) {
self.flags.insert(BridgeFeature::SVE);
self.flags.insert(BridgeFeature::SVE2);
}
pub fn list_enabled(&self) -> Vec<BridgeFeature> {
let mut v: Vec<_> = self.flags.iter().copied().collect();
v.sort_by_key(|f| format!("{:?}", f));
v
}
pub fn to_feature_string(&self) -> String {
let mut feats: Vec<String> = self.flags.iter().map(|f| format!("+{}", f)).collect();
feats.sort();
feats.join(",")
}
pub fn from_string(s: &str) -> Self {
let mut features = Self::default();
for part in s.split(',') {
let part = part.trim();
if part.is_empty() {
continue;
}
let (enabled, name) = if let Some(stripped) = part.strip_prefix('+') {
(true, stripped)
} else if let Some(stripped) = part.strip_prefix('-') {
(false, stripped)
} else {
(true, part)
};
if let Some(feat) = Self::parse_feature(name) {
if enabled {
features.enable(feat);
} else {
features.disable(feat);
}
}
}
features
}
fn parse_feature(name: &str) -> Option<BridgeFeature> {
match name {
"neon" => Some(BridgeFeature::Neon),
"v8a" | "v8" => Some(BridgeFeature::V8A),
"v8.1a" | "v8.1" => Some(BridgeFeature::V81A),
"v8.2a" | "v8.2" => Some(BridgeFeature::V82A),
"v8.3a" | "v8.3" => Some(BridgeFeature::V83A),
"v8.4a" | "v8.4" => Some(BridgeFeature::V84A),
"v8.5a" | "v8.5" => Some(BridgeFeature::V85A),
"v8.6a" | "v8.6" => Some(BridgeFeature::V86A),
"sve" => Some(BridgeFeature::SVE),
"sve2" => Some(BridgeFeature::SVE2),
"sse" => Some(BridgeFeature::SSE),
"sse2" => Some(BridgeFeature::SSE2),
"avx" => Some(BridgeFeature::AVX),
"avx2" => Some(BridgeFeature::AVX2),
"avx512" | "avx512f" => Some(BridgeFeature::AVX512),
"fma" => Some(BridgeFeature::FMA),
"crc" => Some(BridgeFeature::CRC),
"crypto" => Some(BridgeFeature::Crypto),
"lse" => Some(BridgeFeature::LSE),
"rcpc" => Some(BridgeFeature::RCPC),
"pgo" => Some(BridgeFeature::PGO),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BridgeStats {
pub functions_processed: usize,
pub isel_cycles: usize,
pub opt_cycles: usize,
pub ra_cycles: usize,
pub frame_cycles: usize,
pub post_ra_opt_cycles: usize,
pub instructions_eliminated: usize,
pub pattern_matches: usize,
pub spill_slots: usize,
pub abi_conversions: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BridgeError {
UnsupportedArchPair {
source: BridgeArch,
target: BridgeArch,
},
ISelFailed { opcode: u32, reason: String },
RegAllocFailed { reason: String },
FrameLoweringFailed { reason: String },
Unimplemented { feature: String },
Internal(String),
}
impl fmt::Display for BridgeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BridgeError::UnsupportedArchPair { source, target } => {
write!(f, "unsupported architecture pair: {} → {}", source, target)
}
BridgeError::ISelFailed { opcode, reason } => {
write!(
f,
"instruction selection failed for {:?}: {}",
opcode, reason
)
}
BridgeError::RegAllocFailed { reason } => {
write!(f, "register allocation failed: {}", reason)
}
BridgeError::FrameLoweringFailed { reason } => {
write!(f, "frame lowering failed: {}", reason)
}
BridgeError::Unimplemented { feature } => {
write!(f, "unimplemented feature: {}", feature)
}
BridgeError::Internal(msg) => write!(f, "internal bridge error: {}", msg),
}
}
}
#[derive(Debug, Clone)]
pub struct BridgeOutput {
pub instructions_emitted: usize,
pub basic_blocks: usize,
pub target_arch: BridgeArch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CostEstimate {
pub latency: u32,
pub throughput: u32,
pub code_size: u32,
pub vectorizable: bool,
pub profitable: bool,
}
impl CostEstimate {
pub fn zero() -> Self {
Self {
latency: 0,
throughput: 0,
code_size: 0,
vectorizable: false,
profitable: false,
}
}
pub fn new(latency: u32, throughput: u32, code_size: u32) -> Self {
Self {
latency,
throughput,
code_size,
vectorizable: false,
profitable: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MachineIRInst {
pub opcode: GenericMachineOpcode,
pub dst: Option<VirtReg>,
pub srcs: Vec<MachineIROperand>,
pub flags: MachineIRFlags,
}
impl MachineIRInst {
pub fn new(opcode: GenericMachineOpcode) -> Self {
Self {
opcode,
dst: None,
srcs: Vec::new(),
flags: MachineIRFlags::default(),
}
}
pub fn with_dst(mut self, dst: VirtReg) -> Self {
self.def = Some(dst);
self
}
pub fn with_src(mut self, src: MachineIROperand) -> Self {
self.operands.push(src);
self
}
pub fn with_flags(mut self, flags: MachineIRFlags) -> Self {
self.flags = flags;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MachineIROperand {
VReg(VirtReg),
Imm(i64),
FImm(f64),
Block(usize),
Global(String),
FrameIndex(i32),
External(String),
Cond(MachineIRCond),
}
impl MachineIROperand {
pub fn is_reg(&self) -> bool {
matches!(self, MachineIROperand::VReg(_))
}
pub fn is_imm(&self) -> bool {
matches!(self, MachineIROperand::Imm(_) | MachineIROperand::FImm(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GenericMachineOpcode {
Copy,
Load,
Store,
Add,
Sub,
Mul,
SDiv,
UDiv,
And,
Or,
Xor,
Shl,
LShr,
AShr,
ICmp,
FCmp,
Br,
BrCond,
Call,
Ret,
FAdd,
FSub,
FMul,
FDiv,
SExt,
ZExt,
Trunc,
FpToSI,
FpToUI,
SIToFp,
UIToFp,
Select,
FrameSetup,
FrameDestroy,
StackSave,
StackRestore,
InsertElt,
ExtractElt,
Phi,
Debug,
Nop,
TargetSpecific(u32),
}
impl GenericMachineOpcode {
pub fn is_terminator(&self) -> bool {
matches!(
self,
GenericMachineOpcode::Br | GenericMachineOpcode::Br | GenericMachineOpcode::Ret
)
}
pub fn is_memory(&self) -> bool {
matches!(
self,
GenericMachineOpcode::Load
| GenericMachineOpcode::Store
| GenericMachineOpcode::FrameSetup
| GenericMachineOpcode::FrameDestroy
| GenericMachineOpcode::StackSave
| GenericMachineOpcode::StackRestore
)
}
pub fn is_commutative(&self) -> bool {
matches!(
self,
GenericMachineOpcode::Add
| GenericMachineOpcode::Mul
| GenericMachineOpcode::And
| GenericMachineOpcode::Or
| GenericMachineOpcode::Xor
| GenericMachineOpcode::FAdd
| GenericMachineOpcode::FMul
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MachineIRCond {
EQ,
NE,
LT,
LE,
GT,
GE,
LO, LS, HI, HS, MI, PL, VS, VC, }
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct MachineIRFlags {
pub may_trap: bool,
pub may_load: bool,
pub may_store: bool,
pub has_side_effects: bool,
pub is_terminator: bool,
pub is_branch: bool,
pub is_indirect_branch: bool,
pub is_call: bool,
pub is_return: bool,
pub is_barrier: bool,
pub is_convergent: bool,
}
#[derive(Debug, Clone)]
pub struct DAGPattern {
pub name: String,
pub pattern: PatternNode,
pub result: PatternResult,
pub cost: u32,
pub archs: Vec<BridgeArch>,
}
#[derive(Debug, Clone)]
pub enum PatternNode {
Any,
Opcode(GenericMachineOpcode),
IROpcode(Opcode),
Constant(i64),
Immediate { min: i64, max: i64 },
Sequence(Vec<PatternNode>),
Alternative(Vec<PatternNode>),
Predicate {
node: Box<PatternNode>,
pred: PatternPredicate,
},
Capture { slot: usize, node: Box<PatternNode> },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PatternPredicate {
IsPowerOfTwo,
FitsInBits(u32),
IsLogicalImm,
IsArithImm,
IsFloatingPoint,
IsInteger,
IsVector,
}
#[derive(Debug, Clone)]
pub struct PatternResult {
pub opcode: GenericMachineOpcode,
pub operand_mapping: Vec<usize>,
pub flags: Option<MachineIRFlags>,
}
pub struct CrossTargetISel {
pub arch: BridgeArch,
pub vreg_map: HashMap<usize, VirtReg>,
pub patterns: Vec<DAGPattern>,
pub legalize_rules: Vec<LegalizeRule>,
next_vreg: usize,
current_func: Option<String>,
}
impl CrossTargetISel {
pub fn new(arch: BridgeArch) -> Self {
let mut isel = Self {
arch,
vreg_map: HashMap::new(),
patterns: Vec::new(),
legalize_rules: Vec::new(),
next_vreg: 0,
current_func: None,
};
isel.init_default_patterns();
isel.init_legalize_rules();
isel
}
fn init_default_patterns(&mut self) {
self.add_pattern(DAGPattern {
name: "add_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Add),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Immediate {
min: 0,
max: AARCH64_MAX_IMM as i64,
}),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::Add,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "sub_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Sub),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Immediate {
min: 0,
max: AARCH64_MAX_IMM as i64,
}),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::Sub,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "load".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Load),
result: PatternResult {
opcode: GenericMachineOpcode::Load,
operand_mapping: vec![0],
flags: Some(MachineIRFlags {
may_load: true,
..Default::default()
}),
},
cost: 4,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "store".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Store),
result: PatternResult {
opcode: GenericMachineOpcode::Store,
operand_mapping: vec![0, 1],
flags: Some(MachineIRFlags {
may_store: true,
..Default::default()
}),
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "br".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Br),
result: PatternResult {
opcode: GenericMachineOpcode::Br,
operand_mapping: vec![0],
flags: Some(MachineIRFlags {
is_terminator: true,
is_branch: true,
..Default::default()
}),
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "select".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Select),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 2,
node: Box::new(PatternNode::Any),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::Select,
operand_mapping: vec![0, 1, 2],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "zext".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::ZExt),
result: PatternResult {
opcode: GenericMachineOpcode::ZExt,
operand_mapping: vec![0],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "sext".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::SExt),
result: PatternResult {
opcode: GenericMachineOpcode::SExt,
operand_mapping: vec![0],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "fadd".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::FAdd),
result: PatternResult {
opcode: GenericMachineOpcode::FAdd,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 3,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "fmul".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::FMul),
result: PatternResult {
opcode: GenericMachineOpcode::FMul,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 3,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "fma".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::FAdd),
PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::FMul),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Any),
},
]),
PatternNode::Capture {
slot: 2,
node: Box::new(PatternNode::Any),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::TargetSpecific(1), operand_mapping: vec![0, 1, 2],
flags: None,
},
cost: 4,
archs: vec![BridgeArch::AArch64, BridgeArch::X86_64],
});
self.add_pattern(DAGPattern {
name: "mul_by_pow2".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Mul),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Predicate {
node: Box::new(PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Any),
}),
pred: PatternPredicate::IsPowerOfTwo,
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::Shl,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "and_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::And),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Predicate {
node: Box::new(PatternNode::Any),
pred: PatternPredicate::FitsInBits(12),
}),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::And,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "shl_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Shl),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Immediate { min: 0, max: 63 }),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::Shl,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "lshr_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::LShr),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Immediate { min: 0, max: 63 }),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::LShr,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "ashr_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::AShr),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Immediate { min: 0, max: 63 }),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::AShr,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "or_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Or),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Immediate {
min: 0,
max: AARCH64_MAX_IMM as i64,
}),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::Or,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "xor_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Xor),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Immediate {
min: 0,
max: AARCH64_MAX_IMM as i64,
}),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::Xor,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "sub_from_imm".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Sub),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Immediate {
min: 0,
max: AARCH64_MAX_IMM as i64,
}),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Any),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::Sub,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "icmp".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::ICmp),
result: PatternResult {
opcode: GenericMachineOpcode::ICmp,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "fcmp".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::FCmp),
result: PatternResult {
opcode: GenericMachineOpcode::FCmp,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "call".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Call),
result: PatternResult {
opcode: GenericMachineOpcode::Call,
operand_mapping: vec![0],
flags: Some(MachineIRFlags {
is_call: true,
has_side_effects: true,
..Default::default()
}),
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "ret".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Ret),
result: PatternResult {
opcode: GenericMachineOpcode::Ret,
operand_mapping: vec![0],
flags: Some(MachineIRFlags {
is_terminator: true,
is_return: true,
..Default::default()
}),
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "brcond".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Br),
result: PatternResult {
opcode: GenericMachineOpcode::Br,
operand_mapping: vec![0, 1],
flags: Some(MachineIRFlags {
is_terminator: true,
is_branch: true,
..Default::default()
}),
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "phi".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Phi),
result: PatternResult {
opcode: GenericMachineOpcode::Phi,
operand_mapping: vec![],
flags: None,
},
cost: 0, archs: vec![],
});
self.add_pattern(DAGPattern {
name: "sitofp".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::SIToFP),
result: PatternResult {
opcode: GenericMachineOpcode::SIToFP,
operand_mapping: vec![0],
flags: None,
},
cost: 2,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "fptosi".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::FPToSI),
result: PatternResult {
opcode: GenericMachineOpcode::FPToSI,
operand_mapping: vec![0],
flags: None,
},
cost: 2,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "insert_elt".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::InsertElt),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 2,
node: Box::new(PatternNode::Any),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::InsertElt,
operand_mapping: vec![0, 1, 2],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "extract_elt".into(),
pattern: PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::ExtractElt),
PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
},
PatternNode::Capture {
slot: 1,
node: Box::new(PatternNode::Any),
},
]),
result: PatternResult {
opcode: GenericMachineOpcode::ExtractElt,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "trunc".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Trunc),
result: PatternResult {
opcode: GenericMachineOpcode::Trunc,
operand_mapping: vec![0],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "uitofp".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::UIToFP),
result: PatternResult {
opcode: GenericMachineOpcode::UIToFP,
operand_mapping: vec![0],
flags: None,
},
cost: 2,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "fptoui".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::FPToUI),
result: PatternResult {
opcode: GenericMachineOpcode::FPToUI,
operand_mapping: vec![0],
flags: None,
},
cost: 2,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "copy".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Copy),
result: PatternResult {
opcode: GenericMachineOpcode::Copy,
operand_mapping: vec![0],
flags: None,
},
cost: 0, archs: vec![],
});
}
fn init_legalize_rules(&mut self) {
self.legalize_rules.push(LegalizeRule {
name: "promote_i1_to_i8".into(),
from_type: TypeKindRepr::Integer(1),
to_type: TypeKindRepr::Integer(8),
action: LegalizeAction::Promote,
});
self.legalize_rules.push(LegalizeRule {
name: "promote_narrow_int_to_i32".into(),
from_type: TypeKindRepr::Integer(8),
to_type: TypeKindRepr::Integer(32),
action: LegalizeAction::Promote,
});
self.legalize_rules.push(LegalizeRule {
name: "promote_i16_to_i32".into(),
from_type: TypeKindRepr::Integer(16),
to_type: TypeKindRepr::Integer(32),
action: LegalizeAction::Promote,
});
self.legalize_rules.push(LegalizeRule {
name: "split_i64_on_32bit".into(),
from_type: TypeKindRepr::Integer(64),
to_type: TypeKindRepr::Integer(32),
action: LegalizeAction::Split,
});
self.legalize_rules.push(LegalizeRule {
name: "split_i128_on_64bit".into(),
from_type: TypeKindRepr::Integer(128),
to_type: TypeKindRepr::Integer(64),
action: LegalizeAction::Split,
});
self.legalize_rules.push(LegalizeRule {
name: "promote_f16_to_f32".into(),
from_type: TypeKindRepr::Float(16),
to_type: TypeKindRepr::Float(32),
action: LegalizeAction::Promote,
});
self.legalize_rules.push(LegalizeRule {
name: "f80_to_f128".into(),
from_type: TypeKindRepr::Float(80),
to_type: TypeKindRepr::Float(128),
action: LegalizeAction::SoftenFloat,
});
self.legalize_rules.push(LegalizeRule {
name: "widen_v2i1".into(),
from_type: TypeKindRepr::Vector(2, Box::new(TypeKindRepr::Integer(1))),
to_type: TypeKindRepr::Vector(2, Box::new(TypeKindRepr::Integer(8))),
action: LegalizeAction::Widen,
});
self.legalize_rules.push(LegalizeRule {
name: "widen_v4i1".into(),
from_type: TypeKindRepr::Vector(4, Box::new(TypeKindRepr::Integer(1))),
to_type: TypeKindRepr::Vector(4, Box::new(TypeKindRepr::Integer(8))),
action: LegalizeAction::Widen,
});
self.legalize_rules.push(LegalizeRule {
name: "widen_v8i1".into(),
from_type: TypeKindRepr::Vector(8, Box::new(TypeKindRepr::Integer(1))),
to_type: TypeKindRepr::Vector(8, Box::new(TypeKindRepr::Integer(8))),
action: LegalizeAction::Widen,
});
self.legalize_rules.push(LegalizeRule {
name: "widen_v16i1".into(),
from_type: TypeKindRepr::Vector(16, Box::new(TypeKindRepr::Integer(1))),
to_type: TypeKindRepr::Vector(16, Box::new(TypeKindRepr::Integer(8))),
action: LegalizeAction::Widen,
});
self.legalize_rules.push(LegalizeRule {
name: "scalarize_v4i64".into(),
from_type: TypeKindRepr::Vector(4, Box::new(TypeKindRepr::Integer(64))),
to_type: TypeKindRepr::Integer(64),
action: LegalizeAction::Scalarize,
});
self.legalize_rules.push(LegalizeRule {
name: "expand_i1_select".into(),
from_type: TypeKindRepr::Integer(1),
to_type: TypeKindRepr::Integer(32),
action: LegalizeAction::Expand,
});
self.legalize_rules.push(LegalizeRule {
name: "narrow_i64_to_i32".into(),
from_type: TypeKindRepr::Integer(64),
to_type: TypeKindRepr::Integer(32),
action: LegalizeAction::Narrow,
});
}
pub fn add_pattern(&mut self, pattern: DAGPattern) {
self.patterns.push(pattern);
}
pub fn select_instructions(&mut self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
self.current_func = Some(mf.name.clone());
self.vreg_map.clear();
self.assign_vregs(mf)?;
for bb in &mut mf.blocks {
self.lower_basic_block(bb)?;
}
Ok(())
}
fn assign_vregs(&mut self, mf: &MachineFunction) -> Result<(), BridgeError> {
self.next_vreg = 0;
for bb in &mf.blocks {
for inst in &bb.instructions {
if inst.def.is_some() {
let vreg = self.next_vreg as u32;
self.next_vreg += 1;
self.vreg_map.insert(self.next_vreg - 1, vreg);
}
}
}
Ok(())
}
fn lower_basic_block(&mut self, bb: &mut MachineBasicBlock) -> Result<(), BridgeError> {
let mut lowered = Vec::new();
for inst in &bb.instructions {
let result = self.lower_instruction(inst)?;
lowered.extend(result);
}
bb.instructions = lowered;
Ok(())
}
pub fn lower_instruction(&self, inst: &MachineInstr) -> Result<Vec<MachineInstr>, BridgeError> {
let opcode = inst.opcode;
match self.lookup_pattern(opcode) {
Some(_pattern) => {
Ok(vec![inst.clone()])
}
None => Err(BridgeError::ISelFailed {
opcode,
reason: format!("no pattern for opcode {:?}", opcode),
}),
}
}
fn lookup_pattern(&self, opcode: u32) -> Option<&DAGPattern> {
self.patterns
.iter()
.find(|p| matches!(&p.pattern, PatternNode::IROpcode(o) if *o as u32 == opcode))
}
pub fn is_legal(&self, opcode: GenericMachineOpcode, _ty: &TypeKindRepr) -> bool {
match opcode {
GenericMachineOpcode::SDiv | GenericMachineOpcode::UDiv => {
true
}
GenericMachineOpcode::FDiv => {
true
}
_ => true,
}
}
pub fn get_legalized_type(&self, ty: &TypeKindRepr) -> TypeKindRepr {
for rule in &self.legalize_rules {
if rule.from_type == *ty {
return rule.to_type.clone();
}
}
ty.clone()
}
pub fn allocate_vreg(&mut self) -> VirtReg {
let vreg = self.next_vreg as u32;
self.next_vreg += 1;
vreg
}
pub fn run_dag_combine(&self, _insts: &mut [MachineInstr]) -> Vec<MachineInstr> {
Vec::new()
}
pub fn pattern_count(&self) -> usize {
self.patterns.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TypeKindRepr {
Void,
Integer(u32),
Float(u32),
Pointer,
Vector(u32, Box<TypeKindRepr>),
Array(u32, Box<TypeKindRepr>),
Struct(Vec<TypeKindRepr>),
}
impl TypeKindRepr {
pub fn size_bits(&self) -> u32 {
match self {
TypeKindRepr::Void => 0,
TypeKindRepr::Integer(bits) | TypeKindRepr::Float(bits) => *bits,
TypeKindRepr::Pointer => 64, TypeKindRepr::Vector(n, elem) => n * elem.size_bits(),
TypeKindRepr::Array(n, elem) => n * elem.size_bits(),
TypeKindRepr::Struct(fields) => fields.iter().map(|f| f.size_bits()).sum(),
}
}
pub fn is_integer(&self) -> bool {
matches!(self, TypeKindRepr::Integer(_))
}
pub fn is_float(&self) -> bool {
matches!(self, TypeKindRepr::Float(_))
}
pub fn is_vector(&self) -> bool {
matches!(self, TypeKindRepr::Vector(_, _))
}
}
#[derive(Debug, Clone)]
pub struct LegalizeRule {
pub name: String,
pub from_type: TypeKindRepr,
pub to_type: TypeKindRepr,
pub action: LegalizeAction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LegalizeAction {
Promote,
Split,
Widen,
Narrow,
Expand,
SoftenFloat,
Scalarize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CrossRegClass {
GPR64,
GPR32,
GPR16,
GPR8,
FPR128,
FPR64,
FPR32,
FPR16,
VR256,
VR512,
Flags,
}
impl CrossRegClass {
pub fn size_bits(&self) -> u32 {
match self {
CrossRegClass::GPR64 => 64,
CrossRegClass::GPR32 => 32,
CrossRegClass::GPR16 => 16,
CrossRegClass::GPR8 => 8,
CrossRegClass::FPR128 => 128,
CrossRegClass::FPR64 => 64,
CrossRegClass::FPR32 => 32,
CrossRegClass::FPR16 => 16,
CrossRegClass::VR256 => 256,
CrossRegClass::VR512 => 512,
CrossRegClass::Flags => 0,
}
}
pub fn reg_count(&self, arch: BridgeArch) -> usize {
match arch {
BridgeArch::AArch64 => match self {
CrossRegClass::GPR64 | CrossRegClass::GPR32 => AARCH64_GPR_COUNT,
CrossRegClass::FPR128 | CrossRegClass::FPR64 | CrossRegClass::FPR32 => {
AARCH64_FPR_COUNT
}
CrossRegClass::VR512 => AARCH64_FPR_COUNT,
_ => 0,
},
BridgeArch::X86_64 => match self {
CrossRegClass::GPR64 | CrossRegClass::GPR32 => 16,
CrossRegClass::FPR128 | CrossRegClass::FPR64 => 16,
CrossRegClass::VR256 => 16,
CrossRegClass::VR512 => 32,
_ => 0,
},
BridgeArch::X86_32 => match self {
CrossRegClass::GPR32 => 8,
CrossRegClass::FPR128 | CrossRegClass::FPR64 => 8,
_ => 0,
},
BridgeArch::ARM32 => match self {
CrossRegClass::GPR32 => 16,
CrossRegClass::FPR64 | CrossRegClass::FPR32 => 32,
_ => 0,
},
}
}
pub fn to_arm_reg_class(&self) -> Option<ArmRegClass> {
match self {
CrossRegClass::GPR64 => Some(ArmRegClass::GPR64),
CrossRegClass::GPR32 => Some(ArmRegClass::GPR32),
CrossRegClass::FPR128 => Some(ArmRegClass::FPR128),
CrossRegClass::FPR64 => Some(ArmRegClass::FPR64),
CrossRegClass::FPR32 => Some(ArmRegClass::FPR32),
CrossRegClass::FPR16 => Some(ArmRegClass::FPR16),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
CrossRegClass::GPR64 => "GPR64",
CrossRegClass::GPR32 => "GPR32",
CrossRegClass::GPR16 => "GPR16",
CrossRegClass::GPR8 => "GPR8",
CrossRegClass::FPR128 => "FPR128",
CrossRegClass::FPR64 => "FPR64",
CrossRegClass::FPR32 => "FPR32",
CrossRegClass::FPR16 => "FPR16",
CrossRegClass::VR256 => "VR256",
CrossRegClass::VR512 => "VR512",
CrossRegClass::Flags => "FLAGS",
}
}
}
impl fmt::Display for CrossRegClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CrossPhysReg {
pub number: u32,
pub class: CrossRegClass,
}
impl CrossPhysReg {
pub fn new(number: u32, class: CrossRegClass) -> Self {
Self { number, class }
}
pub fn to_aarch64_reg(&self) -> u32 {
match self.class {
CrossRegClass::GPR64 => self.number,
CrossRegClass::GPR32 => self.number + 100, CrossRegClass::FPR128 => self.number + 200,
CrossRegClass::FPR64 => self.number + 300,
CrossRegClass::FPR32 => self.number + 400,
_ => self.number,
}
}
pub fn to_x86_reg(&self) -> u32 {
match self.class {
CrossRegClass::GPR64 => self.number,
CrossRegClass::GPR32 => self.number + 50,
CrossRegClass::FPR128 => self.number + 100,
_ => self.number,
}
}
}
impl fmt::Display for CrossPhysReg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.class.name(), self.number)
}
}
#[derive(Debug, Clone)]
pub struct AllocationOrder {
pub class: CrossRegClass,
pub order: Vec<CrossPhysReg>,
pub reserved: HashSet<CrossPhysReg>,
pub callee_saved: HashSet<CrossPhysReg>,
pub caller_saved: HashSet<CrossPhysReg>,
}
impl AllocationOrder {
pub fn new(class: CrossRegClass) -> Self {
Self {
class,
order: Vec::new(),
reserved: HashSet::new(),
callee_saved: HashSet::new(),
caller_saved: HashSet::new(),
}
}
pub fn aarch64_defaults() -> Vec<Self> {
let mut orders = Vec::new();
let mut gpr64 = Self::new(CrossRegClass::GPR64);
for i in 0..=18 {
gpr64.order.push(CrossPhysReg::new(i, CrossRegClass::GPR64));
gpr64
.caller_saved
.insert(CrossPhysReg::new(i, CrossRegClass::GPR64));
}
for i in 19..=28 {
gpr64.order.push(CrossPhysReg::new(i, CrossRegClass::GPR64));
gpr64
.callee_saved
.insert(CrossPhysReg::new(i, CrossRegClass::GPR64));
}
gpr64
.reserved
.insert(CrossPhysReg::new(29, CrossRegClass::GPR64));
gpr64
.reserved
.insert(CrossPhysReg::new(30, CrossRegClass::GPR64));
gpr64
.reserved
.insert(CrossPhysReg::new(31, CrossRegClass::GPR64));
orders.push(gpr64);
let mut fpr128 = Self::new(CrossRegClass::FPR128);
for i in 0..=7 {
fpr128
.order
.push(CrossPhysReg::new(i, CrossRegClass::FPR128));
fpr128
.caller_saved
.insert(CrossPhysReg::new(i, CrossRegClass::FPR128));
}
for i in 8..=15 {
fpr128
.order
.push(CrossPhysReg::new(i, CrossRegClass::FPR128));
fpr128
.callee_saved
.insert(CrossPhysReg::new(i, CrossRegClass::FPR128));
}
for i in 16..=31 {
fpr128
.order
.push(CrossPhysReg::new(i, CrossRegClass::FPR128));
fpr128
.caller_saved
.insert(CrossPhysReg::new(i, CrossRegClass::FPR128));
}
orders.push(fpr128);
orders
}
pub fn x86_64_defaults() -> Vec<Self> {
let mut orders = Vec::new();
let mut gpr64 = Self::new(CrossRegClass::GPR64);
for i in [0, 2, 1, 6, 7, 8, 9, 10, 11] {
gpr64.order.push(CrossPhysReg::new(i, CrossRegClass::GPR64));
gpr64
.caller_saved
.insert(CrossPhysReg::new(i, CrossRegClass::GPR64));
}
for i in [3, 12, 13, 14, 15, 5] {
gpr64.order.push(CrossPhysReg::new(i, CrossRegClass::GPR64));
gpr64
.callee_saved
.insert(CrossPhysReg::new(i, CrossRegClass::GPR64));
}
gpr64
.reserved
.insert(CrossPhysReg::new(4, CrossRegClass::GPR64));
orders.push(gpr64);
let mut fpr128 = Self::new(CrossRegClass::FPR128);
for i in 0..=15 {
fpr128
.order
.push(CrossPhysReg::new(i, CrossRegClass::FPR128));
fpr128
.caller_saved
.insert(CrossPhysReg::new(i, CrossRegClass::FPR128));
}
orders.push(fpr128);
orders
}
pub fn for_arch(arch: BridgeArch) -> Vec<Self> {
match arch {
BridgeArch::AArch64 | BridgeArch::ARM32 => Self::aarch64_defaults(),
BridgeArch::X86_64 | BridgeArch::X86_32 => Self::x86_64_defaults(),
}
}
}
#[derive(Debug, Clone)]
pub struct LiveInterval {
pub vreg: VirtReg,
pub class: CrossRegClass,
pub ranges: Vec<(usize, usize)>,
pub assigned: Option<CrossPhysReg>,
pub spilled: bool,
pub spill_slot: Option<i32>,
pub priority: f64,
}
impl LiveInterval {
pub fn new(vreg: VirtReg, class: CrossRegClass) -> Self {
Self {
vreg,
class,
ranges: Vec::new(),
assigned: None,
spilled: false,
spill_slot: None,
priority: 0.0,
}
}
pub fn add_range(&mut self, start: usize, end: usize) {
let mut merged = false;
for range in &mut self.ranges {
if start <= range.1 && end >= range.0 {
range.0 = range.0.min(start);
range.1 = range.1.max(end);
merged = true;
break;
}
}
if !merged {
self.ranges.push((start, end));
self.ranges.sort_by_key(|r| r.0);
}
}
pub fn overlaps(&self, other: &LiveInterval) -> bool {
for &(s1, e1) in &self.ranges {
for &(s2, e2) in &other.ranges {
if s1 <= e2 && s2 <= e1 {
return true;
}
}
}
false
}
pub fn compute_priority(&mut self) {
let total_length: usize = self.ranges.iter().map(|(s, e)| e - s).sum();
let range_count = self.ranges.len().max(1) as f64;
self.priority = total_length as f64 / range_count;
}
}
pub struct CrossTargetRegAlloc {
pub arch: BridgeArch,
pub alloc_orders: Vec<AllocationOrder>,
pub intervals: Vec<LiveInterval>,
pub assignments: HashMap<VirtReg, CrossPhysReg>,
pub spill_slots: Vec<SpillSlot>,
next_spill_slot: i32,
}
impl CrossTargetRegAlloc {
pub fn new(arch: BridgeArch) -> Self {
Self {
arch,
alloc_orders: AllocationOrder::for_arch(arch),
intervals: Vec::new(),
assignments: HashMap::new(),
spill_slots: Vec::new(),
next_spill_slot: 0,
}
}
pub fn allocate_registers(&mut self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
self.compute_live_intervals(mf)?;
for interval in &mut self.intervals {
interval.compute_priority();
}
self.linear_scan_allocate()?;
self.rewrite_instructions(mf)?;
self.insert_spill_code(mf)?;
Ok(())
}
fn compute_live_intervals(&mut self, mf: &MachineFunction) -> Result<(), BridgeError> {
self.intervals.clear();
let mut vregs: HashMap<VirtReg, (usize, usize, CrossRegClass)> = HashMap::new();
let mut inst_idx = 0;
for bb in &mf.blocks {
for inst in &bb.instructions {
if let Some(dst) = &inst.def {
let class = self.infer_reg_class_from_opcode(inst.opcode);
vregs
.entry(*dst)
.and_modify(|e| e.1 = inst_idx)
.or_insert((inst_idx, inst_idx, class));
}
for src in &inst.operands {
if let MachineOperand::Reg(vreg) = src {
let class = self.infer_reg_class_from_opcode(inst.opcode);
vregs
.entry(*vreg)
.and_modify(|e| e.1 = inst_idx)
.or_insert((inst_idx, inst_idx, class));
}
}
inst_idx += 1;
}
}
for (vreg, (start, end, class)) in vregs {
let mut interval = LiveInterval::new(vreg, class);
interval.add_range(start, end);
self.intervals.push(interval);
}
self.intervals
.sort_by_key(|i| i.ranges.first().map(|r| r.0).unwrap_or(0));
Ok(())
}
fn infer_reg_class_from_opcode(&self, _opcode: u32) -> CrossRegClass {
if self.arch.is_64bit() {
CrossRegClass::GPR64
} else {
CrossRegClass::GPR32
}
}
fn linear_scan_allocate(&mut self) -> Result<(), BridgeError> {
self.intervals
.sort_by_key(|i| i.ranges.first().map(|r| r.0).unwrap_or(0));
let mut active: Vec<usize> = Vec::new();
for i in 0..self.intervals.len() {
let current_start = self.intervals[i].ranges.first().map(|r| r.0).unwrap_or(0);
active.retain(|&idx| {
self.intervals[idx]
.ranges
.last()
.map(|r| r.1 >= current_start)
.unwrap_or(false)
});
let class = self.intervals[i].class;
let order = self.alloc_orders.iter().find(|o| o.class == class);
if let Some(order) = order {
let assigned = self.try_allocate(&self.intervals[i], order, &active);
match assigned {
Some(reg) => {
self.intervals[i].assigned = Some(reg);
self.assignments.insert(self.intervals[i].vreg, reg);
}
None => {
self.spill_interval(i);
}
}
} else {
self.spill_interval(i);
}
active.push(i);
}
Ok(())
}
fn try_allocate(
&self,
interval: &LiveInterval,
order: &AllocationOrder,
active: &[usize],
) -> Option<CrossPhysReg> {
for reg in &order.order {
if order.reserved.contains(reg) {
continue;
}
let conflict = active.iter().any(|&idx| {
let other = &self.intervals[idx];
if let Some(assigned) = other.assigned {
if assigned == *reg && interval.overlaps(other) {
return true;
}
}
false
});
if !conflict {
return Some(*reg);
}
}
None
}
fn spill_interval(&mut self, idx: usize) {
let slot = SpillSlot {
index: self.next_spill_slot,
size: self.intervals[idx].class.size_bits() / 8,
alignment: (self.intervals[idx].class.size_bits() / 8).max(8),
vreg: self.intervals[idx].vreg,
};
self.next_spill_slot += 1;
self.intervals[idx].spilled = true;
self.intervals[idx].spill_slot = Some(slot.index);
self.spill_slots.push(slot);
}
fn rewrite_instructions(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn insert_spill_code(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
pub fn get_phys_reg(&self, vreg: VirtReg) -> Option<CrossPhysReg> {
self.assignments.get(&vreg).copied()
}
pub fn is_spilled(&self, vreg: VirtReg) -> bool {
self.intervals.iter().any(|i| i.vreg == vreg && i.spilled)
}
pub fn spill_slot_count(&self) -> usize {
self.spill_slots.len()
}
pub fn interval_count(&self) -> usize {
self.intervals.len()
}
}
#[derive(Debug, Clone)]
pub struct SpillSlot {
pub index: i32,
pub size: u32,
pub alignment: u32,
pub vreg: VirtReg,
}
#[derive(Debug, Clone)]
pub struct CrossStackFrame {
pub frame_size: u32,
pub local_area_offset: i32,
pub has_frame_pointer: bool,
pub saved_fp_offset: i32,
pub saved_lr_offset: i32,
pub callee_saved_size: u32,
pub saved_regs: Vec<(CrossPhysReg, i32)>,
pub has_calls: bool,
pub max_call_frame_size: u32,
pub has_var_sized_objects: bool,
pub stack_alignment: u32,
pub red_zone_size: u32,
pub arch: BridgeArch,
}
impl CrossStackFrame {
pub fn new(arch: BridgeArch) -> Self {
let (stack_alignment, red_zone) = match arch {
BridgeArch::AArch64 => (16, 0),
BridgeArch::ARM32 => (8, 0),
BridgeArch::X86_64 => (16, 128),
BridgeArch::X86_32 => (16, 0),
};
Self {
frame_size: 0,
local_area_offset: 0,
has_frame_pointer: false,
saved_fp_offset: 0,
saved_lr_offset: 0,
callee_saved_size: 0,
saved_regs: Vec::new(),
has_calls: false,
max_call_frame_size: 0,
has_var_sized_objects: false,
stack_alignment,
red_zone_size: red_zone,
arch,
}
}
pub fn align_up(&self, size: u32) -> u32 {
let align = self.stack_alignment;
(size + align - 1) & !(align - 1)
}
pub fn compute_total_size(&mut self) -> u32 {
let mut size = self.callee_saved_size + self.max_call_frame_size;
if self.has_var_sized_objects {
size += 16; }
self.frame_size = self.align_up(size);
self.frame_size
}
pub fn frame_pointer_reg(&self) -> CrossPhysReg {
match self.arch {
BridgeArch::AArch64 => CrossPhysReg::new(29, CrossRegClass::GPR64), BridgeArch::ARM32 => CrossPhysReg::new(11, CrossRegClass::GPR32), BridgeArch::X86_64 => CrossPhysReg::new(5, CrossRegClass::GPR64), BridgeArch::X86_32 => CrossPhysReg::new(5, CrossRegClass::GPR32), }
}
pub fn stack_pointer_reg(&self) -> CrossPhysReg {
match self.arch {
BridgeArch::AArch64 => CrossPhysReg::new(31, CrossRegClass::GPR64), BridgeArch::ARM32 => CrossPhysReg::new(13, CrossRegClass::GPR32), BridgeArch::X86_64 => CrossPhysReg::new(4, CrossRegClass::GPR64), BridgeArch::X86_32 => CrossPhysReg::new(4, CrossRegClass::GPR32), }
}
pub fn return_address_reg(&self) -> CrossPhysReg {
match self.arch {
BridgeArch::AArch64 => CrossPhysReg::new(30, CrossRegClass::GPR64), BridgeArch::ARM32 => CrossPhysReg::new(14, CrossRegClass::GPR32), BridgeArch::X86_64 | BridgeArch::X86_32 => CrossPhysReg::new(0, CrossRegClass::GPR64), }
}
}
pub struct CrossTargetFrameLowering {
pub arch: BridgeArch,
pub frame: CrossStackFrame,
finalized: bool,
}
impl CrossTargetFrameLowering {
pub fn new(arch: BridgeArch) -> Self {
Self {
arch,
frame: CrossStackFrame::new(arch),
finalized: false,
}
}
pub fn lower_frame(&mut self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
self.analyze_function(mf)?;
self.build_frame(mf)?;
self.emit_prologue(mf)?;
self.emit_epilogue(mf)?;
self.finalized = true;
Ok(())
}
fn analyze_function(&mut self, mf: &MachineFunction) -> Result<(), BridgeError> {
self.frame.has_calls = mf
.blocks
.iter()
.flat_map(|bb| &bb.instructions)
.any(|inst| inst.opcode == Opcode::Call as u32);
let callee_saved = self.get_callee_saved_regs();
let mut offset: i32 = 0;
for reg in &callee_saved {
self.frame.saved_regs.push((*reg, offset));
offset += (reg.class.size_bits() / 8) as i32;
}
self.frame.callee_saved_size = offset as u32;
self.frame.has_frame_pointer = self.frame.has_calls || self.frame.has_var_sized_objects;
self.frame.max_call_frame_size = if self.frame.has_calls {
128
} else {
0
};
Ok(())
}
fn get_callee_saved_regs(&self) -> Vec<CrossPhysReg> {
match self.arch {
BridgeArch::AArch64 => {
(19..=28)
.map(|i| CrossPhysReg::new(i, CrossRegClass::GPR64))
.chain(
(8..=15).map(|i| CrossPhysReg::new(i, CrossRegClass::FPR64)),
)
.collect()
}
BridgeArch::X86_64 => {
vec![
CrossPhysReg::new(3, CrossRegClass::GPR64), CrossPhysReg::new(5, CrossRegClass::GPR64), CrossPhysReg::new(12, CrossRegClass::GPR64), CrossPhysReg::new(13, CrossRegClass::GPR64), CrossPhysReg::new(14, CrossRegClass::GPR64), CrossPhysReg::new(15, CrossRegClass::GPR64), ]
}
BridgeArch::ARM32 => {
(4..=11)
.map(|i| CrossPhysReg::new(i, CrossRegClass::GPR32))
.collect()
}
BridgeArch::X86_32 => {
vec![
CrossPhysReg::new(3, CrossRegClass::GPR32), CrossPhysReg::new(5, CrossRegClass::GPR32), CrossPhysReg::new(6, CrossRegClass::GPR32), CrossPhysReg::new(7, CrossRegClass::GPR32), ]
}
}
}
fn build_frame(&mut self, _mf: &MachineFunction) -> Result<(), BridgeError> {
self.frame.compute_total_size();
if self.frame.has_frame_pointer {
if self.arch.is_arm_family() {
self.frame.saved_fp_offset = self.frame.frame_size as i32 - 16;
self.frame.saved_lr_offset = self.frame.frame_size as i32 - 8;
} else {
self.frame.saved_fp_offset = self.frame.frame_size as i32 - 8;
self.frame.saved_lr_offset = self.frame.frame_size as i32;
}
}
self.frame.local_area_offset = 0;
Ok(())
}
pub fn emit_prologue(&self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
match self.arch {
BridgeArch::AArch64 => self.emit_prologue_aarch64(mf),
BridgeArch::X86_64 => self.emit_prologue_x86_64(mf),
BridgeArch::ARM32 => self.emit_prologue_arm32(mf),
BridgeArch::X86_32 => self.emit_prologue_x86_32(mf),
}
}
pub fn emit_epilogue(&self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
match self.arch {
BridgeArch::AArch64 => self.emit_epilogue_aarch64(mf),
BridgeArch::X86_64 => self.emit_epilogue_x86_64(mf),
BridgeArch::ARM32 => self.emit_epilogue_arm32(mf),
BridgeArch::X86_32 => self.emit_epilogue_x86_32(mf),
}
}
fn emit_prologue_aarch64(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn emit_epilogue_aarch64(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn emit_prologue_x86_64(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn emit_epilogue_x86_64(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn emit_prologue_arm32(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn emit_epilogue_arm32(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn emit_prologue_x86_32(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn emit_epilogue_x86_32(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
pub fn needs_stack_probe(&self) -> bool {
self.frame.frame_size > AARCH64_PAGE_SIZE as u32 * 4
}
pub fn get_frame_index_ref(&self, index: i32) -> i32 {
if self.frame.has_frame_pointer {
-(self.frame.callee_saved_size as i32) - (index * 8)
} else {
(self.frame.frame_size as i32) - (index * 8) - 8
}
}
pub fn has_red_zone(&self) -> bool {
self.frame.red_zone_size > 0
}
pub fn needs_frame_pointer(&self) -> bool {
self.frame.has_frame_pointer || self.frame.has_var_sized_objects
}
pub fn get_emergency_spill_slot(&self, slot: usize) -> i32 {
-(16 + slot as i32 * 8)
}
}
#[derive(Debug, Clone, Copy)]
pub struct ComparativeCost {
pub opcode: Opcode,
pub x86_latency: u32,
pub aarch64_latency: u32,
pub x86_throughput: u32,
pub aarch64_throughput: u32,
pub x86_size: u32,
pub aarch64_size: u32,
}
impl ComparativeCost {
pub fn latency_for(&self, arch: BridgeArch) -> u32 {
match arch {
BridgeArch::X86_64 | BridgeArch::X86_32 => self.x86_latency,
BridgeArch::AArch64 | BridgeArch::ARM32 => self.aarch64_latency,
}
}
pub fn throughput_for(&self, arch: BridgeArch) -> u32 {
match arch {
BridgeArch::X86_64 | BridgeArch::X86_32 => self.x86_throughput,
BridgeArch::AArch64 | BridgeArch::ARM32 => self.aarch64_throughput,
}
}
pub fn size_for(&self, arch: BridgeArch) -> u32 {
match arch {
BridgeArch::X86_64 | BridgeArch::X86_32 => self.x86_size,
BridgeArch::AArch64 | BridgeArch::ARM32 => self.aarch64_size,
}
}
}
pub struct X86AArch64CostModel {
pub source_arch: BridgeArch,
pub target_arch: BridgeArch,
pub cost_table: BTreeMap<Opcode, ComparativeCost>,
pub opt_for_size: bool,
pub prefer_target: bool,
}
impl X86AArch64CostModel {
pub fn new(source_arch: BridgeArch, target_arch: BridgeArch) -> Self {
let mut model = Self {
source_arch,
target_arch,
cost_table: BTreeMap::new(),
opt_for_size: false,
prefer_target: true,
};
model.init_cost_table();
model
}
fn init_cost_table(&mut self) {
self.add_cost(
Opcode::Add,
1,
1,
1,
2, 4,
4,
);
self.add_cost(Opcode::Sub, 1, 1, 1, 2, 4, 4);
self.add_cost(
Opcode::Mul,
3, 3, 1,
1,
4,
4,
);
self.add_cost(
Opcode::SDiv,
20, 12, 10, 6,
4,
4,
);
self.add_cost(Opcode::UDiv, 20, 12, 10, 6, 4, 4);
self.add_cost(Opcode::And, 1, 1, 1, 2, 4, 4);
self.add_cost(Opcode::Or, 1, 1, 1, 2, 4, 4);
self.add_cost(Opcode::Xor, 1, 1, 1, 2, 4, 4);
self.add_cost(Opcode::Shl, 1, 1, 1, 2, 4, 4);
self.add_cost(
Opcode::Load,
4, 4, 0, 0, 7, 4, );
self.add_cost(Opcode::Store, 1, 1, 0, 0, 8, 4);
self.add_cost(
Opcode::FAdd,
3, 3, 1,
2,
5,
4,
);
self.add_cost(
Opcode::FSub,
3, 3, 1,
2,
5,
4,
);
self.add_cost(
Opcode::FMul,
4, 3, 1,
2,
5,
4,
);
self.add_cost(
Opcode::FDiv,
14, 10, 8,
5,
5,
4,
);
self.add_cost(Opcode::Br, 1, 1, 1, 1, 2, 4); self.add_cost(Opcode::Br, 1, 1, 1, 1, 6, 4);
self.add_cost(Opcode::Call, 2, 2, 2, 2, 5, 4); self.add_cost(Opcode::Ret, 2, 2, 2, 2, 1, 4);
self.add_cost(Opcode::SExt, 1, 1, 1, 2, 4, 4); self.add_cost(Opcode::ZExt, 1, 1, 1, 2, 4, 4); self.add_cost(Opcode::Trunc, 1, 1, 1, 2, 4, 4);
self.add_cost(
Opcode::SIToFP,
5, 4, 3,
2,
5,
4,
);
self.add_cost(
Opcode::FPToSI,
5, 4, 3,
2,
5,
4,
);
self.add_cost(Opcode::Shl, 1, 1, 1, 2, 4, 4);
self.add_cost(Opcode::LShr, 1, 1, 1, 2, 4, 4);
self.add_cost(Opcode::AShr, 1, 1, 1, 2, 4, 4);
self.add_cost(Opcode::ICmp, 1, 1, 1, 2, 4, 4); self.add_cost(Opcode::Select, 1, 1, 1, 1, 5, 4);
self.add_cost(
Opcode::FCmp,
1, 1, 1,
2,
4,
4,
);
self.add_cost(
Opcode::InsertElement,
2, 1, 1,
2,
5,
4,
);
self.add_cost(
Opcode::ExtractElement,
2, 1, 2,
2,
5,
4,
);
self.add_cost(
Opcode::ShuffleVector,
1, 2, 1,
1,
5,
4,
);
self.add_cost(Opcode::Phi, 0, 0, 0, 0, 0, 0); self.add_cost(Opcode::Alloca, 1, 1, 1, 2, 4, 8);
self.add_cost(Opcode::GetElementPtr, 1, 1, 1, 2, 7, 4);
self.add_cost(
Opcode::BitCast,
0, 0, 0,
0,
0,
0,
);
self.add_cost(
Opcode::IntToPtr,
0, 0, 0,
0,
0,
0,
);
self.add_cost(
Opcode::PtrToInt,
0, 0, 0,
0,
0,
0,
);
}
fn add_cost(
&mut self,
opcode: Opcode,
x86_lat: u32,
aarch64_lat: u32,
x86_tp: u32,
aarch64_tp: u32,
x86_sz: u32,
aarch64_sz: u32,
) {
self.cost_table.insert(
opcode,
ComparativeCost {
opcode,
x86_latency: x86_lat,
aarch64_latency: aarch64_lat,
x86_throughput: x86_tp,
aarch64_throughput: aarch64_tp,
x86_size: x86_sz,
aarch64_size: aarch64_sz,
},
);
}
pub fn estimate(&self, opcode: Opcode, _operand_count: usize) -> CostEstimate {
if let Some(cost) = self.cost_table.get(&opcode) {
let latency = cost.latency_for(self.target_arch);
let throughput = cost.throughput_for(self.target_arch);
let code_size = cost.size_for(self.target_arch);
CostEstimate {
latency,
throughput,
code_size,
vectorizable: self.is_vectorizable(opcode),
profitable: latency < 100,
}
} else {
CostEstimate {
latency: 10,
throughput: 4,
code_size: 4,
vectorizable: false,
profitable: true,
}
}
}
fn is_vectorizable(&self, opcode: Opcode) -> bool {
matches!(
opcode,
Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::FAdd
| Opcode::FSub
| Opcode::FMul
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Load
| Opcode::Store
)
}
pub fn is_cheaper_on_aarch64(&self, opcode: Opcode) -> bool {
self.cost_table
.get(&opcode)
.map(|c| {
let x86_cost = c.x86_latency + c.x86_throughput;
let aarch64_cost = c.aarch64_latency + c.aarch64_throughput;
aarch64_cost < x86_cost
})
.unwrap_or(false)
}
pub fn get_cost(&self, opcode: Opcode) -> Option<&ComparativeCost> {
self.cost_table.get(&opcode)
}
pub fn aarch64_advantages(&self) -> Vec<(Opcode, ComparativeCost)> {
self.cost_table
.iter()
.filter(|(_, c)| {
c.aarch64_latency + c.aarch64_throughput < c.x86_latency + c.x86_throughput
})
.map(|(o, c)| (*o, *c))
.collect()
}
pub fn x86_advantages(&self) -> Vec<(Opcode, ComparativeCost)> {
self.cost_table
.iter()
.filter(|(_, c)| {
c.x86_latency + c.x86_throughput < c.aarch64_latency + c.aarch64_throughput
})
.map(|(o, c)| (*o, *c))
.collect()
}
pub fn is_profitable(&self, opcode: Opcode, kind: &str) -> bool {
match kind {
"fold_imm" => {
self.target_arch.is_arm_family()
|| self
.get_cost(opcode)
.map(|c| c.aarch64_size <= c.x86_size)
.unwrap_or(true)
}
"use_postinc" => {
self.target_arch.is_arm_family()
}
"use_shifted_reg" => {
self.target_arch.is_arm_family()
}
"use_conditional_move" => {
true
}
"unroll_loop" => {
matches!(self.target_arch, BridgeArch::AArch64 | BridgeArch::X86_64)
}
_ => true,
}
}
pub fn table_size(&self) -> usize {
self.cost_table.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum A64Opcode {
ADD,
SUB,
MUL,
SDIV,
UDIV,
AND,
ORR,
EOR,
LSL,
LSR,
ASR,
MOV,
MOVK,
MOVN,
MOVZ,
ADR,
ADRP,
LDR,
STR,
LDP,
STP,
LDRB,
STRB,
LDRH,
STRH,
LDUR,
STUR,
B,
BL,
BR,
BLR,
RET,
CBZ,
CBNZ,
TBZ,
TBNZ,
CSEL,
CSINC,
CSINV,
CSNEG,
FADD,
FSUB,
FMUL,
FDIV,
FNEG,
FABS,
FCMP,
FCCMP,
FCVT,
SCVTF,
FCVTZS,
FCVTZU,
ADDV,
SUBV,
MULV,
MLA,
MLS,
FADDP,
FMULV,
DUP,
INS,
EXT,
SVC,
HVC,
SMC,
MSR,
MRS,
NOP,
HINT,
ISB,
DSB,
DMB,
PACIASP,
AUTIASP,
ADD_Z,
MUL_Z,
FADD_Z,
Unknown,
}
impl fmt::Display for A64Opcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
A64Opcode::ADD => "add",
A64Opcode::SUB => "sub",
A64Opcode::MUL => "mul",
A64Opcode::SDIV => "sdiv",
A64Opcode::UDIV => "udiv",
A64Opcode::AND => "and",
A64Opcode::ORR => "orr",
A64Opcode::EOR => "eor",
A64Opcode::LSL => "lsl",
A64Opcode::LSR => "lsr",
A64Opcode::ASR => "asr",
A64Opcode::MOV => "mov",
A64Opcode::MOVK => "movk",
A64Opcode::MOVN => "movn",
A64Opcode::MOVZ => "movz",
A64Opcode::ADR => "adr",
A64Opcode::ADRP => "adrp",
A64Opcode::LDR => "ldr",
A64Opcode::STR => "str",
A64Opcode::LDP => "ldp",
A64Opcode::STP => "stp",
A64Opcode::LDRB => "ldrb",
A64Opcode::STRB => "strb",
A64Opcode::LDRH => "ldrh",
A64Opcode::STRH => "strh",
A64Opcode::LDUR => "ldur",
A64Opcode::STUR => "stur",
A64Opcode::B => "b",
A64Opcode::BL => "bl",
A64Opcode::BR => "br",
A64Opcode::BLR => "blr",
A64Opcode::RET => "ret",
A64Opcode::CBZ => "cbz",
A64Opcode::CBNZ => "cbnz",
A64Opcode::TBZ => "tbz",
A64Opcode::TBNZ => "tbnz",
A64Opcode::CSEL => "csel",
A64Opcode::CSINC => "csinc",
A64Opcode::CSINV => "csinv",
A64Opcode::CSNEG => "csneg",
A64Opcode::FADD => "fadd",
A64Opcode::FSUB => "fsub",
A64Opcode::FMUL => "fmul",
A64Opcode::FDIV => "fdiv",
A64Opcode::FNEG => "fneg",
A64Opcode::FABS => "fabs",
A64Opcode::FCMP => "fcmp",
A64Opcode::FCCMP => "fccmp",
A64Opcode::FCVT => "fcvt",
A64Opcode::SCVTF => "scvtf",
A64Opcode::FCVTZS => "fcvtzs",
A64Opcode::FCVTZU => "fcvtzu",
A64Opcode::ADDV => "addv",
A64Opcode::SUBV => "subv",
A64Opcode::MULV => "mulv",
A64Opcode::MLA => "mla",
A64Opcode::MLS => "mls",
A64Opcode::FADDP => "faddp",
A64Opcode::FMULV => "fmulv",
A64Opcode::DUP => "dup",
A64Opcode::INS => "ins",
A64Opcode::EXT => "ext",
A64Opcode::SVC => "svc",
A64Opcode::HVC => "hvc",
A64Opcode::SMC => "smc",
A64Opcode::MSR => "msr",
A64Opcode::MRS => "mrs",
A64Opcode::NOP => "nop",
A64Opcode::HINT => "hint",
A64Opcode::ISB => "isb",
A64Opcode::DSB => "dsb",
A64Opcode::DMB => "dmb",
A64Opcode::PACIASP => "paciasp",
A64Opcode::AUTIASP => "autiasp",
A64Opcode::ADD_Z => "add_z",
A64Opcode::MUL_Z => "mul_z",
A64Opcode::FADD_Z => "fadd_z",
A64Opcode::Unknown => "unknown",
};
write!(f, "{}", s)
}
}
impl A64Opcode {
pub fn mnemonic(&self) -> &'static str {
match self {
A64Opcode::ADD => "add",
A64Opcode::SUB => "sub",
A64Opcode::MUL => "mul",
A64Opcode::SDIV => "sdiv",
A64Opcode::UDIV => "udiv",
A64Opcode::AND => "and",
A64Opcode::ORR => "orr",
A64Opcode::EOR => "eor",
A64Opcode::LSL => "lsl",
A64Opcode::LSR => "lsr",
A64Opcode::ASR => "asr",
A64Opcode::MOV => "mov",
A64Opcode::MOVK => "movk",
A64Opcode::MOVN => "movn",
A64Opcode::MOVZ => "movz",
A64Opcode::ADR => "adr",
A64Opcode::ADRP => "adrp",
A64Opcode::LDR => "ldr",
A64Opcode::STR => "str",
A64Opcode::LDP => "ldp",
A64Opcode::STP => "stp",
A64Opcode::LDRB => "ldrb",
A64Opcode::STRB => "strb",
A64Opcode::LDRH => "ldrh",
A64Opcode::STRH => "strh",
A64Opcode::LDUR => "ldur",
A64Opcode::STUR => "stur",
A64Opcode::B => "b",
A64Opcode::BL => "bl",
A64Opcode::BR => "br",
A64Opcode::BLR => "blr",
A64Opcode::RET => "ret",
A64Opcode::CBZ => "cbz",
A64Opcode::CBNZ => "cbnz",
A64Opcode::TBZ => "tbz",
A64Opcode::TBNZ => "tbnz",
A64Opcode::CSEL => "csel",
A64Opcode::CSINC => "csinc",
A64Opcode::CSINV => "csinv",
A64Opcode::CSNEG => "csneg",
A64Opcode::FADD => "fadd",
A64Opcode::FSUB => "fsub",
A64Opcode::FMUL => "fmul",
A64Opcode::FDIV => "fdiv",
A64Opcode::FNEG => "fneg",
A64Opcode::FABS => "fabs",
A64Opcode::FCMP => "fcmp",
A64Opcode::FCCMP => "fccmp",
A64Opcode::FCVT => "fcvt",
A64Opcode::SCVTF => "scvtf",
A64Opcode::FCVTZS => "fcvtzs",
A64Opcode::FCVTZU => "fcvtzu",
A64Opcode::ADDV => "addv",
A64Opcode::SUBV => "subv",
A64Opcode::MULV => "mulv",
A64Opcode::MLA => "mla",
A64Opcode::MLS => "mls",
A64Opcode::FADDP => "faddp",
A64Opcode::FMULV => "fmulv",
A64Opcode::DUP => "dup",
A64Opcode::INS => "ins",
A64Opcode::EXT => "ext",
A64Opcode::SVC => "svc",
A64Opcode::HVC => "hvc",
A64Opcode::SMC => "smc",
A64Opcode::MSR => "msr",
A64Opcode::MRS => "mrs",
A64Opcode::NOP => "nop",
A64Opcode::HINT => "hint",
A64Opcode::ISB => "isb",
A64Opcode::DSB => "dsb",
A64Opcode::DMB => "dmb",
A64Opcode::PACIASP => "paciasp",
A64Opcode::AUTIASP => "autiasp",
A64Opcode::ADD_Z => "add_z",
A64Opcode::MUL_Z => "mul_z",
A64Opcode::FADD_Z => "fadd_z",
A64Opcode::Unknown => "unknown",
}
}
pub fn is_branch(&self) -> bool {
matches!(
self,
A64Opcode::B
| A64Opcode::BR
| A64Opcode::BL
| A64Opcode::BLR
| A64Opcode::RET
| A64Opcode::CBZ
| A64Opcode::CBNZ
| A64Opcode::TBZ
| A64Opcode::TBNZ
)
}
pub fn is_memory(&self) -> bool {
matches!(
self,
A64Opcode::LDR
| A64Opcode::STR
| A64Opcode::LDP
| A64Opcode::STP
| A64Opcode::LDRB
| A64Opcode::STRB
| A64Opcode::LDRH
| A64Opcode::STRH
| A64Opcode::LDUR
| A64Opcode::STUR
)
}
pub fn is_fp(&self) -> bool {
matches!(
self,
A64Opcode::FADD
| A64Opcode::FSUB
| A64Opcode::FMUL
| A64Opcode::FDIV
| A64Opcode::FNEG
| A64Opcode::FABS
| A64Opcode::FCMP
| A64Opcode::FCCMP
| A64Opcode::FCVT
| A64Opcode::SCVTF
| A64Opcode::FCVTZS
| A64Opcode::FCVTZU
| A64Opcode::FADDP
| A64Opcode::FMULV
| A64Opcode::FADD_Z
)
}
pub fn is_simd(&self) -> bool {
matches!(
self,
A64Opcode::ADDV
| A64Opcode::SUBV
| A64Opcode::MULV
| A64Opcode::MLA
| A64Opcode::MLS
| A64Opcode::FADDP
| A64Opcode::FMULV
| A64Opcode::DUP
| A64Opcode::INS
| A64Opcode::EXT
| A64Opcode::ADD_Z
| A64Opcode::MUL_Z
| A64Opcode::FADD_Z
)
}
pub fn num_src_operands(&self) -> usize {
match self {
A64Opcode::ADD
| A64Opcode::SUB
| A64Opcode::MUL
| A64Opcode::SDIV
| A64Opcode::UDIV
| A64Opcode::AND
| A64Opcode::ORR
| A64Opcode::EOR
| A64Opcode::LSL
| A64Opcode::LSR
| A64Opcode::ASR
| A64Opcode::FADD
| A64Opcode::FSUB
| A64Opcode::FMUL
| A64Opcode::FDIV => 2,
A64Opcode::MOV | A64Opcode::MOVK | A64Opcode::MOVN | A64Opcode::MOVZ => 1,
A64Opcode::LDR
| A64Opcode::LDUR
| A64Opcode::LDRB
| A64Opcode::LDRH
| A64Opcode::ADR
| A64Opcode::ADRP => 1, A64Opcode::STR | A64Opcode::STUR | A64Opcode::STRB | A64Opcode::STRH => 2,
A64Opcode::STP => 3, A64Opcode::LDP => 2, A64Opcode::B | A64Opcode::BR | A64Opcode::RET => 1,
A64Opcode::BL | A64Opcode::BLR => 1,
A64Opcode::CBZ | A64Opcode::CBNZ => 2,
A64Opcode::TBZ | A64Opcode::TBNZ => 3,
A64Opcode::CSEL | A64Opcode::CSINC | A64Opcode::CSINV | A64Opcode::CSNEG => 3,
A64Opcode::FNEG | A64Opcode::FABS => 1,
A64Opcode::FCMP => 2,
A64Opcode::FCVT | A64Opcode::SCVTF | A64Opcode::FCVTZS | A64Opcode::FCVTZU => 1,
A64Opcode::NOP | A64Opcode::HINT | A64Opcode::ISB | A64Opcode::DSB | A64Opcode::DMB => {
0
}
_ => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum A64Cond {
EQ,
NE,
CS,
CC,
MI,
PL,
VS,
VC,
HI,
LS,
GE,
LT,
GT,
LE,
AL,
NV,
}
impl A64Cond {
pub fn invert(&self) -> Self {
match self {
A64Cond::EQ => A64Cond::NE,
A64Cond::NE => A64Cond::EQ,
A64Cond::CS => A64Cond::CC,
A64Cond::CC => A64Cond::CS,
A64Cond::MI => A64Cond::PL,
A64Cond::PL => A64Cond::MI,
A64Cond::VS => A64Cond::VC,
A64Cond::VC => A64Cond::VS,
A64Cond::HI => A64Cond::LS,
A64Cond::LS => A64Cond::HI,
A64Cond::GE => A64Cond::LT,
A64Cond::LT => A64Cond::GE,
A64Cond::GT => A64Cond::LE,
A64Cond::LE => A64Cond::GT,
A64Cond::AL => A64Cond::NV,
A64Cond::NV => A64Cond::AL,
}
}
pub fn encoding(&self) -> u8 {
match self {
A64Cond::EQ => 0b0000,
A64Cond::NE => 0b0001,
A64Cond::CS => 0b0010,
A64Cond::CC => 0b0011,
A64Cond::MI => 0b0100,
A64Cond::PL => 0b0101,
A64Cond::VS => 0b0110,
A64Cond::VC => 0b0111,
A64Cond::HI => 0b1000,
A64Cond::LS => 0b1001,
A64Cond::GE => 0b1010,
A64Cond::LT => 0b1011,
A64Cond::GT => 0b1100,
A64Cond::LE => 0b1101,
A64Cond::AL => 0b1110,
A64Cond::NV => 0b1111,
}
}
}
impl fmt::Display for A64Cond {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
A64Cond::EQ => "eq",
A64Cond::NE => "ne",
A64Cond::CS => "cs",
A64Cond::CC => "cc",
A64Cond::MI => "mi",
A64Cond::PL => "pl",
A64Cond::VS => "vs",
A64Cond::VC => "vc",
A64Cond::HI => "hi",
A64Cond::LS => "ls",
A64Cond::GE => "ge",
A64Cond::LT => "lt",
A64Cond::GT => "gt",
A64Cond::LE => "le",
A64Cond::AL => "al",
A64Cond::NV => "nv",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone)]
pub struct A64InstrDesc {
pub opcode: A64Opcode,
pub mnemonic: &'static str,
pub num_src: usize,
pub has_dst: bool,
pub writes_flags: bool,
pub reads_flags: bool,
pub category: A64InstrCategory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum A64InstrCategory {
Arithmetic,
Logical,
Multiply,
Divide,
Load,
Store,
Branch,
Call,
Return,
FPArith,
FPMisc,
SIMD,
System,
Misc,
}
#[derive(Debug, Clone)]
pub struct A64InstrTable {
pub table: HashMap<A64Opcode, A64InstrDesc>,
mnemonic_map: HashMap<String, A64Opcode>,
}
impl A64InstrTable {
pub fn new() -> Self {
let mut table = Self {
table: HashMap::new(),
mnemonic_map: HashMap::new(),
};
table.init();
table
}
fn init(&mut self) {
self.add(
A64Opcode::ADD,
"add",
2,
true,
false,
false,
A64InstrCategory::Arithmetic,
);
self.add(
A64Opcode::SUB,
"sub",
2,
true,
false,
false,
A64InstrCategory::Arithmetic,
);
self.add(
A64Opcode::MUL,
"mul",
2,
true,
false,
false,
A64InstrCategory::Multiply,
);
self.add(
A64Opcode::SDIV,
"sdiv",
2,
true,
false,
false,
A64InstrCategory::Divide,
);
self.add(
A64Opcode::UDIV,
"udiv",
2,
true,
false,
false,
A64InstrCategory::Divide,
);
self.add(
A64Opcode::AND,
"and",
2,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::ORR,
"orr",
2,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::EOR,
"eor",
2,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::LSL,
"lsl",
2,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::LSR,
"lsr",
2,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::ASR,
"asr",
2,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::MOV,
"mov",
1,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::MOVK,
"movk",
1,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::MOVN,
"movn",
1,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::MOVZ,
"movz",
1,
true,
false,
false,
A64InstrCategory::Logical,
);
self.add(
A64Opcode::ADR,
"adr",
1,
true,
false,
false,
A64InstrCategory::Arithmetic,
);
self.add(
A64Opcode::ADRP,
"adrp",
1,
true,
false,
false,
A64InstrCategory::Arithmetic,
);
self.add(
A64Opcode::LDR,
"ldr",
1,
true,
false,
false,
A64InstrCategory::Load,
);
self.add(
A64Opcode::STR,
"str",
2,
false,
false,
false,
A64InstrCategory::Store,
);
self.add(
A64Opcode::LDP,
"ldp",
2,
true,
false,
false,
A64InstrCategory::Load,
);
self.add(
A64Opcode::STP,
"stp",
2,
false,
false,
false,
A64InstrCategory::Store,
);
self.add(
A64Opcode::LDRB,
"ldrb",
1,
true,
false,
false,
A64InstrCategory::Load,
);
self.add(
A64Opcode::STRB,
"strb",
2,
false,
false,
false,
A64InstrCategory::Store,
);
self.add(
A64Opcode::LDRH,
"ldrh",
1,
true,
false,
false,
A64InstrCategory::Load,
);
self.add(
A64Opcode::STRH,
"strh",
2,
false,
false,
false,
A64InstrCategory::Store,
);
self.add(
A64Opcode::LDUR,
"ldur",
1,
true,
false,
false,
A64InstrCategory::Load,
);
self.add(
A64Opcode::STUR,
"stur",
2,
false,
false,
false,
A64InstrCategory::Store,
);
self.add(
A64Opcode::B,
"b",
1,
false,
false,
false,
A64InstrCategory::Branch,
);
self.add(
A64Opcode::BL,
"bl",
1,
false,
false,
false,
A64InstrCategory::Call,
);
self.add(
A64Opcode::BR,
"br",
1,
false,
false,
false,
A64InstrCategory::Branch,
);
self.add(
A64Opcode::BLR,
"blr",
1,
false,
false,
false,
A64InstrCategory::Call,
);
self.add(
A64Opcode::RET,
"ret",
1,
false,
false,
false,
A64InstrCategory::Return,
);
self.add(
A64Opcode::CBZ,
"cbz",
2,
false,
false,
false,
A64InstrCategory::Branch,
);
self.add(
A64Opcode::CBNZ,
"cbnz",
2,
false,
false,
false,
A64InstrCategory::Branch,
);
self.add(
A64Opcode::TBZ,
"tbz",
3,
false,
false,
false,
A64InstrCategory::Branch,
);
self.add(
A64Opcode::TBNZ,
"tbnz",
3,
false,
false,
false,
A64InstrCategory::Branch,
);
self.add(
A64Opcode::CSEL,
"csel",
3,
true,
false,
false,
A64InstrCategory::Arithmetic,
);
self.add(
A64Opcode::CSINC,
"csinc",
3,
true,
false,
false,
A64InstrCategory::Arithmetic,
);
self.add(
A64Opcode::CSINV,
"csinv",
3,
true,
false,
false,
A64InstrCategory::Arithmetic,
);
self.add(
A64Opcode::CSNEG,
"csneg",
3,
true,
false,
false,
A64InstrCategory::Arithmetic,
);
self.add(
A64Opcode::FADD,
"fadd",
2,
true,
false,
false,
A64InstrCategory::FPArith,
);
self.add(
A64Opcode::FSUB,
"fsub",
2,
true,
false,
false,
A64InstrCategory::FPArith,
);
self.add(
A64Opcode::FMUL,
"fmul",
2,
true,
false,
false,
A64InstrCategory::FPArith,
);
self.add(
A64Opcode::FDIV,
"fdiv",
2,
true,
false,
false,
A64InstrCategory::FPArith,
);
self.add(
A64Opcode::FNEG,
"fneg",
1,
true,
false,
false,
A64InstrCategory::FPMisc,
);
self.add(
A64Opcode::FABS,
"fabs",
1,
true,
false,
false,
A64InstrCategory::FPMisc,
);
self.add(
A64Opcode::FCMP,
"fcmp",
2,
false,
true,
false,
A64InstrCategory::FPMisc,
);
self.add(
A64Opcode::FCVT,
"fcvt",
1,
true,
false,
false,
A64InstrCategory::FPMisc,
);
self.add(
A64Opcode::SCVTF,
"scvtf",
1,
true,
false,
false,
A64InstrCategory::FPMisc,
);
self.add(
A64Opcode::FCVTZS,
"fcvtzs",
1,
true,
false,
false,
A64InstrCategory::FPMisc,
);
self.add(
A64Opcode::NOP,
"nop",
0,
false,
false,
false,
A64InstrCategory::Misc,
);
self.add(
A64Opcode::HINT,
"hint",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::ISB,
"isb",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::DSB,
"dsb",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::DMB,
"dmb",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::SVC,
"svc",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::HVC,
"hvc",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::SMC,
"smc",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::MSR,
"msr",
1,
false,
false,
true,
A64InstrCategory::System,
);
self.add(
A64Opcode::MRS,
"mrs",
0,
true,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::ADDV,
"add",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::SUBV,
"sub",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::MULV,
"mul",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::MLA,
"mla",
3,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::MLS,
"mls",
3,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::FADDP,
"faddp",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::FMULV,
"fmul",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::DUP,
"dup",
1,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::INS,
"ins",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::EXT,
"ext",
3,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::PACIASP,
"paciasp",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::AUTIASP,
"autiasp",
0,
false,
false,
false,
A64InstrCategory::System,
);
self.add(
A64Opcode::ADD_Z,
"add",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::MUL_Z,
"mul",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
self.add(
A64Opcode::FADD_Z,
"fadd",
2,
true,
false,
false,
A64InstrCategory::SIMD,
);
}
fn add(
&mut self,
opcode: A64Opcode,
mnemonic: &'static str,
num_src: usize,
has_dst: bool,
reads_flags: bool,
writes_flags: bool,
category: A64InstrCategory,
) {
let desc = A64InstrDesc {
opcode,
mnemonic,
num_src,
has_dst,
writes_flags,
reads_flags,
category,
};
self.table.insert(opcode, desc);
self.mnemonic_map.insert(mnemonic.to_string(), opcode);
}
pub fn lookup(&self, opcode: A64Opcode) -> Option<&A64InstrDesc> {
self.table.get(&opcode)
}
pub fn lookup_mnemonic(&self, mnemonic: &str) -> Option<A64Opcode> {
self.mnemonic_map.get(mnemonic).copied()
}
pub fn size(&self) -> usize {
self.table.len()
}
}
impl Default for A64InstrTable {
fn default() -> Self {
Self::new()
}
}
pub struct AArch64TargetMachine {
pub triple: String,
pub is_64bit: bool,
pub data_layout: String,
pub features: BridgeFeatures,
pub opt_level: CodeGenOptLevel,
pub reloc_model: RelocModel,
pub code_model: CodeModel,
pub reg_classes: Vec<CrossRegClass>,
pub instr_table: A64InstrTable,
pub abi: String,
pub cpu: String,
}
impl AArch64TargetMachine {
pub fn new(triple_str: &str) -> Self {
let triple = Triple::parse(triple_str);
let is_64bit = matches!(triple.arch, Arch::AArch64);
let data_layout = AARCH64_DATA_LAYOUT.to_string();
let os = triple.os.to_lowercase();
let abi = Self::infer_abi(&os);
let mut features = BridgeFeatures::default();
features.enable_armv8a();
let cpu = "generic".to_string();
Self {
triple: triple_str.to_string(),
is_64bit,
data_layout,
features,
opt_level: CodeGenOptLevel::Default,
reloc_model: RelocModel::default(),
code_model: CodeModel::Small,
reg_classes: vec![
CrossRegClass::GPR64,
CrossRegClass::GPR32,
CrossRegClass::FPR128,
CrossRegClass::FPR64,
],
instr_table: A64InstrTable::new(),
abi,
cpu,
}
}
pub fn linux_gnu() -> Self {
Self::new("aarch64-unknown-linux-gnu")
}
pub fn apple_darwin() -> Self {
Self::new("aarch64-apple-darwin")
}
pub fn android() -> Self {
Self::new("aarch64-unknown-linux-android")
}
pub fn windows_msvc() -> Self {
Self::new("aarch64-pc-windows-msvc")
}
fn infer_abi(os: &str) -> String {
match os {
"linux" | "android" => "aapcs".to_string(),
"darwin" | "ios" => "darwinpcs".to_string(),
"windows" => "msvc".to_string(),
_ => "aapcs".to_string(),
}
}
pub fn get_data_layout(&self) -> &str {
&self.data_layout
}
pub fn get_triple(&self) -> &str {
&self.triple
}
pub fn has_feature(&self, feature: BridgeFeature) -> bool {
self.features.has(feature)
}
pub fn enable_feature(&mut self, feature: BridgeFeature) {
self.features.enable(feature);
}
pub fn disable_feature(&mut self, feature: BridgeFeature) {
self.features.disable(feature);
}
pub fn set_opt_level(&mut self, level: CodeGenOptLevel) {
self.opt_level = level;
}
pub fn lookup_instr(&self, mnemonic: &str) -> Option<A64Opcode> {
self.instr_table.lookup_mnemonic(mnemonic)
}
pub fn get_instr_desc(&self, opcode: A64Opcode) -> Option<&A64InstrDesc> {
self.instr_table.lookup(opcode)
}
pub fn is_valid_target(triple_str: &str) -> bool {
let t = Triple::parse(triple_str);
matches!(t.arch, Arch::AArch64 | Arch::ARM | Arch::Thumb)
}
pub fn describe(&self) -> String {
format!(
"AArch64TargetMachine {{ triple: {}, cpu: {}, abi: {}, 64-bit: {}, features: {} }}",
self.triple,
self.cpu,
self.abi,
self.is_64bit,
self.features.to_feature_string()
)
}
pub fn pointer_width(&self) -> u32 {
if self.is_64bit {
64
} else {
32
}
}
pub fn is_little_endian(&self) -> bool {
!self.triple.contains("aarch64_be") && !self.triple.contains("armeb")
}
}
impl Default for AArch64TargetMachine {
fn default() -> Self {
Self::linux_gnu()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CrossOptPass {
LoadElimination,
StoreElimination,
ConstantFolding,
StrengthReduction,
LICM,
LoopUnrolling,
Peephole,
LoadStoreMerging,
CodeSinking,
CodeHoisting,
TailDuplication,
IfConversion,
MachineCSE,
BranchFolding,
VectorizationPrep,
}
impl CrossOptPass {
pub fn enabled_at(&self, level: CodeGenOptLevel) -> bool {
match level {
CodeGenOptLevel::None => false,
CodeGenOptLevel::Less => {
matches!(self, CrossOptPass::ConstantFolding | CrossOptPass::Peephole)
}
CodeGenOptLevel::Default => !matches!(
self,
CrossOptPass::LoopUnrolling | CrossOptPass::TailDuplication
),
CodeGenOptLevel::Aggressive => true,
}
}
pub fn name(&self) -> &'static str {
match self {
CrossOptPass::LoadElimination => "load-elimination",
CrossOptPass::StoreElimination => "store-elimination",
CrossOptPass::ConstantFolding => "constant-folding",
CrossOptPass::StrengthReduction => "strength-reduction",
CrossOptPass::LICM => "licm",
CrossOptPass::LoopUnrolling => "loop-unrolling",
CrossOptPass::Peephole => "peephole",
CrossOptPass::LoadStoreMerging => "load-store-merging",
CrossOptPass::CodeSinking => "code-sinking",
CrossOptPass::CodeHoisting => "code-hoisting",
CrossOptPass::TailDuplication => "tail-duplication",
CrossOptPass::IfConversion => "if-conversion",
CrossOptPass::MachineCSE => "machine-cse",
CrossOptPass::BranchFolding => "branch-folding",
CrossOptPass::VectorizationPrep => "vectorization-prep",
}
}
}
#[derive(Debug, Clone)]
pub struct LoopOptimizationPattern {
pub name: String,
pub description: String,
pub applies_x86: bool,
pub applies_aarch64: bool,
pub threshold: u32,
}
impl LoopOptimizationPattern {
pub fn new(
name: &str,
description: &str,
applies_x86: bool,
applies_aarch64: bool,
threshold: u32,
) -> Self {
Self {
name: name.to_string(),
description: description.to_string(),
applies_x86,
applies_aarch64,
threshold,
}
}
pub fn applies_to(&self, arch: BridgeArch) -> bool {
match arch {
BridgeArch::X86_64 | BridgeArch::X86_32 => self.applies_x86,
BridgeArch::AArch64 | BridgeArch::ARM32 => self.applies_aarch64,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VectorizationHint {
None,
PreferScalar,
PreferVector,
VectorWidth(u32),
InterleaveCount(u32),
}
pub struct CrossTargetOptimization {
pub arch: BridgeArch,
pub passes: HashSet<CrossOptPass>,
pub opt_level: CodeGenOptLevel,
pub loop_patterns: Vec<LoopOptimizationPattern>,
pub vectorization_hint: VectorizationHint,
}
impl CrossTargetOptimization {
pub fn new(arch: BridgeArch) -> Self {
let mut opt = Self {
arch,
passes: HashSet::new(),
opt_level: CodeGenOptLevel::Default,
loop_patterns: Vec::new(),
vectorization_hint: VectorizationHint::None,
};
opt.init_default_pass_pipeline();
opt.init_loop_patterns();
opt
}
fn init_default_pass_pipeline(&mut self) {
self.passes.insert(CrossOptPass::ConstantFolding);
self.passes.insert(CrossOptPass::Peephole);
self.passes.insert(CrossOptPass::LoadElimination);
self.passes.insert(CrossOptPass::StoreElimination);
self.passes.insert(CrossOptPass::LICM);
self.passes.insert(CrossOptPass::StrengthReduction);
self.passes.insert(CrossOptPass::CodeSinking);
self.passes.insert(CrossOptPass::CodeHoisting);
self.passes.insert(CrossOptPass::MachineCSE);
self.passes.insert(CrossOptPass::BranchFolding);
self.passes.insert(CrossOptPass::IfConversion);
self.passes.insert(CrossOptPass::VectorizationPrep);
self.passes.insert(CrossOptPass::LoadStoreMerging);
}
fn init_loop_patterns(&mut self) {
self.loop_patterns.push(LoopOptimizationPattern::new(
"counted_to_decrement",
"Convert counted-up loop to decrementing with zero-test for better branch fusion",
true,
true,
4,
));
self.loop_patterns.push(LoopOptimizationPattern::new(
"loop_interchange",
"Swap inner/outer loops for better spatial locality",
true,
true,
8,
));
self.loop_patterns.push(LoopOptimizationPattern::new(
"loop_unswitch",
"Hoist loop-invariant conditionals outside the loop",
true,
true,
0, ));
self.loop_patterns.push(LoopOptimizationPattern::new(
"irce",
"Eliminate range checks on induction variables when bounds are known",
true,
true,
2,
));
self.loop_patterns.push(LoopOptimizationPattern::new(
"loop_idiom",
"Recognize and replace loop idioms (memset, memcpy) with library calls",
true,
true,
8,
));
self.loop_patterns.push(LoopOptimizationPattern::new(
"postinc_addressing",
"Convert indexed addressing to post-increment for AArch64 LDR/STR",
false,
true,
1,
));
self.loop_patterns.push(LoopOptimizationPattern::new(
"lea_address_gen",
"Use LEA for complex address calculations in X86 loops",
true,
false,
1,
));
self.loop_patterns.push(LoopOptimizationPattern::new(
"cmp_branch_fusion",
"Fuse compare-and-branch sequences into CBZ/CBNZ (AArch64) or macro-fused CMP+Jcc (X86)",
true,
true,
0,
));
self.loop_patterns.push(LoopOptimizationPattern::new(
"iv_strength_reduce",
"Replace multiplication-based IVs with addition-based IVs",
true,
true,
2,
));
}
pub fn optimize(&mut self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
for pass in self.passes.iter() {
if pass.enabled_at(self.opt_level) {
self.run_pass(pass, mf)?;
}
}
Ok(())
}
pub fn post_ra_optimize(&mut self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
let post_ra_passes = [
CrossOptPass::Peephole,
CrossOptPass::BranchFolding,
CrossOptPass::MachineCSE,
];
for pass in &post_ra_passes {
if self.passes.contains(pass) && pass.enabled_at(self.opt_level) {
self.run_pass(pass, mf)?;
}
}
Ok(())
}
fn run_pass(&self, pass: &CrossOptPass, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
match pass {
CrossOptPass::ConstantFolding => self.fold_constants(),
CrossOptPass::Peephole => self.run_peephole(),
CrossOptPass::LoadElimination => {}
CrossOptPass::StoreElimination => {}
CrossOptPass::LICM => {}
CrossOptPass::StrengthReduction => {}
CrossOptPass::LoopUnrolling => {}
CrossOptPass::LoadStoreMerging => {}
CrossOptPass::CodeSinking => {}
CrossOptPass::CodeHoisting => {}
CrossOptPass::TailDuplication => {}
CrossOptPass::IfConversion => {}
CrossOptPass::MachineCSE => {}
CrossOptPass::BranchFolding => {}
CrossOptPass::VectorizationPrep => {}
}
Ok(())
}
fn fold_constants(&self) {
}
fn run_peephole(&self) {
}
pub fn enable_pass(&mut self, pass: CrossOptPass) {
self.passes.insert(pass);
}
pub fn disable_pass(&mut self, pass: CrossOptPass) {
self.passes.remove(&pass);
}
pub fn set_opt_level(&mut self, level: CodeGenOptLevel) {
self.opt_level = level;
self.passes.clear();
self.init_default_pass_pipeline();
match level {
CodeGenOptLevel::None => {
self.passes.clear();
}
CodeGenOptLevel::Less => {
self.passes.retain(|p| {
matches!(
p,
CrossOptPass::ConstantFolding
| CrossOptPass::Peephole
| CrossOptPass::MachineCSE
)
});
}
CodeGenOptLevel::Aggressive => {
self.passes.insert(CrossOptPass::LoopUnrolling);
self.passes.insert(CrossOptPass::TailDuplication);
}
_ => {}
}
}
pub fn set_vectorization_hint(&mut self, hint: VectorizationHint) {
self.vectorization_hint = hint;
}
pub fn get_loop_pattern(&self, name: &str) -> Option<&LoopOptimizationPattern> {
self.loop_patterns.iter().find(|p| p.name == name)
}
pub fn applicable_loop_patterns(&self) -> Vec<&LoopOptimizationPattern> {
self.loop_patterns
.iter()
.filter(|p| p.applies_to(self.arch))
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CrossABIClass {
Integer,
Float,
Split {
hi: Box<CrossABIClass>,
lo: Box<CrossABIClass>,
},
Memory,
NoClass,
}
impl CrossABIClass {
pub fn is_integer(&self) -> bool {
matches!(self, CrossABIClass::Integer)
}
pub fn is_float(&self) -> bool {
matches!(self, CrossABIClass::Float)
}
pub fn is_memory(&self) -> bool {
matches!(self, CrossABIClass::Memory)
}
pub fn is_split(&self) -> bool {
matches!(self, CrossABIClass::Split { .. })
}
}
impl fmt::Display for CrossABIClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CrossABIClass::Integer => write!(f, "integer"),
CrossABIClass::Float => write!(f, "float"),
CrossABIClass::Split { hi, lo } => write!(f, "split({}, {})", hi, lo),
CrossABIClass::Memory => write!(f, "memory"),
CrossABIClass::NoClass => write!(f, "none"),
}
}
}
#[derive(Debug, Clone)]
pub struct CrossABIArg {
pub class: CrossABIClass,
pub ty: TypeKindRepr,
pub alignment: u32,
pub by_val: bool,
pub reg: Option<CrossPhysReg>,
pub stack_offset: Option<i32>,
pub gprs_used: u32,
pub fprs_used: u32,
}
impl CrossABIArg {
pub fn new(ty: TypeKindRepr) -> Self {
let alignment = match &ty {
TypeKindRepr::Integer(bits) => (*bits / 8).max(1) as u32,
TypeKindRepr::Float(bits) => (*bits / 8) as u32,
TypeKindRepr::Pointer => 8,
_ => 8,
};
Self {
class: CrossABIClass::NoClass,
ty,
alignment,
by_val: false,
reg: None,
stack_offset: None,
gprs_used: 0,
fprs_used: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct CrossABIRet {
pub class: CrossABIClass,
pub ty: TypeKindRepr,
pub sret: bool,
pub reg: Option<CrossPhysReg>,
pub gprs_used: u32,
pub fprs_used: u32,
}
impl CrossABIRet {
pub fn new(ty: TypeKindRepr) -> Self {
Self {
class: CrossABIClass::NoClass,
ty,
sret: false,
reg: None,
gprs_used: 0,
fprs_used: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct CrossVarArgs {
pub is_variadic: bool,
pub gpr_save_offset: i32,
pub fpr_save_offset: i32,
pub gprs_used_for_named: u32,
pub fprs_used_for_named: u32,
}
impl CrossVarArgs {
pub fn new() -> Self {
Self {
is_variadic: false,
gpr_save_offset: 0,
fpr_save_offset: 0,
gprs_used_for_named: 0,
fprs_used_for_named: 0,
}
}
pub fn gpr_save_size(&self) -> u32 {
if !self.is_variadic {
return 0;
}
64
}
pub fn fpr_save_size(&self) -> u32 {
if !self.is_variadic {
return 0;
}
128
}
}
impl Default for CrossVarArgs {
fn default() -> Self {
Self::new()
}
}
pub struct CrossTargetABI {
pub arch: BridgeArch,
pub calling_conv: String,
pub max_gpr_params: u32,
pub max_fpr_params: u32,
pub args: Vec<CrossABIArg>,
pub ret: CrossABIRet,
pub varargs: CrossVarArgs,
pub stack_alignment: u32,
}
impl CrossTargetABI {
pub fn new(arch: BridgeArch) -> Self {
let (max_gpr, max_fpr, stack_align) = match arch {
BridgeArch::AArch64 => (8, 8, 16),
BridgeArch::ARM32 => (4, 16, 8), BridgeArch::X86_64 => (6, 8, 16), BridgeArch::X86_32 => (0, 0, 16), };
Self {
arch,
calling_conv: Self::default_calling_conv(arch),
max_gpr_params: max_gpr,
max_fpr_params: max_fpr,
args: Vec::new(),
ret: CrossABIRet::new(TypeKindRepr::Void),
varargs: CrossVarArgs::new(),
stack_alignment: stack_align,
}
}
fn default_calling_conv(arch: BridgeArch) -> String {
match arch {
BridgeArch::AArch64 => "aapcs".to_string(),
BridgeArch::ARM32 => "aapcs".to_string(),
BridgeArch::X86_64 => "sysv".to_string(),
BridgeArch::X86_32 => "cdecl".to_string(),
}
}
pub fn classify_arg(&self, ty: &TypeKindRepr) -> CrossABIClass {
match ty {
TypeKindRepr::Integer(bits) => {
if *bits <= 64 {
CrossABIClass::Integer
} else {
if self.arch == BridgeArch::AArch64 && *bits == 128 {
CrossABIClass::Split {
hi: Box::new(CrossABIClass::Integer),
lo: Box::new(CrossABIClass::Integer),
}
} else {
CrossABIClass::Memory
}
}
}
TypeKindRepr::Float(32) | TypeKindRepr::Float(64) => CrossABIClass::Float,
TypeKindRepr::Float(128) => {
if self.arch.is_arm_family() {
CrossABIClass::Split {
hi: Box::new(CrossABIClass::Float),
lo: Box::new(CrossABIClass::Float),
}
} else {
CrossABIClass::Memory
}
}
TypeKindRepr::Pointer => {
CrossABIClass::Integer }
TypeKindRepr::Vector(n, elem) => {
let elem_size = elem.size_bits();
let total = *n * elem_size;
if total <= 128 {
CrossABIClass::Float } else {
CrossABIClass::Memory }
}
TypeKindRepr::Struct(fields) => {
let total_size: u32 = fields.iter().map(|f| f.size_bits()).sum();
if total_size <= 128 {
if self.is_hfa(fields) {
CrossABIClass::Float
} else {
CrossABIClass::Integer
}
} else {
CrossABIClass::Memory
}
}
TypeKindRepr::Void => CrossABIClass::NoClass,
_ => CrossABIClass::Memory,
}
}
fn is_hfa(&self, fields: &[TypeKindRepr]) -> bool {
if fields.is_empty() {
return false;
}
let first = &fields[0];
if !matches!(first, TypeKindRepr::Float(_)) {
return false;
}
fields.len() <= 4 && fields.iter().all(|f| f == first)
}
pub fn classify_ret(&self, ty: &TypeKindRepr) -> CrossABIClass {
match ty {
TypeKindRepr::Void => CrossABIClass::NoClass,
TypeKindRepr::Integer(bits) if *bits <= 64 => CrossABIClass::Integer,
TypeKindRepr::Integer(128) => {
CrossABIClass::Split {
hi: Box::new(CrossABIClass::Integer),
lo: Box::new(CrossABIClass::Integer),
}
}
TypeKindRepr::Float(32) | TypeKindRepr::Float(64) => CrossABIClass::Float,
TypeKindRepr::Float(128) => CrossABIClass::Split {
hi: Box::new(CrossABIClass::Float),
lo: Box::new(CrossABIClass::Float),
},
TypeKindRepr::Pointer => CrossABIClass::Integer,
TypeKindRepr::Struct(fields) => {
let total_size: u32 = fields.iter().map(|f| f.size_bits()).sum();
if total_size <= 128 && !fields.is_empty() {
if self.is_hfa(fields) {
CrossABIClass::Float
} else {
CrossABIClass::Integer
}
} else {
CrossABIClass::Memory
}
}
_ => CrossABIClass::Memory,
}
}
pub fn lower_args(
&mut self,
arg_types: &[TypeKindRepr],
) -> Result<Vec<CrossABIArg>, BridgeError> {
self.args.clear();
let mut gpr_used: u32 = 0;
let mut fpr_used: u32 = 0;
let mut stack_offset: i32 = 0;
for ty in arg_types {
let class = self.classify_arg(ty);
let mut arg = CrossABIArg::new(ty.clone());
arg.class = class;
match class {
CrossABIClass::Integer => {
if gpr_used < self.max_gpr_params {
let reg_num = gpr_used;
arg.reg = Some(CrossPhysReg::new(reg_num, CrossRegClass::GPR64));
arg.gprs_used = 1;
gpr_used += 1;
} else {
arg.stack_offset = Some(stack_offset);
arg.gprs_used = 0;
stack_offset += arg.alignment as i32;
}
}
CrossABIClass::Float => {
if fpr_used < self.max_fpr_params {
let reg_num = fpr_used;
arg.reg = Some(CrossPhysReg::new(reg_num, CrossRegClass::FPR128));
arg.fprs_used = 1;
fpr_used += 1;
} else {
arg.stack_offset = Some(stack_offset);
arg.fprs_used = 0;
stack_offset += arg.alignment as i32;
}
}
CrossABIClass::Split { .. } => {
if gpr_used + 2 <= self.max_gpr_params {
arg.reg = Some(CrossPhysReg::new(gpr_used, CrossRegClass::GPR64));
arg.gprs_used = 2;
gpr_used += 2;
} else {
arg.stack_offset = Some(stack_offset);
stack_offset += arg.alignment as i32;
}
}
CrossABIClass::Memory => {
arg.stack_offset = Some(stack_offset);
stack_offset += arg.alignment as i32;
}
CrossABIClass::NoClass => {}
}
self.args.push(arg);
}
Ok(self.args.clone())
}
pub fn lower_ret(&mut self, ret_ty: &TypeKindRepr) -> CrossABIRet {
let class = self.classify_ret(ret_ty);
let mut ret = CrossABIRet::new(ret_ty.clone());
ret.class = class;
match class {
CrossABIClass::Integer => {
ret.reg = Some(CrossPhysReg::new(0, CrossRegClass::GPR64)); ret.gprs_used = 1;
}
CrossABIClass::Float => {
ret.reg = Some(CrossPhysReg::new(0, CrossRegClass::FPR64)); ret.fprs_used = 1;
}
CrossABIClass::Split { .. } => {
ret.reg = Some(CrossPhysReg::new(0, CrossRegClass::GPR64));
ret.gprs_used = 2;
}
CrossABIClass::Memory => {
ret.sret = true;
ret.reg = Some(match self.arch {
BridgeArch::AArch64 => CrossPhysReg::new(8, CrossRegClass::GPR64),
BridgeArch::X86_64 => CrossPhysReg::new(0, CrossRegClass::GPR64), _ => CrossPhysReg::new(0, CrossRegClass::GPR32),
});
}
CrossABIClass::NoClass => {}
}
self.ret = ret.clone();
ret
}
pub fn setup_varargs(&mut self) {
self.varargs.is_variadic = true;
match self.arch {
BridgeArch::AArch64 => {
self.varargs.gpr_save_offset = 0;
self.varargs.fpr_save_offset = 64; }
BridgeArch::X86_64 => {
self.varargs.gpr_save_offset = 0;
self.varargs.fpr_save_offset = 0;
}
BridgeArch::ARM32 => {
self.varargs.gpr_save_offset = 0;
self.varargs.fpr_save_offset = 0;
}
BridgeArch::X86_32 => {
self.varargs.gpr_save_offset = 0;
self.varargs.fpr_save_offset = 0;
}
}
}
pub fn get_outgoing_arg_size(&self) -> u32 {
let size: i32 = self
.args
.iter()
.filter_map(|a| a.stack_offset.map(|o| o + a.alignment as i32))
.max()
.unwrap_or(0);
let aligned = (size as u32 + self.stack_alignment - 1) & !(self.stack_alignment - 1);
aligned
}
pub fn align_up(&self, size: u32) -> u32 {
(size + self.stack_alignment - 1) & !(self.stack_alignment - 1)
}
}
impl Default for CrossTargetABI {
fn default() -> Self {
Self::new(BridgeArch::AArch64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bridge_arch_display() {
assert_eq!(format!("{}", BridgeArch::X86_64), "x86_64");
assert_eq!(format!("{}", BridgeArch::X86_32), "i386");
assert_eq!(format!("{}", BridgeArch::AArch64), "aarch64");
assert_eq!(format!("{}", BridgeArch::ARM32), "arm");
}
#[test]
fn test_bridge_arch_is_64bit() {
assert!(BridgeArch::X86_64.is_64bit());
assert!(!BridgeArch::X86_32.is_64bit());
assert!(BridgeArch::AArch64.is_64bit());
assert!(!BridgeArch::ARM32.is_64bit());
}
#[test]
fn test_bridge_arch_is_arm_family() {
assert!(!BridgeArch::X86_64.is_arm_family());
assert!(BridgeArch::AArch64.is_arm_family());
assert!(BridgeArch::ARM32.is_arm_family());
}
#[test]
fn test_bridge_arch_is_x86_family() {
assert!(BridgeArch::X86_64.is_x86_family());
assert!(BridgeArch::X86_32.is_x86_family());
assert!(!BridgeArch::AArch64.is_x86_family());
}
#[test]
fn test_bridge_arch_pointer_width() {
assert_eq!(BridgeArch::X86_64.pointer_width(), 64);
assert_eq!(BridgeArch::X86_32.pointer_width(), 32);
assert_eq!(BridgeArch::AArch64.pointer_width(), 64);
assert_eq!(BridgeArch::ARM32.pointer_width(), 32);
}
#[test]
fn test_bridge_arch_data_layout() {
assert_eq!(BridgeArch::AArch64.data_layout(), AARCH64_DATA_LAYOUT);
assert_eq!(BridgeArch::X86_64.data_layout(), X86_64_DATA_LAYOUT);
}
#[test]
fn test_bridge_feature_display() {
assert_eq!(format!("{}", BridgeFeature::Neon), "neon");
assert_eq!(format!("{}", BridgeFeature::SVE), "sve");
assert_eq!(format!("{}", BridgeFeature::V8A), "v8a");
assert_eq!(format!("{}", BridgeFeature::AVX512), "avx512");
}
#[test]
fn test_bridge_features_enable_disable() {
let mut features = BridgeFeatures::default();
assert!(!features.has(BridgeFeature::Neon));
features.enable(BridgeFeature::Neon);
assert!(features.has(BridgeFeature::Neon));
features.disable(BridgeFeature::Neon);
assert!(!features.has(BridgeFeature::Neon));
}
#[test]
fn test_bridge_features_enable_armv8a() {
let mut features = BridgeFeatures::default();
features.enable_armv8a();
assert!(features.has(BridgeFeature::V8A));
assert!(features.has(BridgeFeature::Neon));
assert!(features.has(BridgeFeature::FMA));
assert!(features.has(BridgeFeature::CRC));
assert!(features.has(BridgeFeature::Crypto));
}
#[test]
fn test_bridge_features_enable_armv86a() {
let mut features = BridgeFeatures::default();
features.enable_armv86a();
assert!(features.has(BridgeFeature::V86A));
assert!(features.has(BridgeFeature::V8A)); assert!(features.has(BridgeFeature::Neon));
}
#[test]
fn test_bridge_features_enable_sve2() {
let mut features = BridgeFeatures::default();
features.enable_sve2();
assert!(features.has(BridgeFeature::SVE));
assert!(features.has(BridgeFeature::SVE2));
}
#[test]
fn test_bridge_features_to_feature_string() {
let mut features = BridgeFeatures::default();
features.enable(BridgeFeature::Neon);
features.enable(BridgeFeature::V8A);
let s = features.to_feature_string();
assert!(s.contains("+neon"));
assert!(s.contains("+v8a"));
}
#[test]
fn test_bridge_features_from_string() {
let features = BridgeFeatures::from_string("+neon,+v8a,-crypto");
assert!(features.has(BridgeFeature::Neon));
assert!(features.has(BridgeFeature::V8A));
assert!(!features.has(BridgeFeature::Crypto));
}
#[test]
fn test_bridge_features_from_string_empty() {
let features = BridgeFeatures::from_string("");
assert_eq!(features.list_enabled().len(), 0);
}
#[test]
fn test_bridge_features_list_enabled() {
let mut features = BridgeFeatures::default();
features.enable(BridgeFeature::Neon);
features.enable(BridgeFeature::FMA);
let list = features.list_enabled();
assert_eq!(list.len(), 2);
assert!(list.contains(&BridgeFeature::Neon));
assert!(list.contains(&BridgeFeature::FMA));
}
#[test]
fn test_bridge_create_default() {
let bridge = X86AArch64Bridge::default();
assert_eq!(bridge.source_arch, BridgeArch::X86_64);
assert_eq!(bridge.target_arch, BridgeArch::AArch64);
}
#[test]
fn test_bridge_create_custom() {
let bridge = X86AArch64Bridge::new(BridgeArch::AArch64, BridgeArch::X86_64);
assert_eq!(bridge.source_arch, BridgeArch::AArch64);
assert_eq!(bridge.target_arch, BridgeArch::X86_64);
}
#[test]
fn test_bridge_from_triples() {
let bridge =
X86AArch64Bridge::from_triples("x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu");
assert_eq!(bridge.source_arch, BridgeArch::X86_64);
assert_eq!(bridge.target_arch, BridgeArch::AArch64);
}
#[test]
fn test_bridge_describe() {
let bridge = X86AArch64Bridge::default();
let desc = bridge.describe();
assert!(desc.contains("X86AArch64Bridge"));
assert!(desc.contains("x86_64"));
assert!(desc.contains("aarch64"));
}
#[test]
fn test_bridge_set_opt_level() {
let mut bridge = X86AArch64Bridge::default();
bridge.set_opt_level(CodeGenOptLevel::Aggressive);
assert_eq!(bridge.opt_level, CodeGenOptLevel::Aggressive);
}
#[test]
fn test_bridge_enable_feature() {
let mut bridge = X86AArch64Bridge::default();
assert!(!bridge.has_feature(BridgeFeature::SVE));
bridge.enable_feature(BridgeFeature::SVE);
assert!(bridge.has_feature(BridgeFeature::SVE));
}
#[test]
fn test_isel_create() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
assert_eq!(isel.arch, BridgeArch::AArch64);
assert!(isel.pattern_count() > 0);
}
#[test]
fn test_isel_has_default_patterns() {
let isel = CrossTargetISel::new(BridgeArch::X86_64);
assert!(isel.pattern_count() >= 10);
}
#[test]
fn test_isel_allocate_vreg() {
let mut isel = CrossTargetISel::new(BridgeArch::AArch64);
let v1 = isel.allocate_vreg();
let v2 = isel.allocate_vreg();
assert_ne!(v1, v2);
}
#[test]
fn test_isel_is_legal() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
assert!(isel.is_legal(GenericMachineOpcode::Add, &TypeKindRepr::Integer(64)));
assert!(isel.is_legal(GenericMachineOpcode::SDiv, &TypeKindRepr::Integer(32)));
}
#[test]
fn test_isel_get_legalized_type() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
let legal = isel.get_legalized_type(&TypeKindRepr::Integer(1));
assert_eq!(legal, TypeKindRepr::Integer(8));
let legal = isel.get_legalized_type(&TypeKindRepr::Integer(8));
assert_eq!(legal, TypeKindRepr::Integer(32));
let legal = isel.get_legalized_type(&TypeKindRepr::Integer(64));
assert_eq!(legal, TypeKindRepr::Integer(64));
}
#[test]
fn test_isel_legalize_f16_to_f32() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
let legal = isel.get_legalized_type(&TypeKindRepr::Float(16));
assert_eq!(legal, TypeKindRepr::Float(32));
}
#[test]
fn test_type_kind_repr_size_bits() {
assert_eq!(TypeKindRepr::Integer(32).size_bits(), 32);
assert_eq!(TypeKindRepr::Float(64).size_bits(), 64);
assert_eq!(TypeKindRepr::Pointer.size_bits(), 64);
assert_eq!(TypeKindRepr::Void.size_bits(), 0);
}
#[test]
fn test_type_kind_repr_vector_size() {
let v4i32 = TypeKindRepr::Vector(4, Box::new(TypeKindRepr::Integer(32)));
assert_eq!(v4i32.size_bits(), 128);
assert!(v4i32.is_vector());
}
#[test]
fn test_type_kind_repr_is_integer() {
assert!(TypeKindRepr::Integer(64).is_integer());
assert!(!TypeKindRepr::Float(64).is_integer());
}
#[test]
fn test_type_kind_repr_is_float() {
assert!(TypeKindRepr::Float(32).is_float());
assert!(!TypeKindRepr::Integer(32).is_float());
}
#[test]
fn test_generic_opcode_is_terminator() {
assert!(GenericMachineOpcode::Br.is_terminator());
assert!(GenericMachineOpcode::Br.is_terminator());
assert!(GenericMachineOpcode::Ret.is_terminator());
assert!(!GenericMachineOpcode::Add.is_terminator());
}
#[test]
fn test_generic_opcode_is_memory() {
assert!(GenericMachineOpcode::Load.is_memory());
assert!(GenericMachineOpcode::Store.is_memory());
assert!(!GenericMachineOpcode::Add.is_memory());
}
#[test]
fn test_generic_opcode_is_commutative() {
assert!(GenericMachineOpcode::Add.is_commutative());
assert!(GenericMachineOpcode::Mul.is_commutative());
assert!(GenericMachineOpcode::And.is_commutative());
assert!(!GenericMachineOpcode::Sub.is_commutative());
assert!(!GenericMachineOpcode::SDiv.is_commutative());
}
#[test]
fn test_pattern_creation() {
let pattern = DAGPattern {
name: "test".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Add),
result: PatternResult {
opcode: GenericMachineOpcode::Add,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
};
assert_eq!(pattern.name, "test");
assert_eq!(pattern.cost, 1);
}
#[test]
fn test_cross_reg_class_size_bits() {
assert_eq!(CrossRegClass::GPR64.size_bits(), 64);
assert_eq!(CrossRegClass::GPR32.size_bits(), 32);
assert_eq!(CrossRegClass::FPR128.size_bits(), 128);
assert_eq!(CrossRegClass::VR256.size_bits(), 256);
assert_eq!(CrossRegClass::VR512.size_bits(), 512);
}
#[test]
fn test_cross_reg_class_reg_count_aarch64() {
assert_eq!(CrossRegClass::GPR64.reg_count(BridgeArch::AArch64), 31);
assert_eq!(CrossRegClass::FPR128.reg_count(BridgeArch::AArch64), 32);
}
#[test]
fn test_cross_reg_class_reg_count_x86_64() {
assert_eq!(CrossRegClass::GPR64.reg_count(BridgeArch::X86_64), 16);
assert_eq!(CrossRegClass::FPR128.reg_count(BridgeArch::X86_64), 16);
}
#[test]
fn test_cross_reg_class_name() {
assert_eq!(CrossRegClass::GPR64.name(), "GPR64");
assert_eq!(CrossRegClass::FPR128.name(), "FPR128");
}
#[test]
fn test_cross_reg_class_to_arm_reg_class() {
assert_eq!(
CrossRegClass::GPR64.to_arm_reg_class(),
Some(ArmRegClass::GPR64)
);
assert_eq!(
CrossRegClass::FPR128.to_arm_reg_class(),
Some(ArmRegClass::FPR128)
);
assert_eq!(CrossRegClass::VR256.to_arm_reg_class(), None);
}
#[test]
fn test_cross_phys_reg_creation() {
let reg = CrossPhysReg::new(0, CrossRegClass::GPR64);
assert_eq!(reg.number, 0);
assert_eq!(reg.class, CrossRegClass::GPR64);
}
#[test]
fn test_cross_phys_reg_aarch64_mapping() {
let reg = CrossPhysReg::new(0, CrossRegClass::GPR64);
assert_eq!(reg.to_aarch64_reg(), 0);
let reg32 = CrossPhysReg::new(0, CrossRegClass::GPR32);
assert_eq!(reg32.to_aarch64_reg(), 100);
}
#[test]
fn test_cross_phys_reg_x86_mapping() {
let reg = CrossPhysReg::new(0, CrossRegClass::GPR64);
assert_eq!(reg.to_x86_reg(), 0);
let reg32 = CrossPhysReg::new(0, CrossRegClass::GPR32);
assert_eq!(reg32.to_x86_reg(), 50);
}
#[test]
fn test_allocation_order_aarch64_defaults() {
let orders = AllocationOrder::aarch64_defaults();
assert_eq!(orders.len(), 2);
let gpr64 = &orders[0];
assert_eq!(gpr64.class, CrossRegClass::GPR64);
assert!(gpr64
.reserved
.contains(&CrossPhysReg::new(31, CrossRegClass::GPR64))); assert!(!gpr64.order.is_empty());
}
#[test]
fn test_allocation_order_x86_64_defaults() {
let orders = AllocationOrder::x86_64_defaults();
assert_eq!(orders.len(), 2);
let gpr64 = &orders[0];
assert_eq!(gpr64.class, CrossRegClass::GPR64);
assert!(gpr64
.reserved
.contains(&CrossPhysReg::new(4, CrossRegClass::GPR64))); }
#[test]
fn test_allocation_order_for_arch() {
let aarch64 = AllocationOrder::for_arch(BridgeArch::AArch64);
let x86 = AllocationOrder::for_arch(BridgeArch::X86_64);
assert_eq!(aarch64.len(), 2);
assert_eq!(x86.len(), 2);
}
#[test]
fn test_live_interval_add_range() {
let mut interval = LiveInterval::new(0, CrossRegClass::GPR64);
interval.add_range(0, 10);
assert_eq!(interval.ranges.len(), 1);
assert_eq!(interval.ranges[0], (0, 10));
}
#[test]
fn test_live_interval_overlaps() {
let mut a = LiveInterval::new(0, CrossRegClass::GPR64);
a.add_range(0, 10);
let mut b = LiveInterval::new(1, CrossRegClass::GPR64);
b.add_range(5, 15);
assert!(a.overlaps(&b));
let mut c = LiveInterval::new(2, CrossRegClass::GPR64);
c.add_range(11, 20);
assert!(!a.overlaps(&c));
}
#[test]
fn test_live_interval_compute_priority() {
let mut interval = LiveInterval::new(0, CrossRegClass::GPR64);
interval.add_range(0, 100);
interval.compute_priority();
assert!(interval.priority > 0.0);
}
#[test]
fn test_regalloc_create() {
let ra = CrossTargetRegAlloc::new(BridgeArch::AArch64);
assert_eq!(ra.arch, BridgeArch::AArch64);
assert_eq!(ra.interval_count(), 0);
assert_eq!(ra.spill_slot_count(), 0);
}
#[test]
fn test_regalloc_alloc_orders() {
let ra = CrossTargetRegAlloc::new(BridgeArch::AArch64);
assert_eq!(ra.alloc_orders.len(), 2);
}
#[test]
fn test_regalloc_alloc_orders_x86() {
let ra = CrossTargetRegAlloc::new(BridgeArch::X86_64);
assert_eq!(ra.alloc_orders.len(), 2);
}
#[test]
fn test_stack_frame_aarch64() {
let frame = CrossStackFrame::new(BridgeArch::AArch64);
assert_eq!(frame.stack_alignment, 16);
assert_eq!(frame.red_zone_size, 0);
assert_eq!(frame.frame_size, 0);
}
#[test]
fn test_stack_frame_x86_64() {
let frame = CrossStackFrame::new(BridgeArch::X86_64);
assert_eq!(frame.stack_alignment, 16);
assert_eq!(frame.red_zone_size, 128);
}
#[test]
fn test_stack_frame_align_up() {
let frame = CrossStackFrame::new(BridgeArch::AArch64);
assert_eq!(frame.align_up(1), 16);
assert_eq!(frame.align_up(16), 16);
assert_eq!(frame.align_up(17), 32);
}
#[test]
fn test_stack_frame_compute_total_size() {
let mut frame = CrossStackFrame::new(BridgeArch::AArch64);
frame.callee_saved_size = 80; frame.max_call_frame_size = 32;
let size = frame.compute_total_size();
assert_eq!(size, 112); }
#[test]
fn test_stack_frame_compute_total_size_aligns() {
let mut frame = CrossStackFrame::new(BridgeArch::AArch64);
frame.callee_saved_size = 10;
frame.max_call_frame_size = 0;
let size = frame.compute_total_size();
assert_eq!(size, 16); }
#[test]
fn test_stack_frame_fp_reg() {
let frame = CrossStackFrame::new(BridgeArch::AArch64);
assert_eq!(frame.frame_pointer_reg().number, 29);
assert_eq!(frame.frame_pointer_reg().class, CrossRegClass::GPR64);
}
#[test]
fn test_stack_frame_sp_reg() {
let frame_aarch64 = CrossStackFrame::new(BridgeArch::AArch64);
assert_eq!(frame_aarch64.stack_pointer_reg().number, 31);
let frame_x86 = CrossStackFrame::new(BridgeArch::X86_64);
assert_eq!(frame_x86.stack_pointer_reg().number, 4);
}
#[test]
fn test_frame_lowering_create() {
let fl = CrossTargetFrameLowering::new(BridgeArch::AArch64);
assert_eq!(fl.arch, BridgeArch::AArch64);
}
#[test]
fn test_frame_lowering_needs_stack_probe() {
let fl = CrossTargetFrameLowering::new(BridgeArch::AArch64);
assert!(!fl.needs_stack_probe());
}
#[test]
fn test_frame_lowering_has_red_zone() {
let fl_aarch64 = CrossTargetFrameLowering::new(BridgeArch::AArch64);
assert!(!fl_aarch64.has_red_zone());
let fl_x86 = CrossTargetFrameLowering::new(BridgeArch::X86_64);
assert!(fl_x86.has_red_zone());
}
#[test]
fn test_frame_lowering_get_frame_index_ref() {
let fl = CrossTargetFrameLowering::new(BridgeArch::AArch64);
let offset = fl.get_frame_index_ref(0);
assert!(offset <= 0);
}
#[test]
fn test_frame_lowering_get_emergency_spill_slot() {
let fl = CrossTargetFrameLowering::new(BridgeArch::AArch64);
let slot0 = fl.get_emergency_spill_slot(0);
let slot1 = fl.get_emergency_spill_slot(1);
assert_eq!(slot0, -16);
assert_eq!(slot1, -24);
}
#[test]
fn test_cost_model_create() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
assert_eq!(model.source_arch, BridgeArch::X86_64);
assert_eq!(model.target_arch, BridgeArch::AArch64);
assert!(model.table_size() > 0);
}
#[test]
fn test_cost_model_estimate_add() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
let est = model.estimate(Opcode::Add, 2);
assert_eq!(est.latency, 1);
assert!(est.profitable);
}
#[test]
fn test_cost_model_estimate_sdiv() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
let est = model.estimate(Opcode::SDiv, 2);
assert_eq!(est.latency, 12); }
#[test]
fn test_cost_model_estimate_load() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
let est = model.estimate(Opcode::Load, 1);
assert_eq!(est.code_size, 4);
}
#[test]
fn test_cost_model_is_cheaper_on_aarch64() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
assert!(model.is_cheaper_on_aarch64(Opcode::SDiv));
assert!(!model.is_cheaper_on_aarch64(Opcode::Add));
}
#[test]
fn test_cost_model_vectorizable() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
let est = model.estimate(Opcode::Add, 2);
assert!(est.vectorizable);
}
#[test]
fn test_cost_model_get_cost() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
let cost = model.get_cost(Opcode::Add);
assert!(cost.is_some());
let c = cost.unwrap();
assert_eq!(c.x86_latency, 1);
assert_eq!(c.aarch64_latency, 1);
}
#[test]
fn test_cost_model_advantages() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
let aarch64_adv = model.aarch64_advantages();
let x86_adv = model.x86_advantages();
assert!(!aarch64_adv.is_empty() || !x86_adv.is_empty());
}
#[test]
fn test_cost_model_is_profitable() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
assert!(model.is_profitable(Opcode::Add, "fold_imm"));
assert!(model.is_profitable(Opcode::Add, "use_postinc"));
assert!(model.is_profitable(Opcode::Add, "unroll_loop"));
}
#[test]
fn test_a64_opcode_mnemonic() {
assert_eq!(A64Opcode::ADD.mnemonic(), "add");
assert_eq!(A64Opcode::LDR.mnemonic(), "ldr");
assert_eq!(A64Opcode::RET.mnemonic(), "ret");
}
#[test]
fn test_a64_opcode_display() {
assert_eq!(format!("{}", A64Opcode::ADD), "add");
assert_eq!(format!("{}", A64Opcode::B), "b");
}
#[test]
fn test_a64_opcode_is_branch() {
assert!(A64Opcode::B.is_branch());
assert!(A64Opcode::RET.is_branch());
assert!(A64Opcode::CBZ.is_branch());
assert!(!A64Opcode::ADD.is_branch());
}
#[test]
fn test_a64_opcode_is_memory() {
assert!(A64Opcode::LDR.is_memory());
assert!(A64Opcode::STR.is_memory());
assert!(A64Opcode::LDP.is_memory());
assert!(!A64Opcode::ADD.is_memory());
}
#[test]
fn test_a64_opcode_is_fp() {
assert!(A64Opcode::FADD.is_fp());
assert!(A64Opcode::FMUL.is_fp());
assert!(!A64Opcode::ADD.is_fp());
}
#[test]
fn test_a64_opcode_is_simd() {
assert!(A64Opcode::ADDV.is_simd());
assert!(!A64Opcode::ADD.is_simd());
}
#[test]
fn test_a64_opcode_num_src_operands() {
assert_eq!(A64Opcode::ADD.num_src_operands(), 2);
assert_eq!(A64Opcode::MOV.num_src_operands(), 1);
assert_eq!(A64Opcode::NOP.num_src_operands(), 0);
assert_eq!(A64Opcode::LDR.num_src_operands(), 1);
assert_eq!(A64Opcode::STR.num_src_operands(), 2);
}
#[test]
fn test_a64_cond_invert() {
assert_eq!(A64Cond::EQ.invert(), A64Cond::NE);
assert_eq!(A64Cond::NE.invert(), A64Cond::EQ);
assert_eq!(A64Cond::GE.invert(), A64Cond::LT);
assert_eq!(A64Cond::GT.invert(), A64Cond::LE);
}
#[test]
fn test_a64_cond_encoding() {
assert_eq!(A64Cond::EQ.encoding(), 0b0000);
assert_eq!(A64Cond::NE.encoding(), 0b0001);
assert_eq!(A64Cond::AL.encoding(), 0b1110);
assert_eq!(A64Cond::NV.encoding(), 0b1111);
}
#[test]
fn test_a64_cond_display() {
assert_eq!(format!("{}", A64Cond::EQ), "eq");
assert_eq!(format!("{}", A64Cond::GE), "ge");
}
#[test]
fn test_a64_instr_table_create() {
let table = A64InstrTable::new();
assert!(table.size() > 0);
}
#[test]
fn test_a64_instr_table_lookup() {
let table = A64InstrTable::new();
let desc = table.lookup(A64Opcode::ADD);
assert!(desc.is_some());
let d = desc.unwrap();
assert_eq!(d.mnemonic, "add");
assert_eq!(d.num_src, 2);
}
#[test]
fn test_a64_instr_table_lookup_mnemonic() {
let table = A64InstrTable::new();
assert_eq!(table.lookup_mnemonic("add"), Some(A64Opcode::ADD));
assert_eq!(table.lookup_mnemonic("ldr"), Some(A64Opcode::LDR));
assert_eq!(table.lookup_mnemonic("ret"), Some(A64Opcode::RET));
assert_eq!(table.lookup_mnemonic("nonexistent"), None);
}
#[test]
fn test_a64_instr_table_has_all_basic_opcodes() {
let table = A64InstrTable::new();
let expected: &[A64Opcode] = &[
A64Opcode::ADD,
A64Opcode::SUB,
A64Opcode::MOV,
A64Opcode::LDR,
A64Opcode::STR,
A64Opcode::B,
A64Opcode::BL,
A64Opcode::RET,
];
for op in expected {
assert!(table.lookup(*op).is_some(), "missing opcode: {:?}", op);
}
}
#[test]
fn test_aarch64_target_machine_linux_gnu() {
let tm = AArch64TargetMachine::linux_gnu();
assert_eq!(tm.triple, "aarch64-unknown-linux-gnu");
assert!(tm.is_64bit);
assert_eq!(tm.abi, "aapcs");
}
#[test]
fn test_aarch64_target_machine_apple_darwin() {
let tm = AArch64TargetMachine::apple_darwin();
assert_eq!(tm.triple, "aarch64-apple-darwin");
assert_eq!(tm.abi, "darwinpcs");
}
#[test]
fn test_aarch64_target_machine_android() {
let tm = AArch64TargetMachine::android();
assert!(tm.triple.contains("android"));
}
#[test]
fn test_aarch64_target_machine_windows() {
let tm = AArch64TargetMachine::windows_msvc();
assert_eq!(tm.abi, "msvc");
}
#[test]
fn test_aarch64_target_machine_data_layout() {
let tm = AArch64TargetMachine::linux_gnu();
assert_eq!(tm.get_data_layout(), AARCH64_DATA_LAYOUT);
}
#[test]
fn test_aarch64_target_machine_features() {
let tm = AArch64TargetMachine::linux_gnu();
assert!(tm.has_feature(BridgeFeature::Neon));
assert!(tm.has_feature(BridgeFeature::V8A));
assert!(!tm.has_feature(BridgeFeature::SVE));
}
#[test]
fn test_aarch64_target_machine_enable_disable_feature() {
let mut tm = AArch64TargetMachine::linux_gnu();
assert!(!tm.has_feature(BridgeFeature::SVE));
tm.enable_feature(BridgeFeature::SVE);
assert!(tm.has_feature(BridgeFeature::SVE));
tm.disable_feature(BridgeFeature::SVE);
assert!(!tm.has_feature(BridgeFeature::SVE));
}
#[test]
fn test_aarch64_target_machine_lookup_instr() {
let tm = AArch64TargetMachine::linux_gnu();
assert_eq!(tm.lookup_instr("add"), Some(A64Opcode::ADD));
assert_eq!(tm.lookup_instr("ret"), Some(A64Opcode::RET));
}
#[test]
fn test_aarch64_target_machine_get_instr_desc() {
let tm = AArch64TargetMachine::linux_gnu();
let desc = tm.get_instr_desc(A64Opcode::ADD);
assert!(desc.is_some());
assert_eq!(desc.unwrap().mnemonic, "add");
}
#[test]
fn test_aarch64_target_machine_is_valid_target() {
assert!(AArch64TargetMachine::is_valid_target(
"aarch64-unknown-linux-gnu"
));
assert!(AArch64TargetMachine::is_valid_target(
"arm-unknown-linux-gnueabi"
));
assert!(!AArch64TargetMachine::is_valid_target(
"x86_64-unknown-linux-gnu"
));
}
#[test]
fn test_aarch64_target_machine_pointer_width() {
let tm64 = AArch64TargetMachine::linux_gnu();
assert_eq!(tm64.pointer_width(), 64);
}
#[test]
fn test_aarch64_target_machine_little_endian() {
let tm = AArch64TargetMachine::linux_gnu();
assert!(tm.is_little_endian());
}
#[test]
fn test_aarch64_target_machine_describe() {
let tm = AArch64TargetMachine::linux_gnu();
let desc = tm.describe();
assert!(desc.contains("AArch64TargetMachine"));
assert!(desc.contains("aarch64-unknown-linux-gnu"));
}
#[test]
fn test_aarch64_target_machine_reg_classes() {
let tm = AArch64TargetMachine::linux_gnu();
assert_eq!(tm.reg_classes.len(), 4);
assert!(tm.reg_classes.contains(&CrossRegClass::GPR64));
assert!(tm.reg_classes.contains(&CrossRegClass::GPR32));
assert!(tm.reg_classes.contains(&CrossRegClass::FPR128));
assert!(tm.reg_classes.contains(&CrossRegClass::FPR64));
}
#[test]
fn test_cross_opt_pass_enabled_at_none() {
assert!(!CrossOptPass::LICM.enabled_at(CodeGenOptLevel::None));
assert!(!CrossOptPass::Peephole.enabled_at(CodeGenOptLevel::None));
}
#[test]
fn test_cross_opt_pass_enabled_at_less() {
assert!(CrossOptPass::ConstantFolding.enabled_at(CodeGenOptLevel::Less));
assert!(CrossOptPass::Peephole.enabled_at(CodeGenOptLevel::Less));
assert!(!CrossOptPass::LICM.enabled_at(CodeGenOptLevel::Less));
}
#[test]
fn test_cross_opt_pass_enabled_at_default() {
assert!(CrossOptPass::LICM.enabled_at(CodeGenOptLevel::Default));
assert!(!CrossOptPass::LoopUnrolling.enabled_at(CodeGenOptLevel::Default));
}
#[test]
fn test_cross_opt_pass_enabled_at_aggressive() {
assert!(CrossOptPass::LICM.enabled_at(CodeGenOptLevel::Aggressive));
assert!(CrossOptPass::LoopUnrolling.enabled_at(CodeGenOptLevel::Aggressive));
assert!(CrossOptPass::TailDuplication.enabled_at(CodeGenOptLevel::Aggressive));
}
#[test]
fn test_cross_opt_pass_name() {
assert_eq!(CrossOptPass::LICM.name(), "licm");
assert_eq!(CrossOptPass::Peephole.name(), "peephole");
assert_eq!(CrossOptPass::IfConversion.name(), "if-conversion");
}
#[test]
fn test_optimizer_create() {
let opt = CrossTargetOptimization::new(BridgeArch::AArch64);
assert_eq!(opt.arch, BridgeArch::AArch64);
assert!(!opt.passes.is_empty());
}
#[test]
fn test_optimizer_loop_patterns() {
let opt = CrossTargetOptimization::new(BridgeArch::AArch64);
assert!(!opt.loop_patterns.is_empty());
let pattern = opt.get_loop_pattern("counted_to_decrement");
assert!(pattern.is_some());
assert!(pattern.unwrap().applies_to(BridgeArch::AArch64));
}
#[test]
fn test_optimizer_applicable_loop_patterns() {
let opt = CrossTargetOptimization::new(BridgeArch::AArch64);
let patterns = opt.applicable_loop_patterns();
assert!(!patterns.is_empty());
let postinc = patterns.iter().find(|p| p.name == "postinc_addressing");
assert!(postinc.is_some());
}
#[test]
fn test_optimizer_x86_loop_patterns() {
let opt = CrossTargetOptimization::new(BridgeArch::X86_64);
let patterns = opt.applicable_loop_patterns();
let lea = patterns.iter().find(|p| p.name == "lea_address_gen");
assert!(lea.is_some());
}
#[test]
fn test_optimizer_enable_disable_pass() {
let mut opt = CrossTargetOptimization::new(BridgeArch::AArch64);
opt.disable_pass(CrossOptPass::LICM);
assert!(!opt.passes.contains(&CrossOptPass::LICM));
opt.enable_pass(CrossOptPass::LICM);
assert!(opt.passes.contains(&CrossOptPass::LICM));
}
#[test]
fn test_optimizer_set_opt_level_none() {
let mut opt = CrossTargetOptimization::new(BridgeArch::AArch64);
opt.set_opt_level(CodeGenOptLevel::None);
assert!(opt.passes.is_empty());
}
#[test]
fn test_optimizer_set_opt_level_aggressive() {
let mut opt = CrossTargetOptimization::new(BridgeArch::AArch64);
opt.set_opt_level(CodeGenOptLevel::Aggressive);
assert!(opt.passes.contains(&CrossOptPass::LoopUnrolling));
}
#[test]
fn test_optimizer_vectorization_hint() {
let mut opt = CrossTargetOptimization::new(BridgeArch::AArch64);
opt.set_vectorization_hint(VectorizationHint::VectorWidth(128));
assert_eq!(opt.vectorization_hint, VectorizationHint::VectorWidth(128));
}
#[test]
fn test_loop_pattern_creation() {
let p = LoopOptimizationPattern::new("test", "test pattern", true, false, 4);
assert_eq!(p.name, "test");
assert_eq!(p.description, "test pattern");
assert!(p.applies_x86);
assert!(!p.applies_aarch64);
assert_eq!(p.threshold, 4);
}
#[test]
fn test_loop_pattern_applies_to() {
let p = LoopOptimizationPattern::new("test", "", true, false, 1);
assert!(p.applies_to(BridgeArch::X86_64));
assert!(!p.applies_to(BridgeArch::AArch64));
}
#[test]
fn test_abi_class_is_integer() {
assert!(CrossABIClass::Integer.is_integer());
assert!(!CrossABIClass::Float.is_integer());
assert!(!CrossABIClass::Memory.is_integer());
}
#[test]
fn test_abi_class_is_float() {
assert!(CrossABIClass::Float.is_float());
assert!(!CrossABIClass::Integer.is_float());
}
#[test]
fn test_abi_class_is_memory() {
assert!(CrossABIClass::Memory.is_memory());
assert!(!CrossABIClass::Integer.is_memory());
}
#[test]
fn test_abi_class_display() {
assert_eq!(format!("{}", CrossABIClass::Integer), "integer");
assert_eq!(format!("{}", CrossABIClass::Float), "float");
assert_eq!(format!("{}", CrossABIClass::NoClass), "none");
}
#[test]
fn test_abi_create() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
assert_eq!(abi.arch, BridgeArch::AArch64);
assert_eq!(abi.max_gpr_params, 8);
assert_eq!(abi.max_fpr_params, 8);
assert_eq!(abi.stack_alignment, 16);
}
#[test]
fn test_abi_create_x86_64() {
let abi = CrossTargetABI::new(BridgeArch::X86_64);
assert_eq!(abi.max_gpr_params, 6);
assert_eq!(abi.max_fpr_params, 8);
}
#[test]
fn test_abi_classify_arg_i32() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
assert_eq!(
abi.classify_arg(&TypeKindRepr::Integer(32)),
CrossABIClass::Integer
);
}
#[test]
fn test_abi_classify_arg_f32() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
assert_eq!(
abi.classify_arg(&TypeKindRepr::Float(32)),
CrossABIClass::Float
);
}
#[test]
fn test_abi_classify_arg_i128_aarch64() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
let class = abi.classify_arg(&TypeKindRepr::Integer(128));
assert!(class.is_split());
}
#[test]
fn test_abi_classify_arg_pointer() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
assert_eq!(
abi.classify_arg(&TypeKindRepr::Pointer),
CrossABIClass::Integer
);
}
#[test]
fn test_abi_classify_arg_void() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
assert_eq!(
abi.classify_arg(&TypeKindRepr::Void),
CrossABIClass::NoClass
);
}
#[test]
fn test_abi_classify_ret_void() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
assert_eq!(
abi.classify_ret(&TypeKindRepr::Void),
CrossABIClass::NoClass
);
}
#[test]
fn test_abi_classify_ret_i32() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
assert_eq!(
abi.classify_ret(&TypeKindRepr::Integer(32)),
CrossABIClass::Integer
);
}
#[test]
fn test_abi_classify_ret_i128() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
let class = abi.classify_ret(&TypeKindRepr::Integer(128));
assert!(class.is_split());
}
#[test]
fn test_abi_lower_args_basic() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
let args = abi
.lower_args(&[TypeKindRepr::Integer(64), TypeKindRepr::Integer(64)])
.unwrap();
assert_eq!(args.len(), 2);
assert_eq!(args[0].class, CrossABIClass::Integer);
assert!(args[0].reg.is_some());
assert_eq!(args[1].class, CrossABIClass::Integer);
assert!(args[1].reg.is_some());
}
#[test]
fn test_abi_lower_args_mixed() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
let args = abi
.lower_args(&[TypeKindRepr::Integer(64), TypeKindRepr::Float(64)])
.unwrap();
assert_eq!(args.len(), 2);
assert_eq!(args[0].class, CrossABIClass::Integer);
assert_eq!(args[1].class, CrossABIClass::Float);
}
#[test]
fn test_abi_lower_ret() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
let ret = abi.lower_ret(&TypeKindRepr::Integer(64));
assert_eq!(ret.class, CrossABIClass::Integer);
assert_eq!(ret.reg, Some(CrossPhysReg::new(0, CrossRegClass::GPR64)));
}
#[test]
fn test_abi_varargs() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
abi.setup_varargs();
assert!(abi.varargs.is_variadic);
assert_eq!(abi.varargs.gpr_save_size(), 64);
assert_eq!(abi.varargs.fpr_save_size(), 128);
}
#[test]
fn test_abi_get_outgoing_arg_size() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
let size = abi.get_outgoing_arg_size();
assert_eq!(size, 0);
}
#[test]
fn test_abi_align_up() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
assert_eq!(abi.align_up(1), 16);
assert_eq!(abi.align_up(16), 16);
assert_eq!(abi.align_up(17), 32);
}
#[test]
fn test_bridge_error_display() {
let err = BridgeError::UnsupportedArchPair {
source: BridgeArch::AArch64,
target: BridgeArch::X86_64,
};
let s = format!("{}", err);
assert!(s.contains("unsupported"));
let err = BridgeError::ISelFailed {
opcode: Opcode::Add as u32,
reason: "test".into(),
};
let s = format!("{}", err);
assert!(s.contains("instruction selection failed"));
let err = BridgeError::RegAllocFailed {
reason: "spill".into(),
};
let s = format!("{}", err);
assert!(s.contains("register allocation"));
}
#[test]
fn test_cost_estimate_zero() {
let est = CostEstimate::zero();
assert_eq!(est.latency, 0);
assert_eq!(est.throughput, 0);
assert!(!est.profitable);
}
#[test]
fn test_cost_estimate_new() {
let est = CostEstimate::new(5, 2, 4);
assert_eq!(est.latency, 5);
assert_eq!(est.throughput, 2);
assert_eq!(est.code_size, 4);
assert!(est.profitable);
}
#[test]
fn test_machine_ir_inst_creation() {
let inst = MachineIRInst::new(GenericMachineOpcode::Add)
.with_dst(0)
.with_src(MachineIROperand::VReg(1))
.with_src(MachineIROperand::Imm(42));
assert_eq!(inst.opcode, GenericMachineOpcode::Add);
assert_eq!(inst.def, Some(0));
assert_eq!(inst.operands.len(), 2);
}
#[test]
fn test_machine_ir_operand_is_reg() {
assert!(MachineIROperand::VReg(0).is_reg());
assert!(!MachineIROperand::Imm(0).is_reg());
}
#[test]
fn test_machine_ir_operand_is_imm() {
assert!(MachineIROperand::Imm(42).is_imm());
assert!(MachineIROperand::FImm(3.14).is_imm());
assert!(!MachineIROperand::VReg(0).is_imm());
}
#[test]
fn test_spill_slot_creation() {
let slot = SpillSlot {
index: 0,
size: 8,
alignment: 8,
vreg: 5,
};
assert_eq!(slot.index, 0);
assert_eq!(slot.size, 8);
assert_eq!(slot.vreg, 5);
}
#[test]
fn test_abi_arg_creation() {
let arg = CrossABIArg::new(TypeKindRepr::Integer(64));
assert_eq!(arg.alignment, 8);
assert_eq!(arg.gprs_used, 0);
assert!(!arg.by_val);
}
#[test]
fn test_abi_arg_f32_alignment() {
let arg = CrossABIArg::new(TypeKindRepr::Float(32));
assert_eq!(arg.alignment, 4);
}
#[test]
fn test_bridge_stats_default() {
let stats = BridgeStats::default();
assert_eq!(stats.functions_processed, 0);
assert_eq!(stats.isel_cycles, 0);
assert_eq!(stats.spill_slots, 0);
}
#[test]
fn test_stack_frame_return_address_reg() {
let frame = CrossStackFrame::new(BridgeArch::AArch64);
assert_eq!(frame.return_address_reg().number, 30);
let frame_x86 = CrossStackFrame::new(BridgeArch::X86_64);
assert_eq!(frame_x86.return_address_reg().number, 0);
}
#[test]
fn test_generic_opcode_is_terminator_all() {
let terminators: &[GenericMachineOpcode] = &[
GenericMachineOpcode::Br,
GenericMachineOpcode::Br,
GenericMachineOpcode::Ret,
];
for op in terminators {
assert!(op.is_terminator(), "{:?} should be terminator", op);
}
}
#[test]
fn test_pattern_predicate_types() {
let preds = [
PatternPredicate::IsPowerOfTwo,
PatternPredicate::FitsInBits(12),
PatternPredicate::IsLogicalImm,
PatternPredicate::IsArithImm,
PatternPredicate::IsFloatingPoint,
PatternPredicate::IsInteger,
PatternPredicate::IsVector,
];
assert_eq!(preds.len(), 7);
}
#[test]
fn test_legalize_action_enum() {
let actions = [
LegalizeAction::Promote,
LegalizeAction::Split,
LegalizeAction::Widen,
LegalizeAction::Narrow,
LegalizeAction::Expand,
LegalizeAction::SoftenFloat,
LegalizeAction::Scalarize,
];
assert_eq!(actions.len(), 7);
}
#[test]
fn test_varargs_default() {
let va = CrossVarArgs::new();
assert!(!va.is_variadic);
assert_eq!(va.gpr_save_size(), 0);
assert_eq!(va.fpr_save_size(), 0);
}
#[test]
fn test_varargs_enabled() {
let mut va = CrossVarArgs::new();
va.is_variadic = true;
assert_eq!(va.gpr_save_size(), 64);
assert_eq!(va.fpr_save_size(), 128);
}
#[test]
fn test_bridge_output() {
let output = BridgeOutput {
instructions_emitted: 42,
basic_blocks: 3,
target_arch: BridgeArch::AArch64,
};
assert_eq!(output.instructions_emitted, 42);
assert_eq!(output.basic_blocks, 3);
assert_eq!(output.target_arch, BridgeArch::AArch64);
}
#[test]
fn test_aarch64_target_machine_default() {
let tm = AArch64TargetMachine::default();
assert_eq!(tm.triple, "aarch64-unknown-linux-gnu");
}
#[test]
fn test_bridge_full_pipeline_no_crash() {
let mut bridge =
X86AArch64Bridge::from_triples("x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu");
bridge.set_opt_level(CodeGenOptLevel::Default);
assert!(bridge.isel.pattern_count() > 0);
assert_eq!(bridge.regalloc.alloc_orders.len(), 2);
assert_eq!(bridge.cost_model.table_size(), 24);
assert_eq!(bridge.abi.max_gpr_params, 8);
assert!(!bridge.optimizer.passes.is_empty());
let desc = bridge.describe();
assert!(desc.contains("X86AArch64Bridge"));
}
#[test]
fn test_cross_aarch64_to_x86_bridge() {
let bridge = X86AArch64Bridge::new(BridgeArch::AArch64, BridgeArch::X86_64);
assert_eq!(bridge.source_arch, BridgeArch::AArch64);
assert_eq!(bridge.target_arch, BridgeArch::X86_64);
let est = bridge.estimate_cost(Opcode::Add, 2);
assert_eq!(est.latency, 1);
}
#[test]
fn test_feature_parsing_roundtrip() {
let features = BridgeFeatures::from_string("+neon,+sve,+crypto,-crc");
let s = features.to_feature_string();
let parsed = BridgeFeatures::from_string(&s);
assert_eq!(features.list_enabled().len(), parsed.list_enabled().len());
for f in features.list_enabled() {
assert!(parsed.has(f), "feature {:?} should be present", f);
}
}
#[test]
fn test_all_a64_opcodes_have_table_entries() {
let table = A64InstrTable::new();
let critical: &[A64Opcode] = &[
A64Opcode::ADD,
A64Opcode::SUB,
A64Opcode::MUL,
A64Opcode::MOV,
A64Opcode::LDR,
A64Opcode::STR,
A64Opcode::B,
A64Opcode::BL,
A64Opcode::RET,
A64Opcode::NOP,
];
for op in critical {
assert!(
table.lookup(*op).is_some(),
"Critical opcode {:?} missing from table",
op
);
}
}
#[test]
fn test_abi_lower_ret_f32() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
let ret = abi.lower_ret(&TypeKindRepr::Float(32));
assert_eq!(ret.class, CrossABIClass::Float);
assert_eq!(ret.reg, Some(CrossPhysReg::new(0, CrossRegClass::FPR64)));
assert_eq!(ret.fprs_used, 1);
}
#[test]
fn test_abi_lower_args_overflow_gprs_aarch64() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
let types: Vec<_> = (0..12).map(|_| TypeKindRepr::Integer(64)).collect();
let args = abi.lower_args(&types).unwrap();
assert_eq!(args.len(), 12);
for arg in &args[..8] {
assert!(arg.reg.is_some(), "Arg should have register");
}
for arg in &args[8..] {
assert!(arg.stack_offset.is_some(), "Arg should be on stack");
}
}
#[test]
fn test_frame_lowering_needs_frame_pointer_with_var_size() {
let fl = CrossTargetFrameLowering::new(BridgeArch::AArch64);
assert!(!fl.needs_frame_pointer());
}
#[test]
fn test_cost_model_estimate_unknown_opcode() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
let est = model.estimate(Opcode::Phi, 0); assert_eq!(est.latency, 10); assert_eq!(est.code_size, 4);
}
#[test]
fn test_cost_model_x86_64_estimate_on_x86() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::X86_64);
let est = model.estimate(Opcode::SDiv, 2);
assert_eq!(est.latency, 20); }
#[test]
fn test_cost_model_aarch64_estimate_on_aarch64() {
let model = X86AArch64CostModel::new(BridgeArch::AArch64, BridgeArch::AArch64);
let est = model.estimate(Opcode::SDiv, 2);
assert_eq!(est.latency, 12); }
#[test]
fn test_cost_model_shifts() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
for op in &[Opcode::Shl, Opcode::LShr, Opcode::AShr] {
let est = model.estimate(*op, 2);
assert_eq!(est.latency, 1);
assert_eq!(est.code_size, 4);
assert!(est.profitable);
}
}
#[test]
fn test_cost_model_bitcast_is_free() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
let est = model.estimate(Opcode::BitCast, 1);
assert_eq!(est.latency, 0);
assert_eq!(est.code_size, 0);
}
#[test]
fn test_cost_model_ptr_int_conversions_are_free() {
let model = X86AArch64CostModel::new(BridgeArch::X86_64, BridgeArch::AArch64);
assert_eq!(model.estimate(Opcode::IntToPtr, 1).latency, 0);
assert_eq!(model.estimate(Opcode::PtrToInt, 1).latency, 0);
}
#[test]
fn test_cost_model_is_profitable_all_directions() {
let model_arm_to_x86 = X86AArch64CostModel::new(BridgeArch::AArch64, BridgeArch::X86_64);
assert!(model_arm_to_x86.is_profitable(Opcode::Add, "fold_imm"));
assert!(model_arm_to_x86.is_profitable(Opcode::Add, "unroll_loop"));
}
#[test]
fn test_abi_arm32_max_params() {
let abi = CrossTargetABI::new(BridgeArch::ARM32);
assert_eq!(abi.max_gpr_params, 4);
assert_eq!(abi.max_fpr_params, 16);
assert_eq!(abi.stack_alignment, 8);
}
#[test]
fn test_abi_x86_32_all_on_stack() {
let abi = CrossTargetABI::new(BridgeArch::X86_32);
assert_eq!(abi.max_gpr_params, 0); assert_eq!(abi.max_fpr_params, 0);
}
#[test]
fn test_abi_classify_arg_vector_128() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
let v4i32 = TypeKindRepr::Vector(4, Box::new(TypeKindRepr::Integer(32)));
assert_eq!(abi.classify_arg(&v4i32), CrossABIClass::Float); }
#[test]
fn test_abi_classify_arg_vector_256() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
let v8i32 = TypeKindRepr::Vector(8, Box::new(TypeKindRepr::Integer(32)));
assert_eq!(abi.classify_arg(&v8i32), CrossABIClass::Memory); }
#[test]
fn test_abi_classify_ret_small_struct() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
let small_struct =
TypeKindRepr::Struct(vec![TypeKindRepr::Integer(64), TypeKindRepr::Integer(64)]);
assert_eq!(abi.classify_ret(&small_struct), CrossABIClass::Integer);
}
#[test]
fn test_abi_classify_ret_hfa_struct() {
let abi = CrossTargetABI::new(BridgeArch::AArch64);
let hfa = TypeKindRepr::Struct(vec![TypeKindRepr::Float(32), TypeKindRepr::Float(32)]);
assert_eq!(abi.classify_ret(&hfa), CrossABIClass::Float);
}
#[test]
fn test_abi_lower_ret_sret() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
let big_struct = TypeKindRepr::Struct(vec![
TypeKindRepr::Integer(64),
TypeKindRepr::Integer(64),
TypeKindRepr::Integer(64),
TypeKindRepr::Integer(64),
]); let ret = abi.lower_ret(&big_struct);
assert!(ret.sret);
}
#[test]
fn test_abi_lower_args_mixed_with_spill() {
let mut abi = CrossTargetABI::new(BridgeArch::AArch64);
let types: Vec<_> = (0..10).map(|_| TypeKindRepr::Float(64)).collect();
let args = abi.lower_args(&types).unwrap();
assert_eq!(args.len(), 10);
for (i, arg) in args.iter().enumerate() {
if i < 8 {
assert!(arg.reg.is_some(), "FPR arg {} should be in register", i);
} else {
assert!(
arg.stack_offset.is_some(),
"FPR arg {} should be on stack",
i
);
}
}
}
#[test]
fn test_frame_aarch64_callee_saved_regs() {
let fl = CrossTargetFrameLowering::new(BridgeArch::AArch64);
let regs = fl.get_callee_saved_regs();
assert_eq!(regs.len(), 18);
}
#[test]
fn test_frame_x86_64_callee_saved_regs() {
let fl = CrossTargetFrameLowering::new(BridgeArch::X86_64);
let regs = fl.get_callee_saved_regs();
assert_eq!(regs.len(), 6); }
#[test]
fn test_frame_arm32_callee_saved_regs() {
let fl = CrossTargetFrameLowering::new(BridgeArch::ARM32);
let regs = fl.get_callee_saved_regs();
assert_eq!(regs.len(), 8); }
#[test]
fn test_frame_x86_32_callee_saved_regs() {
let fl = CrossTargetFrameLowering::new(BridgeArch::X86_32);
let regs = fl.get_callee_saved_regs();
assert_eq!(regs.len(), 4); }
#[test]
fn test_frame_prologue_does_not_crash() {
let mut mf = MachineFunction::new("test");
let fl = CrossTargetFrameLowering::new(BridgeArch::AArch64);
assert!(fl.emit_prologue(&mut mf).is_ok());
assert!(fl.emit_epilogue(&mut mf).is_ok());
}
#[test]
fn test_frame_prologue_x86_64_does_not_crash() {
let mut mf = MachineFunction::new("test");
let fl = CrossTargetFrameLowering::new(BridgeArch::X86_64);
assert!(fl.emit_prologue(&mut mf).is_ok());
assert!(fl.emit_epilogue(&mut mf).is_ok());
}
#[test]
fn test_frame_arm32_frame_pointer_reg() {
let frame = CrossStackFrame::new(BridgeArch::ARM32);
assert_eq!(frame.frame_pointer_reg().number, 11); assert_eq!(frame.stack_pointer_reg().number, 13); assert_eq!(frame.return_address_reg().number, 14); }
#[test]
fn test_frame_x86_32_frame_pointer_reg() {
let frame = CrossStackFrame::new(BridgeArch::X86_32);
assert_eq!(frame.frame_pointer_reg().number, 5); assert_eq!(frame.frame_pointer_reg().class, CrossRegClass::GPR32);
}
#[test]
fn test_loop_pattern_cmp_branch_fusion() {
let opt = CrossTargetOptimization::new(BridgeArch::AArch64);
let p = opt.get_loop_pattern("cmp_branch_fusion");
assert!(p.is_some());
let p = p.unwrap();
assert!(p.applies_x86);
assert!(p.applies_aarch64);
assert_eq!(p.threshold, 0);
}
#[test]
fn test_loop_pattern_iv_strength_reduce() {
let opt = CrossTargetOptimization::new(BridgeArch::X86_64);
let p = opt.get_loop_pattern("iv_strength_reduce");
assert!(p.is_some());
assert!(p.unwrap().applies_to(BridgeArch::X86_64));
assert!(p.unwrap().applies_to(BridgeArch::AArch64));
}
#[test]
fn test_optimizer_aggressive_enables_loop_unroll() {
let mut opt = CrossTargetOptimization::new(BridgeArch::AArch64);
assert!(!opt.passes.contains(&CrossOptPass::LoopUnrolling));
opt.set_opt_level(CodeGenOptLevel::Aggressive);
assert!(opt.passes.contains(&CrossOptPass::LoopUnrolling));
}
#[test]
fn test_optimizer_aggressive_enables_tail_dup() {
let mut opt = CrossTargetOptimization::new(BridgeArch::X86_64);
opt.set_opt_level(CodeGenOptLevel::Aggressive);
assert!(opt.passes.contains(&CrossOptPass::TailDuplication));
}
#[test]
fn test_machine_ir_flags_default() {
let flags = MachineIRFlags::default();
assert!(!flags.may_trap);
assert!(!flags.may_load);
assert!(!flags.has_side_effects);
assert!(!flags.is_terminator);
}
#[test]
fn test_machine_ir_inst_with_flags() {
let flags = MachineIRFlags {
is_terminator: true,
is_branch: true,
..Default::default()
};
let inst = MachineIRInst::new(GenericMachineOpcode::Br)
.with_dst(0)
.with_src(MachineIROperand::Block(1))
.with_flags(flags);
assert!(inst.flags.is_terminator);
assert!(inst.flags.is_branch);
}
#[test]
fn test_machine_ir_cond_all_variants() {
let conds = [
MachineIRCond::EQ,
MachineIRCond::NE,
MachineIRCond::LT,
MachineIRCond::LE,
MachineIRCond::GT,
MachineIRCond::GE,
MachineIRCond::LO,
MachineIRCond::LS,
MachineIRCond::HI,
MachineIRCond::HS,
MachineIRCond::MI,
MachineIRCond::PL,
MachineIRCond::VS,
MachineIRCond::VC,
];
assert_eq!(conds.len(), 14);
}
#[test]
fn test_machine_ir_operand_variants() {
let _vreg = MachineIROperand::VReg(0);
let _imm = MachineIROperand::Imm(42);
let _fimm = MachineIROperand::FImm(3.14);
let _block = MachineIROperand::Block(1);
let _global = MachineIROperand::Global("main".into());
let _fi = MachineIROperand::FrameIndex(0);
let _ext = MachineIROperand::External("printf".into());
let _cond = MachineIROperand::Cond(MachineIRCond::EQ);
}
#[test]
fn test_a64_opcode_fp_arith_num_src() {
assert_eq!(A64Opcode::FADD.num_src_operands(), 2);
assert_eq!(A64Opcode::FSUB.num_src_operands(), 2);
assert_eq!(A64Opcode::FMUL.num_src_operands(), 2);
assert_eq!(A64Opcode::FDIV.num_src_operands(), 2);
}
#[test]
fn test_a64_opcode_fp_misc_num_src() {
assert_eq!(A64Opcode::FNEG.num_src_operands(), 1);
assert_eq!(A64Opcode::FABS.num_src_operands(), 1);
assert_eq!(A64Opcode::FCMP.num_src_operands(), 2);
assert_eq!(A64Opcode::FCVT.num_src_operands(), 1);
assert_eq!(A64Opcode::SCVTF.num_src_operands(), 1);
assert_eq!(A64Opcode::FCVTZS.num_src_operands(), 1);
}
#[test]
fn test_a64_opcode_conditional_num_src() {
assert_eq!(A64Opcode::CSEL.num_src_operands(), 3);
assert_eq!(A64Opcode::CSINC.num_src_operands(), 3);
assert_eq!(A64Opcode::CSINV.num_src_operands(), 3);
assert_eq!(A64Opcode::CSNEG.num_src_operands(), 3);
}
#[test]
fn test_a64_opcode_branch_num_src() {
assert_eq!(A64Opcode::B.num_src_operands(), 1);
assert_eq!(A64Opcode::BL.num_src_operands(), 1);
assert_eq!(A64Opcode::BR.num_src_operands(), 1);
assert_eq!(A64Opcode::BLR.num_src_operands(), 1);
assert_eq!(A64Opcode::RET.num_src_operands(), 1);
assert_eq!(A64Opcode::CBZ.num_src_operands(), 2);
assert_eq!(A64Opcode::CBNZ.num_src_operands(), 2);
assert_eq!(A64Opcode::TBZ.num_src_operands(), 3);
assert_eq!(A64Opcode::TBNZ.num_src_operands(), 3);
}
#[test]
fn test_a64_opcode_mov_immediates_num_src() {
for op in &[
A64Opcode::MOV,
A64Opcode::MOVK,
A64Opcode::MOVN,
A64Opcode::MOVZ,
] {
assert_eq!(op.num_src_operands(), 1);
}
}
#[test]
fn test_a64_opcode_address_gen_num_src() {
assert_eq!(A64Opcode::ADR.num_src_operands(), 1);
assert_eq!(A64Opcode::ADRP.num_src_operands(), 1);
}
#[test]
fn test_a64_opcode_simd_num_src() {
assert_eq!(A64Opcode::ADDV.num_src_operands(), 2);
assert_eq!(A64Opcode::MLA.num_src_operands(), 3);
assert_eq!(A64Opcode::MLS.num_src_operands(), 3);
assert_eq!(A64Opcode::DUP.num_src_operands(), 1);
assert_eq!(A64Opcode::INS.num_src_operands(), 2);
assert_eq!(A64Opcode::EXT.num_src_operands(), 3);
}
#[test]
fn test_a64_opcode_load_store_pair() {
assert_eq!(A64Opcode::STP.num_src_operands(), 3);
assert_eq!(A64Opcode::LDP.num_src_operands(), 2);
}
#[test]
fn test_a64_opcode_system_num_src() {
assert_eq!(A64Opcode::SVC.num_src_operands(), 0);
assert_eq!(A64Opcode::MSR.num_src_operands(), 1);
assert_eq!(A64Opcode::MRS.num_src_operands(), 0);
}
#[test]
fn test_instr_table_simd_entries() {
let table = A64InstrTable::new();
for op in &[
A64Opcode::ADDV,
A64Opcode::SUBV,
A64Opcode::MULV,
A64Opcode::MLA,
A64Opcode::MLS,
A64Opcode::DUP,
A64Opcode::INS,
A64Opcode::EXT,
A64Opcode::FADDP,
A64Opcode::FMULV,
] {
assert!(table.lookup(*op).is_some(), "SIMD opcode {:?} missing", op);
}
}
#[test]
fn test_instr_table_system_entries() {
let table = A64InstrTable::new();
for op in &[
A64Opcode::SVC,
A64Opcode::HVC,
A64Opcode::SMC,
A64Opcode::MSR,
A64Opcode::MRS,
A64Opcode::HINT,
A64Opcode::ISB,
A64Opcode::DSB,
A64Opcode::DMB,
] {
assert!(
table.lookup(*op).is_some(),
"System opcode {:?} missing",
op
);
}
}
#[test]
fn test_instr_table_pac_entries() {
let table = A64InstrTable::new();
assert!(table.lookup(A64Opcode::PACIASP).is_some());
assert!(table.lookup(A64Opcode::AUTIASP).is_some());
}
#[test]
fn test_instr_table_sve_entries() {
let table = A64InstrTable::new();
assert!(table.lookup(A64Opcode::ADD_Z).is_some());
assert!(table.lookup(A64Opcode::MUL_Z).is_some());
assert!(table.lookup(A64Opcode::FADD_Z).is_some());
}
#[test]
fn test_instr_table_default_has_all_entries() {
let table = A64InstrTable::default();
assert!(table.size() >= 55); }
#[test]
fn test_legalize_i128_split() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
let legal = isel.get_legalized_type(&TypeKindRepr::Integer(128));
assert_eq!(legal, TypeKindRepr::Integer(64)); }
#[test]
fn test_legalize_v4i1_widen() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
let v4i1 = TypeKindRepr::Vector(4, Box::new(TypeKindRepr::Integer(1)));
let legal = isel.get_legalized_type(&v4i1);
assert_eq!(
legal,
TypeKindRepr::Vector(4, Box::new(TypeKindRepr::Integer(8)))
);
}
#[test]
fn test_legalize_v8i1_widen() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
let v8i1 = TypeKindRepr::Vector(8, Box::new(TypeKindRepr::Integer(1)));
let legal = isel.get_legalized_type(&v8i1);
assert_eq!(
legal,
TypeKindRepr::Vector(8, Box::new(TypeKindRepr::Integer(8)))
);
}
#[test]
fn test_legalize_v16i1_widen() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
let v16i1 = TypeKindRepr::Vector(16, Box::new(TypeKindRepr::Integer(1)));
let legal = isel.get_legalized_type(&v16i1);
assert_eq!(
legal,
TypeKindRepr::Vector(16, Box::new(TypeKindRepr::Integer(8)))
);
}
#[test]
fn test_cross_reg_class_reg_count_arm32() {
assert_eq!(CrossRegClass::GPR32.reg_count(BridgeArch::ARM32), 16);
assert_eq!(CrossRegClass::FPR64.reg_count(BridgeArch::ARM32), 32);
}
#[test]
fn test_cross_reg_class_reg_count_x86_32() {
assert_eq!(CrossRegClass::GPR32.reg_count(BridgeArch::X86_32), 8);
assert_eq!(CrossRegClass::FPR128.reg_count(BridgeArch::X86_32), 8);
}
#[test]
fn test_cross_reg_class_gpr8_gpr16_unavailable() {
assert_eq!(CrossRegClass::GPR8.reg_count(BridgeArch::AArch64), 0);
assert_eq!(CrossRegClass::GPR16.reg_count(BridgeArch::X86_64), 0);
}
#[test]
fn test_live_interval_merge_ranges() {
let mut interval = LiveInterval::new(0, CrossRegClass::GPR64);
interval.add_range(0, 5);
interval.add_range(3, 8);
assert_eq!(interval.ranges.len(), 1);
assert_eq!(interval.ranges[0], (0, 8));
}
#[test]
fn test_live_interval_disjoint_ranges() {
let mut interval = LiveInterval::new(0, CrossRegClass::GPR64);
interval.add_range(0, 5);
interval.add_range(10, 15);
assert_eq!(interval.ranges.len(), 2);
}
#[test]
fn test_live_interval_no_overlap() {
let mut a = LiveInterval::new(0, CrossRegClass::GPR64);
a.add_range(0, 5);
let mut b = LiveInterval::new(1, CrossRegClass::GPR64);
b.add_range(6, 10);
assert!(!a.overlaps(&b));
}
#[test]
fn test_bridge_features_enable_sve() {
let mut features = BridgeFeatures::default();
features.enable_sve();
assert!(features.has(BridgeFeature::SVE));
assert!(!features.has(BridgeFeature::SVE2));
}
#[test]
fn test_bridge_features_enable_sve2_includes_sve() {
let mut features = BridgeFeatures::default();
features.enable_sve2();
assert!(features.has(BridgeFeature::SVE));
assert!(features.has(BridgeFeature::SVE2));
}
#[test]
fn test_bridge_features_enable_armv81a_inherits_v8a() {
let mut features = BridgeFeatures::default();
features.enable_armv81a();
assert!(features.has(BridgeFeature::V81A));
assert!(features.has(BridgeFeature::V8A));
assert!(features.has(BridgeFeature::Neon));
assert!(features.has(BridgeFeature::LSE));
assert!(features.has(BridgeFeature::RCPC));
}
#[test]
fn test_bridge_features_from_string_with_spaces() {
let features = BridgeFeatures::from_string(" +neon , +v8a , -sve ");
assert!(features.has(BridgeFeature::Neon));
assert!(features.has(BridgeFeature::V8A));
assert!(!features.has(BridgeFeature::SVE));
}
#[test]
fn test_bridge_features_parse_feature_aliases() {
assert!(BridgeFeatures::from_string("+avx512f").has(BridgeFeature::AVX512));
assert!(BridgeFeatures::from_string("+v8").has(BridgeFeature::V8A));
assert!(BridgeFeatures::from_string("+v8.1").has(BridgeFeature::V81A));
}
#[test]
fn test_bridge_features_unknown_feature_ignored() {
let features = BridgeFeatures::from_string("+neon,+imaginary_feature");
assert!(features.has(BridgeFeature::Neon));
assert_eq!(features.list_enabled().len(), 1);
}
#[test]
fn test_allocation_order_aarch64_callee_saved() {
let orders = AllocationOrder::aarch64_defaults();
let gpr64 = &orders[0];
for i in 19..=28 {
assert!(
gpr64
.callee_saved
.contains(&CrossPhysReg::new(i, CrossRegClass::GPR64)),
"x{} should be callee-saved",
i
);
}
}
#[test]
fn test_allocation_order_aarch64_caller_saved() {
let orders = AllocationOrder::aarch64_defaults();
let gpr64 = &orders[0];
for i in 0..=18 {
assert!(
gpr64
.caller_saved
.contains(&CrossPhysReg::new(i, CrossRegClass::GPR64)),
"x{} should be caller-saved",
i
);
}
}
#[test]
fn test_allocation_order_x86_64_callee_saved() {
let orders = AllocationOrder::x86_64_defaults();
let gpr64 = &orders[0];
for i in &[3, 12, 13, 14, 15, 5] {
assert!(
gpr64
.callee_saved
.contains(&CrossPhysReg::new(*i, CrossRegClass::GPR64)),
"reg {} should be callee-saved",
i
);
}
}
#[test]
fn test_target_machine_custom_triple() {
let tm = AArch64TargetMachine::new("aarch64-unknown-linux-musl");
assert_eq!(tm.triple, "aarch64-unknown-linux-musl");
assert!(tm.is_64bit);
}
#[test]
fn test_target_machine_set_opt_level() {
let mut tm = AArch64TargetMachine::linux_gnu();
tm.set_opt_level(CodeGenOptLevel::Aggressive);
assert_eq!(tm.opt_level, CodeGenOptLevel::Aggressive);
}
#[test]
fn test_target_machine_get_instr_desc_nonexistent() {
let tm = AArch64TargetMachine::linux_gnu();
assert!(tm.get_instr_desc(A64Opcode::Unknown).is_none());
}
#[test]
fn test_target_machine_big_endian_triple() {
let tm = AArch64TargetMachine::new("aarch64_be-unknown-linux-gnu");
assert!(!tm.is_little_endian());
}
#[test]
fn test_bridge_estimate_cost_aarch64_target() {
let bridge = X86AArch64Bridge::new(BridgeArch::X86_64, BridgeArch::AArch64);
let est = bridge.estimate_cost(Opcode::Store, 2);
assert_eq!(est.code_size, 4); assert!(est.profitable);
}
#[test]
fn test_bridge_estimate_cost_x86_target() {
let bridge = X86AArch64Bridge::new(BridgeArch::AArch64, BridgeArch::X86_64);
let est = bridge.estimate_cost(Opcode::Store, 2);
assert_eq!(est.code_size, 8); }
#[test]
fn test_bridge_enable_debug_info() {
let mut bridge = X86AArch64Bridge::default();
assert!(!bridge.debug_info);
bridge.enable_debug_info();
assert!(bridge.debug_info);
}
#[test]
fn test_bridge_stats_tracking() {
let bridge = X86AArch64Bridge::default();
assert_eq!(bridge.stats.functions_processed, 0);
assert_eq!(bridge.stats.isel_cycles, 0);
assert_eq!(bridge.stats.opt_cycles, 0);
assert_eq!(bridge.stats.ra_cycles, 0);
assert_eq!(bridge.stats.frame_cycles, 0);
assert_eq!(bridge.stats.post_ra_opt_cycles, 0);
assert_eq!(bridge.stats.instructions_eliminated, 0);
assert_eq!(bridge.stats.pattern_matches, 0);
assert_eq!(bridge.stats.spill_slots, 0);
assert_eq!(bridge.stats.abi_conversions, 0);
}
#[test]
fn test_pattern_node_variants_exist() {
let _any = PatternNode::Any;
let _opcode = PatternNode::Opcode(GenericMachineOpcode::Add);
let _ir_op = PatternNode::IROpcode(Opcode::Add);
let _constant = PatternNode::Constant(42);
let _imm_range = PatternNode::Immediate { min: 0, max: 100 };
let _seq = PatternNode::Sequence(vec![]);
let _alt = PatternNode::Alternative(vec![]);
let _pred = PatternNode::Predicate {
node: Box::new(PatternNode::Any),
pred: PatternPredicate::IsPowerOfTwo,
};
let _capture = PatternNode::Capture {
slot: 0,
node: Box::new(PatternNode::Any),
};
}
#[test]
fn test_a64_cond_all_inversions() {
let pairs = [
(A64Cond::EQ, A64Cond::NE),
(A64Cond::CS, A64Cond::CC),
(A64Cond::MI, A64Cond::PL),
(A64Cond::VS, A64Cond::VC),
(A64Cond::HI, A64Cond::LS),
(A64Cond::GE, A64Cond::LT),
(A64Cond::GT, A64Cond::LE),
(A64Cond::AL, A64Cond::NV),
];
for (a, b) in &pairs {
assert_eq!(a.invert(), *b);
assert_eq!(b.invert(), *a);
}
}
#[test]
fn test_a64_cond_all_encodings() {
let expected = [
(A64Cond::EQ, 0b0000),
(A64Cond::NE, 0b0001),
(A64Cond::CS, 0b0010),
(A64Cond::CC, 0b0011),
(A64Cond::MI, 0b0100),
(A64Cond::PL, 0b0101),
(A64Cond::VS, 0b0110),
(A64Cond::VC, 0b0111),
(A64Cond::HI, 0b1000),
(A64Cond::LS, 0b1001),
(A64Cond::GE, 0b1010),
(A64Cond::LT, 0b1011),
(A64Cond::GT, 0b1100),
(A64Cond::LE, 0b1101),
(A64Cond::AL, 0b1110),
(A64Cond::NV, 0b1111),
];
for (cond, enc) in &expected {
assert_eq!(cond.encoding(), *enc, "Wrong encoding for {:?}", cond);
}
}
#[test]
fn test_type_kind_repr_size_bits_all() {
assert_eq!(TypeKindRepr::Void.size_bits(), 0);
assert_eq!(TypeKindRepr::Integer(1).size_bits(), 1);
assert_eq!(TypeKindRepr::Integer(8).size_bits(), 8);
assert_eq!(TypeKindRepr::Integer(16).size_bits(), 16);
assert_eq!(TypeKindRepr::Integer(32).size_bits(), 32);
assert_eq!(TypeKindRepr::Integer(64).size_bits(), 64);
assert_eq!(TypeKindRepr::Integer(128).size_bits(), 128);
assert_eq!(TypeKindRepr::Float(16).size_bits(), 16);
assert_eq!(TypeKindRepr::Float(32).size_bits(), 32);
assert_eq!(TypeKindRepr::Float(64).size_bits(), 64);
assert_eq!(TypeKindRepr::Float(128).size_bits(), 128);
}
#[test]
fn test_type_kind_repr_array_size() {
let arr = TypeKindRepr::Array(10, Box::new(TypeKindRepr::Integer(32)));
assert_eq!(arr.size_bits(), 320);
}
#[test]
fn test_type_kind_repr_struct_size() {
let s = TypeKindRepr::Struct(vec![TypeKindRepr::Integer(64), TypeKindRepr::Float(64)]);
assert_eq!(s.size_bits(), 128);
}
#[test]
fn test_abi_arg_i8_alignment() {
let arg = CrossABIArg::new(TypeKindRepr::Integer(8));
assert_eq!(arg.alignment, 1);
}
#[test]
fn test_abi_arg_i16_alignment() {
let arg = CrossABIArg::new(TypeKindRepr::Integer(16));
assert_eq!(arg.alignment, 2);
}
#[test]
fn test_abi_arg_f64_alignment() {
let arg = CrossABIArg::new(TypeKindRepr::Float(64));
assert_eq!(arg.alignment, 8);
}
#[test]
fn test_abi_arg_pointer_alignment() {
let arg = CrossABIArg::new(TypeKindRepr::Pointer);
assert_eq!(arg.alignment, 8);
}
#[test]
fn test_bridge_error_all_variants_display() {
let errors = [
BridgeError::UnsupportedArchPair {
source: BridgeArch::AArch64,
target: BridgeArch::X86_64,
},
BridgeError::ISelFailed {
opcode: Opcode::Add as u32,
reason: "no match".into(),
},
BridgeError::RegAllocFailed {
reason: "out of registers".into(),
},
BridgeError::FrameLoweringFailed {
reason: "oversized frame".into(),
},
BridgeError::Unimplemented {
feature: "SVE2 gather".into(),
},
BridgeError::Internal("critical failure".into()),
];
for err in &errors {
let s = format!("{}", err);
assert!(!s.is_empty());
}
}
#[test]
fn test_cross_reg_class_display() {
assert_eq!(format!("{}", CrossRegClass::GPR64), "GPR64");
assert_eq!(format!("{}", CrossRegClass::FPR128), "FPR128");
assert_eq!(format!("{}", CrossRegClass::VR256), "VR256");
assert_eq!(format!("{}", CrossRegClass::Flags), "FLAGS");
}
#[test]
fn test_cross_phys_reg_display() {
let reg = CrossPhysReg::new(0, CrossRegClass::GPR64);
assert_eq!(format!("{}", reg), "GPR640");
let reg = CrossPhysReg::new(15, CrossRegClass::FPR128);
assert_eq!(format!("{}", reg), "FPR12815");
}
#[test]
fn test_isel_run_dag_combine_empty() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
let mut insts = vec![];
let result = isel.run_dag_combine(&mut insts);
assert!(result.is_empty());
}
#[test]
fn test_cross_target_abi_default() {
let abi = CrossTargetABI::default();
assert_eq!(abi.arch, BridgeArch::AArch64);
assert_eq!(abi.max_gpr_params, 8);
}
#[test]
fn test_cross_varargs_default() {
let va = CrossVarArgs::default();
assert!(!va.is_variadic);
}
#[test]
fn test_isel_has_all_legalize_rules() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
assert!(isel.legalize_rules.len() >= 12);
}
#[test]
fn test_isel_has_enough_patterns() {
let isel = CrossTargetISel::new(BridgeArch::AArch64);
assert!(isel.pattern_count() >= 25);
}
}