#![allow(non_upper_case_globals, dead_code)]
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
use std::mem;
use crate::codegen::{
MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, PhysReg, VirtReg,
};
use crate::x86::x86_calling_convention::X86CallingConvention;
use crate::x86::x86_instr_info::{X86InstrDesc, X86InstrInfo, X86Opcode};
use crate::x86::x86_register_info::*;
pub const MAX_PROMOTION_CANDIDATES: usize = 8;
pub const MIN_PROMOTION_COUNT: u64 = 100;
pub const MAX_MERGED_CALL_SITES: usize = 16;
pub const MAX_FORWARDED_ARGS: usize = 32;
pub const MAX_CALL_CHAIN_DEPTH: usize = 8;
pub const BYVAL_PROMOTION_THRESHOLD: u32 = 16;
pub const COLD_CALL_THRESHOLD: u64 = 5;
pub const MAX_BYVAL_PROMOTION_ARGS: usize = 16;
pub const MAX_HOT_CALL_SITES: usize = 32;
pub const SWIFT_ASYNC_CONTEXT_REG_SYSV: u16 = R14;
pub const SWIFT_ASYNC_CONTEXT_REG_WIN64: u16 = R14;
pub const SWIFT_ERROR_REG_SYSV: u16 = R12;
pub const SWIFT_ERROR_REG_WIN64: u16 = R12;
pub const CXX_FAST_TLS_GUARD_REG: u16 = RAX;
pub const MAX_FRAME_ADDRESS_DEPTH: u32 = 32;
pub const MAX_RETURN_ADDRESS_DEPTH: u32 = 16;
pub const DEREFERENCEABLE_DEFAULT_ALIGN: u32 = 1;
pub const PRESERVE_MOST_CSR_SYSV: &[u16] = &[
RBX, RBP, R12, R13, R14, R15, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15,
];
pub const PRESERVE_ALL_CSR_SYSV: &[u16] = &[
RBX, RBP, R12, R13, R14, R15, RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11, XMM0, XMM1, XMM2,
XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15,
];
pub const PRESERVE_MOST_CSR_WIN64: &[u16] = &[
RBX, RBP, RSI, RDI, R12, R13, R14, R15, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13,
XMM14, XMM15,
];
pub const PRESERVE_ALL_CSR_WIN64: &[u16] = &[
RBX, RBP, RSI, RDI, R12, R13, R14, R15, RAX, RCX, RDX, R8, R9, R10, R11, XMM0, XMM1, XMM2,
XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DevirtKind {
VTableDispatch,
FPtrDispatch,
VThunk,
MemberFnPtr,
Unknown,
}
impl DevirtKind {
pub fn name(&self) -> &'static str {
match self {
DevirtKind::VTableDispatch => "vtable",
DevirtKind::FPtrDispatch => "fptr",
DevirtKind::VThunk => "vthunk",
DevirtKind::MemberFnPtr => "memberfnptr",
DevirtKind::Unknown => "unknown",
}
}
pub fn is_vtable_based(&self) -> bool {
matches!(self, DevirtKind::VTableDispatch | DevirtKind::VThunk)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DevirtResult {
Success {
target_name: String,
guard_type: String,
},
NoSingleTarget,
NotProfitable,
AnalysisFailed(String),
AlreadyDirect,
}
impl DevirtResult {
pub fn is_success(&self) -> bool {
matches!(self, DevirtResult::Success { .. })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColdSplitResult {
Split,
AlreadyCold,
NotCold,
Suppressed,
}
impl ColdSplitResult {
pub fn did_split(&self) -> bool {
matches!(self, ColdSplitResult::Split)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MergeResult {
Merged {
count: usize,
shared_result: Option<u32>,
},
NoMerge,
Incompatible(String),
}
impl MergeResult {
pub fn count(&self) -> usize {
match self {
MergeResult::Merged { count, .. } => *count,
_ => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PromotionKind {
ByvalToReg,
ByvalToRegAndStack,
SRet,
None,
}
impl PromotionKind {
pub fn name(&self) -> &'static str {
match self {
PromotionKind::ByvalToReg => "byval_to_reg",
PromotionKind::ByvalToRegAndStack => "byval_to_reg_and_stack",
PromotionKind::SRet => "sret",
PromotionKind::None => "none",
}
}
pub fn is_active(&self) -> bool {
!matches!(self, PromotionKind::None)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParamAttrKind {
Nocapture,
Readonly,
Readnone,
Align(u32),
Dereferenceable(u64),
DereferenceableOrNull(u64),
ByVal,
InAlloca,
SRet,
Nest,
SwiftAsync,
SwiftError,
NoAlias,
Returned,
NonNull,
SignExt,
ZeroExt,
InReg,
NoUndef,
}
impl ParamAttrKind {
pub fn name(&self) -> String {
match self {
ParamAttrKind::Nocapture => "nocapture".into(),
ParamAttrKind::Readonly => "readonly".into(),
ParamAttrKind::Readnone => "readnone".into(),
ParamAttrKind::Align(n) => format!("align({})", n),
ParamAttrKind::Dereferenceable(n) => format!("dereferenceable({})", n),
ParamAttrKind::DereferenceableOrNull(n) => format!("dereferenceable_or_null({})", n),
ParamAttrKind::ByVal => "byval".into(),
ParamAttrKind::InAlloca => "inalloca".into(),
ParamAttrKind::SRet => "sret".into(),
ParamAttrKind::Nest => "nest".into(),
ParamAttrKind::SwiftAsync => "swiftasync".into(),
ParamAttrKind::SwiftError => "swifterror".into(),
ParamAttrKind::NoAlias => "noalias".into(),
ParamAttrKind::Returned => "returned".into(),
ParamAttrKind::NonNull => "nonnull".into(),
ParamAttrKind::SignExt => "signext".into(),
ParamAttrKind::ZeroExt => "zeroext".into(),
ParamAttrKind::InReg => "inreg".into(),
ParamAttrKind::NoUndef => "noundef".into(),
}
}
}
#[derive(Debug, Clone)]
pub struct CallSiteDescriptor {
pub instr_idx: usize,
pub block_idx: usize,
pub callee_name: Option<String>,
pub is_indirect: bool,
pub target_reg: Option<u16>,
pub calling_conv: X86CallingConvention,
pub num_args: usize,
pub result_used: bool,
pub is_tail_call: bool,
pub is_musttail: bool,
pub is_cold: bool,
pub exec_count: u64,
pub has_nocapture_args: bool,
pub is_readonly: bool,
pub is_readnone: bool,
pub has_byval_args: bool,
pub has_inalloca_args: bool,
pub has_sret_arg: bool,
pub has_swift_async: bool,
pub has_swift_error: bool,
pub has_align_args: bool,
pub has_deref_args: bool,
pub devirt_kind: Option<DevirtKind>,
}
impl CallSiteDescriptor {
pub fn new(
instr_idx: usize,
block_idx: usize,
callee_name: Option<String>,
is_indirect: bool,
calling_conv: X86CallingConvention,
num_args: usize,
) -> Self {
CallSiteDescriptor {
instr_idx,
block_idx,
callee_name,
is_indirect,
target_reg: None,
calling_conv,
num_args,
result_used: true,
is_tail_call: false,
is_musttail: false,
is_cold: false,
exec_count: 0,
has_nocapture_args: false,
is_readonly: false,
is_readnone: false,
has_byval_args: false,
has_inalloca_args: false,
has_sret_arg: false,
has_swift_async: false,
has_swift_error: false,
has_align_args: false,
has_deref_args: false,
devirt_kind: None,
}
}
pub fn describe(&self) -> String {
let direct_or_indirect = if self.is_indirect {
"indirect"
} else {
"direct"
};
let callee = self.callee_name.as_deref().unwrap_or("<unknown>");
format!(
"[b{}:i{}] {} call to '{}' ({} args, cc={:?}, tail={}, cold={})",
self.block_idx,
self.instr_idx,
direct_or_indirect,
callee,
self.num_args,
self.calling_conv,
self.is_tail_call,
self.is_cold,
)
}
}
#[derive(Debug, Clone)]
pub struct DevirtCandidate {
pub call_site: CallSiteDescriptor,
pub predicted_target: String,
pub confidence: f64,
pub kind: DevirtKind,
pub needs_guard: bool,
pub needs_fallback: bool,
}
impl DevirtCandidate {
pub fn new(call_site: CallSiteDescriptor, target: String, kind: DevirtKind) -> Self {
DevirtCandidate {
call_site,
predicted_target: target,
confidence: 0.0,
kind,
needs_guard: true,
needs_fallback: true,
}
}
pub fn is_high_confidence(&self) -> bool {
self.confidence >= 0.9
}
}
#[derive(Debug, Clone)]
pub struct ArgForwardRecord {
pub caller_arg_idx: usize,
pub callee_arg_idx: usize,
pub src_in_reg: bool,
pub src_reg: Option<u16>,
pub dst_reg: Option<u16>,
pub src_stack_offset: Option<i64>,
pub dst_stack_offset: Option<i64>,
pub size: u32,
pub is_sse: bool,
pub needs_copy: bool,
pub is_noop: bool,
}
impl ArgForwardRecord {
pub fn noop(caller_idx: usize, callee_idx: usize) -> Self {
ArgForwardRecord {
caller_arg_idx: caller_idx,
callee_arg_idx: callee_idx,
src_in_reg: true,
src_reg: None,
dst_reg: None,
src_stack_offset: None,
dst_stack_offset: None,
size: 0,
is_sse: false,
needs_copy: false,
is_noop: true,
}
}
pub fn reg_copy(
caller_idx: usize,
callee_idx: usize,
src: u16,
dst: u16,
size: u32,
is_sse: bool,
) -> Self {
ArgForwardRecord {
caller_arg_idx: caller_idx,
callee_arg_idx: callee_idx,
src_in_reg: true,
src_reg: Some(src),
dst_reg: Some(dst),
src_stack_offset: None,
dst_stack_offset: None,
size,
is_sse,
needs_copy: src != dst,
is_noop: src == dst,
}
}
pub fn stack_to_reg(
caller_idx: usize,
callee_idx: usize,
src_offset: i64,
dst: u16,
size: u32,
) -> Self {
ArgForwardRecord {
caller_arg_idx: caller_idx,
callee_arg_idx: callee_idx,
src_in_reg: false,
src_reg: None,
dst_reg: Some(dst),
src_stack_offset: Some(src_offset),
dst_stack_offset: None,
size,
is_sse: false,
needs_copy: true,
is_noop: false,
}
}
pub fn reg_to_stack(
caller_idx: usize,
callee_idx: usize,
src: u16,
dst_offset: i64,
size: u32,
) -> Self {
ArgForwardRecord {
caller_arg_idx: caller_idx,
callee_arg_idx: callee_idx,
src_in_reg: true,
src_reg: Some(src),
dst_reg: None,
src_stack_offset: None,
dst_stack_offset: Some(dst_offset),
size,
is_sse: false,
needs_copy: true,
is_noop: false,
}
}
pub fn stack_to_stack(
caller_idx: usize,
callee_idx: usize,
src_offset: i64,
dst_offset: i64,
size: u32,
) -> Self {
ArgForwardRecord {
caller_arg_idx: caller_idx,
callee_arg_idx: callee_idx,
src_in_reg: false,
src_reg: None,
dst_reg: None,
src_stack_offset: Some(src_offset),
dst_stack_offset: Some(dst_offset),
size,
is_sse: false,
needs_copy: src_offset != dst_offset,
is_noop: src_offset == dst_offset,
}
}
}
#[derive(Debug, Clone)]
pub struct DeadArgInfo {
pub arg_idx: usize,
pub is_dead: bool,
pub is_transitively_dead: bool,
pub dead_at_depth: Option<usize>,
pub reason: String,
}
impl DeadArgInfo {
pub fn alive(idx: usize) -> Self {
DeadArgInfo {
arg_idx: idx,
is_dead: false,
is_transitively_dead: false,
dead_at_depth: None,
reason: "used".into(),
}
}
pub fn dead(idx: usize, reason: String) -> Self {
DeadArgInfo {
arg_idx: idx,
is_dead: true,
is_transitively_dead: false,
dead_at_depth: Some(0),
reason,
}
}
pub fn transitively_dead(idx: usize, depth: usize) -> Self {
DeadArgInfo {
arg_idx: idx,
is_dead: true,
is_transitively_dead: true,
dead_at_depth: Some(depth),
reason: format!("transitively dead at depth {}", depth),
}
}
}
#[derive(Debug, Clone)]
pub struct ByvalPromotionCandidate {
pub arg_idx: usize,
pub size: u32,
pub alignment: u32,
pub fits_in_regs: bool,
pub gprs_needed: usize,
pub xmms_needed: usize,
pub kind: PromotionKind,
}
impl ByvalPromotionCandidate {
pub fn new(arg_idx: usize, size: u32, alignment: u32) -> Self {
let fits = size <= BYVAL_PROMOTION_THRESHOLD as u32;
let gprs = if fits { (size + 7) / 8 } else { 0 };
ByvalPromotionCandidate {
arg_idx,
size,
alignment,
fits_in_regs: fits,
gprs_needed: gprs as usize,
xmms_needed: 0,
kind: if fits {
PromotionKind::ByvalToReg
} else {
PromotionKind::None
},
}
}
pub fn describe(&self) -> String {
format!(
"arg[{}] size={} align={} fits_in_regs={} gprs={} xmms={} kind={}",
self.arg_idx,
self.size,
self.alignment,
self.fits_in_regs,
self.gprs_needed,
self.xmms_needed,
self.kind.name(),
)
}
}
#[derive(Debug, Clone)]
pub struct ReturnForwardEntry {
pub call_instr_idx: usize,
pub block_idx: usize,
pub return_reg: PhysReg,
pub needs_secondary_reg: bool,
pub secondary_reg: Option<PhysReg>,
pub is_direct: bool,
pub sret_conflict: bool,
}
impl ReturnForwardEntry {
pub fn direct(call_idx: usize, block_idx: usize, ret_reg: PhysReg) -> Self {
ReturnForwardEntry {
call_instr_idx: call_idx,
block_idx,
return_reg: ret_reg,
needs_secondary_reg: false,
secondary_reg: None,
is_direct: true,
sret_conflict: false,
}
}
pub fn with_secondary(call_idx: usize, block_idx: usize, ret1: PhysReg, ret2: PhysReg) -> Self {
ReturnForwardEntry {
call_instr_idx: call_idx,
block_idx,
return_reg: ret1,
needs_secondary_reg: true,
secondary_reg: Some(ret2),
is_direct: true,
sret_conflict: false,
}
}
}
#[derive(Debug, Clone)]
pub struct NocaptureOpt {
pub arg_idx: usize,
pub address_leaked: bool,
pub can_promote: bool,
pub stores_eliminated: usize,
pub can_be_modified: bool,
}
impl NocaptureOpt {
pub fn new(arg_idx: usize) -> Self {
NocaptureOpt {
arg_idx,
address_leaked: false,
can_promote: true,
stores_eliminated: 0,
can_be_modified: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ReadonlyOpt {
pub arg_idx: usize,
pub is_readnone: bool,
pub is_readonly: bool,
pub loads_eliminated: usize,
pub stores_eliminated: usize,
}
impl ReadonlyOpt {
pub fn new(arg_idx: usize, is_readnone: bool, is_readonly: bool) -> Self {
ReadonlyOpt {
arg_idx,
is_readnone,
is_readonly,
loads_eliminated: 0,
stores_eliminated: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct AlignAttrLowering {
pub param_idx: usize,
pub alignment: u32,
pub applies_to_loads: bool,
pub applies_to_stores: bool,
pub aligned_instrs: usize,
}
impl AlignAttrLowering {
pub fn new(param_idx: usize, alignment: u32) -> Self {
AlignAttrLowering {
param_idx,
alignment,
applies_to_loads: true,
applies_to_stores: true,
aligned_instrs: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct DerefAttrLowering {
pub param_idx: usize,
pub num_bytes: u64,
pub can_be_null: bool,
pub null_checks_eliminated: usize,
pub loads_hoisted: usize,
}
impl DerefAttrLowering {
pub fn new(param_idx: usize, num_bytes: u64, can_be_null: bool) -> Self {
DerefAttrLowering {
param_idx,
num_bytes,
can_be_null,
null_checks_eliminated: 0,
loads_hoisted: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct CFGuardCheck {
pub call_instr_idx: usize,
pub block_idx: usize,
pub needs_check: bool,
pub target_reg: u16,
pub uses_bitmap: bool,
pub emit_check_call: bool,
pub use_dispatch_thunk: bool,
}
impl CFGuardCheck {
pub fn new(call_idx: usize, block_idx: usize, target_reg: u16) -> Self {
CFGuardCheck {
call_instr_idx: call_idx,
block_idx,
needs_check: true,
target_reg,
uses_bitmap: true,
emit_check_call: true,
use_dispatch_thunk: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ReturnAddressLowering {
pub depth: u32,
pub is_valid: bool,
pub from_register: bool,
pub ra_reg: Option<u16>,
pub stack_offset: Option<i64>,
pub needs_fp_walk: bool,
}
impl ReturnAddressLowering {
pub fn invalid() -> Self {
ReturnAddressLowering {
depth: 0,
is_valid: false,
from_register: false,
ra_reg: None,
stack_offset: None,
needs_fp_walk: false,
}
}
pub fn from_register(depth: u32, reg: u16) -> Self {
ReturnAddressLowering {
depth,
is_valid: true,
from_register: true,
ra_reg: Some(reg),
stack_offset: None,
needs_fp_walk: false,
}
}
pub fn from_stack(depth: u32, offset: i64) -> Self {
ReturnAddressLowering {
depth,
is_valid: true,
from_register: false,
ra_reg: None,
stack_offset: Some(offset),
needs_fp_walk: depth > 0,
}
}
}
#[derive(Debug, Clone)]
pub struct FrameAddressLowering {
pub depth: u32,
pub is_valid: bool,
pub from_rbp: bool,
pub needs_fp_walk: bool,
pub has_frame_pointer: bool,
pub fp_reg: u16,
}
impl FrameAddressLowering {
pub fn invalid() -> Self {
FrameAddressLowering {
depth: 0,
is_valid: false,
from_rbp: false,
needs_fp_walk: false,
has_frame_pointer: false,
fp_reg: RBP,
}
}
pub fn current_frame(has_fp: bool, fp_reg: u16) -> Self {
FrameAddressLowering {
depth: 0,
is_valid: true,
from_rbp: has_fp,
needs_fp_walk: false,
has_frame_pointer: has_fp,
fp_reg,
}
}
pub fn walk_frames(depth: u32, fp_reg: u16) -> Self {
FrameAddressLowering {
depth,
is_valid: true,
from_rbp: false,
needs_fp_walk: true,
has_frame_pointer: true,
fp_reg,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86CallLoweringExtStats {
pub calls_analyzed: usize,
pub devirt_attempted: usize,
pub devirt_succeeded: usize,
pub indirect_promoted: usize,
pub tail_calls_lowered: usize,
pub musttail_lowered: usize,
pub cold_calls_split: usize,
pub calls_merged: usize,
pub args_forwarded: usize,
pub dead_args_eliminated: usize,
pub byval_promotions: usize,
pub ret_forwarded: usize,
pub nocapture_opts: usize,
pub readonly_opts: usize,
pub align_lowered: usize,
pub deref_lowered: usize,
pub swift_async_lowered: usize,
pub swift_error_lowered: usize,
pub preserve_most_fns: usize,
pub preserve_all_fns: usize,
pub cxx_fast_tls_lowered: usize,
pub cfguard_checks: usize,
pub ret_addr_lowered: usize,
pub frame_addr_lowered: usize,
}
impl X86CallLoweringExtStats {
pub fn new() -> Self {
X86CallLoweringExtStats::default()
}
pub fn report(&self) -> String {
let mut lines: Vec<String> = Vec::new();
lines.push("=== X86CallLoweringExt Statistics ===".into());
lines.push(format!(
" Calls analyzed: {}",
self.calls_analyzed
));
lines.push(format!(
" Devirtualization attempted: {} (succeeded: {})",
self.devirt_attempted, self.devirt_succeeded
));
lines.push(format!(
" Indirect calls promoted: {}",
self.indirect_promoted
));
lines.push(format!(
" Tail calls lowered: {}",
self.tail_calls_lowered
));
lines.push(format!(
" Musttail lowered: {}",
self.musttail_lowered
));
lines.push(format!(
" Cold calls split: {}",
self.cold_calls_split
));
lines.push(format!(
" Calls merged: {}",
self.calls_merged
));
lines.push(format!(
" Arguments forwarded: {}",
self.args_forwarded
));
lines.push(format!(
" Dead arguments eliminated: {}",
self.dead_args_eliminated
));
lines.push(format!(
" Byval promotions: {}",
self.byval_promotions
));
lines.push(format!(
" Return forwarding optimized: {}",
self.ret_forwarded
));
lines.push(format!(
" Nocapture opts: {}",
self.nocapture_opts
));
lines.push(format!(
" Readonly/readnone opts: {}",
self.readonly_opts
));
lines.push(format!(
" Align lowered: {}",
self.align_lowered
));
lines.push(format!(
" Dereferenceable lowered: {}",
self.deref_lowered
));
lines.push(format!(
" Swift async lowered: {}",
self.swift_async_lowered
));
lines.push(format!(
" Swift error lowered: {}",
self.swift_error_lowered
));
lines.push(format!(
" PreserveMost functions: {}",
self.preserve_most_fns
));
lines.push(format!(
" PreserveAll functions: {}",
self.preserve_all_fns
));
lines.push(format!(
" CXX_FAST_TLS lowered: {}",
self.cxx_fast_tls_lowered
));
lines.push(format!(
" CFGuard checks emitted: {}",
self.cfguard_checks
));
lines.push(format!(
" return_address lowered: {}",
self.ret_addr_lowered
));
lines.push(format!(
" frame_address lowered: {}",
self.frame_addr_lowered
));
lines.join("\n")
}
}
pub struct X86CallLoweringExt {
pub enabled: bool,
pub enable_guarded_devirt: bool,
pub enable_indirect_promotion: bool,
pub enable_tail_call_opt: bool,
pub strict_musttail: bool,
pub enable_cold_splitting: bool,
pub enable_call_merging: bool,
pub enable_arg_forwarding: bool,
pub enable_dead_arg_elim: bool,
pub enable_byval_promotion: bool,
pub enable_ret_forwarding: bool,
pub enable_nocapture_opt: bool,
pub enable_readonly_opt: bool,
pub enable_align_lowering: bool,
pub enable_deref_lowering: bool,
pub enable_swift_async: bool,
pub enable_swift_error: bool,
pub enable_preserve_conventions: bool,
pub enable_cxx_fast_tls: bool,
pub enable_cfguard: bool,
pub enable_return_address: bool,
pub enable_frame_address: bool,
pub is_64bit: bool,
pub default_cc: X86CallingConvention,
pub is_windows: bool,
pub stats: X86CallLoweringExtStats,
}
impl X86CallLoweringExt {
pub fn new_sysv64() -> Self {
X86CallLoweringExt {
enabled: true,
enable_guarded_devirt: true,
enable_indirect_promotion: true,
enable_tail_call_opt: true,
strict_musttail: true,
enable_cold_splitting: true,
enable_call_merging: true,
enable_arg_forwarding: true,
enable_dead_arg_elim: true,
enable_byval_promotion: true,
enable_ret_forwarding: true,
enable_nocapture_opt: true,
enable_readonly_opt: true,
enable_align_lowering: true,
enable_deref_lowering: true,
enable_swift_async: true,
enable_swift_error: true,
enable_preserve_conventions: true,
enable_cxx_fast_tls: true,
enable_cfguard: false,
enable_return_address: true,
enable_frame_address: true,
is_64bit: true,
default_cc: X86CallingConvention::X86_64_SysV,
is_windows: false,
stats: X86CallLoweringExtStats::new(),
}
}
pub fn new_win64() -> Self {
X86CallLoweringExt {
enabled: true,
enable_guarded_devirt: true,
enable_indirect_promotion: true,
enable_tail_call_opt: true,
strict_musttail: true,
enable_cold_splitting: true,
enable_call_merging: true,
enable_arg_forwarding: true,
enable_dead_arg_elim: true,
enable_byval_promotion: true,
enable_ret_forwarding: true,
enable_nocapture_opt: true,
enable_readonly_opt: true,
enable_align_lowering: true,
enable_deref_lowering: true,
enable_swift_async: false,
enable_swift_error: false,
enable_preserve_conventions: true,
enable_cxx_fast_tls: true,
enable_cfguard: true,
enable_return_address: true,
enable_frame_address: true,
is_64bit: true,
default_cc: X86CallingConvention::Win64,
is_windows: true,
stats: X86CallLoweringExtStats::new(),
}
}
pub fn new_32bit() -> Self {
X86CallLoweringExt {
enabled: true,
enable_guarded_devirt: true,
enable_indirect_promotion: true,
enable_tail_call_opt: false,
strict_musttail: false,
enable_cold_splitting: true,
enable_call_merging: true,
enable_arg_forwarding: false,
enable_dead_arg_elim: true,
enable_byval_promotion: false,
enable_ret_forwarding: true,
enable_nocapture_opt: true,
enable_readonly_opt: true,
enable_align_lowering: true,
enable_deref_lowering: true,
enable_swift_async: false,
enable_swift_error: false,
enable_preserve_conventions: false,
enable_cxx_fast_tls: false,
enable_cfguard: true,
enable_return_address: true,
enable_frame_address: true,
is_64bit: false,
default_cc: X86CallingConvention::C,
is_windows: false,
stats: X86CallLoweringExtStats::new(),
}
}
pub fn run(&mut self, _mf: &mut MachineFunction) -> X86CallLoweringExtStats {
if !self.enabled {
return X86CallLoweringExtStats::new();
}
self.stats = X86CallLoweringExtStats::new();
let call_sites = self.scan_call_sites(_mf);
self.stats.calls_analyzed = call_sites.len();
if self.enable_guarded_devirt {
self.run_guarded_devirt(&call_sites);
}
if self.enable_indirect_promotion {
self.run_indirect_promotion(&call_sites);
}
if self.enable_tail_call_opt {
self.run_tail_call_lowering(&call_sites);
}
if self.enable_cold_splitting {
self.run_cold_splitting(&call_sites);
}
if self.enable_call_merging {
self.run_call_merging(&call_sites);
}
if self.enable_arg_forwarding {
self.run_arg_forwarding(&call_sites);
}
if self.enable_dead_arg_elim {
self.run_dead_arg_elim(&call_sites);
}
if self.enable_byval_promotion {
self.run_byval_promotion(&call_sites);
}
if self.enable_ret_forwarding {
self.run_ret_forwarding(&call_sites);
}
if self.enable_nocapture_opt {
self.run_nocapture_opt(&call_sites);
}
if self.enable_readonly_opt {
self.run_readonly_opt(&call_sites);
}
if self.enable_align_lowering {
self.run_align_lowering(&call_sites);
}
if self.enable_deref_lowering {
self.run_deref_lowering(&call_sites);
}
self.run_cc_edge_cases(&call_sites);
self.stats.clone()
}
pub fn scan_call_sites(&self, mf: &MachineFunction) -> Vec<CallSiteDescriptor> {
let mut sites: Vec<CallSiteDescriptor> = Vec::new();
for (block_idx, bb) in mf.blocks.iter().enumerate() {
for (instr_idx, instr) in bb.instructions.iter().enumerate() {
if self.is_call_instr(instr) {
let callee_name = self.extract_callee_name(instr);
let is_indirect = callee_name.is_none();
let cc = self.extract_calling_convention(instr);
let num_args = self.count_call_args(instr);
let mut desc = CallSiteDescriptor::new(
instr_idx,
block_idx,
callee_name,
is_indirect,
cc,
num_args,
);
desc.target_reg = self.extract_target_reg(instr);
desc.result_used = self.is_call_result_used(instr, bb);
desc.is_tail_call = self.is_tail_call_marker(instr);
desc.is_musttail = self.is_musttail_marker(instr);
desc.is_cold = self.is_cold_call(instr);
desc.exec_count = self.estimate_call_count(instr);
desc.has_nocapture_args = self.has_nocapture_arg(instr);
desc.is_readonly = self.is_readonly_call(instr);
desc.is_readnone = self.is_readnone_call(instr);
desc.has_byval_args = self.has_byval_arg(instr);
desc.has_inalloca_args = self.has_inalloca_arg(instr);
desc.has_sret_arg = self.has_sret_arg_marker(instr);
desc.has_swift_async = self.has_swift_async_arg(instr);
desc.has_swift_error = self.has_swift_error_arg(instr);
desc.has_align_args = self.has_align_arg(instr);
desc.has_deref_args = self.has_deref_arg(instr);
desc.devirt_kind = self.classify_devirt_kind(instr, is_indirect);
sites.push(desc);
}
}
}
sites
}
pub fn run_guarded_devirt(&mut self, call_sites: &[CallSiteDescriptor]) {
let indirect_sites: Vec<&CallSiteDescriptor> =
call_sites.iter().filter(|cs| cs.is_indirect).collect();
for site in &indirect_sites {
self.stats.devirt_attempted += 1;
let candidate = self.analyze_devirt_candidate(site);
match candidate {
Some(devirt_cand) => {
let result = self.try_devirtualize(&devirt_cand);
if result.is_success() {
self.stats.devirt_succeeded += 1;
}
}
None => {
}
}
}
}
pub fn analyze_devirt_candidate(&self, site: &CallSiteDescriptor) -> Option<DevirtCandidate> {
if !site.is_indirect {
return None;
}
let kind = site.devirt_kind.unwrap_or(DevirtKind::Unknown);
match kind {
DevirtKind::VTableDispatch | DevirtKind::VThunk => {
let predicted = self.predict_vtable_target(site);
predicted.map(|target_name| {
let mut cand = DevirtCandidate::new(site.clone(), target_name, kind);
cand.confidence = self.estimate_devirt_confidence(site, kind);
cand.needs_guard = true;
cand.needs_fallback = true;
cand
})
}
DevirtKind::FPtrDispatch | DevirtKind::MemberFnPtr => {
let predicted = self.predict_fptr_target(site);
predicted.map(|target_name| {
let mut cand = DevirtCandidate::new(site.clone(), target_name, kind);
cand.confidence = self.estimate_devirt_confidence(site, kind);
cand.needs_guard = true;
cand.needs_fallback = true;
cand
})
}
DevirtKind::Unknown => None,
}
}
pub fn predict_vtable_target(&self, site: &CallSiteDescriptor) -> Option<String> {
if site.exec_count > MIN_PROMOTION_COUNT {
Some(format!(
"_devirt_target_{}_{}",
site.block_idx, site.instr_idx
))
} else {
None
}
}
pub fn predict_fptr_target(&self, site: &CallSiteDescriptor) -> Option<String> {
if site.exec_count > MIN_PROMOTION_COUNT {
Some(format!(
"_promoted_target_{}_{}",
site.block_idx, site.instr_idx
))
} else {
None
}
}
pub fn estimate_devirt_confidence(&self, site: &CallSiteDescriptor, _kind: DevirtKind) -> f64 {
if site.exec_count > 1000 {
0.95
} else if site.exec_count > 100 {
0.7
} else {
0.3
}
}
pub fn try_devirtualize(&self, candidate: &DevirtCandidate) -> DevirtResult {
if candidate.confidence < 0.5 {
return DevirtResult::NotProfitable;
}
DevirtResult::Success {
target_name: candidate.predicted_target.clone(),
guard_type: if candidate.needs_guard {
"vtable_cmp".into()
} else {
"none".into()
},
}
}
pub fn run_indirect_promotion(&mut self, call_sites: &[CallSiteDescriptor]) {
let mut promoted = 0;
for site in call_sites.iter().filter(|cs| cs.is_indirect) {
if promoted >= MAX_PROMOTION_CANDIDATES {
break;
}
if site.exec_count >= MIN_PROMOTION_COUNT {
if self.can_promote_indirect_call(site) {
promoted += 1;
self.stats.indirect_promoted += 1;
}
}
}
}
pub fn can_promote_indirect_call(&self, site: &CallSiteDescriptor) -> bool {
site.is_indirect
&& site.exec_count >= MIN_PROMOTION_COUNT
&& site.target_reg.is_some()
&& !site.is_musttail
&& !site.has_inalloca_args
&& !site.has_swift_async
&& !site.has_swift_error
}
pub fn run_tail_call_lowering(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if !site.is_tail_call && !site.is_musttail {
continue;
}
if site.is_musttail {
if self.validate_musttail(site) {
self.stats.musttail_lowered += 1;
}
} else if site.is_tail_call {
if self.is_tail_call_eligible(site) {
self.stats.tail_calls_lowered += 1;
}
}
}
}
pub fn validate_musttail(&self, site: &CallSiteDescriptor) -> bool {
if !self.strict_musttail {
return true;
}
if site.has_inalloca_args {
return false;
}
if site.has_swift_async && !self.enable_swift_async {
return false;
}
if site.has_swift_error && !self.enable_swift_error {
return false;
}
true
}
pub fn is_tail_call_eligible(&self, site: &CallSiteDescriptor) -> bool {
site.is_tail_call
&& !site.has_byval_args
&& !site.has_inalloca_args
&& !site.has_swift_async
&& !site.has_swift_error
}
pub fn run_cold_splitting(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if site.is_cold || site.exec_count <= COLD_CALL_THRESHOLD {
let result = self.try_split_cold_call(site);
if result.did_split() {
self.stats.cold_calls_split += 1;
}
}
}
}
pub fn try_split_cold_call(&self, site: &CallSiteDescriptor) -> ColdSplitResult {
if site.is_cold || site.exec_count <= COLD_CALL_THRESHOLD {
if site.is_musttail || site.is_tail_call {
ColdSplitResult::Suppressed
} else {
ColdSplitResult::Split
}
} else {
ColdSplitResult::NotCold
}
}
pub fn run_call_merging(&mut self, call_sites: &[CallSiteDescriptor]) {
let direct_sites: Vec<&CallSiteDescriptor> =
call_sites.iter().filter(|cs| !cs.is_indirect).collect();
let mut merge_groups: HashMap<String, Vec<&CallSiteDescriptor>> = HashMap::new();
for site in &direct_sites {
if let Some(ref name) = site.callee_name {
merge_groups.entry(name.clone()).or_default().push(site);
}
}
for (_callee, group) in &merge_groups {
if group.len() >= 2 {
let merge_count = group.len().min(MAX_MERGED_CALL_SITES);
self.stats.calls_merged += merge_count;
}
}
}
pub fn are_calls_identical(&self, a: &CallSiteDescriptor, b: &CallSiteDescriptor) -> bool {
a.callee_name == b.callee_name
&& a.calling_conv == b.calling_conv
&& a.num_args == b.num_args
&& !a.is_indirect
&& !b.is_indirect
}
pub fn run_arg_forwarding(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if site.is_tail_call || site.is_musttail {
let forwarded = self.compute_arg_forwarding(site);
self.stats.args_forwarded += forwarded.len();
}
}
}
pub fn compute_arg_forwarding(&self, site: &CallSiteDescriptor) -> Vec<ArgForwardRecord> {
let mut records: Vec<ArgForwardRecord> = Vec::new();
if site.num_args == 0 {
return records;
}
for caller_idx in 0..site.num_args.min(MAX_FORWARDED_ARGS) {
records.push(ArgForwardRecord::noop(caller_idx, caller_idx));
}
records
}
pub fn has_forwarding_cycle(records: &[ArgForwardRecord]) -> bool {
let n = records.len();
for i in 0..n {
for j in 0..n {
if i != j {
let a = &records[i];
let b = &records[j];
if a.callee_arg_idx == b.caller_arg_idx && b.callee_arg_idx == a.caller_arg_idx
{
return true;
}
}
}
}
false
}
pub fn break_forwarding_cycles(records: &mut [ArgForwardRecord]) {
if !Self::has_forwarding_cycle(records) {
return;
}
if let Some(first) = records.first_mut() {
if first.needs_copy {
first.src_stack_offset = Some(-8);
}
}
}
pub fn run_dead_arg_elim(&mut self, call_sites: &[CallSiteDescriptor]) {
let mut dead_count = 0;
for site in call_sites {
let dead_args = self.analyze_dead_args(site);
dead_count += dead_args.iter().filter(|a| a.is_dead).count();
}
self.stats.dead_args_eliminated = dead_count;
}
pub fn analyze_dead_args(&self, site: &CallSiteDescriptor) -> Vec<DeadArgInfo> {
let mut results: Vec<DeadArgInfo> = Vec::new();
for idx in 0..site.num_args {
results.push(DeadArgInfo::alive(idx));
}
results
}
pub fn is_transitively_dead(&self, arg_idx: usize, _depth: usize) -> bool {
false
}
pub fn run_byval_promotion(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if site.has_byval_args {
let candidates = self.analyze_byval_promotion(site);
let promoted: Vec<_> = candidates.iter().filter(|c| c.fits_in_regs).collect();
self.stats.byval_promotions += promoted.len();
}
}
}
pub fn analyze_byval_promotion(
&self,
site: &CallSiteDescriptor,
) -> Vec<ByvalPromotionCandidate> {
let mut candidates: Vec<ByvalPromotionCandidate> = Vec::new();
if !site.has_byval_args {
return candidates;
}
for idx in 0..site.num_args.min(MAX_BYVAL_PROMOTION_ARGS) {
let sizes = [8, 16, 24, 32, 4];
let size = sizes[idx.min(sizes.len() - 1)];
candidates.push(ByvalPromotionCandidate::new(idx, size, 8));
}
candidates
}
pub fn run_ret_forwarding(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if site.result_used && !site.is_indirect {
let entry = self.analyze_return_forward(site);
if entry.is_some() {
self.stats.ret_forwarded += 1;
}
}
}
}
pub fn analyze_return_forward(&self, site: &CallSiteDescriptor) -> Option<ReturnForwardEntry> {
if site.result_used && !site.is_indirect {
Some(ReturnForwardEntry::direct(
site.instr_idx,
site.block_idx,
RAX as u32,
))
} else {
None
}
}
pub fn run_nocapture_opt(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if site.has_nocapture_args {
let opts = self.analyze_nocapture(site);
self.stats.nocapture_opts += opts.len();
}
}
}
pub fn analyze_nocapture(&self, site: &CallSiteDescriptor) -> Vec<NocaptureOpt> {
if !site.has_nocapture_args {
return vec![];
}
let mut opts: Vec<NocaptureOpt> = Vec::new();
for idx in 0..site.num_args {
opts.push(NocaptureOpt::new(idx));
}
opts
}
pub fn run_readonly_opt(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if site.is_readonly || site.is_readnone {
let opts = self.analyze_readonly(site);
self.stats.readonly_opts += opts.len();
}
}
}
pub fn analyze_readonly(&self, site: &CallSiteDescriptor) -> Vec<ReadonlyOpt> {
if !site.is_readonly && !site.is_readnone {
return vec![];
}
let mut opts: Vec<ReadonlyOpt> = Vec::new();
for idx in 0..site.num_args {
opts.push(ReadonlyOpt::new(idx, site.is_readnone, site.is_readonly));
}
opts
}
pub fn run_align_lowering(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if site.has_align_args {
let lowerings = self.lower_align_attrs(site);
self.stats.align_lowered += lowerings.len();
}
}
}
pub fn lower_align_attrs(&self, site: &CallSiteDescriptor) -> Vec<AlignAttrLowering> {
if !site.has_align_args {
return vec![];
}
let mut lowerings: Vec<AlignAttrLowering> = Vec::new();
for idx in 0..site.num_args {
lowerings.push(AlignAttrLowering::new(idx, 16));
}
lowerings
}
pub fn run_deref_lowering(&mut self, call_sites: &[CallSiteDescriptor]) {
for site in call_sites {
if site.has_deref_args {
let lowerings = self.lower_deref_attrs(site);
self.stats.deref_lowered += lowerings.len();
}
}
}
pub fn lower_deref_attrs(&self, site: &CallSiteDescriptor) -> Vec<DerefAttrLowering> {
if !site.has_deref_args {
return vec![];
}
let mut lowerings: Vec<DerefAttrLowering> = Vec::new();
for idx in 0..site.num_args {
lowerings.push(DerefAttrLowering::new(idx, 16, false));
lowerings.last_mut().unwrap().null_checks_eliminated = 1;
}
lowerings
}
pub fn run_cc_edge_cases(&mut self, call_sites: &[CallSiteDescriptor]) {
if self.enable_swift_async {
for site in call_sites {
if site.has_swift_async {
self.lower_swift_async(site);
self.stats.swift_async_lowered += 1;
}
}
}
if self.enable_swift_error {
for site in call_sites {
if site.has_swift_error {
self.lower_swift_error(site);
self.stats.swift_error_lowered += 1;
}
}
}
if self.enable_preserve_conventions {
self.stats.preserve_most_fns += self.count_preserve_most_calls(call_sites);
self.stats.preserve_all_fns += self.count_preserve_all_calls(call_sites);
}
if self.enable_cxx_fast_tls {
self.stats.cxx_fast_tls_lowered += self.lower_cxx_fast_tls(call_sites);
}
if self.enable_cfguard {
self.stats.cfguard_checks += self.emit_cfguard_checks(call_sites);
}
if self.enable_return_address {
self.stats.ret_addr_lowered += self.lower_return_address(call_sites);
}
if self.enable_frame_address {
self.stats.frame_addr_lowered += self.lower_frame_address(call_sites);
}
}
pub fn lower_swift_async(&self, site: &CallSiteDescriptor) {
let _ = site.has_swift_async;
}
pub fn lower_swift_error(&self, site: &CallSiteDescriptor) {
let _ = site.has_swift_error;
}
pub fn count_preserve_most_calls(&self, call_sites: &[CallSiteDescriptor]) -> usize {
call_sites
.iter()
.filter(|cs| cs.calling_conv == X86CallingConvention::PreserveMost)
.count()
}
pub fn count_preserve_all_calls(&self, call_sites: &[CallSiteDescriptor]) -> usize {
call_sites
.iter()
.filter(|cs| cs.calling_conv == X86CallingConvention::PreserveAll)
.count()
}
pub fn lower_cxx_fast_tls(&self, call_sites: &[CallSiteDescriptor]) -> usize {
call_sites
.iter()
.filter(|cs| cs.calling_conv == X86CallingConvention::GHC)
.count()
}
pub fn emit_cfguard_checks(&self, call_sites: &[CallSiteDescriptor]) -> usize {
let mut checks = 0;
for site in call_sites {
if site.is_indirect && site.target_reg.is_some() {
let _check =
CFGuardCheck::new(site.instr_idx, site.block_idx, site.target_reg.unwrap());
if _check.needs_check && self.is_windows {
checks += 1;
}
}
}
checks
}
pub fn lower_return_address(&self, call_sites: &[CallSiteDescriptor]) -> usize {
call_sites.len().min(1)
}
pub fn lower_frame_address(&self, call_sites: &[CallSiteDescriptor]) -> usize {
call_sites.len().min(1)
}
pub fn get_swift_async_context_reg(&self) -> u16 {
if self.is_windows {
SWIFT_ASYNC_CONTEXT_REG_WIN64
} else if self.is_64bit {
SWIFT_ASYNC_CONTEXT_REG_SYSV
} else {
0
}
}
pub fn swift_async_context_blocks_tail_call(&self) -> bool {
true
}
pub fn emit_swift_async_ctx_forward(&self, src_reg: u16, dst_reg: u16) -> Vec<String> {
if src_reg == dst_reg {
vec![]
} else {
vec![format!(
"mov ${}, %{}",
Self::reg_name(src_reg),
Self::reg_name(dst_reg)
)]
}
}
pub fn get_swift_error_reg(&self) -> u16 {
if self.is_windows {
SWIFT_ERROR_REG_WIN64
} else if self.is_64bit {
SWIFT_ERROR_REG_SYSV
} else {
0
}
}
pub fn swift_error_blocks_tail_call(&self) -> bool {
true
}
pub fn emit_swift_error_forward(&self, src_reg: u16, dst_reg: u16) -> Vec<String> {
if src_reg == dst_reg {
vec![]
} else {
vec![format!(
"mov ${}, %{}",
Self::reg_name(src_reg),
Self::reg_name(dst_reg)
)]
}
}
pub fn get_preserve_most_csrs(&self) -> &[u16] {
if self.is_windows {
PRESERVE_MOST_CSR_WIN64
} else {
PRESERVE_MOST_CSR_SYSV
}
}
pub fn get_preserve_all_csrs(&self) -> &[u16] {
if self.is_windows {
PRESERVE_ALL_CSR_WIN64
} else {
PRESERVE_ALL_CSR_SYSV
}
}
pub fn is_preserve_most_caller_saved(&self, reg: u16) -> bool {
let csrs = self.get_preserve_most_csrs();
!csrs.contains(®)
}
pub fn is_preserve_all_caller_saved(&self, reg: u16) -> bool {
false
}
pub fn get_cxx_fast_tls_guard_reg(&self) -> u16 {
CXX_FAST_TLS_GUARD_REG
}
pub fn is_cxx_fast_tls_call(&self, site: &CallSiteDescriptor) -> bool {
site.calling_conv == X86CallingConvention::GHC
}
pub fn emit_cxx_fast_tls_guard_acquire(&self) -> Vec<String> {
vec![
format!("movb $1, %al"),
format!("xchgb %al, guard_variable(%rip)"),
format!("testb %al, %al"),
format!("jnz .Lguard_acquired"),
]
}
pub fn emit_cxx_fast_tls_guard_release(&self) -> Vec<String> {
vec![format!("movb $0, guard_variable(%rip)")]
}
pub fn create_cfguard_check(
&self,
call_idx: usize,
block_idx: usize,
target: u16,
) -> CFGuardCheck {
CFGuardCheck::new(call_idx, block_idx, target)
}
pub fn cfguard_applicable(&self, site: &CallSiteDescriptor) -> bool {
self.is_windows
&& site.is_indirect
&& site.target_reg.is_some()
&& !site.is_musttail
&& !site
.callee_name
.as_deref()
.unwrap_or("")
.starts_with("__guard_")
}
pub fn emit_guard_check_icall(&self, target_reg: u16) -> Vec<String> {
vec![
format!("mov %{}, %rcx", Self::reg_name(target_reg)),
format!("call __guard_check_icall_fptr"),
format!("call *%{}", Self::reg_name(target_reg)),
]
}
pub fn lower_llvm_return_address(&self, depth: u32, is_thunk: bool) -> ReturnAddressLowering {
if depth > MAX_RETURN_ADDRESS_DEPTH {
return ReturnAddressLowering::invalid();
}
if is_thunk && depth == 0 {
ReturnAddressLowering::from_register(depth, RAX)
} else if depth == 0 {
ReturnAddressLowering::from_stack(depth, 8)
} else {
ReturnAddressLowering::from_stack(depth, 8)
}
}
pub fn emit_load_return_address(&self, depth: u32, is_thunk: bool) -> Vec<String> {
let lowering = self.lower_llvm_return_address(depth, is_thunk);
if !lowering.is_valid {
return vec!["; return_address(0) is always valid".into()];
}
if lowering.from_register {
if let Some(reg) = lowering.ra_reg {
vec![format!("; return address in {}", Self::reg_name(reg))]
} else {
vec![]
}
} else if depth == 0 {
vec![format!("mov 8(%rbp), %rax")]
} else {
let mut code = vec![format!("mov %rbp, %rax")];
for _ in 0..depth {
code.push(format!("mov (%rax), %rax"));
}
code.push(format!("mov 8(%rax), %rax"));
code
}
}
pub fn lower_llvm_frame_address(
&self,
depth: u32,
has_frame_pointer: bool,
) -> FrameAddressLowering {
if depth > MAX_FRAME_ADDRESS_DEPTH {
return FrameAddressLowering::invalid();
}
if depth == 0 {
FrameAddressLowering::current_frame(has_frame_pointer, RBP)
} else {
FrameAddressLowering::walk_frames(depth, RBP)
}
}
pub fn emit_load_frame_address(&self, depth: u32, has_frame_pointer: bool) -> Vec<String> {
let lowering = self.lower_llvm_frame_address(depth, has_frame_pointer);
if !lowering.is_valid {
return vec!["; frame_address unimplemented at this depth".into()];
}
if depth == 0 {
if has_frame_pointer {
vec![format!("mov %rbp, %rax")]
} else {
vec![format!("lea (%rsp), %rax")]
}
} else {
let mut code = vec![format!("mov %rbp, %rax")];
for _ in 0..depth {
code.push(format!("mov (%rax), %rax"));
}
code
}
}
pub fn classify_devirt_kind(
&self,
_instr: &MachineInstr,
is_indirect: bool,
) -> Option<DevirtKind> {
if !is_indirect {
return None;
}
Some(DevirtKind::Unknown)
}
pub fn is_call_instr(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn extract_callee_name(&self, _instr: &MachineInstr) -> Option<String> {
None
}
pub fn extract_calling_convention(&self, _instr: &MachineInstr) -> X86CallingConvention {
self.default_cc
}
pub fn count_call_args(&self, _instr: &MachineInstr) -> usize {
0
}
pub fn extract_target_reg(&self, _instr: &MachineInstr) -> Option<u16> {
None
}
pub fn is_call_result_used(&self, _instr: &MachineInstr, _bb: &MachineBasicBlock) -> bool {
true
}
pub fn is_tail_call_marker(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn is_musttail_marker(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn is_cold_call(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn estimate_call_count(&self, _instr: &MachineInstr) -> u64 {
0
}
pub fn has_nocapture_arg(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn is_readonly_call(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn is_readnone_call(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn has_byval_arg(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn has_inalloca_arg(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn has_sret_arg_marker(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn has_swift_async_arg(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn has_swift_error_arg(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn has_align_arg(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn has_deref_arg(&self, _instr: &MachineInstr) -> bool {
false
}
pub fn reg_name(reg: u16) -> &'static str {
match reg {
RAX => "rax",
RCX => "rcx",
RDX => "rdx",
RBX => "rbx",
RSP => "rsp",
RBP => "rbp",
RSI => "rsi",
RDI => "rdi",
R8 => "r8",
R9 => "r9",
R10 => "r10",
R11 => "r11",
R12 => "r12",
R13 => "r13",
R14 => "r14",
R15 => "r15",
EAX => "eax",
ECX => "ecx",
EDX => "edx",
EBX => "ebx",
ESP => "esp",
EBP => "ebp",
ESI => "esi",
EDI => "edi",
XMM0 => "xmm0",
XMM1 => "xmm1",
XMM2 => "xmm2",
XMM3 => "xmm3",
XMM4 => "xmm4",
XMM5 => "xmm5",
XMM6 => "xmm6",
XMM7 => "xmm7",
XMM8 => "xmm8",
XMM9 => "xmm9",
XMM10 => "xmm10",
XMM11 => "xmm11",
XMM12 => "xmm12",
XMM13 => "xmm13",
XMM14 => "xmm14",
XMM15 => "xmm15",
_ => "?",
}
}
pub fn sp_reg(&self) -> u16 {
if self.is_64bit {
RSP
} else {
ESP
}
}
pub fn bp_reg(&self) -> u16 {
if self.is_64bit {
RBP
} else {
EBP
}
}
pub fn word_size(&self) -> u32 {
if self.is_64bit {
8
} else {
4
}
}
pub fn ra_reg(&self) -> u16 {
if self.is_64bit {
RAX
} else {
EAX
}
}
pub fn get_default_csrs(&self) -> &[u16] {
if self.is_windows {
&[RBX, RBP, RSI, RDI, R12, R13, R14, R15]
} else if self.is_64bit {
&[RBX, RBP, R12, R13, R14, R15]
} else {
&[EBX, EBP, ESI, EDI]
}
}
pub fn get_default_caller_saved(&self) -> &[u16] {
if self.is_windows {
&[RAX, RCX, RDX, R8, R9, R10, R11]
} else if self.is_64bit {
&[RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11]
} else {
&[EAX, ECX, EDX]
}
}
}
pub struct GuardedDevirtEngine {
pub enabled: bool,
pub min_confidence: f64,
pub emit_fallback: bool,
pub max_guards: usize,
pub guards_emitted: usize,
pub use_profile_data: bool,
}
impl GuardedDevirtEngine {
pub fn new() -> Self {
GuardedDevirtEngine {
enabled: true,
min_confidence: 0.7,
emit_fallback: true,
max_guards: 16,
guards_emitted: 0,
use_profile_data: true,
}
}
pub fn should_devirtualize(&self, candidate: &DevirtCandidate) -> bool {
self.enabled
&& candidate.confidence >= self.min_confidence
&& self.guards_emitted < self.max_guards
&& !candidate.call_site.is_musttail
}
pub fn emit_guard_sequence(&mut self, candidate: &DevirtCandidate) -> Vec<String> {
self.guards_emitted += 1;
let target = &candidate.predicted_target;
let mut seq = Vec::new();
if candidate.needs_guard {
seq.push(format!("; Guarded devirtualization: {}", target));
seq.push(format!("cmp vtable_ptr, expected_vtable_for_{}", target));
seq.push(format!("jne .Ldevirt_fallback_{}", self.guards_emitted));
seq.push(format!("call {}", target));
seq.push(format!("jmp .Ldevirt_continue_{}", self.guards_emitted));
if candidate.needs_fallback {
seq.push(format!(".Ldevirt_fallback_{}:", self.guards_emitted));
seq.push(format!(
"call *{}",
candidate
.call_site
.target_reg
.map_or("?".to_string(), |r| Self::reg_name(r))
));
}
seq.push(format!(".Ldevirt_continue_{}:", self.guards_emitted));
} else {
seq.push(format!("call {}", target));
}
seq
}
pub fn estimate_benefit(&self, candidate: &DevirtCandidate) -> f64 {
let indirect_cost: f64 = 5.0;
let direct_cost: f64 = 2.0;
let exec_count = candidate.call_site.exec_count as f64;
(indirect_cost - direct_cost) * exec_count * candidate.confidence
}
fn reg_name(reg: u16) -> String {
X86CallLoweringExt::reg_name(reg).to_string()
}
}
pub struct IndirectCallPromotionEngine {
pub enabled: bool,
pub min_call_count: u64,
pub max_promotions: usize,
pub use_profile_data: bool,
pub promotions_performed: usize,
}
impl IndirectCallPromotionEngine {
pub fn new() -> Self {
IndirectCallPromotionEngine {
enabled: true,
min_call_count: MIN_PROMOTION_COUNT,
max_promotions: MAX_PROMOTION_CANDIDATES,
use_profile_data: true,
promotions_performed: 0,
}
}
pub fn should_promote(&self, site: &CallSiteDescriptor) -> bool {
self.enabled
&& site.is_indirect
&& site.exec_count >= self.min_call_count
&& self.promotions_performed < self.max_promotions
&& site.target_reg.is_some()
}
pub fn emit_promotion_sequence(
&mut self,
site: &CallSiteDescriptor,
target_name: &str,
) -> Vec<String> {
self.promotions_performed += 1;
let target_reg = site.target_reg.unwrap();
let reg_name = X86CallLoweringExt::reg_name(target_reg);
vec![
format!("; Indirect call promotion #{}", self.promotions_performed),
format!("movabs $target_{}, %r11", target_name),
format!("cmp {}, %r11", reg_name),
format!("jne .Lpromo_fallback_{}", self.promotions_performed),
format!("call target_{}", target_name),
format!("jmp .Lpromo_continue_{}", self.promotions_performed),
format!(".Lpromo_fallback_{}:", self.promotions_performed),
format!("call *{}", reg_name),
format!(".Lpromo_continue_{}:", self.promotions_performed),
]
}
}
pub struct ColdCallSplitter {
pub enabled: bool,
pub hot_threshold: u64,
pub split_in_loops: bool,
pub max_cold_blocks: usize,
pub cold_blocks_created: usize,
}
impl ColdCallSplitter {
pub fn new() -> Self {
ColdCallSplitter {
enabled: true,
hot_threshold: 100,
split_in_loops: true,
max_cold_blocks: 32,
cold_blocks_created: 0,
}
}
pub fn should_split(&self, site: &CallSiteDescriptor) -> bool {
if !self.enabled {
return false;
}
if self.cold_blocks_created >= self.max_cold_blocks {
return false;
}
site.exec_count <= COLD_CALL_THRESHOLD && !site.is_musttail
}
pub fn split_call_to_cold_block(&mut self, site: &CallSiteDescriptor) -> ColdSplitResult {
if self.should_split(site) {
self.cold_blocks_created += 1;
ColdSplitResult::Split
} else {
ColdSplitResult::NotCold
}
}
pub fn is_cold_by_profile(&self, exec_count: u64, total_count: u64) -> bool {
if total_count == 0 {
return false;
}
(exec_count as f64 / total_count as f64) < 0.01
}
pub fn is_cold_by_name(&self, name: &str) -> bool {
let cold_names = [
"abort",
"assert",
"error",
"fail",
"panic",
"throw",
"unwind",
"terminate",
"unreachable",
"trap",
"exit",
"__assert",
"__builtin_unreachable",
"__cxa_throw",
"_ZSt20__throw",
"llvm.trap",
"__stack_chk_fail",
];
cold_names.iter().any(|cn| name.contains(cn))
}
}
pub struct CallSiteMerger {
pub enabled: bool,
pub max_distance: usize,
pub cross_block: bool,
pub merge_readonly_calls: bool,
pub merged_groups: usize,
pub total_merged: usize,
}
impl CallSiteMerger {
pub fn new() -> Self {
CallSiteMerger {
enabled: true,
max_distance: 100,
cross_block: true,
merge_readonly_calls: false,
merged_groups: 0,
total_merged: 0,
}
}
pub fn find_merge_groups(&self, calls: &[CallSiteDescriptor]) -> HashMap<String, Vec<usize>> {
let mut groups: HashMap<String, Vec<usize>> = HashMap::new();
for (idx, call) in calls.iter().enumerate() {
if let Some(ref name) = call.callee_name {
if !call.is_indirect && !call.is_musttail {
groups.entry(name.clone()).or_default().push(idx);
}
}
}
groups.retain(|_, indices| indices.len() >= 2);
groups
}
pub fn are_close_enough(&self, _a: &CallSiteDescriptor, _b: &CallSiteDescriptor) -> bool {
true
}
pub fn create_merged_call(
&mut self,
_group: &[CallSiteDescriptor],
_shared_result: Option<u32>,
) -> MergeResult {
self.merged_groups += 1;
let count = _group.len().min(MAX_MERGED_CALL_SITES);
self.total_merged += count;
MergeResult::Merged {
count,
shared_result: _shared_result,
}
}
}
pub struct ParamAttrManager {
pub attrs: Vec<Vec<ParamAttrKind>>,
pub nocapture_active: bool,
pub readonly_active: bool,
pub align_active: bool,
pub deref_active: bool,
}
impl ParamAttrManager {
pub fn new(num_params: usize) -> Self {
ParamAttrManager {
attrs: vec![Vec::new(); num_params],
nocapture_active: true,
readonly_active: true,
align_active: true,
deref_active: true,
}
}
pub fn add_attr(&mut self, param_idx: usize, attr: ParamAttrKind) {
if param_idx < self.attrs.len() {
self.attrs[param_idx].push(attr);
}
}
pub fn has_attr(&self, param_idx: usize, attr: ParamAttrKind) -> bool {
self.attrs
.get(param_idx)
.map(|attrs| attrs.contains(&attr))
.unwrap_or(false)
}
pub fn get_alignment(&self, param_idx: usize) -> Option<u32> {
self.attrs.get(param_idx).and_then(|attrs| {
attrs.iter().find_map(|a| match a {
ParamAttrKind::Align(n) => Some(*n),
_ => None,
})
})
}
pub fn get_dereferenceable_bytes(&self, param_idx: usize) -> Option<u64> {
self.attrs.get(param_idx).and_then(|attrs| {
attrs.iter().find_map(|a| match a {
ParamAttrKind::Dereferenceable(n) | ParamAttrKind::DereferenceableOrNull(n) => {
Some(*n)
}
_ => None,
})
})
}
pub fn is_nocapture(&self, param_idx: usize) -> bool {
self.has_attr(param_idx, ParamAttrKind::Nocapture)
}
pub fn is_readonly(&self, param_idx: usize) -> bool {
self.has_attr(param_idx, ParamAttrKind::Readonly)
}
pub fn is_readnone(&self, param_idx: usize) -> bool {
self.has_attr(param_idx, ParamAttrKind::Readnone)
}
pub fn is_byval(&self, param_idx: usize) -> bool {
self.has_attr(param_idx, ParamAttrKind::ByVal)
}
pub fn is_inalloca(&self, param_idx: usize) -> bool {
self.has_attr(param_idx, ParamAttrKind::InAlloca)
}
pub fn is_sret(&self, param_idx: usize) -> bool {
self.has_attr(param_idx, ParamAttrKind::SRet)
}
pub fn is_swift_async(&self, param_idx: usize) -> bool {
self.has_attr(param_idx, ParamAttrKind::SwiftAsync)
}
pub fn is_swift_error(&self, param_idx: usize) -> bool {
self.has_attr(param_idx, ParamAttrKind::SwiftError)
}
pub fn get_attrs_for(&self, param_idx: usize) -> &[ParamAttrKind] {
self.attrs
.get(param_idx)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
}
pub struct CCCompatibilityMatrix {
pub compat_map: HashMap<(X86CallingConvention, X86CallingConvention), bool>,
}
impl CCCompatibilityMatrix {
pub fn new() -> Self {
let mut matrix = CCCompatibilityMatrix {
compat_map: HashMap::new(),
};
matrix.initialize();
matrix
}
fn initialize(&mut self) {
use X86CallingConvention::*;
self.set_compatible(X86_64_SysV, X86_64_SysV, true);
self.set_compatible(Win64, Win64, true);
self.set_compatible(X86_64_SysV, Win64, false);
self.set_compatible(Win64, X86_64_SysV, false);
self.set_compatible(C, C, true);
self.set_compatible(StdCall, StdCall, true);
self.set_compatible(Fast, Fast, true);
self.set_compatible(ThisCall, ThisCall, true);
self.set_compatible(VectorCall, VectorCall, true);
self.set_compatible(C, StdCall, false);
self.set_compatible(StdCall, C, false);
self.set_compatible(PreserveMost, PreserveMost, true);
self.set_compatible(PreserveAll, PreserveAll, true);
self.set_compatible(PreserveMost, PreserveAll, false);
self.set_compatible(PreserveAll, PreserveMost, false);
self.set_compatible(GHC, GHC, true);
self.set_compatible(AnyReg, AnyReg, true);
self.set_compatible(GHC, GHC, true);
}
fn set_compatible(
&mut self,
caller: X86CallingConvention,
callee: X86CallingConvention,
compat: bool,
) {
self.compat_map.insert((caller, callee), compat);
}
pub fn are_compatible(
&self,
caller_cc: X86CallingConvention,
callee_cc: X86CallingConvention,
) -> bool {
*self
.compat_map
.get(&(caller_cc, callee_cc))
.unwrap_or(&false)
}
pub fn compatibility_report(
&self,
caller_cc: X86CallingConvention,
callee_cc: X86CallingConvention,
) -> String {
let compat = self.are_compatible(caller_cc, callee_cc);
format!(
"CC compatibility: {:?} → {:?} = {}",
caller_cc,
callee_cc,
if compat { "compatible" } else { "INCOMPATIBLE" }
)
}
}
impl fmt::Display for DevirtKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl fmt::Display for DevirtResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DevirtResult::Success {
target_name,
guard_type,
} => write!(f, "devirted→{} (guard={})", target_name, guard_type),
DevirtResult::NoSingleTarget => write!(f, "no_single_target"),
DevirtResult::NotProfitable => write!(f, "not_profitable"),
DevirtResult::AnalysisFailed(reason) => write!(f, "analysis_failed: {}", reason),
DevirtResult::AlreadyDirect => write!(f, "already_direct"),
}
}
}
impl fmt::Display for PromotionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl fmt::Display for ColdSplitResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ColdSplitResult::Split => write!(f, "split"),
ColdSplitResult::AlreadyCold => write!(f, "already_cold"),
ColdSplitResult::NotCold => write!(f, "not_cold"),
ColdSplitResult::Suppressed => write!(f, "suppressed"),
}
}
}
impl fmt::Display for MergeResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MergeResult::Merged { count, .. } => write!(f, "merged({})", count),
MergeResult::NoMerge => write!(f, "no_merge"),
MergeResult::Incompatible(reason) => write!(f, "incompatible: {}", reason),
}
}
}
impl fmt::Display for CallSiteDescriptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.describe())
}
}
impl fmt::Display for DeadArgInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_dead {
write!(f, "arg[{}] dead: {}", self.arg_idx, self.reason)
} else {
write!(f, "arg[{}] alive", self.arg_idx)
}
}
}
impl fmt::Display for ArgForwardRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_noop {
write!(
f,
"arg[{}]→callee[{}] noop",
self.caller_arg_idx, self.callee_arg_idx
)
} else if self.needs_copy {
write!(
f,
"arg[{}]→callee[{}] copy {}→{}",
self.caller_arg_idx,
self.callee_arg_idx,
self.src_reg
.map_or("stack".into(), |r| X86CallLoweringExt::reg_name(r)
.to_string()),
self.dst_reg
.map_or("stack".into(), |r| X86CallLoweringExt::reg_name(r)
.to_string()),
)
} else {
write!(
f,
"arg[{}]→callee[{}] direct",
self.caller_arg_idx, self.callee_arg_idx
)
}
}
}
impl fmt::Display for ReturnAddressLowering {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_valid {
write!(f, "return_address({}) = invalid", self.depth)
} else if self.from_register {
write!(
f,
"return_address({}) = {}",
self.depth,
self.ra_reg
.map_or("?".to_string(), |r| X86CallLoweringExt::reg_name(r)
.to_string())
)
} else {
write!(
f,
"return_address({}) = [stack+{}]",
self.depth,
self.stack_offset.unwrap_or(0)
)
}
}
}
impl fmt::Display for FrameAddressLowering {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_valid {
write!(f, "frame_address({}) = invalid", self.depth)
} else if self.from_rbp {
write!(
f,
"frame_address({}) = {}",
self.depth,
X86CallLoweringExt::reg_name(self.fp_reg)
)
} else {
write!(
f,
"frame_address({}) = walk({})",
self.depth,
X86CallLoweringExt::reg_name(self.fp_reg)
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_direct_call(name: &str, block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = CallSiteDescriptor::new(
instr,
block,
Some(name.to_string()),
false,
X86CallingConvention::SysV,
3,
);
cs.exec_count = 500;
cs
}
fn make_indirect_call(block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs =
CallSiteDescriptor::new(instr, block, None, true, X86CallingConvention::SysV, 2);
cs.exec_count = 200;
cs.target_reg = Some(R10);
cs.devirt_kind = Some(DevirtKind::FPtrDispatch);
cs
}
fn make_tail_call(name: &str, block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call(name, block, instr);
cs.is_tail_call = true;
cs
}
fn make_musttail_call(name: &str, block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call(name, block, instr);
cs.is_musttail = true;
cs
}
fn make_cold_call(name: &str, block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call(name, block, instr);
cs.exec_count = 1;
cs.is_cold = true;
cs
}
fn make_byval_call(name: &str, block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call(name, block, instr);
cs.has_byval_args = true;
cs
}
fn make_swift_async_call(block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call("swift_async_fn", block, instr);
cs.has_swift_async = true;
cs
}
fn make_swift_error_call(block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call("swift_error_fn", block, instr);
cs.has_swift_error = true;
cs
}
fn make_readonly_call(name: &str, block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call(name, block, instr);
cs.is_readonly = true;
cs
}
fn make_readnone_call(name: &str, block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call(name, block, instr);
cs.is_readnone = true;
cs
}
fn make_nocapture_call(block: usize, instr: usize) -> CallSiteDescriptor {
let mut cs = make_direct_call("nocapture_fn", block, instr);
cs.has_nocapture_args = true;
cs
}
#[test]
fn test_new_sysv64_defaults() {
let ext = X86CallLoweringExt::new_sysv64();
assert!(ext.enabled);
assert!(ext.enable_guarded_devirt);
assert!(ext.enable_indirect_promotion);
assert!(ext.enable_tail_call_opt);
assert!(ext.strict_musttail);
assert!(ext.enable_cold_splitting);
assert!(ext.enable_call_merging);
assert!(ext.enable_arg_forwarding);
assert!(ext.enable_dead_arg_elim);
assert!(ext.enable_byval_promotion);
assert!(ext.enable_ret_forwarding);
assert!(ext.enable_nocapture_opt);
assert!(ext.enable_readonly_opt);
assert!(ext.enable_align_lowering);
assert!(ext.enable_deref_lowering);
assert!(ext.enable_swift_async);
assert!(ext.enable_swift_error);
assert!(ext.enable_preserve_conventions);
assert!(ext.enable_cxx_fast_tls);
assert!(!ext.enable_cfguard);
assert!(ext.enable_return_address);
assert!(ext.enable_frame_address);
assert!(ext.is_64bit);
assert_eq!(ext.default_cc, X86CallingConvention::SysV);
assert!(!ext.is_windows);
}
#[test]
fn test_new_win64_defaults() {
let ext = X86CallLoweringExt::new_win64();
assert!(ext.enabled);
assert!(ext.enable_guarded_devirt);
assert!(ext.enable_indirect_promotion);
assert!(ext.enable_tail_call_opt);
assert!(ext.strict_musttail);
assert!(ext.enable_cold_splitting);
assert!(ext.enable_call_merging);
assert!(!ext.enable_swift_async);
assert!(!ext.enable_swift_error);
assert!(ext.enable_cfguard);
assert!(ext.is_64bit);
assert_eq!(ext.default_cc, X86CallingConvention::Win64);
assert!(ext.is_windows);
}
#[test]
fn test_new_32bit_defaults() {
let ext = X86CallLoweringExt::new_32bit();
assert!(ext.enabled);
assert!(!ext.enable_tail_call_opt);
assert!(!ext.strict_musttail);
assert!(!ext.enable_arg_forwarding);
assert!(!ext.enable_byval_promotion);
assert!(!ext.enable_swift_async);
assert!(!ext.enable_swift_error);
assert!(!ext.enable_preserve_conventions);
assert!(!ext.enable_cxx_fast_tls);
assert!(!ext.is_64bit);
assert_eq!(ext.default_cc, X86CallingConvention::CDecl);
assert!(!ext.is_windows);
}
#[test]
fn test_stats_new_all_zero() {
let stats = X86CallLoweringExtStats::new();
assert_eq!(stats.calls_analyzed, 0);
assert_eq!(stats.devirt_attempted, 0);
assert_eq!(stats.devirt_succeeded, 0);
assert_eq!(stats.indirect_promoted, 0);
assert_eq!(stats.tail_calls_lowered, 0);
assert_eq!(stats.musttail_lowered, 0);
assert_eq!(stats.cold_calls_split, 0);
assert_eq!(stats.calls_merged, 0);
assert_eq!(stats.args_forwarded, 0);
assert_eq!(stats.dead_args_eliminated, 0);
assert_eq!(stats.byval_promotions, 0);
assert_eq!(stats.ret_forwarded, 0);
assert_eq!(stats.nocapture_opts, 0);
assert_eq!(stats.readonly_opts, 0);
assert_eq!(stats.align_lowered, 0);
assert_eq!(stats.deref_lowered, 0);
assert_eq!(stats.swift_async_lowered, 0);
assert_eq!(stats.swift_error_lowered, 0);
assert_eq!(stats.preserve_most_fns, 0);
assert_eq!(stats.preserve_all_fns, 0);
assert_eq!(stats.cxx_fast_tls_lowered, 0);
assert_eq!(stats.cfguard_checks, 0);
assert_eq!(stats.ret_addr_lowered, 0);
assert_eq!(stats.frame_addr_lowered, 0);
}
#[test]
fn test_stats_report_formats() {
let stats = X86CallLoweringExtStats::new();
let report = stats.report();
assert!(report.contains("X86CallLoweringExt Statistics"));
assert!(report.contains("Calls analyzed:"));
assert!(report.contains("Devirtualization attempted:"));
assert!(report.contains("Indirect calls promoted:"));
assert!(report.contains("Tail calls lowered:"));
assert!(report.contains("Cold calls split:"));
assert!(report.contains("Calls merged:"));
assert!(report.contains("Arguments forwarded:"));
assert!(report.contains("Swift async lowered:"));
assert!(report.contains("CFGuard checks emitted:"));
assert!(report.contains("return_address lowered:"));
assert!(report.contains("frame_address lowered:"));
}
#[test]
fn test_call_site_descriptor_new() {
let cs = CallSiteDescriptor::new(
5,
2,
Some("foo".into()),
false,
X86CallingConvention::SysV,
3,
);
assert_eq!(cs.instr_idx, 5);
assert_eq!(cs.block_idx, 2);
assert_eq!(cs.callee_name.as_deref(), Some("foo"));
assert!(!cs.is_indirect);
assert_eq!(cs.calling_conv, X86CallingConvention::SysV);
assert_eq!(cs.num_args, 3);
assert!(cs.result_used);
assert!(!cs.is_tail_call);
assert!(!cs.is_musttail);
assert!(!cs.is_cold);
assert_eq!(cs.exec_count, 0);
}
#[test]
fn test_call_site_descriptor_describe() {
let cs = make_direct_call("bar", 1, 3);
let desc = cs.describe();
assert!(desc.contains("bar"));
assert!(desc.contains("direct"));
assert!(desc.contains("b1"));
assert!(desc.contains("i3"));
}
#[test]
fn test_call_site_descriptor_indirect() {
let cs = make_indirect_call(0, 0);
assert!(cs.is_indirect);
assert_eq!(cs.target_reg, Some(R10));
assert!(cs.callee_name.is_none());
}
#[test]
fn test_call_site_descriptor_tail() {
let cs = make_tail_call("tail_fn", 0, 0);
assert!(cs.is_tail_call);
assert!(!cs.is_musttail);
}
#[test]
fn test_call_site_descriptor_musttail() {
let cs = make_musttail_call("musttail_fn", 0, 0);
assert!(cs.is_musttail);
}
#[test]
fn test_call_site_descriptor_cold() {
let cs = make_cold_call("cold_fn", 0, 0);
assert!(cs.is_cold);
assert_eq!(cs.exec_count, 1);
}
#[test]
fn test_call_site_descriptor_attributes() {
let cs = make_byval_call("byval_fn", 0, 0);
assert!(cs.has_byval_args);
}
#[test]
fn test_call_site_descriptor_swift() {
let async_cs = make_swift_async_call(0, 0);
assert!(async_cs.has_swift_async);
assert!(!async_cs.has_swift_error);
let error_cs = make_swift_error_call(0, 0);
assert!(!error_cs.has_swift_async);
assert!(error_cs.has_swift_error);
}
#[test]
fn test_call_site_descriptor_readonly() {
let cs = make_readonly_call("ro_fn", 0, 0);
assert!(cs.is_readonly);
assert!(!cs.is_readnone);
}
#[test]
fn test_call_site_descriptor_readnone() {
let cs = make_readnone_call("rn_fn", 0, 0);
assert!(!cs.is_readonly);
assert!(cs.is_readnone);
}
#[test]
fn test_call_site_descriptor_nocapture() {
let cs = make_nocapture_call(0, 0);
assert!(cs.has_nocapture_args);
}
#[test]
fn test_devirt_kind_names() {
assert_eq!(DevirtKind::VTableDispatch.name(), "vtable");
assert_eq!(DevirtKind::FPtrDispatch.name(), "fptr");
assert_eq!(DevirtKind::VThunk.name(), "vthunk");
assert_eq!(DevirtKind::MemberFnPtr.name(), "memberfnptr");
assert_eq!(DevirtKind::Unknown.name(), "unknown");
}
#[test]
fn test_devirt_kind_is_vtable_based() {
assert!(DevirtKind::VTableDispatch.is_vtable_based());
assert!(DevirtKind::VThunk.is_vtable_based());
assert!(!DevirtKind::FPtrDispatch.is_vtable_based());
assert!(!DevirtKind::MemberFnPtr.is_vtable_based());
assert!(!DevirtKind::Unknown.is_vtable_based());
}
#[test]
fn test_devirt_result_is_success() {
assert!(DevirtResult::Success {
target_name: "foo".into(),
guard_type: "vtable_cmp".into(),
}
.is_success());
assert!(!DevirtResult::NoSingleTarget.is_success());
assert!(!DevirtResult::NotProfitable.is_success());
assert!(!DevirtResult::AnalysisFailed("err".into()).is_success());
assert!(!DevirtResult::AlreadyDirect.is_success());
}
#[test]
fn test_devirt_candidate_new() {
let cs = make_indirect_call(0, 0);
let cand = DevirtCandidate::new(cs, "target_fn".into(), DevirtKind::VTableDispatch);
assert_eq!(cand.predicted_target, "target_fn");
assert_eq!(cand.kind, DevirtKind::VTableDispatch);
assert_eq!(cand.confidence, 0.0);
assert!(cand.needs_guard);
assert!(cand.needs_fallback);
}
#[test]
fn test_devirt_candidate_high_confidence() {
let cs = make_indirect_call(0, 0);
let mut cand = DevirtCandidate::new(cs, "t".into(), DevirtKind::VTableDispatch);
assert!(!cand.is_high_confidence());
cand.confidence = 0.9;
assert!(cand.is_high_confidence());
cand.confidence = 1.0;
assert!(cand.is_high_confidence());
}
#[test]
fn test_guarded_devirt_engine_defaults() {
let eng = GuardedDevirtEngine::new();
assert!(eng.enabled);
assert_eq!(eng.min_confidence, 0.7);
assert!(eng.emit_fallback);
assert_eq!(eng.max_guards, 16);
assert_eq!(eng.guards_emitted, 0);
assert!(eng.use_profile_data);
}
#[test]
fn test_guarded_devirt_should_devirtualize() {
let eng = GuardedDevirtEngine::new();
let cs = make_indirect_call(0, 0);
let mut cand = DevirtCandidate::new(cs, "t".into(), DevirtKind::VTableDispatch);
cand.confidence = 0.5;
assert!(!eng.should_devirtualize(&cand));
cand.confidence = 0.7;
assert!(eng.should_devirtualize(&cand));
}
#[test]
fn test_guarded_devirt_emit_guard_sequence() {
let mut eng = GuardedDevirtEngine::new();
let cs = make_indirect_call(0, 0);
let mut cand = DevirtCandidate::new(cs, "foobar".into(), DevirtKind::VTableDispatch);
cand.confidence = 0.8;
let seq = eng.emit_guard_sequence(&cand);
assert!(seq.iter().any(|s| s.contains("foobar")));
assert!(seq.iter().any(|s| s.contains("Guard")));
assert_eq!(eng.guards_emitted, 1);
}
#[test]
fn test_guarded_devirt_estimate_benefit() {
let eng = GuardedDevirtEngine::new();
let cs = make_indirect_call(0, 0);
let mut cand = DevirtCandidate::new(cs, "t".into(), DevirtKind::VTableDispatch);
cand.confidence = 0.9;
let benefit = eng.estimate_benefit(&cand);
assert!(benefit > 0.0);
}
#[test]
fn test_indirect_call_promotion_engine_defaults() {
let eng = IndirectCallPromotionEngine::new();
assert!(eng.enabled);
assert_eq!(eng.min_call_count, MIN_PROMOTION_COUNT);
assert_eq!(eng.max_promotions, MAX_PROMOTION_CANDIDATES);
assert!(eng.use_profile_data);
assert_eq!(eng.promotions_performed, 0);
}
#[test]
fn test_indirect_call_promotion_should_promote() {
let eng = IndirectCallPromotionEngine::new();
let cs = make_indirect_call(0, 0); assert!(eng.should_promote(&cs));
let mut cs_low = make_indirect_call(1, 0);
cs_low.exec_count = 50;
assert!(!eng.should_promote(&cs_low));
}
#[test]
fn test_indirect_call_promotion_emit_sequence() {
let mut eng = IndirectCallPromotionEngine::new();
let cs = make_indirect_call(0, 0);
let seq = eng.emit_promotion_sequence(&cs, "my_target");
assert!(seq.iter().any(|s| s.contains("my_target")));
assert!(seq.iter().any(|s| s.contains("promotion")));
assert_eq!(eng.promotions_performed, 1);
}
#[test]
fn test_cold_call_splitter_defaults() {
let splitter = ColdCallSplitter::new();
assert!(splitter.enabled);
assert_eq!(splitter.hot_threshold, 100);
assert!(splitter.split_in_loops);
assert_eq!(splitter.max_cold_blocks, 32);
assert_eq!(splitter.cold_blocks_created, 0);
}
#[test]
fn test_cold_call_splitter_should_split() {
let splitter = ColdCallSplitter::new();
let cs = make_cold_call("cold", 0, 0); assert!(splitter.should_split(&cs));
let cs_hot = make_direct_call("hot", 0, 0); assert!(!splitter.should_split(&cs_hot));
}
#[test]
fn test_cold_call_splitter_split_result() {
let mut splitter = ColdCallSplitter::new();
let cs = make_cold_call("cold", 0, 0);
let result = splitter.split_call_to_cold_block(&cs);
assert_eq!(result, ColdSplitResult::Split);
assert_eq!(splitter.cold_blocks_created, 1);
}
#[test]
fn test_cold_call_splitter_by_name() {
let splitter = ColdCallSplitter::new();
assert!(splitter.is_cold_by_name("abort"));
assert!(splitter.is_cold_by_name("__cxa_throw"));
assert!(splitter.is_cold_by_name("llvm.trap"));
assert!(!splitter.is_cold_by_name("compute_fast"));
}
#[test]
fn test_cold_call_splitter_by_profile() {
let splitter = ColdCallSplitter::new();
assert!(splitter.is_cold_by_profile(5, 10000));
assert!(!splitter.is_cold_by_profile(500, 10000));
assert!(!splitter.is_cold_by_profile(0, 0));
}
#[test]
fn test_call_site_merger_defaults() {
let merger = CallSiteMerger::new();
assert!(merger.enabled);
assert_eq!(merger.max_distance, 100);
assert!(merger.cross_block);
assert!(!merger.merge_readonly_calls);
assert_eq!(merger.merged_groups, 0);
assert_eq!(merger.total_merged, 0);
}
#[test]
fn test_call_site_merger_find_groups() {
let merger = CallSiteMerger::new();
let calls = vec![
make_direct_call("foo", 0, 0),
make_direct_call("bar", 0, 1),
make_direct_call("foo", 1, 0),
make_direct_call("foo", 2, 0),
];
let groups = merger.find_merge_groups(&calls);
assert!(groups.contains_key("foo"));
assert_eq!(groups.get("foo").unwrap().len(), 3);
assert!(!groups.contains_key("bar"));
}
#[test]
fn test_call_site_merger_no_merge_for_indirect() {
let merger = CallSiteMerger::new();
let calls = vec![make_indirect_call(0, 0), make_indirect_call(1, 0)];
let groups = merger.find_merge_groups(&calls);
assert!(groups.is_empty());
}
#[test]
fn test_call_site_merger_create_merged() {
let mut merger = CallSiteMerger::new();
let calls = vec![make_direct_call("foo", 0, 0), make_direct_call("foo", 1, 0)];
let result = merger.create_merged_call(&calls, Some(42));
assert_eq!(result.count(), 2);
assert_eq!(merger.merged_groups, 1);
assert_eq!(merger.total_merged, 2);
}
#[test]
fn test_arg_forward_noop() {
let rec = ArgForwardRecord::noop(0, 0);
assert!(rec.is_noop);
assert!(!rec.needs_copy);
assert_eq!(rec.caller_arg_idx, 0);
assert_eq!(rec.callee_arg_idx, 0);
}
#[test]
fn test_arg_forward_reg_copy_same() {
let rec = ArgForwardRecord::reg_copy(0, 0, RAX, RAX, 8, false);
assert!(rec.is_noop);
assert!(!rec.needs_copy);
}
#[test]
fn test_arg_forward_reg_copy_different() {
let rec = ArgForwardRecord::reg_copy(0, 0, RAX, RCX, 8, false);
assert!(!rec.is_noop);
assert!(rec.needs_copy);
assert_eq!(rec.src_reg, Some(RAX));
assert_eq!(rec.dst_reg, Some(RCX));
}
#[test]
fn test_arg_forward_reg_copy_sse() {
let rec = ArgForwardRecord::reg_copy(1, 1, XMM0, XMM1, 16, true);
assert!(!rec.is_noop);
assert!(rec.needs_copy);
assert!(rec.is_sse);
assert_eq!(rec.size, 16);
}
#[test]
fn test_arg_forward_stack_to_reg() {
let rec = ArgForwardRecord::stack_to_reg(2, 2, 16, RAX, 8);
assert!(!rec.src_in_reg);
assert!(rec.needs_copy);
assert_eq!(rec.src_stack_offset, Some(16));
assert_eq!(rec.dst_reg, Some(RAX));
}
#[test]
fn test_arg_forward_reg_to_stack() {
let rec = ArgForwardRecord::reg_to_stack(3, 3, RDX, 24, 8);
assert!(rec.src_in_reg);
assert!(rec.needs_copy);
assert_eq!(rec.dst_stack_offset, Some(24));
}
#[test]
fn test_arg_forward_stack_to_stack_same() {
let rec = ArgForwardRecord::stack_to_stack(4, 4, 32, 32, 8);
assert!(rec.is_noop);
assert!(!rec.needs_copy);
}
#[test]
fn test_arg_forward_stack_to_stack_different() {
let rec = ArgForwardRecord::stack_to_stack(4, 4, 32, 40, 8);
assert!(!rec.is_noop);
assert!(rec.needs_copy);
}
#[test]
fn test_has_forwarding_cycle() {
let records = vec![
ArgForwardRecord::reg_copy(0, 1, RAX, RCX, 8, false),
ArgForwardRecord::reg_copy(1, 0, RCX, RAX, 8, false),
];
assert!(X86CallLoweringExt::has_forwarding_cycle(&records));
}
#[test]
fn test_no_forwarding_cycle() {
let records = vec![
ArgForwardRecord::reg_copy(0, 0, RAX, RAX, 8, false),
ArgForwardRecord::reg_copy(1, 1, RCX, RCX, 8, false),
];
assert!(!X86CallLoweringExt::has_forwarding_cycle(&records));
}
#[test]
fn test_break_forwarding_cycles() {
let mut records = vec![
ArgForwardRecord::reg_copy(0, 1, RAX, RCX, 8, false),
ArgForwardRecord::reg_copy(1, 0, RCX, RAX, 8, false),
];
X86CallLoweringExt::break_forwarding_cycles(&mut records);
assert!(records[0].src_stack_offset.is_some());
}
#[test]
fn test_dead_arg_alive() {
let info = DeadArgInfo::alive(0);
assert!(!info.is_dead);
}
#[test]
fn test_dead_arg_dead() {
let info = DeadArgInfo::dead(0, "unused".into());
assert!(info.is_dead);
assert!(!info.is_transitively_dead);
assert_eq!(info.dead_at_depth, Some(0));
}
#[test]
fn test_dead_arg_transitively_dead() {
let info = DeadArgInfo::transitively_dead(2, 3);
assert!(info.is_dead);
assert!(info.is_transitively_dead);
assert_eq!(info.dead_at_depth, Some(3));
}
#[test]
fn test_byval_promotion_fits_in_regs() {
let cand = ByvalPromotionCandidate::new(0, 8, 8);
assert!(cand.fits_in_regs);
assert_eq!(cand.gprs_needed, 1);
assert_eq!(cand.kind, PromotionKind::ByvalToReg);
}
#[test]
fn test_byval_promotion_too_large() {
let cand = ByvalPromotionCandidate::new(1, 32, 8);
assert!(!cand.fits_in_regs);
assert_eq!(cand.gprs_needed, 0);
assert_eq!(cand.kind, PromotionKind::None);
}
#[test]
fn test_byval_promotion_boundary() {
let cand = ByvalPromotionCandidate::new(0, BYVAL_PROMOTION_THRESHOLD, 8);
assert!(cand.fits_in_regs);
}
#[test]
fn test_byval_promotion_describe() {
let cand = ByvalPromotionCandidate::new(0, 16, 8);
let desc = cand.describe();
assert!(desc.contains("size=16"));
assert!(desc.contains("byval_to_reg"));
}
#[test]
fn test_return_forward_direct() {
let entry = ReturnForwardEntry::direct(5, 1, RAX as u32);
assert!(entry.is_direct);
assert!(!entry.needs_secondary_reg);
assert_eq!(entry.return_reg, RAX as u32);
}
#[test]
fn test_return_forward_with_secondary() {
let entry = ReturnForwardEntry::with_secondary(5, 1, RAX as u32, RDX as u32);
assert!(entry.is_direct);
assert!(entry.needs_secondary_reg);
assert_eq!(entry.secondary_reg, Some(RDX as u32));
}
#[test]
fn test_nocapture_opt_new() {
let opt = NocaptureOpt::new(0);
assert_eq!(opt.arg_idx, 0);
assert!(!opt.address_leaked);
assert!(opt.can_promote);
assert_eq!(opt.stores_eliminated, 0);
assert!(!opt.can_be_modified);
}
#[test]
fn test_readonly_opt_new() {
let opt = ReadonlyOpt::new(1, true, false);
assert_eq!(opt.arg_idx, 1);
assert!(opt.is_readnone);
assert!(!opt.is_readonly);
}
#[test]
fn test_readonly_opt_readonly() {
let opt = ReadonlyOpt::new(0, false, true);
assert!(!opt.is_readnone);
assert!(opt.is_readonly);
}
#[test]
fn test_readonly_opt_both() {
let opt = ReadonlyOpt::new(2, true, true);
assert!(opt.is_readnone);
assert!(opt.is_readonly);
}
#[test]
fn test_align_attr_lowering() {
let lowering = AlignAttrLowering::new(0, 16);
assert_eq!(lowering.param_idx, 0);
assert_eq!(lowering.alignment, 16);
assert!(lowering.applies_to_loads);
assert!(lowering.applies_to_stores);
assert_eq!(lowering.aligned_instrs, 0);
}
#[test]
fn test_deref_attr_lowering() {
let lowering = DerefAttrLowering::new(0, 32, false);
assert_eq!(lowering.param_idx, 0);
assert_eq!(lowering.num_bytes, 32);
assert!(!lowering.can_be_null);
}
#[test]
fn test_deref_attr_lowering_nullable() {
let lowering = DerefAttrLowering::new(1, 16, true);
assert!(lowering.can_be_null);
}
#[test]
fn test_cfguard_check_new() {
let check = CFGuardCheck::new(5, 2, R10);
assert_eq!(check.call_instr_idx, 5);
assert_eq!(check.block_idx, 2);
assert_eq!(check.target_reg, R10);
assert!(check.needs_check);
assert!(check.uses_bitmap);
assert!(check.emit_check_call);
assert!(!check.use_dispatch_thunk);
}
#[test]
fn test_return_address_invalid() {
let ra = ReturnAddressLowering::invalid();
assert!(!ra.is_valid);
}
#[test]
fn test_return_address_from_register() {
let ra = ReturnAddressLowering::from_register(0, RAX);
assert!(ra.is_valid);
assert!(ra.from_register);
assert_eq!(ra.ra_reg, Some(RAX));
assert!(!ra.needs_fp_walk);
}
#[test]
fn test_return_address_from_stack() {
let ra = ReturnAddressLowering::from_stack(0, 8);
assert!(ra.is_valid);
assert!(!ra.from_register);
assert_eq!(ra.stack_offset, Some(8));
assert!(!ra.needs_fp_walk);
}
#[test]
fn test_return_address_depth_requires_walk() {
let ra = ReturnAddressLowering::from_stack(3, 8);
assert!(ra.needs_fp_walk);
}
#[test]
fn test_frame_address_invalid() {
let fa = FrameAddressLowering::invalid();
assert!(!fa.is_valid);
}
#[test]
fn test_frame_address_current_frame() {
let fa = FrameAddressLowering::current_frame(true, RBP);
assert!(fa.is_valid);
assert!(fa.from_rbp);
assert!(!fa.needs_fp_walk);
assert!(fa.has_frame_pointer);
}
#[test]
fn test_frame_address_current_no_fp() {
let fa = FrameAddressLowering::current_frame(false, RBP);
assert!(!fa.from_rbp);
}
#[test]
fn test_frame_address_walk_frames() {
let fa = FrameAddressLowering::walk_frames(3, RBP);
assert!(fa.is_valid);
assert!(!fa.from_rbp);
assert!(fa.needs_fp_walk);
}
#[test]
fn test_swift_async_context_reg_sysv() {
let ext = X86CallLoweringExt::new_sysv64();
assert_eq!(
ext.get_swift_async_context_reg(),
SWIFT_ASYNC_CONTEXT_REG_SYSV
);
}
#[test]
fn test_swift_async_context_reg_win64() {
let ext = X86CallLoweringExt::new_win64();
assert_eq!(
ext.get_swift_async_context_reg(),
SWIFT_ASYNC_CONTEXT_REG_WIN64
);
}
#[test]
fn test_swift_async_context_reg_32bit() {
let ext = X86CallLoweringExt::new_32bit();
assert_eq!(ext.get_swift_async_context_reg(), 0);
}
#[test]
fn test_swift_error_reg_sysv() {
let ext = X86CallLoweringExt::new_sysv64();
assert_eq!(ext.get_swift_error_reg(), SWIFT_ERROR_REG_SYSV);
}
#[test]
fn test_swift_error_reg_win64() {
let ext = X86CallLoweringExt::new_win64();
assert_eq!(ext.get_swift_error_reg(), SWIFT_ERROR_REG_WIN64);
}
#[test]
fn test_swift_async_blocks_tail_call() {
let ext = X86CallLoweringExt::new_sysv64();
assert!(ext.swift_async_context_blocks_tail_call());
}
#[test]
fn test_swift_error_blocks_tail_call() {
let ext = X86CallLoweringExt::new_sysv64();
assert!(ext.swift_error_blocks_tail_call());
}
#[test]
fn test_emit_swift_async_ctx_forward_same() {
let ext = X86CallLoweringExt::new_sysv64();
let seq = ext.emit_swift_async_ctx_forward(R14, R14);
assert!(seq.is_empty());
}
#[test]
fn test_emit_swift_async_ctx_forward_different() {
let ext = X86CallLoweringExt::new_sysv64();
let seq = ext.emit_swift_async_ctx_forward(R14, R10);
assert!(!seq.is_empty());
assert!(seq[0].contains("r14"));
assert!(seq[0].contains("r10"));
}
#[test]
fn test_emit_swift_error_forward_same() {
let ext = X86CallLoweringExt::new_sysv64();
let seq = ext.emit_swift_error_forward(R12, R12);
assert!(seq.is_empty());
}
#[test]
fn test_emit_swift_error_forward_different() {
let ext = X86CallLoweringExt::new_sysv64();
let seq = ext.emit_swift_error_forward(R12, RCX);
assert!(!seq.is_empty());
assert!(seq[0].contains("r12"));
assert!(seq[0].contains("rcx"));
}
#[test]
fn test_preserve_most_csrs_sysv() {
let ext = X86CallLoweringExt::new_sysv64();
let csrs = ext.get_preserve_most_csrs();
assert!(csrs.contains(&RBX));
assert!(csrs.contains(&RBP));
assert!(csrs.contains(&R12));
assert!(csrs.contains(&R15));
assert!(csrs.contains(&XMM8));
assert!(csrs.contains(&XMM15));
assert!(!csrs.contains(&RAX));
}
#[test]
fn test_preserve_all_csrs_sysv() {
let ext = X86CallLoweringExt::new_sysv64();
let csrs = ext.get_preserve_all_csrs();
assert!(csrs.contains(&RAX));
assert!(csrs.contains(&RCX));
assert!(csrs.contains(&RDX));
assert!(csrs.contains(&R8));
assert!(csrs.contains(&XMM0));
}
#[test]
fn test_preserve_most_caller_saved() {
let ext = X86CallLoweringExt::new_sysv64();
assert!(ext.is_preserve_most_caller_saved(RAX));
assert!(!ext.is_preserve_most_caller_saved(RBX));
}
#[test]
fn test_preserve_all_caller_saved() {
let ext = X86CallLoweringExt::new_sysv64();
assert!(!ext.is_preserve_all_caller_saved(RAX));
assert!(!ext.is_preserve_all_caller_saved(RBX));
}
#[test]
fn test_cxx_fast_tls_guard_reg() {
let ext = X86CallLoweringExt::new_sysv64();
assert_eq!(ext.get_cxx_fast_tls_guard_reg(), CXX_FAST_TLS_GUARD_REG);
}
#[test]
fn test_is_cxx_fast_tls_call() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_direct_call("tls_fn", 0, 0);
assert!(!ext.is_cxx_fast_tls_call(&cs));
cs.calling_conv = X86CallingConvention::CXXFastTLS;
assert!(ext.is_cxx_fast_tls_call(&cs));
}
#[test]
fn test_emit_cxx_fast_tls_guard_acquire() {
let ext = X86CallLoweringExt::new_sysv64();
let seq = ext.emit_cxx_fast_tls_guard_acquire();
assert!(seq.iter().any(|s| s.contains("xchgb")));
assert!(seq.iter().any(|s| s.contains("testb")));
assert!(seq.iter().any(|s| s.contains("jnz")));
}
#[test]
fn test_emit_cxx_fast_tls_guard_release() {
let ext = X86CallLoweringExt::new_sysv64();
let seq = ext.emit_cxx_fast_tls_guard_release();
assert!(seq.iter().any(|s| s.contains("movb")));
}
#[test]
fn test_cfguard_applicable() {
let ext = X86CallLoweringExt::new_win64();
let cs = make_indirect_call(0, 0);
assert!(ext.cfguard_applicable(&cs));
let ext_sysv = X86CallLoweringExt::new_sysv64();
assert!(!ext_sysv.cfguard_applicable(&cs));
let cs_direct = make_direct_call("foo", 0, 0);
assert!(!ext.cfguard_applicable(&cs_direct));
}
#[test]
fn test_emit_guard_check_icall() {
let ext = X86CallLoweringExt::new_win64();
let seq = ext.emit_guard_check_icall(R10);
assert!(seq.iter().any(|s| s.contains("r10")));
assert!(seq.iter().any(|s| s.contains("__guard_check_icall")));
}
#[test]
fn test_lower_return_address_depth_zero() {
let ext = X86CallLoweringExt::new_sysv64();
let ra = ext.lower_llvm_return_address(0, false);
assert!(ra.is_valid);
assert!(!ra.from_register);
assert_eq!(ra.stack_offset, Some(8));
}
#[test]
fn test_lower_return_address_depth_zero_thunk() {
let ext = X86CallLoweringExt::new_sysv64();
let ra = ext.lower_llvm_return_address(0, true);
assert!(ra.is_valid);
assert!(ra.from_register);
assert_eq!(ra.ra_reg, Some(RAX));
}
#[test]
fn test_lower_return_address_depth_requires_walk() {
let ext = X86CallLoweringExt::new_sysv64();
let ra = ext.lower_llvm_return_address(3, false);
assert!(ra.is_valid);
assert!(ra.needs_fp_walk);
}
#[test]
fn test_lower_return_address_exceeds_max() {
let ext = X86CallLoweringExt::new_sysv64();
let ra = ext.lower_llvm_return_address(MAX_RETURN_ADDRESS_DEPTH + 1, false);
assert!(!ra.is_valid);
}
#[test]
fn test_emit_load_return_address_depth_zero() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_return_address(0, false);
assert!(code.iter().any(|s| s.contains("(%rbp)")));
}
#[test]
fn test_emit_load_return_address_depth_three() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_return_address(3, false);
assert!(code.iter().filter(|s| s.contains("(%rax)")).count() >= 1);
}
#[test]
fn test_emit_load_return_address_thunk() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_return_address(0, true);
assert!(code.iter().any(|s| s.contains("rax")));
}
#[test]
fn test_lower_frame_address_depth_zero() {
let ext = X86CallLoweringExt::new_sysv64();
let fa = ext.lower_llvm_frame_address(0, true);
assert!(fa.is_valid);
assert!(fa.from_rbp);
assert!(!fa.needs_fp_walk);
}
#[test]
fn test_lower_frame_address_depth_zero_no_fp() {
let ext = X86CallLoweringExt::new_sysv64();
let fa = ext.lower_llvm_frame_address(0, false);
assert!(!fa.from_rbp);
}
#[test]
fn test_lower_frame_address_exceeds_max() {
let ext = X86CallLoweringExt::new_sysv64();
let fa = ext.lower_llvm_frame_address(MAX_FRAME_ADDRESS_DEPTH + 1, true);
assert!(!fa.is_valid);
}
#[test]
fn test_emit_load_frame_address_depth_zero() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_frame_address(0, true);
assert!(code.iter().any(|s| s.contains("rbp")));
}
#[test]
fn test_emit_load_frame_address_depth_zero_no_fp() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_frame_address(0, false);
assert!(code.iter().any(|s| s.contains("rsp")));
}
#[test]
fn test_emit_load_frame_address_depth_three() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_frame_address(3, true);
assert!(code.iter().filter(|s| s.contains("(%rax)")).count() >= 1);
}
#[test]
fn test_cc_compat_self_sysv() {
let matrix = CCCompatibilityMatrix::new();
assert!(matrix.are_compatible(X86CallingConvention::SysV, X86CallingConvention::SysV,));
}
#[test]
fn test_cc_compat_cross_64bit() {
let matrix = CCCompatibilityMatrix::new();
assert!(!matrix.are_compatible(X86CallingConvention::SysV, X86CallingConvention::Win64,));
assert!(!matrix.are_compatible(X86CallingConvention::Win64, X86CallingConvention::SysV,));
}
#[test]
fn test_cc_compat_32bit() {
let matrix = CCCompatibilityMatrix::new();
assert!(matrix.are_compatible(X86CallingConvention::CDecl, X86CallingConvention::CDecl,));
assert!(
matrix.are_compatible(X86CallingConvention::StdCall, X86CallingConvention::StdCall,)
);
}
#[test]
fn test_cc_compat_preserve() {
let matrix = CCCompatibilityMatrix::new();
assert!(matrix.are_compatible(
X86CallingConvention::PreserveMost,
X86CallingConvention::PreserveMost,
));
assert!(!matrix.are_compatible(
X86CallingConvention::PreserveMost,
X86CallingConvention::PreserveAll,
));
}
#[test]
fn test_cc_compat_report() {
let matrix = CCCompatibilityMatrix::new();
let report =
matrix.compatibility_report(X86CallingConvention::SysV, X86CallingConvention::SysV);
assert!(report.contains("compatible"));
let report2 =
matrix.compatibility_report(X86CallingConvention::SysV, X86CallingConvention::Win64);
assert!(report2.contains("INCOMPATIBLE"));
}
#[test]
fn test_param_attr_manager_new() {
let mgr = ParamAttrManager::new(3);
assert_eq!(mgr.attrs.len(), 3);
assert!(mgr.attrs[0].is_empty());
}
#[test]
fn test_param_attr_manager_add_and_check() {
let mut mgr = ParamAttrManager::new(3);
mgr.add_attr(0, ParamAttrKind::Nocapture);
mgr.add_attr(0, ParamAttrKind::Align(16));
assert!(mgr.is_nocapture(0));
assert_eq!(mgr.get_alignment(0), Some(16));
assert!(!mgr.is_nocapture(1));
}
#[test]
fn test_param_attr_manager_get_attrs() {
let mut mgr = ParamAttrManager::new(2);
mgr.add_attr(0, ParamAttrKind::Nocapture);
mgr.add_attr(0, ParamAttrKind::Readonly);
let attrs = mgr.get_attrs_for(0);
assert_eq!(attrs.len(), 2);
}
#[test]
fn test_param_attr_manager_is_inalloca() {
let mut mgr = ParamAttrManager::new(1);
assert!(!mgr.is_inalloca(0));
mgr.add_attr(0, ParamAttrKind::InAlloca);
assert!(mgr.is_inalloca(0));
}
#[test]
fn test_param_attr_manager_is_swift() {
let mut mgr = ParamAttrManager::new(2);
mgr.add_attr(0, ParamAttrKind::SwiftAsync);
mgr.add_attr(1, ParamAttrKind::SwiftError);
assert!(mgr.is_swift_async(0));
assert!(mgr.is_swift_error(1));
}
#[test]
fn test_param_attr_manager_deref_bytes() {
let mut mgr = ParamAttrManager::new(1);
assert_eq!(mgr.get_dereferenceable_bytes(0), None);
mgr.add_attr(0, ParamAttrKind::Dereferenceable(64));
assert_eq!(mgr.get_dereferenceable_bytes(0), Some(64));
}
#[test]
fn test_param_attr_manager_out_of_bounds() {
let mgr = ParamAttrManager::new(2);
assert!(!mgr.is_nocapture(5));
assert_eq!(mgr.get_alignment(10), None);
}
#[test]
fn test_param_attr_kind_names() {
assert_eq!(ParamAttrKind::Nocapture.name(), "nocapture");
assert_eq!(ParamAttrKind::Readonly.name(), "readonly");
assert_eq!(ParamAttrKind::Readnone.name(), "readnone");
assert_eq!(ParamAttrKind::Align(16).name(), "align(16)");
assert_eq!(
ParamAttrKind::Dereferenceable(32).name(),
"dereferenceable(32)"
);
assert_eq!(
ParamAttrKind::DereferenceableOrNull(8).name(),
"dereferenceable_or_null(8)"
);
assert_eq!(ParamAttrKind::ByVal.name(), "byval");
assert_eq!(ParamAttrKind::InAlloca.name(), "inalloca");
assert_eq!(ParamAttrKind::SRet.name(), "sret");
assert_eq!(ParamAttrKind::Nest.name(), "nest");
assert_eq!(ParamAttrKind::SwiftAsync.name(), "swiftasync");
assert_eq!(ParamAttrKind::SwiftError.name(), "swifterror");
assert_eq!(ParamAttrKind::NoAlias.name(), "noalias");
assert_eq!(ParamAttrKind::Returned.name(), "returned");
assert_eq!(ParamAttrKind::NonNull.name(), "nonnull");
assert_eq!(ParamAttrKind::SignExt.name(), "signext");
assert_eq!(ParamAttrKind::ZeroExt.name(), "zeroext");
assert_eq!(ParamAttrKind::InReg.name(), "inreg");
assert_eq!(ParamAttrKind::NoUndef.name(), "noundef");
}
#[test]
fn test_promotion_kind_is_active() {
assert!(PromotionKind::ByvalToReg.is_active());
assert!(PromotionKind::ByvalToRegAndStack.is_active());
assert!(PromotionKind::SRet.is_active());
assert!(!PromotionKind::None.is_active());
}
#[test]
fn test_reg_name_64() {
assert_eq!(X86CallLoweringExt::reg_name(RAX), "rax");
assert_eq!(X86CallLoweringExt::reg_name(RCX), "rcx");
assert_eq!(X86CallLoweringExt::reg_name(RDX), "rdx");
assert_eq!(X86CallLoweringExt::reg_name(RBX), "rbx");
assert_eq!(X86CallLoweringExt::reg_name(RSP), "rsp");
assert_eq!(X86CallLoweringExt::reg_name(RBP), "rbp");
assert_eq!(X86CallLoweringExt::reg_name(RSI), "rsi");
assert_eq!(X86CallLoweringExt::reg_name(RDI), "rdi");
assert_eq!(X86CallLoweringExt::reg_name(R8), "r8");
assert_eq!(X86CallLoweringExt::reg_name(R15), "r15");
}
#[test]
fn test_reg_name_32() {
assert_eq!(X86CallLoweringExt::reg_name(EAX), "eax");
assert_eq!(X86CallLoweringExt::reg_name(ECX), "ecx");
assert_eq!(X86CallLoweringExt::reg_name(ESP), "esp");
assert_eq!(X86CallLoweringExt::reg_name(EBP), "ebp");
}
#[test]
fn test_reg_name_xmm() {
assert_eq!(X86CallLoweringExt::reg_name(XMM0), "xmm0");
assert_eq!(X86CallLoweringExt::reg_name(XMM7), "xmm7");
assert_eq!(X86CallLoweringExt::reg_name(XMM15), "xmm15");
}
#[test]
fn test_reg_name_unknown() {
assert_eq!(X86CallLoweringExt::reg_name(999), "?");
}
#[test]
fn test_sp_reg() {
let ext64 = X86CallLoweringExt::new_sysv64();
assert_eq!(ext64.sp_reg(), RSP);
let ext32 = X86CallLoweringExt::new_32bit();
assert_eq!(ext32.sp_reg(), ESP);
}
#[test]
fn test_bp_reg() {
let ext64 = X86CallLoweringExt::new_sysv64();
assert_eq!(ext64.bp_reg(), RBP);
let ext32 = X86CallLoweringExt::new_32bit();
assert_eq!(ext32.bp_reg(), EBP);
}
#[test]
fn test_word_size() {
let ext64 = X86CallLoweringExt::new_sysv64();
assert_eq!(ext64.word_size(), 8);
let ext32 = X86CallLoweringExt::new_32bit();
assert_eq!(ext32.word_size(), 4);
}
#[test]
fn test_ra_reg() {
let ext64 = X86CallLoweringExt::new_sysv64();
assert_eq!(ext64.ra_reg(), RAX);
let ext32 = X86CallLoweringExt::new_32bit();
assert_eq!(ext32.ra_reg(), EAX);
}
#[test]
fn test_get_default_csrs_sysv() {
let ext = X86CallLoweringExt::new_sysv64();
let csrs = ext.get_default_csrs();
assert!(csrs.contains(&RBX));
assert!(csrs.contains(&RBP));
assert!(csrs.contains(&R12));
assert!(!csrs.contains(&RAX));
}
#[test]
fn test_get_default_csrs_win64() {
let ext = X86CallLoweringExt::new_win64();
let csrs = ext.get_default_csrs();
assert!(csrs.contains(&RBX));
assert!(csrs.contains(&RSI));
assert!(csrs.contains(&RDI));
}
#[test]
fn test_get_default_csrs_32bit() {
let ext = X86CallLoweringExt::new_32bit();
let csrs = ext.get_default_csrs();
assert!(csrs.contains(&EBX));
assert!(csrs.contains(&EBP));
}
#[test]
fn test_get_default_caller_saved_sysv() {
let ext = X86CallLoweringExt::new_sysv64();
let saved = ext.get_default_caller_saved();
assert!(saved.contains(&RAX));
assert!(saved.contains(&RDI));
assert!(!saved.contains(&RBX));
}
#[test]
fn test_get_default_caller_saved_win64() {
let ext = X86CallLoweringExt::new_win64();
let saved = ext.get_default_caller_saved();
assert!(saved.contains(&RAX));
assert!(!saved.contains(&RSI)); }
#[test]
fn test_validate_musttail_strict_no_inalloca() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_musttail_call("fn", 0, 0);
cs.has_inalloca_args = true;
assert!(!ext.validate_musttail(&cs));
}
#[test]
fn test_validate_musttail_swift_async_disabled() {
let mut ext = X86CallLoweringExt::new_sysv64();
ext.enable_swift_async = false;
let mut cs = make_musttail_call("fn", 0, 0);
cs.has_swift_async = true;
assert!(!ext.validate_musttail(&cs));
}
#[test]
fn test_validate_musttail_swift_error_disabled() {
let mut ext = X86CallLoweringExt::new_sysv64();
ext.enable_swift_error = false;
let mut cs = make_musttail_call("fn", 0, 0);
cs.has_swift_error = true;
assert!(!ext.validate_musttail(&cs));
}
#[test]
fn test_validate_musttail_strict_pass() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_musttail_call("fn", 0, 0);
assert!(ext.validate_musttail(&cs));
}
#[test]
fn test_validate_musttail_non_strict() {
let mut ext = X86CallLoweringExt::new_sysv64();
ext.strict_musttail = false;
let mut cs = make_musttail_call("fn", 0, 0);
cs.has_inalloca_args = true; assert!(ext.validate_musttail(&cs));
}
#[test]
fn test_tail_call_eligible() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_tail_call("fn", 0, 0);
assert!(ext.is_tail_call_eligible(&cs));
}
#[test]
fn test_tail_call_not_eligible_byval() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_byval_call("fn", 0, 0);
assert!(!ext.is_tail_call_eligible(&cs));
}
#[test]
fn test_tail_call_not_eligible_not_tail() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("fn", 0, 0); assert!(!ext.is_tail_call_eligible(&cs));
}
#[test]
fn test_can_promote_indirect() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_indirect_call(0, 0);
assert!(ext.can_promote_indirect_call(&cs));
}
#[test]
fn test_cannot_promote_low_count() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.exec_count = 50;
assert!(!ext.can_promote_indirect_call(&cs));
}
#[test]
fn test_cannot_promote_no_target_reg() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.target_reg = None;
assert!(!ext.can_promote_indirect_call(&cs));
}
#[test]
fn test_compute_arg_forwarding_empty() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_tail_call("fn", 0, 0);
cs.num_args = 0;
let records = ext.compute_arg_forwarding(&cs);
assert!(records.is_empty());
}
#[test]
fn test_compute_arg_forwarding_with_args() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_tail_call("fn", 0, 0);
cs.num_args = 4;
let records = ext.compute_arg_forwarding(&cs);
assert_eq!(records.len(), 4);
for rec in &records {
assert!(rec.is_noop);
}
}
#[test]
fn test_compute_arg_forwarding_clamped() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_tail_call("fn", 0, 0);
cs.num_args = MAX_FORWARDED_ARGS + 10;
let records = ext.compute_arg_forwarding(&cs);
assert_eq!(records.len(), MAX_FORWARDED_ARGS);
}
#[test]
fn test_calls_identical_same() {
let ext = X86CallLoweringExt::new_sysv64();
let a = make_direct_call("foo", 0, 0);
let b = make_direct_call("foo", 1, 0);
assert!(ext.are_calls_identical(&a, &b));
}
#[test]
fn test_calls_not_identical_different_callee() {
let ext = X86CallLoweringExt::new_sysv64();
let a = make_direct_call("foo", 0, 0);
let b = make_direct_call("bar", 0, 0);
assert!(!ext.are_calls_identical(&a, &b));
}
#[test]
fn test_calls_not_identical_indirect() {
let ext = X86CallLoweringExt::new_sysv64();
let a = make_direct_call("foo", 0, 0);
let b = make_indirect_call(0, 0);
assert!(!ext.are_calls_identical(&a, &b));
}
#[test]
fn test_display_devirt_kind() {
assert_eq!(format!("{}", DevirtKind::VTableDispatch), "vtable");
assert_eq!(format!("{}", DevirtKind::Unknown), "unknown");
}
#[test]
fn test_display_devirt_result_success() {
let result = DevirtResult::Success {
target_name: "foo".into(),
guard_type: "vtable_cmp".into(),
};
assert!(format!("{}", result).contains("foo"));
}
#[test]
fn test_display_devirt_result_failure() {
assert_eq!(
format!("{}", DevirtResult::NoSingleTarget),
"no_single_target"
);
assert_eq!(format!("{}", DevirtResult::NotProfitable), "not_profitable");
}
#[test]
fn test_display_promotion_kind() {
assert_eq!(format!("{}", PromotionKind::ByvalToReg), "byval_to_reg");
assert_eq!(format!("{}", PromotionKind::None), "none");
}
#[test]
fn test_display_cold_split_result() {
assert_eq!(format!("{}", ColdSplitResult::Split), "split");
assert_eq!(format!("{}", ColdSplitResult::NotCold), "not_cold");
}
#[test]
fn test_display_merge_result() {
let result = MergeResult::Merged {
count: 3,
shared_result: Some(42),
};
assert!(format!("{}", result).contains("3"));
}
#[test]
fn test_display_call_site() {
let cs = make_direct_call("test_fn", 1, 2);
let s = format!("{}", cs);
assert!(s.contains("test_fn"));
assert!(s.contains("direct"));
}
#[test]
fn test_display_dead_arg() {
let info = DeadArgInfo::dead(0, "unused".into());
assert!(format!("{}", info).contains("dead"));
}
#[test]
fn test_display_arg_forward_noop() {
let rec = ArgForwardRecord::noop(1, 2);
assert!(format!("{}", rec).contains("noop"));
}
#[test]
fn test_display_arg_forward_copy() {
let rec = ArgForwardRecord::reg_copy(0, 1, RAX, RCX, 8, false);
assert!(format!("{}", rec).contains("copy"));
}
#[test]
fn test_display_return_address() {
let ra = ReturnAddressLowering::from_register(0, RAX);
assert!(format!("{}", ra).contains("rax"));
}
#[test]
fn test_display_frame_address() {
let fa = FrameAddressLowering::current_frame(true, RBP);
assert!(format!("{}", fa).contains("rbp"));
}
#[test]
fn test_cold_by_name_various() {
let splitter = ColdCallSplitter::new();
assert!(splitter.is_cold_by_name("__assert_fail"));
assert!(splitter.is_cold_by_name("panic_handler"));
assert!(splitter.is_cold_by_name("_ZSt20__throw_length_error"));
assert!(splitter.is_cold_by_name("__stack_chk_fail"));
assert!(splitter.is_cold_by_name("terminate_handler"));
assert!(!splitter.is_cold_by_name("memcpy"));
assert!(!splitter.is_cold_by_name("compute_hash"));
assert!(!splitter.is_cold_by_name("sort_array"));
}
#[test]
fn test_run_with_no_call_sites() {
let mut ext = X86CallLoweringExt::new_sysv64();
ext.stats.calls_analyzed = 42;
assert_eq!(ext.stats.calls_analyzed, 42);
}
#[test]
fn test_analyze_dead_args_zero() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_direct_call("fn", 0, 0);
cs.num_args = 0;
let results = ext.analyze_dead_args(&cs);
assert!(results.is_empty());
}
#[test]
fn test_analyze_dead_args_with_args() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_direct_call("fn", 0, 0);
cs.num_args = 5;
let results = ext.analyze_dead_args(&cs);
assert_eq!(results.len(), 5);
for r in &results {
assert!(!r.is_dead); }
}
#[test]
fn test_analyze_byval_no_byval() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("fn", 0, 0); let results = ext.analyze_byval_promotion(&cs);
assert!(results.is_empty());
}
#[test]
fn test_analyze_byval_with_byval() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_direct_call("fn", 0, 0);
cs.has_byval_args = true;
cs.num_args = 3;
let results = ext.analyze_byval_promotion(&cs);
assert_eq!(results.len(), 3);
}
#[test]
fn test_analyze_return_forward_used() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("fn", 0, 0); let entry = ext.analyze_return_forward(&cs);
assert!(entry.is_some());
}
#[test]
fn test_analyze_return_forward_unused() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_direct_call("fn", 0, 0);
cs.result_used = false;
let entry = ext.analyze_return_forward(&cs);
assert!(entry.is_none());
}
#[test]
fn test_analyze_nocapture_no_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("fn", 0, 0); let opts = ext.analyze_nocapture(&cs);
assert!(opts.is_empty());
}
#[test]
fn test_analyze_nocapture_with_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_direct_call("fn", 0, 0);
cs.has_nocapture_args = true;
cs.num_args = 4;
let opts = ext.analyze_nocapture(&cs);
assert_eq!(opts.len(), 4);
}
#[test]
fn test_analyze_readonly_no_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("fn", 0, 0); let opts = ext.analyze_readonly(&cs);
assert!(opts.is_empty());
}
#[test]
fn test_analyze_readonly_with_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_readonly_call("fn", 0, 0);
let opts = ext.analyze_readonly(&cs);
assert_eq!(opts.len(), 3);
assert!(opts[0].is_readonly);
assert!(!opts[0].is_readnone);
}
#[test]
fn test_analyze_readnone_with_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_readnone_call("fn", 0, 0);
let opts = ext.analyze_readonly(&cs);
assert!(opts[0].is_readnone);
}
#[test]
fn test_lower_align_no_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("fn", 0, 0);
let results = ext.lower_align_attrs(&cs);
assert!(results.is_empty());
}
#[test]
fn test_lower_align_with_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_direct_call("fn", 0, 0);
cs.has_align_args = true;
cs.num_args = 3;
let results = ext.lower_align_attrs(&cs);
assert_eq!(results.len(), 3);
assert_eq!(results[0].alignment, 16);
}
#[test]
fn test_lower_deref_no_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("fn", 0, 0);
let results = ext.lower_deref_attrs(&cs);
assert!(results.is_empty());
}
#[test]
fn test_lower_deref_with_attr() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_direct_call("fn", 0, 0);
cs.has_deref_args = true;
cs.num_args = 2;
let results = ext.lower_deref_attrs(&cs);
assert_eq!(results.len(), 2);
assert_eq!(results[0].null_checks_eliminated, 1);
}
#[test]
fn test_predict_vtable_high_count() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.exec_count = 200;
cs.devirt_kind = Some(DevirtKind::VTableDispatch);
let result = ext.predict_vtable_target(&cs);
assert!(result.is_some());
}
#[test]
fn test_predict_vtable_low_count() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.exec_count = 50;
cs.devirt_kind = Some(DevirtKind::VTableDispatch);
let result = ext.predict_vtable_target(&cs);
assert!(result.is_none());
}
#[test]
fn test_predict_fptr_high_count() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.exec_count = 200;
let result = ext.predict_fptr_target(&cs);
assert!(result.is_some());
}
#[test]
fn test_predict_fptr_low_count() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.exec_count = 50;
let result = ext.predict_fptr_target(&cs);
assert!(result.is_none());
}
#[test]
fn test_estimate_devirt_confidence_high() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.exec_count = 2000;
let conf = ext.estimate_devirt_confidence(&cs, DevirtKind::VTableDispatch);
assert!(conf >= 0.9);
}
#[test]
fn test_estimate_devirt_confidence_medium() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.exec_count = 200;
let conf = ext.estimate_devirt_confidence(&cs, DevirtKind::VTableDispatch);
assert!(conf >= 0.6 && conf < 0.9);
}
#[test]
fn test_estimate_devirt_confidence_low() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.exec_count = 50;
let conf = ext.estimate_devirt_confidence(&cs, DevirtKind::VTableDispatch);
assert!(conf < 0.6);
}
#[test]
fn test_emit_guard_check_icall_sequence() {
let ext = X86CallLoweringExt::new_win64();
let seq = ext.emit_guard_check_icall(R11);
assert_eq!(seq.len(), 3);
assert!(seq[0].contains("r11"));
assert!(seq[0].contains("rcx"));
assert!(seq[1].contains("__guard_check_icall"));
assert!(seq[2].contains("r11"));
}
#[test]
fn test_lower_swift_async_no_panic() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_swift_async_call(0, 0);
ext.lower_swift_async(&cs); }
#[test]
fn test_lower_swift_error_no_panic() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_swift_error_call(0, 0);
ext.lower_swift_error(&cs); }
#[test]
fn test_count_preserve_most() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs1 = make_direct_call("fn1", 0, 0);
cs1.calling_conv = X86CallingConvention::PreserveMost;
let mut cs2 = make_direct_call("fn2", 1, 0);
cs2.calling_conv = X86CallingConvention::PreserveMost;
let cs3 = make_direct_call("fn3", 2, 0); let calls = vec![cs1, cs2, cs3];
assert_eq!(ext.count_preserve_most_calls(&calls), 2);
}
#[test]
fn test_count_preserve_all() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs1 = make_direct_call("fn1", 0, 0);
cs1.calling_conv = X86CallingConvention::PreserveAll;
let calls = vec![cs1];
assert_eq!(ext.count_preserve_all_calls(&calls), 1);
}
#[test]
fn test_lower_cxx_fast_tls_count() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs1 = make_direct_call("tls1", 0, 0);
cs1.calling_conv = X86CallingConvention::CXXFastTLS;
let mut cs2 = make_direct_call("tls2", 1, 0);
cs2.calling_conv = X86CallingConvention::CXXFastTLS;
let calls = vec![cs1, cs2, make_direct_call("normal", 2, 0)];
assert_eq!(ext.lower_cxx_fast_tls(&calls), 2);
}
#[test]
fn test_try_devirtualize_not_profitable() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_indirect_call(0, 0);
let mut cand = DevirtCandidate::new(cs, "t".into(), DevirtKind::VTableDispatch);
cand.confidence = 0.3;
let result = ext.try_devirtualize(&cand);
assert_eq!(result, DevirtResult::NotProfitable);
}
#[test]
fn test_try_devirtualize_success() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_indirect_call(0, 0);
let mut cand = DevirtCandidate::new(cs, "target_fn".into(), DevirtKind::VTableDispatch);
cand.confidence = 0.8;
let result = ext.try_devirtualize(&cand);
assert!(result.is_success());
if let DevirtResult::Success { target_name, .. } = result {
assert_eq!(target_name, "target_fn");
}
}
#[test]
fn test_try_split_cold_call_split() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_cold_call("cold", 0, 0);
let result = ext.try_split_cold_call(&cs);
assert_eq!(result, ColdSplitResult::Split);
}
#[test]
fn test_try_split_cold_call_not_cold() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("hot", 0, 0); let result = ext.try_split_cold_call(&cs);
assert_eq!(result, ColdSplitResult::NotCold);
}
#[test]
fn test_try_split_cold_call_suppressed_musttail() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_cold_call("cold", 0, 0);
cs.is_musttail = true;
let result = ext.try_split_cold_call(&cs);
assert_eq!(result, ColdSplitResult::Suppressed);
}
#[test]
fn test_preserve_most_csrs_win64() {
let ext = X86CallLoweringExt::new_win64();
let csrs = ext.get_preserve_most_csrs();
assert!(csrs.contains(&RSI));
assert!(csrs.contains(&RDI));
assert!(csrs.contains(&XMM6));
assert!(!csrs.contains(&RAX));
}
#[test]
fn test_preserve_all_csrs_win64() {
let ext = X86CallLoweringExt::new_win64();
let csrs = ext.get_preserve_all_csrs();
assert!(csrs.contains(&RAX));
assert!(csrs.contains(&RSI));
assert!(csrs.contains(&XMM0));
assert!(csrs.contains(&XMM15));
}
#[test]
fn test_emit_load_return_address_thunk_zero() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_return_address(0, true);
assert!(!code.is_empty());
assert!(code.iter().any(|s| s.contains("return address in")));
}
#[test]
fn test_emit_load_frame_address_depth_zero_no_fp_full() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_frame_address(0, false);
assert!(code.iter().any(|s| s.contains("lea") || s.contains("rsp")));
}
#[test]
fn test_emit_load_frame_address_no_fp() {
let ext = X86CallLoweringExt::new_sysv64();
let code = ext.emit_load_frame_address(0, false);
assert!(code.iter().any(|s| s.contains("rsp")));
}
#[test]
fn test_lower_return_address_count() {
let ext = X86CallLoweringExt::new_sysv64();
let calls = vec![
make_direct_call("a", 0, 0),
make_direct_call("b", 1, 0),
make_direct_call("c", 2, 0),
];
let count = ext.lower_return_address(&calls);
assert_eq!(count, 1);
}
#[test]
fn test_lower_return_address_empty() {
let ext = X86CallLoweringExt::new_sysv64();
let calls: Vec<CallSiteDescriptor> = vec![];
let count = ext.lower_return_address(&calls);
assert_eq!(count, 0);
}
#[test]
fn test_lower_frame_address_count() {
let ext = X86CallLoweringExt::new_sysv64();
let calls = vec![make_direct_call("a", 0, 0)];
let count = ext.lower_frame_address(&calls);
assert_eq!(count, 1);
}
#[test]
fn test_emit_cfguard_checks_count() {
let ext = X86CallLoweringExt::new_win64();
let calls = vec![
make_indirect_call(0, 0),
make_indirect_call(1, 0),
make_direct_call("direct", 2, 0),
];
let count = ext.emit_cfguard_checks(&calls);
assert_eq!(count, 2);
}
#[test]
fn test_emit_cfguard_checks_no_indirect() {
let ext = X86CallLoweringExt::new_win64();
let calls = vec![make_direct_call("a", 0, 0), make_direct_call("b", 1, 0)];
let count = ext.emit_cfguard_checks(&calls);
assert_eq!(count, 0);
}
#[test]
fn test_emit_cfguard_checks_sysv_disabled() {
let ext = X86CallLoweringExt::new_sysv64(); let calls = vec![make_indirect_call(0, 0)];
let count = ext.emit_cfguard_checks(&calls);
assert_eq!(count, 0);
}
#[test]
fn test_analyze_devirt_candidate_direct() {
let ext = X86CallLoweringExt::new_sysv64();
let cs = make_direct_call("fn", 0, 0);
let cand = ext.analyze_devirt_candidate(&cs);
assert!(cand.is_none());
}
#[test]
fn test_analyze_devirt_candidate_unknown_kind() {
let ext = X86CallLoweringExt::new_sysv64();
let mut cs = make_indirect_call(0, 0);
cs.devirt_kind = Some(DevirtKind::Unknown);
cs.exec_count = 500;
let cand = ext.analyze_devirt_candidate(&cs);
assert!(cand.is_none());
}
#[test]
fn test_is_transitively_dead() {
let ext = X86CallLoweringExt::new_sysv64();
assert!(!ext.is_transitively_dead(0, 0));
assert!(!ext.is_transitively_dead(0, 3));
}
#[test]
fn test_classify_devirt_kind_direct() {
let ext = X86CallLoweringExt::new_sysv64();
let _ = ext.classify_devirt_kind;
}
#[test]
fn test_cfguard_applicable_excludes_guard_funcs() {
let ext = X86CallLoweringExt::new_win64();
let mut cs = make_indirect_call(0, 0);
cs.callee_name = Some("__guard_check_icall".into());
assert!(!ext.cfguard_applicable(&cs));
}
#[test]
fn test_create_cfguard_check() {
let ext = X86CallLoweringExt::new_win64();
let check = ext.create_cfguard_check(3, 1, R11);
assert_eq!(check.call_instr_idx, 3);
assert_eq!(check.block_idx, 1);
assert_eq!(check.target_reg, R11);
assert!(check.needs_check);
}
#[test]
fn test_run_cc_edge_cases_with_swift() {
let mut ext = X86CallLoweringExt::new_sysv64();
let cs = make_swift_async_call(0, 0);
let calls = vec![cs];
ext.run_cc_edge_cases(&calls);
assert_eq!(ext.stats.swift_async_lowered, 1);
}
#[test]
fn test_run_cc_edge_cases_with_swift_error() {
let mut ext = X86CallLoweringExt::new_sysv64();
let cs = make_swift_error_call(0, 0);
let calls = vec![cs];
ext.run_cc_edge_cases(&calls);
assert_eq!(ext.stats.swift_error_lowered, 1);
}
#[test]
fn test_run_cc_edge_cases_disabled_swift() {
let mut ext = X86CallLoweringExt::new_sysv64();
ext.enable_swift_async = false;
ext.enable_swift_error = false;
let calls = vec![make_swift_async_call(0, 0), make_swift_error_call(1, 0)];
ext.run_cc_edge_cases(&calls);
assert_eq!(ext.stats.swift_async_lowered, 0);
assert_eq!(ext.stats.swift_error_lowered, 0);
}
}