use crate::attributes_v2::{CallingConvention, ExtendedAttrKind, MemoryEffects};
use crate::basic_block::BasicBlock;
use crate::function::{DLLStorageClass, Function, IntrinsicID, Linkage, Visibility};
use crate::opcode::Opcode;
use crate::types::{Type, TypeKind};
use crate::value::{Value, ValueRef};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone)]
pub struct ArgumentInfo {
pub name: String,
pub ty: TypeKind,
pub has_byval: bool,
pub has_nest: bool,
pub has_sret: bool,
pub has_noalias: bool,
pub has_nocapture: bool,
pub has_nonnull: bool,
pub has_returned: bool,
pub has_signext: bool,
pub has_zeroext: bool,
pub alignment: Option<u32>,
pub dereferenceable: Option<u64>,
pub is_in_reg: bool,
pub is_no_undef: bool,
pub has_swift_self: bool,
pub has_swift_error: bool,
}
impl ArgumentInfo {
pub fn new(name: String, ty: TypeKind) -> Self {
ArgumentInfo {
name,
ty,
has_byval: false,
has_nest: false,
has_sret: false,
has_noalias: false,
has_nocapture: false,
has_nonnull: false,
has_returned: false,
has_signext: false,
has_zeroext: false,
alignment: None,
dereferenceable: None,
is_in_reg: false,
is_no_undef: false,
has_swift_self: false,
has_swift_error: false,
}
}
}
pub struct DeadArgumentElimination;
impl DeadArgumentElimination {
pub fn is_arg_used(arg_name: &str, all_uses: &HashMap<String, Vec<String>>) -> bool {
all_uses
.get(arg_name)
.map_or(false, |uses| !uses.is_empty())
}
pub fn find_dead_args(
args: &[ArgumentInfo],
all_uses: &HashMap<String, Vec<String>>,
) -> Vec<usize> {
args.iter()
.enumerate()
.filter(|(_, arg)| !Self::is_arg_used(&arg.name, all_uses))
.map(|(i, _)| i)
.collect()
}
pub fn can_eliminate_arg(_arg: &ArgumentInfo, linkage: Linkage) -> bool {
matches!(linkage, Linkage::Internal | Linkage::Private)
}
}
pub struct CallingConventionInfo;
impl CallingConventionInfo {
pub fn is_register_based(_cc: CallingConvention) -> bool {
true
}
pub fn is_kernel_cc(cc: CallingConvention) -> bool {
matches!(
cc,
CallingConvention::PTXKernel
| CallingConvention::PTXDevice
| CallingConvention::SPIRKernel
| CallingConvention::AMDGPUKernel
| CallingConvention::AMDGPUVS
| CallingConvention::AMDGPUGS
| CallingConvention::AMDGPUPS
| CallingConvention::AMDGPUCS
)
}
pub fn is_interrupt_cc(cc: CallingConvention) -> bool {
matches!(
cc,
CallingConvention::MSP430Intr
| CallingConvention::AVRIntr
| CallingConvention::AVRSignal
)
}
pub fn num_register_params(cc: CallingConvention) -> usize {
match cc {
CallingConvention::C | CallingConvention::Fast => 6,
CallingConvention::X8664SysV => 6,
CallingConvention::Win64 => 4,
CallingConvention::ArmAAPCS | CallingConvention::ArmAAPCSVFP => 4,
CallingConvention::AArch64SVEPCS => 8,
CallingConvention::RISCVVectorCall => 8,
_ => 4,
}
}
pub fn supports_tail_calls(cc: CallingConvention) -> bool {
!Self::is_interrupt_cc(cc)
&& !Self::is_kernel_cc(cc)
&& cc != CallingConvention::GHC
&& cc != CallingConvention::HiPE
&& cc != CallingConvention::WebKitJS
}
}
#[derive(Debug, Clone)]
pub struct MergeCandidate {
pub func_a: String,
pub func_b: String,
pub similarity: f64,
pub can_be_thunk: bool,
}
pub struct FunctionMerger {
pub signature_map: HashMap<u64, Vec<String>>,
pub similarity_scores: HashMap<(String, String), f64>,
}
impl FunctionMerger {
pub fn new() -> Self {
FunctionMerger {
signature_map: HashMap::new(),
similarity_scores: HashMap::new(),
}
}
pub fn compute_signature_hash(
ret_type: &TypeKind,
param_types: &[TypeKind],
_is_vararg: bool,
) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
format!("{:?}", ret_type).hash(&mut hasher);
for pt in param_types {
format!("{:?}", pt).hash(&mut hasher);
}
hasher.finish()
}
pub fn compute_similarity(_body_a: &[Opcode], _body_b: &[Opcode]) -> f64 {
let min_len = _body_a.len().min(_body_b.len());
if min_len == 0 {
return 0.0;
}
let matches = _body_a
.iter()
.zip(_body_b.iter())
.take(min_len)
.filter(|(a, b)| std::mem::discriminant(*a) == std::mem::discriminant(*b))
.count();
matches as f64 / min_len as f64
}
pub fn find_mergeable_pairs(&self, threshold: f64) -> Vec<MergeCandidate> {
let mut candidates = Vec::new();
for ((a, b), sim) in &self.similarity_scores {
if *sim >= threshold && a < b {
candidates.push(MergeCandidate {
func_a: a.clone(),
func_b: b.clone(),
similarity: *sim,
can_be_thunk: *sim >= 0.95,
});
}
}
candidates.sort_by(|a, b| {
b.similarity
.partial_cmp(&a.similarity)
.unwrap_or(std::cmp::Ordering::Equal)
});
candidates
}
}
impl Default for FunctionMerger {
fn default() -> Self {
FunctionMerger::new()
}
}
#[derive(Debug, Clone)]
pub struct InlineCost {
pub cost: u32,
pub threshold: u32,
pub always_inline: bool,
pub never_inline: bool,
pub size_delta: i32,
}
pub struct InlineCostAnalyzer {
pub default_threshold: u32,
pub optsize_threshold: u32,
pub minsize_threshold: u32,
pub cost_per_inst: u32,
}
impl InlineCostAnalyzer {
pub fn new() -> Self {
InlineCostAnalyzer {
default_threshold: 225,
optsize_threshold: 75,
minsize_threshold: 25,
cost_per_inst: 5,
}
}
pub fn analyze_call(
&self,
caller_size: u32,
callee_size: u32,
is_always_inline: bool,
is_no_inline: bool,
is_opt_size: bool,
is_min_size: bool,
) -> InlineCost {
if is_always_inline {
return InlineCost {
cost: 0,
threshold: u32::MAX,
always_inline: true,
never_inline: false,
size_delta: 0,
};
}
if is_no_inline {
return InlineCost {
cost: u32::MAX,
threshold: 0,
always_inline: false,
never_inline: true,
size_delta: 0,
};
}
let threshold = if is_min_size {
self.minsize_threshold
} else if is_opt_size {
self.optsize_threshold
} else {
self.default_threshold
};
let cost = callee_size * self.cost_per_inst;
let size_delta = callee_size as i32 - 1;
InlineCost {
cost,
threshold,
always_inline: false,
never_inline: false,
size_delta, }
}
pub fn is_profitable(&self, cost: &InlineCost) -> bool {
if cost.always_inline {
return true;
}
if cost.never_inline {
return false;
}
cost.cost <= cost.threshold && cost.size_delta <= 0
}
pub fn is_legal_to_inline(
caller_name: &str,
callee_name: &str,
callee_is_vararg: bool,
callee_has_exception_handling: bool,
) -> bool {
if caller_name == callee_name {
return false;
}
if callee_is_vararg {
return true; }
if callee_has_exception_handling {
return true;
}
true
}
}
impl Default for InlineCostAnalyzer {
fn default() -> Self {
InlineCostAnalyzer::new()
}
}
#[derive(Debug, Clone)]
pub struct SpecializationCandidate {
pub function_name: String,
pub arg_index: usize,
pub constant_value: Option<i64>,
pub hotness: f64,
}
pub struct FunctionSpecializer {
pub candidates: Vec<SpecializationCandidate>,
}
impl FunctionSpecializer {
pub fn new() -> Self {
FunctionSpecializer {
candidates: Vec::new(),
}
}
pub fn analyze_argument_constancy(
&mut self,
func_name: &str,
arg_values: &[Option<i64>],
call_count: u32,
total_calls: u32,
) {
for (i, val) in arg_values.iter().enumerate() {
if let Some(const_val) = val {
let hotness = call_count as f64 / total_calls.max(1) as f64;
self.candidates.push(SpecializationCandidate {
function_name: func_name.to_string(),
arg_index: i,
constant_value: Some(*const_val),
hotness,
});
}
}
}
pub fn is_profitable(&self, candidate: &SpecializationCandidate, threshold: f64) -> bool {
candidate.hotness >= threshold
}
pub fn specialized_name(base: &str, arg_idx: usize, value: i64) -> String {
format!("{}.specialized.arg{}.const{}", base, arg_idx, value)
}
}
impl Default for FunctionSpecializer {
fn default() -> Self {
FunctionSpecializer::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FunctionClass {
Normal,
LandingPad,
Cleanup,
Filter,
Resume,
Unreachable,
NoReturn,
CXXPersonality,
}
pub struct FunctionClassifier;
impl FunctionClassifier {
pub fn classify_block(
is_landing_pad: bool,
is_cleanup: bool,
has_resume: bool,
has_unreachable: bool,
) -> FunctionClass {
if is_landing_pad && is_cleanup {
FunctionClass::Cleanup
} else if is_landing_pad {
FunctionClass::LandingPad
} else if has_resume {
FunctionClass::Resume
} else if has_unreachable {
FunctionClass::Unreachable
} else {
FunctionClass::Normal
}
}
pub fn has_exception_handling(
has_landing_pads: bool,
has_resume_blocks: bool,
has_invoke_instructions: bool,
) -> bool {
has_landing_pads || has_resume_blocks || has_invoke_instructions
}
pub fn is_noreturn(
all_paths_unreachable: bool,
calls_noreturn_only: bool,
has_infinite_loop: bool,
) -> bool {
if has_infinite_loop {
return false;
}
all_paths_unreachable || calls_noreturn_only
}
pub fn is_willreturn(
has_infinite_loop: bool,
has_exception_unwind: bool,
all_callees_willreturn: bool,
) -> bool {
!has_infinite_loop && !has_exception_unwind && all_callees_willreturn
}
pub fn is_norecurse(calls_self: bool, calls_self_indirectly: bool) -> bool {
!calls_self && !calls_self_indirectly
}
pub fn is_unreachable_sentinel(
blocks: &[&str],
block_terminators: &HashMap<String, Opcode>,
) -> bool {
if blocks.len() != 1 {
return false;
}
block_terminators.get(blocks[0]) == Some(&Opcode::Unreachable)
}
}
#[derive(Debug, Clone)]
pub struct FunctionSummary {
pub name: String,
pub linkage: Linkage,
pub visibility: Visibility,
pub module_hash: u64,
pub inst_count: u32,
pub block_count: u32,
pub callees: Vec<String>,
pub referenced_globals: Vec<String>,
pub types_used: Vec<TypeKind>,
pub is_readonly: bool,
pub is_readnone: bool,
pub is_argmemonly: bool,
pub may_throw: bool,
pub call_site_count: u32,
pub hotness: u32,
pub has_inline_hint: bool,
pub has_noinline: bool,
pub guid: u64,
}
impl FunctionSummary {
pub fn new(name: String, linkage: Linkage, visibility: Visibility) -> Self {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
let guid = hasher.finish();
FunctionSummary {
name,
linkage,
visibility,
module_hash: 0,
inst_count: 0,
block_count: 0,
callees: Vec::new(),
referenced_globals: Vec::new(),
types_used: Vec::new(),
is_readonly: false,
is_readnone: false,
is_argmemonly: false,
may_throw: false,
call_site_count: 0,
hotness: 0,
has_inline_hint: false,
has_noinline: false,
guid,
}
}
pub fn get_guid(&self) -> u64 {
self.guid
}
pub fn is_live(&self) -> bool {
!matches!(self.linkage, Linkage::AvailableExternally)
}
}
pub struct AttributeDeduction {
pub can_be_readnone: bool,
pub can_be_readonly: bool,
pub can_be_argmemonly: bool,
pub can_be_nounwind: bool,
pub can_be_willreturn: bool,
pub can_be_norecurse: bool,
pub can_be_nosync: bool,
pub can_be_nofree: bool,
pub memory_effects: MemoryEffects,
}
impl Default for AttributeDeduction {
fn default() -> Self {
AttributeDeduction {
can_be_readnone: false,
can_be_readonly: false,
can_be_argmemonly: false,
can_be_nounwind: false,
can_be_willreturn: false,
can_be_norecurse: false,
can_be_nosync: false,
can_be_nofree: false,
memory_effects: MemoryEffects::unknown(),
}
}
}
pub struct AttributeDeductionAnalyzer;
impl AttributeDeductionAnalyzer {
pub fn analyze(
has_loads: bool,
has_stores: bool,
has_atomics: bool,
has_calls: bool,
has_unwind: bool,
has_synchronization: bool,
has_free_calls: bool,
has_infinite_loops: bool,
has_global_access: bool,
has_argmem_access: bool,
callee_memory_effects: &[MemoryEffects],
) -> AttributeDeduction {
let mut result = AttributeDeduction::default();
if !has_loads && !has_stores && !has_atomics && !has_global_access && !has_argmem_access {
if callee_memory_effects
.iter()
.all(|me| me.does_not_access_memory())
{
result.can_be_readnone = true;
result.memory_effects = MemoryEffects::none();
result.can_be_readonly = true;
result.can_be_argmemonly = true;
}
}
if !result.can_be_readnone && !has_stores && !has_atomics {
if callee_memory_effects
.iter()
.all(|me| me.only_reads_memory() || me.does_not_access_memory())
{
result.can_be_readonly = true;
}
}
if !has_global_access && has_argmem_access {
result.can_be_argmemonly = true;
}
if !has_unwind {
result.can_be_nounwind = true;
}
if !has_infinite_loops {
result.can_be_willreturn = true;
}
if !has_synchronization {
result.can_be_nosync = true;
}
if !has_free_calls {
result.can_be_nofree = true;
}
for me in callee_memory_effects {
result.memory_effects = result.memory_effects.union_with(me);
}
result
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EHPersonality {
None,
GnuCXX,
GnuC,
GnuObjC,
GnuSEH,
WinSEH,
WinCXX,
WasmCXX,
Rust,
Swift,
Unknown,
}
impl EHPersonality {
pub fn from_name(name: &str) -> Self {
match name {
"__gxx_personality_v0" => EHPersonality::GnuCXX,
"__gcc_personality_v0" => EHPersonality::GnuC,
"__gnu_objc_personality_v0" => EHPersonality::GnuObjC,
"__gnu_seh_personality_v0" => EHPersonality::GnuSEH,
"__CxxFrameHandler3" | "__C_specific_handler" => EHPersonality::WinSEH,
"__CxxFrameHandler4" => EHPersonality::WinCXX,
"__gxx_wasm_personality_v0" => EHPersonality::WasmCXX,
"rust_eh_personality" => EHPersonality::Rust,
"swift_exceptionPersonality" => EHPersonality::Swift,
_ => EHPersonality::Unknown,
}
}
pub fn is_gnu_style(&self) -> bool {
matches!(
self,
EHPersonality::GnuCXX
| EHPersonality::GnuC
| EHPersonality::GnuObjC
| EHPersonality::GnuSEH
)
}
pub fn is_msvc_style(&self) -> bool {
matches!(self, EHPersonality::WinSEH | EHPersonality::WinCXX)
}
pub fn is_wasm_style(&self) -> bool {
matches!(self, EHPersonality::WasmCXX)
}
pub fn uses_itanium_eh(&self) -> bool {
self.is_gnu_style()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dead_arg_elimination() {
let mut uses: HashMap<String, Vec<String>> = HashMap::new();
uses.insert("arg0".to_string(), vec!["inst1".to_string()]);
uses.insert("arg1".to_string(), vec![]);
assert!(DeadArgumentElimination::is_arg_used("arg0", &uses));
assert!(!DeadArgumentElimination::is_arg_used("arg1", &uses));
}
#[test]
fn test_calling_conv_classification() {
assert!(CallingConventionInfo::is_kernel_cc(
CallingConvention::PTXKernel
));
assert!(!CallingConventionInfo::is_kernel_cc(CallingConvention::C));
assert!(CallingConventionInfo::is_interrupt_cc(
CallingConvention::MSP430Intr
));
assert!(!CallingConventionInfo::is_interrupt_cc(
CallingConvention::Fast
));
}
#[test]
fn test_inline_cost_always_inline() {
let analyzer = InlineCostAnalyzer::new();
let cost = analyzer.analyze_call(10, 5, true, false, false, false);
assert!(cost.always_inline);
assert!(analyzer.is_profitable(&cost));
}
#[test]
fn test_inline_cost_never_inline() {
let analyzer = InlineCostAnalyzer::new();
let cost = analyzer.analyze_call(10, 5, false, true, false, false);
assert!(cost.never_inline);
assert!(!analyzer.is_profitable(&cost));
}
#[test]
fn test_inline_cost_normal() {
let analyzer = InlineCostAnalyzer::new();
let cost = analyzer.analyze_call(10, 8, false, false, false, false);
assert!(!cost.always_inline);
assert!(!cost.never_inline);
}
#[test]
fn test_inline_legal_same_name() {
assert!(!InlineCostAnalyzer::is_legal_to_inline(
"foo", "foo", false, false
));
}
#[test]
fn test_inline_legal_different() {
assert!(InlineCostAnalyzer::is_legal_to_inline(
"foo", "bar", false, false
));
}
#[test]
fn test_function_merger_hash() {
let h1 = FunctionMerger::compute_signature_hash(
&TypeKind::Integer { bits: 32 },
&[
TypeKind::Integer { bits: 32 },
TypeKind::Integer { bits: 64 },
],
false,
);
let h2 = FunctionMerger::compute_signature_hash(
&TypeKind::Integer { bits: 32 },
&[
TypeKind::Integer { bits: 32 },
TypeKind::Integer { bits: 64 },
],
false,
);
assert_eq!(h1, h2);
}
#[test]
fn test_noreturn_detection() {
assert!(FunctionClassifier::is_noreturn(true, false, false));
assert!(!FunctionClassifier::is_noreturn(false, false, true));
}
#[test]
fn test_willreturn_detection() {
assert!(FunctionClassifier::is_willreturn(false, false, true));
assert!(!FunctionClassifier::is_willreturn(true, false, true));
}
#[test]
fn test_eh_personality() {
assert_eq!(
EHPersonality::from_name("__gxx_personality_v0"),
EHPersonality::GnuCXX
);
assert!(EHPersonality::GnuCXX.is_gnu_style());
assert!(!EHPersonality::WinCXX.is_gnu_style());
}
#[test]
fn test_attribute_deduction_readnone() {
let result = AttributeDeductionAnalyzer::analyze(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
&[],
);
assert!(result.can_be_readnone);
assert!(result.can_be_nounwind);
}
#[test]
fn test_attribute_deduction_with_loads() {
let result = AttributeDeductionAnalyzer::analyze(
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
&[],
);
assert!(!result.can_be_readnone);
}
#[test]
fn test_function_summary() {
let summary =
FunctionSummary::new("test".to_string(), Linkage::External, Visibility::Default);
assert_eq!(summary.name, "test");
assert!(summary.is_live());
}
#[test]
fn test_specialization() {
let mut spec = FunctionSpecializer::new();
spec.analyze_argument_constancy("foo", &[Some(42), None], 80, 100);
assert!(!spec.candidates.is_empty());
assert!(spec.is_profitable(&spec.candidates[0], 0.5));
}
}