use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LegalizeOp {
Add,
Sub,
Mul,
SDiv,
UDiv,
SRem,
URem,
FAdd,
FSub,
FMul,
FDiv,
FRem,
And,
Or,
Xor,
Shl,
LShr,
AShr,
Rotl,
Rotr,
Ctpop,
Ctlz,
Cttz,
Bswap,
Bitreverse,
Trunc,
ZExt,
SExt,
FPTrunc,
FPExt,
FPToUI,
FPToSI,
UIToFP,
SIToFP,
PtrToInt,
IntToPtr,
BitCast,
ICmp,
FCmp,
Br,
BrCond,
Switch,
Ret,
Call,
Invoke,
Load,
Store,
Alloca,
ExtractValue,
InsertValue,
ExtractElement,
InsertElement,
ShuffleVector,
BuildVector,
SAddOvf,
UAddOvf,
SSubOvf,
USubOvf,
SMulOvf,
UMulOvf,
VAStart,
VAEnd,
VACopy,
FrameAddr,
ReturnAddr,
Select,
Phi,
GetElementPtr,
DynamicStackAlloc,
Prefetch,
ReadCycleCounter,
Fence,
Freeze,
}
impl LegalizeOp {
pub fn as_str(&self) -> &'static str {
match self {
Self::Add => "add",
Self::Sub => "sub",
Self::Mul => "mul",
Self::SDiv => "sdiv",
Self::UDiv => "udiv",
Self::SRem => "srem",
Self::URem => "urem",
Self::FAdd => "fadd",
Self::FSub => "fsub",
Self::FMul => "fmul",
Self::FDiv => "fdiv",
Self::FRem => "frem",
Self::And => "and",
Self::Or => "or",
Self::Xor => "xor",
Self::Shl => "shl",
Self::LShr => "lshr",
Self::AShr => "ashr",
Self::Rotl => "rotl",
Self::Rotr => "rotr",
Self::Ctpop => "ctpop",
Self::Ctlz => "ctlz",
Self::Cttz => "cttz",
Self::Bswap => "bswap",
Self::Bitreverse => "bitreverse",
Self::Trunc => "trunc",
Self::ZExt => "zext",
Self::SExt => "sext",
Self::FPTrunc => "fptrunc",
Self::FPExt => "fpext",
Self::FPToUI => "fptoui",
Self::FPToSI => "fptosi",
Self::UIToFP => "uitofp",
Self::SIToFP => "sitofp",
Self::PtrToInt => "ptrtoint",
Self::IntToPtr => "inttoptr",
Self::BitCast => "bitcast",
Self::ICmp => "icmp",
Self::FCmp => "fcmp",
Self::Br => "br",
Self::BrCond => "brcond",
Self::Switch => "switch",
Self::Ret => "ret",
Self::Call => "call",
Self::Invoke => "invoke",
Self::Load => "load",
Self::Store => "store",
Self::Alloca => "alloca",
Self::ExtractValue => "extractvalue",
Self::InsertValue => "insertvalue",
Self::ExtractElement => "extractelement",
Self::InsertElement => "insertelement",
Self::ShuffleVector => "shufflevector",
Self::BuildVector => "buildvector",
Self::SAddOvf => "sadd_overflow",
Self::UAddOvf => "uadd_overflow",
Self::SSubOvf => "ssub_overflow",
Self::USubOvf => "usub_overflow",
Self::SMulOvf => "smul_overflow",
Self::UMulOvf => "umul_overflow",
Self::VAStart => "vastart",
Self::VAEnd => "vaend",
Self::VACopy => "vacopy",
Self::FrameAddr => "frameaddr",
Self::ReturnAddr => "returnaddr",
Self::Select => "select",
Self::Phi => "phi",
Self::GetElementPtr => "getelementptr",
Self::DynamicStackAlloc => "dynamic_stackalloc",
Self::Prefetch => "prefetch",
Self::ReadCycleCounter => "readcyclecounter",
Self::Fence => "fence",
Self::Freeze => "freeze",
}
}
pub fn is_integer_binary(&self) -> bool {
matches!(
self,
Self::Add | Self::Sub | Self::Mul | Self::SDiv | Self::UDiv | Self::SRem | Self::URem
)
}
pub fn is_bitwise(&self) -> bool {
matches!(self, Self::And | Self::Or | Self::Xor)
}
pub fn is_shift(&self) -> bool {
matches!(self, Self::Shl | Self::LShr | Self::AShr)
}
pub fn is_rotate(&self) -> bool {
matches!(self, Self::Rotl | Self::Rotr)
}
pub fn is_bit_count(&self) -> bool {
matches!(
self,
Self::Ctpop | Self::Ctlz | Self::Cttz | Self::Bswap | Self::Bitreverse
)
}
pub fn is_conversion(&self) -> bool {
matches!(
self,
Self::Trunc
| Self::ZExt
| Self::SExt
| Self::FPTrunc
| Self::FPExt
| Self::FPToUI
| Self::FPToSI
| Self::UIToFP
| Self::SIToFP
| Self::PtrToInt
| Self::IntToPtr
| Self::BitCast
)
}
pub fn is_overlow(&self) -> bool {
matches!(
self,
Self::SAddOvf
| Self::UAddOvf
| Self::SSubOvf
| Self::USubOvf
| Self::SMulOvf
| Self::UMulOvf
)
}
pub fn is_va_arg(&self) -> bool {
matches!(self, Self::VAStart | Self::VAEnd | Self::VACopy)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LegalizeType {
Void,
I1,
I8,
I16,
I32,
I64,
I128,
F16,
F32,
F64,
F80,
F128,
PpcFp128,
Pointer,
V2I1,
V4I1,
V8I1,
V16I1,
V32I1,
V64I1,
V2I8,
V4I8,
V8I8,
V16I8,
V32I8,
V64I8,
V2I16,
V4I16,
V8I16,
V16I16,
V32I16,
V2I32,
V4I32,
V8I32,
V16I32,
V2I64,
V4I64,
V8I64,
V2F32,
V4F32,
V8F32,
V16F32,
V2F64,
V4F64,
V8F64,
}
impl LegalizeType {
pub fn scalar_size_bits(&self) -> u32 {
match self {
Self::Void => 0,
Self::I1 => 1,
Self::I8
| Self::V2I8
| Self::V4I8
| Self::V8I8
| Self::V16I8
| Self::V32I8
| Self::V64I8 => 8,
Self::I16 | Self::V2I16 | Self::V4I16 | Self::V8I16 | Self::V16I16 | Self::V32I16 => 16,
Self::I32
| Self::F32
| Self::V2I32
| Self::V4I32
| Self::V8I32
| Self::V16I32
| Self::V2F32
| Self::V4F32
| Self::V8F32
| Self::V16F32 => 32,
Self::I64
| Self::F64
| Self::V2I64
| Self::V4I64
| Self::V8I64
| Self::V2F64
| Self::V4F64
| Self::V8F64
| Self::Pointer => 64,
Self::I128 => 128,
Self::F16 => 16,
Self::F80 => 80,
Self::F128 | Self::PpcFp128 => 128,
Self::V2I1 => 2,
Self::V4I1 => 4,
Self::V8I1 => 8,
Self::V16I1 => 16,
Self::V32I1 => 32,
Self::V64I1 => 64,
}
}
pub fn size_bits(&self) -> u32 {
match self {
Self::Void => 0,
Self::I1 => 1,
Self::I8 => 8,
Self::I16 | Self::F16 => 16,
Self::I32 | Self::F32 => 32,
Self::I64 | Self::F64 | Self::Pointer => 64,
Self::I128 | Self::F128 | Self::PpcFp128 => 128,
Self::F80 => 80,
Self::V2I1 => 2,
Self::V4I1 => 4,
Self::V8I1 => 8,
Self::V16I1 => 16,
Self::V32I1 => 32,
Self::V64I1 => 64,
Self::V2I8 | Self::V4I8 | Self::V8I8 | Self::V16I8 | Self::V32I8 | Self::V64I8 => {
self.scalar_size_bits() * self.num_elements() as u32
}
Self::V2I16 | Self::V4I16 | Self::V8I16 | Self::V16I16 | Self::V32I16 => {
self.scalar_size_bits() * self.num_elements() as u32
}
Self::V2I32
| Self::V4I32
| Self::V8I32
| Self::V16I32
| Self::V2F32
| Self::V4F32
| Self::V8F32
| Self::V16F32 => self.scalar_size_bits() * self.num_elements() as u32,
Self::V2I64 | Self::V4I64 | Self::V8I64 | Self::V2F64 | Self::V4F64 | Self::V8F64 => {
self.scalar_size_bits() * self.num_elements() as u32
}
}
}
pub fn num_elements(&self) -> u32 {
match self {
Self::V2I1
| Self::V2I8
| Self::V2I16
| Self::V2I32
| Self::V2I64
| Self::V2F32
| Self::V2F64 => 2,
Self::V4I1
| Self::V4I8
| Self::V4I16
| Self::V4I32
| Self::V4I64
| Self::V4F32
| Self::V4F64 => 4,
Self::V8I1
| Self::V8I8
| Self::V8I16
| Self::V8I32
| Self::V8I64
| Self::V8F32
| Self::V8F64 => 8,
Self::V16I1 | Self::V16I8 | Self::V16I16 | Self::V16I32 | Self::V16F32 => 16,
Self::V32I1 | Self::V32I8 | Self::V32I16 => 32,
Self::V64I1 | Self::V64I8 => 64,
_ => 1,
}
}
pub fn is_vector(&self) -> bool {
matches!(
self,
Self::V2I1
| Self::V4I1
| Self::V8I1
| Self::V16I1
| Self::V32I1
| Self::V64I1
| Self::V2I8
| Self::V4I8
| Self::V8I8
| Self::V16I8
| Self::V32I8
| Self::V64I8
| Self::V2I16
| Self::V4I16
| Self::V8I16
| Self::V16I16
| Self::V32I16
| Self::V2I32
| Self::V4I32
| Self::V8I32
| Self::V16I32
| Self::V2I64
| Self::V4I64
| Self::V8I64
| Self::V2F32
| Self::V4F32
| Self::V8F32
| Self::V16F32
| Self::V2F64
| Self::V4F64
| Self::V8F64
)
}
pub fn is_scalar_int(&self) -> bool {
matches!(
self,
Self::I1 | Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128
)
}
pub fn is_scalar_fp(&self) -> bool {
matches!(
self,
Self::F16 | Self::F32 | Self::F64 | Self::F80 | Self::F128 | Self::PpcFp128
)
}
pub fn element_type(&self) -> Option<LegalizeType> {
match self {
Self::V2I1 | Self::V4I1 | Self::V8I1 | Self::V16I1 | Self::V32I1 | Self::V64I1 => {
Some(Self::I1)
}
Self::V2I8 | Self::V4I8 | Self::V8I8 | Self::V16I8 | Self::V32I8 | Self::V64I8 => {
Some(Self::I8)
}
Self::V2I16 | Self::V4I16 | Self::V8I16 | Self::V16I16 | Self::V32I16 => {
Some(Self::I16)
}
Self::V2I32 | Self::V4I32 | Self::V8I32 | Self::V16I32 => Some(Self::I32),
Self::V2I64 | Self::V4I64 | Self::V8I64 => Some(Self::I64),
Self::V2F32 | Self::V4F32 | Self::V8F32 | Self::V16F32 => Some(Self::F32),
Self::V2F64 | Self::V4F64 | Self::V8F64 => Some(Self::F64),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LegalizeAction {
Legal,
PromoteTo(LegalizeType),
Expand,
Libcall(String),
Scalarize,
WidenVector,
SplitVector,
Custom,
LowerTo(Vec<LegalizeOp>),
Unsupported,
}
#[derive(Debug, Clone)]
pub struct LegalizeResult {
pub action: LegalizeAction,
pub num_new_ops: u32,
pub needs_libcall: bool,
pub libcall_name: Option<String>,
pub target_type: Option<LegalizeType>,
}
impl LegalizeResult {
pub fn legal() -> Self {
Self {
action: LegalizeAction::Legal,
num_new_ops: 1,
needs_libcall: false,
libcall_name: None,
target_type: None,
}
}
pub fn promote_to(ty: LegalizeType) -> Self {
Self {
action: LegalizeAction::PromoteTo(ty),
num_new_ops: 1,
needs_libcall: false,
libcall_name: None,
target_type: Some(ty),
}
}
pub fn expand() -> Self {
Self {
action: LegalizeAction::Expand,
num_new_ops: 2,
needs_libcall: false,
libcall_name: None,
target_type: None,
}
}
pub fn libcall(name: &str) -> Self {
Self {
action: LegalizeAction::Libcall(name.to_string()),
num_new_ops: 1,
needs_libcall: true,
libcall_name: Some(name.to_string()),
target_type: None,
}
}
pub fn custom() -> Self {
Self {
action: LegalizeAction::Custom,
num_new_ops: 0,
needs_libcall: false,
libcall_name: None,
target_type: None,
}
}
pub fn scalarize() -> Self {
Self {
action: LegalizeAction::Scalarize,
num_new_ops: 0,
needs_libcall: false,
libcall_name: None,
target_type: None,
}
}
pub fn widen_vector() -> Self {
Self {
action: LegalizeAction::WidenVector,
num_new_ops: 1,
needs_libcall: false,
libcall_name: None,
target_type: None,
}
}
pub fn split_vector() -> Self {
Self {
action: LegalizeAction::SplitVector,
num_new_ops: 2,
needs_libcall: false,
libcall_name: None,
target_type: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TargetMode {
X86_64,
X86_32,
X32,
X86_16,
}
impl Default for X86TargetMode {
fn default() -> Self {
Self::X86_64
}
}
impl X86TargetMode {
pub fn is_64bit(&self) -> bool {
matches!(self, Self::X86_64 | Self::X32)
}
pub fn pointer_width(&self) -> u32 {
match self {
Self::X86_64 => 64,
Self::X86_32 => 32,
Self::X32 => 32,
Self::X86_16 => 16,
}
}
pub fn max_vector_width_bits(&self) -> u32 {
512 }
}
#[derive(Debug, Clone)]
pub struct X86LegalizerConfig {
pub target_mode: X86TargetMode,
pub has_sse2: bool,
pub has_sse41: bool,
pub has_avx: bool,
pub has_avx2: bool,
pub has_avx512: bool,
pub has_bmi: bool,
pub has_bmi2: bool,
pub has_lzcnt: bool,
pub has_popcnt: bool,
pub has_bmi_tzcnt: bool,
pub has_movbe: bool,
pub has_cmov: bool,
pub has_fma: bool,
pub has_x87: bool,
pub has_sse3: bool,
pub has_ssse3: bool,
pub has_aes: bool,
pub has_sha: bool,
pub has_rdrnd: bool,
pub has_fsgsbase: bool,
pub has_rtm: bool,
pub has_hle: bool,
pub has_adx: bool,
pub has_rdseed: bool,
pub has_prfchw: bool,
pub has_f16c: bool,
pub has_xop: bool,
pub has_tbm: bool,
pub vector_width: u32,
}
impl Default for X86LegalizerConfig {
fn default() -> Self {
Self {
target_mode: X86TargetMode::X86_64,
has_sse2: true,
has_sse41: true,
has_avx: true,
has_avx2: true,
has_avx512: false,
has_bmi: true,
has_bmi2: true,
has_lzcnt: true,
has_popcnt: true,
has_bmi_tzcnt: true,
has_movbe: true,
has_cmov: true,
has_fma: true,
has_x87: true,
has_sse3: true,
has_ssse3: true,
has_aes: false,
has_sha: false,
has_rdrnd: false,
has_fsgsbase: false,
has_rtm: false,
has_hle: false,
has_adx: false,
has_rdseed: false,
has_prfchw: false,
has_f16c: false,
has_xop: false,
has_tbm: false,
vector_width: 256,
}
}
}
#[derive(Debug, Clone)]
pub struct VAArgABIConfig {
pub gp_offset_offset: u32,
pub fp_offset_offset: u32,
pub overflow_arg_area_offset: u32,
pub reg_save_area_offset: u32,
pub va_list_size: u32,
pub num_gpr_save: u32,
pub num_xmm_save: u32,
pub reg_save_area_size: u32,
pub is_64bit: bool,
}
impl VAArgABIConfig {
pub fn sysv64() -> Self {
Self {
gp_offset_offset: 0,
fp_offset_offset: 4,
overflow_arg_area_offset: 8,
reg_save_area_offset: 16,
va_list_size: 24,
num_gpr_save: 6,
num_xmm_save: 8,
reg_save_area_size: 176, is_64bit: true,
}
}
pub fn win64() -> Self {
Self {
gp_offset_offset: 0,
fp_offset_offset: 4,
overflow_arg_area_offset: 8,
reg_save_area_offset: 16,
va_list_size: 24,
num_gpr_save: 4,
num_xmm_save: 4,
reg_save_area_size: 96, is_64bit: true,
}
}
pub fn x86_32_cdecl() -> Self {
Self {
gp_offset_offset: 0,
fp_offset_offset: 0,
overflow_arg_area_offset: 4,
reg_save_area_offset: 8,
va_list_size: 12,
num_gpr_save: 0,
num_xmm_save: 0,
reg_save_area_size: 0,
is_64bit: false,
}
}
}
#[derive(Debug, Clone)]
pub struct CustomLoweringDescriptor {
pub op: LegalizeOp,
pub description: &'static str,
pub x86_sequence: Vec<&'static str>,
pub requires_feature: Option<&'static str>,
}
pub struct X86OperationLegalizer {
pub config: X86LegalizerConfig,
cache: HashMap<(LegalizeOp, LegalizeType), LegalizeResult>,
pub stats: X86LegalizerStats,
pub va_config: VAArgABIConfig,
pub soft_float: bool,
pub soft_float80: bool,
}
#[derive(Debug, Clone, Default)]
pub struct X86LegalizerStats {
pub ops_legalized: u64,
pub ops_expanded: u64,
pub ops_promoted: u64,
pub ops_libcalled: u64,
pub ops_scalarized: u64,
pub vectors_widened: u64,
pub vectors_split: u64,
pub custom_lowered: u64,
pub unsupported_count: u64,
pub i128_expansions: u64,
pub va_arg_lowerings: u64,
pub switch_lowerings: u64,
pub oveflow_lowerings: u64,
pub cache_hits: u64,
pub cache_misses: u64,
}
impl X86LegalizerStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_expand(&mut self) {
self.ops_expanded += 1;
self.ops_legalized += 1;
}
pub fn record_promote(&mut self) {
self.ops_promoted += 1;
self.ops_legalized += 1;
}
pub fn record_libcall(&mut self) {
self.ops_libcalled += 1;
self.ops_legalized += 1;
}
pub fn record_custom(&mut self) {
self.custom_lowered += 1;
self.ops_legalized += 1;
}
pub fn record_scalarize(&mut self) {
self.ops_scalarized += 1;
self.ops_legalized += 1;
}
pub fn record_widen(&mut self) {
self.vectors_widened += 1;
self.ops_legalized += 1;
}
pub fn record_split(&mut self) {
self.vectors_split += 1;
self.ops_legalized += 1;
}
pub fn total(&self) -> u64 {
self.ops_legalized
}
}
pub fn get_custom_lowering_table() -> Vec<CustomLoweringDescriptor> {
vec![
CustomLoweringDescriptor {
op: LegalizeOp::Rotl,
description: "Lower rotl using (x << amt) | (x >> (width - amt))",
x86_sequence: vec!["shl", "shr", "or"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::Rotr,
description: "Lower rotr using (x >> amt) | (x << (width - amt))",
x86_sequence: vec!["shr", "shl", "or"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::Ctpop,
description: "Lower ctpop using popcnt instruction or software fallback",
x86_sequence: vec!["popcnt"],
requires_feature: Some("popcnt"),
},
CustomLoweringDescriptor {
op: LegalizeOp::Ctlz,
description: "Lower ctlz using lzcnt or bsr+neg fallback",
x86_sequence: vec!["lzcnt"],
requires_feature: Some("lzcnt"),
},
CustomLoweringDescriptor {
op: LegalizeOp::Cttz,
description: "Lower cttz using tzcnt or bsf+cmov fallback",
x86_sequence: vec!["tzcnt"],
requires_feature: Some("bmi"),
},
CustomLoweringDescriptor {
op: LegalizeOp::Bswap,
description: "Lower bswap using x86 bswap instruction",
x86_sequence: vec!["bswap"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::Bitreverse,
description: "Lower bitreverse via byte-reverse + nibble-swap or RBIT equivalent",
x86_sequence: vec!["bswap", "and", "shl", "shr", "or"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::SMulOvf,
description: "Lower smul_overflow using imul + seto",
x86_sequence: vec!["imul", "seto"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::UMulOvf,
description: "Lower umul_overflow using mul + seto",
x86_sequence: vec!["mul", "seto"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::SAddOvf,
description: "Lower sadd_overflow using add + seto",
x86_sequence: vec!["add", "seto"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::UAddOvf,
description: "Lower uadd_overflow using add + setc",
x86_sequence: vec!["add", "setc"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::SSubOvf,
description: "Lower ssub_overflow using sub + seto",
x86_sequence: vec!["sub", "seto"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::USubOvf,
description: "Lower usub_overflow using sub + setc",
x86_sequence: vec!["sub", "setc"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::FrameAddr,
description: "Lower frameaddress to reading rbp register (or ebp in 32-bit)",
x86_sequence: vec!["mov rbp/ebp"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::ReturnAddr,
description: "Lower returnaddress to reading [rbp+8] or [ebp+4]",
x86_sequence: vec!["load [rbp+disp]"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::Prefetch,
description: "Lower prefetch to PREFETCHh instruction",
x86_sequence: vec!["prefetchh"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::ReadCycleCounter,
description: "Lower readcyclecounter to RDTSC instruction",
x86_sequence: vec!["rdtsc"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::VAStart,
description: "Lower vastart — initialize va_list structure",
x86_sequence: vec!["vastart setup"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::VAEnd,
description: "Lower vaend — no-op on x86",
x86_sequence: vec!["nop (vaend)"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::VACopy,
description: "Lower vacopy — memcpy the va_list",
x86_sequence: vec!["memcpy va_list"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::DynamicStackAlloc,
description: "Lower dynamic_stackalloc to sub rsp + probe",
x86_sequence: vec!["sub rsp, size", "and rsp, -align", "probe stack"],
requires_feature: None,
},
CustomLoweringDescriptor {
op: LegalizeOp::Switch,
description: "Lower switch to jump table or binary decision tree",
x86_sequence: vec!["jump table", "binary tree"],
requires_feature: None,
},
]
}
impl X86OperationLegalizer {
pub fn new(config: X86LegalizerConfig) -> Self {
Self {
config,
cache: HashMap::new(),
stats: X86LegalizerStats::new(),
va_config: VAArgABIConfig::sysv64(),
soft_float: false,
soft_float80: false,
}
}
pub fn new_x86_64() -> Self {
let mut config = X86LegalizerConfig::default();
config.target_mode = X86TargetMode::X86_64;
config.has_avx512 = true;
config.vector_width = 512;
let mut legalizer = Self::new(config);
legalizer.va_config = VAArgABIConfig::sysv64();
legalizer
}
pub fn new_x86_32() -> Self {
let mut config = X86LegalizerConfig::default();
config.target_mode = X86TargetMode::X86_32;
config.has_avx = false;
config.has_avx2 = false;
config.has_avx512 = false;
config.has_fma = false;
config.has_bmi = false;
config.has_bmi2 = false;
config.has_lzcnt = false;
config.has_popcnt = true;
config.has_sse41 = false;
config.vector_width = 128;
let mut legalizer = Self::new(config);
legalizer.va_config = VAArgABIConfig::x86_32_cdecl();
legalizer
}
pub fn new_win64() -> Self {
let mut legalizer = Self::new_x86_64();
legalizer.va_config = VAArgABIConfig::win64();
legalizer
}
pub fn with_soft_float(mut self, yes: bool) -> Self {
self.soft_float = yes;
self
}
pub fn with_soft_float80(mut self, yes: bool) -> Self {
self.soft_float80 = yes;
self
}
pub fn with_va_config(mut self, config: VAArgABIConfig) -> Self {
self.va_config = config;
self
}
pub fn with_vector_width(mut self, width: u32) -> Self {
self.config.vector_width = width;
self
}
pub fn max_vector_bits(&self) -> u32 {
self.config.vector_width
}
pub fn legal_int_width(&self) -> u32 {
if self.config.target_mode.is_64bit() {
64
} else {
32
}
}
pub fn legalize(&mut self, op: LegalizeOp, ty: LegalizeType) -> LegalizeResult {
let key = (op, ty);
if let Some(cached) = self.cache.get(&key) {
self.stats.cache_hits += 1;
return cached.clone();
}
self.stats.cache_misses += 1;
let result = self.legalize_impl(op, ty);
self.update_stats(&result);
self.cache.insert(key, result.clone());
result
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
pub fn is_legal(&mut self, op: LegalizeOp, ty: LegalizeType) -> bool {
matches!(self.legalize(op, ty).action, LegalizeAction::Legal)
}
pub fn take_stats(&mut self) -> X86LegalizerStats {
let stats = self.stats.clone();
self.stats = X86LegalizerStats::new();
stats
}
fn legalize_impl(&self, op: LegalizeOp, ty: LegalizeType) -> LegalizeResult {
if self.is_always_legal(op, ty) {
return LegalizeResult::legal();
}
if self.needs_expand(op, ty) {
return self.legalize_expand(op, ty);
}
if self.needs_promote(op, ty) {
return self.legalize_promote(op, ty);
}
if self.needs_fp_libcall(op, ty) {
return self.legalize_fp_libcall(op, ty);
}
if ty.is_vector() {
return self.legalize_vector(op, ty);
}
if self.needs_custom_lower(op) {
return self.legalize_custom(op, ty);
}
LegalizeResult::legal()
}
fn is_always_legal(&self, _op: LegalizeOp, ty: LegalizeType) -> bool {
if matches!(ty, LegalizeType::Void) {
return true;
}
if matches!(ty, LegalizeType::Pointer) {
return true;
}
false
}
fn needs_expand(&self, _op: LegalizeOp, ty: LegalizeType) -> bool {
let _legal_width = self.legal_int_width();
if ty == LegalizeType::I128 {
return true;
}
if ty == LegalizeType::I64 && !self.config.target_mode.is_64bit() {
return true;
}
if ty.is_vector() {
if let Some(elem) = ty.element_type() {
if elem == LegalizeType::I128 {
return true;
}
if elem == LegalizeType::I64 && !self.config.target_mode.is_64bit() {
return true;
}
}
}
false
}
fn legalize_expand(&self, op: LegalizeOp, ty: LegalizeType) -> LegalizeResult {
match op {
LegalizeOp::Add
| LegalizeOp::Sub
| LegalizeOp::Mul
| LegalizeOp::And
| LegalizeOp::Or
| LegalizeOp::Xor => {
LegalizeResult::expand()
}
LegalizeOp::SDiv | LegalizeOp::UDiv | LegalizeOp::SRem | LegalizeOp::URem => {
let name = match op {
LegalizeOp::SDiv if ty == LegalizeType::I128 => "__divti3",
LegalizeOp::UDiv if ty == LegalizeType::I128 => "__udivti3",
LegalizeOp::SRem if ty == LegalizeType::I128 => "__modti3",
LegalizeOp::URem if ty == LegalizeType::I128 => "__umodti3",
LegalizeOp::SDiv => "__divdi3",
LegalizeOp::UDiv => "__udivdi3",
LegalizeOp::SRem => "__moddi3",
_ => "__umoddi3",
};
LegalizeResult::libcall(name)
}
LegalizeOp::Shl | LegalizeOp::LShr | LegalizeOp::AShr => LegalizeResult::expand(),
LegalizeOp::Ctpop
| LegalizeOp::Ctlz
| LegalizeOp::Cttz
| LegalizeOp::Bswap
| LegalizeOp::Bitreverse => {
LegalizeResult::expand()
}
LegalizeOp::ICmp => {
LegalizeResult::expand()
}
LegalizeOp::Trunc => LegalizeResult::legal(), LegalizeOp::ZExt | LegalizeOp::SExt => LegalizeResult::expand(), _ => LegalizeResult::expand(),
}
}
fn needs_promote(&self, op: LegalizeOp, ty: LegalizeType) -> bool {
if ty == LegalizeType::I1 {
return true;
}
if matches!(ty, LegalizeType::I8 | LegalizeType::I16) && ty.is_scalar_int() {
if matches!(op, LegalizeOp::Load | LegalizeOp::Store) {
return false;
}
if op.is_conversion() {
return false;
}
return true;
}
false
}
fn legalize_promote(&self, op: LegalizeOp, _ty: LegalizeType) -> LegalizeResult {
let target = LegalizeType::I32;
match op {
LegalizeOp::Add
| LegalizeOp::Sub
| LegalizeOp::Mul
| LegalizeOp::And
| LegalizeOp::Or
| LegalizeOp::Xor
| LegalizeOp::Shl
| LegalizeOp::LShr
| LegalizeOp::AShr => LegalizeResult::promote_to(target),
LegalizeOp::SDiv | LegalizeOp::UDiv | LegalizeOp::SRem | LegalizeOp::URem => {
LegalizeResult::promote_to(target)
}
LegalizeOp::ICmp => LegalizeResult::promote_to(target),
LegalizeOp::Select => LegalizeResult::promote_to(target),
_ => LegalizeResult::promote_to(target),
}
}
fn needs_fp_libcall(&self, _op: LegalizeOp, ty: LegalizeType) -> bool {
if !ty.is_scalar_fp() {
return false;
}
match ty {
LegalizeType::F16 => true, LegalizeType::F32 | LegalizeType::F64 => false, LegalizeType::F80 => self.soft_float80 || !self.config.has_x87,
LegalizeType::F128 | LegalizeType::PpcFp128 => self.soft_float || true,
_ => false,
}
}
fn legalize_fp_libcall(&self, op: LegalizeOp, ty: LegalizeType) -> LegalizeResult {
let prefix = match ty {
LegalizeType::F16 => "h",
LegalizeType::F32 => "f",
LegalizeType::F64 => "d",
LegalizeType::F80 => "x",
LegalizeType::F128 => "t",
LegalizeType::PpcFp128 => "t",
_ => "d",
};
let suffix = match op {
LegalizeOp::FAdd => "add",
LegalizeOp::FSub => "sub",
LegalizeOp::FMul => "mul",
LegalizeOp::FDiv => "div",
LegalizeOp::FRem => "rem",
LegalizeOp::FCmp => "cmp",
LegalizeOp::FPExt => "ext",
LegalizeOp::FPTrunc => "trunc",
LegalizeOp::SIToFP => "si_to_fp",
LegalizeOp::UIToFP => "ui_to_fp",
LegalizeOp::FPToSI => "fp_to_si",
LegalizeOp::FPToUI => "fp_to_ui",
_ => "op",
};
let name = format!("__{prefix}{suffix}3");
LegalizeResult::libcall(&name)
}
fn needs_custom_lower(&self, op: LegalizeOp) -> bool {
matches!(
op,
LegalizeOp::Rotl
| LegalizeOp::Rotr
| LegalizeOp::Ctpop
| LegalizeOp::Ctlz
| LegalizeOp::Cttz
| LegalizeOp::Bswap
| LegalizeOp::Bitreverse
| LegalizeOp::SMulOvf
| LegalizeOp::UMulOvf
| LegalizeOp::SAddOvf
| LegalizeOp::UAddOvf
| LegalizeOp::SSubOvf
| LegalizeOp::USubOvf
| LegalizeOp::FrameAddr
| LegalizeOp::ReturnAddr
| LegalizeOp::DynamicStackAlloc
| LegalizeOp::Prefetch
| LegalizeOp::ReadCycleCounter
| LegalizeOp::VAStart
| LegalizeOp::VAEnd
| LegalizeOp::VACopy
| LegalizeOp::Switch
)
}
fn legalize_custom(&self, _op: LegalizeOp, _ty: LegalizeType) -> LegalizeResult {
LegalizeResult::custom()
}
fn legalize_vector(&self, op: LegalizeOp, ty: LegalizeType) -> LegalizeResult {
let total_bits = ty.size_bits();
let max_vec = self.max_vector_bits();
if total_bits > max_vec {
return LegalizeResult::split_vector();
}
let min_useful = self.min_vector_bits();
if total_bits < min_useful && self.can_widen_vector(op, ty) {
return LegalizeResult::widen_vector();
}
if let Some(elem) = ty.element_type() {
if elem == LegalizeType::I1 {
return self.legalize_mask_vector(op, ty);
}
}
LegalizeResult::legal()
}
fn min_vector_bits(&self) -> u32 {
if self.config.has_avx || self.config.has_avx2 {
128
} else {
64
}
}
fn can_widen_vector(&self, _op: LegalizeOp, ty: LegalizeType) -> bool {
ty.element_type().is_some()
}
fn legalize_mask_vector(&self, _op: LegalizeOp, _ty: LegalizeType) -> LegalizeResult {
if self.config.has_avx512 {
LegalizeResult::legal()
} else {
LegalizeResult::scalarize()
}
}
fn update_stats(&mut self, result: &LegalizeResult) {
match result.action {
LegalizeAction::Legal => {}
LegalizeAction::PromoteTo(_) => self.stats.record_promote(),
LegalizeAction::Expand => {
self.stats.record_expand();
self.stats.i128_expansions += 1;
}
LegalizeAction::Libcall(_) => self.stats.record_libcall(),
LegalizeAction::Scalarize => self.stats.record_scalarize(),
LegalizeAction::WidenVector => self.stats.record_widen(),
LegalizeAction::SplitVector => self.stats.record_split(),
LegalizeAction::Custom => {
self.stats.record_custom();
}
LegalizeAction::LowerTo(_) => {
self.stats.ops_legalized += 1;
}
LegalizeAction::Unsupported => {
self.stats.unsupported_count += 1;
}
}
}
}
#[derive(Debug, Clone)]
pub struct I128Expansion {
pub lo_op: LegalizeOp,
pub hi_op: LegalizeOp,
pub needs_carry: bool,
pub needs_borrow: bool,
pub extra_ops: Vec<LegalizeOp>,
}
pub fn expand_i128_operation(op: LegalizeOp) -> I128Expansion {
match op {
LegalizeOp::Add => I128Expansion {
lo_op: LegalizeOp::Add,
hi_op: LegalizeOp::Add,
needs_carry: true,
needs_borrow: false,
extra_ops: vec![],
},
LegalizeOp::Sub => I128Expansion {
lo_op: LegalizeOp::Sub,
hi_op: LegalizeOp::Sub,
needs_carry: false,
needs_borrow: true,
extra_ops: vec![],
},
LegalizeOp::Mul => I128Expansion {
lo_op: LegalizeOp::Mul,
hi_op: LegalizeOp::Mul,
needs_carry: false,
needs_borrow: false,
extra_ops: vec![
LegalizeOp::Add,
LegalizeOp::Add, ],
},
LegalizeOp::And => I128Expansion {
lo_op: LegalizeOp::And,
hi_op: LegalizeOp::And,
needs_carry: false,
needs_borrow: false,
extra_ops: vec![],
},
LegalizeOp::Or => I128Expansion {
lo_op: LegalizeOp::Or,
hi_op: LegalizeOp::Or,
needs_carry: false,
needs_borrow: false,
extra_ops: vec![],
},
LegalizeOp::Xor => I128Expansion {
lo_op: LegalizeOp::Xor,
hi_op: LegalizeOp::Xor,
needs_carry: false,
needs_borrow: false,
extra_ops: vec![],
},
LegalizeOp::Shl => I128Expansion {
lo_op: LegalizeOp::Shl,
hi_op: LegalizeOp::Shl,
needs_carry: true,
needs_borrow: false,
extra_ops: vec![
LegalizeOp::Or,
LegalizeOp::LShr,
LegalizeOp::And,
LegalizeOp::Select,
],
},
LegalizeOp::LShr => I128Expansion {
lo_op: LegalizeOp::LShr,
hi_op: LegalizeOp::LShr,
needs_carry: false,
needs_borrow: true,
extra_ops: vec![
LegalizeOp::Or,
LegalizeOp::Shl,
LegalizeOp::And,
LegalizeOp::Select,
],
},
LegalizeOp::AShr => I128Expansion {
lo_op: LegalizeOp::LShr,
hi_op: LegalizeOp::AShr,
needs_carry: false,
needs_borrow: true,
extra_ops: vec![
LegalizeOp::Or,
LegalizeOp::Shl,
LegalizeOp::And,
LegalizeOp::Select,
LegalizeOp::AShr,
],
},
_ => I128Expansion {
lo_op: op,
hi_op: op,
needs_carry: false,
needs_borrow: false,
extra_ops: vec![],
},
}
}
#[derive(Debug, Clone)]
pub struct SwitchLoweringConfig {
pub min_jump_table_cases: u32,
pub max_jump_table_density: f64,
pub use_bit_tests: bool,
pub min_bit_test_cases: u32,
}
impl Default for SwitchLoweringConfig {
fn default() -> Self {
Self {
min_jump_table_cases: 4,
max_jump_table_density: 0.4, use_bit_tests: true,
min_bit_test_cases: 3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SwitchLoweringKind {
LinearChain,
BinaryTree,
JumpTable,
BitTest,
}
#[derive(Debug, Clone)]
pub struct SwitchLoweringDecision {
pub kind: SwitchLoweringKind,
pub num_cases: u32,
pub is_signed: bool,
pub min_value: i64,
pub max_value: i64,
pub density: f64,
pub reason: String,
}
pub fn analyze_switch_for_lowering(
cases: &[(i64, u32)], _default_dest: u32,
config: &SwitchLoweringConfig,
) -> SwitchLoweringDecision {
if cases.is_empty() {
return SwitchLoweringDecision {
kind: SwitchLoweringKind::LinearChain,
num_cases: 0,
is_signed: false,
min_value: 0,
max_value: 0,
density: 0.0,
reason: "no cases".to_string(),
};
}
let num_cases = cases.len() as u32;
let min_value = cases.iter().map(|(v, _)| *v).min().unwrap_or(0);
let max_value = cases.iter().map(|(v, _)| *v).max().unwrap_or(0);
let range = (max_value - min_value).max(1) as f64;
let density = num_cases as f64 / range;
let is_signed = min_value < 0;
if num_cases >= config.min_jump_table_cases && density >= config.max_jump_table_density {
SwitchLoweringDecision {
kind: SwitchLoweringKind::JumpTable,
num_cases,
is_signed,
min_value,
max_value,
density,
reason: format!(
"jump table: {num_cases} cases, density {density:.2} >= {}",
config.max_jump_table_density
),
}
} else if num_cases > 4 {
SwitchLoweringDecision {
kind: SwitchLoweringKind::BinaryTree,
num_cases,
is_signed,
min_value,
max_value,
density,
reason: format!("binary tree: {num_cases} cases, density {density:.2}"),
}
} else {
SwitchLoweringDecision {
kind: SwitchLoweringKind::LinearChain,
num_cases,
is_signed,
min_value,
max_value,
density,
reason: format!("linear chain: only {num_cases} cases"),
}
}
}
#[derive(Debug, Clone)]
pub struct SwitchBinaryTreeNode {
pub pivot: i64,
pub left_child: Box<SwitchBinaryTree>,
pub right_child: Box<SwitchBinaryTree>,
}
#[derive(Debug, Clone)]
pub enum SwitchBinaryTree {
Leaf(u32), Node(SwitchBinaryTreeNode),
}
pub fn build_switch_binary_tree(cases: &[(i64, u32)], default_dest: u32) -> SwitchBinaryTree {
if cases.is_empty() {
return SwitchBinaryTree::Leaf(default_dest);
}
let mut sorted: Vec<(i64, u32)> = cases.to_vec();
sorted.sort_by_key(|(v, _)| *v);
build_switch_tree_recursive(&sorted, default_dest)
}
fn build_switch_tree_recursive(cases: &[(i64, u32)], _default_dest: u32) -> SwitchBinaryTree {
if cases.is_empty() {
return SwitchBinaryTree::Leaf(_default_dest);
}
if cases.len() == 1 {
return SwitchBinaryTree::Leaf(cases[0].1);
}
let mid = cases.len() / 2;
let pivot = cases[mid].0;
let left = build_switch_tree_recursive(&cases[..mid], _default_dest);
let right = build_switch_tree_recursive(&cases[mid..], _default_dest);
SwitchBinaryTree::Node(SwitchBinaryTreeNode {
pivot,
left_child: Box::new(left),
right_child: Box::new(right),
})
}
#[derive(Debug, Clone)]
pub struct JumpTableConfig {
pub base_label: String,
pub min_value: i64,
pub max_value: i64,
pub default_dest: u32,
pub entries: Vec<u32>,
pub is_pic: bool,
pub use_relative_offsets: bool,
}
pub fn build_jump_table_entries(
cases: &[(i64, u32)],
default_dest: u32,
min_value: i64,
max_value: i64,
) -> Vec<u32> {
let range = (max_value - min_value + 1) as usize;
let mut entries = vec![default_dest; range];
for (value, dest) in cases {
let idx = (*value - min_value) as usize;
if idx < range {
entries[idx] = *dest;
}
}
entries
}
#[derive(Debug, Clone)]
pub struct VAStartLowering {
pub init_gp_offset: Vec<String>,
pub init_fp_offset: Vec<String>,
pub init_overflow: Vec<String>,
pub init_reg_save: Vec<String>,
}
pub fn build_va_start_lowering(config: &VAArgABIConfig, va_list_ptr: &str) -> VAStartLowering {
if config.is_64bit {
VAStartLowering {
init_gp_offset: vec![format!("mov dword ptr [{va_list_ptr}], 0")],
init_fp_offset: vec![format!(
"mov dword ptr [{va_list_ptr} + {}], {}",
config.fp_offset_offset,
config.num_gpr_save * 8
)],
init_overflow: vec![
format!(
"lea rax, [rsp + {}]",
8 ),
format!(
"mov qword ptr [{va_list_ptr} + {}], rax",
config.overflow_arg_area_offset
),
],
init_reg_save: vec![
format!("lea rax, [rsp + {}]", 8 + config.reg_save_area_size as i32),
format!(
"mov qword ptr [{va_list_ptr} + {}], rax",
config.reg_save_area_offset
),
],
}
} else {
VAStartLowering {
init_gp_offset: vec![],
init_fp_offset: vec![],
init_overflow: vec![
format!("lea eax, [ebp + {}]", (config.va_list_size + 8) as i32),
format!(
"mov dword ptr [{va_list_ptr} + {}], eax",
config.overflow_arg_area_offset
),
],
init_reg_save: vec![],
}
}
}
pub fn build_va_end_lowering() -> Vec<String> {
vec!["; va_end: no-op on x86".to_string()]
}
pub fn build_va_copy_lowering(
config: &VAArgABIConfig,
dest_ptr: &str,
src_ptr: &str,
) -> Vec<String> {
let size = config.va_list_size;
vec![
format!("; va_copy: memcpy {size} bytes from {src_ptr} to {dest_ptr}"),
format!("mov ecx, {size}"),
"rep movsb".to_string(),
]
}
#[derive(Debug, Clone)]
pub struct DynamicStackAllocLowering {
pub alignment: u32,
pub instructions: Vec<String>,
pub needs_probe: bool,
pub probe_instructions: Vec<String>,
}
pub fn build_dynamic_stack_alloc_lowering(
size_reg: &str,
alignment: u32,
is_64bit: bool,
) -> DynamicStackAllocLowering {
let sp = if is_64bit { "rsp" } else { "esp" };
let _bp = if is_64bit { "rbp" } else { "ebp" };
let mut instructions = Vec::new();
let align_mask = alignment.wrapping_sub(1);
instructions.push(format!(
"; dynamic_stackalloc: allocate {size_reg} bytes aligned to {alignment}"
));
if align_mask != 0 {
instructions.push(format!("add {size_reg}, {align_mask}"));
instructions.push(format!("and {size_reg}, -{}", alignment as i32));
}
instructions.push(format!("sub {sp}, {size_reg}"));
if align_mask != 0 {
instructions.push(format!("and {sp}, -{}", alignment as i32));
}
let needs_probe = true; let probe_instructions = if needs_probe {
vec![
format!("; stack probe for dynamic allocation"),
format!("test dword ptr [{sp}], {sp}"), ]
} else {
vec![]
};
DynamicStackAllocLowering {
alignment,
instructions,
needs_probe,
probe_instructions,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrefetchHint {
T0, T1, T2, NTA, }
impl PrefetchHint {
pub fn to_x86_prefetch_imm(&self) -> u8 {
match self {
Self::T0 => 0,
Self::T1 => 1,
Self::T2 => 2,
Self::NTA => 3,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::T0 => "t0",
Self::T1 => "t1",
Self::T2 => "t2",
Self::NTA => "nta",
}
}
}
pub fn lower_prefetch(
addr_reg: &str,
_hint: PrefetchHint,
is_write: bool,
locality: u32,
) -> Vec<String> {
let prefetch_mnemonic = if is_write {
"prefetchw"
} else {
match locality {
0 => "prefetchnta",
1 => "prefetcht1",
2 => "prefetcht2",
_ => "prefetcht0",
}
};
vec![format!(
"{prefetch_mnemonic} [{addr_reg}] ; locality={locality}"
)]
}
#[derive(Debug, Clone)]
pub struct RdtscLowering {
pub uses_rdtscp: bool,
pub instructions: Vec<String>,
}
pub fn build_rdtsc_lowering(has_rdtscp: bool, is_64bit: bool) -> RdtscLowering {
let reg_hi = if is_64bit { "rdx" } else { "edx" };
let reg_lo = if is_64bit { "rax" } else { "eax" };
let _reg_c = if is_64bit { "rcx" } else { "ecx" };
let instructions = if has_rdtscp {
vec![
format!("; READCYCLECOUNTER via RDTSCP"),
format!("rdtscp"),
format!("shl {reg_hi}, 32"),
format!("or {reg_lo}, {reg_hi}"),
]
} else {
vec![
format!("; READCYCLECOUNTER via RDTSC"),
format!("rdtsc"),
format!("shl {reg_hi}, 32"),
format!("or {reg_lo}, {reg_hi}"),
]
};
RdtscLowering {
uses_rdtscp: has_rdtscp,
instructions,
}
}
#[derive(Debug, Clone)]
pub struct OverflowLoweringResult {
pub value_instructions: Vec<String>, pub overflow_instructions: Vec<String>, pub condition_code: &'static str, }
pub fn lower_overflow_op(op: LegalizeOp, is_64bit: bool) -> OverflowLoweringResult {
let _width = if is_64bit { "64" } else { "32" };
let suffix = if is_64bit { "" } else { "d" };
match op {
LegalizeOp::SAddOvf => OverflowLoweringResult {
value_instructions: vec![format!("add{suffix} dst, src")],
overflow_instructions: vec!["seto al".to_string()],
condition_code: "o",
},
LegalizeOp::UAddOvf => OverflowLoweringResult {
value_instructions: vec![format!("add{suffix} dst, src")],
overflow_instructions: vec!["setc al".to_string()],
condition_code: "c",
},
LegalizeOp::SSubOvf => OverflowLoweringResult {
value_instructions: vec![format!("sub{suffix} dst, src")],
overflow_instructions: vec!["seto al".to_string()],
condition_code: "o",
},
LegalizeOp::USubOvf => OverflowLoweringResult {
value_instructions: vec![format!("sub{suffix} dst, src")],
overflow_instructions: vec!["setc al".to_string()],
condition_code: "c",
},
LegalizeOp::SMulOvf => OverflowLoweringResult {
value_instructions: vec![format!("imul{suffix} dst, src")],
overflow_instructions: vec!["seto al".to_string()],
condition_code: "o",
},
LegalizeOp::UMulOvf => OverflowLoweringResult {
value_instructions: vec![format!("mul{suffix} src")],
overflow_instructions: vec!["test edx, edx".to_string(), "setne al".to_string()],
condition_code: "ne",
},
_ => OverflowLoweringResult {
value_instructions: vec![],
overflow_instructions: vec![],
condition_code: "",
},
}
}
pub fn lower_frame_address(depth: u32, is_64bit: bool) -> Vec<String> {
let fp = if is_64bit { "rbp" } else { "ebp" };
let mut instructions = Vec::new();
if depth == 0 {
instructions.push(format!("mov rax, {fp} ; frame address depth 0"));
} else {
instructions.push(format!(
"mov rax, [{fp}] ; frame address depth {depth}, follow chain"
));
for _ in 1..depth {
instructions.push("mov rax, [rax]".to_string());
}
}
instructions
}
pub fn lower_return_address(depth: u32, is_64bit: bool) -> Vec<String> {
let fp = if is_64bit { "rbp" } else { "ebp" };
let offset = if is_64bit { 8 } else { 4 };
let mut instructions = Vec::new();
if depth == 0 {
instructions.push(format!(
"mov rax, [{fp} + {offset}] ; return address depth 0"
));
} else {
instructions.push(format!("mov rax, [{fp}] ; return address depth {depth}"));
for _ in 1..depth {
instructions.push("mov rax, [rax]".to_string());
}
instructions.push(format!("mov rax, [rax + {offset}]"));
}
instructions
}
pub fn expand_rotate(width: u32, is_left: bool) -> Vec<String> {
let mask = width - 1;
let width_imm = width;
if is_left {
vec![
format!("; expand rotl i{width} to shifts"),
format!("mov ecx, amount"),
format!("and ecx, {mask}"), format!("mov edx, eax"),
format!("shl eax, cl"), format!("neg cl"), format!("add cl, {width_imm}"), format!("shr edx, cl"), format!("or eax, edx"), ]
} else {
vec![
format!("; expand rotr i{width} to shifts"),
format!("mov ecx, amount"),
format!("and ecx, {mask}"), format!("mov edx, eax"),
format!("shr eax, cl"), format!("neg cl"),
format!("add cl, {width_imm}"), format!("shl edx, cl"), format!("or eax, edx"), ]
}
}
pub fn lower_bitreverse(width: u32) -> Vec<String> {
if width <= 32 {
vec![
format!("; bitreverse i{width} via byte-swap + nibble-reverse"),
format!("; Step 1: reverse bytes"),
format!("bswap eax"),
format!("; Step 2: reverse bits within each byte via SWAR"),
format!("mov edx, eax"),
format!("shr eax, 1"),
format!("and eax, 0x55555555"),
format!("and edx, 0x55555555"),
format!("shl edx, 1"),
format!("or eax, edx"), format!("mov edx, eax"),
format!("shr eax, 2"),
format!("and eax, 0x33333333"),
format!("and edx, 0x33333333"),
format!("shl edx, 2"),
format!("or eax, edx"), format!("mov edx, eax"),
format!("shr eax, 4"),
format!("and eax, 0x0F0F0F0F"),
format!("and edx, 0x0F0F0F0F"),
format!("shl edx, 4"),
format!("or eax, edx"), ]
} else {
vec![
format!("; bitreverse i64 via bswap + nibble swap per byte"),
format!("bswap rax"),
format!("mov rdx, 0x5555555555555555"),
format!("mov rcx, rax"),
format!("shr rax, 1"),
format!("and rax, rdx"),
format!("and rcx, rdx"),
format!("shl rcx, 1"),
format!("or rax, rcx"),
format!("mov rdx, 0x3333333333333333"),
format!("mov rcx, rax"),
format!("shr rax, 2"),
format!("and rax, rdx"),
format!("and rcx, rdx"),
format!("shl rcx, 2"),
format!("or rax, rcx"),
format!("mov rdx, 0x0F0F0F0F0F0F0F0F"),
format!("mov rcx, rax"),
format!("shr rax, 4"),
format!("and rax, rdx"),
format!("and rcx, rdx"),
format!("shl rcx, 4"),
format!("or rax, rcx"),
]
}
}
pub fn lower_ctlz_fallback(width: u32) -> Vec<String> {
let max = width - 1;
vec![
format!("; ctlz i{width} fallback via BSR"),
format!("bsr ecx, eax"),
format!("mov eax, {max}"),
format!("cmovne eax, ecx"),
format!("neg eax"),
format!("add eax, {max}"),
]
}
pub fn lower_cttz_fallback(width: u32) -> Vec<String> {
vec![
format!("; cttz i{width} fallback via BSF"),
format!("bsf ecx, eax"),
format!("mov eax, {width}"),
format!("cmovne eax, ecx"),
]
}
pub fn lower_ctpop_fallback(width: u32) -> Vec<String> {
if width <= 32 {
vec![
format!("; ctpop i{width} fallback (SWAR)"),
format!("mov edx, eax"),
format!("shr eax, 1"),
format!("and eax, 0x55555555"),
format!("sub edx, eax"), format!("mov eax, edx"),
format!("shr edx, 2"),
format!("and eax, 0x33333333"),
format!("and edx, 0x33333333"),
format!("add eax, edx"), format!("mov edx, eax"),
format!("shr eax, 4"),
format!("add eax, edx"),
format!("and eax, 0x0F0F0F0F"),
format!("imul eax, eax, 0x01010101"),
format!("shr eax, 24"),
]
} else {
vec![
format!("; ctpop i64 fallback (SWAR)"),
format!("mov rdx, rax"),
format!("shr rax, 1"),
format!("mov rcx, 0x5555555555555555"),
format!("and rax, rcx"),
format!("sub rdx, rax"),
format!("mov rax, 0x3333333333333333"),
format!("mov rcx, rdx"),
format!("shr rdx, 2"),
format!("and rcx, rax"),
format!("and rdx, rax"),
format!("add rcx, rdx"),
format!("mov rax, rcx"),
format!("shr rcx, 4"),
format!("add rax, rcx"),
format!("mov rcx, 0x0F0F0F0F0F0F0F0F"),
format!("and rax, rcx"),
format!("mov rcx, 0x0101010101010101"),
format!("imul rax, rcx"),
format!("shr rax, 56"),
]
}
}
#[derive(Debug, Clone)]
pub struct VectorLegalizationInfo {
pub needs_scalarize: bool,
pub needs_widen: bool,
pub needs_split: bool,
pub num_parts: u32,
pub part_type: Option<LegalizeType>,
pub reason: String,
}
pub fn analyze_vector_type(ty: LegalizeType, max_vec_bits: u32) -> VectorLegalizationInfo {
let total_bits = ty.size_bits();
if total_bits > max_vec_bits {
let num_parts = (total_bits + max_vec_bits - 1) / max_vec_bits;
return VectorLegalizationInfo {
needs_scalarize: false,
needs_widen: false,
needs_split: true,
num_parts,
part_type: None,
reason: format!(
"vector ({total_bits} bits) wider than max ({max_vec_bits} bits), split into {num_parts} parts"
),
};
}
if let Some(elem) = ty.element_type() {
if elem == LegalizeType::I1 {
return VectorLegalizationInfo {
needs_scalarize: true,
needs_widen: false,
needs_split: false,
num_parts: 1,
part_type: None,
reason: "mask vector (i1 elements) needs scalarization".to_string(),
};
}
}
if total_bits < 128 && ty.num_elements() < 4 {
return VectorLegalizationInfo {
needs_scalarize: false,
needs_widen: true,
needs_split: false,
num_parts: 1,
part_type: None,
reason: format!("vector ({total_bits} bits) too narrow, widen"),
};
}
VectorLegalizationInfo {
needs_scalarize: false,
needs_widen: false,
needs_split: false,
num_parts: 1,
part_type: None,
reason: "vector is legal".to_string(),
}
}
pub fn get_float_libcall_name(op: LegalizeOp, ty: LegalizeType) -> Option<String> {
let prefix = match ty {
LegalizeType::F16 => "h",
LegalizeType::F32 => "f",
LegalizeType::F64 => "d",
LegalizeType::F80 => "x",
LegalizeType::F128 | LegalizeType::PpcFp128 => "t",
_ => return None,
};
let suffix = match op {
LegalizeOp::FAdd => "add",
LegalizeOp::FSub => "sub",
LegalizeOp::FMul => "mul",
LegalizeOp::FDiv => "div",
LegalizeOp::FCmp => match ty {
LegalizeType::F16 => "cmp2", _ => "cmp",
},
LegalizeOp::FPExt => match (ty, prefix) {
(LegalizeType::F16, _) => "extendhfsf2",
(_, _) => "ext",
},
LegalizeOp::FPTrunc => match ty {
LegalizeType::F16 => "truncsfhf2",
_ => "trunc",
},
LegalizeOp::SIToFP => "si_to_fp",
LegalizeOp::UIToFP => "ui_to_fp",
LegalizeOp::FPToSI => "fp_to_si",
LegalizeOp::FPToUI => "fp_to_ui",
_ => "op",
};
Some(format!("__{prefix}{suffix}3"))
}
pub fn is_known_libcall(name: &str) -> bool {
matches!(
name,
"__divti3"
| "__udivti3"
| "__modti3"
| "__umodti3"
| "__divdi3"
| "__udivdi3"
| "__moddi3"
| "__umoddi3"
| "__mulodi4"
| "__muloti4"
| "__ashlti3"
| "__ashrti3"
| "__lshrti3"
| "__multi3"
| "__addtf3"
| "__subtf3"
| "__multf3"
| "__divtf3"
| "__extendsftf2"
| "__extenddftf2"
| "__trunctfdf2"
| "__trunctfsf2"
| "__fixtfsi"
| "__fixunstfsi"
| "__floatsitf"
| "__floatunsitf"
| "__floatditf"
| "__floatunditf"
| "__adddf3"
| "__subdf3"
| "__muldf3"
| "__divdf3"
| "__addsf3"
| "__subsf3"
| "__mulsf3"
| "__divsf3"
| "__extendsfdf2"
| "__truncdfsf2"
| "__fixdfsi"
| "__fixunsdfsi"
| "__floatsidf"
| "__floatunsidf"
)
}
pub fn make_x86_64_operation_legalizer() -> X86OperationLegalizer {
X86OperationLegalizer::new_x86_64()
}
pub fn make_x86_32_operation_legalizer() -> X86OperationLegalizer {
X86OperationLegalizer::new_x86_32()
}
pub fn make_win64_operation_legalizer() -> X86OperationLegalizer {
X86OperationLegalizer::new_win64()
}
pub fn make_operation_legalizer_with_config(config: X86LegalizerConfig) -> X86OperationLegalizer {
X86OperationLegalizer::new(config)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_legalizer_64() -> X86OperationLegalizer {
X86OperationLegalizer::new_x86_64()
}
fn make_legalizer_32() -> X86OperationLegalizer {
X86OperationLegalizer::new_x86_32()
}
#[test]
fn test_legalizer_creation() {
let leg = make_legalizer_64();
assert!(leg.config.target_mode.is_64bit());
assert_eq!(leg.config.vector_width, 512);
assert_eq!(leg.va_config.num_gpr_save, 6);
}
#[test]
fn test_legalizer_32bit_creation() {
let leg = make_legalizer_32();
assert!(!leg.config.target_mode.is_64bit());
assert!(!leg.config.has_avx);
assert_eq!(leg.va_config.num_gpr_save, 0);
}
#[test]
fn test_legalizer_win64_creation() {
let leg = X86OperationLegalizer::new_win64();
assert_eq!(leg.va_config.num_gpr_save, 4);
assert_eq!(leg.va_config.num_xmm_save, 4);
}
#[test]
fn test_i32_add_is_legal() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_i64_add_is_legal_64bit() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::I64);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_i64_add_needs_expand_32bit() {
let mut leg = make_legalizer_32();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::I64);
assert!(!matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_i128_add_needs_expand() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::I128);
assert!(matches!(result.action, LegalizeAction::Expand));
}
#[test]
fn test_i128_div_needs_libcall() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::SDiv, LegalizeType::I128);
assert!(matches!(result.action, LegalizeAction::Libcall(_)));
if let LegalizeAction::Libcall(name) = &result.action {
assert!(name.contains("divti3"));
}
}
#[test]
fn test_i8_add_needs_promotion() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::I8);
assert!(matches!(
result.action,
LegalizeAction::PromoteTo(LegalizeType::I32)
));
}
#[test]
fn test_i16_add_needs_promotion() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::I16);
assert!(matches!(
result.action,
LegalizeAction::PromoteTo(LegalizeType::I32)
));
}
#[test]
fn test_i1_add_needs_promotion() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::I1);
assert!(matches!(
result.action,
LegalizeAction::PromoteTo(LegalizeType::I32)
));
}
#[test]
fn test_i8_load_is_legal() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Load, LegalizeType::I8);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_i8_store_is_legal() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Store, LegalizeType::I8);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_f32_fadd_is_legal() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::FAdd, LegalizeType::F32);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_f64_fadd_is_legal() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::FAdd, LegalizeType::F64);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_f128_fadd_needs_libcall() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::FAdd, LegalizeType::F128);
assert!(matches!(result.action, LegalizeAction::Libcall(_)));
}
#[test]
fn test_f16_fadd_needs_libcall() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::FAdd, LegalizeType::F16);
assert!(matches!(result.action, LegalizeAction::Libcall(_)));
}
#[test]
fn test_f80_fadd_needs_libcall_if_soft() {
let mut leg = make_legalizer_64();
leg = leg.with_soft_float80(true);
let result = leg.legalize(LegalizeOp::FAdd, LegalizeType::F80);
assert!(matches!(result.action, LegalizeAction::Libcall(_)));
}
#[test]
fn test_f80_fadd_is_legal_with_x87() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::FAdd, LegalizeType::F80);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_rotl_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Rotl, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_rotr_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Rotr, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_ctpop_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Ctpop, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_ctlz_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Ctlz, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_cttz_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Cttz, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_bswap_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Bswap, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_bitreverse_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Bitreverse, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_smulovf_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::SMulOvf, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_umulovf_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::UMulOvf, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_saddovf_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::SAddOvf, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_uaddovf_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::UAddOvf, LegalizeType::I32);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_frameaddr_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::FrameAddr, LegalizeType::Pointer);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_returnaddr_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::ReturnAddr, LegalizeType::Pointer);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_prefetch_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Prefetch, LegalizeType::Void);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_readcyclecounter_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::ReadCycleCounter, LegalizeType::I64);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_vastart_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::VAStart, LegalizeType::Void);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_vaend_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::VAEnd, LegalizeType::Void);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_vacopy_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::VACopy, LegalizeType::Void);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_dynamic_stackalloc_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::DynamicStackAlloc, LegalizeType::Pointer);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_switch_needs_custom_lower() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Switch, LegalizeType::Void);
assert!(matches!(result.action, LegalizeAction::Custom));
}
#[test]
fn test_v4i32_is_legal() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::V4I32);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_v16i32_needs_split_without_avx512() {
let mut leg = make_legalizer_64();
leg = leg.with_vector_width(256);
let result = leg.legalize(LegalizeOp::Add, LegalizeType::V16I32);
assert!(matches!(result.action, LegalizeAction::SplitVector));
}
#[test]
fn test_v16i32_is_legal_with_avx512() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::V16I32);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_v2i32_needs_widen() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::V2I32);
assert!(matches!(result.action, LegalizeAction::WidenVector));
}
#[test]
fn test_v4i1_needs_scalarize_without_avx512() {
let mut leg = make_legalizer_64();
leg.config.has_avx512 = false;
let result = leg.legalize(LegalizeOp::Add, LegalizeType::V4I1);
assert!(matches!(result.action, LegalizeAction::Scalarize));
}
#[test]
fn test_v4i1_is_legal_with_avx512() {
let mut leg = make_legalizer_64();
let result = leg.legalize(LegalizeOp::Add, LegalizeType::V4I1);
assert!(matches!(result.action, LegalizeAction::Legal));
}
#[test]
fn test_vector_element_type() {
assert_eq!(LegalizeType::V4I32.element_type(), Some(LegalizeType::I32));
assert_eq!(LegalizeType::V8F32.element_type(), Some(LegalizeType::F32));
assert_eq!(LegalizeType::I32.element_type(), None);
}
#[test]
fn test_vector_size_bits() {
assert_eq!(LegalizeType::V4I32.size_bits(), 128);
assert_eq!(LegalizeType::V8I16.size_bits(), 128);
assert_eq!(LegalizeType::V16I8.size_bits(), 128);
assert_eq!(LegalizeType::V8F32.size_bits(), 256);
assert_eq!(LegalizeType::V16I32.size_bits(), 512);
}
#[test]
fn test_expand_i128_add() {
let plan = expand_i128_operation(LegalizeOp::Add);
assert_eq!(plan.lo_op, LegalizeOp::Add);
assert_eq!(plan.hi_op, LegalizeOp::Add);
assert!(plan.needs_carry);
assert!(!plan.needs_borrow);
assert!(plan.extra_ops.is_empty());
}
#[test]
fn test_expand_i128_sub() {
let plan = expand_i128_operation(LegalizeOp::Sub);
assert_eq!(plan.lo_op, LegalizeOp::Sub);
assert_eq!(plan.hi_op, LegalizeOp::Sub);
assert!(!plan.needs_carry);
assert!(plan.needs_borrow);
}
#[test]
fn test_expand_i128_and() {
let plan = expand_i128_operation(LegalizeOp::And);
assert_eq!(plan.lo_op, LegalizeOp::And);
assert_eq!(plan.hi_op, LegalizeOp::And);
assert!(!plan.needs_carry);
assert!(!plan.needs_borrow);
assert!(plan.extra_ops.is_empty());
}
#[test]
fn test_expand_i128_shl() {
let plan = expand_i128_operation(LegalizeOp::Shl);
assert_eq!(plan.lo_op, LegalizeOp::Shl);
assert!(plan.needs_carry);
assert!(!plan.extra_ops.is_empty());
}
#[test]
fn test_switch_empty_cases() {
let decision = analyze_switch_for_lowering(&[], 99, &SwitchLoweringConfig::default());
assert_eq!(decision.kind, SwitchLoweringKind::LinearChain);
assert_eq!(decision.num_cases, 0);
}
#[test]
fn test_switch_jump_table() {
let cases: Vec<(i64, u32)> = (0..10).map(|i| (i, i as u32)).collect();
let decision = analyze_switch_for_lowering(&cases, 99, &SwitchLoweringConfig::default());
assert_eq!(decision.kind, SwitchLoweringKind::JumpTable);
}
#[test]
fn test_switch_binary_tree() {
let cases: Vec<(i64, u32)> = (0..8).map(|i| (i * 100, i as u32)).collect();
let decision = analyze_switch_for_lowering(&cases, 99, &SwitchLoweringConfig::default());
assert_eq!(decision.kind, SwitchLoweringKind::BinaryTree);
}
#[test]
fn test_switch_linear_chain() {
let cases: Vec<(i64, u32)> = vec![(1, 10), (2, 20), (3, 30)];
let decision = analyze_switch_for_lowering(&cases, 99, &SwitchLoweringConfig::default());
assert_eq!(decision.kind, SwitchLoweringKind::LinearChain);
}
#[test]
fn test_build_switch_tree() {
let cases: Vec<(i64, u32)> = vec![(1, 10), (5, 20), (10, 30)];
let tree = build_switch_binary_tree(&cases, 99);
match tree {
SwitchBinaryTree::Node(node) => {
assert_eq!(node.pivot, 5);
}
_ => panic!("expected Node"),
}
}
#[test]
fn test_build_jump_table_entries() {
let cases: Vec<(i64, u32)> = vec![(0, 10), (2, 20), (3, 30)];
let entries = build_jump_table_entries(&cases, 99, 0, 3);
assert_eq!(entries, vec![10, 99, 20, 30]);
}
#[test]
fn test_va_start_lowering_sysv64() {
let config = VAArgABIConfig::sysv64();
let result = build_va_start_lowering(&config, "rdi");
assert!(!result.init_gp_offset.is_empty());
assert!(!result.init_fp_offset.is_empty());
assert!(!result.init_overflow.is_empty());
assert!(!result.init_reg_save.is_empty());
}
#[test]
fn test_va_start_lowering_x86_32() {
let config = VAArgABIConfig::x86_32_cdecl();
let result = build_va_start_lowering(&config, "eax");
assert!(result.init_gp_offset.is_empty());
assert!(result.init_fp_offset.is_empty());
assert!(!result.init_overflow.is_empty());
}
#[test]
fn test_va_end_is_noop() {
let result = build_va_end_lowering();
assert_eq!(result.len(), 1);
}
#[test]
fn test_va_copy_lowering() {
let config = VAArgABIConfig::sysv64();
let result = build_va_copy_lowering(&config, "rdi", "rsi");
assert_eq!(result.len(), 3);
}
#[test]
fn test_dynamic_stack_alloc_lowering() {
let result = build_dynamic_stack_alloc_lowering("rax", 16, true);
assert!(!result.instructions.is_empty());
assert!(result.needs_probe);
}
#[test]
fn test_dynamic_stack_alloc_32bit() {
let result = build_dynamic_stack_alloc_lowering("eax", 16, false);
assert!(!result.instructions.is_empty());
}
#[test]
fn test_rdtsc_lowering() {
let result = build_rdtsc_lowering(false, true);
assert!(!result.instructions.is_empty());
}
#[test]
fn test_rdtscp_lowering() {
let result = build_rdtsc_lowering(true, true);
assert!(result.uses_rdtscp);
assert!(!result.instructions.is_empty());
}
#[test]
fn test_saddovf_lowering() {
let result = lower_overflow_op(LegalizeOp::SAddOvf, true);
assert!(!result.value_instructions.is_empty());
assert!(!result.overflow_instructions.is_empty());
assert_eq!(result.condition_code, "o");
}
#[test]
fn test_uaddovf_lowering() {
let result = lower_overflow_op(LegalizeOp::UAddOvf, true);
assert_eq!(result.condition_code, "c");
}
#[test]
fn test_smulovf_lowering() {
let result = lower_overflow_op(LegalizeOp::SMulOvf, true);
assert!(!result.overflow_instructions.is_empty());
}
#[test]
fn test_umulovf_lowering() {
let result = lower_overflow_op(LegalizeOp::UMulOvf, true);
assert!(!result.overflow_instructions.is_empty());
}
#[test]
fn test_frame_address_depth_0() {
let result = lower_frame_address(0, true);
assert!(!result.is_empty());
}
#[test]
fn test_frame_address_depth_2() {
let result = lower_frame_address(2, true);
assert!(result.len() >= 3);
}
#[test]
fn test_return_address_depth_0() {
let result = lower_return_address(0, true);
assert!(!result.is_empty());
}
#[test]
fn test_return_address_depth_3() {
let result = lower_return_address(3, true);
assert!(result.len() >= 4);
}
#[test]
fn test_frame_address_32bit() {
let result = lower_frame_address(0, false);
assert!(!result.is_empty());
}
#[test]
fn test_expand_rotl_32() {
let result = expand_rotate(32, true);
assert!(!result.is_empty());
}
#[test]
fn test_expand_rotr_64() {
let result = expand_rotate(64, false);
assert!(!result.is_empty());
}
#[test]
fn test_bitreverse_32() {
let result = lower_bitreverse(32);
assert!(!result.is_empty());
}
#[test]
fn test_bitreverse_64() {
let result = lower_bitreverse(64);
assert!(!result.is_empty());
}
#[test]
fn test_ctlz_fallback() {
let result = lower_ctlz_fallback(32);
assert!(result.len() >= 5);
}
#[test]
fn test_cttz_fallback() {
let result = lower_cttz_fallback(32);
assert!(result.len() >= 3);
}
#[test]
fn test_ctpop_fallback_32() {
let result = lower_ctpop_fallback(32);
assert!(!result.is_empty());
}
#[test]
fn test_ctpop_fallback_64() {
let result = lower_ctpop_fallback(64);
assert!(!result.is_empty());
}
#[test]
fn test_f128_add_libcall_name() {
let name = get_float_libcall_name(LegalizeOp::FAdd, LegalizeType::F128);
assert_eq!(name, Some("__addtf3".to_string()));
}
#[test]
fn test_f64_mul_libcall_name() {
let name = get_float_libcall_name(LegalizeOp::FMul, LegalizeType::F64);
assert_eq!(name, Some("__muldf3".to_string()));
}
#[test]
fn test_f32_div_libcall_name() {
let name = get_float_libcall_name(LegalizeOp::FDiv, LegalizeType::F32);
assert_eq!(name, Some("__divsf3".to_string()));
}
#[test]
fn test_is_known_libcall() {
assert!(is_known_libcall("__divti3"));
assert!(is_known_libcall("__addtf3"));
assert!(!is_known_libcall("__unknown"));
}
#[test]
fn test_prefetch_hint_values() {
assert_eq!(PrefetchHint::T0.to_x86_prefetch_imm(), 0);
assert_eq!(PrefetchHint::T1.to_x86_prefetch_imm(), 1);
assert_eq!(PrefetchHint::T2.to_x86_prefetch_imm(), 2);
assert_eq!(PrefetchHint::NTA.to_x86_prefetch_imm(), 3);
}
#[test]
fn test_lower_prefetch() {
let result = lower_prefetch("rax", PrefetchHint::T0, false, 0);
assert!(!result.is_empty());
}
#[test]
fn test_lower_prefetch_write() {
let result = lower_prefetch("rax", PrefetchHint::T0, true, 0);
assert!(result[0].contains("prefetchw"));
}
#[test]
fn test_analyze_vector_v16i32_with_512() {
let info = analyze_vector_type(LegalizeType::V16I32, 512);
assert!(!info.needs_split);
assert!(!info.needs_scalarize);
assert!(!info.needs_widen);
}
#[test]
fn test_analyze_vector_v16i32_with_256() {
let info = analyze_vector_type(LegalizeType::V16I32, 256);
assert!(info.needs_split);
assert_eq!(info.num_parts, 2);
}
#[test]
fn test_analyze_vector_v2i32_needs_widen() {
let info = analyze_vector_type(LegalizeType::V2I32, 512);
assert!(info.needs_widen);
}
#[test]
fn test_analyze_vector_mask_needs_scalarize() {
let info = analyze_vector_type(LegalizeType::V4I1, 256);
assert!(info.needs_scalarize);
}
#[test]
fn test_config_with_vector_width() {
let mut leg = make_legalizer_64();
leg = leg.with_vector_width(128);
assert_eq!(leg.max_vector_bits(), 128);
}
#[test]
fn test_config_with_soft_float() {
let mut leg = make_legalizer_64();
leg = leg.with_soft_float(true);
assert!(leg.soft_float);
let result = leg.legalize(LegalizeOp::FAdd, LegalizeType::F128);
assert!(matches!(result.action, LegalizeAction::Libcall(_)));
}
#[test]
fn test_config_with_va_config() {
let mut leg = make_legalizer_64();
leg = leg.with_va_config(VAArgABIConfig::win64());
assert_eq!(leg.va_config.num_gpr_save, 4);
}
#[test]
fn test_stats_recording() {
let mut stats = X86LegalizerStats::new();
stats.record_expand();
stats.record_promote();
stats.record_libcall();
stats.record_custom();
stats.record_scalarize();
stats.record_widen();
stats.record_split();
assert_eq!(stats.total(), 7);
}
#[test]
fn test_stats_clone() {
let mut leg = make_legalizer_64();
leg.legalize(LegalizeOp::Add, LegalizeType::I128);
leg.legalize(LegalizeOp::Add, LegalizeType::I8);
let stats = leg.take_stats();
assert!(stats.total() > 0);
assert_eq!(leg.stats.total(), 0);
}
#[test]
fn test_cache_hit() {
let mut leg = make_legalizer_64();
leg.legalize(LegalizeOp::Add, LegalizeType::I32);
assert_eq!(leg.stats.cache_misses, 1);
assert_eq!(leg.stats.cache_hits, 0);
leg.legalize(LegalizeOp::Add, LegalizeType::I32);
assert_eq!(leg.stats.cache_misses, 1);
assert_eq!(leg.stats.cache_hits, 1);
}
#[test]
fn test_cache_clear() {
let mut leg = make_legalizer_64();
leg.legalize(LegalizeOp::Add, LegalizeType::I32);
leg.clear_cache();
assert_eq!(leg.stats.cache_hits, 0);
leg.legalize(LegalizeOp::Add, LegalizeType::I32);
assert_eq!(leg.stats.cache_misses, 2);
}
#[test]
fn test_is_legal() {
let mut leg = make_legalizer_64();
assert!(leg.is_legal(LegalizeOp::Add, LegalizeType::I32));
assert!(!leg.is_legal(LegalizeOp::Add, LegalizeType::I128));
}
#[test]
fn test_factory_functions() {
let leg64 = make_x86_64_operation_legalizer();
assert!(leg64.config.target_mode.is_64bit());
let leg32 = make_x86_32_operation_legalizer();
assert!(!leg32.config.target_mode.is_64bit());
let leg_win = make_win64_operation_legalizer();
assert_eq!(leg_win.va_config.num_gpr_save, 4);
}
#[test]
fn test_custom_lowering_table_is_complete() {
let table = get_custom_lowering_table();
assert!(table.len() >= 22);
let has_rotl = table.iter().any(|d| d.op == LegalizeOp::Rotl);
let has_ctpop = table.iter().any(|d| d.op == LegalizeOp::Ctpop);
let has_frameaddr = table.iter().any(|d| d.op == LegalizeOp::FrameAddr);
let has_switch = table.iter().any(|d| d.op == LegalizeOp::Switch);
assert!(has_rotl);
assert!(has_ctpop);
assert!(has_frameaddr);
assert!(has_switch);
}
#[test]
fn test_legalizeop_as_str() {
assert_eq!(LegalizeOp::Add.as_str(), "add");
assert_eq!(LegalizeOp::Rotl.as_str(), "rotl");
assert_eq!(LegalizeOp::SMulOvf.as_str(), "smul_overflow");
}
#[test]
fn test_legalizeop_classifiers() {
assert!(LegalizeOp::Add.is_integer_binary());
assert!(!LegalizeOp::FAdd.is_integer_binary());
assert!(LegalizeOp::And.is_bitwise());
assert!(LegalizeOp::Shl.is_shift());
assert!(LegalizeOp::Rotl.is_rotate());
assert!(LegalizeOp::Ctpop.is_bit_count());
assert!(LegalizeOp::Trunc.is_conversion());
assert!(LegalizeOp::SAddOvf.is_overlow());
assert!(LegalizeOp::VAStart.is_va_arg());
}
#[test]
fn test_type_utility_methods() {
assert!(LegalizeType::I32.is_scalar_int());
assert!(!LegalizeType::F32.is_scalar_int());
assert!(LegalizeType::F64.is_scalar_fp());
assert!(!LegalizeType::I64.is_scalar_fp());
assert!(LegalizeType::V4I32.is_vector());
assert!(!LegalizeType::I32.is_vector());
assert_eq!(LegalizeType::V4I32.num_elements(), 4);
assert_eq!(LegalizeType::V8I16.num_elements(), 8);
assert_eq!(LegalizeType::I32.num_elements(), 1);
}
#[test]
fn test_legalization_matrix_smoke() {
let mut leg = make_legalizer_64();
let ops = vec![
LegalizeOp::Add,
LegalizeOp::Sub,
LegalizeOp::Mul,
LegalizeOp::And,
LegalizeOp::Or,
LegalizeOp::Xor,
LegalizeOp::Shl,
LegalizeOp::LShr,
LegalizeOp::AShr,
];
let types = vec![
LegalizeType::I8,
LegalizeType::I16,
LegalizeType::I32,
LegalizeType::I64,
LegalizeType::I128,
];
for &op in &ops {
for &ty in &types {
let result = leg.legalize(op, ty);
match ty {
LegalizeType::I8 | LegalizeType::I16 => {
assert!(matches!(
result.action,
LegalizeAction::PromoteTo(LegalizeType::I32)
));
}
LegalizeType::I32 | LegalizeType::I64 => {
assert!(matches!(result.action, LegalizeAction::Legal));
}
LegalizeType::I128 => {
assert!(matches!(result.action, LegalizeAction::Expand));
}
_ => {}
}
}
}
}
#[test]
fn test_switch_binary_tree_leaf_cases() {
let cases: Vec<(i64, u32)> = vec![(1, 100)];
let tree = build_switch_binary_tree(&cases, 99);
assert!(matches!(tree, SwitchBinaryTree::Leaf(100)));
}
#[test]
fn test_switch_binary_tree_empty() {
let cases: Vec<(i64, u32)> = vec![];
let tree = build_switch_binary_tree(&cases, 99);
assert!(matches!(tree, SwitchBinaryTree::Leaf(99)));
}
#[test]
fn test_jump_table_entry_bounds() {
let cases: Vec<(i64, u32)> = vec![(10, 200), (11, 201), (12, 202)];
let entries = build_jump_table_entries(&cases, 99, 10, 12);
assert_eq!(entries.len(), 3);
assert_eq!(entries[0], 200);
assert_eq!(entries[1], 201);
assert_eq!(entries[2], 202);
}
#[test]
fn test_jump_table_gaps() {
let cases: Vec<(i64, u32)> = vec![(10, 200), (12, 202)];
let entries = build_jump_table_entries(&cases, 99, 10, 12);
assert_eq!(entries.len(), 3);
assert_eq!(entries[0], 200);
assert_eq!(entries[1], 99); assert_eq!(entries[2], 202);
}
#[test]
fn test_overflow_lowering_sadd_32() {
let result = lower_overflow_op(LegalizeOp::SAddOvf, false);
assert_eq!(result.condition_code, "o");
}
#[test]
fn test_overflow_lowering_ssub_32() {
let result = lower_overflow_op(LegalizeOp::SSubOvf, false);
assert_eq!(result.condition_code, "o");
}
#[test]
fn test_legacyop_enum_coverage() {
let all_ops = [
LegalizeOp::Add,
LegalizeOp::Sub,
LegalizeOp::Mul,
LegalizeOp::SDiv,
LegalizeOp::UDiv,
LegalizeOp::SRem,
LegalizeOp::URem,
LegalizeOp::FAdd,
LegalizeOp::FSub,
LegalizeOp::FMul,
LegalizeOp::FDiv,
LegalizeOp::FRem,
LegalizeOp::And,
LegalizeOp::Or,
LegalizeOp::Xor,
LegalizeOp::Shl,
LegalizeOp::LShr,
LegalizeOp::AShr,
LegalizeOp::Rotl,
LegalizeOp::Rotr,
LegalizeOp::Ctpop,
LegalizeOp::Ctlz,
LegalizeOp::Cttz,
LegalizeOp::Bswap,
LegalizeOp::Bitreverse,
LegalizeOp::Trunc,
LegalizeOp::ZExt,
LegalizeOp::SExt,
LegalizeOp::FPTrunc,
LegalizeOp::FPExt,
LegalizeOp::FPToUI,
LegalizeOp::FPToSI,
LegalizeOp::UIToFP,
LegalizeOp::SIToFP,
LegalizeOp::PtrToInt,
LegalizeOp::IntToPtr,
LegalizeOp::BitCast,
LegalizeOp::ICmp,
LegalizeOp::FCmp,
LegalizeOp::Br,
LegalizeOp::BrCond,
LegalizeOp::Switch,
LegalizeOp::Ret,
LegalizeOp::Call,
LegalizeOp::Invoke,
LegalizeOp::Load,
LegalizeOp::Store,
LegalizeOp::Alloca,
LegalizeOp::ExtractValue,
LegalizeOp::InsertValue,
LegalizeOp::ExtractElement,
LegalizeOp::InsertElement,
LegalizeOp::ShuffleVector,
LegalizeOp::BuildVector,
LegalizeOp::SAddOvf,
LegalizeOp::UAddOvf,
LegalizeOp::SSubOvf,
LegalizeOp::USubOvf,
LegalizeOp::SMulOvf,
LegalizeOp::UMulOvf,
LegalizeOp::VAStart,
LegalizeOp::VAEnd,
LegalizeOp::VACopy,
LegalizeOp::FrameAddr,
LegalizeOp::ReturnAddr,
LegalizeOp::Select,
LegalizeOp::Phi,
LegalizeOp::GetElementPtr,
LegalizeOp::DynamicStackAlloc,
LegalizeOp::Prefetch,
LegalizeOp::ReadCycleCounter,
LegalizeOp::Fence,
LegalizeOp::Freeze,
];
for op in all_ops {
let s = op.as_str();
assert!(!s.is_empty(), "Missing as_str for {op:?}");
}
}
#[test]
fn test_config_32bit_disables_avx_features() {
let config = X86LegalizerConfig {
target_mode: X86TargetMode::X86_32,
..X86LegalizerConfig::default()
};
let leg = X86OperationLegalizer::new(config);
assert!(!leg.config.has_avx512);
}
#[test]
fn test_target_mode_pointer_width() {
assert_eq!(X86TargetMode::X86_64.pointer_width(), 64);
assert_eq!(X86TargetMode::X86_32.pointer_width(), 32);
assert_eq!(X86TargetMode::X32.pointer_width(), 32);
assert_eq!(X86TargetMode::X86_16.pointer_width(), 16);
}
#[test]
fn test_factory_with_config() {
let config = X86LegalizerConfig {
has_popcnt: false,
..X86LegalizerConfig::default()
};
let leg = make_operation_legalizer_with_config(config);
assert!(!leg.config.has_popcnt);
}
}