use crate::lanai::lanai_instr_info::{
LanaiInstrDesc, LanaiInstrInfo, LanaiOpcode, LanaiOperandType,
};
use crate::lanai::lanai_register_info::{
LanaiRegClass, LanaiRegisterInfo, GPR_BASE, GPR_COUNT, GPR_LAST, IR, LANAI_MAX_REG_ID, MCR, PC,
PSW, R0, R1, R10, R11, R12, R13, R14, R15, R16, R17, R18, R19, R2, R20, R21, R22, R23, R24,
R25, R26, R27, R28, R29, R3, R30, R31, R4, R5, R6, R7, R8, R9, RCA, RPC, RR1, RRA, SP,
};
use crate::lanai::{LANAI_ENDIANNESS, LANAI_INSTR_SIZE, LANAI_STACK_ALIGNMENT};
use crate::target_machine::{CodeGenOptLevel, CodeModel, RelocModel, TargetOptions};
use std::collections::HashMap;
use std::fmt;
pub const X86_GPR_COUNT: usize = 16;
pub const X86_XMM_COUNT: usize = 16;
pub const X86_RED_ZONE_SIZE: u32 = 128;
pub const X86_STACK_ALIGN: u32 = 16;
pub const X86_PAGE_SIZE: u32 = 4096;
pub const X86_AVG_INSTR_SIZE: u32 = 4;
pub const X86_MAX_INSTR_SIZE: u32 = 15;
pub const LANAI_REG_COUNT: usize = 32;
pub const LANAI_SPECIAL_COUNT: usize = 4;
pub fn lanai_gpr_to_x86(lanai_reg: u16) -> Option<&'static str> {
match lanai_reg {
12000 => Some("rax"), 12001 => Some("rdi"), 12002 => Some("rsi"), 12003 => Some("rdx"), 12004 => Some("rcx"), 12005 => Some("r8"), 12006 => Some("r9"), 12007 => Some("r10"), 12008 => Some("r11"), 12009 => Some("rax"), 12010 => Some("rbx"), 12011 => Some("r12"), 12012 => Some("r13"), 12013 => Some("r14"), 12014 => Some("r15"), 12015 => Some("rdi"), 12016 => Some("rsi"), 12017 => Some("rdx"), 12018 => Some("rcx"), 12019 => Some("r8"), 12020 => Some("r9"), 12021 => Some("r10"), 12022 => Some("r11"), 12023 => Some("rbx"), 12024 => Some("r12"), 12025 => Some("r13"), 12026 => Some("rax"), 12027 => Some("rdx"), 12028 => Some("r14"), 12029 => Some("r15"), 12030 => Some("rsp"), 12031 => Some("rbp"), _ => None,
}
}
pub fn x86_gpr_to_lanai(x86_reg: &str) -> Option<u16> {
match x86_reg {
"rax" | "eax" => Some(12026), "rbx" | "ebx" => Some(12010), "rcx" | "ecx" => Some(12004), "rdx" | "edx" => Some(12003), "rsi" | "esi" => Some(12002), "rdi" | "edi" => Some(12001), "rsp" | "esp" => Some(12030), "rbp" | "ebp" => Some(12031), "r8" | "r8d" => Some(12005), "r9" | "r9d" => Some(12006), "r10" | "r10d" => Some(12007), "r11" | "r11d" => Some(12008), "r12" | "r12d" => Some(12011), "r13" | "r13d" => Some(12012), "r14" | "r14d" => Some(12013), "r15" | "r15d" => Some(12014), _ => None,
}
}
pub fn lanai_special_to_x86_desc(sreg: u16) -> Option<&'static str> {
match sreg {
12100 => Some("implicit (PC → RIP)"),
12101 => Some("implicit (PSW → EFLAGS)"),
12102 => Some("no equivalent (MCR)"),
12103 => Some("no equivalent (IR)"),
_ => None,
}
}
#[derive(Clone)]
pub struct LanaiX86Bridge {
pub triple: String,
pub is_lanai_primary: bool,
pub is_64bit: bool,
pub lanai_abi: LanaiAbi,
pub x86_cc: X86CallingConventionVariant,
pub lanai_instr_info: LanaiInstrInfo,
pub lanai_reg_info: LanaiRegisterInfo,
pub reg_map_lanai_to_x86: HashMap<u16, String>,
pub reg_map_x86_to_lanai: HashMap<String, u16>,
pub vreg_allocations: HashMap<u32, AllocatedReg>,
pub spill_slots: Vec<SpillSlot>,
pub cost_weights: CostWeights,
pub cost_data: Vec<InstructionCostEntry>,
pub has_sse42: bool,
pub has_avx2: bool,
pub pattern_cache: HashMap<u32, SharedPattern>,
pub frame_info: BridgeFrameInfo,
pub call_conv: BridgeCallingConvention,
pub opt_level: CodeGenOptLevel,
pub code_model: CodeModel,
pub reloc_model: RelocModel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LanaiAbi {
Standard,
BareMetal,
}
impl Default for LanaiAbi {
fn default() -> Self {
LanaiAbi::Standard
}
}
impl fmt::Display for LanaiAbi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LanaiAbi::Standard => write!(f, "standard"),
LanaiAbi::BareMetal => write!(f, "baremetal"),
}
}
}
impl LanaiAbi {
pub fn num_int_arg_regs(&self) -> usize {
4
}
pub fn num_int_ret_regs(&self) -> usize {
2
}
pub fn stack_alignment(&self) -> u32 {
LANAI_STACK_ALIGNMENT
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CallingConventionVariant {
SysV64,
Win64,
CDecl32,
StdCall32,
FastCall32,
}
impl Default for X86CallingConventionVariant {
fn default() -> Self {
Self::SysV64
}
}
impl fmt::Display for X86CallingConventionVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SysV64 => write!(f, "sysv64"),
Self::Win64 => write!(f, "win64"),
Self::CDecl32 => write!(f, "cdecl32"),
Self::StdCall32 => write!(f, "stdcall32"),
Self::FastCall32 => write!(f, "fastcall32"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AllocatedReg {
pub vreg: u32,
pub lanai_reg: u16,
pub x86_reg: String,
pub is_spill: bool,
pub spill_slot: Option<usize>,
pub reg_class: RegClassKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegClassKind {
GPR32,
GPR64,
SpecialReg,
XMM,
YMM,
Flag,
}
impl RegClassKind {
pub fn size_bytes(&self) -> usize {
match self {
RegClassKind::GPR32 => 4,
RegClassKind::GPR64 => 8,
RegClassKind::SpecialReg => 4,
RegClassKind::XMM => 16,
RegClassKind::YMM => 32,
RegClassKind::Flag => 0,
}
}
pub fn is_integer(&self) -> bool {
matches!(self, RegClassKind::GPR32 | RegClassKind::GPR64)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpillSlot {
pub offset: i32,
pub size: u32,
pub alignment: u32,
pub is_callee_saved: bool,
pub lanai_reg: Option<u16>,
pub x86_reg: Option<String>,
pub reg_class: Option<RegClassKind>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CostWeights {
pub size_weight: f64,
pub latency_weight: f64,
pub throughput_weight: f64,
pub reg_pressure_weight: f64,
pub power_weight: f64,
pub target_bias: f64,
}
impl Default for CostWeights {
fn default() -> Self {
CostWeights {
size_weight: 0.25,
latency_weight: 0.35,
throughput_weight: 0.25,
reg_pressure_weight: 0.10,
power_weight: 0.05,
target_bias: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct InstructionCostEntry {
pub ir_opcode: u32,
pub lanai_mnemonic: String,
pub x86_mnemonic: String,
pub lanai_cost: TargetInstructionCost,
pub x86_cost: TargetInstructionCost,
pub winner: CostWinner,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct TargetInstructionCost {
pub latency: u32,
pub rthroughput: f64,
pub size: u32,
pub uops: u32,
pub gpr_used: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CostWinner {
Lanai,
X86,
Tie,
LanaiUnsupported,
X86Unsupported,
}
impl fmt::Display for CostWinner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Lanai => write!(f, "Lanai"),
Self::X86 => write!(f, "X86"),
Self::Tie => write!(f, "Tie"),
Self::LanaiUnsupported => write!(f, "LanaiUnsupported"),
Self::X86Unsupported => write!(f, "X86Unsupported"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BridgeFrameInfo {
pub frame_size: u32,
pub num_callee_saved_gprs: u32,
pub local_area_offset: i32,
pub spill_area_offset: i32,
pub has_frame_pointer: bool,
pub is_leaf: bool,
pub has_variable_alloca: bool,
pub has_struct_ret: bool,
pub outgoing_args_size: u32,
pub max_call_frame_size: u32,
pub frame_alignment: u32,
pub use_sa_instruction: bool,
}
impl BridgeFrameInfo {
pub fn new() -> Self {
BridgeFrameInfo {
frame_size: 0,
num_callee_saved_gprs: 0,
local_area_offset: 0,
spill_area_offset: 0,
has_frame_pointer: true,
is_leaf: true,
has_variable_alloca: false,
has_struct_ret: false,
outgoing_args_size: 0,
max_call_frame_size: 0,
frame_alignment: LANAI_STACK_ALIGNMENT,
use_sa_instruction: true,
}
}
pub fn compute_frame_layout(&mut self) -> u32 {
let mut o: u32 = self.max_call_frame_size;
self.outgoing_args_size = self.max_call_frame_size;
o += self.num_callee_saved_gprs * 4;
self.spill_area_offset = o as i32;
self.local_area_offset = o as i32;
let a = align_up(o, self.frame_alignment);
self.frame_size = a;
a
}
pub fn emit_lanai_prologue(&self) -> String {
let mut c = String::new();
if self.use_sa_instruction {
c.push_str(&format!("\tsa {}, {}\n", self.frame_size, -1));
} else {
c.push_str(&format!("\tsub {}, r30, r30\n", self.frame_size));
}
if self.has_frame_pointer {
c.push_str("\tadd r30, r30, r0\n");
}
if self.num_callee_saved_gprs > 0 {
c.push_str("\t// Callee-saved GPRs saved\n");
}
c
}
pub fn emit_lanai_epilogue(&self) -> String {
let mut c = String::new();
if self.num_callee_saved_gprs > 0 {
c.push_str("\t// Restore callee-saved GPRs\n");
}
if !self.use_sa_instruction {
c.push_str(&format!("\tadd r30, r30, {}\n", self.frame_size));
}
c.push_str("\tbt rca\n");
c
}
pub fn emit_x86_prologue(&self) -> String {
let mut c = String::new();
c.push_str("\tpushq\t%rbp\n\tmovq\t%rsp, %rbp\n");
if self.frame_size > 0 {
c.push_str(&format!(
"\tsubq\t${}, %rsp\n",
align_up(self.frame_size, 16)
));
}
c
}
pub fn emit_x86_epilogue(&self) -> String {
"\tmovq\t%rbp, %rsp\n\tpopq\t%rbp\n\tretq\n".to_string()
}
}
fn align_up(v: u32, a: u32) -> u32 {
if a == 0 {
v
} else {
(v + a - 1) & !(a - 1)
}
}
#[derive(Debug, Clone)]
pub struct BridgeCallingConvention {
pub lanai_abi: LanaiAbi,
pub x86_cc: X86CallingConventionVariant,
pub lanai_int_arg_regs: Vec<u16>,
pub lanai_int_ret_regs: Vec<u16>,
pub x86_int_arg_regs: Vec<String>,
pub x86_int_ret_reg: String,
pub lanai_callee_saved_gprs: Vec<u16>,
pub x86_callee_saved_gprs: Vec<String>,
pub arg_allocations: Vec<ArgAllocation>,
}
impl BridgeCallingConvention {
pub fn new(lanai_abi: LanaiAbi, x86_cc: X86CallingConventionVariant) -> Self {
let l_args = vec![R0, R1, R2, R3];
let l_rets = vec![RR1, R27];
let x_args: Vec<String> = vec![
"rdi".into(),
"rsi".into(),
"rdx".into(),
"rcx".into(),
"r8".into(),
"r9".into(),
];
let l_cs: Vec<u16> = (10..26).map(|i| GPR_BASE + i).collect();
let x_cs = vec![
"rbx".into(),
"rbp".into(),
"r12".into(),
"r13".into(),
"r14".into(),
"r15".into(),
];
Self {
lanai_abi,
x86_cc,
lanai_int_arg_regs: l_args,
lanai_int_ret_regs: l_rets,
x86_int_arg_regs: x_args,
x86_int_ret_reg: "rax".into(),
lanai_callee_saved_gprs: l_cs,
x86_callee_saved_gprs: x_cs,
arg_allocations: vec![],
}
}
pub fn classify_lanai_type(&self, size_bytes: u32) -> ArgClass {
if size_bytes <= 4 {
ArgClass::Integer
} else {
ArgClass::Memory
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgClass {
Integer,
IntegerPair,
Memory,
NoClass,
}
#[derive(Debug, Clone)]
pub struct ArgAllocation {
pub arg_index: u32,
pub lanai_reg: Option<u16>,
pub x86_reg: Option<String>,
pub stack_offset: Option<i32>,
pub arg_class: ArgClass,
pub size: u32,
}
#[derive(Debug, Clone)]
pub struct SharedPattern {
pub opcode: u32,
pub lanai_sequence: Vec<PatternInstruction>,
pub x86_sequence: Vec<PatternInstruction>,
pub is_identical: bool,
pub lanai_cost: u32,
pub x86_cost: u32,
pub can_be_special: bool,
}
#[derive(Debug, Clone)]
pub struct PatternInstruction {
pub mnemonic: String,
pub operands: Vec<PatternOperand>,
pub has_def: bool,
pub is_terminator: bool,
pub is_branch: bool,
}
#[derive(Debug, Clone)]
pub struct PatternOperand {
pub kind: PatternOperandKind,
pub vreg: Option<u32>,
pub immediate: Option<i64>,
pub reg_name: Option<String>,
pub reg_class: Option<RegClassKind>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PatternOperandKind {
Def,
Use,
Immediate,
Memory,
BranchTarget,
ConditionCode,
SpecialReg,
}
#[derive(Debug, Clone)]
pub struct CrossTargetIRInst {
pub opcode: u32,
pub dest: Option<u32>,
pub srcs: Vec<u32>,
pub immediate: Option<i64>,
pub type_code: u8,
pub is_terminator: bool,
pub mem_addr: Option<u32>,
pub branch_target: Option<String>,
pub condition: Option<CrossTargetCond>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrossTargetCond {
EQ,
NE,
LT,
LE,
GT,
GE,
LTU,
LEU,
GTU,
GEU,
Always,
Never,
}
#[derive(Debug, Clone)]
pub struct CrossTargetMBB {
pub label: String,
pub instructions: Vec<CrossTargetIRInst>,
pub successors: Vec<String>,
pub predecessors: Vec<String>,
pub is_entry: bool,
pub has_fallthrough: bool,
}
#[derive(Debug, Clone)]
pub struct CrossTargetMF {
pub name: String,
pub blocks: Vec<CrossTargetMBB>,
pub num_vregs: u32,
pub frame_size: u32,
pub is_varargs: bool,
}
impl LanaiX86Bridge {
pub fn new(triple: &str) -> Self {
let mut bridge = Self {
triple: triple.into(),
is_lanai_primary: triple.contains("lanai"),
is_64bit: false,
lanai_abi: LanaiAbi::default(),
x86_cc: X86CallingConventionVariant::SysV64,
lanai_instr_info: LanaiInstrInfo::new(),
lanai_reg_info: LanaiRegisterInfo,
reg_map_lanai_to_x86: HashMap::new(),
reg_map_x86_to_lanai: HashMap::new(),
vreg_allocations: HashMap::new(),
spill_slots: vec![],
cost_weights: CostWeights::default(),
cost_data: vec![],
has_sse42: true,
has_avx2: false,
pattern_cache: HashMap::new(),
frame_info: BridgeFrameInfo::new(),
call_conv: BridgeCallingConvention::new(
LanaiAbi::default(),
X86CallingConventionVariant::SysV64,
),
opt_level: CodeGenOptLevel::Default,
code_model: CodeModel::Small,
reloc_model: RelocModel::Static,
};
bridge.build_reg_maps();
bridge.init_patterns();
bridge
}
fn build_reg_maps(&mut self) {
for reg in GPR_BASE..=GPR_LAST {
if let Some(n) = lanai_gpr_to_x86(reg) {
self.reg_map_lanai_to_x86.insert(reg, n.to_string());
self.reg_map_x86_to_lanai.insert(n.to_string(), reg);
}
}
}
fn init_patterns(&mut self) {
self.add_alu();
self.add_mem();
self.add_branch();
self.add_special();
self.add_pseudo();
}
fn mk_reg_op(k: PatternOperandKind, v: u32) -> PatternOperand {
PatternOperand {
kind: k,
vreg: Some(v),
immediate: None,
reg_name: None,
reg_class: Some(RegClassKind::GPR32),
}
}
fn mk_alu(&mut self, op: u32, l: &str, x: &str, cost: u32, spec: bool) {
self.pattern_cache.insert(
op,
SharedPattern {
opcode: op,
lanai_sequence: vec![PatternInstruction {
mnemonic: l.into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
self.mk_reg_op(PatternOperandKind::Use, 2),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: x.into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Use, 2),
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: cost,
x86_cost: cost,
can_be_special: spec,
},
);
}
fn add_alu(&mut self) {
for (op, l, x) in &[
(LanaiOpcode::Add as u32, "add", "addl"),
(LanaiOpcode::Sub as u32, "sub", "subl"),
(LanaiOpcode::And as u32, "and", "andl"),
(LanaiOpcode::Or as u32, "or", "orl"),
(LanaiOpcode::Xor as u32, "xor", "xorl"),
] {
self.mk_alu(*op, l, x, 1, false);
}
self.pattern_cache.insert(
LanaiOpcode::Sh as u32,
SharedPattern {
opcode: LanaiOpcode::Sh as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "sh".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
PatternOperand {
kind: PatternOperandKind::Immediate,
vreg: None,
immediate: Some(0),
reg_name: None,
reg_class: None,
},
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "shll".into(),
operands: vec![
PatternOperand {
kind: PatternOperandKind::Immediate,
vreg: None,
immediate: Some(0),
reg_name: None,
reg_class: None,
},
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
self.pattern_cache.insert(
LanaiOpcode::Sel as u32,
SharedPattern {
opcode: LanaiOpcode::Sel as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "sel".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
self.mk_reg_op(PatternOperandKind::Use, 2),
PatternOperand {
kind: PatternOperandKind::ConditionCode,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
},
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "cmovnel".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Use, 2),
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 2,
can_be_special: false,
},
);
}
fn add_mem(&mut self) {
for (op, l, x) in &[
(LanaiOpcode::Ld as u32, "ld", "movl"),
(LanaiOpcode::Ldh as u32, "ldh", "movswl"),
(LanaiOpcode::Ldb as u32, "ldb", "movsbl"),
] {
self.pattern_cache.insert(
*op,
SharedPattern {
opcode: *op,
lanai_sequence: vec![PatternInstruction {
mnemonic: l.to_string(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
PatternOperand {
kind: PatternOperandKind::Memory,
vreg: Some(1),
immediate: Some(0),
reg_name: None,
reg_class: Some(RegClassKind::GPR32),
},
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: x.to_string(),
operands: vec![
PatternOperand {
kind: PatternOperandKind::Memory,
vreg: Some(1),
immediate: None,
reg_name: None,
reg_class: Some(RegClassKind::GPR32),
},
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
}
self.pattern_cache.insert(
LanaiOpcode::St as u32,
SharedPattern {
opcode: LanaiOpcode::St as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "st".into(),
operands: vec![
PatternOperand {
kind: PatternOperandKind::Memory,
vreg: Some(1),
immediate: Some(0),
reg_name: None,
reg_class: Some(RegClassKind::GPR32),
},
PatternOperand {
kind: PatternOperandKind::Use,
vreg: Some(0),
immediate: None,
reg_name: None,
reg_class: Some(RegClassKind::GPR32),
},
],
has_def: false,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "movl".into(),
operands: vec![
PatternOperand {
kind: PatternOperandKind::Use,
vreg: Some(0),
immediate: None,
reg_name: None,
reg_class: Some(RegClassKind::GPR32),
},
PatternOperand {
kind: PatternOperandKind::Memory,
vreg: Some(1),
immediate: None,
reg_name: None,
reg_class: Some(RegClassKind::GPR32),
},
],
has_def: false,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
}
fn add_branch(&mut self) {
self.pattern_cache.insert(
LanaiOpcode::Br as u32,
SharedPattern {
opcode: LanaiOpcode::Br as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "br".into(),
operands: vec![PatternOperand {
kind: PatternOperandKind::BranchTarget,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
}],
has_def: false,
is_terminator: true,
is_branch: true,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "jmp".into(),
operands: vec![PatternOperand {
kind: PatternOperandKind::BranchTarget,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
}],
has_def: false,
is_terminator: true,
is_branch: true,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
self.pattern_cache.insert(
LanaiOpcode::Brcc as u32,
SharedPattern {
opcode: LanaiOpcode::Brcc as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "brcc".into(),
operands: vec![
PatternOperand {
kind: PatternOperandKind::ConditionCode,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
},
PatternOperand {
kind: PatternOperandKind::BranchTarget,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
},
],
has_def: false,
is_terminator: true,
is_branch: true,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "jg".into(),
operands: vec![PatternOperand {
kind: PatternOperandKind::BranchTarget,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
}],
has_def: false,
is_terminator: true,
is_branch: true,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
self.pattern_cache.insert(
LanaiOpcode::Bt as u32,
SharedPattern {
opcode: LanaiOpcode::Bt as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "bt".into(),
operands: vec![PatternOperand {
kind: PatternOperandKind::Use,
vreg: None,
immediate: None,
reg_name: Some("rca".into()),
reg_class: Some(RegClassKind::GPR32),
}],
has_def: false,
is_terminator: true,
is_branch: true,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "ret".into(),
operands: vec![],
has_def: false,
is_terminator: true,
is_branch: true,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
self.pattern_cache.insert(
LanaiOpcode::Sa as u32,
SharedPattern {
opcode: LanaiOpcode::Sa as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "sa".into(),
operands: vec![
PatternOperand {
kind: PatternOperandKind::Immediate,
vreg: None,
immediate: Some(0),
reg_name: None,
reg_class: None,
},
PatternOperand {
kind: PatternOperandKind::BranchTarget,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
},
],
has_def: false,
is_terminator: false,
is_branch: true,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "call".into(),
operands: vec![PatternOperand {
kind: PatternOperandKind::BranchTarget,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
}],
has_def: false,
is_terminator: false,
is_branch: true,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
}
fn add_special(&mut self) {
self.pattern_cache.insert(
LanaiOpcode::Popc as u32,
SharedPattern {
opcode: LanaiOpcode::Popc as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "popc".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "popcntl".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Use, 1),
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 3,
can_be_special: true,
},
);
self.pattern_cache.insert(
LanaiOpcode::Leadz as u32,
SharedPattern {
opcode: LanaiOpcode::Leadz as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "leadz".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "lzcntl".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Use, 1),
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 3,
can_be_special: true,
},
);
self.pattern_cache.insert(
LanaiOpcode::Trailz as u32,
SharedPattern {
opcode: LanaiOpcode::Trailz as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "trailz".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "tzcntl".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Use, 1),
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 3,
can_be_special: true,
},
);
self.pattern_cache.insert(
LanaiOpcode::Addc as u32,
SharedPattern {
opcode: LanaiOpcode::Addc as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "addc".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
self.mk_reg_op(PatternOperandKind::Use, 2),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "adcl".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Use, 2),
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: true,
},
);
self.pattern_cache.insert(
LanaiOpcode::Subb as u32,
SharedPattern {
opcode: LanaiOpcode::Subb as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "subb".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
self.mk_reg_op(PatternOperandKind::Use, 2),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "sbbl".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Use, 2),
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: true,
},
);
}
fn add_pseudo(&mut self) {
self.pattern_cache.insert(
LanaiOpcode::Mov as u32,
SharedPattern {
opcode: LanaiOpcode::Mov as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "mov".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
self.mk_reg_op(PatternOperandKind::Use, 1),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "movl".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Use, 1),
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
self.pattern_cache.insert(
LanaiOpcode::Li as u32,
SharedPattern {
opcode: LanaiOpcode::Li as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "li".into(),
operands: vec![
self.mk_reg_op(PatternOperandKind::Def, 0),
PatternOperand {
kind: PatternOperandKind::Immediate,
vreg: None,
immediate: Some(0),
reg_name: None,
reg_class: None,
},
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "movl".into(),
operands: vec![
PatternOperand {
kind: PatternOperandKind::Immediate,
vreg: None,
immediate: Some(0),
reg_name: None,
reg_class: None,
},
self.mk_reg_op(PatternOperandKind::Def, 0),
],
has_def: true,
is_terminator: false,
is_branch: false,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
self.pattern_cache.insert(
LanaiOpcode::Nop as u32,
SharedPattern {
opcode: LanaiOpcode::Nop as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "nop".into(),
operands: vec![],
has_def: false,
is_terminator: false,
is_branch: false,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "nop".into(),
operands: vec![],
has_def: false,
is_terminator: false,
is_branch: false,
}],
is_identical: true,
lanai_cost: 0,
x86_cost: 0,
can_be_special: false,
},
);
self.pattern_cache.insert(
LanaiOpcode::Jal as u32,
SharedPattern {
opcode: LanaiOpcode::Jal as u32,
lanai_sequence: vec![PatternInstruction {
mnemonic: "jal".into(),
operands: vec![PatternOperand {
kind: PatternOperandKind::BranchTarget,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
}],
has_def: true,
is_terminator: false,
is_branch: true,
}],
x86_sequence: vec![PatternInstruction {
mnemonic: "call".into(),
operands: vec![PatternOperand {
kind: PatternOperandKind::BranchTarget,
vreg: None,
immediate: None,
reg_name: None,
reg_class: None,
}],
has_def: false,
is_terminator: false,
is_branch: true,
}],
is_identical: false,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
}
pub fn with_features(triple: &str, features: &[&str]) -> Self {
let mut b = Self::new(triple);
for f in features {
match *f {
"sse42" => b.has_sse42 = true,
"avx2" => b.has_avx2 = true,
_ => {}
}
}
b
}
pub fn set_opt_level(&mut self, l: CodeGenOptLevel) {
self.opt_level = l;
}
pub fn set_code_model(&mut self, m: CodeModel) {
self.code_model = m;
}
pub fn calculate_frame(&mut self, _local: u32, num_cs: u32, has_fp: bool, is_leaf: bool) {
self.frame_info.has_frame_pointer = has_fp;
self.frame_info.is_leaf = is_leaf;
self.frame_info.num_callee_saved_gprs = num_cs;
self.frame_info.compute_frame_layout();
}
pub fn allocate_vreg(&mut self, vreg: u32, class: RegClassKind) -> AllocatedReg {
let lr = GPR_BASE + (vreg % 28) as u16;
let xr = lanai_gpr_to_x86(lr).unwrap_or("rax").to_string();
let a = AllocatedReg {
vreg,
lanai_reg: lr,
x86_reg: xr,
is_spill: false,
spill_slot: None,
reg_class: class,
};
self.vreg_allocations.insert(vreg, a.clone());
a
}
pub fn find_pattern(&self, opcode: u32) -> Option<&SharedPattern> {
self.pattern_cache.get(&opcode)
}
pub fn generate_lanai_code(&self, ir: &[CrossTargetIRInst]) -> String {
let mut c = self.frame_info.emit_lanai_prologue();
for inst in ir {
if let Some(p) = self.pattern_cache.get(&inst.opcode) {
if let Some(i) = p.lanai_sequence.first() {
c.push_str(&format!("\t{}\n", i.mnemonic));
}
} else {
c.push_str(&format!("\t// Unlowered IR opcode {}\n", inst.opcode));
}
}
c.push_str(&self.frame_info.emit_lanai_epilogue());
c
}
pub fn generate_x86_code(&self, ir: &[CrossTargetIRInst]) -> String {
let mut c = self.frame_info.emit_x86_prologue();
for inst in ir {
if let Some(p) = self.pattern_cache.get(&inst.opcode) {
if let Some(i) = p.x86_sequence.first() {
c.push_str(&format!("\t{}\n", i.mnemonic));
}
} else {
c.push_str(&format!("\t// Unlowered IR opcode {}\n", inst.opcode));
}
}
c.push_str(&self.frame_info.emit_x86_epilogue());
c
}
pub fn generate_cross_target(&self, ir: &[CrossTargetIRInst]) -> (String, String) {
(self.generate_lanai_code(ir), self.generate_x86_code(ir))
}
pub fn compare_costs(&self, ir: &[CrossTargetIRInst]) -> CostComparison {
let mut lt = 0u32;
let mut xt = 0u32;
let mut lu = 0;
let mut xu = 0;
for inst in ir {
if let Some(p) = self.pattern_cache.get(&inst.opcode) {
lt += p.lanai_cost;
xt += p.x86_cost;
} else {
lu += 1;
xu += 1;
}
}
let w = if lu > 0 && xu > 0 {
CostWinner::Tie
} else if lt < xt {
CostWinner::Lanai
} else if xt < lt {
CostWinner::X86
} else {
CostWinner::Tie
};
let n = ir.len();
CostComparison {
lanai_total_cost: lt,
x86_total_cost: xt,
lanai_unsupported_count: lu,
x86_unsupported_count: xu,
winner: w,
instruction_count: n,
average_lanai_cost: if n == 0 { 0.0 } else { lt as f64 / n as f64 },
average_x86_cost: if n == 0 { 0.0 } else { xt as f64 / n as f64 },
}
}
pub fn add_pattern(&mut self, opcode: u32, p: SharedPattern) {
self.pattern_cache.insert(opcode, p);
}
pub fn pattern_count(&self) -> usize {
self.pattern_cache.len()
}
pub fn reset(&mut self) {
self.vreg_allocations.clear();
self.spill_slots.clear();
self.frame_info = BridgeFrameInfo::new();
self.cost_data.clear();
}
pub fn dump_state(&self) -> String {
format!(
"LanaiX86Bridge for {}: {} patterns, {} vregs allocated, {} spill slots",
self.triple,
self.pattern_count(),
self.vreg_allocations.len(),
self.spill_slots.len()
)
}
}
#[derive(Debug, Clone)]
pub struct CostComparison {
pub lanai_total_cost: u32,
pub x86_total_cost: u32,
pub lanai_unsupported_count: usize,
pub x86_unsupported_count: usize,
pub winner: CostWinner,
pub instruction_count: usize,
pub average_lanai_cost: f64,
pub average_x86_cost: f64,
}
impl fmt::Display for CostComparison {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Lanai ↔ X86 Cost Comparison:")?;
writeln!(f, " Instruction count: {}", self.instruction_count)?;
writeln!(f, " Lanai total cost: {}", self.lanai_total_cost)?;
writeln!(f, " X86 total cost: {}", self.x86_total_cost)?;
writeln!(
f,
" Lanai avg cost: {:.2}",
self.average_lanai_cost
)?;
writeln!(
f,
" X86 avg cost: {:.2}",
self.average_x86_cost
)?;
writeln!(f, " Winner: {}", self.winner)
}
}
#[derive(Debug, Clone)]
pub struct MicroarchitectureCosts {
pub lanai_microarch: String,
pub x86_microarch: String,
pub lanai_cycles: HashMap<u32, u32>,
pub x86_cycles: HashMap<u32, u32>,
}
impl Default for MicroarchitectureCosts {
fn default() -> Self {
let mut lc = HashMap::new();
let mut xc = HashMap::new();
for o in [
LanaiOpcode::Add as u32,
LanaiOpcode::Sub as u32,
LanaiOpcode::And as u32,
LanaiOpcode::Or as u32,
LanaiOpcode::Xor as u32,
] {
lc.insert(o, 1);
xc.insert(o, 1);
}
lc.insert(LanaiOpcode::Sh as u32, 1);
xc.insert(LanaiOpcode::Sh as u32, 1);
lc.insert(LanaiOpcode::Addc as u32, 1);
xc.insert(LanaiOpcode::Addc as u32, 2);
lc.insert(LanaiOpcode::Subb as u32, 1);
xc.insert(LanaiOpcode::Subb as u32, 2);
lc.insert(LanaiOpcode::Sel as u32, 1);
xc.insert(LanaiOpcode::Sel as u32, 2);
for o in [LanaiOpcode::Ld as u32, LanaiOpcode::St as u32] {
lc.insert(o, 2);
xc.insert(o, 2);
}
for o in [
LanaiOpcode::Br as u32,
LanaiOpcode::Brcc as u32,
LanaiOpcode::Sa as u32,
] {
lc.insert(o, 1);
xc.insert(o, 1);
}
lc.insert(LanaiOpcode::Popc as u32, 1);
xc.insert(LanaiOpcode::Popc as u32, 3);
lc.insert(LanaiOpcode::Leadz as u32, 1);
xc.insert(LanaiOpcode::Leadz as u32, 3);
lc.insert(LanaiOpcode::Trailz as u32, 1);
xc.insert(LanaiOpcode::Trailz as u32, 3);
MicroarchitectureCosts {
lanai_microarch: "Lanai R1".into(),
x86_microarch: "Intel Skylake".into(),
lanai_cycles: lc,
x86_cycles: xc,
}
}
}
#[derive(Clone)]
pub struct LanaiX86TargetMachine {
pub bridge: LanaiX86Bridge,
pub triple: String,
pub data_layout: String,
pub options: TargetOptions,
}
impl LanaiX86TargetMachine {
pub fn new(triple: &str) -> Self {
let b = LanaiX86Bridge::new(triple);
Self {
bridge: b,
triple: triple.into(),
data_layout: "E-m:e-p:32:32-i64:64-n32-S128".into(),
options: TargetOptions::default(),
}
}
pub fn with_features(triple: &str, f: &[&str]) -> Self {
let b = LanaiX86Bridge::with_features(triple, f);
Self {
bridge: b,
triple: triple.into(),
data_layout: "E-m:e-p:32:32-i64:64-n32-S128".into(),
options: TargetOptions::default(),
}
}
pub fn generate_lanai(&self, ir: &[CrossTargetIRInst]) -> String {
self.bridge.generate_lanai_code(ir)
}
pub fn generate_x86(&self, ir: &[CrossTargetIRInst]) -> String {
self.bridge.generate_x86_code(ir)
}
pub fn compare_costs(&self, ir: &[CrossTargetIRInst]) -> CostComparison {
self.bridge.compare_costs(ir)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let b = LanaiX86Bridge::new("lanai-unknown-none");
assert!(b.is_lanai_primary);
assert!(b.pattern_count() > 10);
}
#[test]
fn test_gpr_to_x86() {
assert_eq!(lanai_gpr_to_x86(R0), Some("rax"));
assert_eq!(lanai_gpr_to_x86(SP), Some("rsp"));
assert_eq!(lanai_gpr_to_x86(RPC), Some("rbp"));
assert_eq!(lanai_gpr_to_x86(9999), None);
}
#[test]
fn test_x86_to_lanai() {
assert_eq!(x86_gpr_to_lanai("rax"), Some(RR1));
assert_eq!(x86_gpr_to_lanai("rsp"), Some(SP));
assert_eq!(x86_gpr_to_lanai("rip"), None);
}
#[test]
fn test_special_desc() {
assert!(lanai_special_to_x86_desc(PC).is_some());
assert!(lanai_special_to_x86_desc(PSW).is_some());
}
#[test]
fn test_abi() {
assert_eq!(LanaiAbi::default().num_int_arg_regs(), 4);
assert_eq!(LanaiAbi::default().num_int_ret_regs(), 2);
}
#[test]
fn test_cc() {
let cc =
BridgeCallingConvention::new(LanaiAbi::default(), X86CallingConventionVariant::SysV64);
assert_eq!(cc.lanai_int_arg_regs.len(), 4);
assert_eq!(cc.x86_int_arg_regs.len(), 6);
}
#[test]
fn test_classify() {
let cc =
BridgeCallingConvention::new(LanaiAbi::default(), X86CallingConventionVariant::SysV64);
assert_eq!(cc.classify_lanai_type(4), ArgClass::Integer);
assert_eq!(cc.classify_lanai_type(8), ArgClass::Memory);
}
#[test]
fn test_frame() {
let fi = BridgeFrameInfo::new();
assert_eq!(fi.frame_size, 0);
assert!(fi.use_sa_instruction);
}
#[test]
fn test_frame_layout() {
let mut fi = BridgeFrameInfo::new();
fi.num_callee_saved_gprs = 4;
fi.max_call_frame_size = 16;
assert!(fi.compute_frame_layout() >= 32);
}
#[test]
fn test_lanai_prologue() {
assert!(BridgeFrameInfo::new().emit_lanai_prologue().contains("sa"));
}
#[test]
fn test_lanai_epilogue() {
assert!(BridgeFrameInfo::new()
.emit_lanai_epilogue()
.contains("bt rca"));
}
#[test]
fn test_x86_prologue() {
assert!(BridgeFrameInfo::new().emit_x86_prologue().contains("pushq"));
}
#[test]
fn test_x86_epilogue() {
assert!(BridgeFrameInfo::new().emit_x86_epilogue().contains("retq"));
}
#[test]
fn test_align() {
assert_eq!(align_up(0, 8), 0);
assert_eq!(align_up(5, 8), 8);
}
#[test]
fn test_find_add() {
assert!(LanaiX86Bridge::new("lanai-unknown-none")
.find_pattern(LanaiOpcode::Add as u32)
.is_some());
}
#[test]
fn test_find_unknown() {
assert!(LanaiX86Bridge::new("lanai-unknown-none")
.find_pattern(99999)
.is_none());
}
#[test]
fn test_gen_lanai() {
let b = LanaiX86Bridge::new("lanai-unknown-none");
let c = b.generate_lanai_code(&[CrossTargetIRInst {
opcode: LanaiOpcode::Add as u32,
dest: Some(0),
srcs: vec![1, 2],
immediate: None,
type_code: 0,
is_terminator: false,
mem_addr: None,
branch_target: None,
condition: None,
}]);
assert!(c.contains("sa"));
assert!(c.contains("bt rca"));
}
#[test]
fn test_gen_x86() {
let b = LanaiX86Bridge::new("lanai-unknown-none");
let c = b.generate_x86_code(&[CrossTargetIRInst {
opcode: LanaiOpcode::Add as u32,
dest: Some(0),
srcs: vec![1, 2],
immediate: None,
type_code: 0,
is_terminator: false,
mem_addr: None,
branch_target: None,
condition: None,
}]);
assert!(c.contains("retq"));
}
#[test]
fn test_cross() {
let b = LanaiX86Bridge::new("lanai-unknown-none");
let (l, x) = b.generate_cross_target(&[]);
assert!(!l.is_empty());
assert!(!x.is_empty());
}
#[test]
fn test_compare() {
let b = LanaiX86Bridge::new("lanai-unknown-none");
let c = b.compare_costs(&[CrossTargetIRInst {
opcode: LanaiOpcode::Add as u32,
dest: Some(0),
srcs: vec![1, 2],
immediate: None,
type_code: 0,
is_terminator: false,
mem_addr: None,
branch_target: None,
condition: None,
}]);
assert_eq!(c.instruction_count, 1);
assert!(c.lanai_total_cost > 0);
}
#[test]
fn test_empty_compare() {
assert_eq!(
LanaiX86Bridge::new("lanai-unknown-none")
.compare_costs(&[])
.instruction_count,
0
);
}
#[test]
fn test_winner_display() {
assert_eq!(format!("{}", CostWinner::Lanai), "Lanai");
assert_eq!(format!("{}", CostWinner::X86), "X86");
}
#[test]
fn test_cost_display() {
let c = CostComparison {
lanai_total_cost: 10,
x86_total_cost: 8,
lanai_unsupported_count: 0,
x86_unsupported_count: 0,
winner: CostWinner::X86,
instruction_count: 4,
average_lanai_cost: 2.5,
average_x86_cost: 2.0,
};
assert!(format!("{}", c).contains("Winner: X86"));
}
#[test]
fn test_alloc_vreg() {
let mut b = LanaiX86Bridge::new("lanai-unknown-none");
let a = b.allocate_vreg(5, RegClassKind::GPR32);
assert_eq!(a.vreg, 5);
assert!(!a.is_spill);
}
#[test]
fn test_reg_class_sizes() {
assert_eq!(RegClassKind::GPR32.size_bytes(), 4);
assert_eq!(RegClassKind::GPR64.size_bytes(), 8);
assert_eq!(RegClassKind::XMM.size_bytes(), 16);
assert_eq!(RegClassKind::Flag.size_bytes(), 0);
}
#[test]
fn test_rc_is_integer() {
assert!(RegClassKind::GPR32.is_integer());
assert!(!RegClassKind::XMM.is_integer());
}
#[test]
fn test_microarch() {
let m = MicroarchitectureCosts::default();
assert!(m.lanai_cycles.len() > 5);
assert!(m.x86_cycles.len() > 5);
}
#[test]
fn test_reset() {
let mut b = LanaiX86Bridge::new("lanai-unknown-none");
b.allocate_vreg(1, RegClassKind::GPR32);
b.reset();
assert!(b.vreg_allocations.is_empty());
}
#[test]
fn test_clone() {
let b = LanaiX86Bridge::new("lanai-unknown-none");
let c = b.clone();
assert_eq!(b.triple, c.triple);
}
#[test]
fn test_add_pat() {
let mut b = LanaiX86Bridge::new("lanai-unknown-none");
let n = b.pattern_count();
b.add_pattern(
999,
SharedPattern {
opcode: 999,
lanai_sequence: vec![],
x86_sequence: vec![],
is_identical: true,
lanai_cost: 1,
x86_cost: 1,
can_be_special: false,
},
);
assert_eq!(b.pattern_count(), n + 1);
}
#[test]
fn test_special_patterns() {
let b = LanaiX86Bridge::new("lanai-unknown-none");
assert!(b.find_pattern(LanaiOpcode::Popc as u32).is_some());
assert!(b.find_pattern(LanaiOpcode::Leadz as u32).is_some());
assert!(b.find_pattern(LanaiOpcode::Trailz as u32).is_some());
}
#[test]
fn test_dump_state() {
let b = LanaiX86Bridge::new("lanai-unknown-none");
let s = b.dump_state();
assert!(s.contains("LanaiX86Bridge"));
assert!(s.contains("patterns"));
}
#[test]
fn test_tm() {
let tm = LanaiX86TargetMachine::new("lanai-unknown-none");
assert!(tm.generate_lanai(&[]).contains("sa"));
}
#[test]
fn test_tm_features() {
let tm = LanaiX86TargetMachine::with_features("lanai-unknown-none", &["avx2"]);
assert!(tm.bridge.has_avx2);
}
#[test]
fn test_pattern_with_empty() {
let p = SharedPattern {
opcode: 0,
lanai_sequence: vec![],
x86_sequence: vec![],
is_identical: true,
lanai_cost: 0,
x86_cost: 0,
can_be_special: false,
};
assert!(p.lanai_sequence.is_empty());
}
#[test]
fn test_mir() {
let i = CrossTargetIRInst {
opcode: 42,
dest: None,
srcs: vec![],
immediate: None,
type_code: 0,
is_terminator: false,
mem_addr: None,
branch_target: None,
condition: None,
};
assert_eq!(i.opcode, 42);
}
#[test]
fn test_mbb() {
let m = CrossTargetMBB {
label: "e".into(),
instructions: vec![],
successors: vec![],
predecessors: vec![],
is_entry: true,
has_fallthrough: true,
};
assert!(m.is_entry);
}
#[test]
fn test_x86cc() {
assert_eq!(format!("{}", X86CallingConventionVariant::SysV64), "sysv64");
}
#[test]
fn test_cost_weights() {
let w = CostWeights::default();
assert!(w.size_weight > 0.0);
assert!(w.target_bias == 0.0);
}
}