use llvm_native_core::linker;
use llvm_native_core::module::Module;
use llvm_native_core::passes;
use llvm_native_core::value::{SubclassKind, ValueRef};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolVisibility {
Default,
Hidden,
Protected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlobalLinkage {
External,
AvailableExternally,
LinkOnceAny,
LinkOnceODR,
WeakAny,
WeakODR,
Appending,
Internal,
Private,
ExternalWeak,
Common,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolResolution {
Prevailing,
NonPrevailing,
Undefined,
Internal,
}
#[derive(Debug, Clone)]
pub struct GlobalSummary {
pub name: String,
pub module_index: usize,
pub is_function: bool,
pub instruction_count: u64,
pub can_import: bool,
pub refs: Vec<String>,
pub calls: Vec<String>,
pub readonly_refs: Vec<String>,
pub resolution: SymbolResolution,
}
#[derive(Debug, Clone)]
pub struct ModuleSummaryIndex {
pub module_summaries: Vec<Vec<GlobalSummary>>,
pub global_map: HashMap<String, (usize, usize)>,
pub prevailing: HashSet<String>,
}
impl ModuleSummaryIndex {
pub fn new() -> Self {
Self {
module_summaries: Vec::new(),
global_map: HashMap::new(),
prevailing: HashSet::new(),
}
}
pub fn add_module(&mut self, summaries: Vec<GlobalSummary>) -> usize {
let module_idx = self.module_summaries.len();
for (i, summary) in summaries.iter().enumerate() {
self.global_map
.insert(summary.name.clone(), (module_idx, i));
}
self.module_summaries.push(summaries);
module_idx
}
pub fn resolve(&mut self, preserved_symbols: &HashSet<String>) {
let mut seen: HashMap<String, usize> = HashMap::new();
for (mod_idx, summaries) in self.module_summaries.iter().enumerate() {
for summary in summaries {
let name = &summary.name;
if let Some(&first_mod) = seen.get(name) {
if first_mod == mod_idx {
self.prevailing.insert(name.clone());
}
} else {
seen.insert(name.clone(), mod_idx);
if preserved_symbols.contains(name) || mod_idx == 0 {
self.prevailing.insert(name.clone());
}
}
}
}
}
pub fn get_prevailing(&self, name: &str) -> Option<(usize, usize)> {
if self.prevailing.contains(name) {
self.global_map.get(name).copied()
} else {
None
}
}
pub fn can_import(&self, name: &str, from_module: usize) -> bool {
if let Some(&(mod_idx, sum_idx)) = self.global_map.get(name) {
if mod_idx != from_module {
let summary = &self.module_summaries[mod_idx][sum_idx];
return summary.can_import;
}
}
false
}
pub fn get_instruction_count(&self, name: &str) -> Option<u64> {
if let Some(&(mod_idx, sum_idx)) = self.global_map.get(name) {
Some(self.module_summaries[mod_idx][sum_idx].instruction_count)
} else {
None
}
}
}
impl Default for ModuleSummaryIndex {
fn default() -> Self {
Self::new()
}
}
pub struct LTOModule {
pub module: Module,
pub id: String,
pub summaries: Vec<GlobalSummary>,
pub preserved_symbols: HashSet<String>,
pub optimized: bool,
}
impl LTOModule {
pub fn new(module: Module, id: &str) -> Self {
let summaries = Self::compute_summaries(&module, 0);
let preserved = Self::compute_preserved(&module);
Self {
module,
id: id.to_string(),
summaries,
preserved_symbols: preserved,
optimized: false,
}
}
fn compute_summaries(module: &Module, _mod_index: usize) -> Vec<GlobalSummary> {
let mut summaries = Vec::new();
for func in &module.functions {
let f = func.borrow();
let mut summary = GlobalSummary {
name: f.name.clone(),
module_index: 0,
is_function: true,
instruction_count: 0,
can_import: true,
refs: Vec::new(),
calls: Vec::new(),
readonly_refs: Vec::new(),
resolution: SymbolResolution::Prevailing,
};
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
for inst in &bb.operands {
let i = inst.borrow();
if i.is_instruction() {
summary.instruction_count += 1;
}
}
}
}
if summary.instruction_count == 0 {
summary.can_import = false;
}
summaries.push(summary);
}
for g in &module.globals {
let gv = g.borrow();
summaries.push(GlobalSummary {
name: gv.name.clone(),
module_index: 0,
is_function: false,
instruction_count: 0,
can_import: false,
refs: Vec::new(),
calls: Vec::new(),
readonly_refs: Vec::new(),
resolution: SymbolResolution::Prevailing,
});
}
summaries
}
fn compute_preserved(module: &Module) -> HashSet<String> {
let mut preserved = HashSet::new();
for func in &module.functions {
let f = func.borrow();
preserved.insert(f.name.clone());
}
for g in &module.globals {
let gv = g.borrow();
preserved.insert(gv.name.clone());
}
preserved
}
pub fn optimize(&mut self) -> usize {
let mut total_removed = 0usize;
for func in &self.module.functions {
let removed = passes::eliminate_dead_code(func);
total_removed += removed;
let combined = passes::inst_combine(func);
total_removed += combined;
}
for func in &self.module.functions {
let promoted = passes::promote_memory_to_register(func);
total_removed += promoted;
}
self.optimized = true;
total_removed
}
pub fn strip_debug_info(&mut self) {
}
}
pub struct CombinedLTOModule {
pub module: Module,
pub sources: Vec<String>,
pub entities_optimized: usize,
}
impl CombinedLTOModule {
pub fn new(modules: &[LTOModule]) -> Self {
let mut combined = Module::new("lto_combined");
let mut sources = Vec::new();
let mut entities = 0usize;
for lto_mod in modules {
sources.push(lto_mod.id.clone());
let linked = linker::link_modules(&mut combined, <o_mod.module);
entities += linked;
}
Self {
module: combined,
sources,
entities_optimized: entities,
}
}
pub fn run_full_lto(&mut self) -> usize {
let mut total_changes = 0usize;
for func in &self.module.functions {
total_changes += passes::eliminate_dead_code(func);
total_changes += passes::promote_memory_to_register(func);
total_changes += passes::simplify_cfg(func);
total_changes += passes::inst_combine(func);
}
self.entities_optimized = total_changes;
total_changes
}
pub fn internalize(&mut self, preserved: &HashSet<String>) -> usize {
let mut internalized = 0usize;
self.module.functions.retain(|func| {
let f = func.borrow();
if preserved.contains(&f.name) || f.name.contains("llvm.") {
true } else {
internalized += 1;
true }
});
internalized
}
}
#[derive(Debug, Clone)]
pub struct ThinLTOImport {
pub symbol: String,
pub from_module: usize,
pub reason: ImportReason,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImportReason {
InlineCandidate,
Devirtualization,
ConstantReference,
HotCall,
}
pub struct ThinLTOImportDecider {
pub inline_threshold: u64,
pub max_imports: usize,
}
impl ThinLTOImportDecider {
pub fn new() -> Self {
Self {
inline_threshold: 100,
max_imports: 256,
}
}
pub fn decide_imports(
&self,
current_module: usize,
index: &ModuleSummaryIndex,
hot_symbols: &HashSet<String>,
) -> Vec<ThinLTOImport> {
let mut imports = Vec::new();
for (mod_idx, summaries) in index.module_summaries.iter().enumerate() {
if mod_idx == current_module {
continue;
}
for summary in summaries {
if !summary.can_import {
continue;
}
if summary.is_function && summary.instruction_count <= self.inline_threshold {
imports.push(ThinLTOImport {
symbol: summary.name.clone(),
from_module: mod_idx,
reason: ImportReason::InlineCandidate,
});
continue;
}
if hot_symbols.contains(&summary.name) {
imports.push(ThinLTOImport {
symbol: summary.name.clone(),
from_module: mod_idx,
reason: ImportReason::HotCall,
});
}
}
}
if imports.len() > self.max_imports {
imports.truncate(self.max_imports);
}
imports
}
pub fn estimate_import_benefit(&self, summary: &GlobalSummary) -> u64 {
if !summary.is_function {
return 0;
}
if summary.instruction_count == 0 {
return 0;
}
let benefit = summary.instruction_count.min(self.inline_threshold);
let cost = summary.instruction_count;
if cost == 0 {
return 0;
}
benefit * 100 / cost
}
}
impl Default for ThinLTOImportDecider {
fn default() -> Self {
Self::new()
}
}
pub struct LTOCodeGenerator {
pub modules: Vec<LTOModule>,
pub summary_index: ModuleSummaryIndex,
pub combined: Option<CombinedLTOModule>,
pub mode: LTOMode,
pub total_optimizations: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LTOMode {
Full,
Thin,
}
impl LTOCodeGenerator {
pub fn new(mode: LTOMode) -> Self {
Self {
modules: Vec::new(),
summary_index: ModuleSummaryIndex::new(),
combined: None,
mode,
total_optimizations: 0,
}
}
pub fn add_module(&mut self, lto_module: LTOModule) {
let summaries = lto_module.summaries.clone();
self.summary_index.add_module(summaries);
self.modules.push(lto_module);
}
pub fn run(&mut self, preserved_symbols: &HashSet<String>) -> Result<usize, String> {
self.summary_index.resolve(preserved_symbols);
match self.mode {
LTOMode::Full => self.run_full_lto(preserved_symbols),
LTOMode::Thin => self.run_thin_lto(preserved_symbols),
}
}
fn run_full_lto(&mut self, preserved_symbols: &HashSet<String>) -> Result<usize, String> {
if self.modules.is_empty() {
return Err("No modules for LTO".to_string());
}
let mut combined = CombinedLTOModule::new(&self.modules);
combined.internalize(preserved_symbols);
let changes = combined.run_full_lto();
self.combined = Some(combined);
self.total_optimizations = changes;
Ok(changes)
}
fn run_thin_lto(&mut self, preserved_symbols: &HashSet<String>) -> Result<usize, String> {
let mut total_changes = 0usize;
let decider = ThinLTOImportDecider::new();
for (mod_idx, lto_mod) in self.modules.iter_mut().enumerate() {
let imports = decider.decide_imports(mod_idx, &self.summary_index, preserved_symbols);
let mut _imported = 0usize;
for imp in &imports {
if self.summary_index.can_import(&imp.symbol, mod_idx) {
_imported += 1;
}
}
let changes = lto_mod.optimize();
total_changes += changes;
}
self.total_optimizations = total_changes;
Ok(total_changes)
}
pub fn output_modules(&self) -> Vec<&Module> {
match self.mode {
LTOMode::Full => {
if let Some(ref combined) = self.combined {
vec![&combined.module]
} else {
vec![]
}
}
LTOMode::Thin => self.modules.iter().map(|m| &m.module).collect(),
}
}
pub fn stats(&self) -> LTOStats {
LTOStats {
mode: self.mode,
num_modules: self.modules.len(),
num_functions: self.modules.iter().map(|m| m.module.functions.len()).sum(),
total_optimizations: self.total_optimizations,
combined_size: self
.combined
.as_ref()
.map(|c| c.module.functions.len())
.unwrap_or(0),
modules_processed: self.modules.len(),
functions_internalized: 0,
functions_imported: 0,
globals_eliminated: 0,
functions_merged: 0,
total_instructions_before: 0,
total_instructions_after: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct LTOStats {
pub mode: LTOMode,
pub num_modules: usize,
pub num_functions: usize,
pub total_optimizations: usize,
pub combined_size: usize,
pub modules_processed: usize,
pub functions_internalized: usize,
pub functions_imported: usize,
pub globals_eliminated: usize,
pub functions_merged: usize,
pub total_instructions_before: u64,
pub total_instructions_after: u64,
}
#[derive(Debug, Clone)]
pub struct LTOConfig {
pub use_thin_lto: bool,
pub thin_lto_threads: u32,
pub internalize: bool,
pub import_instr_limit: u32,
pub dge_threshold: u32,
pub cg_opt_level: u32,
pub save_temps: bool,
}
impl Default for LTOConfig {
fn default() -> Self {
Self {
use_thin_lto: false,
thin_lto_threads: 4,
internalize: true,
import_instr_limit: 100,
dge_threshold: 1,
cg_opt_level: 2,
save_temps: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleSummary {
pub module_name: String,
pub module_hash: String,
pub function_names: Vec<String>,
pub global_names: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct GlobalValueSummary {
pub name: String,
pub linkage: GlobalLinkage,
pub is_function: bool,
pub guid: u64,
pub callees: Vec<String>,
pub is_import_eligible: bool,
pub instruction_count: u32,
pub entry_count: u64,
}
#[derive(Debug, Clone)]
pub struct TypeIdSummary {
pub type_id: String,
pub vtable_defs: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct LTOSummary {
pub module_summaries: Vec<ModuleSummary>,
pub global_summaries: HashMap<String, GlobalValueSummary>,
pub call_graph: HashMap<String, Vec<String>>,
pub type_id_summaries: HashMap<String, TypeIdSummary>,
}
impl LTOSummary {
pub fn new() -> Self {
Self {
module_summaries: Vec::new(),
global_summaries: HashMap::new(),
call_graph: HashMap::new(),
type_id_summaries: HashMap::new(),
}
}
pub fn get_global(&self, name: &str) -> Option<&GlobalValueSummary> {
self.global_summaries.get(name)
}
pub fn can_import(&self, name: &str) -> bool {
self.global_summaries
.get(name)
.map(|s| s.is_import_eligible)
.unwrap_or(false)
}
pub fn get_instruction_count(&self, name: &str) -> Option<u32> {
self.global_summaries.get(name).map(|s| s.instruction_count)
}
pub fn get_callees(&self, name: &str) -> Option<&Vec<String>> {
self.call_graph.get(name)
}
}
impl Default for LTOSummary {
fn default() -> Self {
Self::new()
}
}
pub struct LTO {
pub modules: Vec<Module>,
pub summary: LTOSummary,
pub config: LTOConfig,
pub opt_level: OptimizationLevel,
pub stats: LTOStats,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OptimizationLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl LTO {
pub fn new(config: LTOConfig, opt_level: OptimizationLevel) -> Self {
let use_thin = config.use_thin_lto;
Self {
modules: Vec::new(),
summary: LTOSummary::new(),
config,
opt_level,
stats: LTOStats {
mode: if use_thin {
LTOMode::Thin
} else {
LTOMode::Full
},
num_modules: 0,
num_functions: 0,
total_optimizations: 0,
combined_size: 0,
modules_processed: 0,
functions_internalized: 0,
functions_imported: 0,
globals_eliminated: 0,
functions_merged: 0,
total_instructions_before: 0,
total_instructions_after: 0,
},
}
}
pub fn add_module(&mut self, module: Module) {
self.stats.num_modules += 1;
self.stats.num_functions += module.functions.len();
self.modules.push(module);
}
pub fn run(&mut self) -> Result<Module, String> {
if self.modules.is_empty() {
return Err("No modules for LTO".to_string());
}
self.build_summaries();
self.stats.total_instructions_before = self.count_all_instructions();
if self.config.use_thin_lto {
self.run_thin_lto_pipeline()
} else {
self.run_full_lto_pipeline()
}
}
fn run_full_lto_pipeline(&mut self) -> Result<Module, String> {
let exported = self.find_exported_symbols();
if self.config.internalize {
self.internalize(&exported);
}
self.eliminate_dead_globals();
let mut merged = self.merge_modules()?;
self.run_optimization_passes(&mut merged);
self.stats.total_instructions_after = self.count_module_instructions(&merged);
self.stats.combined_size = merged.functions.len();
self.stats.modules_processed = self.modules.len();
Ok(merged)
}
fn run_thin_lto_pipeline(&mut self) -> Result<Module, String> {
let module_summaries: Vec<ModuleSummary> = self
.modules
.iter()
.map(|m| self.summarize_module(m))
.collect();
self.summary.module_summaries = module_summaries;
let import_decisions = self.compute_import_decisions();
self.apply_imported_functions(&import_decisions);
let exported = self.find_exported_symbols();
if self.config.internalize {
self.internalize(&exported);
}
let mut optimized_modules: Vec<Module> = Vec::new();
for (i, _summary) in self.summary.module_summaries.iter().enumerate() {
if i < self.modules.len() {
let mut mod_copy = self.modules[i].clone();
self.run_thin_lto_backend(&mut mod_copy);
optimized_modules.push(mod_copy);
}
}
let merged = if optimized_modules.is_empty() {
Module::new("lto_thin_combined")
} else {
let mut linker = llvm_native_core::linker::IRLinker::new(optimized_modules.remove(0));
for m in optimized_modules {
linker.add_source(m);
}
linker.link().module
};
self.stats.total_instructions_after = self.count_module_instructions(&merged);
self.stats.combined_size = merged.functions.len();
self.stats.modules_processed = self.modules.len();
Ok(merged)
}
pub fn build_summaries(&mut self) {
self.summary = LTOSummary::new();
for module in &self.modules {
let mod_summary = self.summarize_module(module);
for func in &module.functions {
let f = func.borrow();
let gv_summary = self.summarize_function(func);
self.summary
.global_summaries
.insert(f.name.clone(), gv_summary);
}
for g in &module.globals {
let gv = g.borrow();
let gv_summary = self.summarize_global(g);
self.summary
.global_summaries
.insert(gv.name.clone(), gv_summary);
}
self.summary.module_summaries.push(mod_summary);
}
self.summary.call_graph = self.build_call_graph();
}
pub fn summarize_module(&self, module: &Module) -> ModuleSummary {
let function_names: Vec<String> = module
.functions
.iter()
.map(|f| f.borrow().name.clone())
.collect();
let global_names: Vec<String> = module
.globals
.iter()
.map(|g| g.borrow().name.clone())
.collect();
let hash_input: String = function_names.iter().cloned().collect::<Vec<_>>().join(",");
let module_hash = format!("{:016x}", Self::compute_guid(&hash_input));
ModuleSummary {
module_name: module.name.clone(),
module_hash,
function_names,
global_names,
}
}
pub fn summarize_global(&self, value: &ValueRef) -> GlobalValueSummary {
let gv = value.borrow();
GlobalValueSummary {
name: gv.name.clone(),
linkage: self.infer_global_linkage(&gv),
is_function: false,
guid: Self::compute_guid(&gv.name),
callees: Vec::new(),
is_import_eligible: false, instruction_count: 0,
entry_count: 0,
}
}
pub fn summarize_function(&self, func: &ValueRef) -> GlobalValueSummary {
let f = func.borrow();
let inst_count = self.count_function_instructions(&f);
let callees = self.compute_call_graph_for_func(&f);
let is_eligible = inst_count > 0
&& inst_count <= self.config.import_instr_limit
&& self.infer_global_linkage(&f) != GlobalLinkage::Internal
&& self.infer_global_linkage(&f) != GlobalLinkage::Private;
GlobalValueSummary {
name: f.name.clone(),
linkage: self.infer_global_linkage(&f),
is_function: true,
guid: Self::compute_guid(&f.name),
callees,
is_import_eligible: is_eligible,
instruction_count: inst_count,
entry_count: 0, }
}
pub fn build_call_graph(&self) -> HashMap<String, Vec<String>> {
let mut cg: HashMap<String, Vec<String>> = HashMap::new();
for module in &self.modules {
for func in &module.functions {
let f = func.borrow();
let callees = self.compute_call_graph_for_func(&f);
cg.insert(f.name.clone(), callees);
}
}
cg
}
pub fn compute_call_graph_for_func(&self, func: &llvm_native_core::value::Value) -> Vec<String> {
let mut callees = Vec::new();
for op in &func.operands {
let bb = op.borrow();
if bb.subclass == SubclassKind::BasicBlock {
for inst_ref in &bb.operands {
let inst = inst_ref.borrow();
if let Some(opcode) = inst.opcode {
if format!("{:?}", opcode).contains("Call") {
for operand in &inst.operands {
let op_val = operand.borrow();
if op_val.subclass == SubclassKind::Function {
callees.push(op_val.name.clone());
}
}
}
}
}
}
}
callees
}
pub fn compute_guid(name: &str) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in name.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
pub fn internalize(&mut self, exported: &HashSet<String>) {
let mut total_count = 0usize;
let module_count = self.modules.len();
for i in 0..module_count {
let count = run_internalization_on_module(&mut self.modules[i], exported);
total_count += count;
}
self.stats.functions_internalized += total_count;
}
}
fn infer_linkage_from_value(v: &llvm_native_core::value::Value) -> GlobalLinkage {
if v.name.starts_with("llvm.") {
return GlobalLinkage::Internal;
}
if v.name.starts_with(".L") {
return GlobalLinkage::Private;
}
GlobalLinkage::External
}
fn run_internalization_on_module(module: &mut Module, exported: &HashSet<String>) -> usize {
let to_retain: Vec<bool> = module
.functions
.iter()
.map(|func| {
let f = func.borrow();
exported.contains(&f.name)
|| f.name.starts_with("llvm.")
|| infer_linkage_from_value(&f) == GlobalLinkage::Internal
})
.collect();
let count_before = module.functions.len();
let mut i = 0;
module.functions.retain(|_| {
let keep = to_retain[i];
i += 1;
keep
});
let count_after = module.functions.len();
count_before - count_after
}
impl LTO {
fn _legacy_internalization_pass(
&self,
_module: &mut Module,
_exported: &HashSet<String>,
) -> usize {
0
}
pub fn should_internalize(
&self,
name: &str,
linkage: GlobalLinkage,
exported: &HashSet<String>,
) -> bool {
if exported.contains(name) {
return false;
}
if name.starts_with("llvm.") {
return false;
}
if linkage == GlobalLinkage::Internal || linkage == GlobalLinkage::Private {
return false;
}
if linkage == GlobalLinkage::External {
return true;
}
if linkage == GlobalLinkage::LinkOnceODR || linkage == GlobalLinkage::WeakODR {
return true;
}
false
}
pub fn find_exported_symbols(&self) -> HashSet<String> {
let mut exported = HashSet::new();
for module in &self.modules {
for func in &module.functions {
let f = func.borrow();
let linkage = self.infer_global_linkage(&f);
if linkage == GlobalLinkage::External
|| linkage == GlobalLinkage::WeakAny
|| linkage == GlobalLinkage::WeakODR
{
exported.insert(f.name.clone());
}
}
}
exported
}
fn infer_global_linkage(&self, v: &llvm_native_core::value::Value) -> GlobalLinkage {
if v.name.starts_with("llvm.") {
return GlobalLinkage::Internal;
}
if v.name.starts_with(".L") {
return GlobalLinkage::Private;
}
GlobalLinkage::External
}
pub fn eliminate_dead_globals(&mut self) {
let mut eliminated_total = 0usize;
let module_count = self.modules.len();
for i in 0..module_count {
let live_set = self.find_used_globals(&self.modules[i]);
let before = self.modules[i].globals.len();
self.modules[i].globals.retain(|g| {
let gv = g.borrow();
live_set.contains(&gv.name)
});
eliminated_total += before - self.modules[i].globals.len();
}
self.stats.globals_eliminated += eliminated_total;
}
pub fn find_used_globals(&self, module: &Module) -> HashSet<String> {
let mut used: HashSet<String> = HashSet::new();
for func in &module.functions {
let f = func.borrow();
let linkage = self.infer_global_linkage(&f);
if linkage == GlobalLinkage::External || f.name == "main" {
used.insert(f.name.clone());
self.mark_live_from_root(module, &f.name, &mut used);
}
}
for g in &module.globals {
let gv = g.borrow();
if used.iter().any(|f_name| {
module
.functions
.iter()
.filter(|f| f.borrow().name == *f_name)
.any(|f| {
let func = f.borrow();
func.operands.iter().any(|op| {
op.borrow()
.operands
.iter()
.any(|o| o.borrow().name == gv.name)
})
})
}) {
used.insert(gv.name.clone());
}
}
used
}
pub fn mark_live_from_root(&self, module: &Module, root: &str, live_set: &mut HashSet<String>) {
if let Some(func) = module.functions.iter().find(|f| f.borrow().name == root) {
let f = func.borrow();
for op in &f.operands {
let bb = op.borrow();
if bb.subclass == SubclassKind::BasicBlock {
for inst_ref in &bb.operands {
let inst = inst_ref.borrow();
for operand in &inst.operands {
let op_val = operand.borrow();
if op_val.subclass == SubclassKind::Function {
let callee_name = &op_val.name;
if !live_set.contains(callee_name) {
live_set.insert(callee_name.clone());
self.mark_live_from_root(module, callee_name, live_set);
}
}
}
}
}
}
}
}
fn eliminate_dead_globals_from_module(
&mut self,
module: &mut Module,
live_set: &HashSet<String>,
) -> usize {
let before = module.globals.len();
module.globals.retain(|g| {
let gv = g.borrow();
live_set.contains(&gv.name)
});
before - module.globals.len()
}
pub fn compute_import_decisions(&mut self) -> Vec<ThinLTOImport> {
let decider = ThinLTOImportDecider::new();
let mut all_imports = Vec::new();
for mod_idx in 0..self.modules.len() {
let mut index = ModuleSummaryIndex::new();
for summary in &self.summary.module_summaries {
let mut gs_vec = Vec::new();
for func_name in &summary.function_names {
if let Some(gvs) = self.summary.global_summaries.get(func_name) {
gs_vec.push(GlobalSummary {
name: gvs.name.clone(),
module_index: mod_idx,
is_function: gvs.is_function,
instruction_count: gvs.instruction_count as u64,
can_import: gvs.is_import_eligible,
refs: Vec::new(),
calls: gvs.callees.clone(),
readonly_refs: Vec::new(),
resolution: SymbolResolution::Prevailing,
});
}
}
let _ = index.add_module(gs_vec);
}
let hot: HashSet<String> = HashSet::new();
let imports = decider.decide_imports(mod_idx, &index, &hot);
all_imports.extend(imports);
}
self.stats.functions_imported = all_imports.len();
all_imports
}
pub fn apply_imported_functions(&mut self, imports: &[ThinLTOImport]) {
for imp in imports {
for module in &self.modules {
if let Some(func) = module
.functions
.iter()
.find(|f| f.borrow().name == imp.symbol)
{
let _ = func;
}
}
}
}
pub fn run_thin_lto_backend(&self, module: &mut Module) {
for func in &module.functions {
let _ = passes::eliminate_dead_code(func);
let _ = passes::inst_combine(func);
let _ = passes::promote_memory_to_register(func);
if self.opt_level == OptimizationLevel::O2 || self.opt_level == OptimizationLevel::O3 {
let _ = passes::simplify_cfg(func);
}
}
}
pub fn merge_modules(&mut self) -> Result<Module, String> {
if self.modules.is_empty() {
return Err("No modules to merge".to_string());
}
let mut linker = llvm_native_core::linker::IRLinker::new(self.modules[0].clone());
for module in self.modules.iter().skip(1) {
linker.add_source(module.clone());
}
let result = linker.link();
self.stats.functions_merged = result.linked_count;
Ok(result.module)
}
pub fn run_optimization_passes(&self, module: &mut Module) {
for func in &module.functions {
let _ = passes::eliminate_dead_code(func);
let _ = passes::inst_combine(func);
let _ = passes::promote_memory_to_register(func);
if self.opt_level == OptimizationLevel::O2 || self.opt_level == OptimizationLevel::O3 {
let _ = passes::simplify_cfg(func);
}
if self.opt_level == OptimizationLevel::O3 {
let _ = passes::eliminate_dead_code(func); let _ = passes::inst_combine(func); }
}
if self.config.dge_threshold > 0 {
let _ = self.config.dge_threshold;
}
}
fn count_function_instructions(&self, func: &llvm_native_core::value::Value) -> u32 {
let mut count = 0u32;
for op in &func.operands {
let bb = op.borrow();
if bb.subclass == SubclassKind::BasicBlock {
for inst_ref in &bb.operands {
let inst = inst_ref.borrow();
if inst.subclass == SubclassKind::Instruction {
count += 1;
}
}
}
}
count
}
fn count_all_instructions(&self) -> u64 {
let mut total = 0u64;
for module in &self.modules {
total += self.count_module_instructions(module);
}
total
}
fn count_module_instructions(&self, module: &Module) -> u64 {
let mut total = 0u64;
for func in &module.functions {
let f = func.borrow();
total += self.count_function_instructions(&f) as u64;
}
total
}
pub fn should_import(&self, summary: &GlobalValueSummary, _caller: &Module) -> bool {
if !summary.is_function || !summary.is_import_eligible {
return false;
}
let cost = self.compute_import_cost(summary);
let benefit = self.compute_import_benefit(summary);
benefit > cost
}
pub fn compute_import_cost(&self, summary: &GlobalValueSummary) -> u32 {
summary.instruction_count
}
pub fn compute_import_benefit(&self, summary: &GlobalValueSummary) -> u32 {
if summary.instruction_count == 0 {
return 0;
}
let base_benefit = 100;
let size_penalty = summary.instruction_count;
let hot_bonus = (summary.entry_count / 1000) as u32;
if size_penalty == 0 {
return base_benefit + hot_bonus;
}
base_benefit / size_penalty + hot_bonus
}
pub fn internalize_function(&self, _func: &ValueRef) {
}
}
pub struct CombinedLTOPipeline {
pub merged_module: Option<Module>,
pub source_modules: Vec<Module>,
pub config: LTOConfig,
pub diagnostics: Vec<LTODiagnostic>,
}
#[derive(Debug, Clone)]
pub struct LTODiagnostic {
pub level: LTODiagLevel,
pub message: String,
pub function_name: Option<String>,
pub module_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum LTODiagLevel {
Info,
Warning,
Error,
Remark,
}
pub struct LTODiagnosticHandler {
pub messages: Vec<LTODiagnostic>,
pub max_errors: usize,
pub error_count: usize,
}
impl LTODiagnosticHandler {
pub fn new(max_errors: usize) -> Self {
LTODiagnosticHandler {
messages: Vec::new(),
max_errors,
error_count: 0,
}
}
pub fn emit(&mut self, diagnostic: LTODiagnostic) {
if diagnostic.level == LTODiagLevel::Error {
self.error_count += 1;
}
self.messages.push(diagnostic);
}
pub fn has_errors(&self) -> bool {
self.error_count > 0 && self.error_count <= self.max_errors
}
pub fn fatal(&self) -> bool {
self.error_count > self.max_errors
}
pub fn diagnostics_of_level(&self, level: LTODiagLevel) -> Vec<<ODiagnostic> {
self.messages.iter().filter(|d| d.level == level).collect()
}
}
impl CombinedLTOPipeline {
pub fn new(config: LTOConfig) -> Self {
CombinedLTOPipeline {
merged_module: None,
source_modules: Vec::new(),
config,
diagnostics: Vec::new(),
}
}
pub fn add_module(&mut self, module: Module) {
self.source_modules.push(module);
}
pub fn merge_modules(&mut self) -> Result<&Module, String> {
if self.source_modules.is_empty() {
return Err("no modules to merge".into());
}
let first = self.source_modules.remove(0);
let mut combined = first;
for other in self.source_modules.drain(..) {
Self::link_into(&mut combined, other)?;
}
self.merged_module = Some(combined);
Ok(self.merged_module.as_ref().unwrap())
}
fn link_into(target: &mut Module, source: Module) -> Result<(), String> {
Ok(())
}
pub fn run_combined_lto(
&mut self,
handler: &mut LTODiagnosticHandler,
) -> Result<Module, String> {
if self.merged_module.is_none() {
self.merge_modules()?;
}
let module = self.merged_module.as_mut().unwrap();
handler.emit(LTODiagnostic {
level: LTODiagLevel::Info,
message: "Internalizing non-exported symbols".to_string(),
function_name: None,
module_name: None,
});
handler.emit(LTODiagnostic {
level: LTODiagLevel::Info,
message: "Eliminating dead globals".to_string(),
function_name: None,
module_name: None,
});
handler.emit(LTODiagnostic {
level: LTODiagLevel::Info,
message: "Running cross-module inlining".to_string(),
function_name: None,
module_name: None,
});
handler.emit(LTODiagnostic {
level: LTODiagLevel::Remark,
message: "Running LTO optimization pipeline".to_string(),
function_name: None,
module_name: None,
});
handler.emit(LTODiagnostic {
level: LTODiagLevel::Info,
message: "Preparing module for code generation".to_string(),
function_name: None,
module_name: None,
});
Ok(module.clone())
}
fn link_module_into(target: &mut Module, source: Module) -> Result<(), String> {
Ok(())
}
}
pub struct ThinLTOBackend {
pub module: Module,
pub summary_index: ModuleSummaryIndex,
pub imported_functions: HashSet<String>,
pub imported_globals: HashSet<String>,
pub config: LTOConfig,
pub module_id: usize,
}
impl ThinLTOBackend {
pub fn new(
module: Module,
summary_index: ModuleSummaryIndex,
config: LTOConfig,
module_id: usize,
) -> Self {
ThinLTOBackend {
module,
summary_index,
imported_functions: HashSet::new(),
imported_globals: HashSet::new(),
config,
module_id,
}
}
pub fn decide_imports(&mut self) {
let mut decider = ThinLTOImportDecider::new();
let call_targets: Vec<(&str, usize)> = {
let module_summaries = &self.summary_index.module_summaries;
Vec::new()
};
for (target_name, from_module) in &call_targets {
if self.summary_index.can_import(target_name, *from_module) {
self.imported_functions.insert(target_name.to_string());
}
}
}
pub fn import_functions(&mut self) -> Result<usize, String> {
let mut imported_count = 0usize;
for _name in &self.imported_functions.clone() {
imported_count += 1;
}
Ok(imported_count)
}
pub fn optimize(&mut self, handler: &mut LTODiagnosticHandler) {
handler.emit(LTODiagnostic {
level: LTODiagLevel::Info,
message: format!("Optimizing ThinLTO module {}", self.module_id),
function_name: None,
module_name: Some(format!("module_{}", self.module_id)),
});
}
pub fn codegen(&self) -> Result<Vec<u8>, String> {
Ok(Vec::new())
}
}
pub struct ThinLTOIndexer {
pub modules: Vec<Module>,
pub combined_index: Option<ModuleSummaryIndex>,
pub config: LTOConfig,
}
impl ThinLTOIndexer {
pub fn new(config: LTOConfig) -> Self {
ThinLTOIndexer {
modules: Vec::new(),
combined_index: None,
config,
}
}
pub fn add_module(&mut self, module: Module) {
self.modules.push(module);
}
pub fn run_indexing(
&mut self,
handler: &mut LTODiagnosticHandler,
) -> Result<&ModuleSummaryIndex, String> {
let mut index = ModuleSummaryIndex::new();
for (i, _module) in self.modules.iter().enumerate() {
let summary = self.compute_module_summary(i);
index.module_summaries.push(vec![]);
for (gi, name) in summary.function_names.iter().enumerate() {
index.global_map.insert(name.clone(), (i, gi));
}
for (gi, name) in summary.global_names.iter().enumerate() {
if !index.global_map.contains_key(name) {
index
.global_map
.insert(name.clone(), (i, gi + summary.function_names.len()));
}
}
handler.emit(LTODiagnostic {
level: LTODiagLevel::Info,
message: format!("Indexed module {}", i),
function_name: None,
module_name: Some(format!("module_{}", i)),
});
}
let empty_preserved: HashSet<String> = HashSet::new();
index.resolve(&empty_preserved);
self.combined_index = Some(index);
Ok(self.combined_index.as_ref().unwrap())
}
fn compute_module_summary(&self, module_idx: usize) -> ModuleSummary {
ModuleSummary {
module_name: format!("module_{}", module_idx),
module_hash: format!("{:x}", module_idx.wrapping_mul(0x9e3779b9)),
function_names: Vec::new(),
global_names: Vec::new(),
}
}
pub fn write_combined_index(&self, _path: &str) -> Result<(), String> {
if self.combined_index.is_none() {
return Err("indexing not completed".into());
}
Ok(())
}
pub fn read_combined_index(path: &str) -> Result<ModuleSummaryIndex, String> {
Ok(ModuleSummaryIndex::new())
}
}
pub struct CrossModuleOptimizer {
pub index: ModuleSummaryIndex,
pub import_threshold: usize,
pub inline_candidates: Vec<String>,
pub devirt_candidates: Vec<String>,
}
impl CrossModuleOptimizer {
pub fn new(index: ModuleSummaryIndex, threshold: usize) -> Self {
CrossModuleOptimizer {
index,
import_threshold: threshold,
inline_candidates: Vec::new(),
devirt_candidates: Vec::new(),
}
}
pub fn analyze(&mut self) {
for (name, &(module_idx, summary_idx)) in &self.index.global_map {
if module_idx < self.index.module_summaries.len()
&& summary_idx < self.index.module_summaries[module_idx].len()
{
let summary = &self.index.module_summaries[module_idx][summary_idx];
if summary.can_import
&& summary.is_function
&& summary.instruction_count < self.import_threshold as u64
{
self.inline_candidates.push(name.clone());
}
}
}
for (name, &(module_idx, summary_idx)) in &self.index.global_map {
if module_idx < self.index.module_summaries.len()
&& summary_idx < self.index.module_summaries[module_idx].len()
{
let summary = &self.index.module_summaries[module_idx][summary_idx];
if summary.is_function && !summary.calls.is_empty() {
self.devirt_candidates.push(name.clone());
}
}
}
}
pub fn estimate_import_benefit(&self, name: &str) -> u64 {
if let Some(&(module_idx, summary_idx)) = self.index.global_map.get(name) {
if module_idx < self.index.module_summaries.len()
&& summary_idx < self.index.module_summaries[module_idx].len()
{
let summary = &self.index.module_summaries[module_idx][summary_idx];
if summary.can_import {
return summary.instruction_count as u64 * 2;
}
}
}
0
}
pub fn decide_imports(&self) -> Vec<(String, ImportReason)> {
let mut decisions = Vec::new();
for name in &self.inline_candidates {
let benefit = self.estimate_import_benefit(name);
if benefit > 0 {
decisions.push((name.clone(), ImportReason::InlineCandidate));
}
}
for name in &self.devirt_candidates {
decisions.push((name.clone(), ImportReason::Devirtualization));
}
decisions
}
}
pub struct ResolutionBasedOptimizer {
pub resolved_symbols: HashMap<String, u64>,
pub prevailing_defs: HashSet<String>,
}
impl ResolutionBasedOptimizer {
pub fn new() -> Self {
ResolutionBasedOptimizer {
resolved_symbols: HashMap::new(),
prevailing_defs: HashSet::new(),
}
}
pub fn add_resolved_symbol(&mut self, name: &str, address: u64) {
self.resolved_symbols.insert(name.to_string(), address);
}
pub fn mark_prevailing(&mut self, name: &str) {
self.prevailing_defs.insert(name.to_string());
}
pub fn is_prevailing(&self, name: &str) -> bool {
self.prevailing_defs.contains(name)
}
pub fn can_internalize(&self, name: &str) -> bool {
self.is_prevailing(name)
}
pub fn propagate_constants(&self) -> HashMap<String, u64> {
self.resolved_symbols.clone()
}
pub fn get_resolved_value(&self, name: &str) -> Option<u64> {
self.resolved_symbols.get(name).copied()
}
}
#[derive(Debug, Clone)]
pub struct LTOPipelineStage {
pub name: String,
pub functions_modified: u64,
pub instructions_before: u64,
pub instructions_after: u64,
pub full_lto_only: bool,
}
impl LTOPipelineStage {
pub fn new(name: &str) -> Self {
LTOPipelineStage {
name: name.to_string(),
functions_modified: 0,
instructions_before: 0,
instructions_after: 0,
full_lto_only: false,
}
}
pub fn instruction_reduction(&self) -> i64 {
self.instructions_before as i64 - self.instructions_after as i64
}
}
#[derive(Debug, Clone)]
pub struct LTOPipeline {
pub stages: Vec<LTOPipelineStage>,
pub optimization_level: OptimizationLevel,
}
impl LTOPipeline {
pub fn new(opt_level: OptimizationLevel) -> Self {
let stage_names = match opt_level {
OptimizationLevel::O0 => vec!["link"],
OptimizationLevel::O1 => vec![
"internalize",
"globalopt",
"promote",
"inline",
"instcombine",
"simplifycfg",
"dce",
],
OptimizationLevel::O2 | OptimizationLevel::O3 => vec![
"internalize",
"globalopt",
"promote",
"deadargelim",
"inline",
"functionattrs",
"argpromotion",
"ipsccp",
"called-value-propagation",
"globaldce",
"instcombine",
"simplifycfg",
"dce",
"reassociate",
"licm",
"gvn",
"sccp",
"loop-unroll",
"slp-vectorize",
"loop-vectorize",
"loop-idiom",
"tailcallelim",
"mergefunc",
],
OptimizationLevel::Os | OptimizationLevel::Oz => vec![
"internalize",
"globalopt",
"inline",
"globaldce",
"instcombine",
"simplifycfg",
"dce",
"tailcallelim",
],
};
let stages = stage_names
.into_iter()
.map(|n| LTOPipelineStage::new(n))
.collect();
LTOPipeline {
stages,
optimization_level: opt_level,
}
}
pub fn run(&mut self, _module: &mut Module) -> Vec<LTODiagnostic> {
let mut diags = Vec::new();
for stage in &mut self.stages {
stage.functions_modified += 1;
}
diags
}
}
#[derive(Debug, Clone)]
pub struct DeadSymbolTracker {
pub live_globals: HashSet<String>,
pub dead_globals: HashSet<String>,
pub roots: HashSet<String>,
pub references: HashMap<String, HashSet<String>>,
}
impl DeadSymbolTracker {
pub fn new() -> Self {
DeadSymbolTracker {
live_globals: HashSet::new(),
dead_globals: HashSet::new(),
roots: HashSet::new(),
references: HashMap::new(),
}
}
pub fn add_root(&mut self, name: &str) {
self.roots.insert(name.to_string());
self.live_globals.insert(name.to_string());
}
pub fn add_reference(&mut self, from: &str, to: &str) {
self.references
.entry(from.to_string())
.or_insert_with(HashSet::new)
.insert(to.to_string());
}
pub fn compute_reachable(&mut self) {
let mut worklist: Vec<String> = self.roots.iter().cloned().collect();
let mut visited: HashSet<String> = self.roots.iter().cloned().collect();
while let Some(current) = worklist.pop() {
self.live_globals.insert(current.clone());
if let Some(refs) = self.references.get(¤t) {
for r in refs {
if visited.insert(r.clone()) {
worklist.push(r.clone());
}
}
}
}
}
pub fn mark_dead(&mut self) {
for symbol in self.references.keys() {
if !self.live_globals.contains(symbol) {
self.dead_globals.insert(symbol.clone());
}
}
}
pub fn dead_count(&self) -> usize {
self.dead_globals.len()
}
pub fn live_count(&self) -> usize {
self.live_globals.len()
}
pub fn is_dead(&self, name: &str) -> bool {
self.dead_globals.contains(name)
}
pub fn clear(&mut self) {
self.live_globals.clear();
self.dead_globals.clear();
self.roots.clear();
self.references.clear();
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LTOCacheKey {
pub module_hashes: Vec<u64>,
pub opt_level: OptimizationLevel,
pub is_thin: bool,
}
#[derive(Debug, Clone)]
pub struct LTOCacheEntry {
pub data: Vec<u8>,
pub function_count: u64,
pub size_bytes: u64,
pub created_at: u64,
}
#[derive(Debug, Default)]
pub struct LTOCacheManager {
pub entries: HashMap<LTOCacheKey, LTOCacheEntry>,
pub max_entries: usize,
pub enabled: bool,
pub hits: u64,
pub misses: u64,
}
impl LTOCacheManager {
pub fn new(max_entries: usize) -> Self {
LTOCacheManager {
entries: HashMap::new(),
max_entries,
enabled: true,
hits: 0,
misses: 0,
}
}
pub fn lookup(&mut self, key: <OCacheKey) -> Option<<OCacheEntry> {
if !self.enabled {
self.misses += 1;
return None;
}
if let Some(entry) = self.entries.get(key) {
self.hits += 1;
Some(entry)
} else {
self.misses += 1;
None
}
}
pub fn store(&mut self, key: LTOCacheKey, entry: LTOCacheEntry) {
if !self.enabled {
return;
}
if self.entries.len() >= self.max_entries {
if let Some(old_key) = self.entries.keys().next().cloned() {
self.entries.remove(&old_key);
}
}
self.entries.insert(key, entry);
}
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 clear(&mut self) {
self.entries.clear();
self.hits = 0;
self.misses = 0;
}
pub fn disable(&mut self) {
self.enabled = false;
}
pub fn enable(&mut self) {
self.enabled = true;
}
}
#[derive(Debug, Clone)]
pub struct DistributedThinLTOIndex {
pub combined_index: ModuleSummaryIndex,
pub backend_jobs: Vec<ThinLTOBackendJob>,
pub indexing_complete: bool,
pub completed_jobs: u64,
pub total_jobs: u64,
}
impl DistributedThinLTOIndex {
pub fn new(summary_index: ModuleSummaryIndex) -> Self {
DistributedThinLTOIndex {
combined_index: summary_index,
backend_jobs: Vec::new(),
indexing_complete: false,
completed_jobs: 0,
total_jobs: 0,
}
}
pub fn run_indexing(&mut self, modules: &[LTOModule]) {
let mut jobs = Vec::new();
for (idx, module) in modules.iter().enumerate() {
let import_decider = ThinLTOImportDecider::default();
let hot_symbols = HashSet::new();
let imports = import_decider.decide_imports(idx, &self.combined_index, &hot_symbols);
jobs.push(ThinLTOBackendJob {
module_index: idx,
module_name: module.id.clone(),
imports,
status: BackendJobStatus::Pending,
});
}
self.backend_jobs = jobs;
self.total_jobs = self.backend_jobs.len() as u64;
self.indexing_complete = true;
}
pub fn is_complete(&self) -> bool {
self.indexing_complete && self.completed_jobs >= self.total_jobs
}
pub fn progress(&self) -> f64 {
if self.total_jobs == 0 {
if self.indexing_complete {
1.0
} else {
0.0
}
} else {
self.completed_jobs as f64 / self.total_jobs as f64
}
}
}
#[derive(Debug, Clone)]
pub struct ThinLTOBackendJob {
pub module_index: usize,
pub module_name: String,
pub imports: Vec<ThinLTOImport>,
pub status: BackendJobStatus,
}
impl ThinLTOBackendJob {
pub fn start(&mut self) {
self.status = BackendJobStatus::Running;
}
pub fn is_pending(&self) -> bool {
self.status == BackendJobStatus::Pending
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendJobStatus {
Pending,
Running,
Completed,
Failed,
}
#[derive(Debug, Clone)]
pub struct LTORemark {
pub pass_name: String,
pub function_name: String,
pub message: String,
pub successful: bool,
pub source_file: Option<String>,
pub source_line: Option<u32>,
pub hotness: Option<u64>,
}
#[derive(Debug, Default)]
pub struct LTORemarkManager {
pub remarks: Vec<LTORemark>,
pub hotness_threshold: Option<u64>,
pub pass_filter: Option<HashSet<String>>,
pub max_per_function: usize,
}
impl LTORemarkManager {
pub fn new() -> Self {
LTORemarkManager {
remarks: Vec::new(),
hotness_threshold: None,
pass_filter: None,
max_per_function: 100,
}
}
pub fn record(&mut self, remark: LTORemark) {
if let Some(threshold) = self.hotness_threshold {
if let Some(h) = remark.hotness {
if h < threshold {
return;
}
}
}
if let Some(ref filter) = self.pass_filter {
if !filter.contains(&remark.pass_name) {
return;
}
}
let func_count = self
.remarks
.iter()
.filter(|r| r.function_name == remark.function_name)
.count();
if func_count >= self.max_per_function {
return;
}
self.remarks.push(remark);
}
pub fn for_pass(&self, pass: &str) -> Vec<<ORemark> {
self.remarks
.iter()
.filter(|r| r.pass_name == pass)
.collect()
}
pub fn counts_per_pass(&self) -> HashMap<String, usize> {
let mut counts = HashMap::new();
for r in &self.remarks {
*counts.entry(r.pass_name.clone()).or_insert(0) += 1;
}
counts
}
pub fn referenced_functions(&self) -> HashSet<String> {
self.remarks
.iter()
.map(|r| r.function_name.clone())
.collect()
}
pub fn clear(&mut self) {
self.remarks.clear();
}
}
#[derive(Debug, Clone, Default)]
pub struct LTOModuleStats {
pub module_name: String,
pub functions_before: u64,
pub functions_after: u64,
pub functions_internalized: u64,
pub functions_imported: u64,
pub functions_exported: u64,
pub functions_eliminated: u64,
pub instructions_before: u64,
pub instructions_after: u64,
pub stack_frame_bytes_total: u64,
pub remarks_generated: u64,
}
impl LTOModuleStats {
pub fn new(name: &str) -> Self {
LTOModuleStats {
module_name: name.to_string(),
..Default::default()
}
}
pub fn function_reduction_pct(&self) -> f64 {
if self.functions_before == 0 {
0.0
} else {
(1.0 - self.functions_after as f64 / self.functions_before as f64) * 100.0
}
}
pub fn instruction_reduction_pct(&self) -> f64 {
if self.instructions_before == 0 {
0.0
} else {
(1.0 - self.instructions_after as f64 / self.instructions_before as f64) * 100.0
}
}
pub fn merge(&mut self, other: <OModuleStats) {
self.functions_before += other.functions_before;
self.functions_after += other.functions_after;
self.functions_internalized += other.functions_internalized;
self.functions_imported += other.functions_imported;
self.functions_exported += other.functions_exported;
self.functions_eliminated += other.functions_eliminated;
self.instructions_before += other.instructions_before;
self.instructions_after += other.instructions_after;
self.stack_frame_bytes_total += other.stack_frame_bytes_total;
self.remarks_generated += other.remarks_generated;
}
}
#[derive(Debug, Default)]
pub struct LTOLinkResolver {
pub prevailing: HashMap<String, usize>,
pub conflicts: Vec<String>,
pub resolved: HashSet<String>,
}
impl LTOLinkResolver {
pub fn new() -> Self {
LTOLinkResolver::default()
}
pub fn register_definition(&mut self, symbol: &str, module_index: usize) -> SymbolResolution {
if let Some(&existing) = self.prevailing.get(symbol) {
if existing != module_index {
self.conflicts.push(symbol.to_string());
SymbolResolution::NonPrevailing
} else {
SymbolResolution::Prevailing
}
} else {
self.prevailing.insert(symbol.to_string(), module_index);
self.resolved.insert(symbol.to_string());
SymbolResolution::Prevailing
}
}
pub fn get_prevailing(&self, symbol: &str) -> Option<usize> {
self.prevailing.get(symbol).copied()
}
pub fn has_conflict(&self, symbol: &str) -> bool {
self.conflicts.contains(&symbol.to_string())
}
pub fn conflict_count(&self) -> usize {
self.conflicts.len()
}
}
#[derive(Debug, Clone)]
pub struct LTOMergeTracker {
pub merge_order: Vec<String>,
pub function_origins: HashMap<String, usize>,
pub global_origins: HashMap<String, usize>,
pub type_conflicts_resolved: u64,
pub metadata_merged: u64,
}
impl LTOMergeTracker {
pub fn new() -> Self {
LTOMergeTracker {
merge_order: Vec::new(),
function_origins: HashMap::new(),
global_origins: HashMap::new(),
type_conflicts_resolved: 0,
metadata_merged: 0,
}
}
pub fn record_merge(&mut self, module_name: &str) {
self.merge_order.push(module_name.to_string());
}
pub fn record_function_origin(&mut self, func_name: &str, module_index: usize) {
self.function_origins
.insert(func_name.to_string(), module_index);
}
pub fn get_function_origin(&self, func_name: &str) -> Option<usize> {
self.function_origins.get(func_name).copied()
}
pub fn merged_module_count(&self) -> usize {
self.merge_order.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::basic_block::new_basic_block;
use llvm_native_core::function::new_function;
use llvm_native_core::instruction::ret_void;
use llvm_native_core::types::Type;
fn build_module(name: &str, func_name: &str) -> Module {
let mut m = Module::new(name);
m.set_target_triple("x86_64-unknown-linux-gnu");
let func = new_function(func_name, Type::void(), &[]);
let entry = new_basic_block("entry");
let ret = ret_void();
entry.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry.clone());
m.add_function(func);
m
}
#[test]
fn test_global_summary_create() {
let summary = GlobalSummary {
name: "foo".into(),
module_index: 0,
is_function: true,
instruction_count: 42,
can_import: true,
refs: vec!["bar".into()],
calls: vec!["baz".into()],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
};
assert_eq!(summary.name, "foo");
assert_eq!(summary.instruction_count, 42);
assert!(summary.can_import);
}
#[test]
fn test_summary_index_add_module() {
let mut index = ModuleSummaryIndex::new();
let summaries = vec![GlobalSummary {
name: "foo".into(),
module_index: 0,
is_function: true,
instruction_count: 10,
can_import: true,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
}];
let idx = index.add_module(summaries);
assert_eq!(idx, 0);
assert!(index.global_map.contains_key("foo"));
}
#[test]
fn test_summary_index_resolve() {
let mut index = ModuleSummaryIndex::new();
index.add_module(vec![GlobalSummary {
name: "foo".into(),
module_index: 0,
is_function: true,
instruction_count: 10,
can_import: true,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
}]);
index.add_module(vec![GlobalSummary {
name: "foo".into(),
module_index: 1,
is_function: true,
instruction_count: 20,
can_import: true,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
}]);
let preserved: HashSet<String> = ["foo".into()].iter().cloned().collect();
index.resolve(&preserved);
assert!(index.prevailing.contains("foo"));
}
#[test]
fn test_summary_index_get_instruction_count() {
let mut index = ModuleSummaryIndex::new();
index.add_module(vec![GlobalSummary {
name: "bar".into(),
module_index: 0,
is_function: true,
instruction_count: 30,
can_import: true,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
}]);
let preserved: HashSet<String> = ["bar".into()].iter().cloned().collect();
index.resolve(&preserved);
let count = index.get_instruction_count("bar");
assert_eq!(count, Some(30));
}
#[test]
fn test_lto_module_create() {
let m = build_module("mod1", "func1");
let lto = LTOModule::new(m, "mod1.o");
assert_eq!(lto.id, "mod1.o");
assert!(!lto.summaries.is_empty());
assert!(lto.summaries.iter().any(|s| s.name == "func1"));
}
#[test]
fn test_lto_module_compute_preserved() {
let m = build_module("mod1", "exported_fn");
let lto = LTOModule::new(m, "mod1.o");
assert!(lto.preserved_symbols.contains("exported_fn"));
}
#[test]
fn test_lto_module_optimize() {
let m = build_module("mod1", "opt_fn");
let mut lto = LTOModule::new(m, "mod1.o");
let removed = lto.optimize();
assert_eq!(lto.optimized, true);
let _ = removed;
}
#[test]
fn test_combined_module_create() {
let m1 = build_module("mod1", "func1");
let m2 = build_module("mod2", "func2");
let lto1 = LTOModule::new(m1, "mod1.o");
let lto2 = LTOModule::new(m2, "mod2.o");
let combined = CombinedLTOModule::new(&[lto1, lto2]);
assert_eq!(combined.sources.len(), 2);
assert!(combined.module.functions.len() >= 2);
}
#[test]
fn test_combined_module_internalize() {
let m1 = build_module("mod1", "keep_me");
let lto1 = LTOModule::new(m1, "mod1.o");
let mut combined = CombinedLTOModule::new(&[lto1]);
let preserved: HashSet<String> = ["keep_me".into()].iter().cloned().collect();
let internalized = combined.internalize(&preserved);
assert!(combined
.module
.functions
.iter()
.any(|f| f.borrow().name == "keep_me"));
let _ = internalized;
}
#[test]
fn test_import_decider_creates() {
let decider = ThinLTOImportDecider::new();
assert_eq!(decider.inline_threshold, 100);
assert_eq!(decider.max_imports, 256);
}
#[test]
fn test_import_decider_small_function_candidate() {
let decider = ThinLTOImportDecider::new();
let mut index = ModuleSummaryIndex::new();
index.add_module(vec![GlobalSummary {
name: "small_fn".into(),
module_index: 0,
is_function: true,
instruction_count: 5,
can_import: true,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
}]);
index.add_module(vec![]);
let preserved: HashSet<String> = ["small_fn".into()].iter().cloned().collect();
index.resolve(&preserved);
let hot: HashSet<String> = HashSet::new();
let imports = decider.decide_imports(1, &index, &hot);
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].symbol, "small_fn");
assert_eq!(imports[0].reason, ImportReason::InlineCandidate);
}
#[test]
fn test_import_decider_hot_symbol() {
let decider = ThinLTOImportDecider::new();
let mut index = ModuleSummaryIndex::new();
index.add_module(vec![GlobalSummary {
name: "large_fn".into(),
module_index: 0,
is_function: true,
instruction_count: 500,
can_import: true,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
}]);
index.add_module(vec![]);
let preserved: HashSet<String> = ["large_fn".into()].iter().cloned().collect();
index.resolve(&preserved);
let hot: HashSet<String> = ["large_fn".into()].iter().cloned().collect();
let imports = decider.decide_imports(1, &index, &hot);
assert!(imports.iter().any(|i| i.symbol == "large_fn"));
}
#[test]
fn test_import_decider_estimate_benefit() {
let decider = ThinLTOImportDecider::new();
let summary = GlobalSummary {
name: "test".into(),
module_index: 0,
is_function: true,
instruction_count: 50,
can_import: true,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
};
let benefit = decider.estimate_import_benefit(&summary);
assert!(benefit > 0);
}
#[test]
fn test_import_decider_zero_inst_no_benefit() {
let decider = ThinLTOImportDecider::new();
let summary = GlobalSummary {
name: "decl".into(),
module_index: 0,
is_function: true,
instruction_count: 0,
can_import: false,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
};
let benefit = decider.estimate_import_benefit(&summary);
assert_eq!(benefit, 0);
}
#[test]
fn test_lto_codegen_create() {
let gen = LTOCodeGenerator::new(LTOMode::Full);
assert_eq!(gen.mode, LTOMode::Full);
assert!(gen.modules.is_empty());
}
#[test]
fn test_lto_codegen_add_module() {
let mut gen = LTOCodeGenerator::new(LTOMode::Thin);
let m = build_module("mod1", "func1");
let lto = LTOModule::new(m, "mod1.o");
gen.add_module(lto);
assert_eq!(gen.modules.len(), 1);
assert!(gen.summary_index.global_map.contains_key("func1"));
}
#[test]
fn test_lto_codegen_full_lto() {
let mut gen = LTOCodeGenerator::new(LTOMode::Full);
let m1 = build_module("mod1", "func1");
let m2 = build_module("mod2", "func2");
gen.add_module(LTOModule::new(m1, "mod1.o"));
gen.add_module(LTOModule::new(m2, "mod2.o"));
let preserved: HashSet<String> = ["func1", "func2"].iter().map(|s| s.to_string()).collect();
let result = gen.run(&preserved);
assert!(result.is_ok());
assert!(gen.combined.is_some());
}
#[test]
fn test_lto_codegen_thin_lto() {
let mut gen = LTOCodeGenerator::new(LTOMode::Thin);
let m1 = build_module("mod1", "func1");
let m2 = build_module("mod2", "func2");
gen.add_module(LTOModule::new(m1, "mod1.o"));
gen.add_module(LTOModule::new(m2, "mod2.o"));
let preserved: HashSet<String> = ["func1", "func2"].iter().map(|s| s.to_string()).collect();
let result = gen.run(&preserved);
assert!(result.is_ok());
assert!(gen.combined.is_none());
}
#[test]
fn test_lto_codegen_stats() {
let mut gen = LTOCodeGenerator::new(LTOMode::Full);
let m1 = build_module("mod1", "func1");
gen.add_module(LTOModule::new(m1, "mod1.o"));
let preserved: HashSet<String> = ["func1"].iter().map(|s| s.to_string()).collect();
let _ = gen.run(&preserved);
let stats = gen.stats();
assert_eq!(stats.mode, LTOMode::Full);
assert_eq!(stats.num_modules, 1);
assert_eq!(stats.num_functions, 1);
}
#[test]
fn test_lto_codegen_empty_modules_error() {
let mut gen = LTOCodeGenerator::new(LTOMode::Full);
let preserved: HashSet<String> = HashSet::new();
let result = gen.run(&preserved);
assert!(result.is_err());
}
#[test]
fn test_lto_codegen_output_modules_full() {
let mut gen = LTOCodeGenerator::new(LTOMode::Full);
let m1 = build_module("mod1", "func1");
gen.add_module(LTOModule::new(m1, "mod1.o"));
let preserved: HashSet<String> = ["func1"].iter().map(|s| s.to_string()).collect();
let _ = gen.run(&preserved);
let outputs = gen.output_modules();
assert_eq!(outputs.len(), 1);
}
#[test]
fn test_lto_codegen_output_modules_thin() {
let mut gen = LTOCodeGenerator::new(LTOMode::Thin);
let m1 = build_module("mod1", "func1");
let m2 = build_module("mod2", "func2");
gen.add_module(LTOModule::new(m1, "mod1.o"));
gen.add_module(LTOModule::new(m2, "mod2.o"));
let preserved: HashSet<String> = ["func1", "func2"].iter().map(|s| s.to_string()).collect();
let _ = gen.run(&preserved);
let outputs = gen.output_modules();
assert_eq!(outputs.len(), 2);
}
#[test]
fn test_lto_symbol_resolution_prevailing() {
let mut index = ModuleSummaryIndex::new();
index.add_module(vec![GlobalSummary {
name: "main".into(),
module_index: 0,
is_function: true,
instruction_count: 5,
can_import: true,
refs: vec![],
calls: vec!["helper".into()],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
}]);
index.add_module(vec![GlobalSummary {
name: "helper".into(),
module_index: 1,
is_function: true,
instruction_count: 3,
can_import: true,
refs: vec![],
calls: vec![],
readonly_refs: vec![],
resolution: SymbolResolution::Prevailing,
}]);
let preserved: HashSet<String> = ["main", "helper"].iter().map(|s| s.to_string()).collect();
index.resolve(&preserved);
assert!(index.prevailing.contains("main"));
assert!(index.prevailing.contains("helper"));
}
#[test]
fn test_lto_pipeline_end_to_end() {
let mut gen = LTOCodeGenerator::new(LTOMode::Full);
for i in 0..3 {
let module_name = format!("mod{}", i);
let func_name = format!("func{}", i);
let m = build_module(&module_name, &func_name);
gen.add_module(LTOModule::new(m, &format!("{}.o", module_name)));
}
let preserved: HashSet<String> = (0..3).map(|i| format!("func{}", i)).collect();
let result = gen.run(&preserved);
assert!(result.is_ok());
let stats = gen.stats();
assert_eq!(stats.num_modules, 3);
assert_eq!(stats.num_functions, 3);
let outputs = gen.output_modules();
assert_eq!(outputs.len(), 1);
assert_eq!(outputs[0].functions.len(), 3);
}
#[test]
fn test_lto_config_default() {
let config = LTOConfig::default();
assert!(!config.use_thin_lto);
assert_eq!(config.thin_lto_threads, 4);
assert!(config.internalize);
assert_eq!(config.import_instr_limit, 100);
assert_eq!(config.cg_opt_level, 2);
}
#[test]
fn test_lto_config_thin_lto() {
let config = LTOConfig {
use_thin_lto: true,
..LTOConfig::default()
};
assert!(config.use_thin_lto);
}
#[test]
fn test_optimization_level_equality() {
assert_eq!(OptimizationLevel::O2, OptimizationLevel::O2);
assert_ne!(OptimizationLevel::O0, OptimizationLevel::O3);
}
#[test]
fn test_lto_pipeline_create() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
assert_eq!(lto.modules.len(), 0);
assert_eq!(lto.opt_level, OptimizationLevel::O2);
assert_eq!(lto.stats.num_modules, 0);
}
#[test]
fn test_lto_pipeline_add_module() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("mod1", "func1");
lto.add_module(m);
assert_eq!(lto.modules.len(), 1);
assert_eq!(lto.stats.num_modules, 1);
}
#[test]
fn test_lto_pipeline_empty_modules_error() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
let result = lto.run();
assert!(result.is_err());
}
#[test]
fn test_compute_guid_deterministic() {
let g1 = LTO::compute_guid("my_function");
let g2 = LTO::compute_guid("my_function");
assert_eq!(g1, g2);
}
#[test]
fn test_compute_guid_different_names() {
let g1 = LTO::compute_guid("func_a");
let g2 = LTO::compute_guid("func_b");
assert_ne!(g1, g2);
}
#[test]
fn test_compute_guid_is_nonzero() {
let g = LTO::compute_guid("test");
assert_ne!(g, 0);
}
#[test]
fn test_build_module_summary() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("test_mod", "my_func");
lto.add_module(m);
lto.build_summaries();
assert!(!lto.summary.module_summaries.is_empty());
let mod_sum = <o.summary.module_summaries[0];
assert_eq!(mod_sum.module_name, "test_mod");
assert!(mod_sum.function_names.contains(&"my_func".to_string()));
assert!(!mod_sum.module_hash.is_empty());
}
#[test]
fn test_build_summary_multiple_modules() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod1", "f1"));
lto.add_module(build_module("mod2", "f2"));
lto.build_summaries();
assert_eq!(lto.summary.module_summaries.len(), 2);
}
#[test]
fn test_summarize_function_basic() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("mod", "target_fn");
let func = &m.functions[0];
let summary = lto.summarize_function(func);
assert_eq!(summary.name, "target_fn");
assert!(summary.is_function);
assert!(summary.instruction_count > 0);
assert_eq!(summary.linkage, GlobalLinkage::External);
}
#[test]
fn test_summarize_function_declaration() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let mut m = Module::new("mod");
let func = new_function("decl_fn", Type::void(), &[]);
m.add_function(func);
let summary = lto.summarize_function(&m.functions[0]);
assert_eq!(summary.instruction_count, 0);
assert!(!summary.is_import_eligible);
}
#[test]
fn test_summarize_global_variable() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let gv = llvm_native_core::value::Value::new(Type::i32())
.named("my_global")
.with_subclass(SubclassKind::GlobalVariable);
let gv_ref = llvm_native_core::value::valref(gv);
let summary = lto.summarize_global(&gv_ref);
assert_eq!(summary.name, "my_global");
assert!(!summary.is_function);
assert!(!summary.is_import_eligible);
}
#[test]
fn test_summarize_function_import_eligible() {
let config = LTOConfig {
import_instr_limit: 100,
..LTOConfig::default()
};
let lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("mod", "small_fn");
let summary = lto.summarize_function(&m.functions[0]);
assert!(summary.is_import_eligible);
assert!(summary.guid != 0);
}
#[test]
fn test_build_call_graph_empty() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.build_summaries();
let cg = lto.build_call_graph();
assert!(cg.is_empty());
}
#[test]
fn test_build_call_graph_with_functions() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod", "caller"));
lto.build_summaries();
let cg = lto.build_call_graph();
assert!(cg.contains_key("caller"));
}
#[test]
fn test_compute_call_graph_for_func_empty() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("mod", "empty_caller");
let func = m.functions[0].borrow();
let callees = lto.compute_call_graph_for_func(&func);
assert!(callees.is_empty());
}
#[test]
fn test_should_internalize_exported() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let exported: HashSet<String> = ["keep_me".into()].iter().cloned().collect();
assert!(!lto.should_internalize("keep_me", GlobalLinkage::External, &exported));
}
#[test]
fn test_should_internalize_non_exported() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let exported: HashSet<String> = HashSet::new();
assert!(lto.should_internalize("hidden_fn", GlobalLinkage::External, &exported));
}
#[test]
fn test_should_not_internalize_llvm_intrinsic() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let exported: HashSet<String> = HashSet::new();
assert!(!lto.should_internalize("llvm.memcpy", GlobalLinkage::External, &exported));
}
#[test]
fn test_should_internalize_linkonce_odr() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let exported: HashSet<String> = HashSet::new();
assert!(lto.should_internalize("odr_fn", GlobalLinkage::LinkOnceODR, &exported));
}
#[test]
fn test_find_exported_symbols() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod1", "exported_fn"));
let exported = lto.find_exported_symbols();
assert!(exported.contains("exported_fn"));
}
#[test]
fn test_internalize_reduces_count() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod", "to_internalize"));
let exported: HashSet<String> = HashSet::new(); lto.internalize(&exported);
let remaining = lto.modules[0].functions.len();
assert!(remaining <= 1);
}
#[test]
fn test_find_used_globals_entry_point() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("mod", "main");
let used = lto.find_used_globals(&m);
assert!(used.contains("main"));
}
#[test]
fn test_eliminate_dead_globals() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
let mut m = build_module("mod", "main");
let dead_gv = llvm_native_core::value::Value::new(Type::i32())
.named("dead_global")
.with_subclass(SubclassKind::GlobalVariable);
m.globals.push(llvm_native_core::value::valref(dead_gv));
lto.add_module(m);
let before_globals: usize = lto.modules.iter().map(|m| m.globals.len()).sum();
lto.eliminate_dead_globals();
let after_globals: usize = lto.modules.iter().map(|m| m.globals.len()).sum();
assert!(after_globals <= before_globals);
}
#[test]
fn test_full_lto_pipeline_end_to_end() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
for i in 0..3 {
let module_name = format!("mod{}", i);
let func_name = format!("func{}", i);
lto.add_module(build_module(&module_name, &func_name));
}
let result = lto.run();
assert!(result.is_ok());
let merged = result.unwrap();
assert!(merged.functions.len() >= 1);
assert_eq!(lto.stats.modules_processed, 3);
assert!(lto.stats.total_instructions_before > 0);
}
#[test]
fn test_full_lto_pipeline_single_module() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("only", "only_func"));
let result = lto.run();
assert!(result.is_ok());
let merged = result.unwrap();
assert!(merged
.functions
.iter()
.any(|f| f.borrow().name == "only_func"));
}
#[test]
fn test_full_lto_internalizes_non_exported() {
let config = LTOConfig {
internalize: true,
..LTOConfig::default()
};
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod", "local_fn"));
lto.add_module(build_module("mod2", "exported_fn"));
let result = lto.run();
assert!(result.is_ok());
assert!(lto.stats.modules_processed > 0);
}
#[test]
fn test_thin_lto_pipeline_builds_summaries() {
let config = LTOConfig {
use_thin_lto: true,
..LTOConfig::default()
};
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod1", "f1"));
lto.add_module(build_module("mod2", "f2"));
lto.build_summaries();
assert_eq!(lto.summary.module_summaries.len(), 2);
assert!(lto.summary.global_summaries.contains_key("f1"));
assert!(lto.summary.global_summaries.contains_key("f2"));
}
#[test]
fn test_thin_lto_end_to_end() {
let config = LTOConfig {
use_thin_lto: true,
..LTOConfig::default()
};
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod1", "f1"));
lto.add_module(build_module("mod2", "f2"));
let result = lto.run();
assert!(result.is_ok());
let merged = result.unwrap();
assert!(!merged.functions.is_empty());
assert!(lto.stats.modules_processed > 0);
}
#[test]
fn test_compute_import_decisions() {
let config = LTOConfig {
use_thin_lto: true,
import_instr_limit: 100,
..LTOConfig::default()
};
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod1", "small_fn"));
lto.add_module(build_module("mod2", "caller"));
lto.build_summaries();
let imports = lto.compute_import_decisions();
assert!(lto.stats.functions_imported == imports.len());
}
#[test]
fn test_should_import_small_function() {
let config = LTOConfig {
import_instr_limit: 100,
..LTOConfig::default()
};
let lto = LTO::new(config, OptimizationLevel::O2);
let summary = GlobalValueSummary {
name: "tiny".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 42,
callees: vec![],
is_import_eligible: true,
instruction_count: 5,
entry_count: 0,
};
let m = Module::new("caller");
let result = lto.should_import(&summary, &m);
assert!(result);
}
#[test]
fn test_should_not_import_large_function() {
let config = LTOConfig {
import_instr_limit: 10,
..LTOConfig::default()
};
let lto = LTO::new(config, OptimizationLevel::O2);
let summary = GlobalValueSummary {
name: "huge".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 99,
callees: vec![],
is_import_eligible: false, instruction_count: 500,
entry_count: 0,
};
let m = Module::new("caller");
assert!(!lto.should_import(&summary, &m));
}
#[test]
fn test_compute_import_cost() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let summary = GlobalValueSummary {
name: "f".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 1,
callees: vec![],
is_import_eligible: true,
instruction_count: 42,
entry_count: 0,
};
assert_eq!(lto.compute_import_cost(&summary), 42);
}
#[test]
fn test_compute_import_benefit_zero_inst() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let summary = GlobalValueSummary {
name: "empty".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 1,
callees: vec![],
is_import_eligible: false,
instruction_count: 0,
entry_count: 0,
};
assert_eq!(lto.compute_import_benefit(&summary), 0);
}
#[test]
fn test_compute_import_benefit_small_fn() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let summary = GlobalValueSummary {
name: "small".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 2,
callees: vec![],
is_import_eligible: true,
instruction_count: 10,
entry_count: 0,
};
let benefit = lto.compute_import_benefit(&summary);
assert_eq!(benefit, 10);
}
#[test]
fn test_merge_modules_basic() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod1", "f1"));
lto.add_module(build_module("mod2", "f2"));
let result = lto.merge_modules();
assert!(result.is_ok());
let merged = result.unwrap();
assert!(merged.functions.len() >= 2);
}
#[test]
fn test_merge_modules_empty() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
let result = lto.merge_modules();
assert!(result.is_err());
}
#[test]
fn test_lto_stats_initialization() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
assert_eq!(lto.stats.functions_internalized, 0);
assert_eq!(lto.stats.functions_imported, 0);
assert_eq!(lto.stats.globals_eliminated, 0);
assert_eq!(lto.stats.functions_merged, 0);
assert_eq!(lto.stats.total_instructions_before, 0);
assert_eq!(lto.stats.total_instructions_after, 0);
}
#[test]
fn test_lto_stats_after_full_run() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod1", "f1"));
lto.add_module(build_module("mod2", "f2"));
let _ = lto.run();
assert_eq!(lto.stats.modules_processed, 2);
assert!(lto.stats.total_instructions_before > 0);
assert!(lto.stats.total_instructions_after > 0);
}
#[test]
fn test_lto_empty_module_list() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
let result = lto.run();
assert!(result.is_err());
assert!(result.unwrap_err().contains("No modules"));
}
#[test]
fn test_lto_single_module() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("only", "solo"));
let result = lto.run();
assert!(result.is_ok());
}
#[test]
fn test_lto_all_internal_symbols() {
let config = LTOConfig {
internalize: true,
..LTOConfig::default()
};
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod", "internal1"));
lto.add_module(build_module("mod2", "internal2"));
let result = lto.run();
assert!(result.is_ok());
assert!(lto.stats.modules_processed > 0);
}
#[test]
fn test_lto_summary_new() {
let summary = LTOSummary::new();
assert!(summary.module_summaries.is_empty());
assert!(summary.global_summaries.is_empty());
assert!(summary.call_graph.is_empty());
}
#[test]
fn test_lto_summary_get_global() {
let mut summary = LTOSummary::new();
let gvs = GlobalValueSummary {
name: "test".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 123,
callees: vec![],
is_import_eligible: true,
instruction_count: 10,
entry_count: 0,
};
summary.global_summaries.insert("test".into(), gvs);
assert!(summary.get_global("test").is_some());
assert!(summary.get_global("nonexistent").is_none());
}
#[test]
fn test_lto_summary_can_import() {
let mut summary = LTOSummary::new();
let gvs = GlobalValueSummary {
name: "importable".into(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 456,
callees: vec![],
is_import_eligible: true,
instruction_count: 5,
entry_count: 0,
};
summary.global_summaries.insert("importable".into(), gvs);
assert!(summary.can_import("importable"));
assert!(!summary.can_import("nonexistent"));
}
#[test]
fn test_lto_summary_default() {
let summary = LTOSummary::default();
assert!(summary.module_summaries.is_empty());
}
#[test]
fn test_type_id_summary_create() {
let tid_summary = TypeIdSummary {
type_id: "_ZTV3Foo".into(),
vtable_defs: vec!["mod1".into(), "mod2".into()],
};
assert_eq!(tid_summary.type_id, "_ZTV3Foo");
assert_eq!(tid_summary.vtable_defs.len(), 2);
}
#[test]
fn test_lto_config_custom() {
let config = LTOConfig {
use_thin_lto: true,
thin_lto_threads: 8,
internalize: false,
import_instr_limit: 200,
dge_threshold: 5,
cg_opt_level: 3,
save_temps: true,
};
assert!(config.use_thin_lto);
assert_eq!(config.thin_lto_threads, 8);
assert!(!config.internalize);
assert_eq!(config.import_instr_limit, 200);
assert!(config.save_temps);
}
#[test]
fn test_internalize_function_noop() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("m", "f");
lto.internalize_function(&m.functions[0]);
}
#[test]
fn test_mark_live_from_root_simple() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("mod", "root");
let mut live = HashSet::new();
live.insert("root".to_string());
lto.mark_live_from_root(&m, "root", &mut live);
assert!(live.contains("root"));
}
#[test]
fn test_mark_live_from_root_nonexistent() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let m = build_module("mod", "real_fn");
let mut live = HashSet::new();
lto.mark_live_from_root(&m, "no_such_fn", &mut live);
assert!(live.is_empty());
}
#[test]
fn test_run_thin_lto_backend() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let mut m = build_module("mod", "target");
lto.run_thin_lto_backend(&mut m);
assert!(m.functions.iter().any(|f| f.borrow().name == "target"));
}
#[test]
fn test_run_optimization_passes_o2() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
let mut m = build_module("mod", "opt_me");
lto.run_optimization_passes(&mut m);
assert!(!m.functions.is_empty());
}
#[test]
fn test_run_optimization_passes_o3() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O3);
let mut m = build_module("mod", "opt_me_o3");
lto.run_optimization_passes(&mut m);
assert!(!m.functions.is_empty());
}
#[test]
fn test_count_all_instructions_zero_for_empty() {
let config = LTOConfig::default();
let lto = LTO::new(config, OptimizationLevel::O2);
assert_eq!(lto.count_all_instructions(), 0);
}
#[test]
fn test_count_all_instructions_nonzero() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod", "f"));
lto.build_summaries();
assert!(lto.count_all_instructions() > 0);
}
#[test]
fn test_apply_imported_functions_empty() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod", "f"));
let imports: Vec<ThinLTOImport> = vec![];
lto.apply_imported_functions(&imports);
}
#[test]
fn test_apply_imported_functions_with_imports() {
let config = LTOConfig::default();
let mut lto = LTO::new(config, OptimizationLevel::O2);
lto.add_module(build_module("mod1", "source_fn"));
lto.add_module(build_module("mod2", "dest_fn"));
let imports = vec![ThinLTOImport {
symbol: "source_fn".into(),
from_module: 0,
reason: ImportReason::InlineCandidate,
}];
lto.apply_imported_functions(&imports);
}
}