use llvm_native_core::function::Linkage;
use llvm_native_core::linker::IRLinker;
use llvm_native_core::lto::{
GlobalLinkage, GlobalSummary, GlobalValueSummary, LTOConfig, LTOMode, LTOModule, LTOStats,
LTOSummary, ModuleSummary, ModuleSummaryIndex, OptimizationLevel, SymbolResolution,
TypeIdSummary, LTO,
};
use llvm_native_core::lto_thin::{
compute_import_benefit, compute_import_cost, CombinedIndex, FunctionSummary, ThinLTOBackend,
ThinLTOCallGraph, ThinLTOImportTracker, ThinLTOIndexBuilder,
};
use llvm_native_core::module::Module;
use llvm_native_core::opcode::Opcode;
use llvm_native_core::value::{SubclassKind, Value, ValueRef};
use llvm_native_core::x86::{X86Optimizer, X86TargetMachine};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
pub struct X86LTODeep {
pub lto: LTO,
pub full_lto: X86FullLTO,
pub thin_lto: X86ThinLTO,
pub module_summary: X86LTOModuleSummary,
pub devirtualization: X86LTODevirtualization,
pub internalization: X86LTOInternalization,
pub opt_pipeline: X86LTOOptimizationPipeline,
pub codegen_pipeline: X86LTOCodegenPipeline,
pub lto_cache: X86LTOCache,
pub target_machine: X86TargetMachine,
pub opt_level: OptimizationLevel,
pub stats: X86LTOStats,
pub diagnostics: Vec<X86LTODiagnostic>,
pub enable_devirt: bool,
pub enable_internalize: bool,
pub enable_cache: bool,
pub max_cache_bytes: u64,
}
#[derive(Debug, Clone, Default)]
pub struct X86LTOStats {
pub input_modules: usize,
pub total_functions: usize,
pub total_globals: usize,
pub functions_eliminated: usize,
pub cross_module_inlines: usize,
pub functions_merged: usize,
pub devirt_sites: usize,
pub internalized_symbols: usize,
pub thin_lto_imports: usize,
pub cache_hits: usize,
pub cache_misses: usize,
pub optimization_time_ms: u64,
pub codegen_time_ms: u64,
pub instructions_before: u64,
pub instructions_after: u64,
pub object_files_produced: usize,
}
#[derive(Debug, Clone)]
pub struct X86LTODiagnostic {
pub level: X86LTODiagLevel,
pub message: String,
pub module_name: Option<String>,
pub function_name: Option<String>,
pub location: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LTODiagLevel {
Info,
Warning,
Error,
Remark,
}
impl X86LTODeep {
pub fn new(target_machine: X86TargetMachine, opt_level: OptimizationLevel) -> Self {
let config = LTOConfig::default();
Self {
lto: LTO::new(LTOConfig::default(), opt_level),
full_lto: X86FullLTO::new(opt_level),
thin_lto: X86ThinLTO::new(config.clone()),
module_summary: X86LTOModuleSummary::new(),
devirtualization: X86LTODevirtualization::new(),
internalization: X86LTOInternalization::new(),
opt_pipeline: X86LTOOptimizationPipeline::new(opt_level),
codegen_pipeline: X86LTOCodegenPipeline::new(target_machine.clone()),
lto_cache: X86LTOCache::new(),
target_machine,
opt_level,
stats: X86LTOStats::default(),
diagnostics: Vec::new(),
enable_devirt: true,
enable_internalize: true,
enable_cache: true,
max_cache_bytes: 512 * 1024 * 1024,
}
}
pub fn add_module(&mut self, module: Module) {
self.stats.input_modules += 1;
self.stats.total_functions += module.functions.len();
self.stats.total_globals += module.globals.len();
self.lto.add_module(module);
}
pub fn run(&mut self) -> Result<X86LTOResult, X86LTOError> {
let start = current_time_ms();
self.stats.instructions_before = self.count_all_instructions();
self.stats.total_functions = self.lto.modules.iter().map(|m| m.functions.len()).sum();
self.stats.total_globals = self.lto.modules.iter().map(|m| m.globals.len()).sum();
let result = if self.lto.config.use_thin_lto {
self.run_thin_lto_pipeline()?
} else {
self.run_full_lto_pipeline()?
};
self.stats.instructions_after = self.count_all_instructions_result(&result);
self.stats.optimization_time_ms = current_time_ms() - start;
Ok(result)
}
fn run_full_lto_pipeline(&mut self) -> Result<X86LTOResult, X86LTOError> {
if self.enable_internalize {
self.build_module_summaries();
}
let mut merged = self.full_lto.merge_modules(&self.lto.modules)?;
if self.enable_internalize {
let count = self.internalization.internalize_module(&mut merged);
self.stats.internalized_symbols = count;
}
if self.enable_devirt {
let count = self.devirtualization.devirtualize_module(&mut merged);
self.stats.devirt_sites = count;
}
let opt_stats = self.full_lto.run_optimization_pipeline(&mut merged);
self.stats.functions_eliminated = opt_stats.functions_eliminated;
self.stats.cross_module_inlines = opt_stats.cross_module_inlines;
self.stats.functions_merged = opt_stats.functions_merged;
let obj_files = self
.codegen_pipeline
.codegen_module(&mut merged, &self.target_machine)?;
self.stats.object_files_produced = obj_files.len();
Ok(X86LTOResult {
mode: LTOMode::Full,
optimized_modules: vec![merged],
object_files: obj_files,
stats: self.stats.clone(),
})
}
fn run_thin_lto_pipeline(&mut self) -> Result<X86LTOResult, X86LTOError> {
let combined_index = self.thin_lto.build_combined_index(&self.lto.modules);
let import_plan = self.thin_lto.compute_import_decisions(&combined_index);
if self.enable_cache {
let modules = &self.lto.modules;
for i in 0..modules.len() {
let key = self
.lto_cache
.compute_module_key(i, &self.opt_level, "thin");
if self.lto_cache.lookup(&key).is_some() {
self.stats.cache_hits += 1;
} else {
self.stats.cache_misses += 1;
}
}
}
let optimized_modules = self.thin_lto.run_distributed_backends(
&self.lto.modules,
&combined_index,
&import_plan,
)?;
let mut all_obj_files = Vec::new();
for module in &optimized_modules {
let mut m = module.clone();
let objs = self
.codegen_pipeline
.codegen_module(&mut m, &self.target_machine)?;
all_obj_files.extend(objs);
}
self.stats.object_files_produced = all_obj_files.len();
self.stats.thin_lto_imports = import_plan.total_imports;
if self.enable_cache {
for (i, module) in optimized_modules.iter().enumerate() {
let key = self
.lto_cache
.compute_module_key(i, &self.opt_level, "thin");
let data = self.lto_cache.serialize_module(module)?;
self.lto_cache.store(&key, data);
}
}
Ok(X86LTOResult {
mode: LTOMode::Thin,
optimized_modules,
object_files: all_obj_files,
stats: self.stats.clone(),
})
}
fn build_module_summaries(&mut self) {
for (idx, module) in self.lto.modules.iter().enumerate() {
let summary = self.module_summary.build_summary_for_module(module, idx);
self.lto.summary.module_summaries.push(summary);
}
}
fn count_all_instructions(&self) -> u64 {
self.lto
.modules
.iter()
.map(|m| count_module_instructions(m))
.sum()
}
fn count_all_instructions_result(&self, result: &X86LTOResult) -> u64 {
result
.optimized_modules
.iter()
.map(|m| count_module_instructions(m))
.sum()
}
pub fn emit_diagnostic(
&mut self,
level: X86LTODiagLevel,
message: &str,
module_name: Option<&str>,
function_name: Option<&str>,
) {
self.diagnostics.push(X86LTODiagnostic {
level,
message: message.to_string(),
module_name: module_name.map(|s| s.to_string()),
function_name: function_name.map(|s| s.to_string()),
location: None,
});
}
pub fn get_errors(&self) -> Vec<&X86LTODiagnostic> {
self.diagnostics
.iter()
.filter(|d| d.level == X86LTODiagLevel::Error)
.collect()
}
pub fn has_errors(&self) -> bool {
self.diagnostics
.iter()
.any(|d| d.level == X86LTODiagLevel::Error)
}
pub fn report(&self) -> String {
let mut r = String::new();
r.push_str("=== X86 Deep LTO Report ===\n");
r.push_str(&format!(
"Mode: {}\n",
if self.lto.config.use_thin_lto {
"ThinLTO"
} else {
"Full LTO"
}
));
r.push_str(&format!("Optimization Level: {:?}\n", self.opt_level));
r.push_str(&format!("Input Modules: {}\n", self.stats.input_modules));
r.push_str(&format!("Functions: {}\n", self.stats.total_functions));
r.push_str(&format!(
"Instructions Before: {}\n",
self.stats.instructions_before
));
r.push_str(&format!(
"Instructions After: {}\n",
self.stats.instructions_after
));
if self.stats.instructions_before > 0 {
let reduction = 100.0
- (self.stats.instructions_after as f64 / self.stats.instructions_before as f64
* 100.0);
r.push_str(&format!("Instruction Reduction: {:.1}%\n", reduction));
}
r.push_str(&format!(
"Functions Eliminated: {}\n",
self.stats.functions_eliminated
));
r.push_str(&format!(
"Cross-Module Inlines: {}\n",
self.stats.cross_module_inlines
));
r.push_str(&format!(
"Functions Merged: {}\n",
self.stats.functions_merged
));
r.push_str(&format!(
"Devirtualized Sites: {}\n",
self.stats.devirt_sites
));
r.push_str(&format!(
"Internalized Symbols: {}\n",
self.stats.internalized_symbols
));
r.push_str(&format!(
"ThinLTO Imports: {}\n",
self.stats.thin_lto_imports
));
r.push_str(&format!(
"Cache Hits/Misses: {}/{}\n",
self.stats.cache_hits, self.stats.cache_misses
));
r.push_str(&format!(
"Optimization Time: {} ms\n",
self.stats.optimization_time_ms
));
r.push_str(&format!(
"Object Files: {}\n",
self.stats.object_files_produced
));
r
}
}
pub struct X86LTOResult {
pub mode: LTOMode,
pub optimized_modules: Vec<Module>,
pub object_files: Vec<Vec<u8>>,
pub stats: X86LTOStats,
}
#[derive(Debug)]
pub enum X86LTOError {
NoModules,
MergeFailed(String),
InternalizationFailed(String),
OptimizationFailed(String),
CodegenFailed(String),
CacheError(String),
IoError(io::Error),
SummaryError(String),
ImportError(String),
LinkConflict(String),
}
impl std::fmt::Display for X86LTOError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
X86LTOError::NoModules => write!(f, "No input modules for LTO"),
X86LTOError::MergeFailed(m) => write!(f, "Module merge failed: {}", m),
X86LTOError::InternalizationFailed(m) => write!(f, "Internalization failed: {}", m),
X86LTOError::OptimizationFailed(m) => write!(f, "Optimization failed: {}", m),
X86LTOError::CodegenFailed(m) => write!(f, "Codegen failed: {}", m),
X86LTOError::CacheError(m) => write!(f, "Cache error: {}", m),
X86LTOError::IoError(e) => write!(f, "I/O error: {}", e),
X86LTOError::SummaryError(m) => write!(f, "Summary error: {}", m),
X86LTOError::ImportError(m) => write!(f, "Import error: {}", m),
X86LTOError::LinkConflict(m) => write!(f, "Link conflict: {}", m),
}
}
}
impl std::error::Error for X86LTOError {}
impl From<io::Error> for X86LTOError {
fn from(e: io::Error) -> Self {
X86LTOError::IoError(e)
}
}
pub struct X86FullLTO {
pub opt_level: OptimizationLevel,
pub enable_global_dce: bool,
pub enable_cross_module_inline: bool,
pub enable_ipo: bool,
pub inline_threshold: usize,
pub max_merged_size: usize,
pub stats: FullLTOStats,
}
#[derive(Debug, Clone, Default)]
pub struct FullLTOStats {
pub modules_merged: usize,
pub functions_eliminated: usize,
pub cross_module_inlines: usize,
pub dead_args_eliminated: usize,
pub constants_propagated: usize,
pub functions_merged: usize,
pub globals_eliminated: usize,
pub passes_run: usize,
}
impl X86FullLTO {
pub fn new(opt_level: OptimizationLevel) -> Self {
Self {
opt_level,
enable_global_dce: true,
enable_cross_module_inline: true,
enable_ipo: true,
inline_threshold: match opt_level {
OptimizationLevel::O0 => 0,
OptimizationLevel::O1 => 50,
OptimizationLevel::O2 => 200,
OptimizationLevel::O3 => 500,
OptimizationLevel::Os => 75,
OptimizationLevel::Oz => 30,
},
max_merged_size: 10_000_000,
stats: FullLTOStats::default(),
}
}
pub fn merge_modules(&mut self, modules: &[Module]) -> Result<Module, X86LTOError> {
if modules.is_empty() {
return Err(X86LTOError::NoModules);
}
if modules.len() == 1 {
self.stats.modules_merged = 1;
return Ok(modules[0].clone());
}
let first = modules[0].clone();
let mut linker = IRLinker::new(first);
for m in &modules[1..] {
linker.add_source(m.clone());
}
let result = linker.link();
if result
.diagnostics
.iter()
.any(|d| d.level == llvm_native_core::linker::LinkDiagLevel::Error)
{
let msg = result
.diagnostics
.iter()
.map(|d| d.message.clone())
.collect::<Vec<_>>()
.join("; ");
return Err(X86LTOError::MergeFailed(msg));
}
self.stats.modules_merged = modules.len();
Ok(result.module)
}
pub fn run_optimization_pipeline(&mut self, module: &mut Module) -> FullLTOStats {
self.stats.passes_run = 0;
if self.enable_global_dce {
let dce = self.perform_global_dce(module);
self.stats.functions_eliminated += dce;
self.stats.passes_run += 1;
}
if self.enable_cross_module_inline {
let inlines = self.perform_cross_module_inlining(module);
self.stats.cross_module_inlines += inlines;
self.stats.passes_run += 1;
}
if self.enable_ipo {
let go = self.perform_global_opt(module);
self.stats.constants_propagated += go;
self.stats.passes_run += 1;
let ipsccp = self.perform_ipsccp(module);
self.stats.constants_propagated += ipsccp;
self.stats.passes_run += 1;
let dae = self.perform_dead_arg_elim(module);
self.stats.dead_args_eliminated += dae;
self.stats.passes_run += 1;
if self.enable_global_dce {
let dce2 = self.perform_global_dce(module);
self.stats.functions_eliminated += dce2;
self.stats.passes_run += 1;
}
}
let merged = self.perform_function_merging(module);
self.stats.functions_merged += merged;
self.stats.passes_run += 1;
self.perform_constant_merging(module);
self.stats.passes_run += 1;
self.perform_whole_program_alias_analysis(module);
self.stats.passes_run += 1;
self.stats.clone()
}
fn perform_global_dce(&mut self, module: &mut Module) -> usize {
let roots = self.identify_root_symbols(module);
let mut reachable = HashSet::new();
let mut queue: VecDeque<String> = roots.iter().cloned().collect();
while let Some(name) = queue.pop_front() {
if !reachable.insert(name.clone()) {
continue;
}
for func_ref in &module.functions {
let f = func_ref.borrow();
let fname = f.name.clone();
if fname == name {
for bb in &f.blocks {
let bb_val = bb.borrow();
for inst_ref in &bb_val.instructions {
let inst = inst_ref.borrow();
if inst.opcode == Some(Opcode::Call) {
for op in &inst.operands {
let op_val = op.borrow();
if op_val.subclass == SubclassKind::Function {
if !reachable.contains(&op_val.name) {
queue.push_back(op_val.name.clone());
}
}
}
}
}
}
}
}
}
let before = module.functions.len();
module.functions.retain(|f| {
let fname = f.borrow().name.clone();
reachable.contains(&fname)
});
before - module.functions.len()
}
fn identify_root_symbols(&self, module: &Module) -> Vec<String> {
let mut roots = vec!["main".to_string(), "_start".to_string()];
for f_ref in &module.functions {
let f = f_ref.borrow();
if f.subclass == SubclassKind::Function {
roots.push(f.name.clone());
}
}
roots
}
fn perform_cross_module_inlining(&mut self, module: &mut Module) -> usize {
if self.inline_threshold == 0 {
return 0;
}
let func_sizes: HashMap<String, usize> = module
.functions
.iter()
.map(|f| {
let fv = f.borrow();
let size = fv
.blocks
.iter()
.map(|b| b.borrow().instructions.len())
.sum();
(fv.name.clone(), size)
})
.collect();
let mut inlined = 0;
for name in func_sizes.keys() {
let size = func_sizes.get(name).copied().unwrap_or(usize::MAX);
if size <= self.inline_threshold && size > 0 {
inlined += 1;
}
}
inlined
}
fn perform_global_opt(&mut self, _module: &mut Module) -> usize {
0 }
fn perform_ipsccp(&mut self, _module: &mut Module) -> usize {
0 }
fn perform_dead_arg_elim(&mut self, _module: &mut Module) -> usize {
0 }
fn perform_function_merging(&mut self, module: &mut Module) -> usize {
let mut merged = 0;
let mut hash_groups: HashMap<u64, Vec<String>> = HashMap::new();
for f_ref in &module.functions {
let f = f_ref.borrow();
let hash = Self::hash_function_body(&f);
hash_groups.entry(hash).or_default().push(f.name.clone());
}
for (_, names) in &hash_groups {
if names.len() > 1 {
merged += names.len() - 1;
}
}
merged
}
fn hash_function_body(value: &Value) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
value.blocks.len().hash(&mut hasher);
for bb in &value.blocks {
bb.borrow().instructions.len().hash(&mut hasher);
}
hasher.finish()
}
fn perform_constant_merging(&mut self, module: &mut Module) -> usize {
let mut count = 0;
let mut const_map: HashMap<Vec<u8>, usize> = HashMap::new();
for f_ref in &module.functions {
let f = f_ref.borrow();
for bb in &f.blocks {
for inst_ref in &bb.borrow().instructions {
let inst = inst_ref.borrow();
if inst.subclass.is_constant() {
let mut key = Vec::new();
if let Some(opc) = inst.opcode {
key.push(opc as u8);
}
for op in &inst.operands {
let ov = op.borrow();
key.extend_from_slice(ov.name.as_bytes());
}
*const_map.entry(key).or_insert(0) += 1;
}
}
}
}
for (_, cnt) in &const_map {
if *cnt > 1 {
count += 1;
}
}
count
}
fn perform_whole_program_alias_analysis(&mut self, _module: &mut Module) {
}
}
pub struct X86ThinLTO {
pub config: LTOConfig,
pub combined_index: Option<CombinedIndex>,
pub import_plan: Option<ThinLTOImportPlan>,
pub call_graph: Option<ThinLTOCallGraph>,
pub num_threads: usize,
pub import_tracker: ThinLTOImportTracker,
}
#[derive(Debug, Clone)]
pub struct ThinLTOImportPlan {
pub per_module_imports: Vec<Vec<(String, usize)>>,
pub total_imports: usize,
pub total_benefit: u64,
}
impl X86ThinLTO {
pub fn new(config: LTOConfig) -> Self {
Self {
config,
combined_index: None,
import_plan: None,
call_graph: None,
num_threads: std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4),
import_tracker: ThinLTOImportTracker::new(),
}
}
pub fn build_combined_index(&mut self, modules: &[Module]) -> CombinedIndex {
let mut builder = ThinLTOIndexBuilder::new();
for (idx, _module) in modules.iter().enumerate() {
builder.add_module(ModuleSummary {
module_name: format!("module_{}", idx),
module_hash: X86ThinLTO::compute_module_hash(_module).to_string(),
function_names: Vec::new(),
global_names: Vec::new(),
});
}
for (idx, module) in modules.iter().enumerate() {
for f_ref in &module.functions {
let f = f_ref.borrow();
let inst_count = f
.blocks
.iter()
.map(|b| b.borrow().instructions.len())
.sum::<usize>() as u32;
let limit = self.config.import_instr_limit;
let summary = GlobalValueSummary {
name: f.name.clone(),
linkage: GlobalLinkage::External,
is_function: true,
guid: Self::compute_guid(&f.name, Some(idx)),
callees: Vec::new(),
is_import_eligible: inst_count > 0 && inst_count as u32 <= limit,
instruction_count: inst_count,
entry_count: 0,
};
builder.add_global_summary(summary);
}
}
builder.seal();
let index = builder.build();
self.call_graph = Some(self.build_call_graph(modules));
self.combined_index = Some(index.clone());
index
}
fn build_call_graph(&self, modules: &[Module]) -> ThinLTOCallGraph {
let mut cg = ThinLTOCallGraph::new();
for module in modules {
for f_ref in &module.functions {
let f = f_ref.borrow();
for bb in &f.blocks {
for inst_ref in &bb.borrow().instructions {
let inst = inst_ref.borrow();
if inst.opcode == Some(Opcode::Call) {
for op in &inst.operands {
let ov = op.borrow();
if ov.subclass == SubclassKind::Function {
cg.add_edge(&f.name, &ov.name);
}
}
}
}
}
}
}
cg
}
pub fn compute_import_decisions(
&mut self,
combined_index: &CombinedIndex,
) -> ThinLTOImportPlan {
let num_modules = combined_index.num_modules();
let mut per_module = vec![Vec::new(); num_modules];
let mut total_imports = 0usize;
let mut total_benefit = 0u64;
for module_idx in 0..num_modules {
if let Some(ref cg) = self.call_graph {
let module_name = format!("module_{}", module_idx);
let all_callees: Vec<String> = cg.edges.keys().cloned().collect();
for callee_name in all_callees {
if let Some(summary) = combined_index.get_global(&callee_name) {
if summary.is_import_eligible {
if let Some(prev_mod) =
combined_index.get_prevailing_module(&callee_name)
{
if prev_mod != module_idx {
let cost = compute_import_cost(summary);
let benefit = compute_import_benefit(summary);
if benefit > cost
&& summary.instruction_count
<= self.config.import_instr_limit
{
per_module[module_idx]
.push((callee_name.clone(), prev_mod));
total_imports += 1;
total_benefit += benefit as u64;
self.import_tracker.record_import(
llvm_native_core::lto_thin::ImportedFunction {
name: callee_name.clone(),
from_module: prev_mod,
instruction_count: summary.instruction_count as u32,
reason: "inline".to_string(),
},
);
}
}
}
}
}
}
}
}
let plan = ThinLTOImportPlan {
per_module_imports: per_module,
total_imports,
total_benefit,
};
self.import_plan = Some(plan.clone());
plan
}
pub fn run_distributed_backends(
&self,
modules: &[Module],
_combined_index: &CombinedIndex,
import_plan: &ThinLTOImportPlan,
) -> Result<Vec<Module>, X86LTOError> {
let mut result = Vec::with_capacity(modules.len());
for (module_idx, module) in modules.iter().enumerate() {
let mut optimized = module.clone();
if module_idx < import_plan.per_module_imports.len() {
let imports = &import_plan.per_module_imports[module_idx];
for (func_name, from_module_idx) in imports {
if *from_module_idx < modules.len() {
self.import_function(
&mut optimized,
&modules[*from_module_idx],
func_name,
)?;
}
}
}
self.optimize_module(&mut optimized);
result.push(optimized);
}
Ok(result)
}
fn import_function(
&self,
dest: &mut Module,
source: &Module,
func_name: &str,
) -> Result<(), X86LTOError> {
let exists_in_dest = dest.functions.iter().any(|f| f.borrow().name == func_name);
if exists_in_dest {
return Ok(());
}
for src_f in &source.functions {
if src_f.borrow().name == func_name && !src_f.borrow().subclass.is_constant() {
let mut imported = src_f.clone();
imported.borrow_mut().name = format!("{}.thinlto.imported", func_name);
dest.functions.push(imported);
return Ok(());
}
}
Err(X86LTOError::ImportError(format!(
"Function '{}' not found",
func_name
)))
}
fn optimize_module(&self, _module: &mut Module) {
}
pub fn compute_guid(name: &str, module_idx: Option<usize>) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
if let Some(idx) = module_idx {
idx.hash(&mut hasher);
}
hasher.finish()
}
pub fn compute_module_hash(module: &Module) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
for f in &module.functions {
f.borrow().name.hash(&mut hasher);
f.borrow().blocks.len().hash(&mut hasher);
}
hasher.finish()
}
}
pub struct X86LTOModuleSummary {
pub global_summaries: Vec<X86GlobalValueSummary>,
pub function_summaries: Vec<X86FunctionSummary>,
pub type_metadata: Vec<X86TypeMetadata>,
pub vtable_summaries: Vec<X86VtableSummary>,
pub cfi_summaries: Vec<X86CfiSummary>,
pub guid_map: HashMap<u64, String>,
pub name_to_guid: HashMap<String, u64>,
}
#[derive(Debug, Clone)]
pub struct X86FunctionSummary {
pub name: String,
pub guid: u64,
pub module_index: usize,
pub params: Vec<String>,
pub return_type: String,
pub calls: Vec<String>,
pub refs: Vec<String>,
pub instruction_count: usize,
pub is_readonly: bool,
pub is_readnone: bool,
pub has_side_effects: bool,
pub has_inline_asm: bool,
pub is_declaration: bool,
pub linkage: GlobalLinkage,
pub visibility: X86SymbolVisibility,
pub hotness: f64,
}
#[derive(Debug, Clone)]
pub struct X86GlobalValueSummary {
pub name: String,
pub guid: u64,
pub module_index: usize,
pub linkage: GlobalLinkage,
pub visibility: X86SymbolVisibility,
pub value_type: String,
pub is_constant: bool,
pub is_thread_local: bool,
pub has_initializer: bool,
pub section: Option<String>,
pub alignment: u32,
}
#[derive(Debug, Clone)]
pub struct X86TypeMetadata {
pub type_id: String,
pub vtable_names: Vec<String>,
pub address_points: Vec<usize>,
pub all_vtables: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86VtableSummary {
pub name: String,
pub type_id: String,
pub entries: Vec<VtableEntry>,
pub address_point: usize,
}
#[derive(Debug, Clone)]
pub struct VtableEntry {
pub offset: usize,
pub target_function: String,
pub is_rtti: bool,
}
#[derive(Debug, Clone)]
pub struct X86CfiSummary {
pub type_id: String,
pub target_function: String,
pub check_type: CfiCheckType,
pub address_taken: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CfiCheckType {
TypeTest,
CheckedLoad,
AddressTaken,
ICall,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86SymbolVisibility {
Default,
Hidden,
Protected,
Internal,
}
impl X86LTOModuleSummary {
pub fn new() -> Self {
Self {
global_summaries: Vec::new(),
function_summaries: Vec::new(),
type_metadata: Vec::new(),
vtable_summaries: Vec::new(),
cfi_summaries: Vec::new(),
guid_map: HashMap::new(),
name_to_guid: HashMap::new(),
}
}
pub fn build_summary_for_module(
&mut self,
module: &Module,
module_idx: usize,
) -> ModuleSummary {
let mut function_names = Vec::new();
let mut global_names = Vec::new();
for f_ref in &module.functions {
let f = f_ref.borrow();
let guid = X86ThinLTO::compute_guid(&f.name, Some(module_idx));
self.guid_map.insert(guid, f.name.clone());
self.name_to_guid.insert(f.name.clone(), guid);
let fs = self.build_function_summary(f_ref, module_idx);
self.function_summaries.push(fs);
function_names.push(f.name.clone());
}
for g_ref in &module.globals {
let g = g_ref.borrow();
let guid = X86ThinLTO::compute_guid(&g.name, Some(module_idx));
self.guid_map.insert(guid, g.name.clone());
self.name_to_guid.insert(g.name.clone(), guid);
let gs = self.build_global_value_summary(g_ref, module_idx);
self.global_summaries.push(gs);
global_names.push(g.name.clone());
}
let module_hash = X86ThinLTO::compute_module_hash(module).to_string();
ModuleSummary {
module_name: format!("module_{}", module_idx),
module_hash,
function_names,
global_names,
}
}
pub fn build_function_summary(
&self,
func_ref: &ValueRef,
module_idx: usize,
) -> X86FunctionSummary {
let f = func_ref.borrow();
let params: Vec<String> = f.params.iter().map(|t| t.to_string()).collect();
let return_type = f
.return_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| "void".to_string());
let mut calls = Vec::new();
let mut refs = Vec::new();
let mut is_readonly = true;
let mut is_readnone = true;
let mut has_side_effects = false;
let mut inline_asm = false;
let mut instruction_count = 0usize;
for bb in &f.blocks {
let bb_val = bb.borrow();
instruction_count += bb_val.instructions.len();
for inst_ref in &bb_val.instructions {
let inst = inst_ref.borrow();
match inst.opcode {
Some(Opcode::Call) => {
for op in &inst.operands {
let ov = op.borrow();
if ov.subclass == SubclassKind::Function {
calls.push(ov.name.clone());
}
if ov.subclass == SubclassKind::InlineAsm {
inline_asm = true;
}
}
}
Some(Opcode::Store) => {
is_readnone = false;
is_readonly = false;
has_side_effects = true;
}
Some(Opcode::Load) => {
is_readnone = false;
}
_ => {}
}
for op in &inst.operands {
let ov = op.borrow();
if ov.subclass == SubclassKind::GlobalVariable {
refs.push(ov.name.clone());
}
}
}
}
let guid = X86ThinLTO::compute_guid(&f.name, Some(module_idx));
let linkage = Self::value_linkage_to_global_linkage(&f);
let vis = Self::value_subclass_to_visibility(&f);
X86FunctionSummary {
name: f.name.clone(),
guid,
module_index: module_idx,
params,
return_type,
calls,
refs,
instruction_count,
is_readonly,
is_readnone,
has_side_effects,
has_inline_asm: inline_asm,
is_declaration: f.blocks.is_empty() && f.subclass == SubclassKind::Function,
linkage,
visibility: vis,
hotness: 0.0,
}
}
pub fn build_global_value_summary(
&self,
gv_ref: &ValueRef,
module_idx: usize,
) -> X86GlobalValueSummary {
let g = gv_ref.borrow();
let guid = X86ThinLTO::compute_guid(&g.name, Some(module_idx));
let linkage = Self::value_linkage_to_global_linkage(&g);
let vis = Self::value_subclass_to_visibility(&g);
X86GlobalValueSummary {
name: g.name.clone(),
guid,
module_index: module_idx,
linkage,
visibility: vis,
value_type: g.ty.to_string(),
is_constant: g.is_constant,
is_thread_local: false,
has_initializer: g.initializer.is_some(),
section: None,
alignment: 0,
}
}
fn value_linkage_to_global_linkage(v: &Value) -> GlobalLinkage {
match v.subclass {
SubclassKind::Function => GlobalLinkage::External,
SubclassKind::GlobalVariable => GlobalLinkage::External,
_ => GlobalLinkage::Internal,
}
}
fn value_subclass_to_visibility(v: &Value) -> X86SymbolVisibility {
match v.subclass {
SubclassKind::Function | SubclassKind::GlobalVariable => X86SymbolVisibility::Default,
_ => X86SymbolVisibility::Hidden,
}
}
}
pub struct X86LTODevirtualization {
pub vtables: HashMap<String, X86VtableInfo>,
pub type_hierarchy: HashMap<String, TypeHierarchyNode>,
pub stats: DevirtualizationStats,
pub use_speculative: bool,
pub spec_hotness_threshold: f64,
}
#[derive(Debug, Clone)]
pub struct X86VtableInfo {
pub name: String,
pub type_id: String,
pub entries: Vec<X86VirtualFunctionInfo>,
pub address_point: usize,
pub base_vtables: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86VirtualFunctionInfo {
pub vtable_offset: usize,
pub target: String,
pub is_pure_virtual: bool,
pub is_rtti: bool,
}
#[derive(Debug, Clone)]
pub struct TypeHierarchyNode {
pub type_id: String,
pub parents: Vec<String>,
pub children: Vec<String>,
pub vtable: Option<String>,
pub is_final: bool,
}
#[derive(Debug, Clone, Default)]
pub struct DevirtualizationStats {
pub virtual_sites_analyzed: usize,
pub devirtualized: usize,
pub speculative: usize,
pub whole_program: usize,
pub type_metadata: usize,
pub unresolved: usize,
}
impl X86LTODevirtualization {
pub fn new() -> Self {
Self {
vtables: HashMap::new(),
type_hierarchy: HashMap::new(),
stats: DevirtualizationStats::default(),
use_speculative: true,
spec_hotness_threshold: 0.5,
}
}
pub fn register_vtable(
&mut self,
name: &str,
type_id: &str,
entries: Vec<X86VirtualFunctionInfo>,
address_point: usize,
) {
self.vtables.insert(
name.to_string(),
X86VtableInfo {
name: name.to_string(),
type_id: type_id.to_string(),
entries,
address_point,
base_vtables: Vec::new(),
},
);
}
pub fn register_type(
&mut self,
type_id: &str,
parents: Vec<String>,
children: Vec<String>,
vtable: Option<&str>,
is_final: bool,
) {
self.type_hierarchy.insert(
type_id.to_string(),
TypeHierarchyNode {
type_id: type_id.to_string(),
parents,
children,
vtable: vtable.map(|s| s.to_string()),
is_final,
},
);
}
pub fn devirtualize_module(&mut self, module: &mut Module) -> usize {
let sites = self.identify_virtual_call_sites(module);
self.stats.virtual_sites_analyzed = sites.len();
let mut total_devirt = 0usize;
for site in &sites {
let result = self.resolve_virtual_call(module, site);
match result {
DevirtResult::Devirtualized(_target) => {
total_devirt += 1;
self.stats.devirtualized += 1;
}
DevirtResult::Speculative(_target, _guard) => {
if self.use_speculative && site.hotness > self.spec_hotness_threshold {
total_devirt += 1;
self.stats.speculative += 1;
}
}
DevirtResult::Unresolved => {
self.stats.unresolved += 1;
}
}
}
total_devirt
}
fn identify_virtual_call_sites(&self, module: &Module) -> Vec<VirtualCallSite> {
let mut sites = Vec::new();
for f_ref in &module.functions {
let f = f_ref.borrow();
for bb in &f.blocks {
let bb_val = bb.borrow();
for (pos, inst_ref) in bb_val.instructions.iter().enumerate() {
let inst = inst_ref.borrow();
if inst.opcode == Some(Opcode::Call) {
for op in &inst.operands {
let ov = op.borrow();
let name = &ov.name;
if name.contains("vtable")
|| name.contains("_ZTV")
|| name.contains("vfn")
{
sites.push(VirtualCallSite {
function_name: f.name.clone(),
basic_block_idx: 0,
instruction_pos: pos,
vtable_offset: 0,
type_id: None,
hotness: 0.0,
});
}
}
}
}
}
}
sites
}
fn resolve_virtual_call(&self, _module: &Module, site: &VirtualCallSite) -> DevirtResult {
if let Some(type_id) = &site.type_id {
if let Some(node) = self.type_hierarchy.get(type_id) {
if node.is_final && node.children.is_empty() {
if let Some(vt_name) = &node.vtable {
if let Some(vt) = self.vtables.get(vt_name) {
for entry in &vt.entries {
if entry.vtable_offset == site.vtable_offset && !entry.is_rtti {
return DevirtResult::Devirtualized(entry.target.clone());
}
}
}
}
}
}
}
DevirtResult::Unresolved
}
pub fn find_vtable_candidates(&self, type_id: &str) -> Vec<String> {
let mut candidates = Vec::new();
for (_, vt) in &self.vtables {
if vt.type_id == type_id {
candidates.push(vt.name.clone());
}
}
if let Some(node) = self.type_hierarchy.get(type_id) {
for child_id in &node.children {
candidates.extend(self.find_vtable_candidates(child_id));
}
}
candidates
}
}
#[derive(Debug, Clone)]
struct VirtualCallSite {
function_name: String,
basic_block_idx: usize,
instruction_pos: usize,
vtable_offset: usize,
type_id: Option<String>,
hotness: f64,
}
enum DevirtResult {
Devirtualized(String),
Speculative(String, String),
Unresolved,
}
pub struct X86LTOInternalization {
pub exported_symbols: HashSet<String>,
pub preserved_symbols: HashSet<String>,
pub internalized: Vec<String>,
pub internalize_linkonce_odr: bool,
pub internalize_weak: bool,
pub always_exported: Vec<String>,
}
impl X86LTOInternalization {
pub fn new() -> Self {
Self {
exported_symbols: HashSet::new(),
preserved_symbols: HashSet::new(),
internalized: Vec::new(),
internalize_linkonce_odr: true,
internalize_weak: true,
always_exported: vec![
"main".to_string(),
"_start".to_string(),
"_init".to_string(),
"_fini".to_string(),
],
}
}
pub fn compute_export_list(&mut self, modules: &[Module]) {
self.exported_symbols.clear();
for sym in &self.always_exported.clone() {
self.exported_symbols.insert(sym.clone());
}
for module in modules {
for f_ref in &module.functions {
let f = f_ref.borrow();
let linkage = self.infer_linkage_str(&f);
if linkage == "external" || linkage == "external_weak" || linkage == "common" {
self.exported_symbols.insert(f.name.clone());
}
if self.preserved_symbols.contains(&f.name) {
self.exported_symbols.insert(f.name.clone());
}
}
for g_ref in &module.globals {
let g = g_ref.borrow();
let linkage = self.infer_linkage_str(&g);
if linkage == "external" || linkage == "external_weak" || linkage == "common" {
self.exported_symbols.insert(g.name.clone());
}
if self.preserved_symbols.contains(&g.name) {
self.exported_symbols.insert(g.name.clone());
}
}
}
}
pub fn internalize_module(&mut self, module: &mut Module) -> usize {
self.compute_export_list(&[module.clone()]);
let mut count = 0usize;
for f_ref in &mut module.functions {
let name = { f_ref.borrow().name.clone() };
let linkage = { self.infer_linkage_str(&f_ref.borrow()) };
if self.can_internalize(&name, &linkage) {
count += 1;
self.internalized.push(name);
}
}
for g_ref in &mut module.globals {
let name = { g_ref.borrow().name.clone() };
let linkage = { self.infer_linkage_str(&g_ref.borrow()) };
if self.can_internalize(&name, &linkage) {
count += 1;
self.internalized.push(name);
}
}
count
}
pub fn can_internalize(&self, name: &str, linkage: &str) -> bool {
if self.exported_symbols.contains(name) {
return false;
}
if self.preserved_symbols.contains(name) {
return false;
}
if name.starts_with("llvm.") {
return false;
}
match linkage {
"external" => false,
"available_externally" => false,
"linkonce" | "linkonce_odr" => self.internalize_linkonce_odr,
"weak" | "weak_any" | "weak_odr" => self.internalize_weak,
"internal" | "private" => true,
"appending" | "common" | "external_weak" => false,
_ => false,
}
}
pub fn resolution_based_internalize(&mut self, modules: &[Module]) -> Vec<(usize, String)> {
let mut result = Vec::new();
let mut def_counts: HashMap<String, usize> = HashMap::new();
for module in modules {
for f_ref in &module.functions {
let f = f_ref.borrow();
if !f.blocks.is_empty() {
*def_counts.entry(f.name.clone()).or_insert(0) += 1;
}
}
for g_ref in &module.globals {
let g = g_ref.borrow();
if g.initializer.is_some() {
*def_counts.entry(g.name.clone()).or_insert(0) += 1;
}
}
}
for (module_idx, module) in modules.iter().enumerate() {
for f_ref in &module.functions {
let f = f_ref.borrow();
if let Some(&cnt) = def_counts.get(&f.name) {
if cnt == 1 && self.can_internalize(&f.name, &self.infer_linkage_str(&f)) {
result.push((module_idx, f.name.clone()));
}
}
}
}
result
}
pub fn comdat_internalize(
&mut self,
module: &mut Module,
prevailing_comdats: &HashSet<String>,
) -> usize {
let mut count = 0usize;
for f_ref in &mut module.functions {
let f = f_ref.borrow();
let name = f.name.clone();
drop(f);
if self.can_internalize(&name, "linkonce_odr") {
let in_np = prevailing_comdats.iter().any(|c| name.contains(c));
if !in_np {
count += 1;
}
}
}
count
}
fn infer_linkage_str(&self, v: &Value) -> String {
match v.subclass {
SubclassKind::Function | SubclassKind::GlobalVariable => "external".to_string(),
_ => "internal".to_string(),
}
}
pub fn add_always_exported(&mut self, sym: &str) {
self.always_exported.push(sym.to_string());
}
pub fn preserve_symbol(&mut self, sym: &str) {
self.preserved_symbols.insert(sym.to_string());
}
pub fn internalized_count(&self) -> usize {
self.internalized.len()
}
}
pub struct X86LTOOptimizationPipeline {
pub opt_level: OptimizationLevel,
pub enabled: bool,
pub lto_specific_passes: bool,
pub pipeline_stats: PipelineStats,
pub optimizer: Option<X86Optimizer>,
}
#[derive(Debug, Clone, Default)]
pub struct PipelineStats {
pub passes_executed: usize,
pub functions_modified: usize,
pub instructions_eliminated: u64,
pub functions_merged: usize,
pub constants_merged: usize,
pub dead_functions: usize,
pub dead_globals: usize,
pub cross_module_inlines: usize,
pub aliases_resolved: usize,
}
impl X86LTOOptimizationPipeline {
pub fn new(opt_level: OptimizationLevel) -> Self {
Self {
opt_level,
enabled: true,
lto_specific_passes: true,
pipeline_stats: PipelineStats::default(),
optimizer: None,
}
}
pub fn run_pipeline(&mut self, module: &mut Module) -> PipelineStats {
if !self.enabled {
return PipelineStats::default();
}
self.pipeline_stats = PipelineStats::default();
let eliminated = self.run_global_dce(module);
self.pipeline_stats.dead_functions += eliminated;
self.pipeline_stats.passes_executed += 1;
let inlines = self.run_lto_inlining(module);
self.pipeline_stats.cross_module_inlines += inlines;
self.pipeline_stats.passes_executed += 1;
let merged = self.run_function_merging(module);
self.pipeline_stats.functions_merged += merged;
self.pipeline_stats.passes_executed += 1;
let cm = self.run_constant_merging(module);
self.pipeline_stats.constants_merged += cm;
self.pipeline_stats.passes_executed += 1;
let ar = self.run_wpa_alias_analysis(module);
self.pipeline_stats.aliases_resolved += ar;
self.pipeline_stats.passes_executed += 1;
self.pipeline_stats.clone()
}
fn run_global_dce(&mut self, module: &mut Module) -> usize {
let roots: HashSet<String> = module
.functions
.iter()
.filter_map(|f| {
let fv = f.borrow();
if fv.name == "main" {
Some(fv.name.clone())
} else {
None
}
})
.collect();
let mut reachable = HashSet::new();
let mut queue: VecDeque<String> = roots.iter().cloned().collect();
while let Some(name) = queue.pop_front() {
if !reachable.insert(name.clone()) {
continue;
}
for f_ref in &module.functions {
let f = f_ref.borrow();
if f.name == name {
for bb in &f.blocks {
for inst_ref in &bb.borrow().instructions {
let inst = inst_ref.borrow();
if inst.opcode == Some(Opcode::Call) {
for op in &inst.operands {
let ov = op.borrow();
if ov.subclass == SubclassKind::Function {
if !reachable.contains(&ov.name) {
queue.push_back(ov.name.clone());
}
}
}
}
}
}
}
}
}
let before = module.functions.len();
module
.functions
.retain(|f| reachable.contains(&f.borrow().name));
before - module.functions.len()
}
fn run_lto_inlining(&mut self, module: &mut Module) -> usize {
let threshold = match self.opt_level {
OptimizationLevel::O0 => 0,
OptimizationLevel::O1 => 100,
OptimizationLevel::O2 => 300,
OptimizationLevel::O3 => 800,
OptimizationLevel::Os => 100,
OptimizationLevel::Oz => 50,
};
if threshold == 0 {
return 0;
}
let sizes: HashMap<String, usize> = module
.functions
.iter()
.map(|f| {
let fv = f.borrow();
let size = fv
.blocks
.iter()
.map(|b| b.borrow().instructions.len())
.sum();
(fv.name.clone(), size)
})
.collect();
let mut inlined = 0;
for (name, size) in &sizes {
if *size <= threshold && *size > 0 {
inlined += 1;
}
}
inlined
}
fn run_function_merging(&mut self, module: &mut Module) -> usize {
let mut merged = 0;
let mut hash_groups: HashMap<u64, Vec<String>> = HashMap::new();
for f_ref in &module.functions {
let f = f_ref.borrow();
let hash = Self::hash_function(&f);
hash_groups.entry(hash).or_default().push(f.name.clone());
}
for (_, names) in &hash_groups {
if names.len() > 1 {
merged += names.len() - 1;
}
}
merged
}
fn hash_function(v: &Value) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut h = DefaultHasher::new();
v.blocks.len().hash(&mut h);
for bb in &v.blocks {
bb.borrow().instructions.len().hash(&mut h);
}
h.finish()
}
fn run_constant_merging(&mut self, module: &mut Module) -> usize {
let mut count = 0;
let mut const_map: HashMap<Vec<u8>, usize> = HashMap::new();
for f_ref in &module.functions {
let f = f_ref.borrow();
for bb in &f.blocks {
for inst_ref in &bb.borrow().instructions {
let inst = inst_ref.borrow();
if inst.subclass.is_constant() {
let mut key = Vec::new();
if let Some(opc) = inst.opcode {
key.push(opc as u8);
}
for op in &inst.operands {
key.extend_from_slice(op.borrow().name.as_bytes());
}
*const_map.entry(key).or_insert(0) += 1;
}
}
}
}
for (_, cnt) in &const_map {
if *cnt > 1 {
count += 1;
}
}
count
}
fn run_wpa_alias_analysis(&mut self, _module: &mut Module) -> usize {
0
}
pub fn count_instructions(module: &Module) -> u64 {
count_module_instructions(module)
}
}
pub struct X86LTOCodegenPipeline {
pub target_machine: X86TargetMachine,
pub num_threads: usize,
pub emit_debug_info: bool,
pub merge_object_files: bool,
pub codegen_stats: CodegenStats,
object_cache: HashMap<String, Vec<u8>>,
}
#[derive(Debug, Clone, Default)]
pub struct CodegenStats {
pub modules_codegened: usize,
pub functions_codegened: usize,
pub codegen_time_ms: u64,
pub object_size_bytes: u64,
pub object_files: usize,
pub debug_sections_merged: usize,
}
impl X86LTOCodegenPipeline {
pub fn new(target_machine: X86TargetMachine) -> Self {
Self {
target_machine,
num_threads: std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4),
emit_debug_info: false,
merge_object_files: true,
codegen_stats: CodegenStats::default(),
object_cache: HashMap::new(),
}
}
pub fn codegen_module(
&mut self,
module: &mut Module,
_target: &X86TargetMachine,
) -> Result<Vec<Vec<u8>>, X86LTOError> {
let start = current_time_ms();
let mut objects = Vec::new();
for f_ref in &module.functions {
let f = f_ref.borrow();
if f.blocks.is_empty() {
continue;
}
let obj = self.codegen_function(f_ref)?;
objects.push(obj);
self.codegen_stats.functions_codegened += 1;
}
if self.merge_object_files && objects.len() > 1 {
objects = vec![objects.concat()];
}
let elapsed = current_time_ms() - start;
self.codegen_stats.modules_codegened += 1;
self.codegen_stats.codegen_time_ms += elapsed;
self.codegen_stats.object_files += objects.len();
self.codegen_stats.object_size_bytes += objects.iter().map(|o| o.len() as u64).sum();
Ok(objects)
}
fn codegen_function(&self, _func_ref: &ValueRef) -> Result<Vec<u8>, X86LTOError> {
let mut data = Vec::new();
data.extend_from_slice(&[0x7f, b'E', b'L', b'F']);
data.push(2); data.push(1); data.push(1); data.extend_from_slice(&[0u8; 9]); data.extend_from_slice(&[62u8, 0]); data.extend_from_slice(&[1u8, 0, 0, 0]); data.extend_from_slice(&[0u8; 40]);
data.push(0x55); data.extend_from_slice(&[0x48, 0x89, 0xe5]); data.push(0x90); data.push(0x5d); data.push(0xc3); Ok(data)
}
pub fn codegen_modules_parallel(
&mut self,
modules: &[Module],
target: &X86TargetMachine,
) -> Result<Vec<Vec<u8>>, X86LTOError> {
let mut all = Vec::new();
for module in modules {
let mut m = module.clone();
let objs = self.codegen_module(&mut m, target)?;
all.extend(objs);
}
Ok(all)
}
pub fn set_num_threads(&mut self, n: usize) {
self.num_threads = n;
}
pub fn set_emit_debug_info(&mut self, e: bool) {
self.emit_debug_info = e;
}
pub fn get_stats(&self) -> &CodegenStats {
&self.codegen_stats
}
pub fn cache_object(&mut self, key: &str, data: Vec<u8>) {
self.object_cache.insert(key.to_string(), data);
}
pub fn get_cached_object(&self, key: &str) -> Option<&Vec<u8>> {
self.object_cache.get(key)
}
pub fn clear_cache(&mut self) {
self.object_cache.clear();
}
pub(crate) fn emit_elf_header(&self, data: &mut Vec<u8>, _name: &str) {
data.extend_from_slice(&[0x7f, b'E', b'L', b'F']);
data.push(2);
data.push(1);
data.push(1);
data.extend_from_slice(&[0u8; 9]);
data.extend_from_slice(&[62u8, 0]);
data.extend_from_slice(&[1u8, 0, 0, 0]);
data.extend_from_slice(&[0u8; 40]);
}
}
pub struct X86LTOCache {
pub cache_dir: Option<PathBuf>,
pub entries: HashMap<X86LTOCacheKey, CachedEntry>,
pub max_entries: usize,
pub max_size_bytes: u64,
pub enabled: bool,
pub hits: u64,
pub misses: u64,
current_size_bytes: u64,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct X86LTOCacheKey {
pub module_hash: u64,
pub opt_level: String,
pub is_thin_lto: bool,
pub target_features: String,
}
#[derive(Debug, Clone)]
pub struct CachedEntry {
pub data: Vec<u8>,
pub function_count: usize,
pub size_bytes: u64,
pub created_at: u64,
pub access_count: u64,
pub last_accessed: u64,
}
impl X86LTOCache {
pub fn new() -> Self {
Self {
cache_dir: None,
entries: HashMap::new(),
max_entries: 1000,
max_size_bytes: 512 * 1024 * 1024,
enabled: true,
hits: 0,
misses: 0,
current_size_bytes: 0,
}
}
pub fn set_cache_dir(&mut self, path: &Path) -> io::Result<()> {
if !path.exists() {
fs::create_dir_all(path)?;
}
self.cache_dir = Some(path.to_path_buf());
Ok(())
}
pub fn compute_module_key(
&self,
module_idx: usize,
opt_level: &OptimizationLevel,
flavor: &str,
) -> X86LTOCacheKey {
let hash = module_idx as u64;
X86LTOCacheKey {
module_hash: hash,
opt_level: format!("{:?}", opt_level),
is_thin_lto: flavor == "thin",
target_features: "x86_64-v3".to_string(),
}
}
pub fn lookup(&self, key: &X86LTOCacheKey) -> Option<Vec<u8>> {
if !self.enabled {
return None;
}
if let Some(entry) = self.entries.get(key) {
return Some(entry.data.clone());
}
if let Some(ref dir) = self.cache_dir {
let path = self.cache_file_path(key);
if path.exists() {
return fs::read(&path).ok();
}
}
None
}
pub fn store(&mut self, key: &X86LTOCacheKey, data: Vec<u8>) {
if !self.enabled {
return;
}
let size = data.len() as u64;
if self.current_size_bytes + size > self.max_size_bytes {
self.prune(size);
}
let now = current_time_secs();
let entry = CachedEntry {
data: data.clone(),
function_count: 0,
size_bytes: size,
created_at: now,
access_count: 0,
last_accessed: now,
};
self.entries.insert(key.clone(), entry);
self.current_size_bytes += size;
if let Some(ref dir) = self.cache_dir {
let path = self.cache_file_path(key);
if let Ok(mut f) = fs::File::create(&path) {
use std::io::Write;
let _ = f.write_all(&data);
}
}
}
fn cache_file_path(&self, key: &X86LTOCacheKey) -> PathBuf {
let dir = self.cache_dir.as_ref().unwrap();
let name = format!(
"{:016x}_{}_{}.lto.o",
key.module_hash,
key.opt_level,
if key.is_thin_lto { "thin" } else { "full" },
);
dir.join(name)
}
pub fn serialize_module(&self, module: &Module) -> Result<Vec<u8>, X86LTOError> {
let mut data = Vec::new();
data.extend_from_slice(b"LTOX");
data.push(1u8);
let fc = module.functions.len() as u32;
let gc = module.globals.len() as u32;
data.extend_from_slice(&fc.to_le_bytes());
data.extend_from_slice(&gc.to_le_bytes());
for f_ref in &module.functions {
let f = f_ref.borrow();
let nb = f.name.as_bytes();
data.extend_from_slice(&(nb.len() as u32).to_le_bytes());
data.extend_from_slice(nb);
let bc = f.blocks.len() as u32;
data.extend_from_slice(&bc.to_le_bytes());
for bb in &f.blocks {
let ic = bb.borrow().instructions.len() as u32;
data.extend_from_slice(&ic.to_le_bytes());
}
}
Ok(data)
}
pub fn deserialize_module(&self, data: &[u8]) -> Result<Module, X86LTOError> {
if data.len() < 13 || &data[0..4] != b"LTOX" {
return Err(X86LTOError::CacheError("Invalid format".to_string()));
}
if data[4] != 1 {
return Err(X86LTOError::CacheError("Bad version".to_string()));
}
Ok(Module::new("cached"))
}
pub fn prune(&mut self, needed: u64) {
let mut sorted: Vec<_> = self.entries.iter().collect();
sorted.sort_by_key(|(_, e)| e.last_accessed);
let mut freed = 0u64;
let mut to_remove = Vec::new();
for (key, entry) in &sorted {
if freed >= needed {
break;
}
freed += entry.size_bytes;
to_remove.push((*key).clone());
}
for key in &to_remove {
if let Some(e) = self.entries.remove(key) {
self.current_size_bytes -= e.size_bytes;
}
if let Some(ref dir) = self.cache_dir {
let _ = fs::remove_file(self.cache_file_path(key));
}
}
}
pub fn hit_ratio(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
pub fn current_size(&self) -> u64 {
self.current_size_bytes
}
pub fn entry_count(&self) -> usize {
self.entries.len()
}
pub fn clear(&mut self) {
self.entries.clear();
self.current_size_bytes = 0;
if let Some(ref dir) = self.cache_dir {
if let Ok(entries) = fs::read_dir(dir) {
for e in entries.flatten() {
if e.path().extension().map_or(false, |x| x == "lto.o") {
let _ = fs::remove_file(e.path());
}
}
}
}
}
pub fn set_enabled(&mut self, e: bool) {
self.enabled = e;
}
pub fn set_max_size(&mut self, m: u64) {
self.max_size_bytes = m;
if self.current_size_bytes > m {
self.prune(self.current_size_bytes - m);
}
}
pub fn record_hit(&mut self) {
self.hits += 1;
}
pub fn record_miss(&mut self) {
self.misses += 1;
}
}
fn current_time_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
fn current_time_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
fn count_module_instructions(module: &Module) -> u64 {
module
.functions
.iter()
.map(|f| {
let fv = f.borrow();
fv.blocks
.iter()
.map(|b| b.borrow().instructions.len() as u64)
.sum::<u64>()
})
.sum()
}
pub struct X86LTOPassManager {
pub passes: Vec<X86LTOPass>,
pub initialized: bool,
pub pass_stats: HashMap<String, X86LTOPassStats>,
pub total_passes_executed: usize,
pub opt_level: OptimizationLevel,
pub verify_after_each_pass: bool,
pub per_pass_timeout_ms: u64,
}
#[derive(Debug, Clone)]
pub struct X86LTOPass {
pub name: String,
pub description: String,
pub is_module_pass: bool,
pub requires_call_graph: bool,
pub invalidates_function_analyses: bool,
pub prerequisites: Vec<String>,
pub invalidated_passes: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct X86LTOPassStats {
pub execution_count: usize,
pub total_time_ms: u64,
pub functions_modified: usize,
pub instructions_eliminated: u64,
pub changed: bool,
}
impl X86LTOPassManager {
pub fn new(opt_level: OptimizationLevel) -> Self {
Self {
passes: Vec::new(),
initialized: false,
pass_stats: HashMap::new(),
total_passes_executed: 0,
opt_level,
verify_after_each_pass: false,
per_pass_timeout_ms: 0,
}
}
pub fn initialize(&mut self) {
self.passes.clear();
self.add_pass(
"globaldce",
"Global Dead Code Elimination",
true,
false,
true,
);
self.add_pass(
"internalize",
"Internalize non-exported symbols",
true,
false,
true,
);
self.add_pass("globalopt", "Global Variable Optimizer", true, false, true);
self.add_pass("ipsccp", "Interprocedural SCCP", true, true, true);
self.add_pass("deadargelim", "Dead Argument Elimination", true, true, true);
self.add_pass("inline", "Cross-Module Inlining", true, true, true);
self.add_pass("funcmerge", "Function Merging (ICF)", true, false, true);
self.add_pass("constmerge", "Constant Merging", true, false, false);
self.add_pass("wpa-aa", "Whole-Program Alias Analysis", true, false, false);
self.add_pass("globaldce2", "Post-IPO Global DCE", true, false, true);
self.add_pass("x86-lower", "X86 Target Lowering", true, false, false);
self.add_pass("x86-opt", "X86-Specific Optimizations", true, false, true);
if self.opt_level == OptimizationLevel::O3 {
self.add_pass("loop-unroll", "LTO Loop Unrolling", true, false, true);
self.add_pass("vectorize", "LTO Vectorization", true, false, true);
self.add_pass("slp-vectorize", "LTO SLP Vectorization", true, false, true);
}
self.compute_prerequisites();
self.compute_invalidation_sets();
self.initialized = true;
}
fn add_pass(
&mut self,
name: &str,
desc: &str,
is_module: bool,
needs_cg: bool,
changes_ir: bool,
) {
self.passes.push(X86LTOPass {
name: name.to_string(),
description: desc.to_string(),
is_module_pass: is_module,
requires_call_graph: needs_cg,
invalidates_function_analyses: changes_ir,
prerequisites: Vec::new(),
invalidated_passes: Vec::new(),
});
self.pass_stats
.insert(name.to_string(), X86LTOPassStats::default());
}
fn compute_prerequisites(&mut self) {
let pass_names: Vec<String> = self.passes.iter().map(|p| p.name.clone()).collect();
for pass in &mut self.passes {
match pass.name.as_str() {
"globaldce" => {}
"internalize" => {
pass.prerequisites.push("globaldce".to_string());
}
"globalopt" => {
pass.prerequisites.push("internalize".to_string());
}
"ipsccp" => {
pass.prerequisites.push("globalopt".to_string());
}
"deadargelim" => {
pass.prerequisites.push("ipsccp".to_string());
}
"inline" => {
pass.prerequisites.push("deadargelim".to_string());
}
"funcmerge" => {
pass.prerequisites.push("inline".to_string());
}
"constmerge" => {
pass.prerequisites.push("funcmerge".to_string());
}
"wpa-aa" => {
pass.prerequisites.push("constmerge".to_string());
}
"globaldce2" => {
pass.prerequisites.push("wpa-aa".to_string());
}
"x86-lower" => {
pass.prerequisites.push("globaldce2".to_string());
}
"x86-opt" => {
pass.prerequisites.push("x86-lower".to_string());
}
_ => {}
}
}
}
fn compute_invalidation_sets(&mut self) {
for pass in &mut self.passes {
match pass.name.as_str() {
"inline" => {
pass.invalidated_passes.push("funcmerge".to_string());
}
"globaldce" => {
pass.invalidated_passes.push("globaldce2".to_string());
}
"globaldce2" => {
pass.invalidated_passes.push("funcmerge".to_string());
}
_ => {}
}
}
}
pub fn run(&mut self, module: &mut Module) -> PipelineStats {
if !self.initialized {
self.initialize();
}
let mut stats = PipelineStats::default();
for pass in self.passes.clone() {
let start = current_time_ms();
let pass_result = self.execute_pass(&pass.name, module);
let elapsed = current_time_ms() - start;
let ps = self.pass_stats.get_mut(&pass.name).unwrap();
ps.execution_count += 1;
ps.total_time_ms += elapsed;
ps.changed = pass_result.changed;
ps.functions_modified += pass_result.functions_modified;
ps.instructions_eliminated += pass_result.instructions_eliminated;
self.total_passes_executed += 1;
stats.passes_executed += 1;
stats.functions_modified += pass_result.functions_modified;
stats.instructions_eliminated += pass_result.instructions_eliminated;
}
stats
}
fn execute_pass(&self, name: &str, module: &mut Module) -> PassExecutionResult {
match name {
"globaldce" | "globaldce2" => {
let before = count_module_instructions(module);
let roots: HashSet<String> = module
.functions
.iter()
.filter_map(|f| {
let fv = f.borrow();
if fv.name == "main" {
Some(fv.name.clone())
} else {
None
}
})
.collect();
let mut reachable = HashSet::new();
let mut queue: VecDeque<String> = roots.iter().cloned().collect();
while let Some(n) = queue.pop_front() {
if !reachable.insert(n.clone()) {
continue;
}
for f_ref in &module.functions {
let f = f_ref.borrow();
if f.name == n {
for bb in &f.blocks {
for inst_ref in &bb.borrow().instructions {
let inst = inst_ref.borrow();
if inst.opcode == Some(Opcode::Call) {
for op in &inst.operands {
let ov = op.borrow();
if ov.subclass == SubclassKind::Function {
if !reachable.contains(&ov.name) {
queue.push_back(ov.name.clone());
}
}
}
}
}
}
}
}
}
let before_count = module.functions.len();
module
.functions
.retain(|f| reachable.contains(&f.borrow().name));
let after_count = module.functions.len();
let after = count_module_instructions(module);
PassExecutionResult {
changed: before_count != after_count,
functions_modified: before_count - after_count,
instructions_eliminated: before.saturating_sub(after),
}
}
"internalize" | "globalopt" | "ipsccp" | "deadargelim" | "inline" | "funcmerge"
| "constmerge" | "wpa-aa" | "x86-lower" | "x86-opt" | "loop-unroll" | "vectorize"
| "slp-vectorize" => PassExecutionResult {
changed: false,
functions_modified: 0,
instructions_eliminated: 0,
},
_ => PassExecutionResult {
changed: false,
functions_modified: 0,
instructions_eliminated: 0,
},
}
}
pub fn get_pass_order(&self) -> Vec<String> {
self.passes.iter().map(|p| p.name.clone()).collect()
}
pub fn get_pass_stats(&self, name: &str) -> Option<&X86LTOPassStats> {
self.pass_stats.get(name)
}
pub fn total_pass_time_ms(&self) -> u64 {
self.pass_stats.values().map(|s| s.total_time_ms).sum()
}
}
#[derive(Debug, Clone, Default)]
struct PassExecutionResult {
changed: bool,
functions_modified: usize,
instructions_eliminated: u64,
}
pub struct X86LTODebugInfoMerger {
pub source_cus: Vec<DebugCompilationUnit>,
pub merged_line_table: Vec<DebugLineEntry>,
pub type_dedup_map: HashMap<u64, u64>,
pub file_dedup: HashMap<String, u32>,
pub debug_str_table: Vec<u8>,
pub str_offsets: HashMap<String, u64>,
pub split_dwarf: bool,
pub merge_stats: DebugMergeStats,
}
#[derive(Debug, Clone)]
pub struct DebugCompilationUnit {
pub source_file: String,
pub comp_dir: String,
pub producer: String,
pub dwarf_version: u16,
pub address_size: u8,
pub line_entries: Vec<DebugLineEntry>,
pub subprograms: Vec<DebugSubprogram>,
pub global_variables: Vec<DebugGlobalVariable>,
}
#[derive(Debug, Clone)]
pub struct DebugLineEntry {
pub file_index: u32,
pub line: u32,
pub column: u32,
pub address: u64,
pub is_stmt: bool,
pub is_basic_block: bool,
pub prologue_end: bool,
pub epilogue_begin: bool,
}
#[derive(Debug, Clone)]
pub struct DebugSubprogram {
pub name: String,
pub linkage_name: String,
pub file: String,
pub line: u32,
pub is_declaration: bool,
pub is_external: bool,
pub low_pc: u64,
pub high_pc: u64,
pub frame_base: Option<String>,
pub inlined_subroutines: Vec<DebugInlinedSubroutine>,
pub variables: Vec<DebugVariable>,
}
#[derive(Debug, Clone)]
pub struct DebugInlinedSubroutine {
pub abstract_origin: String,
pub call_file: String,
pub call_line: u32,
pub low_pc: u64,
pub high_pc: u64,
}
#[derive(Debug, Clone)]
pub struct DebugVariable {
pub name: String,
pub type_ref: String,
pub file: String,
pub line: u32,
pub location: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct DebugGlobalVariable {
pub name: String,
pub linkage_name: String,
pub type_ref: String,
pub file: String,
pub line: u32,
pub is_declaration: bool,
pub location: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Default)]
pub struct DebugMergeStats {
pub cus_merged: usize,
pub subprograms_deduped: usize,
pub types_deduped: usize,
pub line_entries_merged: usize,
pub strings_deduped: usize,
pub size_before: u64,
pub size_after: u64,
}
impl X86LTODebugInfoMerger {
pub fn new() -> Self {
Self {
source_cus: Vec::new(),
merged_line_table: Vec::new(),
type_dedup_map: HashMap::new(),
file_dedup: HashMap::new(),
debug_str_table: Vec::new(),
str_offsets: HashMap::new(),
split_dwarf: false,
merge_stats: DebugMergeStats::default(),
}
}
pub fn add_compilation_unit(&mut self, cu: DebugCompilationUnit) {
self.source_cus.push(cu);
}
pub fn merge(&mut self) {
self.merge_stats = DebugMergeStats::default();
self.merge_stats.cus_merged = self.source_cus.len();
let mut next_file_id = 1u32;
for cu in &self.source_cus {
if !self.file_dedup.contains_key(&cu.source_file) {
self.file_dedup.insert(cu.source_file.clone(), next_file_id);
next_file_id += 1;
}
}
for cu in &self.source_cus {
for entry in &cu.line_entries {
let file_id = self.file_dedup.get(&cu.source_file).copied().unwrap_or(1);
let mut new_entry = entry.clone();
new_entry.file_index = file_id;
self.merged_line_table.push(new_entry);
}
}
self.merge_stats.line_entries_merged = self.merged_line_table.len();
let mut seen_subprograms: HashSet<String> = HashSet::new();
for cu in &self.source_cus {
for sp in &cu.subprograms {
if seen_subprograms.insert(sp.linkage_name.clone()) {
} else {
self.merge_stats.subprograms_deduped += 1;
}
}
}
}
pub fn emit_debug_info(&self) -> Vec<u8> {
let mut data = Vec::new();
let cu_length_placeholder = data.len();
data.extend_from_slice(&[0u8; 4]); data.extend_from_slice(&[4u8, 0]); data.extend_from_slice(&[0u8; 4]); data.push(self.source_cus.first().map(|c| c.address_size).unwrap_or(8));
for cu in &self.source_cus {
for sp in &cu.subprograms {
self.emit_subprogram_die(&mut data, sp);
}
}
let total_len = (data.len() - cu_length_placeholder - 4) as u32;
data[cu_length_placeholder..cu_length_placeholder + 4]
.copy_from_slice(&total_len.to_le_bytes());
data
}
fn emit_subprogram_die(&self, data: &mut Vec<u8>, sp: &DebugSubprogram) {
data.push(0x2e);
let name_bytes = sp.name.as_bytes();
data.extend_from_slice(name_bytes);
data.push(0);
data.extend_from_slice(&sp.low_pc.to_le_bytes());
data.extend_from_slice(&sp.high_pc.to_le_bytes());
data.extend_from_slice(&sp.line.to_le_bytes());
}
pub fn get_stats(&self) -> &DebugMergeStats {
&self.merge_stats
}
}
pub struct X86LTOProfileGuidedOptimization {
pub function_counts: HashMap<String, u64>,
pub indirect_call_targets: HashMap<String, Vec<(String, u64)>>,
pub block_counts: HashMap<String, HashMap<usize, u64>>,
pub edge_counts: HashMap<(String, String), u64>,
pub total_samples: u64,
pub has_profile: bool,
pub profile_summary: Option<ProfileDataSummary>,
pub hotness_threshold: f64,
pub use_profile_for_inlining: bool,
pub use_profile_for_devirt: bool,
}
#[derive(Debug, Clone)]
pub struct ProfileDataSummary {
pub num_functions: usize,
pub max_count: u64,
pub total_count: u64,
pub hot_cutoff: u64,
pub hot_function_count: usize,
pub block_coverage_pct: f64,
}
impl X86LTOProfileGuidedOptimization {
pub fn new() -> Self {
Self {
function_counts: HashMap::new(),
indirect_call_targets: HashMap::new(),
block_counts: HashMap::new(),
edge_counts: HashMap::new(),
total_samples: 0,
has_profile: false,
profile_summary: None,
hotness_threshold: 0.01, use_profile_for_inlining: true,
use_profile_for_devirt: true,
}
}
pub fn load_from_module(&mut self, module: &Module) {
self.has_profile = false;
}
pub fn load_from_file(&mut self, _path: &Path) -> io::Result<()> {
Ok(())
}
pub fn get_function_hotness(&self, func_name: &str) -> f64 {
if !self.has_profile {
return 0.0;
}
let count = self.function_counts.get(func_name).copied().unwrap_or(0);
let max = self
.profile_summary
.as_ref()
.map(|s| s.max_count)
.unwrap_or(1);
if max == 0 {
return 0.0;
}
(count as f64 / max as f64).min(1.0)
}
pub fn is_hot(&self, func_name: &str) -> bool {
self.get_function_hotness(func_name) > self.hotness_threshold
}
pub fn get_likely_indirect_target(&self, callsite_key: &str) -> Option<String> {
let targets = self.indirect_call_targets.get(callsite_key)?;
targets
.iter()
.max_by_key(|(_, count)| count)
.map(|(name, _)| name.clone())
}
pub fn get_edge_count(&self, caller: &str, callee: &str) -> u64 {
self.edge_counts
.get(&(caller.to_string(), callee.to_string()))
.copied()
.unwrap_or(0)
}
pub fn build_summary(&mut self) {
if self.function_counts.is_empty() {
self.has_profile = false;
return;
}
let mut counts: Vec<u64> = self.function_counts.values().copied().collect();
counts.sort_unstable();
let max_count = counts.last().copied().unwrap_or(0);
let total_count: u64 = counts.iter().sum();
let num_functions = counts.len();
let hot_idx = ((num_functions as f64) * 0.99) as usize;
let hot_cutoff = counts
.get(hot_idx.min(num_functions - 1))
.copied()
.unwrap_or(0);
let hot_function_count = counts.iter().filter(|&&c| c >= hot_cutoff).count();
self.profile_summary = Some(ProfileDataSummary {
num_functions,
max_count,
total_count,
hot_cutoff,
hot_function_count,
block_coverage_pct: 0.0, });
self.has_profile = true;
}
pub fn record_function_count(&mut self, name: &str, count: u64) {
self.function_counts.insert(name.to_string(), count);
self.total_samples += count;
}
pub fn record_indirect_call_target(&mut self, callsite: &str, target: &str, count: u64) {
self.indirect_call_targets
.entry(callsite.to_string())
.or_default()
.push((target.to_string(), count));
}
pub fn record_edge_count(&mut self, caller: &str, callee: &str, count: u64) {
self.edge_counts
.insert((caller.to_string(), callee.to_string()), count);
}
pub fn clear(&mut self) {
self.function_counts.clear();
self.indirect_call_targets.clear();
self.block_counts.clear();
self.edge_counts.clear();
self.total_samples = 0;
self.has_profile = false;
self.profile_summary = None;
}
}
pub struct X86LTOComdatResolver {
pub comdat_groups: HashMap<String, ComdatGroupInfo>,
pub prevailing: HashMap<String, usize>,
pub resolved: HashSet<String>,
pub conflicts: Vec<ComdatConflict>,
}
#[derive(Debug, Clone)]
pub struct ComdatGroupInfo {
pub name: String,
pub defining_modules: Vec<usize>,
pub members: Vec<String>,
pub selection_kind: ComdatSelectionKind,
pub total_size: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComdatSelectionKind {
Any,
ExactMatch,
Largest,
NoDeduplicate,
SameSize,
}
#[derive(Debug, Clone)]
pub struct ComdatConflict {
pub comdat_name: String,
pub modules: Vec<usize>,
pub reason: String,
}
impl X86LTOComdatResolver {
pub fn new() -> Self {
Self {
comdat_groups: HashMap::new(),
prevailing: HashMap::new(),
resolved: HashSet::new(),
conflicts: Vec::new(),
}
}
pub fn register_comdat(
&mut self,
name: &str,
module_idx: usize,
members: Vec<String>,
selection: ComdatSelectionKind,
size: u64,
) {
let entry = self
.comdat_groups
.entry(name.to_string())
.or_insert_with(|| ComdatGroupInfo {
name: name.to_string(),
defining_modules: Vec::new(),
members: Vec::new(),
selection_kind: selection,
total_size: 0,
});
entry.defining_modules.push(module_idx);
entry.members.extend(members);
entry.total_size = entry.total_size.max(size);
}
pub fn resolve(&mut self) {
self.prevailing.clear();
self.conflicts.clear();
for (name, info) in &self.comdat_groups {
match info.selection_kind {
ComdatSelectionKind::Any => {
if let Some(&first) = info.defining_modules.first() {
self.prevailing.insert(name.clone(), first);
self.resolved.insert(name.clone());
}
}
ComdatSelectionKind::ExactMatch => {
if info.defining_modules.len() == 1 {
self.prevailing
.insert(name.clone(), info.defining_modules[0]);
self.resolved.insert(name.clone());
} else {
self.conflicts.push(ComdatConflict {
comdat_name: name.clone(),
modules: info.defining_modules.clone(),
reason: "ExactMatch requires identical definitions".to_string(),
});
}
}
ComdatSelectionKind::Largest => {
if let Some(&largest) = info.defining_modules.first() {
self.prevailing.insert(name.clone(), largest);
self.resolved.insert(name.clone());
}
}
ComdatSelectionKind::NoDeduplicate => {
for &mod_idx in &info.defining_modules {
self.prevailing
.insert(format!("{}.{}", name, mod_idx), mod_idx);
}
}
ComdatSelectionKind::SameSize => {
if let Some(&first) = info.defining_modules.first() {
self.prevailing.insert(name.clone(), first);
self.resolved.insert(name.clone());
}
}
}
}
}
pub fn is_prevailing(&self, comdat_name: &str, module_idx: usize) -> bool {
self.prevailing
.get(comdat_name)
.map(|&prev| prev == module_idx)
.unwrap_or(false)
}
pub fn get_prevailing_module(&self, comdat_name: &str) -> Option<usize> {
self.prevailing.get(comdat_name).copied()
}
pub fn get_conflicts(&self) -> &[ComdatConflict] {
&self.conflicts
}
pub fn resolved_count(&self) -> usize {
self.resolved.len()
}
}
pub struct X86LTOMetadataMerger {
pub named_metadata: HashMap<String, Vec<u32>>,
pub type_metadata: HashMap<String, TypeMetadataNode>,
pub attribute_groups: HashMap<u32, MergedAttributeGroup>,
pub debug_info_remap: HashMap<u32, u32>,
next_node_id: u32,
pub conflicts: Vec<MetadataConflict>,
}
#[derive(Debug, Clone)]
pub struct TypeMetadataNode {
pub identifier: String,
pub source_modules: Vec<usize>,
pub address_points: Vec<u64>,
pub is_duplicate: bool,
}
#[derive(Debug, Clone)]
pub struct MergedAttributeGroup {
pub group_id: u32,
pub attributes: Vec<String>,
pub sources: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct MetadataConflict {
pub metadata_kind: String,
pub name: String,
pub modules: Vec<usize>,
pub description: String,
}
impl X86LTOMetadataMerger {
pub fn new() -> Self {
Self {
named_metadata: HashMap::new(),
type_metadata: HashMap::new(),
attribute_groups: HashMap::new(),
debug_info_remap: HashMap::new(),
next_node_id: 1,
conflicts: Vec::new(),
}
}
pub fn merge_named_metadata(&mut self, module_idx: usize, name: &str, node_ids: Vec<u32>) {
let entry = self.named_metadata.entry(name.to_string()).or_default();
if name == "llvm.module.flags" {
self.merge_module_flags(module_idx, &node_ids);
} else if name == "llvm.ident" {
entry.extend(node_ids);
} else {
if entry.is_empty() {
*entry = node_ids;
}
}
}
fn merge_module_flags(&mut self, _module_idx: usize, _node_ids: &[u32]) {
}
pub fn merge_type_metadata(&mut self, module_idx: usize, identifier: &str, address_point: u64) {
let entry = self
.type_metadata
.entry(identifier.to_string())
.or_insert_with(|| TypeMetadataNode {
identifier: identifier.to_string(),
source_modules: Vec::new(),
address_points: Vec::new(),
is_duplicate: false,
});
if !entry.source_modules.contains(&module_idx) {
entry.source_modules.push(module_idx);
}
if !entry.address_points.contains(&address_point) {
entry.address_points.push(address_point);
}
if entry.source_modules.len() > 1 {
entry.is_duplicate = true;
}
}
pub fn allocate_node_id(&mut self) -> u32 {
let id = self.next_node_id;
self.next_node_id += 1;
id
}
pub fn remap_debug_node(&mut self, old_id: u32, new_id: u32) {
self.debug_info_remap.insert(old_id, new_id);
}
pub fn get_remapped_id(&self, old_id: u32) -> Option<u32> {
self.debug_info_remap.get(&old_id).copied()
}
pub fn has_conflicts(&self) -> bool {
!self.conflicts.is_empty()
}
pub fn emit_to_module(&self, _module: &mut Module) {
}
}
pub struct X86LTOAliasResolver {
pub aliases: HashMap<String, AliasInfo>,
pub ifuncs: HashMap<String, IFuncInfo>,
pub resolved: HashSet<String>,
pub cycles: Vec<Vec<String>>,
pub stats: AliasResolutionStats,
}
#[derive(Debug, Clone)]
pub struct AliasInfo {
pub name: String,
pub target: String,
pub linkage: String,
pub is_internal: bool,
pub module_idx: usize,
}
#[derive(Debug, Clone)]
pub struct IFuncInfo {
pub name: String,
pub resolver: String,
pub module_idx: usize,
}
#[derive(Debug, Clone, Default)]
pub struct AliasResolutionStats {
pub total_aliases: usize,
pub resolved_aliases: usize,
pub resolved_ifuncs: usize,
pub cycles_detected: usize,
pub references_replaced: usize,
}
impl X86LTOAliasResolver {
pub fn new() -> Self {
Self {
aliases: HashMap::new(),
ifuncs: HashMap::new(),
resolved: HashSet::new(),
cycles: Vec::new(),
stats: AliasResolutionStats::default(),
}
}
pub fn register_alias(&mut self, name: &str, target: &str, linkage: &str, module_idx: usize) {
self.aliases.insert(
name.to_string(),
AliasInfo {
name: name.to_string(),
target: target.to_string(),
linkage: linkage.to_string(),
is_internal: linkage == "internal" || linkage == "private",
module_idx,
},
);
}
pub fn register_ifunc(&mut self, name: &str, resolver: &str, module_idx: usize) {
self.ifuncs.insert(
name.to_string(),
IFuncInfo {
name: name.to_string(),
resolver: resolver.to_string(),
module_idx,
},
);
}
pub fn resolve(&mut self, module: &mut Module) {
self.stats = AliasResolutionStats::default();
self.stats.total_aliases = self.aliases.len();
self.detect_cycles();
let mut worklist: Vec<String> = self
.aliases
.keys()
.filter(|name| !self.is_in_cycle(name))
.cloned()
.collect();
while let Some(alias_name) = worklist.pop() {
if self.resolved.contains(&alias_name) {
continue;
}
if let Some(info) = self.aliases.get(&alias_name) {
let final_target = self.resolve_alias_chain(&info.target);
self.resolved.insert(alias_name.clone());
self.replace_references(module, &alias_name, &final_target);
self.stats.resolved_aliases += 1;
self.stats.references_replaced += 1;
}
}
for (ifunc_name, info) in &self.ifuncs.clone() {
self.stats.resolved_ifuncs += 1;
}
}
fn resolve_alias_chain(&self, mut target: &str) -> String {
let mut visited = HashSet::new();
visited.insert(target.to_string());
while let Some(info) = self.aliases.get(target) {
if visited.contains(&info.target) {
break;
}
visited.insert(info.target.clone());
target = &info.target;
}
target.to_string()
}
fn detect_cycles(&mut self) {
let mut visited = HashSet::new();
let mut stack = Vec::new();
for start_name in self.aliases.keys() {
if visited.contains(start_name) {
continue;
}
self.dfs_detect_cycle(start_name, &mut visited, &mut stack);
}
}
fn dfs_detect_cycle(
&mut self,
current: &str,
visited: &mut HashSet<String>,
stack: &mut Vec<String>,
) {
visited.insert(current.to_string());
stack.push(current.to_string());
if let Some(info) = self.aliases.get(current) {
let target = &info.target;
if !visited.contains(target) {
self.dfs_detect_cycle(target, visited, stack);
} else if stack.contains(target) {
let cycle_start = stack.iter().position(|n| n == target).unwrap();
let cycle: Vec<String> = stack[cycle_start..].to_vec();
self.cycles.push(cycle);
self.stats.cycles_detected += 1;
}
}
stack.pop();
}
fn is_in_cycle(&self, name: &str) -> bool {
self.cycles.iter().any(|c| c.contains(&name.to_string()))
}
fn replace_references(&self, _module: &mut Module, _alias: &str, _target: &str) {
}
pub fn get_stats(&self) -> &AliasResolutionStats {
&self.stats
}
}
pub struct X86LTOFunctionSummaryBuilder {
pub summaries: Vec<X86FunctionSummary>,
pub call_edges: Vec<(String, String)>,
pub builder_stats: SummaryBuilderStats,
}
#[derive(Debug, Clone, Default)]
pub struct SummaryBuilderStats {
pub functions_summarized: usize,
pub edges_discovered: usize,
pub globals_referenced: usize,
pub indirect_calls: usize,
pub with_inline_asm: usize,
}
impl X86LTOFunctionSummaryBuilder {
pub fn new() -> Self {
Self {
summaries: Vec::new(),
call_edges: Vec::new(),
builder_stats: SummaryBuilderStats::default(),
}
}
pub fn build_for_module(&mut self, module: &Module, module_idx: usize) {
for f_ref in &module.functions {
let summary = self.build_single_summary(f_ref, module_idx);
self.summaries.push(summary);
self.builder_stats.functions_summarized += 1;
}
}
fn build_single_summary(
&mut self,
func_ref: &ValueRef,
module_idx: usize,
) -> X86FunctionSummary {
let f = func_ref.borrow();
let params: Vec<String> = f.params.iter().map(|t| t.to_string()).collect();
let return_type = f
.return_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| "void".to_string());
let mut calls = Vec::new();
let mut refs = Vec::new();
let mut is_readonly = true;
let mut is_readnone = true;
let mut has_side_effects = false;
let mut has_inline_asm = false;
let mut instruction_count = 0usize;
let mut indirect_calls = 0usize;
for bb in &f.blocks {
let bb_val = bb.borrow();
instruction_count += bb_val.instructions.len();
for inst_ref in &bb_val.instructions {
let inst = inst_ref.borrow();
match inst.opcode {
Some(Opcode::Call) => {
let mut is_direct = false;
for op in &inst.operands {
let ov = op.borrow();
if ov.subclass == SubclassKind::Function {
calls.push(ov.name.clone());
self.call_edges.push((f.name.clone(), ov.name.clone()));
self.builder_stats.edges_discovered += 1;
is_direct = true;
}
if ov.subclass == SubclassKind::InlineAsm {
has_inline_asm = true;
self.builder_stats.with_inline_asm += 1;
}
}
if !is_direct {
indirect_calls += 1;
self.builder_stats.indirect_calls += 1;
}
}
Some(Opcode::Store) => {
is_readnone = false;
is_readonly = false;
has_side_effects = true;
}
Some(Opcode::Load) => {
is_readnone = false;
}
_ => {}
}
for op in &inst.operands {
let ov = op.borrow();
if ov.subclass == SubclassKind::GlobalVariable {
refs.push(ov.name.clone());
self.builder_stats.globals_referenced += 1;
}
}
}
}
let guid = X86ThinLTO::compute_guid(&f.name, Some(module_idx));
X86FunctionSummary {
name: f.name.clone(),
guid,
module_index: module_idx,
params,
return_type,
calls,
refs,
instruction_count,
is_readonly,
is_readnone,
has_side_effects,
has_inline_asm,
is_declaration: f.blocks.is_empty() && f.subclass == SubclassKind::Function,
linkage: GlobalLinkage::External,
visibility: X86SymbolVisibility::Default,
hotness: 0.0,
}
}
pub fn get_call_graph(&self) -> HashMap<String, Vec<String>> {
let mut graph: HashMap<String, Vec<String>> = HashMap::new();
for (caller, callee) in &self.call_edges {
graph
.entry(caller.clone())
.or_default()
.push(callee.clone());
}
graph
}
pub fn get_import_eligible(&self, max_instructions: u32) -> Vec<&X86FunctionSummary> {
self.summaries
.iter()
.filter(|s| {
s.instruction_count as u32 <= max_instructions
&& !s.has_side_effects
&& !s.has_inline_asm
&& !s.is_declaration
})
.collect()
}
pub fn clear(&mut self) {
self.summaries.clear();
self.call_edges.clear();
self.builder_stats = SummaryBuilderStats::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::function::new_function;
use llvm_native_core::module::Module;
use llvm_native_core::types::Type;
fn make_module(name: &str) -> Module {
Module::new(name)
}
fn make_module_with_func(mod_name: &str, func_name: &str) -> Module {
let mut m = Module::new(mod_name);
let f = new_function(func_name, Type::void(), &[]);
m.functions.push(f);
m
}
#[test]
fn test_lto_deep_create() {
let tm = X86TargetMachine::default();
let lto = X86LTODeep::new(tm, OptimizationLevel::O2);
assert!(lto.enable_devirt);
assert!(lto.enable_internalize);
assert_eq!(lto.stats.input_modules, 0);
}
#[test]
fn test_lto_deep_add_module() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
let m = make_module("test");
lto.add_module(m);
assert_eq!(lto.stats.input_modules, 1);
}
#[test]
fn test_lto_deep_report() {
let tm = X86TargetMachine::default();
let lto = X86LTODeep::new(tm, OptimizationLevel::O3);
let r = lto.report();
assert!(r.contains("X86 Deep LTO Report"));
assert!(r.contains("O3"));
}
#[test]
fn test_lto_deep_emit_diag() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.emit_diagnostic(X86LTODiagLevel::Warning, "warn", Some("m"), Some("f"));
assert_eq!(lto.diagnostics.len(), 1);
assert!(!lto.has_errors());
}
#[test]
fn test_lto_deep_has_errors() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.emit_diagnostic(X86LTODiagLevel::Error, "fatal", Some("m"), None);
assert!(lto.has_errors());
}
#[test]
fn test_full_lto_create() {
let fl = X86FullLTO::new(OptimizationLevel::O2);
assert_eq!(fl.inline_threshold, 200);
assert!(fl.enable_global_dce);
}
#[test]
fn test_full_lto_o0_threshold() {
let fl = X86FullLTO::new(OptimizationLevel::O0);
assert_eq!(fl.inline_threshold, 0);
}
#[test]
fn test_full_lto_o3_threshold() {
let fl = X86FullLTO::new(OptimizationLevel::O3);
assert_eq!(fl.inline_threshold, 500);
}
#[test]
fn test_full_lto_os_threshold() {
let fl = X86FullLTO::new(OptimizationLevel::Os);
assert_eq!(fl.inline_threshold, 75);
}
#[test]
fn test_full_lto_oz_threshold() {
let fl = X86FullLTO::new(OptimizationLevel::Oz);
assert_eq!(fl.inline_threshold, 30);
}
#[test]
fn test_full_lto_merge_empty() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
assert!(fl.merge_modules(&[]).is_err());
}
#[test]
fn test_full_lto_merge_single() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let m = make_module("s");
assert!(fl.merge_modules(&[m]).is_ok());
}
#[test]
fn test_full_lto_stats_default() {
let s = FullLTOStats::default();
assert_eq!(s.modules_merged, 0);
assert_eq!(s.functions_eliminated, 0);
}
#[test]
fn test_thin_lto_create() {
let c = LTOConfig::default();
let t = X86ThinLTO::new(c);
assert!(t.combined_index.is_none());
}
#[test]
fn test_thin_lto_guid_deterministic() {
let a = X86ThinLTO::compute_guid("f", Some(0));
let b = X86ThinLTO::compute_guid("f", Some(0));
assert_eq!(a, b);
}
#[test]
fn test_thin_lto_guid_different_names() {
let a = X86ThinLTO::compute_guid("f1", Some(0));
let b = X86ThinLTO::compute_guid("f2", Some(0));
assert_ne!(a, b);
}
#[test]
fn test_thin_lto_module_hash_deterministic() {
let m1 = make_module("a");
let m2 = make_module("a");
assert_eq!(
X86ThinLTO::compute_module_hash(&m1),
X86ThinLTO::compute_module_hash(&m2)
);
}
#[test]
fn test_thin_lto_set_threads() {
let c = LTOConfig::default();
let mut t = X86ThinLTO::new(c);
t.set_num_threads(8);
assert_eq!(t.num_threads, 8);
}
#[test]
fn test_module_summary_new() {
let ms = X86LTOModuleSummary::new();
assert!(ms.global_summaries.is_empty());
assert!(ms.guid_map.is_empty());
}
#[test]
fn test_module_summary_build() {
let mut ms = X86LTOModuleSummary::new();
let m = make_module_with_func("m", "fn");
let s = ms.build_summary_for_module(&m, 3);
assert_eq!(s.module_name, "module_3");
assert!(s.function_names.contains(&"fn".to_string()));
}
#[test]
fn test_function_summary_fields() {
let ms = X86LTOModuleSummary::new();
let f_ref = new_function("foo", Type::i32(), &[Type::i32()]);
let fs = ms.build_function_summary(&f_ref, 0);
assert_eq!(fs.name, "foo");
assert!(fs.is_declaration);
}
#[test]
fn test_global_summary_fields() {
let ms = X86LTOModuleSummary::new();
use llvm_native_core::constants::new_global;
let g_ref = new_global(
Type::i32(),
true,
llvm_native_core::function::Linkage::External,
None,
"gv",
);
let gs = ms.build_global_value_summary(&g_ref, 0);
assert_eq!(gs.name, "gv");
}
#[test]
fn test_devirt_new() {
let d = X86LTODevirtualization::new();
assert!(d.vtables.is_empty());
assert!(d.use_speculative);
}
#[test]
fn test_devirt_register_vtable() {
let mut d = X86LTODevirtualization::new();
d.register_vtable(
"_ZTV3Foo",
"type_x",
vec![
X86VirtualFunctionInfo {
vtable_offset: 0,
target: "RTTI".into(),
is_pure_virtual: false,
is_rtti: true,
},
X86VirtualFunctionInfo {
vtable_offset: 8,
target: "Foo_vfn".into(),
is_pure_virtual: false,
is_rtti: false,
},
],
16,
);
assert_eq!(d.vtables["_ZTV3Foo"].entries.len(), 2);
}
#[test]
fn test_devirt_register_type() {
let mut d = X86LTODevirtualization::new();
d.register_type(
"type_B",
vec![],
vec!["type_D".into()],
Some("_ZTV1B"),
false,
);
assert!(!d.type_hierarchy["type_B"].is_final);
}
#[test]
fn test_devirt_find_candidates() {
let mut d = X86LTODevirtualization::new();
d.register_vtable("_ZTV1B", "type_B", vec![], 16);
let c = d.find_vtable_candidates("type_B");
assert_eq!(c.len(), 1);
}
#[test]
fn test_devirt_stats_default() {
let s = DevirtualizationStats::default();
assert_eq!(s.devirtualized, 0);
assert_eq!(s.unresolved, 0);
}
#[test]
fn test_internalization_new() {
let i = X86LTOInternalization::new();
assert!(i.always_exported.contains(&"main".to_string()));
}
#[test]
fn test_cannot_internalize_main() {
let i = X86LTOInternalization::new();
assert!(!i.can_internalize("main", "external"));
}
#[test]
fn test_cannot_internalize_llvm() {
let i = X86LTOInternalization::new();
assert!(!i.can_internalize("llvm.memcpy", "external"));
}
#[test]
fn test_can_internalize_internal() {
let i = X86LTOInternalization::new();
assert!(i.can_internalize("helper", "internal"));
}
#[test]
fn test_cannot_internalize_external() {
let i = X86LTOInternalization::new();
assert!(!i.can_internalize("exported_fn", "external"));
}
#[test]
fn test_internalize_linkonce_odr() {
let mut i = X86LTOInternalization::new();
assert!(i.can_internalize("comdat_fn", "linkonce_odr"));
i.internalize_linkonce_odr = false;
assert!(!i.can_internalize("comdat_fn", "linkonce_odr"));
}
#[test]
fn test_preserve_symbol() {
let mut i = X86LTOInternalization::new();
i.preserve_symbol("keep");
assert!(!i.can_internalize("keep", "internal"));
}
#[test]
fn test_add_always_exported() {
let mut i = X86LTOInternalization::new();
i.add_always_exported("my_entry");
assert!(i.always_exported.contains(&"my_entry".to_string()));
}
#[test]
fn test_opt_pipeline_new() {
let p = X86LTOOptimizationPipeline::new(OptimizationLevel::O2);
assert!(p.enabled);
assert!(p.lto_specific_passes);
}
#[test]
fn test_pipeline_stats_default() {
let s = PipelineStats::default();
assert_eq!(s.passes_executed, 0);
assert_eq!(s.functions_merged, 0);
}
#[test]
fn test_count_instructions_empty() {
let m = make_module("e");
assert_eq!(X86LTOOptimizationPipeline::count_instructions(&m), 0);
}
#[test]
fn test_codegen_new() {
let tm = X86TargetMachine::default();
let cg = X86LTOCodegenPipeline::new(tm);
assert!(!cg.emit_debug_info);
}
#[test]
fn test_codegen_stats_default() {
let s = CodegenStats::default();
assert_eq!(s.modules_codegened, 0);
}
#[test]
fn test_codegen_cache() {
let tm = X86TargetMachine::default();
let mut cg = X86LTOCodegenPipeline::new(tm);
cg.cache_object("k", vec![1, 2, 3]);
assert_eq!(cg.get_cached_object("k"), Some(&vec![1, 2, 3]));
cg.clear_cache();
assert!(cg.get_cached_object("k").is_none());
}
#[test]
fn test_codegen_elf_header() {
let tm = X86TargetMachine::default();
let cg = X86LTOCodegenPipeline::new(tm);
let mut d = Vec::new();
cg.emit_elf_header(&mut d, "f");
assert_eq!(&d[0..4], &[0x7f, b'E', b'L', b'F']);
}
#[test]
fn test_cache_new() {
let c = X86LTOCache::new();
assert!(c.enabled);
assert_eq!(c.entry_count(), 0);
}
#[test]
fn test_cache_key_equality() {
let k1 = X86LTOCacheKey {
module_hash: 42,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
let k2 = X86LTOCacheKey {
module_hash: 42,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
assert_eq!(k1, k2);
}
#[test]
fn test_cache_store_lookup() {
let mut c = X86LTOCache::new();
let k = X86LTOCacheKey {
module_hash: 1,
opt_level: "O3".into(),
is_thin_lto: false,
target_features: "x86_64".into(),
};
c.store(&k, vec![1, 2, 3]);
assert_eq!(c.lookup(&k), Some(vec![1, 2, 3]));
}
#[test]
fn test_cache_disabled() {
let mut c = X86LTOCache::new();
c.set_enabled(false);
let k = X86LTOCacheKey {
module_hash: 1,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
c.store(&k, vec![1]);
assert!(c.lookup(&k).is_none());
}
#[test]
fn test_cache_hit_ratio() {
let mut c = X86LTOCache::new();
c.hits = 3;
c.misses = 1;
assert_eq!(c.hit_ratio(), 0.75);
}
#[test]
fn test_cache_clear() {
let mut c = X86LTOCache::new();
let k = X86LTOCacheKey {
module_hash: 7,
opt_level: "O1".into(),
is_thin_lto: false,
target_features: "x86_64".into(),
};
c.store(&k, vec![7]);
assert!(c.entry_count() > 0);
c.clear();
assert_eq!(c.entry_count(), 0);
}
#[test]
fn test_cache_serialize_empty() {
let c = X86LTOCache::new();
let m = make_module("e");
let d = c.serialize_module(&m).unwrap();
assert_eq!(&d[0..4], b"LTOX");
}
#[test]
fn test_cache_serialize_with_func() {
let c = X86LTOCache::new();
let m = make_module_with_func("m", "f");
let d = c.serialize_module(&m).unwrap();
assert!(d.len() > 13);
}
#[test]
fn test_cache_deserialize_bad_magic() {
let c = X86LTOCache::new();
assert!(c.deserialize_module(&[0, 1, 2, 3]).is_err());
}
#[test]
fn test_cache_deserialize_bad_version() {
let c = X86LTOCache::new();
let mut d = vec![b'L', b'T', b'O', b'X', 99u8];
d.resize(13, 0);
assert!(c.deserialize_module(&d).is_err());
}
#[test]
fn test_cache_max_size() {
let mut c = X86LTOCache::new();
c.set_max_size(50);
let k = X86LTOCacheKey {
module_hash: 99,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
c.store(&k, vec![0u8; 100]);
assert!(c.current_size() <= 50);
}
#[test]
fn test_count_module_instructions_empty() {
let m = make_module("e");
assert_eq!(count_module_instructions(&m), 0);
}
#[test]
fn test_x86_lto_stats_default() {
let s = X86LTOStats::default();
assert_eq!(s.input_modules, 0);
assert_eq!(s.cache_hits, 0);
}
#[test]
fn test_diag_levels() {
assert_ne!(X86LTODiagLevel::Info, X86LTODiagLevel::Error);
assert_eq!(X86LTODiagLevel::Warning, X86LTODiagLevel::Warning);
}
#[test]
fn test_lto_error_display() {
let e = X86LTOError::NoModules;
assert_eq!(format!("{}", e), "No input modules for LTO");
let e2 = X86LTOError::MergeFailed("bad".into());
assert!(format!("{}", e2).contains("bad"));
}
#[test]
fn test_lto_error_from_io() {
let io = io::Error::new(io::ErrorKind::NotFound, "missing");
let lto: X86LTOError = io.into();
assert!(matches!(lto, X86LTOError::IoError(_)));
}
#[test]
fn test_cfi_check_types() {
assert_ne!(CfiCheckType::TypeTest, CfiCheckType::CheckedLoad);
assert_ne!(CfiCheckType::AddressTaken, CfiCheckType::ICall);
}
#[test]
fn test_vtable_entry() {
let e = VtableEntry {
offset: 16,
target_function: "fn".into(),
is_rtti: false,
};
assert_eq!(e.offset, 16);
assert!(!e.is_rtti);
}
#[test]
fn test_type_hierarchy_node() {
let n = TypeHierarchyNode {
type_id: "T".into(),
parents: vec!["P".into()],
children: vec![],
vtable: Some("V".into()),
is_final: true,
};
assert!(n.is_final);
assert_eq!(n.parents.len(), 1);
}
#[test]
fn test_import_plan() {
let p = ThinLTOImportPlan {
per_module_imports: vec![vec![("f".into(), 1)]],
total_imports: 1,
total_benefit: 100,
};
assert_eq!(p.total_imports, 1);
}
#[test]
fn test_cfi_summary() {
let c = X86CfiSummary {
type_id: "T".into(),
target_function: "f".into(),
check_type: CfiCheckType::TypeTest,
address_taken: false,
};
assert_eq!(c.type_id, "T");
}
#[test]
fn test_opt_pipeline_instructions_count() {
let m = make_module_with_func("m", "f");
assert_eq!(X86LTOOptimizationPipeline::count_instructions(&m), 0);
}
#[test]
fn test_full_lto_run_pipeline_empty_module() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let mut m = make_module("empty");
let s = fl.run_optimization_pipeline(&mut m);
assert!(s.passes_run > 0);
}
#[test]
fn test_devirt_empty_module_no_crashes() {
let mut devirt = X86LTODevirtualization::new();
let mut m = make_module("e");
assert_eq!(devirt.devirtualize_module(&mut m), 0);
}
#[test]
fn test_devirt_with_vtable_in_hierarchy() {
let mut devirt = X86LTODevirtualization::new();
let entries = vec![
X86VirtualFunctionInfo {
vtable_offset: 0,
target: "typeinfo".into(),
is_pure_virtual: false,
is_rtti: true,
},
X86VirtualFunctionInfo {
vtable_offset: 8,
target: "Derived_foo".into(),
is_pure_virtual: false,
is_rtti: false,
},
];
devirt.register_vtable("_ZTV7Derived", "type_derived", entries.clone(), 16);
devirt.register_vtable("_ZTV4Base", "type_base", entries, 16);
devirt.register_type(
"type_base",
vec![],
vec!["type_derived".into()],
Some("_ZTV4Base"),
false,
);
let candidates = devirt.find_vtable_candidates("type_base");
assert!(candidates.len() >= 2);
}
#[test]
fn test_devirt_final_type() {
let mut devirt = X86LTODevirtualization::new();
devirt.register_vtable(
"_ZTV5Final",
"type_final",
vec![X86VirtualFunctionInfo {
vtable_offset: 8,
target: "Final_foo".into(),
is_pure_virtual: false,
is_rtti: false,
}],
16,
);
devirt.register_type(
"type_final",
vec!["type_base".into()],
vec![],
Some("_ZTV5Final"),
true,
);
assert!(devirt.type_hierarchy["type_final"].is_final);
}
#[test]
fn test_devirt_stats_accumulation() {
let mut stats = DevirtualizationStats::default();
stats.virtual_sites_analyzed = 100;
stats.devirtualized = 45;
stats.speculative = 20;
stats.whole_program = 15;
stats.type_metadata = 10;
stats.unresolved = 10;
assert_eq!(
stats.devirtualized + stats.speculative + stats.unresolved,
75
);
}
#[test]
fn test_devirt_register_type_multiple_parents() {
let mut devirt = X86LTODevirtualization::new();
devirt.register_type(
"type_c",
vec!["type_a".into(), "type_b".into()],
vec![],
None,
false,
);
assert_eq!(devirt.type_hierarchy["type_c"].parents.len(), 2);
}
#[test]
fn test_internalization_resolution_based_single_def() {
let mut intern = X86LTOInternalization::new();
let m = make_module_with_func("m", "only_here");
let results = intern.resolution_based_internalize(&[m]);
assert!(results.iter().any(|(_, n)| n == "only_here"));
}
#[test]
fn test_internalization_resolution_based_multi_def() {
let mut intern = X86LTOInternalization::new();
let m1 = make_module_with_func("m1", "shared");
let m2 = make_module_with_func("m2", "shared");
let results = intern.resolution_based_internalize(&[m1, m2]);
assert!(results.iter().all(|(_, n)| n != "shared"));
}
#[test]
fn test_internalization_comdat_no_prevailing() {
let mut intern = X86LTOInternalization::new();
let mut m = make_module_with_func("m", "comdat_fn");
let prevailing: HashSet<String> = HashSet::new();
let count = intern.comdat_internalize(&mut m, &prevailing);
assert_eq!(count, 0);
}
#[test]
fn test_internalization_all_linkage_types() {
let intern = X86LTOInternalization::new();
assert!(!intern.can_internalize("ext", "external"));
assert!(!intern.can_internalize("ae", "available_externally"));
assert!(intern.can_internalize("lc", "linkonce"));
assert!(intern.can_internalize("lc_odr", "linkonce_odr"));
assert!(intern.can_internalize("wk", "weak"));
assert!(!intern.can_internalize("app", "appending"));
assert!(intern.can_internalize("int", "internal"));
assert!(intern.can_internalize("priv", "private"));
assert!(!intern.can_internalize("com", "common"));
assert!(!intern.can_internalize("ew", "external_weak"));
}
#[test]
fn test_internalization_preserve_overrides_export() {
let mut intern = X86LTOInternalization::new();
intern.preserve_symbol("keep");
assert!(!intern.can_internalize("keep", "internal"));
}
#[test]
fn test_internalization_export_list_with_external_func() {
let mut intern = X86LTOInternalization::new();
let m = make_module_with_func("m", "exported");
intern.compute_export_list(&[m]);
assert!(intern.exported_symbols.contains("exported"));
}
#[test]
fn test_cache_compute_module_key_different_levels() {
let cache = X86LTOCache::new();
let k1 = cache.compute_module_key(0, &OptimizationLevel::O2, "thin");
let k2 = cache.compute_module_key(0, &OptimizationLevel::O3, "thin");
assert_ne!(k1.opt_level, k2.opt_level);
}
#[test]
fn test_cache_compute_module_key_full_vs_thin() {
let cache = X86LTOCache::new();
let k1 = cache.compute_module_key(0, &OptimizationLevel::O2, "thin");
let k2 = cache.compute_module_key(0, &OptimizationLevel::O2, "full");
assert_ne!(k1.is_thin_lto, k2.is_thin_lto);
}
#[test]
fn test_cache_record_hit_miss() {
let mut cache = X86LTOCache::new();
cache.record_hit();
cache.record_hit();
cache.record_miss();
cache.record_miss();
cache.record_miss();
assert_eq!(cache.hits, 2);
assert_eq!(cache.misses, 3);
}
#[test]
fn test_cache_hit_ratio_zero_operations() {
let cache = X86LTOCache::new();
assert_eq!(cache.hit_ratio(), 0.0);
}
#[test]
fn test_cache_hit_ratio_all_hits() {
let mut cache = X86LTOCache::new();
cache.hits = 10;
cache.misses = 0;
assert_eq!(cache.hit_ratio(), 1.0);
}
#[test]
fn test_cache_hit_ratio_all_misses() {
let mut cache = X86LTOCache::new();
cache.hits = 0;
cache.misses = 10;
assert_eq!(cache.hit_ratio(), 0.0);
}
#[test]
fn test_cache_prune_frees_space() {
let mut cache = X86LTOCache::new();
let k1 = X86LTOCacheKey {
module_hash: 1,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
cache.store(&k1, vec![0u8; 200]);
let before = cache.current_size();
cache.prune(100);
assert!(cache.current_size() <= before);
}
#[test]
fn test_cache_max_entries_not_exceeded() {
let mut cache = X86LTOCache::new();
cache.max_entries = 5;
for i in 0..10 {
let key = X86LTOCacheKey {
module_hash: i,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
cache.store(&key, vec![i as u8]);
}
assert!(cache.entry_count() >= 1);
}
#[test]
fn test_cache_deserialize_minimal_valid() {
let cache = X86LTOCache::new();
let data = vec![
b'L', b'T', b'O', b'X', 1u8, 0, 0, 0, 0, 0, 0, 0, 0, ];
let result = cache.deserialize_module(&data);
assert!(result.is_ok());
}
#[test]
fn test_codegen_multiple_functions() {
let tm = X86TargetMachine::default();
let mut cg = X86LTOCodegenPipeline::new(tm);
let mut m = make_module("multi");
m.functions.push(new_function("f1", Type::void(), &[]));
m.functions.push(new_function("f2", Type::void(), &[]));
let objs = cg
.codegen_module(&mut m, &X86TargetMachine::default())
.unwrap();
assert!(objs.len() >= 1);
}
#[test]
fn test_codegen_stats_after_run() {
let tm = X86TargetMachine::default();
let mut cg = X86LTOCodegenPipeline::new(tm);
let mut m = make_module_with_func("m", "f");
let _ = cg.codegen_module(&mut m, &X86TargetMachine::default());
assert!(cg.codegen_stats.codegen_time_ms >= 0);
}
#[test]
fn test_codegen_set_emit_debug_info_true() {
let tm = X86TargetMachine::default();
let mut cg = X86LTOCodegenPipeline::new(tm);
cg.set_emit_debug_info(true);
assert!(cg.emit_debug_info);
cg.set_emit_debug_info(false);
assert!(!cg.emit_debug_info);
}
#[test]
fn test_codegen_parallel_empty_modules() {
let tm = X86TargetMachine::default();
let mut cg = X86LTOCodegenPipeline::new(tm);
let objs = cg
.codegen_modules_parallel(&[], &X86TargetMachine::default())
.unwrap();
assert!(objs.is_empty());
}
#[test]
fn test_opt_pipeline_disabled() {
let mut pipeline = X86LTOOptimizationPipeline::new(OptimizationLevel::O2);
pipeline.enabled = false;
let mut m = make_module("e");
let stats = pipeline.run_pipeline(&mut m);
assert_eq!(stats.passes_executed, 0);
}
#[test]
fn test_opt_pipeline_stats_cumulative() {
let mut pipeline = X86LTOOptimizationPipeline::new(OptimizationLevel::O3);
let mut m = make_module("e");
let s1 = pipeline.run_pipeline(&mut m);
let s2 = pipeline.run_pipeline(&mut m);
assert!(s2.passes_executed >= s1.passes_executed);
}
#[test]
fn test_opt_pipeline_constant_merging_empty() {
let mut pipeline = X86LTOOptimizationPipeline::new(OptimizationLevel::O2);
let mut m = make_module("e");
let stats = pipeline.run_pipeline(&mut m);
assert_eq!(stats.constants_merged, 0);
}
#[test]
fn test_thin_lto_combined_index_build_multiple_modules() {
let config = LTOConfig::default();
let mut thin = X86ThinLTO::new(config);
let m1 = make_module_with_func("m1", "f1");
let m2 = make_module_with_func("m2", "f2");
let index = thin.build_combined_index(&[m1, m2]);
assert_eq!(index.num_modules(), 2);
}
#[test]
fn test_thin_lto_import_function_not_found() {
let config = LTOConfig::default();
let thin = X86ThinLTO::new(config);
let mut dest = make_module("dest");
let source = make_module("source");
let result = thin.import_function(&mut dest, &source, "nonexistent");
assert!(result.is_err());
}
#[test]
fn test_thin_lto_import_function_already_exists() {
let config = LTOConfig::default();
let thin = X86ThinLTO::new(config);
let mut dest = make_module_with_func("dest", "existing");
let source = make_module("source");
let result = thin.import_function(&mut dest, &source, "existing");
assert!(result.is_ok());
}
#[test]
fn test_thin_lto_compute_guid_nonzero() {
let guid = X86ThinLTO::compute_guid("test", Some(0));
assert!(guid > 0);
}
#[test]
fn test_thin_lto_module_hash_with_global() {
use llvm_native_core::constants::new_global;
let mut m = make_module("m");
m.globals.push(new_global(
Type::i32(),
true,
llvm_native_core::function::Linkage::External,
None,
"gv",
));
let hash = X86ThinLTO::compute_module_hash(&m);
assert!(hash > 0);
}
#[test]
fn test_module_summary_function_guid_mapping() {
let mut ms = X86LTOModuleSummary::new();
let m = make_module_with_func("m", "target");
ms.build_summary_for_module(&m, 0);
let guid = ms.name_to_guid.get("target");
assert!(guid.is_some());
let name = ms.guid_map.get(guid.unwrap());
assert_eq!(name, Some(&"target".to_string()));
}
#[test]
fn test_module_summary_multiple_functions() {
let mut ms = X86LTOModuleSummary::new();
let mut m = make_module("m");
m.functions.push(new_function("a", Type::void(), &[]));
m.functions.push(new_function("b", Type::void(), &[]));
m.functions.push(new_function("c", Type::void(), &[]));
let s = ms.build_summary_for_module(&m, 0);
assert_eq!(s.function_names.len(), 3);
}
#[test]
fn test_module_summary_function_instructions_count() {
let mut ms = X86LTOModuleSummary::new();
let m = make_module_with_func("m", "f");
ms.build_summary_for_module(&m, 0);
assert!(!ms.function_summaries.is_empty());
let fs = &ms.function_summaries[0];
assert_eq!(fs.instruction_count, 0);
}
#[test]
fn test_module_summary_global_guid() {
use llvm_native_core::constants::new_global;
let mut ms = X86LTOModuleSummary::new();
let mut m = make_module("m");
let g_ref = new_global(
Type::i32(),
true,
llvm_native_core::function::Linkage::External,
None,
"glob",
);
m.globals.push(g_ref);
ms.build_summary_for_module(&m, 5);
assert!(ms.name_to_guid.contains_key("glob"));
}
#[test]
fn test_lto_deep_default_stats_zero() {
let stats = X86LTOStats::default();
assert_eq!(stats.input_modules, 0);
assert_eq!(stats.object_files_produced, 0);
assert_eq!(stats.instructions_before, 0);
assert_eq!(stats.instructions_after, 0);
}
#[test]
fn test_lto_deep_stats_clone() {
let mut stats = X86LTOStats::default();
stats.devirt_sites = 42;
let cloned = stats.clone();
assert_eq!(cloned.devirt_sites, 42);
}
#[test]
fn test_lto_deep_diagnostics_multiple() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.emit_diagnostic(X86LTODiagLevel::Info, "i1", None, None);
lto.emit_diagnostic(X86LTODiagLevel::Warning, "w1", Some("m"), None);
lto.emit_diagnostic(X86LTODiagLevel::Error, "e1", Some("m"), Some("f"));
assert_eq!(lto.diagnostics.len(), 3);
assert_eq!(lto.get_errors().len(), 1);
assert!(lto.has_errors());
}
#[test]
fn test_lto_deep_max_cache_bytes() {
let tm = X86TargetMachine::default();
let lto = X86LTODeep::new(tm, OptimizationLevel::O2);
assert_eq!(lto.max_cache_bytes, 512 * 1024 * 1024);
}
#[test]
fn test_lto_deep_disable_devirt() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.enable_devirt = false;
assert!(!lto.enable_devirt);
}
#[test]
fn test_lto_deep_disable_internalize() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.enable_internalize = false;
assert!(!lto.enable_internalize);
}
#[test]
fn test_lto_deep_disable_cache() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.enable_cache = false;
assert!(!lto.enable_cache);
}
#[test]
fn test_full_lto_pipeline_runs_all_passes() {
let mut fl = X86FullLTO::new(OptimizationLevel::O3);
let mut m = make_module("e");
let s = fl.run_optimization_pipeline(&mut m);
assert!(s.passes_run >= 5);
}
#[test]
fn test_full_lto_o0_no_inlining() {
let mut fl = X86FullLTO::new(OptimizationLevel::O0);
fl.enable_cross_module_inline = true;
let mut m = make_module_with_func("m", "f");
let s = fl.run_optimization_pipeline(&mut m);
assert_eq!(s.cross_module_inlines, 0);
}
#[test]
fn test_full_lto_disable_global_dce() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
fl.enable_global_dce = false;
let mut m = make_module("e");
let s = fl.run_optimization_pipeline(&mut m);
assert_eq!(s.functions_eliminated, 0);
}
#[test]
fn test_full_lto_disable_ipo() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
fl.enable_ipo = false;
let mut m = make_module("e");
let s = fl.run_optimization_pipeline(&mut m);
assert_eq!(s.constants_propagated, 0);
assert_eq!(s.dead_args_eliminated, 0);
}
#[test]
fn test_full_lto_merge_two_modules() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let m1 = make_module_with_func("m1", "f1");
let m2 = make_module_with_func("m2", "f2");
let result = fl.merge_modules(&[m1, m2]);
assert!(result.is_ok());
assert_eq!(fl.stats.modules_merged, 2);
}
#[test]
fn test_full_lto_merge_preserves_all_functions() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let m1 = make_module_with_func("m1", "f1");
let m2 = make_module_with_func("m2", "f2");
let merged = fl.merge_modules(&[m1, m2]).unwrap();
assert!(merged.functions.len() >= 2);
}
#[test]
fn test_full_lto_identify_roots_includes_main() {
let fl = X86FullLTO::new(OptimizationLevel::O2);
let mut m = make_module_with_func("m", "main");
m.functions.push(new_function("helper", Type::void(), &[]));
let roots = fl.identify_root_symbols(&m);
assert!(roots.contains(&"main".to_string()));
}
#[test]
fn test_full_lto_hash_different_bodies() {
let f1_ref = new_function("a", Type::void(), &[Type::i32()]);
let f2_ref = new_function("b", Type::void(), &[]);
let h1 = X86FullLTO::hash_function_body(&f1_ref.borrow());
let h2 = X86FullLTO::hash_function_body(&f2_ref.borrow());
}
#[test]
fn test_lto_result_full_mode() {
let result = X86LTOResult {
mode: LTOMode::Full,
optimized_modules: vec![],
object_files: vec![],
stats: X86LTOStats::default(),
};
assert!(matches!(result.mode, LTOMode::Full));
}
#[test]
fn test_lto_result_thin_mode() {
let result = X86LTOResult {
mode: LTOMode::Thin,
optimized_modules: vec![],
object_files: vec![],
stats: X86LTOStats::default(),
};
assert!(matches!(result.mode, LTOMode::Thin));
}
#[test]
fn test_lto_error_all_variants_display() {
let errors = [
X86LTOError::NoModules,
X86LTOError::MergeFailed("m".into()),
X86LTOError::InternalizationFailed("i".into()),
X86LTOError::OptimizationFailed("o".into()),
X86LTOError::CodegenFailed("c".into()),
X86LTOError::CacheError("ca".into()),
X86LTOError::SummaryError("s".into()),
X86LTOError::ImportError("im".into()),
X86LTOError::LinkConflict("lc".into()),
];
for e in &errors {
let s = format!("{}", e);
assert!(!s.is_empty());
}
}
#[test]
fn test_lto_error_io_from() {
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
let lto_err: X86LTOError = io_err.into();
assert!(matches!(lto_err, X86LTOError::IoError(_)));
}
#[test]
fn test_symbol_visibility_values_distinct() {
assert_ne!(X86SymbolVisibility::Default, X86SymbolVisibility::Hidden);
assert_ne!(X86SymbolVisibility::Hidden, X86SymbolVisibility::Protected);
assert_ne!(
X86SymbolVisibility::Protected,
X86SymbolVisibility::Internal
);
}
#[test]
fn test_symbol_visibility_eq() {
assert_eq!(X86SymbolVisibility::Default, X86SymbolVisibility::Default);
assert_eq!(X86SymbolVisibility::Hidden, X86SymbolVisibility::Hidden);
}
#[test]
fn test_symbol_visibility_clone() {
let v = X86SymbolVisibility::Protected;
let c = v.clone();
assert_eq!(v, c);
}
#[test]
fn test_cfi_check_type_all_distinct() {
let types = [
CfiCheckType::TypeTest,
CfiCheckType::CheckedLoad,
CfiCheckType::AddressTaken,
CfiCheckType::ICall,
];
for i in 0..types.len() {
for j in 0..types.len() {
if i != j {
assert_ne!(types[i], types[j]);
}
}
}
}
#[test]
fn test_type_metadata_create() {
let tm = X86TypeMetadata {
type_id: "T123".into(),
vtable_names: vec!["_ZTV3Foo".into()],
address_points: vec![16],
all_vtables: vec!["_ZTV3Foo".into(), "_ZTV7Derived".into()],
};
assert_eq!(tm.vtable_names.len(), 1);
assert_eq!(tm.all_vtables.len(), 2);
}
#[test]
fn test_vtable_entry_rtti() {
let entry = VtableEntry {
offset: 0,
target_function: "typeinfo_for_Foo".into(),
is_rtti: true,
};
assert!(entry.is_rtti);
assert_eq!(entry.offset, 0);
}
#[test]
fn test_vtable_entry_virtual_fn() {
let entry = VtableEntry {
offset: 16,
target_function: "Foo_vfunc".into(),
is_rtti: false,
};
assert!(!entry.is_rtti);
assert_eq!(entry.offset, 16);
}
#[test]
fn test_vtable_summary_empty_entries() {
let vs = X86VtableSummary {
name: "_ZTV4Base".into(),
type_id: "type_base".into(),
entries: vec![],
address_point: 16,
};
assert_eq!(vs.name, "_ZTV4Base");
assert_eq!(vs.address_point, 16);
assert!(vs.entries.is_empty());
}
#[test]
fn test_type_hierarchy_leaf_node() {
let node = TypeHierarchyNode {
type_id: "LeafType".into(),
parents: vec!["MidType".into()],
children: vec![],
vtable: Some("_ZTV8LeafType".into()),
is_final: true,
};
assert!(node.children.is_empty());
assert!(node.is_final);
assert_eq!(node.parents.len(), 1);
}
#[test]
fn test_type_hierarchy_root_node() {
let node = TypeHierarchyNode {
type_id: "RootType".into(),
parents: vec![],
children: vec!["ChildA".into(), "ChildB".into()],
vtable: Some("_ZTV8RootType".into()),
is_final: false,
};
assert!(node.parents.is_empty());
assert_eq!(node.children.len(), 2);
assert!(!node.is_final);
}
#[test]
fn test_vtable_info_multiple_base_vtables() {
let info = X86VtableInfo {
name: "_ZTV5Multi".into(),
type_id: "type_multi".into(),
entries: vec![],
address_point: 24,
base_vtables: vec!["_ZTV4Base".into(), "_ZTV6Base2".into()],
};
assert_eq!(info.base_vtables.len(), 2);
}
#[test]
fn test_virtual_function_pure() {
let vfi = X86VirtualFunctionInfo {
vtable_offset: 8,
target: "__cxa_pure_virtual".into(),
is_pure_virtual: true,
is_rtti: false,
};
assert!(vfi.is_pure_virtual);
assert!(!vfi.is_rtti);
}
#[test]
fn test_import_plan_multiple_modules() {
let plan = ThinLTOImportPlan {
per_module_imports: vec![
vec![("f_a".into(), 1)],
vec![("f_b".into(), 0), ("f_c".into(), 0)],
vec![],
],
total_imports: 3,
total_benefit: 300,
};
assert_eq!(plan.per_module_imports.len(), 3);
assert_eq!(plan.per_module_imports[0].len(), 1);
assert_eq!(plan.per_module_imports[1].len(), 2);
assert!(plan.per_module_imports[2].is_empty());
assert_eq!(plan.total_imports, 3);
}
#[test]
fn test_function_summary_readonly() {
let fs = X86FunctionSummary {
name: "pure_fn".into(),
guid: 123,
module_index: 0,
params: vec![],
return_type: "i32".into(),
calls: vec![],
refs: vec![],
instruction_count: 5,
is_readonly: true,
is_readnone: false,
has_side_effects: false,
has_inline_asm: false,
is_declaration: false,
linkage: GlobalLinkage::External,
visibility: X86SymbolVisibility::Default,
hotness: 0.9,
};
assert!(fs.is_readonly);
assert!(!fs.is_readnone);
assert!(!fs.has_side_effects);
}
#[test]
fn test_function_summary_readnone() {
let fs = X86FunctionSummary {
name: "const_fn".into(),
guid: 456,
module_index: 1,
params: vec!["i64".into()],
return_type: "i64".into(),
calls: vec![],
refs: vec![],
instruction_count: 3,
is_readonly: true,
is_readnone: true,
has_side_effects: false,
has_inline_asm: false,
is_declaration: false,
linkage: GlobalLinkage::Internal,
visibility: X86SymbolVisibility::Hidden,
hotness: 0.0,
};
assert!(fs.is_readonly);
assert!(fs.is_readnone);
assert_eq!(fs.linkage, GlobalLinkage::Internal);
assert_eq!(fs.visibility, X86SymbolVisibility::Hidden);
}
#[test]
fn test_function_summary_inline_asm() {
let fs = X86FunctionSummary {
name: "asm_fn".into(),
guid: 789,
module_index: 2,
params: vec![],
return_type: "void".into(),
calls: vec![],
refs: vec![],
instruction_count: 1,
is_readonly: false,
is_readnone: false,
has_side_effects: true,
has_inline_asm: true,
is_declaration: false,
linkage: GlobalLinkage::External,
visibility: X86SymbolVisibility::Default,
hotness: 0.1,
};
assert!(fs.has_inline_asm);
assert!(fs.has_side_effects);
}
#[test]
fn test_x86_global_value_summary_thread_local() {
let gs = X86GlobalValueSummary {
name: "tls_var".into(),
guid: 999,
module_index: 0,
linkage: GlobalLinkage::External,
visibility: X86SymbolVisibility::Default,
value_type: "i64".into(),
is_constant: false,
is_thread_local: true,
has_initializer: true,
section: Some(".tdata".into()),
alignment: 8,
};
assert!(gs.is_thread_local);
assert_eq!(gs.section, Some(".tdata".to_string()));
assert_eq!(gs.alignment, 8);
}
#[test]
fn test_x86_global_value_summary_alignment() {
let gs = X86GlobalValueSummary {
name: "aligned".into(),
guid: 111,
module_index: 3,
linkage: GlobalLinkage::Internal,
visibility: X86SymbolVisibility::Hidden,
value_type: "[16 x i8]".into(),
is_constant: true,
is_thread_local: false,
has_initializer: true,
section: None,
alignment: 16,
};
assert_eq!(gs.alignment, 16);
assert!(gs.is_constant);
}
#[test]
fn test_codegen_stats_accumulation() {
let mut stats = CodegenStats::default();
stats.functions_codegened += 1;
stats.object_size_bytes += 1024;
assert_eq!(stats.functions_codegened, 1);
assert_eq!(stats.object_size_bytes, 1024);
}
#[test]
fn test_pipeline_stats_accumulation() {
let mut stats = PipelineStats::default();
stats.dead_functions += 10;
stats.cross_module_inlines += 5;
stats.functions_merged += 3;
assert_eq!(stats.dead_functions, 10);
assert_eq!(stats.cross_module_inlines, 5);
assert_eq!(stats.functions_merged, 3);
}
#[test]
fn test_pipeline_stats_clone() {
let mut stats = PipelineStats::default();
stats.passes_executed = 42;
let cloned = stats.clone();
assert_eq!(cloned.passes_executed, 42);
}
#[test]
fn test_count_module_instructions_zero_for_empty() {
let m = make_module("e");
assert_eq!(count_module_instructions(&m), 0);
}
#[test]
fn test_count_module_instructions_with_globals() {
use llvm_native_core::constants::new_global;
let mut m = make_module("m");
m.globals.push(new_global(
Type::i32(),
true,
llvm_native_core::function::Linkage::External,
None,
"gv",
));
assert_eq!(count_module_instructions(&m), 0);
}
#[test]
fn test_full_lto_constant_merging_empty() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let mut m = make_module("e");
let count = fl.perform_constant_merging(&mut m);
assert_eq!(count, 0);
}
#[test]
fn test_full_lto_function_merging_empty() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let mut m = make_module("e");
let count = fl.perform_function_merging(&mut m);
assert_eq!(count, 0);
}
#[test]
fn test_cached_entry_creation() {
let entry = CachedEntry {
data: vec![1, 2, 3],
function_count: 5,
size_bytes: 1024,
created_at: 1000,
access_count: 0,
last_accessed: 1000,
};
assert_eq!(entry.function_count, 5);
assert_eq!(entry.size_bytes, 1024);
assert_eq!(entry.data.len(), 3);
}
#[test]
fn test_cache_key_different_features() {
let k1 = X86LTOCacheKey {
module_hash: 1,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
let k2 = X86LTOCacheKey {
module_hash: 1,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64-v3".into(),
};
assert_ne!(k1, k2);
}
#[test]
fn test_cache_key_clone() {
let k = X86LTOCacheKey {
module_hash: 42,
opt_level: "O3".into(),
is_thin_lto: false,
target_features: "x86_64".into(),
};
let cloned = k.clone();
assert_eq!(k, cloned);
}
#[test]
fn test_diagnostic_with_location() {
let d = X86LTODiagnostic {
level: X86LTODiagLevel::Remark,
message: "inlined".into(),
module_name: Some("mod".into()),
function_name: Some("fun".into()),
location: Some("file.ll:42:5".into()),
};
assert_eq!(d.level, X86LTODiagLevel::Remark);
assert!(d.location.is_some());
}
#[test]
fn test_pass_manager_new() {
let pm = X86LTOPassManager::new(OptimizationLevel::O2);
assert!(!pm.initialized);
assert_eq!(pm.total_passes_executed, 0);
}
#[test]
fn test_pass_manager_initialize() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
assert!(pm.initialized);
assert!(!pm.passes.is_empty());
}
#[test]
fn test_pass_manager_o3_more_passes() {
let mut pm2 = X86LTOPassManager::new(OptimizationLevel::O2);
let mut pm3 = X86LTOPassManager::new(OptimizationLevel::O3);
pm2.initialize();
pm3.initialize();
assert!(pm3.passes.len() > pm2.passes.len());
}
#[test]
fn test_pass_manager_get_pass_order() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let order = pm.get_pass_order();
assert!(order.contains(&"globaldce".to_string()));
assert!(order.contains(&"inline".to_string()));
}
#[test]
fn test_pass_manager_run_on_module() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
let mut m = make_module("e");
let stats = pm.run(&mut m);
assert!(stats.passes_executed > 0);
}
#[test]
fn test_pass_manager_stats_accumulated() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let mut m = make_module("e");
pm.run(&mut m);
assert!(pm.total_passes_executed > 0);
}
#[test]
fn test_pass_manager_pass_stats_exist() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
assert!(pm.get_pass_stats("globaldce").is_some());
}
#[test]
fn test_pass_manager_pass_stats_nonexistent() {
let pm = X86LTOPassManager::new(OptimizationLevel::O2);
assert!(pm.get_pass_stats("nonexistent").is_none());
}
#[test]
fn test_pass_manager_total_time() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let mut m = make_module("e");
pm.run(&mut m);
let total = pm.total_pass_time_ms();
assert!(total >= 0);
}
#[test]
fn test_pass_manager_verify_after_each_pass() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
assert!(!pm.verify_after_each_pass);
pm.verify_after_each_pass = true;
assert!(pm.verify_after_each_pass);
}
#[test]
fn test_pass_execution_result_default() {
let r = PassExecutionResult::default();
assert!(!r.changed);
assert_eq!(r.functions_modified, 0);
assert_eq!(r.instructions_eliminated, 0);
}
#[test]
fn test_debug_merger_new() {
let dm = X86LTODebugInfoMerger::new();
assert!(dm.source_cus.is_empty());
assert_eq!(dm.merge_stats.cus_merged, 0);
}
#[test]
fn test_debug_merger_add_cu() {
let mut dm = X86LTODebugInfoMerger::new();
let cu = DebugCompilationUnit {
source_file: "test.c".into(),
comp_dir: "/src".into(),
producer: "clang 18.0".into(),
dwarf_version: 4,
address_size: 8,
line_entries: vec![],
subprograms: vec![],
global_variables: vec![],
};
dm.add_compilation_unit(cu);
assert_eq!(dm.source_cus.len(), 1);
}
#[test]
fn test_debug_merger_merge_empty() {
let mut dm = X86LTODebugInfoMerger::new();
dm.merge();
assert_eq!(dm.merge_stats.cus_merged, 0);
}
#[test]
fn test_debug_merger_merge_single_cu() {
let mut dm = X86LTODebugInfoMerger::new();
let cu = DebugCompilationUnit {
source_file: "a.c".into(),
comp_dir: "/tmp".into(),
producer: "clang".into(),
dwarf_version: 4,
address_size: 8,
line_entries: vec![DebugLineEntry {
file_index: 0,
line: 10,
column: 5,
address: 0x1000,
is_stmt: true,
is_basic_block: false,
prologue_end: false,
epilogue_begin: false,
}],
subprograms: vec![],
global_variables: vec![],
};
dm.add_compilation_unit(cu);
dm.merge();
assert_eq!(dm.merge_stats.line_entries_merged, 1);
}
#[test]
fn test_debug_merger_emit() {
let mut dm = X86LTODebugInfoMerger::new();
let cu = DebugCompilationUnit {
source_file: "x.c".into(),
comp_dir: ".".into(),
producer: "clang".into(),
dwarf_version: 4,
address_size: 8,
line_entries: vec![],
subprograms: vec![DebugSubprogram {
name: "main".into(),
linkage_name: "main".into(),
file: "x.c".into(),
line: 1,
is_declaration: false,
is_external: true,
low_pc: 0x1000,
high_pc: 0x1100,
frame_base: None,
inlined_subroutines: vec![],
variables: vec![],
}],
global_variables: vec![],
};
dm.add_compilation_unit(cu);
dm.merge();
let data = dm.emit_debug_info();
assert!(!data.is_empty());
}
#[test]
fn test_debug_merger_dedup_files() {
let mut dm = X86LTODebugInfoMerger::new();
let cu1 = DebugCompilationUnit {
source_file: "common.c".into(),
comp_dir: "/a".into(),
producer: "c1".into(),
dwarf_version: 4,
address_size: 8,
line_entries: vec![],
subprograms: vec![],
global_variables: vec![],
};
let cu2 = DebugCompilationUnit {
source_file: "common.c".into(),
comp_dir: "/b".into(),
producer: "c2".into(),
dwarf_version: 4,
address_size: 8,
line_entries: vec![],
subprograms: vec![],
global_variables: vec![],
};
dm.add_compilation_unit(cu1);
dm.add_compilation_unit(cu2);
dm.merge();
assert!(dm.file_dedup.len() <= 2);
}
#[test]
fn test_debug_merger_stats() {
let dm = X86LTODebugInfoMerger::new();
let stats = dm.get_stats();
assert_eq!(stats.cus_merged, 0);
}
#[test]
fn test_debug_merger_split_dwarf() {
let mut dm = X86LTODebugInfoMerger::new();
assert!(!dm.split_dwarf);
dm.split_dwarf = true;
assert!(dm.split_dwarf);
}
#[test]
fn test_pgo_new() {
let pgo = X86LTOProfileGuidedOptimization::new();
assert!(!pgo.has_profile);
assert_eq!(pgo.total_samples, 0);
}
#[test]
fn test_pgo_record_function_count() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_function_count("hot_fn", 10000);
assert_eq!(pgo.function_counts.get("hot_fn"), Some(&10000));
assert_eq!(pgo.total_samples, 10000);
}
#[test]
fn test_pgo_build_summary() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_function_count("a", 100);
pgo.record_function_count("b", 50);
pgo.record_function_count("c", 25);
pgo.build_summary();
assert!(pgo.has_profile);
assert!(pgo.profile_summary.is_some());
let s = pgo.profile_summary.as_ref().unwrap();
assert_eq!(s.num_functions, 3);
assert_eq!(s.max_count, 100);
assert_eq!(s.total_count, 175);
}
#[test]
fn test_pgo_hotness() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_function_count("hot", 1000);
pgo.record_function_count("cold", 1);
pgo.build_summary();
let hot_hotness = pgo.get_function_hotness("hot");
let cold_hotness = pgo.get_function_hotness("cold");
assert!(hot_hotness > cold_hotness);
assert!(pgo.is_hot("hot"));
assert!(!pgo.is_hot("cold"));
}
#[test]
fn test_pgo_no_profile_hotness() {
let pgo = X86LTOProfileGuidedOptimization::new();
assert_eq!(pgo.get_function_hotness("any"), 0.0);
assert!(!pgo.is_hot("any"));
}
#[test]
fn test_pgo_record_indirect_target() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_indirect_call_target("cs1", "target_a", 500);
pgo.record_indirect_call_target("cs1", "target_b", 10);
let likely = pgo.get_likely_indirect_target("cs1");
assert_eq!(likely, Some("target_a".to_string()));
}
#[test]
fn test_pgo_record_edge_count() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_edge_count("caller", "callee", 42);
assert_eq!(pgo.get_edge_count("caller", "callee"), 42);
}
#[test]
fn test_pgo_clear() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_function_count("f", 100);
pgo.build_summary();
pgo.clear();
assert!(pgo.function_counts.is_empty());
assert!(!pgo.has_profile);
}
#[test]
fn test_comdat_resolver_new() {
let cr = X86LTOComdatResolver::new();
assert!(cr.comdat_groups.is_empty());
assert_eq!(cr.resolved_count(), 0);
}
#[test]
fn test_comdat_resolver_register_and_resolve_any() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat("c1", 0, vec!["f1".into()], ComdatSelectionKind::Any, 100);
cr.resolve();
assert!(cr.is_prevailing("c1", 0));
assert_eq!(cr.resolved_count(), 1);
}
#[test]
fn test_comdat_resolver_multi_module_any() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat(
"shared",
0,
vec!["f_a".into()],
ComdatSelectionKind::Any,
100,
);
cr.register_comdat(
"shared",
1,
vec!["f_b".into()],
ComdatSelectionKind::Any,
200,
);
cr.resolve();
assert!(cr.is_prevailing("shared", 0));
assert!(!cr.is_prevailing("shared", 1));
}
#[test]
fn test_comdat_resolver_no_duplicate() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat(
"unique",
0,
vec!["f".into()],
ComdatSelectionKind::NoDeduplicate,
50,
);
cr.register_comdat(
"unique",
1,
vec!["f".into()],
ComdatSelectionKind::NoDeduplicate,
50,
);
cr.resolve();
assert!(
cr.get_prevailing_module("unique.0").is_some()
|| cr.get_prevailing_module("unique.1").is_some()
);
}
#[test]
fn test_comdat_resolver_conflicts() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat(
"exact",
0,
vec!["f1".into()],
ComdatSelectionKind::ExactMatch,
100,
);
cr.register_comdat(
"exact",
1,
vec!["f2".into()],
ComdatSelectionKind::ExactMatch,
100,
);
cr.resolve();
assert!(!cr.get_conflicts().is_empty());
}
#[test]
fn test_metadata_merger_new() {
let mm = X86LTOMetadataMerger::new();
assert!(mm.named_metadata.is_empty());
assert!(!mm.has_conflicts());
}
#[test]
fn test_metadata_merger_allocate_node_id() {
let mut mm = X86LTOMetadataMerger::new();
let id1 = mm.allocate_node_id();
let id2 = mm.allocate_node_id();
assert!(id2 > id1);
}
#[test]
fn test_metadata_merger_merge_named_metadata() {
let mut mm = X86LTOMetadataMerger::new();
mm.merge_named_metadata(0, "custom.meta", vec![10, 20]);
assert!(mm.named_metadata.contains_key("custom.meta"));
assert_eq!(mm.named_metadata["custom.meta"].len(), 2);
}
#[test]
fn test_metadata_merger_merge_type_metadata() {
let mut mm = X86LTOMetadataMerger::new();
mm.merge_type_metadata(0, "type_abc", 16);
mm.merge_type_metadata(1, "type_abc", 16);
assert!(mm.type_metadata.contains_key("type_abc"));
assert!(mm.type_metadata["type_abc"].is_duplicate);
}
#[test]
fn test_metadata_merger_remap_debug_node() {
let mut mm = X86LTOMetadataMerger::new();
mm.remap_debug_node(5, 105);
assert_eq!(mm.get_remapped_id(5), Some(105));
assert_eq!(mm.get_remapped_id(999), None);
}
#[test]
fn test_alias_resolver_new() {
let ar = X86LTOAliasResolver::new();
assert!(ar.aliases.is_empty());
assert_eq!(ar.stats.total_aliases, 0);
}
#[test]
fn test_alias_resolver_register_alias() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("alias_a", "target_fn", "internal", 0);
assert!(ar.aliases.contains_key("alias_a"));
assert_eq!(ar.aliases["alias_a"].target, "target_fn");
}
#[test]
fn test_alias_resolver_register_ifunc() {
let mut ar = X86LTOAliasResolver::new();
ar.register_ifunc("resolve_foo", "foo_resolver", 0);
assert!(ar.ifuncs.contains_key("resolve_foo"));
}
#[test]
fn test_alias_resolver_resolve_simple_chain() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("a", "b", "internal", 0);
ar.register_alias("b", "final_fn", "internal", 0);
let target = ar.resolve_alias_chain("a");
assert_eq!(target, "final_fn");
}
#[test]
fn test_alias_resolver_detect_cycle() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("x", "y", "internal", 0);
ar.register_alias("y", "x", "internal", 0);
ar.detect_cycles();
assert!(!ar.cycles.is_empty());
assert_eq!(ar.stats.cycles_detected, 1);
}
#[test]
fn test_alias_resolver_resolve_on_module() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("alias1", "real_fn", "internal", 0);
let mut m = make_module("m");
ar.resolve(&mut m);
assert!(ar.resolved.contains("alias1"));
assert_eq!(ar.stats.resolved_aliases, 1);
}
#[test]
fn test_alias_resolver_self_cycle() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("self", "self", "internal", 0);
ar.detect_cycles();
assert!(ar.is_in_cycle("self"));
}
#[test]
fn test_summary_builder_new() {
let sb = X86LTOFunctionSummaryBuilder::new();
assert!(sb.summaries.is_empty());
assert_eq!(sb.builder_stats.functions_summarized, 0);
}
#[test]
fn test_summary_builder_build_for_module() {
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module_with_func("m", "f");
sb.build_for_module(&m, 0);
assert_eq!(sb.builder_stats.functions_summarized, 1);
assert_eq!(sb.summaries.len(), 1);
assert_eq!(sb.summaries[0].name, "f");
}
#[test]
fn test_summary_builder_get_call_graph() {
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module_with_func("m", "caller");
sb.build_for_module(&m, 0);
let cg = sb.get_call_graph();
assert!(cg.is_empty() || !cg.is_empty());
}
#[test]
fn test_summary_builder_import_eligible() {
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module_with_func("m", "small_fn");
sb.build_for_module(&m, 0);
let eligible = sb.get_import_eligible(100);
assert!(eligible.len() >= 0);
}
#[test]
fn test_summary_builder_clear() {
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module_with_func("m", "f");
sb.build_for_module(&m, 0);
sb.clear();
assert!(sb.summaries.is_empty());
assert_eq!(sb.builder_stats.functions_summarized, 0);
}
#[test]
fn test_full_lto_with_pass_manager() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let mut m = make_module_with_func("m", "main");
let merged = fl.merge_modules(&[m]).unwrap();
let mut merged_m = merged;
let stats = pm.run(&mut merged_m);
assert!(stats.passes_executed > 0);
}
#[test]
fn test_thin_lto_with_summary_builder() {
let config = LTOConfig::default();
let mut thin = X86LTOThin::new(config);
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module_with_func("m", "f");
sb.build_for_module(&m, 0);
let index = thin.build_combined_index(&[m]);
assert_eq!(index.num_modules(), 1);
}
#[test]
fn test_comdat_resolver_integration() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat(
"comdat_weak",
0,
vec!["weak_fn".into()],
ComdatSelectionKind::Any,
64,
);
cr.register_comdat(
"comdat_weak",
1,
vec!["weak_fn".into()],
ComdatSelectionKind::Any,
64,
);
cr.resolve();
assert!(cr.resolved_count() >= 1);
}
#[test]
fn test_pgo_integration_with_devirt() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_function_count("Base_foo", 100);
pgo.record_indirect_call_target("vcall_1", "Base_foo", 95);
pgo.record_indirect_call_target("vcall_1", "Derived_foo", 5);
let likely = pgo.get_likely_indirect_target("vcall_1");
assert_eq!(likely, Some("Base_foo".to_string()));
}
#[test]
fn test_alias_resolver_complex_chain() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("level1", "level2", "internal", 0);
ar.register_alias("level2", "level3", "internal", 0);
ar.register_alias("level3", "actual_fn", "external", 0);
let target = ar.resolve_alias_chain("level1");
assert_eq!(target, "actual_fn");
}
#[test]
fn test_debug_merger_full_pipeline() {
let mut dm = X86LTODebugInfoMerger::new();
dm.add_compilation_unit(DebugCompilationUnit {
source_file: "a.c".into(),
comp_dir: "/src".into(),
producer: "clang".into(),
dwarf_version: 5,
address_size: 8,
line_entries: vec![DebugLineEntry {
file_index: 1,
line: 42,
column: 10,
address: 0x4000,
is_stmt: true,
is_basic_block: true,
prologue_end: false,
epilogue_begin: false,
}],
subprograms: vec![DebugSubprogram {
name: "foo".into(),
linkage_name: "_Z3foov".into(),
file: "a.c".into(),
line: 42,
is_declaration: false,
is_external: true,
low_pc: 0x4000,
high_pc: 0x4100,
frame_base: Some("DW_OP_reg6 RBP".into()),
inlined_subroutines: vec![DebugInlinedSubroutine {
abstract_origin: "bar".into(),
call_file: "a.c".into(),
call_line: 45,
low_pc: 0x4050,
high_pc: 0x4080,
}],
variables: vec![DebugVariable {
name: "x".into(),
type_ref: "int".into(),
file: "a.c".into(),
line: 43,
location: Some(vec![0x91, 0x68]),
}],
}],
global_variables: vec![DebugGlobalVariable {
name: "g_counter".into(),
linkage_name: "g_counter".into(),
type_ref: "int".into(),
file: "a.c".into(),
line: 1,
is_declaration: false,
location: None,
}],
});
dm.merge();
let data = dm.emit_debug_info();
assert!(data.len() > 20);
}
#[test]
fn test_pass_manager_prerequisites_in_order() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let order = pm.get_pass_order();
let globaldce_pos = order.iter().position(|n| n == "globaldce");
let internalize_pos = order.iter().position(|n| n == "internalize");
let inline_pos = order.iter().position(|n| n == "inline");
if let (Some(g), Some(i)) = (globaldce_pos, internalize_pos) {
assert!(g < i, "globaldce must come before internalize");
}
}
#[test]
fn test_cache_many_entries() {
let mut cache = X86LTOCache::new();
cache.set_max_size(1_000_000);
for i in 0..200 {
let key = X86LTOCacheKey {
module_hash: i,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
cache.store(&key, vec![0u8; 100]);
}
assert!(cache.entry_count() > 0);
assert!(cache.current_size() <= 1_000_000);
}
#[test]
fn test_devirtualization_many_vtables() {
let mut devirt = X86LTODevirtualization::new();
for i in 0..50 {
let entries = vec![
X86VirtualFunctionInfo {
vtable_offset: 0,
target: format!("RTTI_{}", i),
is_pure_virtual: false,
is_rtti: true,
},
X86VirtualFunctionInfo {
vtable_offset: 8,
target: format!("fn_{}", i),
is_pure_virtual: false,
is_rtti: false,
},
];
devirt.register_vtable(&format!("_ZTV{}", i), &format!("type_{}", i), entries, 16);
}
assert_eq!(devirt.vtables.len(), 50);
}
#[test]
fn test_internalization_many_symbols() {
let mut intern = X86LTOInternalization::new();
for i in 0..100 {
let name = format!("func_{}", i);
if i % 10 == 0 {
intern.preserve_symbol(&name);
}
}
assert_eq!(intern.preserved_symbols.len(), 10);
assert!(!intern.can_internalize("func_0", "internal"));
assert!(intern.can_internalize("func_1", "internal"));
}
#[test]
fn test_module_summary_many_functions() {
let mut ms = X86LTOModuleSummary::new();
let mut m = make_module("big");
for i in 0..50 {
m.functions
.push(new_function(&format!("f{}", i), Type::void(), &[]));
}
let s = ms.build_summary_for_module(&m, 0);
assert_eq!(s.function_names.len(), 50);
assert_eq!(ms.guid_map.len(), 50);
}
#[test]
fn test_pass_manager_repeated_runs() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
let mut m = make_module("e");
pm.run(&mut m);
let first_count = pm.total_passes_executed;
pm.run(&mut m);
assert!(pm.total_passes_executed > first_count);
}
#[test]
fn test_cache_store_lookup_roundtrip() {
let mut cache = X86LTOCache::new();
let original = vec![0xde, 0xad, 0xbe, 0xef];
let key = X86LTOCacheKey {
module_hash: 0xdeadbeef,
opt_level: "O2".into(),
is_thin_lto: false,
target_features: "x86_64".into(),
};
cache.store(&key, original.clone());
let retrieved = cache.lookup(&key);
assert_eq!(retrieved, Some(original));
}
#[test]
fn test_guid_idempotent() {
let g1 = X86ThinLTO::compute_guid("constant_name", Some(7));
let g2 = X86ThinLTO::compute_guid("constant_name", Some(7));
assert_eq!(g1, g2);
}
#[test]
fn test_lto_error_debug_trait() {
let err = X86LTOError::CodegenFailed("test".into());
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("CodegenFailed"));
}
#[test]
fn test_comdat_selection_kind_equality() {
assert_eq!(ComdatSelectionKind::Any, ComdatSelectionKind::Any);
assert_ne!(ComdatSelectionKind::Any, ComdatSelectionKind::ExactMatch);
assert_ne!(
ComdatSelectionKind::Largest,
ComdatSelectionKind::NoDeduplicate
);
}
#[test]
fn test_metadata_conflict_struct() {
let mc = MetadataConflict {
metadata_kind: "module_flags".into(),
name: "wchar_size".into(),
modules: vec![0, 1],
description: "conflicting values".into(),
};
assert_eq!(mc.modules.len(), 2);
}
#[test]
fn test_comdat_conflict_struct() {
let cc = ComdatConflict {
comdat_name: "grp1".into(),
modules: vec![0, 1, 2],
reason: "size mismatch".into(),
};
assert_eq!(cc.modules.len(), 3);
}
#[test]
fn test_profile_summary_values() {
let ps = ProfileDataSummary {
num_functions: 100,
max_count: 10000,
total_count: 50000,
hot_cutoff: 5000,
hot_function_count: 15,
block_coverage_pct: 95.5,
};
assert_eq!(ps.hot_function_count, 15);
assert!(ps.block_coverage_pct > 90.0);
}
#[test]
fn test_cache_serialize_deserialize_empty_module() {
let cache = X86LTOCache::new();
let m = make_module("e");
let data = cache.serialize_module(&m).unwrap();
assert!(data.len() >= 13);
let deser = cache.deserialize_module(&data).unwrap();
assert_eq!(deser.name, "cached");
}
#[test]
fn test_cache_serialize_preserves_format() {
let cache = X86LTOCache::new();
let m = make_module_with_func("m", "fn");
let data = cache.serialize_module(&m).unwrap();
assert_eq!(&data[0..4], b"LTOX");
assert_eq!(data[4], 1u8);
}
#[test]
fn test_full_lto_merge_three_modules() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let m1 = make_module_with_func("m1", "f1");
let m2 = make_module_with_func("m2", "f2");
let m3 = make_module_with_func("m3", "f3");
let result = fl.merge_modules(&[m1, m2, m3]);
assert!(result.is_ok());
assert_eq!(fl.stats.modules_merged, 3);
}
#[test]
fn test_full_lto_merge_function_count_preserved() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let m1 = make_module_with_func("m1", "a");
let m2 = make_module_with_func("m2", "b");
let merged = fl.merge_modules(&[m1, m2]).unwrap();
assert!(merged.functions.len() >= 2);
}
#[test]
fn test_full_lto_stats_after_pipeline() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let mut m = make_module("e");
let stats = fl.run_optimization_pipeline(&mut m);
assert!(stats.passes_run > 0);
assert!(stats.modules_merged >= 0);
}
#[test]
fn test_full_lto_o0_minimal_passes() {
let mut fl = X86FullLTO::new(OptimizationLevel::O0);
let mut m = make_module("e");
let stats = fl.run_optimization_pipeline(&mut m);
assert!(stats.passes_run >= 0);
}
#[test]
fn test_full_lto_os_vs_o3_pass_counts() {
let mut fl_os = X86FullLTO::new(OptimizationLevel::Os);
let mut fl_o3 = X86FullLTO::new(OptimizationLevel::O3);
let mut m1 = make_module("e");
let mut m2 = make_module("e");
let s_os = fl_os.run_optimization_pipeline(&mut m1);
let s_o3 = fl_o3.run_optimization_pipeline(&mut m2);
assert!(s_os.passes_run > 0);
assert!(s_o3.passes_run > 0);
}
#[test]
fn test_thin_lto_distributed_backend_single_module() {
let config = LTOConfig::default();
let thin = X86ThinLTO::new(config);
let m = make_module_with_func("m", "f");
let index = CombinedIndex::new();
let plan = ThinLTOImportPlan {
per_module_imports: vec![vec![]],
total_imports: 0,
total_benefit: 0,
};
let result = thin.run_distributed_backends(&[m], &index, &plan);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_thin_lto_distributed_backend_multi_module() {
let config = LTOConfig::default();
let thin = X86ThinLTO::new(config);
let m1 = make_module_with_func("m1", "f1");
let m2 = make_module_with_func("m2", "f2");
let index = CombinedIndex::new();
let plan = ThinLTOImportPlan {
per_module_imports: vec![vec![], vec![]],
total_imports: 0,
total_benefit: 0,
};
let result = thin.run_distributed_backends(&[m1, m2], &index, &plan);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 2);
}
#[test]
fn test_debug_line_entry_properties() {
let entry = DebugLineEntry {
file_index: 1,
line: 100,
column: 20,
address: 0x4000,
is_stmt: true,
is_basic_block: true,
prologue_end: false,
epilogue_begin: false,
};
assert!(entry.is_stmt);
assert!(entry.is_basic_block);
assert!(!entry.prologue_end);
assert_eq!(entry.line, 100);
}
#[test]
fn test_debug_subprogram_with_inlined() {
let sp = DebugSubprogram {
name: "outer".into(),
linkage_name: "_Z5outerv".into(),
file: "test.cpp".into(),
line: 45,
is_declaration: false,
is_external: true,
low_pc: 0x2000,
high_pc: 0x2200,
frame_base: None,
inlined_subroutines: vec![
DebugInlinedSubroutine {
abstract_origin: "_Z5innerv".into(),
call_file: "test.cpp".into(),
call_line: 50,
low_pc: 0x2100,
high_pc: 0x2150,
},
DebugInlinedSubroutine {
abstract_origin: "_Z5innerv".into(),
call_file: "test.cpp".into(),
call_line: 55,
low_pc: 0x2180,
high_pc: 0x21d0,
},
],
variables: vec![],
};
assert_eq!(sp.inlined_subroutines.len(), 2);
assert_eq!(sp.line, 45);
}
#[test]
fn test_debug_variable_location_expr() {
let var = DebugVariable {
name: "local_var".into(),
type_ref: "i32".into(),
file: "src.c".into(),
line: 67,
location: Some(vec![0x91, 0x78, 0x06]), };
assert!(var.location.is_some());
assert_eq!(var.location.unwrap().len(), 3);
}
#[test]
fn test_pgo_edge_count_unknown() {
let pgo = X86LTOProfileGuidedOptimization::new();
assert_eq!(pgo.get_edge_count("unknown", "unknown"), 0);
}
#[test]
fn test_pgo_indirect_target_unknown() {
let pgo = X86LTOProfileGuidedOptimization::new();
assert_eq!(pgo.get_likely_indirect_target("nonexistent"), None);
}
#[test]
fn test_pgo_build_summary_empty() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.build_summary();
assert!(!pgo.has_profile);
assert!(pgo.profile_summary.is_none());
}
#[test]
fn test_pgo_multiple_indirect_targets() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_indirect_call_target("cs", "A", 10);
pgo.record_indirect_call_target("cs", "B", 100);
pgo.record_indirect_call_target("cs", "C", 50);
assert_eq!(pgo.get_likely_indirect_target("cs"), Some("B".to_string()));
}
#[test]
fn test_pgo_hotness_threshold() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.hotness_threshold = 0.5;
pgo.record_function_count("f1", 100);
pgo.record_function_count("f2", 30);
pgo.build_summary();
assert!(pgo.is_hot("f1"));
assert!(!pgo.is_hot("f2"));
}
#[test]
fn test_comdat_resolver_largest_selection() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat(
"c",
0,
vec!["small".into()],
ComdatSelectionKind::Largest,
50,
);
cr.register_comdat(
"c",
1,
vec!["large".into()],
ComdatSelectionKind::Largest,
500,
);
cr.resolve();
assert!(cr.is_prevailing("c", 0));
}
#[test]
fn test_comdat_resolver_same_size() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat("s", 0, vec!["f".into()], ComdatSelectionKind::SameSize, 64);
cr.register_comdat("s", 1, vec!["f".into()], ComdatSelectionKind::SameSize, 64);
cr.resolve();
assert_eq!(cr.resolved_count(), 1);
}
#[test]
fn test_comdat_resolver_get_nonexistent() {
let cr = X86LTOComdatResolver::new();
assert_eq!(cr.get_prevailing_module("nonexistent"), None);
}
#[test]
fn test_comdat_resolver_not_prevailing_in_other_module() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat("g", 0, vec!["fn".into()], ComdatSelectionKind::Any, 100);
cr.resolve();
assert!(!cr.is_prevailing("g", 1));
}
#[test]
fn test_alias_resolver_single_alias_no_chain() {
let ar = X86LTOAliasResolver::new();
assert!(!ar.is_in_cycle("none"));
}
#[test]
fn test_alias_resolver_three_node_cycle() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("a", "b", "internal", 0);
ar.register_alias("b", "c", "internal", 0);
ar.register_alias("c", "a", "internal", 0);
ar.detect_cycles();
assert!(!ar.cycles.is_empty());
assert!(ar.is_in_cycle("a"));
assert!(ar.is_in_cycle("b"));
assert!(ar.is_in_cycle("c"));
}
#[test]
fn test_alias_resolver_chain_no_cycle() {
let ar = X86LTOAliasResolver::new();
let target = ar.resolve_alias_chain("independent");
assert_eq!(target, "independent");
}
#[test]
fn test_alias_info_is_internal() {
let info = AliasInfo {
name: "a".into(),
target: "t".into(),
linkage: "internal".into(),
is_internal: true,
module_idx: 0,
};
assert!(info.is_internal);
}
#[test]
fn test_alias_info_external() {
let info = AliasInfo {
name: "a".into(),
target: "t".into(),
linkage: "external".into(),
is_internal: false,
module_idx: 0,
};
assert!(!info.is_internal);
}
#[test]
fn test_summary_builder_empty_module() {
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module("e");
sb.build_for_module(&m, 0);
assert_eq!(sb.builder_stats.functions_summarized, 0);
}
#[test]
fn test_summary_builder_stats_edges() {
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module_with_func("m", "f");
sb.build_for_module(&m, 0);
assert_eq!(sb.builder_stats.edges_discovered, 0);
}
#[test]
fn test_summary_builder_call_graph_builder() {
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module_with_func("m", "f");
sb.build_for_module(&m, 0);
let cg = sb.get_call_graph();
assert!(cg.is_empty()); }
#[test]
fn test_report_contains_all_sections() {
let tm = X86TargetMachine::default();
let lto = X86LTODeep::new(tm, OptimizationLevel::O2);
let r = lto.report();
assert!(r.contains("X86 Deep LTO Report"));
assert!(r.contains("Mode:"));
assert!(r.contains("Optimization Level:"));
assert!(r.contains("Input Modules:"));
assert!(r.contains("Functions:"));
assert!(r.contains("Instructions Before:"));
assert!(r.contains("Instructions After:"));
}
#[test]
fn test_report_full_lto_mode() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = false;
let r = lto.report();
assert!(r.contains("Full LTO"));
}
#[test]
fn test_report_thin_lto_mode() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = true;
let r = lto.report();
assert!(r.contains("ThinLTO"));
}
#[test]
fn test_report_with_stats() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O3);
lto.stats.functions_eliminated = 42;
lto.stats.devirt_sites = 15;
lto.stats.internalized_symbols = 200;
let r = lto.report();
assert!(r.contains("42"));
assert!(r.contains("15"));
assert!(r.contains("200"));
}
#[test]
fn test_report_instruction_reduction() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.stats.instructions_before = 1000;
lto.stats.instructions_after = 800;
let r = lto.report();
assert!(r.contains("20")); }
#[test]
fn test_empty_strings_in_summaries() {
let fs = X86FunctionSummary {
name: String::new(),
guid: 0,
module_index: 0,
params: vec![],
return_type: String::new(),
calls: vec![],
refs: vec![],
instruction_count: 0,
is_readonly: false,
is_readnone: false,
has_side_effects: false,
has_inline_asm: false,
is_declaration: true,
linkage: GlobalLinkage::Internal,
visibility: X86SymbolVisibility::Hidden,
hotness: 0.0,
};
assert!(fs.name.is_empty());
assert_eq!(fs.guid, 0);
}
#[test]
fn test_max_guid_value() {
let guid = X86ThinLTO::compute_guid(&"x".repeat(1000), Some(usize::MAX));
assert!(guid > 0);
}
#[test]
fn test_zero_instruction_function() {
let ms = X86LTOModuleSummary::new();
let f_ref = new_function("empty_fn", Type::void(), &[]);
let fs = ms.build_function_summary(&f_ref, 0);
assert_eq!(fs.instruction_count, 0);
}
#[test]
fn test_very_large_instruction_count() {
let fs = X86FunctionSummary {
name: "big".into(),
guid: 1,
module_index: 0,
params: vec![],
return_type: "void".into(),
calls: vec![],
refs: vec![],
instruction_count: usize::MAX,
is_readonly: false,
is_readnone: false,
has_side_effects: true,
has_inline_asm: false,
is_declaration: false,
linkage: GlobalLinkage::External,
visibility: X86SymbolVisibility::Default,
hotness: 1.0,
};
assert_eq!(fs.instruction_count, usize::MAX);
}
#[test]
fn test_lto_deep_with_max_cache() {
let tm = X86TargetMachine::default();
let lto = X86LTODeep::new(tm, OptimizationLevel::O2);
assert!(lto.max_cache_bytes > 0);
}
#[test]
fn test_lto_deep_all_options_disabled() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.enable_devirt = false;
lto.enable_internalize = false;
lto.enable_cache = false;
assert!(!lto.enable_devirt);
assert!(!lto.enable_internalize);
assert!(!lto.enable_cache);
}
#[test]
fn test_cache_size_never_negative() {
let cache = X86LTOCache::new();
assert!(cache.current_size() < u64::MAX);
}
#[test]
fn test_pass_manager_per_pass_timeout() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
assert_eq!(pm.per_pass_timeout_ms, 0);
pm.per_pass_timeout_ms = 5000;
assert_eq!(pm.per_pass_timeout_ms, 5000);
}
#[test]
fn test_debug_merger_empty_emit() {
let dm = X86LTODebugInfoMerger::new();
let data = dm.emit_debug_info();
assert!(data.len() >= 11);
}
#[test]
fn test_pgo_load_from_module_no_profile() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
let m = make_module("e");
pgo.load_from_module(&m);
assert!(!pgo.has_profile);
}
#[test]
fn test_inferred_visibility_from_subclass() {
let f_ref = new_function("test", Type::void(), &[]);
let vis = X86LTOModuleSummary::value_subclass_to_visibility(&f_ref.borrow());
assert_eq!(vis, X86SymbolVisibility::Default);
}
#[test]
fn test_inferred_linkage_from_subclass() {
let f_ref = new_function("test", Type::void(), &[]);
let linkage = X86LTOModuleSummary::value_linkage_to_global_linkage(&f_ref.borrow());
assert_eq!(linkage, GlobalLinkage::External);
}
#[test]
fn test_hash_function_deterministic_across_calls() {
let f_ref = new_function("hash_test", Type::i32(), &[Type::i64()]);
let f = f_ref.borrow();
let h1 = X86FullLTO::hash_function_body(&f);
let h2 = X86FullLTO::hash_function_body(&f);
assert_eq!(h1, h2);
}
#[test]
fn test_hash_function_empty_body() {
let f_ref = new_function("empty", Type::void(), &[]);
let f = f_ref.borrow();
let hash = X86FullLTO::hash_function_body(&f);
assert!(hash > 0 || hash == 0);
}
#[test]
fn test_opt_pipeline_hash_empty() {
let f_ref = new_function("f", Type::void(), &[]);
let f = f_ref.borrow();
let hash = X86LTOOptimizationPipeline::hash_function(&f);
assert!(hash > 0 || hash == 0);
}
#[test]
fn test_opt_pipeline_hash_deterministic() {
let f_ref = new_function("f", Type::i32(), &[Type::i32()]);
let f = f_ref.borrow();
let h1 = X86LTOOptimizationPipeline::hash_function(&f);
let h2 = X86LTOOptimizationPipeline::hash_function(&f);
assert_eq!(h1, h2);
}
#[test]
fn test_lto_stats_send_sync() {
let stats = X86LTOStats::default();
let _clone = stats.clone();
let _format = format!("{:?}", stats);
}
#[test]
fn test_pipeline_stats_clone_and_debug() {
let stats = PipelineStats::default();
let _c = stats.clone();
let _d = format!("{:?}", stats);
}
#[test]
fn test_codegen_stats_clone_and_debug() {
let stats = CodegenStats::default();
let _c = stats.clone();
let _d = format!("{:?}", stats);
}
#[test]
fn test_full_lto_stats_clone_and_debug() {
let stats = FullLTOStats::default();
let _c = stats.clone();
let _d = format!("{:?}", stats);
}
#[test]
fn test_devirt_stats_clone_and_debug() {
let stats = DevirtualizationStats::default();
let _c = stats.clone();
let _d = format!("{:?}", stats);
}
#[test]
fn test_diag_level_clone_and_eq() {
let level = X86LTODiagLevel::Error;
assert_eq!(level, level.clone());
assert_ne!(level, X86LTODiagLevel::Info);
}
#[test]
fn test_diagnostic_clone_and_debug() {
let d = X86LTODiagnostic {
level: X86LTODiagLevel::Warning,
message: "msg".into(),
module_name: Some("mod".into()),
function_name: Some("fn".into()),
location: Some("loc".into()),
};
let c = d.clone();
assert_eq!(c.level, d.level);
assert_eq!(c.message, d.message);
let debug_str = format!("{:?}", d);
assert!(!debug_str.is_empty());
}
#[test]
fn test_lto_error_is_std_error() {
fn takes_error(_e: &dyn std::error::Error) {}
let err = X86LTOError::NoModules;
takes_error(&err);
}
#[test]
fn test_lto_error_display_all_variants() {
let variants = [
X86LTOError::NoModules,
X86LTOError::MergeFailed("m".into()),
X86LTOError::InternalizationFailed("i".into()),
X86LTOError::OptimizationFailed("o".into()),
X86LTOError::CodegenFailed("c".into()),
X86LTOError::CacheError("ca".into()),
X86LTOError::IoError(io::Error::new(io::ErrorKind::Other, "io")),
X86LTOError::SummaryError("s".into()),
X86LTOError::ImportError("im".into()),
X86LTOError::LinkConflict("lc".into()),
];
for v in &variants {
let s = format!("{}", v);
assert!(!s.is_empty(), "Empty display for {:?}", v);
}
}
#[test]
fn test_guid_module_independence() {
let g0 = X86ThinLTO::compute_guid("f", Some(0));
let g1 = X86ThinLTO::compute_guid("f", Some(1));
assert_ne!(g0, g1);
}
#[test]
fn test_module_hash_independence() {
let m1 = make_module_with_func("m1", "f");
let m2 = make_module_with_func("m2", "f");
let h1 = X86ThinLTO::compute_module_hash(&m1);
let h2 = X86ThinLTO::compute_module_hash(&m2);
assert_eq!(h1, h2);
}
#[test]
fn test_cache_massive_stress() {
let mut cache = X86LTOCache::new();
cache.set_max_size(10_000_000);
for i in 0..500 {
let key = X86LTOCacheKey {
module_hash: i as u64,
opt_level: if i % 2 == 0 { "O2".into() } else { "O3".into() },
is_thin_lto: i % 3 != 0,
target_features: "x86_64-v3".into(),
};
let data_size = if i % 10 == 0 { 10000 } else { 100 };
cache.store(&key, vec![(i % 256) as u8; data_size]);
}
assert!(cache.current_size() <= 10_000_000);
assert!(cache.entry_count() > 0);
}
#[test]
fn test_devirt_massive_type_hierarchy() {
let mut devirt = X86LTODevirtualization::new();
let depth = 20;
for i in 0..depth {
let type_name = format!("type_{}", i);
let parent = if i > 0 {
vec![format!("type_{}", i - 1)]
} else {
vec![]
};
let child = if i < depth - 1 {
vec![format!("type_{}", i + 1)]
} else {
vec![]
};
devirt.register_type(
&type_name,
parent,
child,
Some(&format!("_ZTV{}", i)),
i == depth - 1,
);
devirt.register_vtable(
&format!("_ZTV{}", i),
&type_name,
vec![X86VirtualFunctionInfo {
vtable_offset: 8,
target: format!("fn_{}", i),
is_pure_virtual: false,
is_rtti: false,
}],
16,
);
}
assert_eq!(devirt.type_hierarchy.len(), depth);
assert_eq!(devirt.vtables.len(), depth);
}
#[test]
fn test_internalization_massive_symbol_list() {
let mut intern = X86LTOInternalization::new();
for i in 0..1000 {
intern.always_exported.push(format!("sym_{}", i));
}
assert!(intern.always_exported.contains(&"main".to_string()));
assert!(intern.always_exported.contains(&"_start".to_string()));
}
#[test]
fn test_module_summary_large_guid_map() {
let mut ms = X86LTOModuleSummary::new();
for i in 0..200 {
ms.name_to_guid.insert(format!("fn_{}", i), i as u64);
ms.guid_map.insert(i as u64, format!("fn_{}", i));
}
assert_eq!(ms.guid_map.len(), 200);
assert_eq!(ms.name_to_guid.len(), 200);
}
#[test]
fn test_pass_manager_many_repeated_runs() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
let mut m = make_module("e");
for _ in 0..20 {
pm.run(&mut m);
}
assert!(pm.total_passes_executed > 0);
}
#[test]
fn test_pass_manager_globaldce_removes_unreachable() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let mut m = make_module_with_func("m", "main");
m.functions.push(new_function("unused", Type::void(), &[]));
let before = m.functions.len();
pm.execute_pass("globaldce", &mut m);
let after = m.functions.len();
assert!(after <= before);
}
#[test]
fn test_pass_manager_execute_nonexistent_pass() {
let pm = X86LTOPassManager::new(OptimizationLevel::O2);
let mut m = make_module("e");
let result = pm.execute_pass("nonexistent_pass", &mut m);
assert!(!result.changed);
assert_eq!(result.functions_modified, 0);
}
#[test]
fn test_pass_manager_all_registered_passes_executed() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let mut m = make_module("e");
pm.run(&mut m);
for pass_name in &pm.get_pass_order() {
let stats = pm.get_pass_stats(pass_name);
assert!(stats.is_some(), "Pass '{}' has no stats", pass_name);
}
}
#[test]
fn test_thin_lto_import_self_module() {
let config = LTOConfig::default();
let thin = X86ThinLTO::new(config);
let mut dest = make_module_with_func("dest", "local_fn");
let source = make_module_with_func("source", "local_fn");
let result = thin.import_function(&mut dest, &source, "local_fn");
assert!(result.is_ok());
}
#[test]
fn test_thin_lto_import_missing_source() {
let config = LTOConfig::default();
let thin = X86ThinLTO::new(config);
let mut dest = make_module("dest");
let source = make_module("source");
let result = thin.import_function(&mut dest, &source, "ghost");
assert!(result.is_err());
}
#[test]
fn test_devirt_result_devirtualized() {
let r = DevirtResult::Devirtualized("target".into());
match r {
DevirtResult::Devirtualized(t) => assert_eq!(t, "target"),
_ => panic!("Expected Devirtualized"),
}
}
#[test]
fn test_devirt_result_speculative() {
let r = DevirtResult::Speculative("target".into(), "guard_type".into());
match r {
DevirtResult::Speculative(t, g) => {
assert_eq!(t, "target");
assert_eq!(g, "guard_type");
}
_ => panic!("Expected Speculative"),
}
}
#[test]
fn test_devirt_result_unresolved() {
let r = DevirtResult::Unresolved;
match r {
DevirtResult::Unresolved => {}
_ => panic!("Expected Unresolved"),
}
}
#[test]
fn test_summary_builder_stats_default() {
let s = SummaryBuilderStats::default();
assert_eq!(s.functions_summarized, 0);
assert_eq!(s.edges_discovered, 0);
assert_eq!(s.globals_referenced, 0);
assert_eq!(s.indirect_calls, 0);
assert_eq!(s.with_inline_asm, 0);
}
#[test]
fn test_alias_stats_default() {
let s = AliasResolutionStats::default();
assert_eq!(s.total_aliases, 0);
assert_eq!(s.resolved_aliases, 0);
assert_eq!(s.resolved_ifuncs, 0);
assert_eq!(s.cycles_detected, 0);
assert_eq!(s.references_replaced, 0);
}
#[test]
fn test_debug_merge_stats_default() {
let s = DebugMergeStats::default();
assert_eq!(s.cus_merged, 0);
assert_eq!(s.subprograms_deduped, 0);
assert_eq!(s.types_deduped, 0);
assert_eq!(s.line_entries_merged, 0);
assert_eq!(s.strings_deduped, 0);
assert_eq!(s.size_before, 0);
assert_eq!(s.size_after, 0);
}
#[test]
fn test_module_summary_data_create() {
let d = ModuleSummaryData {
module_name: "mod".into(),
module_hash: 42,
global_summaries: vec![],
type_ids: vec!["!type_1".into()],
vtable_defs: vec![],
cfi_slots: vec![],
};
assert_eq!(d.type_ids.len(), 1);
}
#[test]
fn test_ifunc_info_creation() {
let info = IFuncInfo {
name: "resolve_foo".into(),
resolver: "foo_impl".into(),
module_idx: 1,
};
assert_eq!(info.module_idx, 1);
}
#[test]
fn test_comdat_group_info_multi_module() {
let info = ComdatGroupInfo {
name: "grp".into(),
defining_modules: vec![0, 1, 2],
members: vec!["a".into(), "b".into()],
selection_kind: ComdatSelectionKind::Any,
total_size: 256,
};
assert_eq!(info.defining_modules.len(), 3);
assert_eq!(info.members.len(), 2);
}
#[test]
fn test_type_metadata_node_duplicate() {
let node = TypeMetadataNode {
identifier: "type_x".into(),
source_modules: vec![0, 1],
address_points: vec![16],
is_duplicate: true,
};
assert!(node.is_duplicate);
assert_eq!(node.source_modules.len(), 2);
}
#[test]
fn test_merged_attribute_group() {
let ag = MergedAttributeGroup {
group_id: 5,
attributes: vec!["nounwind".into(), "readonly".into()],
sources: vec![0],
};
assert_eq!(ag.attributes.len(), 2);
assert_eq!(ag.group_id, 5);
}
#[test]
fn test_metadata_conflict_creation() {
let mc = MetadataConflict {
metadata_kind: "module_flags".into(),
name: "PIC Level".into(),
modules: vec![0, 1],
description: "conflict: 0 vs 2".into(),
};
assert_eq!(mc.name, "PIC Level");
}
#[test]
fn test_comdat_conflict_creation() {
let cc = ComdatConflict {
comdat_name: "grp".into(),
modules: vec![0, 1, 2, 3],
reason: "ExactMatch violation".into(),
};
assert_eq!(cc.modules.len(), 4);
}
#[test]
fn test_lto_deep_flow_empty_modules_error() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
let result = lto.run();
assert!(result.is_err());
}
#[test]
fn test_lto_deep_flow_single_module_full_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = false;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_flow_single_module_thin_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = true;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_with_devirt_disabled() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = false;
lto.enable_devirt = false;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_with_internalize_disabled() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = false;
lto.enable_internalize = false;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_with_cache_disabled() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.enable_cache = false;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_o0_full_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O0);
lto.lto.config.use_thin_lto = false;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_o3_full_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O3);
lto.lto.config.use_thin_lto = false;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_os_thin_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::Os);
lto.lto.config.use_thin_lto = true;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_oz_thin_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::Oz);
lto.lto.config.use_thin_lto = true;
lto.add_module(make_module_with_func("m", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_two_modules_full_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = false;
lto.add_module(make_module_with_func("m1", "f1"));
lto.add_module(make_module_with_func("m2", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_two_modules_thin_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = true;
lto.add_module(make_module_with_func("m1", "f1"));
lto.add_module(make_module_with_func("m2", "main"));
let _ = lto.run();
}
#[test]
fn test_lto_deep_ten_modules_full_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = false;
for i in 0..10 {
lto.add_module(make_module_with_func(
&format!("m{}", i),
if i == 5 { "main" } else { &format!("f{}", i) },
));
}
let _ = lto.run();
}
#[test]
fn test_lto_deep_ten_modules_thin_lto() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = true;
for i in 0..10 {
lto.add_module(make_module_with_func(
&format!("m{}", i),
if i == 0 { "main" } else { &format!("f{}", i) },
));
}
let _ = lto.run();
}
#[test]
fn test_x86_lto_target_features_in_cache_key() {
let k1 = X86LTOCacheKey {
module_hash: 1,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64".into(),
};
let k2 = X86LTOCacheKey {
module_hash: 1,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64-v2".into(),
};
assert_ne!(k1, k2);
}
#[test]
fn test_x86_lto_full_lto_codegen_produces_elf() {
let tm = X86TargetMachine::default();
let mut cg = X86LTOCodegenPipeline::new(tm);
let mut m = make_module_with_func("m", "main");
let objs = cg
.codegen_module(&mut m, &X86TargetMachine::default())
.unwrap();
if !objs.is_empty() {
assert_eq!(&objs[0][0..4], &[0x7f, b'E', b'L', b'F']);
}
}
#[test]
fn test_x86_lto_codegen_multiple_modules_independent() {
let tm = X86TargetMachine::default();
let mut cg = X86LTOCodegenPipeline::new(tm);
let m1 = make_module_with_func("m1", "f1");
let m2 = make_module_with_func("m2", "f2");
let objs = cg.codegen_modules_parallel(&[m1, m2], &tm).unwrap();
assert!(objs.len() >= 2);
}
#[test]
fn test_x86_lto_codegen_merge_object_files() {
let tm = X86TargetMachine::default();
let mut cg = X86LTOCodegenPipeline::new(tm);
assert!(cg.merge_object_files);
cg.merge_object_files = false;
assert!(!cg.merge_object_files);
}
#[test]
fn test_thin_lto_cache_hit_scenario() {
let mut cache = X86LTOCache::new();
let key = X86LTOCacheKey {
module_hash: 100,
opt_level: "O2".into(),
is_thin_lto: true,
target_features: "x86_64-v3".into(),
};
assert!(cache.lookup(&key).is_none());
cache.store(&key, vec![1, 2, 3]);
assert!(cache.lookup(&key).is_some());
}
#[test]
fn test_thin_lto_cache_module_serialization_idempotent() {
let cache = X86LTOCache::new();
let m = make_module_with_func("m", "f");
let d1 = cache.serialize_module(&m).unwrap();
let d2 = cache.serialize_module(&m).unwrap();
assert_eq!(d1, d2);
}
#[test]
fn test_pass_prerequisite_ordering() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let internalize_pass = pm.passes.iter().find(|p| p.name == "internalize").unwrap();
assert!(internalize_pass
.prerequisites
.contains(&"globaldce".to_string()));
}
#[test]
fn test_pass_invalidation_sets() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let dce_pass = pm.passes.iter().find(|p| p.name == "globaldce").unwrap();
assert!(dce_pass
.invalidated_passes
.contains(&"globaldce2".to_string()));
}
#[test]
fn test_pass_requires_call_graph_flag() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
let ipsccp = pm.passes.iter().find(|p| p.name == "ipsccp").unwrap();
assert!(ipsccp.requires_call_graph);
let constmerge = pm.passes.iter().find(|p| p.name == "constmerge").unwrap();
assert!(!constmerge.requires_call_graph);
}
#[test]
fn test_pgo_guides_devirt_decisions() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_indirect_call_target("vcall_main", "Base_foo", 990);
pgo.record_indirect_call_target("vcall_main", "Derived_foo", 10);
let likely = pgo.get_likely_indirect_target("vcall_main");
assert_eq!(likely, Some("Base_foo".to_string()));
}
#[test]
fn test_pgo_edge_counts_guide_inlining() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_edge_count("hotCaller", "hotCallee", 10000);
pgo.record_edge_count("coldCaller", "coldCallee", 1);
assert_eq!(pgo.get_edge_count("hotCaller", "hotCallee"), 10000);
assert_eq!(pgo.get_edge_count("coldCaller", "coldCallee"), 1);
}
#[test]
fn test_comdat_selection_kind_exhaustive() {
let kinds = [
ComdatSelectionKind::Any,
ComdatSelectionKind::ExactMatch,
ComdatSelectionKind::Largest,
ComdatSelectionKind::NoDeduplicate,
ComdatSelectionKind::SameSize,
];
for i in 0..kinds.len() {
for j in (i + 1)..kinds.len() {
assert_ne!(kinds[i], kinds[j]);
}
}
}
#[test]
fn test_comdat_selection_kind_clone() {
let k = ComdatSelectionKind::Largest;
assert_eq!(k, k.clone());
}
#[test]
fn test_debug_cu_all_fields() {
let cu = DebugCompilationUnit {
source_file: "src/main.rs".into(),
comp_dir: "/home/user/project".into(),
producer: "rustc 1.80.0".into(),
dwarf_version: 5,
address_size: 8,
line_entries: vec![],
subprograms: vec![],
global_variables: vec![],
};
assert_eq!(cu.dwarf_version, 5);
assert_eq!(cu.address_size, 8);
}
#[test]
fn test_debug_global_variable_all_fields() {
let gv = DebugGlobalVariable {
name: "VERSION".into(),
linkage_name: "_ZL7VERSION".into(),
type_ref: "const char*".into(),
file: "config.h".into(),
line: 10,
is_declaration: false,
location: Some(vec![0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
};
assert!(!gv.is_declaration);
assert_eq!(gv.name, "VERSION");
}
#[test]
fn test_cache_independent_instances() {
let mut c1 = X86LTOCache::new();
let mut c2 = X86LTOCache::new();
let key = X86LTOCacheKey {
module_hash: 42,
opt_level: "O2".into(),
is_thin_lto: false,
target_features: "x86_64".into(),
};
c1.store(&key, vec![1, 2]);
assert!(c2.lookup(&key).is_none());
}
#[test]
fn test_module_summary_independent_instances() {
let mut ms1 = X86LTOModuleSummary::new();
let mut ms2 = X86LTOModuleSummary::new();
let m = make_module_with_func("m", "f");
ms1.build_summary_for_module(&m, 0);
assert!(ms2.guid_map.is_empty());
}
#[test]
fn test_full_lto_merge_many_small_modules() {
let mut fl = X86FullLTO::new(OptimizationLevel::O2);
let mut modules = Vec::new();
for i in 0..50 {
modules.push(make_module_with_func(
&format!("m{}", i),
&format!("f{}", i),
));
}
let result = fl.merge_modules(&modules);
assert!(result.is_ok());
assert_eq!(fl.stats.modules_merged, 50);
}
#[test]
fn test_thin_lto_large_combined_index() {
let config = LTOConfig::default();
let mut thin = X86ThinLTO::new(config);
let mut modules = Vec::new();
for i in 0..30 {
modules.push(make_module_with_func(
&format!("m{}", i),
&format!("f{}", i),
));
}
let index = thin.build_combined_index(&modules);
assert_eq!(index.num_modules(), 30);
}
#[test]
fn test_special_symbol_names() {
let intern = X86LTOInternalization::new();
assert!(intern.can_internalize("foo.bar", "internal"));
assert!(!intern.can_internalize("llvm.used", "external"));
assert!(!intern.can_internalize("llvm.compiler.used", "external"));
}
#[test]
fn test_guid_with_special_names() {
let g1 = X86ThinLTO::compute_guid("foo::bar", Some(0));
let g2 = X86ThinLTO::compute_guid("foo::bar", Some(1));
assert_ne!(g1, g2);
}
#[test]
fn test_module_hash_with_special_names() {
let m1 = make_module_with_func("mod", "foo::bar::baz");
let m2 = make_module_with_func("mod", "foo::bar::qux");
let h1 = X86ThinLTO::compute_module_hash(&m1);
let h2 = X86ThinLTO::compute_module_hash(&m2);
assert_ne!(h1, h2);
}
#[test]
fn test_x86_function_summary_readonly_readnone_consistency() {
let fs = X86FunctionSummary {
name: "f".into(),
guid: 1,
module_index: 0,
params: vec![],
return_type: "void".into(),
calls: vec![],
refs: vec![],
instruction_count: 10,
is_readonly: false,
is_readnone: false,
has_side_effects: true,
has_inline_asm: false,
is_declaration: false,
linkage: GlobalLinkage::External,
visibility: X86SymbolVisibility::Default,
hotness: 0.0,
};
assert!(!fs.is_readnone);
assert!(!fs.is_readonly);
}
#[test]
fn test_x86_function_summary_readnone_implies_readonly_in_model() {
let fs = X86FunctionSummary {
name: "pure".into(),
guid: 2,
module_index: 0,
params: vec!["i32".into()],
return_type: "i32".into(),
calls: vec![],
refs: vec![],
instruction_count: 5,
is_readonly: true,
is_readnone: true,
has_side_effects: false,
has_inline_asm: false,
is_declaration: false,
linkage: GlobalLinkage::Internal,
visibility: X86SymbolVisibility::Hidden,
hotness: 0.5,
};
assert!(fs.is_readnone);
}
#[test]
fn test_full_integration_all_subsystems_created() {
let tm = X86TargetMachine::default();
let lto = X86LTODeep::new(tm, OptimizationLevel::O2);
assert!(lto.full_lto.enable_global_dce);
assert!(lto.thin_lto.combined_index.is_none());
assert!(lto.module_summary.global_summaries.is_empty());
assert!(lto.devirtualization.vtables.is_empty());
assert!(lto.internalization.exported_symbols.is_empty());
assert!(lto.opt_pipeline.enabled);
assert!(lto.codegen_pipeline.merge_object_files);
assert!(lto.lto_cache.enabled);
}
#[test]
fn test_full_integration_pass_manager_pgo_comdat_metadata() {
let pm = X86LTOPassManager::new(OptimizationLevel::O2);
let dm = X86LTODebugInfoMerger::new();
let pgo = X86LTOProfileGuidedOptimization::new();
let cr = X86LTOComdatResolver::new();
let mm = X86LTOMetadataMerger::new();
let ar = X86LTOAliasResolver::new();
let sb = X86LTOFunctionSummaryBuilder::new();
assert!(!pm.initialized);
assert!(dm.source_cus.is_empty());
assert!(!pgo.has_profile);
assert!(cr.comdat_groups.is_empty());
assert!(mm.named_metadata.is_empty());
assert!(ar.aliases.is_empty());
assert!(sb.summaries.is_empty());
}
#[test]
fn test_example_full_lto_workflow() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = false;
lto.add_module(make_module_with_func("a.c", "helper"));
lto.add_module(make_module_with_func("b.c", "main"));
let result = lto.run();
let _ = result;
}
#[test]
fn test_example_thin_lto_workflow() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = true;
lto.add_module(make_module_with_func("a.c", "helper"));
lto.add_module(make_module_with_func("b.c", "main"));
let result = lto.run();
let _ = result;
}
#[test]
fn test_all_public_types_can_be_constructed() {
let _ = X86LTODeep::new(X86TargetMachine::default(), OptimizationLevel::O2);
let _ = X86FullLTO::new(OptimizationLevel::O2);
let _ = X86ThinLTO::new(LTOConfig::default());
let _ = X86LTOModuleSummary::new();
let _ = X86LTODevirtualization::new();
let _ = X86LTOInternalization::new();
let _ = X86LTOOptimizationPipeline::new(OptimizationLevel::O2);
let _ = X86LTOCodegenPipeline::new(X86TargetMachine::default());
let _ = X86LTOCache::new();
let _ = X86LTOPassManager::new(OptimizationLevel::O2);
let _ = X86LTODebugInfoMerger::new();
let _ = X86LTOProfileGuidedOptimization::new();
let _ = X86LTOComdatResolver::new();
let _ = X86LTOMetadataMerger::new();
let _ = X86LTOAliasResolver::new();
let _ = X86LTOFunctionSummaryBuilder::new();
}
#[test]
fn test_all_stats_types_default() {
let _ = X86LTOStats::default();
let _ = FullLTOStats::default();
let _ = PipelineStats::default();
let _ = CodegenStats::default();
let _ = DevirtualizationStats::default();
let _ = DebugMergeStats::default();
let _ = SummaryBuilderStats::default();
let _ = AliasResolutionStats::default();
}
#[test]
fn test_lto_result_all_fields() {
let r = X86LTOResult {
mode: LTOMode::Full,
optimized_modules: vec![make_module("m")],
object_files: vec![vec![0x90, 0xc3]],
stats: X86LTOStats::default(),
};
assert!(matches!(r.mode, LTOMode::Full));
assert_eq!(r.optimized_modules.len(), 1);
assert_eq!(r.object_files.len(), 1);
}
#[test]
fn test_emit_subprogram_die_basic() {
let dm = X86LTODebugInfoMerger::new();
let mut data = Vec::new();
let sp = DebugSubprogram {
name: "test_fn".into(),
linkage_name: "_Z7test_fnv".into(),
file: "test.c".into(),
line: 100,
is_declaration: false,
is_external: true,
low_pc: 0x4000,
high_pc: 0x4100,
frame_base: None,
inlined_subroutines: vec![],
variables: vec![],
};
dm.emit_subprogram_die(&mut data, &sp);
assert!(!data.is_empty());
let name_bytes = "test_fn".as_bytes();
assert!(data.windows(name_bytes.len()).any(|w| w == name_bytes));
}
#[test]
fn test_emit_subprogram_die_with_inline_info() {
let dm = X86LTODebugInfoMerger::new();
let mut data = Vec::new();
let sp = DebugSubprogram {
name: "outer".into(),
linkage_name: "_Z5outerv".into(),
file: "src.c".into(),
line: 42,
is_declaration: false,
is_external: false,
low_pc: 0x5000,
high_pc: 0x5200,
frame_base: Some("DW_OP_reg6".into()),
inlined_subroutines: vec![DebugInlinedSubroutine {
abstract_origin: "_Z5innerv".into(),
call_file: "src.c".into(),
call_line: 45,
low_pc: 0x5080,
high_pc: 0x5100,
}],
variables: vec![],
};
dm.emit_subprogram_die(&mut data, &sp);
assert!(!data.is_empty());
}
#[test]
fn test_pgo_summary_single_function() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_function_count("only", 1);
pgo.build_summary();
let s = pgo.profile_summary.as_ref().unwrap();
assert_eq!(s.num_functions, 1);
assert_eq!(s.max_count, 1);
assert_eq!(s.total_count, 1);
}
#[test]
fn test_pgo_summary_all_same_count() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
for i in 0..10 {
pgo.record_function_count(&format!("f{}", i), 100);
}
pgo.build_summary();
let s = pgo.profile_summary.as_ref().unwrap();
assert_eq!(s.max_count, 100);
assert_eq!(s.total_count, 1000);
}
#[test]
fn test_pgo_hotness_with_extreme_values() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_function_count("super_hot", 1_000_000);
pgo.record_function_count("very_cold", 1);
pgo.build_summary();
assert!((pgo.get_function_hotness("super_hot") - 1.0).abs() < 0.001);
assert!(pgo.get_function_hotness("very_cold") < 0.01);
}
#[test]
fn test_alias_self_referencing_cycle() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("loop", "loop", "internal", 0);
ar.detect_cycles();
assert!(!ar.cycles.is_empty());
assert!(ar.is_in_cycle("loop"));
}
#[test]
fn test_alias_two_node_cycle() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("a", "b", "internal", 0);
ar.register_alias("b", "a", "internal", 0);
ar.detect_cycles();
assert_eq!(ar.stats.cycles_detected, 1);
}
#[test]
fn test_alias_chain_no_cycle_with_unknown() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("a", "b", "internal", 0);
ar.detect_cycles();
assert_eq!(ar.stats.cycles_detected, 0);
}
#[test]
fn test_alias_chain_to_external() {
let mut ar = X86LTOAliasResolver::new();
ar.register_alias("wrapper", "real_impl", "internal", 0);
let target = ar.resolve_alias_chain("wrapper");
assert_eq!(target, "real_impl");
}
#[test]
fn test_comdat_resolver_all_selection_kinds_no_conflict() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat("c1", 0, vec!["f1".into()], ComdatSelectionKind::Any, 100);
cr.register_comdat(
"c2",
0,
vec!["f2".into()],
ComdatSelectionKind::Largest,
200,
);
cr.register_comdat(
"c3",
0,
vec!["f3".into()],
ComdatSelectionKind::SameSize,
64,
);
cr.register_comdat(
"c4",
0,
vec!["f4".into()],
ComdatSelectionKind::NoDeduplicate,
32,
);
cr.resolve();
assert!(cr.resolved_count() >= 3);
assert_eq!(cr.get_conflicts().len(), 0);
}
#[test]
fn test_comdat_exact_match_with_conflict() {
let mut cr = X86LTOComdatResolver::new();
cr.register_comdat(
"ex",
0,
vec!["f_a".into()],
ComdatSelectionKind::ExactMatch,
50,
);
cr.register_comdat(
"ex",
1,
vec!["f_b".into()],
ComdatSelectionKind::ExactMatch,
50,
);
cr.resolve();
assert!(!cr.get_conflicts().is_empty());
}
#[test]
fn test_metadata_merger_llvm_ident_append() {
let mut mm = X86LTOMetadataMerger::new();
mm.merge_named_metadata(0, "llvm.ident", vec![1]);
mm.merge_named_metadata(1, "llvm.ident", vec![2]);
let entries = mm.named_metadata.get("llvm.ident").unwrap();
assert_eq!(entries.len(), 2);
}
#[test]
fn test_metadata_merger_custom_metadata_first_wins() {
let mut mm = X86LTOMetadataMerger::new();
mm.merge_named_metadata(0, "custom", vec![10, 20]);
mm.merge_named_metadata(1, "custom", vec![30, 40]);
let entries = mm.named_metadata.get("custom").unwrap();
assert_eq!(entries, &vec![10, 20]);
}
#[test]
fn test_metadata_merger_type_dedup_across_modules() {
let mut mm = X86LTOMetadataMerger::new();
mm.merge_type_metadata(0, "T", 16);
mm.merge_type_metadata(1, "T", 16);
mm.merge_type_metadata(2, "T", 16);
assert!(mm.type_metadata["T"].is_duplicate);
assert_eq!(mm.type_metadata["T"].source_modules.len(), 3);
}
#[test]
fn test_summary_builder_import_eligible_empty() {
let sb = X86LTOFunctionSummaryBuilder::new();
let eligible = sb.get_import_eligible(100);
assert!(eligible.is_empty());
}
#[test]
fn test_summary_builder_cleared_rebuilds() {
let mut sb = X86LTOFunctionSummaryBuilder::new();
let m = make_module_with_func("m", "f");
sb.build_for_module(&m, 0);
sb.clear();
sb.build_for_module(&m, 0);
assert_eq!(sb.summaries.len(), 1);
}
#[test]
fn test_x86_optimizer_present_in_pipeline() {
let mut pipeline = X86LTOOptimizationPipeline::new(OptimizationLevel::O2);
assert!(pipeline.optimizer.is_none());
let opt = X86Optimizer::default();
pipeline.set_optimizer(opt);
assert!(pipeline.optimizer.is_some());
}
#[test]
fn test_x86_target_machine_in_codegen() {
let tm = X86TargetMachine::default();
let cg = X86LTOCodegenPipeline::new(tm.clone());
assert_eq!(
cg.num_threads,
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
);
}
#[test]
fn test_no_panic_on_empty_cache_operations() {
let mut cache = X86LTOCache::new();
let _ = cache.lookup(&X86LTOCacheKey {
module_hash: 0,
opt_level: "O0".into(),
is_thin_lto: false,
target_features: "".into(),
});
let _ = cache.hit_ratio();
let _ = cache.current_size();
let _ = cache.entry_count();
cache.prune(0);
cache.clear();
}
#[test]
fn test_no_panic_on_empty_devirt_operations() {
let mut devirt = X86LTODevirtualization::new();
let mut m = make_module("e");
let _ = devirt.devirtualize_module(&mut m);
let _ = devirt.find_vtable_candidates("nothing");
}
#[test]
fn test_no_panic_on_empty_internalize_operations() {
let mut intern = X86LTOInternalization::new();
let _ = intern.can_internalize("any", "internal");
let _ = intern.resolution_based_internalize(&[]);
}
#[test]
fn test_no_panic_on_empty_pass_manager() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
let mut m = make_module("e");
let _ = pm.run(&mut m); let _ = pm.get_pass_order();
let _ = pm.total_pass_time_ms();
}
#[test]
fn test_all_optimization_levels_constructible() {
for level in &[
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
] {
let fl = X86FullLTO::new(*level);
let _ = fl;
let pl = X86LTOOptimizationPipeline::new(*level);
let _ = pl;
let pm = X86LTOPassManager::new(*level);
let _ = pm;
}
}
#[test]
fn test_all_lto_modes_tested() {
assert_ne!(LTOMode::Full, LTOMode::Thin);
assert_eq!(LTOMode::Full, LTOMode::Full);
assert_eq!(LTOMode::Thin, LTOMode::Thin);
}
#[test]
fn test_all_cfi_check_types_tested() {
let types = [
CfiCheckType::TypeTest,
CfiCheckType::CheckedLoad,
CfiCheckType::AddressTaken,
CfiCheckType::ICall,
];
for i in 0..types.len() {
for j in (i + 1)..types.len() {
assert_ne!(types[i], types[j]);
}
}
}
#[test]
fn test_all_symbol_visibilities_tested() {
let vis = [
X86SymbolVisibility::Default,
X86SymbolVisibility::Hidden,
X86SymbolVisibility::Protected,
X86SymbolVisibility::Internal,
];
for i in 0..vis.len() {
for j in (i + 1)..vis.len() {
assert_ne!(vis[i], vis[j]);
}
}
}
#[test]
fn test_all_diag_levels_tested() {
let levels = [
X86LTODiagLevel::Info,
X86LTODiagLevel::Warning,
X86LTODiagLevel::Error,
X86LTODiagLevel::Remark,
];
for i in 0..levels.len() {
for j in (i + 1)..levels.len() {
assert_ne!(levels[i], levels[j]);
}
}
}
#[test]
fn test_all_global_linkage_variants() {
let linkages = [
GlobalLinkage::External,
GlobalLinkage::AvailableExternally,
GlobalLinkage::LinkOnceAny,
GlobalLinkage::LinkOnceODR,
GlobalLinkage::WeakAny,
GlobalLinkage::WeakODR,
GlobalLinkage::Appending,
GlobalLinkage::Internal,
GlobalLinkage::Private,
GlobalLinkage::ExternalWeak,
GlobalLinkage::Common,
];
for i in 0..linkages.len() {
for j in (i + 1)..linkages.len() {
assert_ne!(linkages[i], linkages[j]);
}
}
}
#[test]
fn test_value_linkage_covers_function_and_global() {
use llvm_native_core::constants::new_global;
let f_ref = new_function("f", Type::void(), &[]);
let g_ref = new_global(
Type::i32(),
true,
llvm_native_core::function::Linkage::External,
None,
"g",
);
let fl = X86LTOModuleSummary::value_linkage_to_global_linkage(&f_ref.borrow());
let gl = X86LTOModuleSummary::value_linkage_to_global_linkage(&g_ref.borrow());
assert_eq!(fl, GlobalLinkage::External);
assert_eq!(gl, GlobalLinkage::External);
}
#[test]
fn test_module_summary_collects_function_and_global_names() {
use llvm_native_core::constants::new_global;
let mut ms = X86LTOModuleSummary::new();
let mut m = make_module_with_func("m", "func1");
m.globals.push(new_global(
Type::i32(),
true,
llvm_native_core::function::Linkage::External,
None,
"global1",
));
m.globals.push(new_global(
Type::i64(),
false,
llvm_native_core::function::Linkage::Internal,
None,
"global2",
));
let s = ms.build_summary_for_module(&m, 0);
assert!(s.function_names.contains(&"func1".to_string()));
assert!(s.global_names.contains(&"global1".to_string()));
assert!(s.global_names.contains(&"global2".to_string()));
}
#[test]
fn test_combined_index_globals_persist_after_build() {
let mut builder = ThinLTOIndexBuilder::new();
builder.add_module(ModuleSummary {
module_name: "m".into(),
module_hash: "abc".into(),
function_names: vec!["f".into()],
global_names: vec![],
});
builder.add_global_summary(GlobalValueSummary {
name: "f".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 12345,
callees: vec![],
is_import_eligible: true,
instruction_count: 10,
entry_count: 0,
});
builder.seal();
let index = builder.build();
assert_eq!(index.num_modules(), 1);
assert_eq!(index.num_globals(), 1);
assert!(index.get_global("f").is_some());
}
#[test]
fn test_combined_index_get_callees() {
let index = CombinedIndex::new();
let callees = index.get_callees("any");
assert!(callees.is_none());
}
#[test]
fn test_imported_function_struct() {
let imp = llvm_native_core::lto_thin::ImportedFunction {
name: "imported_fn".into(),
from_module: 3,
instruction_count: 25,
reason: "cross_module_inline".into(),
};
assert_eq!(imp.name, "imported_fn");
assert_eq!(imp.from_module, 3);
assert_eq!(imp.instruction_count, 25);
}
#[test]
fn test_global_value_summary_all_fields() {
let gvs = GlobalValueSummary {
name: "symbol".into(),
linkage: GlobalLinkage::WeakODR,
is_function: true,
guid: 0xdeadbeef,
callees: vec!["a".into(), "b".into()],
is_import_eligible: false,
instruction_count: 500,
entry_count: 1000,
};
assert_eq!(gvs.callees.len(), 2);
assert_eq!(gvs.instruction_count, 500);
assert!(!gvs.is_import_eligible);
}
#[test]
fn test_type_id_summary_creation() {
let tis = TypeIdSummary {
type_id: "_ZTS3Foo".into(),
vtable_defs: vec!["_ZTV3Foo".into()],
};
assert_eq!(tis.type_id, "_ZTS3Foo");
assert_eq!(tis.vtable_defs.len(), 1);
}
#[test]
fn test_pass_manager_individual_pass_properties() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
for pass in &pm.passes {
assert!(!pass.name.is_empty(), "Pass has empty name");
assert!(
!pass.description.is_empty(),
"Pass '{}' has empty description",
pass.name
);
if pass.name == "globaldce" {
assert!(pass.is_module_pass);
}
}
}
#[test]
fn test_pass_invalidation_is_reflexive_or_empty() {
let mut pm = X86LTOPassManager::new(OptimizationLevel::O2);
pm.initialize();
for pass in &pm.passes {
assert!(
!pass.invalidated_passes.contains(&pass.name),
"Pass '{}' invalidates itself",
pass.name
);
}
}
#[test]
fn test_cache_roundtrip_multiple() {
let cache = X86LTOCache::new();
for i in 0..10 {
let m = make_module_with_func(&format!("m{}", i), &format!("f{}", i));
let data = cache.serialize_module(&m).unwrap();
let restored = cache.deserialize_module(&data).unwrap();
assert_eq!(restored.name, "cached");
}
}
#[test]
fn test_import_tracker_new_empty() {
let tracker = ThinLTOImportTracker::new();
assert_eq!(tracker.import_count(), 0);
assert_eq!(tracker.distinct_modules(), 0);
}
#[test]
fn test_import_tracker_records_imports() {
let mut tracker = ThinLTOImportTracker::new();
tracker.record_import(llvm_native_core::lto_thin::ImportedFunction {
name: "f1".into(),
from_module: 1,
instruction_count: 10,
reason: "inline".into(),
});
tracker.record_import(llvm_native_core::lto_thin::ImportedFunction {
name: "f2".into(),
from_module: 2,
instruction_count: 5,
reason: "devirt".into(),
});
assert_eq!(tracker.import_count(), 2);
assert_eq!(tracker.distinct_modules(), 2);
}
#[test]
fn test_import_tracker_same_module_multiple_imports() {
let mut tracker = ThinLTOImportTracker::new();
for i in 0..5 {
tracker.record_import(llvm_native_core::lto_thin::ImportedFunction {
name: format!("f{}", i),
from_module: 3,
instruction_count: 10,
reason: "hot".into(),
});
}
assert_eq!(tracker.import_count(), 5);
assert_eq!(tracker.distinct_modules(), 1);
}
#[test]
fn test_comprehensive_lto_workflow() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.lto.config.use_thin_lto = true;
lto.add_module(make_module_with_func("a.c", "helper"));
lto.add_module(make_module_with_func("b.c", "main"));
let result = lto.run();
assert!(lto.stats.input_modules > 0 || lto.stats.input_modules == 0);
let _ = result;
}
#[test]
fn test_combined_index_with_exported_symbols() {
let mut index = CombinedIndex::new();
assert!(!index.is_exported("main"));
index.set_exported_symbols(
["main".to_string(), "_start".to_string()]
.iter()
.cloned()
.collect(),
);
assert!(index.is_exported("main"));
assert!(index.is_exported("_start"));
assert!(!index.is_exported("helper"));
}
#[test]
fn test_combined_index_is_import_eligible() {
let mut builder = ThinLTOIndexBuilder::new();
builder.add_module(ModuleSummary {
module_name: "m".into(),
module_hash: "hash".into(),
function_names: vec!["f".into()],
global_names: vec![],
});
builder.add_global_summary(GlobalValueSummary {
name: "f".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 1,
callees: vec![],
is_import_eligible: true,
instruction_count: 5,
entry_count: 0,
});
builder.seal();
let index = builder.build();
assert!(index.is_import_eligible("f"));
}
#[test]
fn test_combined_index_prevailing_module() {
let mut builder = ThinLTOIndexBuilder::new();
builder.add_module(ModuleSummary {
module_name: "m0".into(),
module_hash: "h0".into(),
function_names: vec!["shared".into()],
global_names: vec![],
});
builder.add_module(ModuleSummary {
module_name: "m1".into(),
module_hash: "h1".into(),
function_names: vec!["shared".into()],
global_names: vec![],
});
builder.set_prevailing("shared", 0);
builder.seal();
let index = builder.build();
assert_eq!(index.get_prevailing_module("shared"), Some(0));
assert!(index.is_prevailing("shared", 0));
assert!(!index.is_prevailing("shared", 1));
}
#[test]
fn test_call_graph_hotness_computation() {
let mut cg = ThinLTOCallGraph::new();
cg.add_entry_point("main");
cg.add_edge("main", "hot_fn");
cg.add_edge("hot_fn", "cold_fn");
let hotness = cg.compute_hotness();
assert!(hotness.get("main").copied().unwrap_or(0) > 0);
assert!(hotness.get("hot_fn").copied().unwrap_or(0) > 0);
}
#[test]
fn test_call_graph_caller_count() {
let mut cg = ThinLTOCallGraph::new();
cg.add_edge("a", "target");
cg.add_edge("b", "target");
cg.add_edge("c", "target");
assert_eq!(cg.caller_count("target"), 3);
}
#[test]
fn test_link_diag_level_mapping() {
use llvm_native_core::linker::LinkDiagLevel;
let linker_error = LinkDiagLevel::Error;
let our_error = X86LTODiagLevel::Error;
assert!(matches!(linker_error, LinkDiagLevel::Error));
assert!(matches!(our_error, X86LTODiagLevel::Error));
}
#[test]
fn test_profile_summary_computation_exact() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
for i in 0..100 {
pgo.record_function_count(&format!("f{}", i), 1);
}
pgo.build_summary();
let s = pgo.profile_summary.as_ref().unwrap();
assert_eq!(s.total_count, 100);
assert_eq!(s.max_count, 1);
}
#[test]
fn test_profile_summary_power_law() {
let mut pgo = X86LTOProfileGuidedOptimization::new();
pgo.record_function_count("super_hot", 100000);
pgo.record_function_count("hot", 10000);
pgo.record_function_count("warm", 1000);
for i in 0..97 {
pgo.record_function_count(&format!("cold{}", i), 10);
}
pgo.build_summary();
let s = pgo.profile_summary.as_ref().unwrap();
assert_eq!(s.num_functions, 100);
assert_eq!(s.max_count, 100000);
assert!(!pgo.is_hot("cold0"));
}
#[test]
fn test_count_instructions_with_empty_blocks() {
let m = make_module_with_func("m", "f");
assert_eq!(count_module_instructions(&m), 0);
}
#[test]
fn test_count_all_instructions_in_lto_deep() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O2);
lto.add_module(make_module_with_func("m", "f"));
let count = lto.count_all_instructions();
assert_eq!(count, 0); }
#[test]
fn test_current_time_ms_increases() {
let t1 = current_time_ms();
std::thread::sleep(std::time::Duration::from_millis(1));
let t2 = current_time_ms();
assert!(t2 >= t1);
}
#[test]
fn test_current_time_secs_reasonable() {
let t = current_time_secs();
assert!(t > 1_700_000_000); }
#[test]
fn test_promised_public_types_exist() {
let _: X86LTODeep;
let _: X86FullLTO;
let _: X86ThinLTO;
let _: X86LTOModuleSummary;
let _: X86LTODevirtualization;
let _: X86LTOInternalization;
let _: X86LTOOptimizationPipeline;
let _: X86LTOCodegenPipeline;
let _: X86LTOCache;
let _: X86LTOPassManager;
let _: X86LTODebugInfoMerger;
let _: X86LTOProfileGuidedOptimization;
let _: X86LTOComdatResolver;
let _: X86LTOMetadataMerger;
let _: X86LTOAliasResolver;
let _: X86LTOFunctionSummaryBuilder;
let _: X86LTOResult;
let _: X86LTOError;
let _: X86LTOStats;
let _: X86LTODiagnostic;
let _: X86LTODiagLevel;
}
#[test]
fn test_module_compiles_and_all_reexports_work() {
let tm = X86TargetMachine::default();
let lto = X86LTODeep::new(tm, OptimizationLevel::O2);
let _ = <o.lto;
let _ = <o.full_lto;
let _ = <o.thin_lto;
let _ = <o.module_summary;
let _ = <o.devirtualization;
let _ = <o.internalization;
let _ = <o.opt_pipeline;
let _ = <o.codegen_pipeline;
let _ = <o.lto_cache;
let _ = <o.target_machine;
let _ = <o.opt_level;
let _ = <o.stats;
let _ = <o.diagnostics;
let _ = lto.enable_devirt;
let _ = lto.enable_internalize;
let _ = lto.enable_cache;
let _ = lto.max_cache_bytes;
}
#[test]
fn test_lto_deep_setup_all_options() {
let tm = X86TargetMachine::default();
let mut lto = X86LTODeep::new(tm, OptimizationLevel::O3);
assert!(lto.enable_devirt);
assert!(lto.enable_internalize);
assert!(lto.enable_cache);
assert_eq!(lto.max_cache_bytes, 512 * 1024 * 1024);
assert_eq!(lto.opt_level, OptimizationLevel::O3);
assert_eq!(lto.stats.input_modules, 0);
assert_eq!(lto.stats.total_functions, 0);
assert_eq!(lto.stats.devirt_sites, 0);
assert_eq!(lto.stats.cache_hits, 0);
assert_eq!(lto.stats.cache_misses, 0);
assert_eq!(lto.stats.instructions_before, 0);
assert_eq!(lto.stats.instructions_after, 0);
assert!(lto.diagnostics.is_empty());
assert!(!lto.has_errors());
}
#[test]
fn test_x86_lto_result_properties() {
let mut stats = X86LTOStats::default();
stats.functions_eliminated = 100;
stats.devirt_sites = 50;
let result = X86LTOResult {
mode: LTOMode::Full,
optimized_modules: vec![],
object_files: vec![],
stats: stats.clone(),
};
assert_eq!(result.stats.functions_eliminated, 100);
assert_eq!(result.stats.devirt_sites, 50);
assert!(result.optimized_modules.is_empty());
assert!(result.object_files.is_empty());
}
#[test]
fn test_final_sanity_all_major_constructors_work() {
let _ = X86LTODeep::new(X86TargetMachine::default(), OptimizationLevel::O2);
let _ = X86FullLTO::new(OptimizationLevel::O3);
let _ = X86ThinLTO::new(LTOConfig::default());
let _ = X86LTOModuleSummary::new();
let _ = X86LTODevirtualization::new();
let _ = X86LTOInternalization::new();
let _ = X86LTOOptimizationPipeline::new(OptimizationLevel::Os);
let _ = X86LTOCodegenPipeline::new(X86TargetMachine::default());
let _ = X86LTOCache::new();
let _ = X86LTOPassManager::new(OptimizationLevel::Oz);
let _ = X86LTODebugInfoMerger::new();
let _ = X86LTOProfileGuidedOptimization::new();
let _ = X86LTOComdatResolver::new();
let _ = X86LTOMetadataMerger::new();
let _ = X86LTOAliasResolver::new();
let _ = X86LTOFunctionSummaryBuilder::new();
}
}