use crate::bpf::bpf_instr_info::{BpfInstrInfo, BpfOpcode};
use crate::bpf::bpf_target_machine::{
BpfCallingConvention, BpfRegClass, BpfRegister, BpfTargetMachine,
};
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 BPF_DATA_LAYOUT: &str = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128";
pub const BPFEB_DATA_LAYOUT: &str = "E-m:e-p:64:64-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 BPF_ABI_NAMES: &[&str] = &["bpf"];
pub const X86_ABI_NAMES: &[&str] = &["sysv", "win64", "msvc"];
pub const BPF_GPR_COUNT: usize = 11;
pub const BPF_STACK_ALIGNMENT: u32 = 8;
pub const BPF_MAX_STACK: u32 = 512;
pub const BPF_RED_ZONE_SIZE: u32 = 0;
pub const BPF_INSTR_SIZE: u32 = 8;
pub const BPF_MAX_IMM: i64 = 0x7FFF_FFFF;
pub const BPF_MAX_OFFSET: i64 = 0x7FFF;
pub const BPF_MAX_INSNS: usize = 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BridgeArch {
X86_64,
X86_32,
Bpf,
Bpfeb,
}
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::Bpf => write!(f, "bpf"),
BridgeArch::Bpfeb => write!(f, "bpfeb"),
}
}
}
impl BridgeArch {
pub fn is_64bit(&self) -> bool {
matches!(
self,
BridgeArch::X86_64 | BridgeArch::Bpf | BridgeArch::Bpfeb
)
}
pub fn is_bpf_family(&self) -> bool {
matches!(self, BridgeArch::Bpf | BridgeArch::Bpfeb)
}
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::Bpf | BridgeArch::Bpfeb => 64,
BridgeArch::X86_32 => 32,
}
}
pub fn data_layout(&self) -> &'static str {
match self {
BridgeArch::Bpf => BPF_DATA_LAYOUT,
BridgeArch::Bpfeb => BPFEB_DATA_LAYOUT,
BridgeArch::X86_64 | BridgeArch::X86_32 => X86_64_DATA_LAYOUT,
}
}
pub fn is_little_endian(&self) -> bool {
!matches!(self, BridgeArch::Bpfeb)
}
}
pub struct BPFX86Bridge {
pub source_arch: BridgeArch,
pub target_arch: BridgeArch,
pub isel: CrossTargetISel,
pub regalloc: CrossTargetRegAlloc,
pub frame_lowering: CrossTargetFrameLowering,
pub cost_model: BPFX86CostModel,
pub abi: CrossTargetABI,
pub optimizer: CrossTargetOptimization,
pub opt_level: CodeGenOptLevel,
pub debug_info: bool,
pub features: BridgeFeatures,
pub stats: BridgeStats,
pub bpf_tm: BpfTargetMachine,
}
impl BPFX86Bridge {
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 = BPFX86CostModel::new(source_arch, target_arch);
let abi = CrossTargetABI::new(target_arch);
let optimizer = CrossTargetOptimization::new(target_arch);
let bpf_tm = BpfTargetMachine::new();
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(),
bpf_tm,
}
}
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::BPF | Arch::BPF64 => BridgeArch::Bpf,
Arch::BPFEB => BridgeArch::Bpfeb,
_ => BridgeArch::Bpf, }
}
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;
}
if self.target_arch.is_x86_family() && self.source_arch.is_bpf_family() {
self.apply_jit_patterns(mf)?;
self.stats.pattern_matches += 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 apply_jit_patterns(&mut self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
self.stats.pattern_matches += mf.blocks.iter().flat_map(|bb| &bb.instructions).count();
Ok(())
}
pub fn estimate_cost(&self, opcode: Opcode, operand_count: usize) -> CostEstimate {
self.cost_model.estimate(opcode, operand_count)
}
pub fn describe(&self) -> String {
format!(
"BPFX86Bridge: {} → {} (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)
}
pub fn set_bpf_cpu(&mut self, cpu: &str) {
self.bpf_tm = BpfTargetMachine::for_cpu(cpu);
}
pub fn get_bpf_max_instructions(&self) -> usize {
self.bpf_tm.max_instructions()
}
}
impl Default for BPFX86Bridge {
fn default() -> Self {
Self::new(BridgeArch::Bpf, BridgeArch::X86_64)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BridgeFeature {
ALU32,
DWARFRIS,
NoSpeculationBarrier,
SSE,
SSE2,
AVX,
AVX2,
AVX512,
Atomics,
EndianOps,
SignedDivMod,
TailCall,
CO_RE,
BitCount,
FMA,
PGO,
}
impl fmt::Display for BridgeFeature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
BridgeFeature::ALU32 => "alu32",
BridgeFeature::DWARFRIS => "dwarfris",
BridgeFeature::NoSpeculationBarrier => "no_spec_barrier",
BridgeFeature::SSE => "sse",
BridgeFeature::SSE2 => "sse2",
BridgeFeature::AVX => "avx",
BridgeFeature::AVX2 => "avx2",
BridgeFeature::AVX512 => "avx512",
BridgeFeature::Atomics => "atomics",
BridgeFeature::EndianOps => "endian",
BridgeFeature::SignedDivMod => "sdiv_smod",
BridgeFeature::TailCall => "tail_call",
BridgeFeature::CO_RE => "co_re",
BridgeFeature::BitCount => "bitcount",
BridgeFeature::FMA => "fma",
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_bpf_v2(&mut self) {
self.flags.insert(BridgeFeature::ALU32);
}
pub fn enable_bpf_v3(&mut self) {
self.enable_bpf_v2();
self.flags.insert(BridgeFeature::EndianOps);
self.flags.insert(BridgeFeature::Atomics);
self.flags.insert(BridgeFeature::TailCall);
self.flags.insert(BridgeFeature::SignedDivMod);
}
pub fn enable_x86_baseline(&mut self) {
self.flags.insert(BridgeFeature::SSE);
self.flags.insert(BridgeFeature::SSE2);
self.flags.insert(BridgeFeature::BitCount);
}
pub fn enable_x86_avx(&mut self) {
self.enable_x86_baseline();
self.flags.insert(BridgeFeature::AVX);
self.flags.insert(BridgeFeature::AVX2);
self.flags.insert(BridgeFeature::FMA);
}
pub fn enable_x86_avx512(&mut self) {
self.enable_x86_avx();
self.flags.insert(BridgeFeature::AVX512);
}
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 {
"alu32" => Some(BridgeFeature::ALU32),
"dwarfris" => Some(BridgeFeature::DWARFRIS),
"no_spec_barrier" => Some(BridgeFeature::NoSpeculationBarrier),
"sse" => Some(BridgeFeature::SSE),
"sse2" => Some(BridgeFeature::SSE2),
"avx" => Some(BridgeFeature::AVX),
"avx2" => Some(BridgeFeature::AVX2),
"avx512" | "avx512f" => Some(BridgeFeature::AVX512),
"atomics" => Some(BridgeFeature::Atomics),
"endian" | "endian_ops" => Some(BridgeFeature::EndianOps),
"sdiv_smod" | "signed_div_mod" => Some(BridgeFeature::SignedDivMod),
"tail_call" => Some(BridgeFeature::TailCall),
"co_re" => Some(BridgeFeature::CO_RE),
"bitcount" => Some(BridgeFeature::BitCount),
"fma" => Some(BridgeFeature::FMA),
"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,
pub jit_patterns_applied: 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 },
VerifierRejection { reason: String },
ProgramTooLarge { count: usize, limit: usize },
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::VerifierRejection { reason } => {
write!(f, "BPF verifier rejection: {}", reason)
}
BridgeError::ProgramTooLarge { count, limit } => {
write!(
f,
"BPF program too large: {} instructions (limit: {})",
count, limit
)
}
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,
}
}
}
pub mod bpf_class {
pub const LD: u8 = 0x00;
pub const LDX: u8 = 0x01;
pub const ST: u8 = 0x02;
pub const STX: u8 = 0x03;
pub const ALU: u8 = 0x04;
pub const JMP: u8 = 0x05;
pub const JMP32: u8 = 0x06;
pub const ALU64: u8 = 0x07;
}
pub mod bpf_alu_op {
pub const ADD: u8 = 0x00;
pub const SUB: u8 = 0x10;
pub const MUL: u8 = 0x20;
pub const DIV: u8 = 0x30;
pub const OR: u8 = 0x40;
pub const AND: u8 = 0x50;
pub const LSH: u8 = 0x60;
pub const RSH: u8 = 0x70;
pub const NEG: u8 = 0x80;
pub const MOD: u8 = 0x90;
pub const XOR: u8 = 0xA0;
pub const MOV: u8 = 0xB0;
pub const ARSH: u8 = 0xC0;
pub const END: u8 = 0xD0;
}
pub mod bpf_jmp_op {
pub const JA: u8 = 0x00;
pub const JEQ: u8 = 0x10;
pub const JGT: u8 = 0x20;
pub const JGE: u8 = 0x30;
pub const JSET: u8 = 0x40;
pub const JNE: u8 = 0x50;
pub const JSGT: u8 = 0x60;
pub const JSGE: u8 = 0x70;
pub const CALL: u8 = 0x80;
pub const EXIT: u8 = 0x90;
pub const JLT: u8 = 0xA0;
pub const JLE: u8 = 0xB0;
pub const JSLT: u8 = 0xC0;
pub const JSLE: u8 = 0xD0;
}
pub struct BridgeBPFInstrInfo;
impl BridgeBPFInstrInfo {
pub fn get_mnemonic(opcode: BpfOpcode) -> &'static str {
match opcode {
BpfOpcode::ADD => "add",
BpfOpcode::SUB => "sub",
BpfOpcode::MUL => "mul",
BpfOpcode::DIV => "div",
BpfOpcode::SDIV => "sdiv",
BpfOpcode::OR => "or",
BpfOpcode::AND => "and",
BpfOpcode::LSH => "lsh",
BpfOpcode::RSH => "rsh",
BpfOpcode::NEG => "neg",
BpfOpcode::MOD => "mod",
BpfOpcode::SMOD => "smod",
BpfOpcode::XOR => "xor",
BpfOpcode::MOV => "mov",
BpfOpcode::ARSH => "arsh",
BpfOpcode::ADD32 => "add32",
BpfOpcode::SUB32 => "sub32",
BpfOpcode::MUL32 => "mul32",
BpfOpcode::DIV32 => "div32",
BpfOpcode::SDIV32 => "sdiv32",
BpfOpcode::OR32 => "or32",
BpfOpcode::AND32 => "and32",
BpfOpcode::LSH32 => "lsh32",
BpfOpcode::RSH32 => "rsh32",
BpfOpcode::NEG32 => "neg32",
BpfOpcode::MOD32 => "mod32",
BpfOpcode::SMOD32 => "smod32",
BpfOpcode::XOR32 => "xor32",
BpfOpcode::MOV32 => "mov32",
BpfOpcode::ARSH32 => "arsh32",
BpfOpcode::END_LE16 => "le16",
BpfOpcode::END_LE32 => "le32",
BpfOpcode::END_LE64 => "le64",
BpfOpcode::END_BE16 => "be16",
BpfOpcode::END_BE32 => "be32",
BpfOpcode::END_BE64 => "be64",
BpfOpcode::LD_DW => "lddw",
BpfOpcode::LDX_B => "ldxb",
BpfOpcode::LDX_H => "ldxh",
BpfOpcode::LDX_W => "ldxw",
BpfOpcode::LDX_DW => "ldxdw",
BpfOpcode::ST_DW => "stdw",
BpfOpcode::STX_DW => "stxdw",
BpfOpcode::ST_B => "stb",
BpfOpcode::ST_H => "sth",
BpfOpcode::ST_W => "stw",
BpfOpcode::STX_B => "stxb",
BpfOpcode::STX_H => "stxh",
BpfOpcode::STX_W => "stxw",
BpfOpcode::JA => "ja",
BpfOpcode::JEQ => "jeq",
BpfOpcode::JGT => "jgt",
BpfOpcode::JGE => "jge",
BpfOpcode::JLT => "jlt",
BpfOpcode::JLE => "jle",
BpfOpcode::JSET => "jset",
BpfOpcode::JNE => "jne",
BpfOpcode::JSGT => "jsgt",
BpfOpcode::JSGE => "jsge",
BpfOpcode::JSLT => "jslt",
BpfOpcode::JSLE => "jsle",
BpfOpcode::CALL => "call",
BpfOpcode::EXIT => "exit",
BpfOpcode::JEQ32 => "jeq32",
BpfOpcode::JGT32 => "jgt32",
BpfOpcode::JGE32 => "jge32",
BpfOpcode::JLT32 => "jlt32",
BpfOpcode::JLE32 => "jle32",
BpfOpcode::JSET32 => "jset32",
BpfOpcode::JNE32 => "jne32",
BpfOpcode::JSGT32 => "jsgt32",
BpfOpcode::JSGE32 => "jsge32",
BpfOpcode::JSLT32 => "jslt32",
BpfOpcode::JSLE32 => "jsle32",
BpfOpcode::XADD => "xadd",
BpfOpcode::XADD32 => "xadd32",
BpfOpcode::FETCH_ADD => "fetch_add",
BpfOpcode::FETCH_AND => "fetch_and",
BpfOpcode::FETCH_OR => "fetch_or",
BpfOpcode::FETCH_XOR => "fetch_xor",
_ => "unknown",
}
}
pub fn is_branch(opcode: BpfOpcode) -> bool {
matches!(
opcode,
BpfOpcode::JA
| BpfOpcode::JEQ
| BpfOpcode::JGT
| BpfOpcode::JGE
| BpfOpcode::JLT
| BpfOpcode::JLE
| BpfOpcode::JSET
| BpfOpcode::JNE
| BpfOpcode::JSGT
| BpfOpcode::JSGE
| BpfOpcode::JSLT
| BpfOpcode::JSLE
| BpfOpcode::JEQ32
| BpfOpcode::JGT32
| BpfOpcode::JGE32
| BpfOpcode::JLT32
| BpfOpcode::JLE32
| BpfOpcode::JSET32
| BpfOpcode::JNE32
| BpfOpcode::JSGT32
| BpfOpcode::JSGE32
| BpfOpcode::JSLT32
| BpfOpcode::JSLE32
)
}
pub fn is_terminator(opcode: BpfOpcode) -> bool {
matches!(opcode, BpfOpcode::EXIT | BpfOpcode::JA | BpfOpcode::CALL)
}
pub fn is_unconditional(opcode: BpfOpcode) -> bool {
matches!(opcode, BpfOpcode::JA)
}
pub fn is_alu32(opcode: BpfOpcode) -> bool {
matches!(
opcode,
BpfOpcode::ADD32
| BpfOpcode::SUB32
| BpfOpcode::MUL32
| BpfOpcode::DIV32
| BpfOpcode::SDIV32
| BpfOpcode::OR32
| BpfOpcode::AND32
| BpfOpcode::LSH32
| BpfOpcode::RSH32
| BpfOpcode::NEG32
| BpfOpcode::MOD32
| BpfOpcode::SMOD32
| BpfOpcode::XOR32
| BpfOpcode::MOV32
| BpfOpcode::ARSH32
)
}
pub fn is_atomic(opcode: BpfOpcode) -> bool {
matches!(
opcode,
BpfOpcode::XADD
| BpfOpcode::XADD32
| BpfOpcode::FETCH_ADD
| BpfOpcode::FETCH_AND
| BpfOpcode::FETCH_OR
| BpfOpcode::FETCH_XOR
)
}
pub fn is_endian(opcode: BpfOpcode) -> bool {
matches!(
opcode,
BpfOpcode::END_LE16
| BpfOpcode::END_LE32
| BpfOpcode::END_LE64
| BpfOpcode::END_BE16
| BpfOpcode::END_BE32
| BpfOpcode::END_BE64
)
}
pub fn is_memory(opcode: BpfOpcode) -> bool {
matches!(
opcode,
BpfOpcode::LD_DW
| BpfOpcode::LDX_B
| BpfOpcode::LDX_H
| BpfOpcode::LDX_W
| BpfOpcode::LDX_DW
| BpfOpcode::ST_DW
| BpfOpcode::STX_DW
| BpfOpcode::ST_B
| BpfOpcode::ST_H
| BpfOpcode::ST_W
| BpfOpcode::STX_B
| BpfOpcode::STX_H
| BpfOpcode::STX_W
)
}
pub fn all_opcodes() -> Vec<BpfOpcode> {
vec![
BpfOpcode::ADD,
BpfOpcode::SUB,
BpfOpcode::MUL,
BpfOpcode::DIV,
BpfOpcode::SDIV,
BpfOpcode::OR,
BpfOpcode::AND,
BpfOpcode::LSH,
BpfOpcode::RSH,
BpfOpcode::NEG,
BpfOpcode::MOD,
BpfOpcode::SMOD,
BpfOpcode::XOR,
BpfOpcode::MOV,
BpfOpcode::ARSH,
BpfOpcode::ADD32,
BpfOpcode::SUB32,
BpfOpcode::MUL32,
BpfOpcode::DIV32,
BpfOpcode::SDIV32,
BpfOpcode::OR32,
BpfOpcode::AND32,
BpfOpcode::LSH32,
BpfOpcode::RSH32,
BpfOpcode::NEG32,
BpfOpcode::MOD32,
BpfOpcode::SMOD32,
BpfOpcode::XOR32,
BpfOpcode::MOV32,
BpfOpcode::ARSH32,
BpfOpcode::END_LE16,
BpfOpcode::END_LE32,
BpfOpcode::END_LE64,
BpfOpcode::END_BE16,
BpfOpcode::END_BE32,
BpfOpcode::END_BE64,
BpfOpcode::LD_DW,
BpfOpcode::LDX_B,
BpfOpcode::LDX_H,
BpfOpcode::LDX_W,
BpfOpcode::LDX_DW,
BpfOpcode::ST_DW,
BpfOpcode::STX_DW,
BpfOpcode::ST_B,
BpfOpcode::ST_H,
BpfOpcode::ST_W,
BpfOpcode::STX_B,
BpfOpcode::STX_H,
BpfOpcode::STX_W,
BpfOpcode::JA,
BpfOpcode::JEQ,
BpfOpcode::JGT,
BpfOpcode::JGE,
BpfOpcode::JLT,
BpfOpcode::JLE,
BpfOpcode::JSET,
BpfOpcode::JNE,
BpfOpcode::JSGT,
BpfOpcode::JSGE,
BpfOpcode::JSLT,
BpfOpcode::JSLE,
BpfOpcode::CALL,
BpfOpcode::EXIT,
BpfOpcode::JEQ32,
BpfOpcode::JGT32,
BpfOpcode::JGE32,
BpfOpcode::JLT32,
BpfOpcode::JLE32,
BpfOpcode::JSET32,
BpfOpcode::JNE32,
BpfOpcode::JSGT32,
BpfOpcode::JSGE32,
BpfOpcode::JSLT32,
BpfOpcode::JSLE32,
BpfOpcode::XADD,
BpfOpcode::XADD32,
BpfOpcode::FETCH_ADD,
BpfOpcode::FETCH_AND,
BpfOpcode::FETCH_OR,
BpfOpcode::FETCH_XOR,
]
}
pub fn opcode_count() -> usize {
78
}
}
pub mod bpf_regs {
pub const R0: u8 = 0;
pub const R1: u8 = 1;
pub const R2: u8 = 2;
pub const R3: u8 = 3;
pub const R4: u8 = 4;
pub const R5: u8 = 5;
pub const R6: u8 = 6;
pub const R7: u8 = 7;
pub const R8: u8 = 8;
pub const R9: u8 = 9;
pub const R10: u8 = 10;
pub const R11_FP: u8 = 10;
}
pub struct BridgeBPFRegisterInfo;
impl BridgeBPFRegisterInfo {
pub fn reg_name(index: u8) -> &'static str {
match index {
0 => "r0",
1 => "r1",
2 => "r2",
3 => "r3",
4 => "r4",
5 => "r5",
6 => "r6",
7 => "r7",
8 => "r8",
9 => "r9",
10 => "r10",
_ => "unknown",
}
}
pub fn is_callee_saved(index: u8) -> bool {
matches!(index, 6 | 7 | 8 | 9 | 10)
}
pub fn is_caller_saved(index: u8) -> bool {
matches!(index, 0 | 1 | 2 | 3 | 4 | 5)
}
pub fn return_reg() -> u8 {
bpf_regs::R0
}
pub fn arg_regs() -> Vec<u8> {
vec![
bpf_regs::R1,
bpf_regs::R2,
bpf_regs::R3,
bpf_regs::R4,
bpf_regs::R5,
]
}
pub fn callee_saved_regs() -> Vec<u8> {
vec![bpf_regs::R6, bpf_regs::R7, bpf_regs::R8, bpf_regs::R9]
}
pub fn frame_pointer_reg() -> u8 {
bpf_regs::R10
}
pub fn all_regs() -> Vec<u8> {
(0..=10).collect()
}
pub fn num_regs() -> usize {
BPF_GPR_COUNT
}
pub fn max_reg_id() -> u8 {
10
}
}
pub struct BridgeBPFCallingConvention;
impl BridgeBPFCallingConvention {
pub fn arg_reg(n: usize) -> Option<u8> {
match n {
0 => Some(bpf_regs::R1),
1 => Some(bpf_regs::R2),
2 => Some(bpf_regs::R3),
3 => Some(bpf_regs::R4),
4 => Some(bpf_regs::R5),
_ => None,
}
}
pub fn num_arg_regs() -> usize {
5
}
pub fn return_reg() -> u8 {
bpf_regs::R0
}
pub fn callee_saved_regs() -> Vec<u8> {
vec![bpf_regs::R6, bpf_regs::R7, bpf_regs::R8, bpf_regs::R9]
}
pub fn caller_saved_regs() -> Vec<u8> {
vec![
bpf_regs::R0,
bpf_regs::R1,
bpf_regs::R2,
bpf_regs::R3,
bpf_regs::R4,
bpf_regs::R5,
]
}
pub fn emit_prologue(frame_size: u32) -> Vec<u8> {
if frame_size == 0 {
return vec![];
}
vec![]
}
pub fn emit_epilogue() -> Vec<u8> {
vec![0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
}
pub fn bpf_reg_to_x86(bpf_reg: u8) -> u8 {
match bpf_reg {
0 => 0, 1 => 7, 2 => 6, 3 => 2, 4 => 8, 5 => 1, 6 => 3, 7 => 13, 8 => 14, 9 => 15, 10 => 5, _ => 0,
}
}
}
#[derive(Debug, Clone)]
pub struct BPFJITPattern {
pub name: String,
pub bpf_opcode: BpfOpcode,
pub x86_sequence: Vec<u8>,
pub x86_size: usize,
pub verifier_safe: bool,
pub x86_latency: u32,
}
impl BPFJITPattern {
pub fn new(name: &str, bpf_opcode: BpfOpcode) -> Self {
Self {
name: name.to_string(),
bpf_opcode,
x86_sequence: Vec::new(),
x86_size: 0,
verifier_safe: true,
x86_latency: 1,
}
}
pub fn with_x86_bytes(mut self, bytes: &[u8]) -> Self {
self.x86_sequence = bytes.to_vec();
self.x86_size = bytes.len();
self
}
pub fn with_x86_latency(mut self, latency: u32) -> Self {
self.x86_latency = latency;
self
}
}
pub struct BPFJITPatternTable {
pub patterns: HashMap<BpfOpcode, BPFJITPattern>,
}
impl BPFJITPatternTable {
pub fn new() -> Self {
let mut table = Self {
patterns: HashMap::new(),
};
table.init_default_patterns();
table
}
fn init_default_patterns(&mut self) {
self.add_pattern(
BPFJITPattern::new("add64_rr", BpfOpcode::ADD)
.with_x86_bytes(&[0x48, 0x01, 0xC0]) .with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("sub64_rr", BpfOpcode::SUB)
.with_x86_bytes(&[0x48, 0x29, 0xC0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("mul64_rr", BpfOpcode::MUL)
.with_x86_bytes(&[0x48, 0x0F, 0xAF, 0xC0])
.with_x86_latency(3),
);
self.add_pattern(
BPFJITPattern::new("div64_rr", BpfOpcode::DIV)
.with_x86_bytes(&[0x48, 0xF7, 0xF1])
.with_x86_latency(20),
);
self.add_pattern(
BPFJITPattern::new("or64_rr", BpfOpcode::OR)
.with_x86_bytes(&[0x48, 0x09, 0xC0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("and64_rr", BpfOpcode::AND)
.with_x86_bytes(&[0x48, 0x21, 0xC0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("lsh64_rr", BpfOpcode::LSH)
.with_x86_bytes(&[0x48, 0xD3, 0xE0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("rsh64_rr", BpfOpcode::RSH)
.with_x86_bytes(&[0x48, 0xD3, 0xE8])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("neg64_r", BpfOpcode::NEG)
.with_x86_bytes(&[0x48, 0xF7, 0xD8])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("xor64_rr", BpfOpcode::XOR)
.with_x86_bytes(&[0x48, 0x31, 0xC0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("mov64_rr", BpfOpcode::MOV)
.with_x86_bytes(&[0x48, 0x89, 0xC0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("arsh64_rr", BpfOpcode::ARSH)
.with_x86_bytes(&[0x48, 0xD3, 0xF8])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("add32_rr", BpfOpcode::ADD32)
.with_x86_bytes(&[0x01, 0xC0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("sub32_rr", BpfOpcode::SUB32)
.with_x86_bytes(&[0x29, 0xC0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("mul32_rr", BpfOpcode::MUL32)
.with_x86_bytes(&[0x0F, 0xAF, 0xC0])
.with_x86_latency(3),
);
self.add_pattern(
BPFJITPattern::new("mov32_rr", BpfOpcode::MOV32)
.with_x86_bytes(&[0x89, 0xC0])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("ja", BpfOpcode::JA)
.with_x86_bytes(&[0xE9, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("exit", BpfOpcode::EXIT)
.with_x86_bytes(&[0xC3])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("call", BpfOpcode::CALL)
.with_x86_bytes(&[0xE8, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(3),
);
self.add_pattern(
BPFJITPattern::new("jeq", BpfOpcode::JEQ)
.with_x86_bytes(&[0x0F, 0x84, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("jgt", BpfOpcode::JGT)
.with_x86_bytes(&[0x0F, 0x87, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("jge", BpfOpcode::JGE)
.with_x86_bytes(&[0x0F, 0x83, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("jlt", BpfOpcode::JLT)
.with_x86_bytes(&[0x0F, 0x82, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("jle", BpfOpcode::JLE)
.with_x86_bytes(&[0x0F, 0x86, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("lddw", BpfOpcode::LD_DW)
.with_x86_bytes(&[0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) .with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("ldxb", BpfOpcode::LDX_B)
.with_x86_bytes(&[0x0F, 0xB6, 0x00]) .with_x86_latency(4),
);
self.add_pattern(
BPFJITPattern::new("ldxh", BpfOpcode::LDX_H)
.with_x86_bytes(&[0x0F, 0xB7, 0x00]) .with_x86_latency(4),
);
self.add_pattern(
BPFJITPattern::new("ldxw", BpfOpcode::LDX_W)
.with_x86_bytes(&[0x8B, 0x00]) .with_x86_latency(4),
);
self.add_pattern(
BPFJITPattern::new("ldxdw", BpfOpcode::LDX_DW)
.with_x86_bytes(&[0x48, 0x8B, 0x00]) .with_x86_latency(4),
);
self.add_pattern(
BPFJITPattern::new("stb", BpfOpcode::ST_B)
.with_x86_bytes(&[0x88, 0x00]) .with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("sth", BpfOpcode::ST_H)
.with_x86_bytes(&[0x66, 0x89, 0x00]) .with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("stw", BpfOpcode::ST_W)
.with_x86_bytes(&[0x89, 0x00]) .with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("stdw", BpfOpcode::ST_DW)
.with_x86_bytes(&[0x48, 0x89, 0x00]) .with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("xadd", BpfOpcode::XADD)
.with_x86_bytes(&[0xF0, 0x48, 0x0F, 0xC1, 0x00]) .with_x86_latency(10),
);
self.add_pattern(
BPFJITPattern::new("fetch_add", BpfOpcode::FETCH_ADD)
.with_x86_bytes(&[0xF0, 0x48, 0x01, 0x00]) .with_x86_latency(10),
);
self.add_pattern(
BPFJITPattern::new("fetch_and", BpfOpcode::FETCH_AND)
.with_x86_bytes(&[0xF0, 0x48, 0x21, 0x00]) .with_x86_latency(10),
);
self.add_pattern(
BPFJITPattern::new("fetch_or", BpfOpcode::FETCH_OR)
.with_x86_bytes(&[0xF0, 0x48, 0x09, 0x00]) .with_x86_latency(10),
);
self.add_pattern(
BPFJITPattern::new("fetch_xor", BpfOpcode::FETCH_XOR)
.with_x86_bytes(&[0xF0, 0x48, 0x31, 0x00]) .with_x86_latency(10),
);
self.add_pattern(
BPFJITPattern::new("jeq32", BpfOpcode::JEQ32)
.with_x86_bytes(&[0x0F, 0x84, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(1),
);
self.add_pattern(
BPFJITPattern::new("jne32", BpfOpcode::JNE32)
.with_x86_bytes(&[0x0F, 0x85, 0x00, 0x00, 0x00, 0x00])
.with_x86_latency(1),
);
}
pub fn add_pattern(&mut self, pattern: BPFJITPattern) {
self.patterns.insert(pattern.bpf_opcode, pattern);
}
pub fn lookup(&self, opcode: BpfOpcode) -> Option<&BPFJITPattern> {
self.patterns.get(&opcode)
}
pub fn pattern_count(&self) -> usize {
self.patterns.len()
}
pub fn jit_translate(&self, opcode: BpfOpcode) -> Option<Vec<u8>> {
self.patterns.get(&opcode).map(|p| p.x86_sequence.clone())
}
}
impl Default for BPFJITPatternTable {
fn default() -> Self {
Self::new()
}
}
#[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.dst = Some(dst);
self
}
pub fn with_src(mut self, src: MachineIROperand) -> Self {
self.srcs.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,
SExt,
ZExt,
Trunc,
Select,
FrameSetup,
FrameDestroy,
BpfLddw,
BpfAtomicAdd,
BpfEndian,
Phi,
Debug,
Nop,
TargetSpecific(u32),
}
impl GenericMachineOpcode {
pub fn is_terminator(&self) -> bool {
matches!(self, GenericMachineOpcode::Br | GenericMachineOpcode::Ret)
}
pub fn is_memory(&self) -> bool {
matches!(
self,
GenericMachineOpcode::Load
| GenericMachineOpcode::Store
| GenericMachineOpcode::FrameSetup
| GenericMachineOpcode::FrameDestroy
)
}
pub fn is_commutative(&self) -> bool {
matches!(
self,
GenericMachineOpcode::Add
| GenericMachineOpcode::Mul
| GenericMachineOpcode::And
| GenericMachineOpcode::Or
| GenericMachineOpcode::Xor
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MachineIRCond {
EQ,
NE,
LT,
LE,
GT,
GE,
LO, LS, HI, HS, }
#[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),
FitsIn32Bits,
}
#[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>,
pub jit_patterns: BPFJITPatternTable,
}
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,
jit_patterns: BPFJITPatternTable::new(),
};
isel.init_default_patterns();
isel.init_legalize_rules();
isel
}
fn init_default_patterns(&mut self) {
self.add_pattern(DAGPattern {
name: "add".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Add),
result: PatternResult {
opcode: GenericMachineOpcode::Add,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "sub".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Sub),
result: PatternResult {
opcode: GenericMachineOpcode::Sub,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "mul".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Mul),
result: PatternResult {
opcode: GenericMachineOpcode::Mul,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 3,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "and".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::And),
result: PatternResult {
opcode: GenericMachineOpcode::And,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "or".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Or),
result: PatternResult {
opcode: GenericMachineOpcode::Or,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "xor".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Xor),
result: PatternResult {
opcode: GenericMachineOpcode::Xor,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "shl".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Shl),
result: PatternResult {
opcode: GenericMachineOpcode::Shl,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "lshr".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::LShr),
result: PatternResult {
opcode: GenericMachineOpcode::LShr,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 1,
archs: vec![],
});
self.add_pattern(DAGPattern {
name: "ashr".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::AShr),
result: PatternResult {
opcode: GenericMachineOpcode::AShr,
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: "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: "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: "bpf_lddw".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::BpfLddw),
result: PatternResult {
opcode: GenericMachineOpcode::BpfLddw,
operand_mapping: vec![0, 1],
flags: Some(MachineIRFlags {
may_load: true,
..Default::default()
}),
},
cost: 2,
archs: vec![BridgeArch::Bpf, BridgeArch::Bpfeb],
});
self.add_pattern(DAGPattern {
name: "bpf_atomic_add".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::BpfAtomicAdd),
result: PatternResult {
opcode: GenericMachineOpcode::BpfAtomicAdd,
operand_mapping: vec![0, 1],
flags: Some(MachineIRFlags {
has_side_effects: true,
..Default::default()
}),
},
cost: 10,
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![],
});
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: "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: "select".into(),
pattern: PatternNode::Opcode(GenericMachineOpcode::Select),
result: PatternResult {
opcode: GenericMachineOpcode::Select,
operand_mapping: vec![0, 1, 2],
flags: None,
},
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![],
});
}
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_i8_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_i128".into(),
from_type: TypeKindRepr::Integer(128),
to_type: TypeKindRepr::Integer(64),
action: LegalizeAction::Split,
});
}
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() {
self.next_vreg += 1;
self.vreg_map
.insert(self.next_vreg - 1, (self.next_vreg - 1) as VirtReg);
}
}
}
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(_) => 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 {
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 VirtReg;
self.next_vreg += 1;
vreg
}
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(_))
}
}
#[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,
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::VR256 => 256,
CrossRegClass::VR512 => 512,
CrossRegClass::Flags => 0,
}
}
pub fn reg_count(&self, arch: BridgeArch) -> usize {
match arch {
BridgeArch::Bpf | BridgeArch::Bpfeb => match self {
CrossRegClass::GPR64 | CrossRegClass::GPR32 => BPF_GPR_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,
},
}
}
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::VR256 => "VR256",
CrossRegClass::VR512 => "VR512",
CrossRegClass::Flags => "FLAGS",
}
}
pub fn to_bpf_reg_class(&self) -> Option<BpfRegClass> {
match self {
CrossRegClass::GPR64 | CrossRegClass::GPR32 => Some(BpfRegClass::GPR),
CrossRegClass::Flags => Some(BpfRegClass::FramePtr),
_ => None,
}
}
}
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_bpf_reg(&self) -> u8 {
self.number as u8
}
pub fn to_x86_reg(&self) -> u32 {
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 bpf_defaults() -> Vec<Self> {
let mut orders = Vec::new();
let mut gpr64 = Self::new(CrossRegClass::GPR64);
for i in 0..=5u32 {
gpr64.order.push(CrossPhysReg::new(i, CrossRegClass::GPR64));
gpr64
.caller_saved
.insert(CrossPhysReg::new(i, CrossRegClass::GPR64));
}
for i in 6..=9u32 {
gpr64.order.push(CrossPhysReg::new(i, CrossRegClass::GPR64));
gpr64
.callee_saved
.insert(CrossPhysReg::new(i, CrossRegClass::GPR64));
}
gpr64
.reserved
.insert(CrossPhysReg::new(10, CrossRegClass::GPR64));
orders.push(gpr64);
orders
}
pub fn x86_64_defaults() -> Vec<Self> {
let mut orders = Vec::new();
let mut gpr64 = Self::new(CrossRegClass::GPR64);
for i in [0u32, 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 [3u32, 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..=15u32 {
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::Bpf | BridgeArch::Bpfeb => Self::bpf_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 {
let class = self.infer_reg_class(inst.opcode);
if let Some(dst) = &inst.def {
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 {
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(&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::Bpf | BridgeArch::Bpfeb => (BPF_STACK_ALIGNMENT, BPF_RED_ZONE_SIZE),
BridgeArch::X86_64 => (16, 128),
BridgeArch::X86_32 => (16, 0),
};
Self {
frame_size: 0,
local_area_offset: 0,
has_frame_pointer: true,
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,
arch,
}
}
pub fn align_frame_size(size: u32, alignment: u32) -> u32 {
(size + alignment - 1) & !(alignment - 1)
}
pub fn compute_saved_reg_offset(&self, reg_offset: i32) -> i32 {
-((reg_offset + 1) as i32 * 8)
}
pub fn compute_total_size(&mut self) {
let mut total = self.callee_saved_size + self.max_call_frame_size;
if self.has_var_sized_objects {
total += 256; }
self.frame_size = Self::align_frame_size(total, self.stack_alignment);
}
pub fn bpf_stack_size(&self) -> u32 {
self.frame_size.min(BPF_MAX_STACK)
}
}
pub struct CrossTargetFrameLowering {
pub arch: BridgeArch,
pub frame: CrossStackFrame,
}
impl CrossTargetFrameLowering {
pub fn new(arch: BridgeArch) -> Self {
Self {
arch,
frame: CrossStackFrame::new(arch),
}
}
pub fn lower_frame(&mut self, mf: &mut MachineFunction) -> Result<(), BridgeError> {
self.frame.callee_saved_size = self.compute_callee_saved_size(mf);
self.frame.max_call_frame_size = self.compute_max_call_frame(mf);
self.frame.compute_total_size();
self.emit_prologue(mf)?;
self.emit_epilogue(mf)?;
Ok(())
}
fn compute_callee_saved_size(&self, _mf: &MachineFunction) -> u32 {
match self.arch {
BridgeArch::Bpf | BridgeArch::Bpfeb => 32,
BridgeArch::X86_64 => 48,
BridgeArch::X86_32 => 16,
}
}
fn compute_max_call_frame(&self, _mf: &MachineFunction) -> u32 {
match self.arch {
BridgeArch::Bpf | BridgeArch::Bpfeb => 0,
BridgeArch::X86_64 => 32,
BridgeArch::X86_32 => 16,
}
}
fn emit_prologue(&mut self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn emit_epilogue(&mut self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
pub fn get_frame(&self) -> &CrossStackFrame {
&self.frame
}
pub fn needs_frame_lowering(&self) -> bool {
self.frame.frame_size > 0 || self.frame.has_calls || self.frame.has_var_sized_objects
}
}
pub struct CrossTargetABI {
pub arch: BridgeArch,
}
impl CrossTargetABI {
pub fn new(arch: BridgeArch) -> Self {
Self { arch }
}
pub fn lower_formal_arguments(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
match self.arch {
BridgeArch::Bpf | BridgeArch::Bpfeb => {
}
BridgeArch::X86_64 | BridgeArch::X86_32 => {
}
}
Ok(())
}
pub fn lower_call(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
match self.arch {
BridgeArch::Bpf | BridgeArch::Bpfeb => {
}
BridgeArch::X86_64 | BridgeArch::X86_32 => {
}
}
Ok(())
}
pub fn lower_return(&self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
match self.arch {
BridgeArch::Bpf | BridgeArch::Bpfeb => {
}
BridgeArch::X86_64 | BridgeArch::X86_32 => {
}
}
Ok(())
}
}
pub struct BPFX86CostModel {
pub source_arch: BridgeArch,
pub target_arch: BridgeArch,
pub cost_table: HashMap<GenericMachineOpcode, (u32, u32)>,
}
impl BPFX86CostModel {
pub fn new(source_arch: BridgeArch, target_arch: BridgeArch) -> Self {
let mut model = Self {
source_arch,
target_arch,
cost_table: HashMap::new(),
};
model.init_cost_table();
model
}
fn init_cost_table(&mut self) {
self.cost_table.insert(GenericMachineOpcode::Add, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Sub, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Mul, (1, 3));
self.cost_table.insert(GenericMachineOpcode::SDiv, (1, 20));
self.cost_table.insert(GenericMachineOpcode::UDiv, (1, 20));
self.cost_table.insert(GenericMachineOpcode::And, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Or, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Xor, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Shl, (1, 1));
self.cost_table.insert(GenericMachineOpcode::LShr, (1, 1));
self.cost_table.insert(GenericMachineOpcode::AShr, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Load, (1, 4));
self.cost_table.insert(GenericMachineOpcode::Store, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Br, (1, 1));
self.cost_table.insert(GenericMachineOpcode::BrCond, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Call, (1, 3));
self.cost_table.insert(GenericMachineOpcode::Ret, (1, 1));
self.cost_table.insert(GenericMachineOpcode::SExt, (1, 1));
self.cost_table.insert(GenericMachineOpcode::ZExt, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Trunc, (1, 1));
self.cost_table.insert(GenericMachineOpcode::Select, (1, 1));
self.cost_table
.insert(GenericMachineOpcode::BpfLddw, (2, 1));
self.cost_table
.insert(GenericMachineOpcode::BpfAtomicAdd, (1, 10));
}
pub fn estimate(&self, opcode: Opcode, _operand_count: usize) -> CostEstimate {
let generic = self.map_opcode(opcode);
if let Some(&(src_cost, tgt_cost)) = self.cost_table.get(&generic) {
let cost = if self.target_arch.is_64bit() {
tgt_cost
} else {
src_cost
};
CostEstimate::new(cost, cost, cost * 8)
} else {
CostEstimate::new(1, 1, 8)
}
}
fn map_opcode(&self, _opcode: Opcode) -> GenericMachineOpcode {
GenericMachineOpcode::Add
}
pub fn target_cost(&self, opcode: GenericMachineOpcode) -> u32 {
self.cost_table
.get(&opcode)
.map(|&(_, tgt)| tgt)
.unwrap_or(1)
}
pub fn source_cost(&self, opcode: GenericMachineOpcode) -> u32 {
self.cost_table
.get(&opcode)
.map(|&(src, _)| src)
.unwrap_or(1)
}
}
pub struct CrossTargetOptimization {
pub arch: BridgeArch,
pub peephole_patterns: Vec<PeepholePattern>,
pub constant_folding: bool,
pub dead_code_elim: bool,
pub jit_optimize: bool,
}
impl CrossTargetOptimization {
pub fn new(arch: BridgeArch) -> Self {
Self {
arch,
peephole_patterns: Vec::new(),
constant_folding: true,
dead_code_elim: true,
jit_optimize: arch.is_x86_family(),
}
}
pub fn optimize(&mut self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
if self.constant_folding {
self.run_constant_folding()?;
}
if self.dead_code_elim {
self.run_dce()?;
}
self.run_peephole()?;
if self.jit_optimize {
self.run_jit_optimizations()?;
}
Ok(())
}
pub fn post_ra_optimize(&mut self, _mf: &mut MachineFunction) -> Result<(), BridgeError> {
Ok(())
}
fn run_constant_folding(&self) -> Result<(), BridgeError> {
Ok(())
}
fn run_dce(&self) -> Result<(), BridgeError> {
Ok(())
}
fn run_peephole(&self) -> Result<(), BridgeError> {
Ok(())
}
fn run_jit_optimizations(&self) -> Result<(), BridgeError> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PeepholePattern {
pub name: String,
pub match_seq: Vec<GenericMachineOpcode>,
pub replace_seq: Vec<GenericMachineOpcode>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bridge_new() {
let bridge = BPFX86Bridge::new(BridgeArch::Bpf, BridgeArch::X86_64);
assert_eq!(bridge.source_arch, BridgeArch::Bpf);
assert_eq!(bridge.target_arch, BridgeArch::X86_64);
}
#[test]
fn test_bridge_default() {
let bridge = BPFX86Bridge::default();
assert_eq!(bridge.source_arch, BridgeArch::Bpf);
assert_eq!(bridge.target_arch, BridgeArch::X86_64);
}
#[test]
fn test_bridge_from_triples() {
let bridge = BPFX86Bridge::from_triples("bpf", "x86_64");
assert!(bridge.source_arch.is_bpf_family());
assert!(bridge.target_arch.is_x86_family());
}
#[test]
fn test_bridge_describe() {
let bridge = BPFX86Bridge::default();
let desc = bridge.describe();
assert!(desc.contains("BPFX86Bridge"));
}
#[test]
fn test_bridge_set_opt_level() {
let mut bridge = BPFX86Bridge::default();
bridge.set_opt_level(CodeGenOptLevel::Aggressive);
}
#[test]
fn test_bridge_set_bpf_cpu() {
let mut bridge = BPFX86Bridge::default();
bridge.set_bpf_cpu("v3");
assert_eq!(bridge.bpf_tm.get_cpu(), "v3");
}
#[test]
fn test_bridge_features() {
let mut bridge = BPFX86Bridge::default();
assert!(!bridge.has_feature(BridgeFeature::AVX));
bridge.enable_feature(BridgeFeature::AVX);
assert!(bridge.has_feature(BridgeFeature::AVX));
}
#[test]
fn test_arch_is_64bit() {
assert!(BridgeArch::X86_64.is_64bit());
assert!(!BridgeArch::X86_32.is_64bit());
assert!(BridgeArch::Bpf.is_64bit());
assert!(BridgeArch::Bpfeb.is_64bit());
}
#[test]
fn test_arch_is_bpf_family() {
assert!(BridgeArch::Bpf.is_bpf_family());
assert!(BridgeArch::Bpfeb.is_bpf_family());
assert!(!BridgeArch::X86_64.is_bpf_family());
}
#[test]
fn test_arch_is_x86_family() {
assert!(BridgeArch::X86_64.is_x86_family());
assert!(BridgeArch::X86_32.is_x86_family());
assert!(!BridgeArch::Bpf.is_x86_family());
}
#[test]
fn test_arch_pointer_width() {
assert_eq!(BridgeArch::X86_64.pointer_width(), 64);
assert_eq!(BridgeArch::X86_32.pointer_width(), 32);
assert_eq!(BridgeArch::Bpf.pointer_width(), 64);
}
#[test]
fn test_arch_data_layout() {
assert!(BridgeArch::Bpf.data_layout().contains("e-m:e"));
assert!(BridgeArch::Bpfeb.data_layout().contains("E-m:e"));
}
#[test]
fn test_arch_display() {
assert_eq!(format!("{}", BridgeArch::X86_64), "x86_64");
assert_eq!(format!("{}", BridgeArch::Bpf), "bpf");
assert_eq!(format!("{}", BridgeArch::Bpfeb), "bpfeb");
}
#[test]
fn test_features_enable_disable() {
let mut features = BridgeFeatures::default();
assert!(!features.has(BridgeFeature::ALU32));
features.enable(BridgeFeature::ALU32);
assert!(features.has(BridgeFeature::ALU32));
features.disable(BridgeFeature::ALU32);
assert!(!features.has(BridgeFeature::ALU32));
}
#[test]
fn test_features_enable_bpf_v2() {
let mut features = BridgeFeatures::default();
features.enable_bpf_v2();
assert!(features.has(BridgeFeature::ALU32));
}
#[test]
fn test_features_enable_bpf_v3() {
let mut features = BridgeFeatures::default();
features.enable_bpf_v3();
assert!(features.has(BridgeFeature::ALU32));
assert!(features.has(BridgeFeature::Atomics));
assert!(features.has(BridgeFeature::TailCall));
assert!(features.has(BridgeFeature::EndianOps));
}
#[test]
fn test_features_enable_x86_avx() {
let mut features = BridgeFeatures::default();
features.enable_x86_avx();
assert!(features.has(BridgeFeature::AVX));
assert!(features.has(BridgeFeature::AVX2));
assert!(features.has(BridgeFeature::FMA));
}
#[test]
fn test_features_to_string() {
let mut features = BridgeFeatures::default();
features.enable(BridgeFeature::ALU32);
features.enable(BridgeFeature::SSE);
let s = features.to_feature_string();
assert!(s.contains("+alu32"));
assert!(s.contains("+sse"));
}
#[test]
fn test_features_from_string() {
let features = BridgeFeatures::from_string("+alu32,+sse,-avx");
assert!(features.has(BridgeFeature::ALU32));
assert!(features.has(BridgeFeature::SSE));
assert!(!features.has(BridgeFeature::AVX));
}
#[test]
fn test_features_list_enabled() {
let mut features = BridgeFeatures::default();
features.enable(BridgeFeature::ALU32);
features.enable(BridgeFeature::Atomics);
let list = features.list_enabled();
assert_eq!(list.len(), 2);
}
#[test]
fn test_bpf_instr_mnemonic_count() {
let all = BridgeBPFInstrInfo::all_opcodes();
assert_eq!(all.len(), 78);
assert_eq!(BridgeBPFInstrInfo::opcode_count(), 78);
}
#[test]
fn test_bpf_instr_is_branch() {
assert!(BridgeBPFInstrInfo::is_branch(BpfOpcode::JEQ));
assert!(BridgeBPFInstrInfo::is_branch(BpfOpcode::JGT32));
assert!(!BridgeBPFInstrInfo::is_branch(BpfOpcode::ADD));
assert!(!BridgeBPFInstrInfo::is_branch(BpfOpcode::LD_DW));
}
#[test]
fn test_bpf_instr_is_terminator() {
assert!(BridgeBPFInstrInfo::is_terminator(BpfOpcode::EXIT));
assert!(BridgeBPFInstrInfo::is_terminator(BpfOpcode::JA));
assert!(!BridgeBPFInstrInfo::is_terminator(BpfOpcode::ADD));
}
#[test]
fn test_bpf_instr_is_unconditional() {
assert!(BridgeBPFInstrInfo::is_unconditional(BpfOpcode::JA));
assert!(!BridgeBPFInstrInfo::is_unconditional(BpfOpcode::JEQ));
}
#[test]
fn test_bpf_instr_is_alu32() {
assert!(BridgeBPFInstrInfo::is_alu32(BpfOpcode::ADD32));
assert!(BridgeBPFInstrInfo::is_alu32(BpfOpcode::MOV32));
assert!(!BridgeBPFInstrInfo::is_alu32(BpfOpcode::ADD));
}
#[test]
fn test_bpf_instr_is_atomic() {
assert!(BridgeBPFInstrInfo::is_atomic(BpfOpcode::XADD));
assert!(BridgeBPFInstrInfo::is_atomic(BpfOpcode::FETCH_AND));
assert!(!BridgeBPFInstrInfo::is_atomic(BpfOpcode::ADD));
}
#[test]
fn test_bpf_instr_is_endian() {
assert!(BridgeBPFInstrInfo::is_endian(BpfOpcode::END_LE16));
assert!(BridgeBPFInstrInfo::is_endian(BpfOpcode::END_BE64));
assert!(!BridgeBPFInstrInfo::is_endian(BpfOpcode::ADD));
}
#[test]
fn test_bpf_instr_is_memory() {
assert!(BridgeBPFInstrInfo::is_memory(BpfOpcode::LDX_B));
assert!(BridgeBPFInstrInfo::is_memory(BpfOpcode::ST_B));
assert!(!BridgeBPFInstrInfo::is_memory(BpfOpcode::ADD));
}
#[test]
fn test_bpf_instr_all_mnemonics_unique() {
let all = BridgeBPFInstrInfo::all_opcodes();
let mnemonics: Vec<&str> = all
.iter()
.map(|&op| BridgeBPFInstrInfo::get_mnemonic(op))
.collect();
let mut uniq: HashSet<&str> = HashSet::new();
for m in &mnemonics {
uniq.insert(m);
}
assert_eq!(
mnemonics.len(),
uniq.len(),
"all mnemonics should be unique"
);
}
#[test]
fn test_bpf_reg_name() {
assert_eq!(BridgeBPFRegisterInfo::reg_name(0), "r0");
assert_eq!(BridgeBPFRegisterInfo::reg_name(10), "r10");
assert_eq!(BridgeBPFRegisterInfo::reg_name(255), "unknown");
}
#[test]
fn test_bpf_reg_callee_saved() {
assert!(!BridgeBPFRegisterInfo::is_callee_saved(0));
assert!(!BridgeBPFRegisterInfo::is_callee_saved(5));
assert!(BridgeBPFRegisterInfo::is_callee_saved(6));
assert!(BridgeBPFRegisterInfo::is_callee_saved(9));
assert!(BridgeBPFRegisterInfo::is_callee_saved(10));
}
#[test]
fn test_bpf_reg_caller_saved() {
assert!(BridgeBPFRegisterInfo::is_caller_saved(0));
assert!(BridgeBPFRegisterInfo::is_caller_saved(5));
assert!(!BridgeBPFRegisterInfo::is_caller_saved(6));
assert!(!BridgeBPFRegisterInfo::is_caller_saved(10));
}
#[test]
fn test_bpf_reg_return() {
assert_eq!(BridgeBPFRegisterInfo::return_reg(), 0);
}
#[test]
fn test_bpf_reg_args() {
assert_eq!(BridgeBPFRegisterInfo::arg_regs(), vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_bpf_reg_callee_saved_regs() {
assert_eq!(BridgeBPFRegisterInfo::callee_saved_regs(), vec![6, 7, 8, 9]);
}
#[test]
fn test_bpf_reg_frame_pointer() {
assert_eq!(BridgeBPFRegisterInfo::frame_pointer_reg(), 10);
}
#[test]
fn test_bpf_reg_all() {
assert_eq!(BridgeBPFRegisterInfo::all_regs().len(), 11);
}
#[test]
fn test_bpf_reg_num() {
assert_eq!(BridgeBPFRegisterInfo::num_regs(), 11);
}
#[test]
fn test_bpf_reg_max_id() {
assert_eq!(BridgeBPFRegisterInfo::max_reg_id(), 10);
}
#[test]
fn test_bpf_cc_arg_reg() {
assert_eq!(BridgeBPFCallingConvention::arg_reg(0), Some(1));
assert_eq!(BridgeBPFCallingConvention::arg_reg(4), Some(5));
assert_eq!(BridgeBPFCallingConvention::arg_reg(5), None);
}
#[test]
fn test_bpf_cc_num_arg_regs() {
assert_eq!(BridgeBPFCallingConvention::num_arg_regs(), 5);
}
#[test]
fn test_bpf_cc_return_reg() {
assert_eq!(BridgeBPFCallingConvention::return_reg(), 0);
}
#[test]
fn test_bpf_cc_callee_saved() {
assert_eq!(
BridgeBPFCallingConvention::callee_saved_regs(),
vec![6, 7, 8, 9]
);
}
#[test]
fn test_bpf_cc_caller_saved() {
let regs = BridgeBPFCallingConvention::caller_saved_regs();
assert_eq!(regs, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn test_bpf_cc_epilogue_bytes() {
let epi = BridgeBPFCallingConvention::emit_epilogue();
assert!(!epi.is_empty());
}
#[test]
fn test_bpf_reg_to_x86() {
assert_eq!(BridgeBPFCallingConvention::bpf_reg_to_x86(0), 0); assert_eq!(BridgeBPFCallingConvention::bpf_reg_to_x86(1), 7); assert_eq!(BridgeBPFCallingConvention::bpf_reg_to_x86(10), 5); }
#[test]
fn test_jit_pattern_table_creation() {
let table = BPFJITPatternTable::new();
assert!(table.pattern_count() > 0);
}
#[test]
fn test_jit_lookup_common_ops() {
let table = BPFJITPatternTable::new();
assert!(table.lookup(BpfOpcode::ADD).is_some());
assert!(table.lookup(BpfOpcode::EXIT).is_some());
assert!(table.lookup(BpfOpcode::MOV).is_some());
}
#[test]
fn test_jit_translate_add() {
let table = BPFJITPatternTable::new();
let bytes = table.jit_translate(BpfOpcode::ADD).unwrap();
assert!(!bytes.is_empty());
assert!(bytes[0] == 0x48); assert!(bytes[1] == 0x01); }
#[test]
fn test_jit_translate_exit() {
let table = BPFJITPatternTable::new();
let bytes = table.jit_translate(BpfOpcode::EXIT).unwrap();
assert_eq!(bytes, vec![0xC3]); }
#[test]
fn test_jit_translate_atomic() {
let table = BPFJITPatternTable::new();
let bytes = table.jit_translate(BpfOpcode::XADD).unwrap();
assert!(!bytes.is_empty());
assert_eq!(bytes[0], 0xF0); }
#[test]
fn test_jit_translate_fetch_add() {
let table = BPFJITPatternTable::new();
let bytes = table.jit_translate(BpfOpcode::FETCH_ADD).unwrap();
assert_eq!(bytes[0], 0xF0); }
#[test]
fn test_jit_translate_alu32() {
let table = BPFJITPatternTable::new();
let bytes = table.jit_translate(BpfOpcode::ADD32).unwrap();
assert!(!bytes.is_empty());
assert_ne!(bytes[0], 0x48);
}
#[test]
fn test_jit_translate_load() {
let table = BPFJITPatternTable::new();
assert!(table.jit_translate(BpfOpcode::LDX_W).is_some());
assert!(table.jit_translate(BpfOpcode::LDX_DW).is_some());
}
#[test]
fn test_jit_translate_store() {
let table = BPFJITPatternTable::new();
assert!(table.jit_translate(BpfOpcode::ST_B).is_some());
assert!(table.jit_translate(BpfOpcode::ST_DW).is_some());
}
#[test]
fn test_jit_pattern_unique_per_opcode() {
let table = BPFJITPatternTable::new();
let mut seen = HashSet::new();
for pattern in table.patterns.values() {
assert!(
!seen.contains(&pattern.bpf_opcode),
"duplicate pattern for {:?}",
pattern.bpf_opcode
);
seen.insert(pattern.bpf_opcode);
}
}
#[test]
fn test_isel_new() {
let isel = CrossTargetISel::new(BridgeArch::Bpf);
assert!(isel.pattern_count() > 0);
}
#[test]
fn test_isel_allocate_vreg() {
let mut isel = CrossTargetISel::new(BridgeArch::Bpf);
let v1 = isel.allocate_vreg();
let v2 = isel.allocate_vreg();
assert!(v1 < v2);
}
#[test]
fn test_isel_is_legal() {
let isel = CrossTargetISel::new(BridgeArch::Bpf);
assert!(isel.is_legal(GenericMachineOpcode::Add, &TypeKindRepr::Integer(64)));
}
#[test]
fn test_isel_get_legalized_type() {
let isel = CrossTargetISel::new(BridgeArch::Bpf);
let i1 = TypeKindRepr::Integer(1);
let legal = isel.get_legalized_type(&i1);
assert_eq!(legal, TypeKindRepr::Integer(8));
}
#[test]
fn test_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);
}
#[test]
fn test_reg_class_bpf_count() {
assert_eq!(CrossRegClass::GPR64.reg_count(BridgeArch::Bpf), 11);
assert_eq!(CrossRegClass::GPR32.reg_count(BridgeArch::Bpf), 11);
}
#[test]
fn test_reg_class_x86_count() {
assert_eq!(CrossRegClass::GPR64.reg_count(BridgeArch::X86_64), 16);
}
#[test]
fn test_reg_class_name() {
assert_eq!(CrossRegClass::GPR64.name(), "GPR64");
assert_eq!(CrossRegClass::FPR128.name(), "FPR128");
}
#[test]
fn test_reg_class_to_bpf_reg_class() {
assert_eq!(
CrossRegClass::GPR64.to_bpf_reg_class(),
Some(BpfRegClass::GPR)
);
assert_eq!(
CrossRegClass::GPR32.to_bpf_reg_class(),
Some(BpfRegClass::GPR)
);
}
#[test]
fn test_reg_class_display() {
assert_eq!(format!("{}", CrossRegClass::GPR64), "GPR64");
}
#[test]
fn test_phys_reg_new() {
let reg = CrossPhysReg::new(0, CrossRegClass::GPR64);
assert_eq!(reg.number, 0);
assert_eq!(reg.class, CrossRegClass::GPR64);
}
#[test]
fn test_phys_reg_to_bpf_reg() {
let reg = CrossPhysReg::new(5, CrossRegClass::GPR64);
assert_eq!(reg.to_bpf_reg(), 5);
}
#[test]
fn test_phys_reg_display() {
let reg = CrossPhysReg::new(0, CrossRegClass::GPR64);
assert_eq!(format!("{}", reg), "GPR640");
}
#[test]
fn test_alloc_order_bpf() {
let orders = AllocationOrder::bpf_defaults();
assert!(!orders.is_empty());
let gpr = &orders[0];
assert_eq!(gpr.caller_saved.len(), 6); assert_eq!(gpr.callee_saved.len(), 4); assert_eq!(gpr.reserved.len(), 1); }
#[test]
fn test_alloc_order_x86() {
let orders = AllocationOrder::x86_64_defaults();
assert!(!orders.is_empty());
}
#[test]
fn test_alloc_order_for_arch() {
let bpf_orders = AllocationOrder::for_arch(BridgeArch::Bpf);
let x86_orders = AllocationOrder::for_arch(BridgeArch::X86_64);
assert!(!bpf_orders.is_empty());
assert!(!x86_orders.is_empty());
}
#[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_merge_ranges() {
let mut interval = LiveInterval::new(0, CrossRegClass::GPR64);
interval.add_range(0, 5);
interval.add_range(3, 10);
assert_eq!(interval.ranges.len(), 1);
assert_eq!(interval.ranges[0], (0, 10));
}
#[test]
fn test_live_interval_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(3, 8);
assert!(a.overlaps(&b));
}
#[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_live_interval_priority() {
let mut interval = LiveInterval::new(0, CrossRegClass::GPR64);
interval.add_range(0, 20);
interval.compute_priority();
assert!(interval.priority > 0.0);
}
#[test]
fn test_reg_alloc_new() {
let ra = CrossTargetRegAlloc::new(BridgeArch::Bpf);
assert!(!ra.alloc_orders.is_empty());
assert_eq!(ra.intervals.len(), 0);
assert_eq!(ra.spill_slots.len(), 0);
}
#[test]
fn test_reg_alloc_get_phys_reg_none() {
let ra = CrossTargetRegAlloc::new(BridgeArch::Bpf);
assert!(ra.get_phys_reg(0).is_none());
}
#[test]
fn test_reg_alloc_is_not_spilled() {
let ra = CrossTargetRegAlloc::new(BridgeArch::Bpf);
assert!(!ra.is_spilled(0));
}
#[test]
fn test_reg_alloc_counts() {
let ra = CrossTargetRegAlloc::new(BridgeArch::Bpf);
assert_eq!(ra.spill_slot_count(), 0);
assert_eq!(ra.interval_count(), 0);
}
#[test]
fn test_frame_new_bpf() {
let frame = CrossStackFrame::new(BridgeArch::Bpf);
assert_eq!(frame.stack_alignment, 8);
assert_eq!(frame.red_zone_size, 0);
}
#[test]
fn test_frame_new_x86() {
let frame = CrossStackFrame::new(BridgeArch::X86_64);
assert_eq!(frame.stack_alignment, 16);
assert_eq!(frame.red_zone_size, 128);
}
#[test]
fn test_frame_align() {
assert_eq!(CrossStackFrame::align_frame_size(10, 16), 16);
assert_eq!(CrossStackFrame::align_frame_size(16, 16), 16);
assert_eq!(CrossStackFrame::align_frame_size(17, 8), 24);
}
#[test]
fn test_frame_compute_total_size() {
let mut frame = CrossStackFrame::new(BridgeArch::Bpf);
frame.callee_saved_size = 32;
frame.max_call_frame_size = 0;
frame.compute_total_size();
assert_eq!(frame.frame_size, 32);
}
#[test]
fn test_frame_bpf_stack_size_limit() {
let mut frame = CrossStackFrame::new(BridgeArch::Bpf);
frame.frame_size = 1024;
assert_eq!(frame.bpf_stack_size(), BPF_MAX_STACK);
}
#[test]
fn test_frame_lowering_new() {
let fl = CrossTargetFrameLowering::new(BridgeArch::Bpf);
assert!(!fl.needs_frame_lowering());
}
#[test]
fn test_frame_lowering_needs_when_calls() {
let mut fl = CrossTargetFrameLowering::new(BridgeArch::Bpf);
fl.frame.has_calls = true;
assert!(fl.needs_frame_lowering());
}
#[test]
fn test_abi_new() {
let abi = CrossTargetABI::new(BridgeArch::Bpf);
assert_eq!(abi.arch, BridgeArch::Bpf);
}
#[test]
fn test_cost_model_new() {
let model = BPFX86CostModel::new(BridgeArch::Bpf, BridgeArch::X86_64);
assert!(!model.cost_table.is_empty());
}
#[test]
fn test_cost_model_target_cost() {
let model = BPFX86CostModel::new(BridgeArch::Bpf, BridgeArch::X86_64);
let cost = model.target_cost(GenericMachineOpcode::Add);
assert!(cost > 0);
}
#[test]
fn test_cost_model_source_cost() {
let model = BPFX86CostModel::new(BridgeArch::Bpf, BridgeArch::X86_64);
let cost = model.source_cost(GenericMachineOpcode::Add);
assert!(cost > 0);
}
#[test]
fn test_cost_model_expensive_ops() {
let model = BPFX86CostModel::new(BridgeArch::Bpf, BridgeArch::X86_64);
let div_cost = model.target_cost(GenericMachineOpcode::SDiv);
assert!(div_cost > 10);
}
#[test]
fn test_optimizer_new() {
let opt = CrossTargetOptimization::new(BridgeArch::Bpf);
assert!(opt.constant_folding);
assert!(opt.dead_code_elim);
assert!(!opt.jit_optimize); }
#[test]
fn test_optimizer_x86_jit() {
let opt = CrossTargetOptimization::new(BridgeArch::X86_64);
assert!(opt.jit_optimize);
}
#[test]
fn test_machine_ir_inst_new() {
let inst = MachineIRInst::new(GenericMachineOpcode::Add);
assert!(inst.dst.is_none());
assert!(inst.srcs.is_empty());
}
#[test]
fn test_machine_ir_inst_with_dst() {
let inst = MachineIRInst::new(GenericMachineOpcode::Add).with_dst(42);
assert_eq!(inst.dst, Some(42));
}
#[test]
fn test_machine_ir_inst_with_src() {
let inst =
MachineIRInst::new(GenericMachineOpcode::Add).with_src(MachineIROperand::Imm(10));
assert_eq!(inst.srcs.len(), 1);
}
#[test]
fn test_mir_operand_is_reg() {
let op = MachineIROperand::VReg(1);
assert!(op.is_reg());
assert!(!op.is_imm());
}
#[test]
fn test_mir_operand_is_imm() {
let op = MachineIROperand::Imm(42);
assert!(op.is_imm());
assert!(!op.is_reg());
}
#[test]
fn test_generic_opcode_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::Sub.is_commutative());
}
#[test]
fn test_bridge_error_display() {
let err = BridgeError::UnsupportedArchPair {
source: BridgeArch::Bpf,
target: BridgeArch::X86_32,
};
let msg = format!("{}", err);
assert!(msg.contains("bpf"));
assert!(msg.contains("i386"));
}
#[test]
fn test_bridge_error_isel() {
let err = BridgeError::ISelFailed {
opcode: 42,
reason: "no pattern".to_string(),
};
let msg = format!("{}", err);
assert!(msg.contains("42"));
}
#[test]
fn test_bridge_error_verifier() {
let err = BridgeError::VerifierRejection {
reason: "unbounded loop".to_string(),
};
let msg = format!("{}", err);
assert!(msg.contains("unbounded loop"));
}
#[test]
fn test_bridge_error_program_too_large() {
let err = BridgeError::ProgramTooLarge {
count: 2_000_000,
limit: 1_000_000,
};
let msg = format!("{}", err);
assert!(msg.contains("2000000"));
assert!(msg.contains("1000000"));
}
#[test]
fn test_cost_estimate_zero() {
let cost = CostEstimate::zero();
assert_eq!(cost.latency, 0);
assert_eq!(cost.throughput, 0);
assert_eq!(cost.code_size, 0);
}
#[test]
fn test_cost_estimate_new() {
let cost = CostEstimate::new(5, 2, 16);
assert_eq!(cost.latency, 5);
assert_eq!(cost.throughput, 2);
assert_eq!(cost.code_size, 16);
}
#[test]
fn test_bridge_output() {
let output = BridgeOutput {
instructions_emitted: 100,
basic_blocks: 5,
target_arch: BridgeArch::X86_64,
};
assert_eq!(output.instructions_emitted, 100);
assert_eq!(output.basic_blocks, 5);
}
#[test]
fn test_type_kind_size_bits() {
assert_eq!(TypeKindRepr::Void.size_bits(), 0);
assert_eq!(TypeKindRepr::Integer(32).size_bits(), 32);
assert_eq!(TypeKindRepr::Float(64).size_bits(), 64);
assert_eq!(TypeKindRepr::Pointer.size_bits(), 64);
}
#[test]
fn test_type_kind_vector_size() {
let v4i32 = TypeKindRepr::Vector(4, Box::new(TypeKindRepr::Integer(32)));
assert_eq!(v4i32.size_bits(), 128);
}
#[test]
fn test_type_kind_is_integer_float() {
assert!(TypeKindRepr::Integer(64).is_integer());
assert!(!TypeKindRepr::Integer(64).is_float());
assert!(TypeKindRepr::Float(32).is_float());
assert!(!TypeKindRepr::Float(32).is_integer());
}
#[test]
fn test_bpf_constants() {
assert_eq!(BPF_STACK_ALIGNMENT, 8);
assert_eq!(BPF_RED_ZONE_SIZE, 0);
assert_eq!(BPF_INSTR_SIZE, 8);
assert_eq!(BPF_GPR_COUNT, 11);
}
#[test]
fn test_bpf_max_constants() {
assert_eq!(BPF_MAX_STACK, 512);
assert!(BPF_MAX_INSNS > 0);
assert!(BPF_MAX_IMM > 0);
assert!(BPF_MAX_OFFSET > 0);
}
#[test]
fn test_data_layout_constants() {
assert!(!BPF_DATA_LAYOUT.is_empty());
assert!(!BPFEB_DATA_LAYOUT.is_empty());
assert!(!X86_64_DATA_LAYOUT.is_empty());
}
#[test]
fn test_peephole_pattern_creation() {
let pattern = PeepholePattern {
name: "mov_r_r".into(),
match_seq: vec![GenericMachineOpcode::Copy],
replace_seq: vec![],
};
assert_eq!(pattern.name, "mov_r_r");
}
#[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.jit_patterns_applied, 0);
}
#[test]
fn test_bridge_integration_describe() {
let mut bridge = BPFX86Bridge::default();
bridge.enable_feature(BridgeFeature::ALU32);
bridge.set_bpf_cpu("v3");
let desc = bridge.describe();
assert!(!desc.is_empty());
}
#[test]
fn test_verifier_max_instructions() {
let bridge = BPFX86Bridge::default();
assert_eq!(bridge.get_bpf_max_instructions(), 1_000_000);
}
#[test]
fn test_bpf_program_size_check() {
let err = BridgeError::ProgramTooLarge {
count: 2_000_000,
limit: 1_000_000,
};
let msg = format!("{}", err);
assert!(msg.contains("2000000"));
}
#[test]
fn test_bpf_unimplemented_feature() {
let err = BridgeError::Unimplemented {
feature: "bpf_to_bpf_calls".to_string(),
};
let msg = format!("{}", err);
assert!(msg.contains("bpf_to_bpf_calls"));
}
#[test]
fn test_jit_all_patterns_verifier_safe() {
let table = BPFJITPatternTable::new();
for pattern in table.patterns.values() {
assert!(
pattern.verifier_safe,
"pattern {} should be verifier safe",
pattern.name
);
}
}
#[test]
fn test_jit_pattern_latency_reasonable() {
let table = BPFJITPatternTable::new();
for pattern in table.patterns.values() {
assert!(
pattern.x86_latency > 0,
"pattern {} should have positive latency",
pattern.name
);
assert!(
pattern.x86_latency <= 100,
"pattern {} latency should be reasonable",
pattern.name
);
}
}
#[test]
fn test_jit_patterns_all_have_size() {
let table = BPFJITPatternTable::new();
for pattern in table.patterns.values() {
assert!(
pattern.x86_size > 0,
"pattern {} should have non-zero size",
pattern.name
);
assert!(
!pattern.x86_sequence.is_empty(),
"pattern {} should have x86 bytes",
pattern.name
);
}
}
#[test]
fn test_jit_translate_all_reg_reg_alu() {
let table = BPFJITPatternTable::new();
for op in &[
BpfOpcode::ADD,
BpfOpcode::SUB,
BpfOpcode::MUL,
BpfOpcode::OR,
BpfOpcode::AND,
BpfOpcode::LSH,
BpfOpcode::RSH,
BpfOpcode::XOR,
BpfOpcode::MOV,
BpfOpcode::ARSH,
] {
assert!(
table.jit_translate(*op).is_some(),
"missing JIT for {:?}",
op
);
}
}
#[test]
fn test_jit_alu64_has_rex_prefix() {
let table = BPFJITPatternTable::new();
let ops: &[BpfOpcode] = &[
BpfOpcode::ADD,
BpfOpcode::SUB,
BpfOpcode::OR,
BpfOpcode::AND,
BpfOpcode::XOR,
BpfOpcode::MOV,
BpfOpcode::MUL,
];
for op in ops {
let bytes = table.jit_translate(*op).unwrap();
assert_eq!(bytes[0], 0x48, "64-bit {:?} should have REX.W prefix", op);
}
}
#[test]
fn test_jit_alu32_no_rex_prefix() {
let table = BPFJITPatternTable::new();
let ops: &[BpfOpcode] = &[
BpfOpcode::ADD32,
BpfOpcode::SUB32,
BpfOpcode::MUL32,
BpfOpcode::MOV32,
];
for op in ops {
let bytes = table.jit_translate(*op).unwrap();
assert_ne!(
bytes[0], 0x48,
"32-bit {:?} should NOT have REX.W prefix",
op
);
}
}
#[test]
fn test_bpf_reg_to_x86_all_mapped() {
for i in 0..=10u8 {
let x86 = BridgeBPFCallingConvention::bpf_reg_to_x86(i);
assert!(x86 <= 15, "BPF reg {} maps to X86 reg {} (max 15)", i, x86);
}
}
#[test]
fn test_bpf_reg_callee_saved_consistent() {
let bridge_regs = BridgeBPFRegisterInfo::callee_saved_regs();
let cc_regs = BridgeBPFCallingConvention::callee_saved_regs();
assert_eq!(bridge_regs, cc_regs);
}
#[test]
fn test_bpf_caller_saved_complement_callee_saved() {
let callee = BridgeBPFRegisterInfo::callee_saved_regs();
let caller = BridgeBPFCallingConvention::caller_saved_regs();
for reg in 0..=10u8 {
let in_callee = callee.contains(®);
let in_caller = caller.contains(®);
if reg == 10 {
assert!(in_callee);
assert!(!in_caller);
} else {
assert_ne!(
in_callee, in_caller,
"reg {} should be exactly one of callee/caller saved",
reg
);
}
}
}
#[test]
fn test_frame_lowering_leaf_bpf() {
let mut fl = CrossTargetFrameLowering::new(BridgeArch::Bpf);
fl.frame.has_calls = false;
fl.frame.compute_total_size(0);
assert_eq!(fl.frame.frame_size, 0);
}
#[test]
fn test_frame_lowering_nonleaf_bpf() {
let mut fl = CrossTargetFrameLowering::new(BridgeArch::Bpf);
fl.frame.has_calls = true;
fl.frame.callee_saved_size = 32;
fl.frame.max_call_frame_size = 0;
fl.frame.compute_total_size();
assert!(fl.frame.frame_size >= 32);
}
#[test]
fn test_bpf_stack_size_capped() {
let mut frame = CrossStackFrame::new(BridgeArch::Bpf);
frame.frame_size = 600;
assert_eq!(frame.bpf_stack_size(), BPF_MAX_STACK);
}
#[test]
fn test_bpf_frame_no_red_zone() {
let frame = CrossStackFrame::new(BridgeArch::Bpf);
assert_eq!(frame.red_zone_size, 0);
}
#[test]
fn test_abi_bpf_and_x86_diff() {
let bpf_abi = CrossTargetABI::new(BridgeArch::Bpf);
let x86_abi = CrossTargetABI::new(BridgeArch::X86_64);
assert_ne!(bpf_abi.arch, x86_abi.arch);
}
#[test]
fn test_reg_alloc_bpf_has_11_regs() {
let ra = CrossTargetRegAlloc::new(BridgeArch::Bpf);
let gpr = ra
.alloc_orders
.iter()
.find(|o| o.class == CrossRegClass::GPR64)
.unwrap();
assert_eq!(gpr.order.len() + gpr.reserved.len(), 11);
}
#[test]
fn test_reg_alloc_bpf_r10_reserved() {
let ra = CrossTargetRegAlloc::new(BridgeArch::Bpf);
let gpr = ra
.alloc_orders
.iter()
.find(|o| o.class == CrossRegClass::GPR64)
.unwrap();
assert!(gpr.reserved.iter().any(|r| r.number == 10));
}
#[test]
fn test_cost_model_bpf_to_x86_division_expensive() {
let model = BPFX86CostModel::new(BridgeArch::Bpf, BridgeArch::X86_64);
let div_cost = model.target_cost(GenericMachineOpcode::SDiv);
let add_cost = model.target_cost(GenericMachineOpcode::Add);
assert!(div_cost > add_cost);
}
#[test]
fn test_cost_model_bpf_load_cheaper_than_x86() {
let model = BPFX86CostModel::new(BridgeArch::Bpf, BridgeArch::X86_64);
let src_cost = model.source_cost(GenericMachineOpcode::Load);
let tgt_cost = model.target_cost(GenericMachineOpcode::Load);
assert!(
tgt_cost >= src_cost,
"BPF load cost = {}, X86 load cost = {}",
src_cost,
tgt_cost
);
}
#[test]
fn test_bpf_lddw_has_special_pattern() {
let isel = CrossTargetISel::new(BridgeArch::Bpf);
let has_lddw = isel.patterns.iter().any(|p| p.name == "bpf_lddw");
assert!(has_lddw);
}
#[test]
fn test_bpf_atomic_has_pattern() {
let isel = CrossTargetISel::new(BridgeArch::Bpf);
let has_atomic = isel.patterns.iter().any(|p| p.name == "bpf_atomic_add");
assert!(has_atomic);
}
#[test]
fn test_pattern_count_consistent() {
let isel_bpf = CrossTargetISel::new(BridgeArch::Bpf);
let isel_x86 = CrossTargetISel::new(BridgeArch::X86_64);
assert_eq!(isel_bpf.pattern_count(), isel_x86.pattern_count());
}
#[test]
fn test_all_bpf_opcodes_have_mnemonic() {
for op in BridgeBPFInstrInfo::all_opcodes() {
let mnemonic = BridgeBPFInstrInfo::get_mnemonic(op);
assert!(!mnemonic.is_empty());
assert_ne!(mnemonic, "unknown", "opcode {:?} has unknown mnemonic", op);
}
}
#[test]
fn test_bpf_opcode_count_matches() {
assert_eq!(
BridgeBPFInstrInfo::all_opcodes().len(),
BridgeBPFInstrInfo::opcode_count()
);
}
#[test]
fn test_bridge_stats_accumulation() {
let mut stats = BridgeStats::default();
stats.functions_processed += 1;
stats.isel_cycles += 1;
stats.pattern_matches += 5;
stats.jit_patterns_applied += 20;
assert_eq!(stats.functions_processed, 1);
assert_eq!(stats.jit_patterns_applied, 20);
}
#[test]
fn test_features_roundtrip_empty() {
let features = BridgeFeatures::default();
let s = features.to_feature_string();
let parsed = BridgeFeatures::from_string(&s);
assert_eq!(features.list_enabled(), parsed.list_enabled());
}
#[test]
fn test_features_roundtrip_with_features() {
let mut features = BridgeFeatures::default();
features.enable(BridgeFeature::ALU32);
features.enable(BridgeFeature::CO_RE);
features.enable(BridgeFeature::Atomics);
let s = features.to_feature_string();
let parsed = BridgeFeatures::from_string(&s);
assert!(parsed.has(BridgeFeature::ALU32));
assert!(parsed.has(BridgeFeature::CO_RE));
assert!(parsed.has(BridgeFeature::Atomics));
}
#[test]
fn test_features_from_string_negative() {
let features = BridgeFeatures::from_string("+alu32,-atomics");
assert!(features.has(BridgeFeature::ALU32));
assert!(!features.has(BridgeFeature::Atomics));
}
#[test]
fn test_reg_name_out_of_bounds() {
assert_eq!(BridgeBPFRegisterInfo::reg_name(255), "unknown");
assert_eq!(BridgeBPFRegisterInfo::reg_name(11), "unknown");
}
#[test]
fn test_bpf_reg_to_x86_out_of_bounds() {
assert_eq!(BridgeBPFCallingConvention::bpf_reg_to_x86(255), 0);
}
#[test]
fn test_cost_estimate_profitable() {
let cost = CostEstimate::new(1, 1, 8);
assert!(cost.profitable);
assert!(!cost.vectorizable);
}
#[test]
fn test_bridge_always_has_jit_patterns_for_target_x86() {
let bridge = BPFX86Bridge::new(BridgeArch::Bpf, BridgeArch::X86_64);
assert!(bridge.isel.jit_patterns.pattern_count() > 0);
}
#[test]
fn test_bpf_class_alu_constants() {
assert_eq!(bpf_class::LD, 0x00);
assert_eq!(bpf_class::LDX, 0x01);
assert_eq!(bpf_class::ST, 0x02);
assert_eq!(bpf_class::STX, 0x03);
assert_eq!(bpf_class::ALU, 0x04);
assert_eq!(bpf_class::JMP, 0x05);
assert_eq!(bpf_class::JMP32, 0x06);
assert_eq!(bpf_class::ALU64, 0x07);
}
#[test]
fn test_bpf_alu_op_constants() {
assert_eq!(bpf_alu_op::ADD, 0x00);
assert_eq!(bpf_alu_op::XOR, 0xA0);
assert_eq!(bpf_alu_op::MOV, 0xB0);
assert_eq!(bpf_alu_op::END, 0xD0);
}
#[test]
fn test_bpf_jmp_op_constants() {
assert_eq!(bpf_jmp_op::JA, 0x00);
assert_eq!(bpf_jmp_op::CALL, 0x80);
assert_eq!(bpf_jmp_op::EXIT, 0x90);
}
#[test]
fn test_bpf_alloc_order_no_duplicates() {
let orders = AllocationOrder::bpf_defaults();
for order in &orders {
let mut seen = HashSet::new();
for reg in &order.order {
assert!(
!seen.contains(reg),
"duplicate register in allocation order"
);
seen.insert(*reg);
}
}
}
#[test]
fn test_bpf_alloc_order_disjoint_sets() {
let orders = AllocationOrder::bpf_defaults();
for order in &orders {
for reg in &order.callee_saved {
assert!(
!order.caller_saved.contains(reg),
"reg {:?} in both callee and caller saved",
reg
);
}
for reg in &order.reserved {
assert!(
!order.order.contains(reg),
"reserved reg {:?} also in order",
reg
);
}
}
}
#[test]
fn test_x86_alloc_order_rsp_reserved() {
let orders = AllocationOrder::x86_64_defaults();
let gpr = orders
.iter()
.find(|o| o.class == CrossRegClass::GPR64)
.unwrap();
assert!(gpr.reserved.iter().any(|r| r.number == 4)); }
#[test]
fn test_machine_ir_cond_all_values() {
let conds = [
MachineIRCond::EQ,
MachineIRCond::NE,
MachineIRCond::LT,
MachineIRCond::LE,
MachineIRCond::GT,
MachineIRCond::GE,
MachineIRCond::LO,
MachineIRCond::LS,
MachineIRCond::HI,
MachineIRCond::HS,
];
assert_eq!(conds.len(), 10);
}
#[test]
fn test_commutative_ops_subset() {
let commutative = [
GenericMachineOpcode::Add,
GenericMachineOpcode::Mul,
GenericMachineOpcode::And,
GenericMachineOpcode::Or,
GenericMachineOpcode::Xor,
];
for op in &commutative {
assert!(op.is_commutative(), "{:?} should be commutative", op);
}
assert!(!GenericMachineOpcode::Sub.is_commutative());
assert!(!GenericMachineOpcode::SDiv.is_commutative());
}
#[test]
fn test_bpf_opcode_classification_coverage() {
let all_ops = BridgeBPFInstrInfo::all_opcodes();
for &op in &all_ops {
let categorized = BridgeBPFInstrInfo::is_branch(op)
|| BridgeBPFInstrInfo::is_terminator(op)
|| BridgeBPFInstrInfo::is_alu32(op)
|| BridgeBPFInstrInfo::is_atomic(op)
|| BridgeBPFInstrInfo::is_endian(op)
|| BridgeBPFInstrInfo::is_memory(op)
|| BridgeBPFInstrInfo::is_unconditional(op);
let mnemonic = BridgeBPFInstrInfo::get_mnemonic(op);
assert!(categorized || !mnemonic.is_empty(),
"opcode {:?} is not categorized", op);
}
}
#[test]
fn test_jit_patterns_for_all_alu_ops() {
let table = BPFJITPatternTable::new();
let alu_ops = [
BpfOpcode::ADD, BpfOpcode::SUB, BpfOpcode::MUL, BpfOpcode::DIV,
BpfOpcode::OR, BpfOpcode::AND, BpfOpcode::LSH, BpfOpcode::RSH,
BpfOpcode::NEG, BpfOpcode::XOR, BpfOpcode::MOV, BpfOpcode::ARSH,
];
for op in &alu_ops {
assert!(table.lookup(*op).is_some(), "missing JIT pattern for ALU op {:?}", op);
}
}
#[test]
fn test_jit_patterns_for_all_jump_ops() {
let table = BPFJITPatternTable::new();
let jmp_ops = [
BpfOpcode::JA, BpfOpcode::JEQ, BpfOpcode::JGT, BpfOpcode::JGE,
BpfOpcode::JLT, BpfOpcode::JLE, BpfOpcode::CALL, BpfOpcode::EXIT,
];
for op in &jmp_ops {
assert!(table.lookup(*op).is_some(), "missing JIT pattern for JMP op {:?}", op);
}
}
#[test]
fn test_jit_patterns_for_all_store_ops() {
let table = BPFJITPatternTable::new();
let st_ops = [BpfOpcode::ST_B, BpfOpcode::ST_H, BpfOpcode::ST_W, BpfOpcode::ST_DW];
for op in &st_ops {
assert!(table.lookup(*op).is_some(), "missing JIT pattern for ST op {:?}", op);
}
}
#[test]
fn test_jit_patterns_for_all_load_ops() {
let table = BPFJITPatternTable::new();
let ld_ops = [
BpfOpcode::LD_DW, BpfOpcode::LDX_B, BpfOpcode::LDX_H,
BpfOpcode::LDX_W, BpfOpcode::LDX_DW,
];
for op in &ld_ops {
assert!(table.lookup(*op).is_some(), "missing JIT pattern for LD op {:?}", op);
}
}
#[test]
fn test_jit_patterns_for_all_atomic_ops() {
let table = BPFJITPatternTable::new();
let atom_ops = [
BpfOpcode::XADD, BpfOpcode::FETCH_ADD, BpfOpcode::FETCH_AND,
BpfOpcode::FETCH_OR, BpfOpcode::FETCH_XOR,
];
for op in &atom_ops {
assert!(table.lookup(*op).is_some(), "missing JIT pattern for atomic op {:?}", op);
}
}
#[test]
fn test_bpf_reg_map_is_bijective() {
let mut x86_seen = HashSet::new();
for bpf_reg in 0..=10u8 {
let x86_reg = BridgeBPFCallingConvention::bpf_reg_to_x86(bpf_reg);
assert!(!x86_seen.contains(&x86_reg),
"BPF reg {} maps to X86 reg {} (already used)", bpf_reg, x86_reg);
x86_seen.insert(x86_reg);
}
assert!(x86_seen.len() <= 11);
}
#[test]
fn test_bridge_pipeline_phases_order() {
let bridge = BPFX86Bridge::default();
assert_eq!(bridge.stats.functions_processed, 0);
assert_eq!(bridge.stats.isel_cycles, 0);
assert_eq!(bridge.stats.ra_cycles, 0);
assert_eq!(bridge.stats.frame_cycles, 0);
}
#[test]
fn test_bridge_opt_level_default() {
let bridge = BPFX86Bridge::default();
let _ = bridge.describe();
}
#[test]
fn test_bridge_debug_info_default() {
let bridge = BPFX86Bridge::default();
assert!(!bridge.debug_info);
}
#[test]
fn test_bridge_enable_debug() {
let mut bridge = BPFX86Bridge::default();
bridge.enable_debug_info();
assert!(bridge.debug_info);
}
#[test]
fn test_bpf_max_instructions_constant() {
assert_eq!(BPF_MAX_INSNS, 1_000_000);
}
#[test]
fn test_bpf_stack_limit_constant() {
assert_eq!(BPF_MAX_STACK, 512);
}
#[test]
fn test_bpf_instr_size_constant() {
assert_eq!(BPF_INSTR_SIZE, 8);
}
#[test]
fn test_bpf_max_offset_constant() {
assert_eq!(BPF_MAX_OFFSET, 0x7FFF);
}
#[test]
fn test_bpf_max_imm_constant() {
assert_eq!(BPF_MAX_IMM, 0x7FFF_FFFF);
}
#[test]
fn test_bpf_abi_names_contains_bpf() {
assert!(BPF_ABI_NAMES.contains(&"bpf"));
}
#[test]
fn test_x86_abi_names_contains_sysv() {
assert!(X86_ABI_NAMES.contains(&"sysv"));
}
#[test]
fn test_data_layouts_are_non_empty() {
assert!(!BPF_DATA_LAYOUT.is_empty());
assert!(!BPFEB_DATA_LAYOUT.is_empty());
assert!(!X86_64_DATA_LAYOUT.is_empty());
}
#[test]
fn test_data_layouts_different_endian() {
let le = BPF_DATA_LAYOUT;
let be = BPFEB_DATA_LAYOUT;
assert!(le.starts_with('e'));
assert!(be.starts_with('E'));
}
#[test]
fn test_bridge_arch_is_little_endian() {
assert!(BridgeArch::Bpf.is_little_endian());
assert!(!BridgeArch::Bpfeb.is_little_endian());
assert!(BridgeArch::X86_64.is_little_endian());
}
#[test]
fn test_cross_reg_class_bpf_no_fpr() {
assert_eq!(CrossRegClass::FPR128.reg_count(BridgeArch::Bpf), 0);
assert_eq!(CrossRegClass::FPR64.reg_count(BridgeArch::Bpf), 0);
}
#[test]
fn test_cross_reg_class_x86_has_xmm() {
assert!(CrossRegClass::FPR128.reg_count(BridgeArch::X86_64) > 0);
}
#[test]
fn test_cross_reg_class_mapping_to_bpf() {
assert_eq!(CrossRegClass::GPR64.to_bpf_reg_class(), Some(BpfRegClass::GPR));
assert_eq!(CrossRegClass::GPR32.to_bpf_reg_class(), Some(BpfRegClass::GPR));
}
#[test]
fn test_spill_slot_creation() {
let slot = SpillSlot {
index: 0,
size: 8,
alignment: 8,
vreg: 42,
};
assert_eq!(slot.index, 0);
assert_eq!(slot.size, 8);
assert_eq!(slot.alignment, 8);
assert_eq!(slot.vreg, 42);
}
#[test]
fn test_legalize_rule_creation() {
let rule = LegalizeRule {
name: "test".into(),
from_type: TypeKindRepr::Integer(1),
to_type: TypeKindRepr::Integer(8),
action: LegalizeAction::Promote,
};
assert_eq!(rule.name, "test");
assert_eq!(rule.action, LegalizeAction::Promote);
}
#[test]
fn test_legalize_action_all_variants() {
let actions = [
LegalizeAction::Promote, LegalizeAction::Split, LegalizeAction::Widen,
LegalizeAction::Narrow, LegalizeAction::Expand, LegalizeAction::SoftenFloat,
LegalizeAction::Scalarize,
];
assert_eq!(actions.len(), 7);
}
#[test]
fn test_type_kind_repr_variants() {
assert!(TypeKindRepr::Void.size_bits() == 0);
assert!(TypeKindRepr::Integer(64).size_bits() == 64);
assert!(TypeKindRepr::Pointer.size_bits() == 64);
}
#[test]
fn test_type_kind_repr_vector() {
let v = TypeKindRepr::Vector(8, Box::new(TypeKindRepr::Integer(16)));
assert_eq!(v.size_bits(), 128);
}
#[test]
fn test_type_kind_repr_struct() {
let s = TypeKindRepr::Struct(vec![
TypeKindRepr::Integer(64),
TypeKindRepr::Integer(64),
]);
assert_eq!(s.size_bits(), 128);
}
#[test]
fn test_machine_ir_inst_with_flags() {
let flags = MachineIRFlags {
may_load: true,
is_terminator: false,
..Default::default()
};
let inst = MachineIRInst::new(GenericMachineOpcode::Load).with_flags(flags);
assert!(inst.flags.may_load);
assert!(!inst.flags.is_terminator);
}
#[test]
fn test_pattern_node_creation() {
let node = PatternNode::Sequence(vec![
PatternNode::Opcode(GenericMachineOpcode::Add),
PatternNode::Capture { slot: 0, node: Box::new(PatternNode::Any) },
PatternNode::Immediate { min: 0, max: 100 },
]);
match node {
PatternNode::Sequence(v) => assert_eq!(v.len(), 3),
_ => panic!("expected Sequence"),
}
}
#[test]
fn test_dag_pattern_creation() {
let pattern = DAGPattern {
name: "test_pattern".into(),
pattern: PatternNode::Any,
result: PatternResult {
opcode: GenericMachineOpcode::Add,
operand_mapping: vec![0, 1],
flags: None,
},
cost: 5,
archs: vec![BridgeArch::Bpf],
};
assert_eq!(pattern.name, "test_pattern");
assert_eq!(pattern.cost, 5);
assert_eq!(pattern.archs.len(), 1);
}
#[test]
fn test_peephole_pattern_match_empty() {
let pp = PeepholePattern {
name: "nop_removal".into(),
match_seq: vec![GenericMachineOpcode::Nop],
replace_seq: vec![],
};
assert_eq!(pp.match_seq.len(), 1);
assert!(pp.replace_seq.is_empty());
}
#[test]
fn test_live_interval_priority_computation() {
let mut interval = LiveInterval::new(0, CrossRegClass::GPR64);
interval.add_range(0, 10);
interval.add_range(20, 30);
interval.compute_priority();
assert!(interval.priority > 0.0);
}
#[test]
fn test_bridge_error_clone() {
let err = BridgeError::VerifierRejection { reason: "test".into() };
let cloned = err.clone();
assert_eq!(err, cloned);
}
#[test]
fn test_bridge_error_partial_eq() {
let a = BridgeError::Internal("x".into());
let b = BridgeError::Internal("x".into());
let c = BridgeError::Internal("y".into());
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_bridge_output_clone() {
let out = BridgeOutput {
instructions_emitted: 42,
basic_blocks: 7,
target_arch: BridgeArch::Bpf,
};
let cloned = out.clone();
assert_eq!(cloned.instructions_emitted, 42);
}
#[test]
fn test_cost_estimate_clone_copy() {
let cost = CostEstimate::new(3, 2, 8);
let copy = cost;
assert_eq!(copy.latency, 3);
}
#[test]
fn test_bpf_target_machine_integration() {
let mut bridge = BPFX86Bridge::default();
bridge.set_bpf_cpu("v1");
assert_eq!(bridge.bpf_tm.get_cpu(), "v1");
bridge.set_bpf_cpu("v2");
assert_eq!(bridge.bpf_tm.get_cpu(), "v2");
}
#[test]
fn test_jit_pattern_name_unique() {
let table = BPFJITPatternTable::new();
let mut names = HashSet::new();
for pattern in table.patterns.values() {
assert!(!names.contains(&pattern.name), "duplicate pattern name: {}", pattern.name);
names.insert(pattern.name.clone());
}
}
#[test]
fn test_alloc_order_for_bpf_has_gpr64() {
let orders = AllocationOrder::for_arch(BridgeArch::Bpf);
assert!(orders.iter().any(|o| o.class == CrossRegClass::GPR64));
}
#[test]
fn test_alloc_order_for_x86_has_gpr64_and_fpr() {
let orders = AllocationOrder::for_arch(BridgeArch::X86_64);
assert!(orders.iter().any(|o| o.class == CrossRegClass::GPR64));
assert!(orders.iter().any(|o| o.class == CrossRegClass::FPR128));
}
#[test]
fn test_bpf_frame_align_to_8() {
assert_eq!(CrossStackFrame::align_frame_size(0, 8), 0);
assert_eq!(CrossStackFrame::align_frame_size(1, 8), 8);
assert_eq!(CrossStackFrame::align_frame_size(7, 8), 8);
assert_eq!(CrossStackFrame::align_frame_size(8, 8), 8);
assert_eq!(CrossStackFrame::align_frame_size(9, 8), 16);
}
#[test]
fn test_x86_frame_align_to_16() {
assert_eq!(CrossStackFrame::align_frame_size(0, 16), 0);
assert_eq!(CrossStackFrame::align_frame_size(1, 16), 16);
assert_eq!(CrossStackFrame::align_frame_size(15, 16), 16);
assert_eq!(CrossStackFrame::align_frame_size(16, 16), 16);
assert_eq!(CrossStackFrame::align_frame_size(17, 16), 32);
}
#[test]
fn test_bpf_frame_properties() {
let frame = CrossStackFrame::new(BridgeArch::Bpf);
assert_eq!(frame.stack_alignment, 8);
assert_eq!(frame.red_zone_size, 0);
assert!(frame.has_frame_pointer);
}
#[test]
fn test_x86_frame_properties() {
let frame = CrossStackFrame::new(BridgeArch::X86_64);
assert_eq!(frame.stack_alignment, 16);
assert_eq!(frame.red_zone_size, 128);
}
#[test]
fn test_frame_lowering_bpf_leaf() {
let fl = CrossTargetFrameLowering::new(BridgeArch::Bpf);
assert!(!fl.needs_frame_lowering());
}
#[test]
fn test_abi_bpf_and_x86_are_different() {
let bpf = CrossTargetABI::new(BridgeArch::Bpf);
let x86 = CrossTargetABI::new(BridgeArch::X86_64);
assert_eq!(bpf.arch, BridgeArch::Bpf);
assert_eq!(x86.arch, BridgeArch::X86_64);
}
#[test]
fn test_bridge_parse_arch_bpf_triples() {
let b1 = BPFX86Bridge::from_triples("bpf", "x86_64");
assert!(b1.source_arch.is_bpf_family());
let b2 = BPFX86Bridge::from_triples("bpfel", "x86_64");
assert!(b2.source_arch.is_bpf_family());
let b3 = BPFX86Bridge::from_triples("bpfeb", "x86_64");
assert!(b3.source_arch.is_bpf_family());
}
#[test]
fn test_arch_is_little_endian_bpf_variants() {
assert!(BridgeArch::Bpf.is_little_endian());
assert!(!BridgeArch::Bpfeb.is_little_endian());
}
#[test]
fn test_bridge_from_triples_x86_to_bpf() {
let bridge = BPFX86Bridge::from_triples("x86_64", "bpf");
assert!(bridge.source_arch.is_x86_family());
assert!(bridge.target_arch.is_bpf_family());
}
#[test]
fn test_mir_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,
];
assert_eq!(conds.len(), 10);
}
#[test]
fn test_mir_flags_terminator() {
let flags = MachineIRFlags {
is_terminator: true,
..Default::default()
};
assert!(flags.is_terminator);
assert!(!flags.is_branch);
}
#[test]
fn test_generic_opcode_bpf_specific() {
assert!(GenericMachineOpcode::BpfLddw as u32 != GenericMachineOpcode::BpfAtomicAdd as u32);
assert!(GenericMachineOpcode::BpfEndian as u32 != GenericMachineOpcode::BpfLddw as u32);
}
#[test]
fn test_pattern_predicate_all_variants() {
let preds = [
PatternPredicate::IsPowerOfTwo,
PatternPredicate::FitsInBits(12),
PatternPredicate::FitsIn32Bits,
];
assert_eq!(preds.len(), 3);
}
#[test]
fn test_mir_operand_global() {
let op = MachineIROperand::Global("my_var".into());
assert!(!op.is_reg());
assert!(!op.is_imm());
}
#[test]
fn test_mir_operand_external() {
let op = MachineIROperand::External("printf".into());
assert!(!op.is_reg());
assert!(!op.is_imm());
}
#[test]
fn test_mir_operand_frame_index() {
let op = MachineIROperand::FrameIndex(-8);
assert!(!op.is_reg());
assert!(!op.is_imm());
}
#[test]
fn test_isel_legalize_get_type_noop() {
let isel = CrossTargetISel::new(BridgeArch::Bpf);
let i64 = TypeKindRepr::Integer(64);
let legal = isel.get_legalized_type(&i64);
assert_eq!(legal, TypeKindRepr::Integer(64));
}
#[test]
fn test_features_enable_x86_avx512() {
let mut features = BridgeFeatures::default();
features.enable_x86_avx512();
assert!(features.has(BridgeFeature::AVX512));
assert!(features.has(BridgeFeature::AVX2));
assert!(features.has(BridgeFeature::FMA));
}
#[test]
fn test_features_disable_then_enable() {
let mut features = BridgeFeatures::default();
features.enable(BridgeFeature::SSE);
features.disable(BridgeFeature::SSE);
assert!(!features.has(BridgeFeature::SSE));
features.enable(BridgeFeature::SSE);
assert!(features.has(BridgeFeature::SSE));
}
#[test]
fn test_features_from_string_multiple_commas() {
let features = BridgeFeatures::from_string("+alu32,,+sse,,");
assert!(features.has(BridgeFeature::ALU32));
assert!(features.has(BridgeFeature::SSE));
}
#[test]
fn test_bpf_reg_constants() {
assert_eq!(bpf_regs::R0, 0);
assert_eq!(bpf_regs::R10, 10);
assert_eq!(bpf_regs::R11_FP, 10);
}
#[test]
fn test_bpf_class_constants_all_unique() {
let classes = [
bpf_class::LD, bpf_class::LDX, bpf_class::ST, bpf_class::STX,
bpf_class::ALU, bpf_class::JMP, bpf_class::JMP32, bpf_class::ALU64,
];
let mut seen = HashSet::new();
for &c in &classes {
assert!(!seen.contains(&c));
seen.insert(c);
}
}
#[test]
fn test_bpf_alu_op_constants_all_unique() {
let ops = [
bpf_alu_op::ADD, bpf_alu_op::SUB, bpf_alu_op::MUL, bpf_alu_op::DIV,
bpf_alu_op::OR, bpf_alu_op::AND, bpf_alu_op::LSH, bpf_alu_op::RSH,
bpf_alu_op::NEG, bpf_alu_op::MOD, bpf_alu_op::XOR, bpf_alu_op::MOV,
bpf_alu_op::ARSH, bpf_alu_op::END,
];
let mut seen = HashSet::new();
for &op in &ops {
assert!(!seen.contains(&op));
seen.insert(op);
}
}
#[test]
fn test_bpf_jmp_op_constants_all_unique() {
let ops = [
bpf_jmp_op::JA, bpf_jmp_op::JEQ, bpf_jmp_op::JGT, bpf_jmp_op::JGE,
bpf_jmp_op::JSET, bpf_jmp_op::JNE, bpf_jmp_op::JSGT, bpf_jmp_op::JSGE,
bpf_jmp_op::CALL, bpf_jmp_op::EXIT, bpf_jmp_op::JLT, bpf_jmp_op::JLE,
bpf_jmp_op::JSLT, bpf_jmp_op::JSLE,
];
let mut seen = HashSet::new();
for &op in &ops {
assert!(!seen.contains(&op));
seen.insert(op);
}
}
#[test]
fn test_bridge_all_features_listed() {
let feats = [
BridgeFeature::ALU32, BridgeFeature::DWARFRIS, BridgeFeature::NoSpeculationBarrier,
BridgeFeature::SSE, BridgeFeature::SSE2, BridgeFeature::AVX, BridgeFeature::AVX2,
BridgeFeature::AVX512, BridgeFeature::Atomics, BridgeFeature::EndianOps,
BridgeFeature::SignedDivMod, BridgeFeature::TailCall, BridgeFeature::CO_RE,
BridgeFeature::BitCount, BridgeFeature::FMA, BridgeFeature::PGO,
];
assert_eq!(feats.len(), 16);
}
#[test]
fn test_cost_model_all_entries_valid() {
let model = BPFX86CostModel::new(BridgeArch::Bpf, BridgeArch::X86_64);
for (_, &(src, tgt)) in &model.cost_table {
assert!(src > 0);
assert!(tgt > 0);
}
}
}