pub const THINLTO_MAX_PARTITIONS: usize = 256;
pub const THINLTO_HOTNESS_THRESHOLD: u8 = 200;
pub const THINLTO_MAX_IMPORT_DEPTH: u32 = 5;
pub const THINLTO_MAX_IMPORTS_PER_MODULE: usize = 5000;
pub const THINLTO_CACHE_TTL_SECS: u64 = 3600;
pub const THINLTO_MAX_CACHE_FILE_SIZE: u64 = 1_073_741_824;
pub const THINLTO_SUMMARY_VERSION: u32 = 8;
pub const THINLTO_SUMMARY_MAGIC: [u8; 4] = [0x54, 0x4C, 0x54, 0x4F];
pub const PGO_HASH_SEED: u64 = 0xC6A4_A793_5BD1_E995;
pub const MAX_TYPE_UNITS: usize = 65536;
pub const DWARF_SKELETON_CU_VERSION: u16 = 5;
pub const DEVIRT_MAX_VTABLE_ENTRIES: usize = 1024;
pub const COFF_LTO_OPT_FLAG: u32 = 0x0000_0001;
pub const MACHO_LTO_LIBRARY_DEFAULT: &str = "libLTO.dylib";
pub const LTO_API_VERSION: u32 = 29;
pub const MAX_EXPORT_SYMBOLS: usize = 1_000_000;
pub const FNV64_OFFSET_BASIS: u64 = 0xCBF2_9CE4_8422_2325;
pub const FNV64_PRIME: u64 = 0x0000_0100_0000_01B3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThinLTOImportReason {
HotCall,
ColdCall,
Transitive,
GlobalRef,
Devirtualization,
ProfileGuided,
AlwaysImport,
NotImported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThinLTOCacheMode {
Disabled,
HashBased,
ComprehensiveKey,
Incremental,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ImportDecision {
Import {
name: String,
reason: ThinLTOImportReason,
hotness: u8,
guid: u64,
},
Skip {
name: String,
reason: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InternalizationDecision {
KeepGlobal,
Internalize,
Hidden,
Discard,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComdatSelectionKind {
Any,
ExactMatch,
Largest,
NoDuplicates,
SameSize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LTODebugMergeMode {
Full,
Skeleton,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeUnitDedupStrategy {
HashBased,
Structural,
NameBased,
Combined,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DevirtResult {
Devirtualized {
vtable_name: String,
target_function: String,
offset: u64,
},
NotDevirtualized { reason: String },
AbstractClass,
NotVirtual,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlobalOptKind {
DemoteToLocal,
ConstantProp,
DeadElimination,
GlobalMerge,
Shrink,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LTOPluginStatus {
Ready,
Processing,
Completed,
Error(LTOPluginErrorKind),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LTOPluginErrorKind {
InvalidBitcode,
TargetMismatch,
OptimizationFailed,
CodegenFailed,
CacheError,
InternalError,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClaimResult {
Claimed,
NotClaimed,
Skip,
}
#[derive(Debug, Clone)]
pub struct ModuleSummaryIndex {
pub module_hash: u64,
pub module_path: String,
pub target_triple: String,
pub function_summaries: Vec<FunctionSummary>,
pub global_summaries: Vec<GlobalVarSummary>,
pub vtable_summaries: Vec<VTableSummary>,
pub alias_summaries: Vec<AliasSummary>,
pub instruction_count: u64,
pub is_thin_lto: bool,
pub pgo_profile_hash: Option<u64>,
pub flags: ModuleSummaryFlags,
}
#[derive(Debug, Clone)]
pub struct FunctionSummary {
pub name: String,
pub guid: u64,
pub inst_count: u32,
pub call_count: u32,
pub callees: Vec<u64>,
pub hotness: u8,
pub has_inline_asm: bool,
pub has_varargs: bool,
pub is_external: bool,
pub is_entry_point: bool,
pub is_local: bool,
pub param_count: u8,
pub return_type: ReturnTypeClass,
pub call_profile: Vec<(u64, u64)>,
pub cfi_enabled: bool,
}
#[derive(Debug, Clone)]
pub struct GlobalVarSummary {
pub name: String,
pub guid: u64,
pub size: u64,
pub alignment: u32,
pub is_constant: bool,
pub is_read_only: bool,
pub is_external: bool,
pub refs: Vec<u64>,
pub init_hash: u64,
pub is_tls: bool,
pub linkage: GlobalLinkageKind,
}
#[derive(Debug, Clone)]
pub struct VTableSummary {
pub name: String,
pub guid: u64,
pub entries: Vec<u64>,
pub class_name: String,
pub has_subclasses: bool,
pub hierarchy_depth: u32,
}
#[derive(Debug, Clone)]
pub struct AliasSummary {
pub name: String,
pub guid: u64,
pub aliasee_guid: u64,
pub aliasee_is_function: bool,
pub visibility: SymbolVisibility,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlobalLinkageKind {
External,
Internal,
LinkonceODR,
WeakODR,
Common,
Appending,
ExternalWeak,
AvailableExternally,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReturnTypeClass {
Void,
Integer,
Float,
Pointer,
StructSRet,
StructRegisters,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ModuleSummaryFlags {
pub function_sections: bool,
pub data_sections: bool,
pub sanitizer_coverage: bool,
pub lto_compiled: bool,
pub cfi_enabled: bool,
pub safestack: bool,
pub has_profile: bool,
pub has_eh: bool,
pub is_pic: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolVisibility {
Default,
Hidden,
Protected,
Internal,
}
pub struct X86LLDFeatures {
pub thin_lto: X86ThinLTOFull,
pub internalization: X86LTOInternalization,
pub debug_info: X86LTODebugInfo,
pub whole_program: X86LTOWholeProgram,
pub plugin: X86LTOPlugin,
pub combined_index: Option<CombinedIndex>,
pub enabled: bool,
pub target_triple: String,
pub output_path: String,
pub opt_level: u8,
pub num_threads: u32,
pub diagnostics: Vec<LTOFeatureDiagnostic>,
}
#[derive(Debug, Clone)]
pub struct CombinedIndex {
pub modules: Vec<ModuleSummaryIndex>,
pub function_to_module: std::collections::HashMap<u64, usize>,
pub global_to_module: std::collections::HashMap<u64, usize>,
pub vtable_to_module: std::collections::HashMap<u64, usize>,
pub alias_to_module: std::collections::HashMap<u64, usize>,
pub call_graph: std::collections::HashMap<u64, Vec<u64>>,
pub reverse_call_graph: std::collections::HashMap<u64, Vec<u64>>,
pub export_guids: std::collections::HashSet<u64>,
pub total_functions: usize,
pub total_globals: usize,
}
#[derive(Debug, Clone)]
pub struct LTOFeatureDiagnostic {
pub level: LTOFeatureDiagLevel,
pub message: String,
pub source: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LTOFeatureDiagLevel {
Info,
Warning,
Error,
Debug,
}
#[derive(Debug, Clone)]
pub struct SymbolResolutionEntry {
pub name: String,
pub guid: u64,
pub decision: InternalizationDecision,
pub visibility: SymbolVisibility,
pub is_exported: bool,
pub is_imported: bool,
pub is_dllexport: bool,
pub comdat_leader: Option<u64>,
pub external_refs: u32,
pub survives: bool,
}
#[derive(Debug, Clone)]
pub struct PGOProfileEntry {
pub guid: u64,
pub entry_count: u64,
pub total_frequency: u64,
pub max_frequency: u64,
}
#[derive(Debug, Clone)]
pub struct DwarfTypeUnit {
pub type_signature: u64,
pub offset: u64,
pub length: u32,
pub module_hash: u64,
pub type_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SkeletonCompileUnit {
pub module_hash: u64,
pub object_path: String,
pub comp_dir: String,
pub dwo_name: Option<String>,
pub dwarf_version: u16,
pub address_size: u8,
}
#[derive(Debug, Clone)]
pub struct CrossModuleLineEntry {
pub file: String,
pub line: u32,
pub column: u32,
pub address: u64,
pub module_index: usize,
pub inline_depth: u32,
}
#[derive(Debug, Clone)]
pub struct AliasAnalysisResult {
pub may_alias: bool,
pub no_alias: bool,
pub must_alias: bool,
pub partial_alias: bool,
pub alias_set_id: u64,
}
#[derive(Debug, Clone)]
pub struct DeadGlobalResult {
pub name: String,
pub guid: u64,
pub size_freed: u64,
pub reason: DeadGlobalReason,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeadGlobalReason {
Unreferenced,
ConstantFolded,
Replaced,
Merged,
}
#[derive(Debug, Clone)]
pub struct ConstantPropagationRecord {
pub global_guid: u64,
pub function_guid: u64,
pub instruction_offset: u64,
pub constant_value: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct PluginClaimRequest {
pub file_path: String,
pub file_data: Vec<u8>,
pub file_descriptor: Option<i32>,
pub file_size: u64,
pub symbol_table_offset: u64,
pub symbol_count: u32,
}
#[derive(Debug, Clone)]
pub struct PluginSymbolInfo {
pub name: String,
pub section_kind: PluginSectionKind,
pub visibility: SymbolVisibility,
pub is_global: bool,
pub is_definition: bool,
pub is_common: bool,
pub is_weak: bool,
pub size: u64,
pub alignment: u32,
pub comdat_key: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginSectionKind {
Text,
Data,
BSS,
ReadOnly,
TLS,
Other,
}
pub struct X86ThinLTOFull {
pub module_indices: Vec<ModuleSummaryIndex>,
pub combined_index: Option<CombinedIndex>,
pub import_decisions: std::collections::HashMap<u64, Vec<ImportDecision>>,
pub pgo_profiles: std::collections::HashMap<u64, PGOProfileEntry>,
pub cache_mode: ThinLTOCacheMode,
pub cache_dir: Option<String>,
pub cache: std::collections::HashMap<String, Vec<u8>>,
pub opt_level: u8,
pub num_threads: u32,
pub cpu: String,
pub features: String,
pub debug_info: bool,
pub hotness_threshold: u8,
pub max_import_depth: u32,
pub stats: ThinLTOStats,
}
#[derive(Debug, Clone, Default)]
pub struct ThinLTOStats {
pub modules_processed: usize,
pub total_functions: usize,
pub functions_imported: usize,
pub functions_exported: usize,
pub cache_hits: usize,
pub cache_misses: usize,
pub import_decisions: usize,
pub devirtualized_calls: usize,
pub summary_time_ms: u64,
pub import_decision_time_ms: u64,
pub backend_time_ms: u64,
pub size_reduction_bytes: u64,
}
impl X86ThinLTOFull {
pub fn new() -> Self {
X86ThinLTOFull {
module_indices: Vec::new(),
combined_index: None,
import_decisions: std::collections::HashMap::new(),
pgo_profiles: std::collections::HashMap::new(),
cache_mode: ThinLTOCacheMode::ComprehensiveKey,
cache_dir: None,
cache: std::collections::HashMap::new(),
opt_level: 2,
num_threads: 4,
cpu: String::from("x86-64"),
features: String::new(),
debug_info: false,
hotness_threshold: THINLTO_HOTNESS_THRESHOLD,
max_import_depth: THINLTO_MAX_IMPORT_DEPTH,
stats: ThinLTOStats::default(),
}
}
pub fn new_aggressive() -> Self {
X86ThinLTOFull {
hotness_threshold: 100,
max_import_depth: 10,
num_threads: 8,
..X86ThinLTOFull::new()
}
}
pub fn set_cache_dir(&mut self, dir: &str) {
self.cache_dir = Some(dir.to_string());
self.cache_mode = ThinLTOCacheMode::ComprehensiveKey;
}
pub fn set_cache_mode(&mut self, mode: ThinLTOCacheMode) {
self.cache_mode = mode;
}
pub fn set_opt_level(&mut self, level: u8) {
self.opt_level = level.min(3);
}
pub fn set_num_threads(&mut self, threads: u32) {
self.num_threads = threads.max(1).min(256);
}
pub fn set_hotness_threshold(&mut self, threshold: u8) {
self.hotness_threshold = threshold;
}
pub fn load_pgo_profile(&mut self, profile_data: &[u8]) {
self.pgo_profiles.clear();
if profile_data.len() < 24 {
return;
}
let entry_count = (profile_data.len() - 8) / 24;
let mut offset = 8; for _ in 0..entry_count {
if offset + 24 > profile_data.len() {
break;
}
let guid = u64::from_le_bytes([
profile_data[offset],
profile_data[offset + 1],
profile_data[offset + 2],
profile_data[offset + 3],
profile_data[offset + 4],
profile_data[offset + 5],
profile_data[offset + 6],
profile_data[offset + 7],
]);
offset += 8;
let entry_count_val = u64::from_le_bytes([
profile_data[offset],
profile_data[offset + 1],
profile_data[offset + 2],
profile_data[offset + 3],
profile_data[offset + 4],
profile_data[offset + 5],
profile_data[offset + 6],
profile_data[offset + 7],
]);
offset += 8;
let total_freq = u64::from_le_bytes([
profile_data[offset],
profile_data[offset + 1],
profile_data[offset + 2],
profile_data[offset + 3],
profile_data[offset + 4],
profile_data[offset + 5],
profile_data[offset + 6],
profile_data[offset + 7],
]);
offset += 8;
let max_freq = if entry_count_val > 0 { total_freq } else { 0 };
self.pgo_profiles.insert(
guid,
PGOProfileEntry {
guid,
entry_count: entry_count_val,
total_frequency: total_freq,
max_frequency: max_freq,
},
);
}
}
pub fn compute_module_summary(
&mut self,
module_path: &str,
object_data: &[u8],
) -> ModuleSummaryIndex {
let module_hash = compute_fnv1a_64_full(object_data);
let is_thin_lto = object_data.len() >= 20
&& &object_data[0..4] == b"BC\xC0\xDE"
&& object_data.get(16..20) == Some(b"THIN");
let mut summary = ModuleSummaryIndex {
module_hash,
module_path: module_path.to_string(),
target_triple: String::from("x86_64-unknown-linux-gnu"),
function_summaries: Vec::new(),
global_summaries: Vec::new(),
vtable_summaries: Vec::new(),
alias_summaries: Vec::new(),
instruction_count: 0,
is_thin_lto,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
summary.function_summaries = self.extract_function_summaries(object_data, module_hash);
summary.global_summaries = self.extract_global_summaries(object_data, module_hash);
summary.vtable_summaries = self.extract_vtable_summaries(object_data, module_hash);
summary.alias_summaries = self.extract_alias_summaries(object_data, module_hash);
summary.instruction_count = summary
.function_summaries
.iter()
.map(|f| f.inst_count as u64)
.sum();
summary
}
fn extract_function_summaries(
&self,
object_data: &[u8],
module_hash: u64,
) -> Vec<FunctionSummary> {
let mut summaries = Vec::new();
if object_data.len() < 100 {
return summaries;
}
let mut pos = 0;
while pos + 16 <= object_data.len() {
if object_data[pos] == 0x14 && object_data[pos + 1] == 0x00 {
let name_len = object_data[pos + 2] as usize;
let name_end = pos + 3 + name_len;
if name_end + 12 <= object_data.len() {
let name_bytes = &object_data[pos + 3..name_end];
let name = String::from_utf8_lossy(name_bytes).to_string();
let guid = compute_guid_from_name(&name);
let inst_count = u32::from_le_bytes([
object_data[name_end],
object_data[name_end + 1],
object_data[name_end + 2],
object_data[name_end + 3],
]);
let call_count = u32::from_le_bytes([
object_data[name_end + 4],
object_data[name_end + 5],
object_data[name_end + 6],
object_data[name_end + 7],
]);
let hotness = object_data[name_end + 8];
let flags = object_data[name_end + 9];
let mut callees = Vec::new();
let callee_count = object_data[name_end + 10] as usize;
let mut callee_pos = name_end + 11;
for _ in 0..callee_count.min(64) {
if callee_pos + 8 <= object_data.len() {
let cg = u64::from_le_bytes([
object_data[callee_pos],
object_data[callee_pos + 1],
object_data[callee_pos + 2],
object_data[callee_pos + 3],
object_data[callee_pos + 4],
object_data[callee_pos + 5],
object_data[callee_pos + 6],
object_data[callee_pos + 7],
]);
callees.push(cg);
callee_pos += 8;
}
}
let effective_hotness = if let Some(profile) = self.pgo_profiles.get(&guid) {
if profile.entry_count > 1000 {
255u8
} else {
((profile.entry_count as f64 / 10.0).min(255.0)) as u8
}
} else {
hotness
};
summaries.push(FunctionSummary {
name,
guid,
inst_count,
call_count,
callees,
hotness: effective_hotness,
has_inline_asm: (flags & 0x01) != 0,
has_varargs: (flags & 0x02) != 0,
is_external: (flags & 0x04) != 0,
is_entry_point: (flags & 0x08) != 0,
is_local: (flags & 0x10) == 0,
param_count: (flags >> 5) & 0x07,
return_type: ReturnTypeClass::Integer,
call_profile: Vec::new(),
cfi_enabled: (flags & 0x80) != 0,
});
}
pos = name_end;
}
pos += 1;
}
summaries
}
fn extract_global_summaries(
&self,
object_data: &[u8],
_module_hash: u64,
) -> Vec<GlobalVarSummary> {
let mut summaries = Vec::new();
if object_data.len() < 40 {
return summaries;
}
let mut pos = 0;
while pos + 24 <= object_data.len() {
if object_data[pos] == 0x16 && object_data[pos + 1] == 0x00 {
let name_len = object_data[pos + 2] as usize;
let name_end = pos + 3 + name_len;
if name_end + 16 <= object_data.len() {
let name_bytes = &object_data[pos + 3..name_end];
let name = String::from_utf8_lossy(name_bytes).to_string();
let guid = compute_guid_from_name(&name);
let size = u64::from_le_bytes([
object_data[name_end],
object_data[name_end + 1],
object_data[name_end + 2],
object_data[name_end + 3],
object_data[name_end + 4],
object_data[name_end + 5],
object_data[name_end + 6],
object_data[name_end + 7],
]);
let alignment = u32::from_le_bytes([
object_data[name_end + 8],
object_data[name_end + 9],
object_data[name_end + 10],
object_data[name_end + 11],
]);
let flags = object_data[name_end + 12];
summaries.push(GlobalVarSummary {
name,
guid,
size,
alignment,
is_constant: (flags & 0x01) != 0,
is_read_only: (flags & 0x02) != 0,
is_external: (flags & 0x04) != 0,
refs: Vec::new(),
init_hash: 0,
is_tls: (flags & 0x08) != 0,
linkage: GlobalLinkageKind::External,
});
}
pos = name_end;
}
pos += 1;
}
summaries
}
fn extract_vtable_summaries(
&self,
object_data: &[u8],
_module_hash: u64,
) -> Vec<VTableSummary> {
let mut summaries = Vec::new();
let mut pos = 0;
while pos + 16 <= object_data.len() {
if object_data[pos] == 0x18 && object_data[pos + 1] == 0x00 {
let name_len = object_data[pos + 2] as usize;
let name_end = pos + 3 + name_len;
if name_end + 16 <= object_data.len() {
let name_bytes = &object_data[pos + 3..name_end];
let name = String::from_utf8_lossy(name_bytes).to_string();
let guid = compute_guid_from_name(&name);
let entry_count = object_data[name_end] as usize;
let hierarchy_depth = u32::from_le_bytes([
object_data[name_end + 1],
object_data[name_end + 2],
object_data[name_end + 3],
object_data[name_end + 4],
]);
let class_name_len = object_data[name_end + 5] as usize;
let class_end = name_end + 6 + class_name_len;
let class_name = if class_end <= object_data.len() {
String::from_utf8_lossy(&object_data[name_end + 6..class_end]).to_string()
} else {
String::new()
};
let mut entries = Vec::new();
let mut entry_pos = class_end;
for _ in 0..entry_count.min(64) {
if entry_pos + 8 <= object_data.len() {
let eg = u64::from_le_bytes([
object_data[entry_pos],
object_data[entry_pos + 1],
object_data[entry_pos + 2],
object_data[entry_pos + 3],
object_data[entry_pos + 4],
object_data[entry_pos + 5],
object_data[entry_pos + 6],
object_data[entry_pos + 7],
]);
entries.push(eg);
entry_pos += 8;
}
}
summaries.push(VTableSummary {
name,
guid,
entries,
class_name,
has_subclasses: (object_data[name_end + 5] & 0x80) != 0,
hierarchy_depth,
});
}
pos = name_end;
}
pos += 1;
}
summaries
}
fn extract_alias_summaries(&self, object_data: &[u8], _module_hash: u64) -> Vec<AliasSummary> {
let mut summaries = Vec::new();
let mut pos = 0;
while pos + 24 <= object_data.len() {
if object_data[pos] == 0x1A && object_data[pos + 1] == 0x00 {
let name_len = object_data[pos + 2] as usize;
let name_end = pos + 3 + name_len;
if name_end + 16 <= object_data.len() {
let name_bytes = &object_data[pos + 3..name_end];
let name = String::from_utf8_lossy(name_bytes).to_string();
let guid = compute_guid_from_name(&name);
let aliasee_guid = u64::from_le_bytes([
object_data[name_end],
object_data[name_end + 1],
object_data[name_end + 2],
object_data[name_end + 3],
object_data[name_end + 4],
object_data[name_end + 5],
object_data[name_end + 6],
object_data[name_end + 7],
]);
let flags = object_data[name_end + 8];
summaries.push(AliasSummary {
name,
guid,
aliasee_guid,
aliasee_is_function: (flags & 0x01) != 0,
visibility: SymbolVisibility::Default,
});
}
pos = name_end;
}
pos += 1;
}
summaries
}
pub fn build_combined_index(&mut self, modules: Vec<ModuleSummaryIndex>) {
let mut combined = CombinedIndex {
modules,
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 0,
};
for (mod_idx, module) in combined.modules.iter().enumerate() {
for func in &module.function_summaries {
combined.function_to_module.insert(func.guid, mod_idx);
if !func.callees.is_empty() {
combined.call_graph.insert(func.guid, func.callees.clone());
}
for callee_guid in &func.callees {
combined
.reverse_call_graph
.entry(*callee_guid)
.or_default()
.push(func.guid);
}
if func.is_external {
combined.export_guids.insert(func.guid);
}
}
combined.total_functions += module.function_summaries.len();
for global in &module.global_summaries {
combined.global_to_module.insert(global.guid, mod_idx);
if global.is_external {
combined.export_guids.insert(global.guid);
}
}
combined.total_globals += module.global_summaries.len();
for vtable in &module.vtable_summaries {
combined.vtable_to_module.insert(vtable.guid, mod_idx);
}
for alias in &module.alias_summaries {
combined.alias_to_module.insert(alias.guid, mod_idx);
}
}
self.combined_index = Some(combined);
self.stats.total_functions = self
.combined_index
.as_ref()
.map_or(0, |ci| ci.total_functions);
}
pub fn decide_imports(&mut self) {
let start = std::time::Instant::now();
self.import_decisions.clear();
let combined = match &self.combined_index {
Some(c) => c.clone(),
None => return,
};
for (mod_idx, module) in combined.modules.iter().enumerate() {
let module_hash = module.module_hash;
let mut decisions = Vec::new();
for func in &module.function_summaries {
for callee_guid in &func.callees {
if let Some(&callee_mod) = combined.function_to_module.get(callee_guid) {
if callee_mod != mod_idx {
let hotness =
self.compute_call_hotness(*callee_guid, func.guid, &combined);
if hotness >= self.hotness_threshold {
let callee_module = &combined.modules[callee_mod];
if let Some(callee_summary) = callee_module
.function_summaries
.iter()
.find(|fs| fs.guid == *callee_guid)
{
let reason = if hotness >= 240 {
ThinLTOImportReason::HotCall
} else if self.pgo_profiles.contains_key(callee_guid) {
ThinLTOImportReason::ProfileGuided
} else {
ThinLTOImportReason::ColdCall
};
decisions.push(ImportDecision::Import {
name: callee_summary.name.clone(),
reason,
hotness,
guid: *callee_guid,
});
}
} else {
decisions.push(ImportDecision::Skip {
name: format!("guid_{}", callee_guid),
reason: format!(
"Hotness {} below threshold {}",
hotness, self.hotness_threshold
),
});
}
}
}
}
}
self.expand_transitive_imports(&mut decisions, &combined, mod_idx);
if decisions.len() > THINLTO_MAX_IMPORTS_PER_MODULE {
decisions.sort_by(|a, b| {
let ha = match a {
ImportDecision::Import { hotness, .. } => *hotness,
ImportDecision::Skip { .. } => 0,
};
let hb = match b {
ImportDecision::Import { hotness, .. } => *hotness,
ImportDecision::Skip { .. } => 0,
};
hb.cmp(&ha)
});
decisions.truncate(THINLTO_MAX_IMPORTS_PER_MODULE);
}
self.stats.import_decisions += decisions.len();
self.import_decisions.insert(module_hash, decisions);
}
self.stats.import_decision_time_ms = start.elapsed().as_millis() as u64;
}
fn compute_call_hotness(
&self,
callee_guid: u64,
caller_guid: u64,
combined: &CombinedIndex,
) -> u8 {
if let Some(profile) = self.pgo_profiles.get(&callee_guid) {
if profile.entry_count > 10000 {
return 255;
}
if profile.entry_count > 1000 {
return 220;
}
}
if let Some(caller_mod_idx) = combined.function_to_module.get(&caller_guid) {
let module = &combined.modules[*caller_mod_idx];
if let Some(caller_summary) = module
.function_summaries
.iter()
.find(|fs| fs.guid == caller_guid)
{
for (cg, count) in &caller_summary.call_profile {
if *cg == callee_guid {
return if *count > 1000 {
255
} else if *count > 100 {
200
} else if *count > 10 {
150
} else {
100
};
}
}
}
}
let caller_count = combined
.reverse_call_graph
.get(&callee_guid)
.map_or(0, |v| v.len());
if caller_count > 10 {
180
} else if caller_count > 5 {
140
} else if caller_count > 0 {
80
} else {
0
}
}
fn expand_transitive_imports(
&self,
decisions: &mut Vec<ImportDecision>,
combined: &CombinedIndex,
mod_idx: usize,
) {
let mut imported_guids: std::collections::HashSet<u64> = decisions
.iter()
.filter_map(|d| match d {
ImportDecision::Import { guid, .. } => Some(*guid),
_ => None,
})
.collect();
for depth in 0..self.max_import_depth {
let mut new_imports: Vec<u64> = Vec::new();
for guid in &imported_guids {
if let Some(&import_mod) = combined.function_to_module.get(guid) {
if import_mod == mod_idx {
continue;
}
let module = &combined.modules[import_mod];
if let Some(fs) = module.function_summaries.iter().find(|f| f.guid == *guid) {
for callee in &fs.callees {
if !imported_guids.contains(callee)
&& combined.function_to_module.contains_key(callee)
{
let callee_mod = combined.function_to_module[callee];
if callee_mod != mod_idx && callee_mod != import_mod {
new_imports.push(*callee);
}
}
}
}
}
}
if new_imports.is_empty() {
break;
}
for new_guid in &new_imports {
imported_guids.insert(*new_guid);
if let Some(&m_idx) = combined.function_to_module.get(new_guid) {
let m = &combined.modules[m_idx];
if let Some(fs) = m.function_summaries.iter().find(|f| f.guid == *new_guid) {
decisions.push(ImportDecision::Import {
name: fs.name.clone(),
reason: ThinLTOImportReason::Transitive,
hotness: 0,
guid: *new_guid,
});
}
}
}
if depth == self.max_import_depth.saturating_sub(1) {
break;
}
}
}
pub fn run_distributed_backend(&mut self, modules: &[Vec<u8>]) -> Vec<Vec<u8>> {
let start = std::time::Instant::now();
let mut results = Vec::with_capacity(modules.len());
for (i, module_data) in modules.iter().enumerate() {
let optimized = self.optimize_module_backend(module_data, i);
results.push(optimized);
}
self.stats.backend_time_ms = start.elapsed().as_millis() as u64;
results
}
fn optimize_module_backend(&self, module_data: &[u8], module_index: usize) -> Vec<u8> {
if let Some(cache_key) = self.compute_cache_key(module_data, module_index) {
if let Some(cached) = self.cache.get(&cache_key) {
return cached.clone();
}
}
let mut optimized = module_data.to_vec();
self.apply_imports_to_module(&mut optimized, module_index);
self.apply_module_optimizations(&mut optimized);
let object_code = self.codegen_module(&optimized);
if let Some(cache_key) = self.compute_cache_key(module_data, module_index) {
}
object_code
}
fn apply_imports_to_module(&self, _module_data: &mut Vec<u8>, _module_index: usize) {
}
fn apply_module_optimizations(&self, _module_data: &mut Vec<u8>) {
}
fn codegen_module(&self, _ir_data: &[u8]) -> Vec<u8> {
let mut obj = Vec::new();
obj.extend_from_slice(&[0x7f, b'E', b'L', b'F']); obj.push(2); obj.push(1); obj.push(1); obj.push(0); obj.push(0); obj.extend_from_slice(&[0; 7]); obj.extend_from_slice(&[0x01, 0x00]); obj.extend_from_slice(&[0x3E, 0x00]); obj.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); obj.extend_from_slice(&[0; 40]);
obj
}
fn compute_cache_key(&self, module_data: &[u8], module_index: usize) -> Option<String> {
match self.cache_mode {
ThinLTOCacheMode::Disabled => None,
ThinLTOCacheMode::HashBased => {
let hash = compute_fnv1a_64_full(module_data);
Some(format!("thinlto_{:016x}_{}", hash, module_index))
}
ThinLTOCacheMode::ComprehensiveKey | ThinLTOCacheMode::Incremental => {
let hash = compute_fnv1a_64_full(module_data);
let key_data = format!(
"{:016x}_{}_{}_{}_{}",
hash, module_index, self.opt_level, self.cpu, self.features
);
let key_hash = compute_fnv1a_64_full(key_data.as_bytes());
Some(format!("thinlto_comp_{:016x}", key_hash))
}
}
}
pub fn cache_lookup(&self, module_hash: u64) -> Option<&Vec<u8>> {
let key = format!("thinlto_comp_{:016x}", module_hash);
self.cache.get(&key)
}
pub fn cache_store(&mut self, module_hash: u64, data: Vec<u8>) {
let key = format!("thinlto_comp_{:016x}", module_hash);
self.cache.insert(key, data);
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
pub fn cache_hit_ratio(&self) -> f64 {
let total = self.stats.cache_hits + self.stats.cache_misses;
if total == 0 {
return 0.0;
}
self.stats.cache_hits as f64 / total as f64
}
pub fn get_stats(&self) -> &ThinLTOStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = ThinLTOStats::default();
}
pub fn get_combined_index(&self) -> Option<&CombinedIndex> {
self.combined_index.as_ref()
}
pub fn get_import_decisions(&self, module_hash: u64) -> Option<&Vec<ImportDecision>> {
self.import_decisions.get(&module_hash)
}
}
impl Default for X86ThinLTOFull {
fn default() -> Self {
X86ThinLTOFull::new()
}
}
pub struct X86LTOInternalization {
pub resolution_table: Vec<SymbolResolutionEntry>,
pub export_list: std::collections::HashSet<u64>,
pub import_list: std::collections::HashSet<u64>,
pub internalized: std::collections::HashSet<u64>,
pub comdat_groups: std::collections::HashMap<String, Vec<u64>>,
pub comdat_leaders: std::collections::HashMap<String, u64>,
pub linkonce_odr_pruned: std::collections::HashSet<u64>,
pub internalization_stats: InternalizationStats,
}
#[derive(Debug, Clone, Default)]
pub struct InternalizationStats {
pub total_symbols: usize,
pub kept_global: usize,
pub internalized: usize,
pub discarded: usize,
pub comdat_groups_resolved: usize,
pub linkonce_odr_pruned: usize,
}
impl X86LTOInternalization {
pub fn new() -> Self {
X86LTOInternalization {
resolution_table: Vec::new(),
export_list: std::collections::HashSet::new(),
import_list: std::collections::HashSet::new(),
internalized: std::collections::HashSet::new(),
comdat_groups: std::collections::HashMap::new(),
comdat_leaders: std::collections::HashMap::new(),
linkonce_odr_pruned: std::collections::HashSet::new(),
internalization_stats: InternalizationStats::default(),
}
}
pub fn build_resolution_table(&mut self, combined_index: &CombinedIndex) {
self.resolution_table.clear();
for module in &combined_index.modules {
for func in &module.function_summaries {
let is_exported = combined_index.export_guids.contains(&func.guid);
let entry = SymbolResolutionEntry {
name: func.name.clone(),
guid: func.guid,
decision: InternalizationDecision::KeepGlobal,
visibility: SymbolVisibility::Default,
is_exported,
is_imported: false,
is_dllexport: false,
comdat_leader: None,
external_refs: self.count_external_refs(func.guid, combined_index),
survives: is_exported || func.is_external,
};
self.resolution_table.push(entry);
}
for global in &module.global_summaries {
let is_exported = combined_index.export_guids.contains(&global.guid);
let entry = SymbolResolutionEntry {
name: global.name.clone(),
guid: global.guid,
decision: InternalizationDecision::KeepGlobal,
visibility: SymbolVisibility::Default,
is_exported,
is_imported: false,
is_dllexport: false,
comdat_leader: None,
external_refs: 0,
survives: is_exported || global.is_external,
};
self.resolution_table.push(entry);
}
}
self.internalization_stats.total_symbols = self.resolution_table.len();
}
fn count_external_refs(&self, guid: u64, combined_index: &CombinedIndex) -> u32 {
let mut count = 0u32;
for module in &combined_index.modules {
for func in &module.function_summaries {
if func.callees.contains(&guid) {
count += 1;
}
}
}
if let Some(callers) = combined_index.reverse_call_graph.get(&guid) {
count += callers.len() as u32;
}
count
}
pub fn compute_export_list(
&mut self,
combined_index: &CombinedIndex,
visibility_overrides: &std::collections::HashMap<String, SymbolVisibility>,
dllexport_symbols: &[String],
) {
self.export_list.clear();
self.import_list.clear();
for module in &combined_index.modules {
for func in &module.function_summaries {
if func.is_external {
let effective_visibility = visibility_overrides
.get(&func.name)
.unwrap_or(&SymbolVisibility::Default);
match effective_visibility {
SymbolVisibility::Default | SymbolVisibility::Protected => {
self.export_list.insert(func.guid);
}
SymbolVisibility::Hidden | SymbolVisibility::Internal => {
}
}
if dllexport_symbols.contains(&func.name) {
self.export_list.insert(func.guid);
}
}
}
for global in &module.global_summaries {
if global.is_external {
let effective_visibility = visibility_overrides
.get(&global.name)
.unwrap_or(&SymbolVisibility::Default);
match effective_visibility {
SymbolVisibility::Default | SymbolVisibility::Protected => {
self.export_list.insert(global.guid);
}
_ => {}
}
}
}
}
}
pub fn internalize_non_exported(&mut self) {
self.internalized.clear();
for entry in &mut self.resolution_table {
if !self.export_list.contains(&entry.guid) && !entry.is_imported {
entry.decision = InternalizationDecision::Internalize;
entry.survives = true; self.internalized.insert(entry.guid);
}
}
self.internalization_stats.internalized = self.internalized.len();
self.internalization_stats.kept_global = self.export_list.len();
}
pub fn select_comdat_leaders(&mut self, strategy: ComdatSelectionKind) {
self.comdat_leaders.clear();
for (group_name, members) in &self.comdat_groups {
if members.is_empty() {
continue;
}
let leader = match strategy {
ComdatSelectionKind::Any | ComdatSelectionKind::NoDuplicates => {
members[0]
}
ComdatSelectionKind::Largest => {
*members.last().unwrap_or(&members[0])
}
ComdatSelectionKind::ExactMatch => {
members[0]
}
ComdatSelectionKind::SameSize => {
members[0]
}
};
self.comdat_leaders.insert(group_name.clone(), leader);
for &member in members.iter().filter(|&&m| m != leader) {
if let Some(entry) = self.resolution_table.iter_mut().find(|e| e.guid == member) {
entry.decision = InternalizationDecision::Discard;
entry.survives = false;
}
}
}
self.internalization_stats.comdat_groups_resolved = self.comdat_groups.len();
}
pub fn prune_linkonce_odr(&mut self) {
let mut seen_names: std::collections::HashSet<String> = std::collections::HashSet::new();
for entry in &mut self.resolution_table {
if entry.decision == InternalizationDecision::Discard {
continue;
}
if !seen_names.insert(entry.name.clone()) {
entry.decision = InternalizationDecision::Discard;
entry.survives = false;
self.linkonce_odr_pruned.insert(entry.guid);
}
}
self.internalization_stats.linkonce_odr_pruned = self.linkonce_odr_pruned.len();
}
pub fn add_comdat_group(&mut self, group_name: String, members: Vec<u64>) {
self.comdat_groups
.entry(group_name)
.or_default()
.extend(members);
}
pub fn is_internalized(&self, guid: u64) -> bool {
self.internalized.contains(&guid)
}
pub fn get_decision(&self, guid: u64) -> Option<InternalizationDecision> {
self.resolution_table
.iter()
.find(|e| e.guid == guid)
.map(|e| e.decision)
}
pub fn get_stats(&self) -> &InternalizationStats {
&self.internalization_stats
}
pub fn clear(&mut self) {
self.resolution_table.clear();
self.export_list.clear();
self.import_list.clear();
self.internalized.clear();
self.comdat_groups.clear();
self.comdat_leaders.clear();
self.linkonce_odr_pruned.clear();
self.internalization_stats = InternalizationStats::default();
}
}
impl Default for X86LTOInternalization {
fn default() -> Self {
X86LTOInternalization::new()
}
}
pub struct X86LTODebugInfo {
pub merge_mode: LTODebugMergeMode,
pub compile_units: Vec<DwarfCompileUnitData>,
pub type_units: Vec<DwarfTypeUnit>,
pub dedup_type_units: std::collections::HashMap<u64, Vec<u8>>,
pub skeleton_cus: Vec<SkeletonCompileUnit>,
pub line_table: Vec<CrossModuleLineEntry>,
pub address_ranges: Vec<(u64, u64, usize)>, pub string_table: std::collections::HashMap<String, u64>,
pub abbrev_table: Vec<u8>,
pub frame_data: Vec<u8>,
pub info_data: Vec<u8>,
pub types_data: Vec<u8>,
pub total_debug_size: u64,
pub debug_stats: DebugInfoStats,
pub dedup_strategy: TypeUnitDedupStrategy,
}
#[derive(Debug, Clone)]
pub struct DwarfCompileUnitData {
pub offset: u64,
pub length: u32,
pub version: u16,
pub abbrev_offset: u64,
pub address_size: u8,
pub module_hash: u64,
pub comp_dir: String,
pub source_file: String,
pub producer: String,
pub low_pc: u64,
pub high_pc: u64,
pub line_table_data: Vec<u8>,
pub range_list_data: Vec<u8>,
}
#[derive(Debug, Clone, Default)]
pub struct DebugInfoStats {
pub cus_merged: usize,
pub type_units_deduplicated: usize,
pub type_units_kept: usize,
pub skeleton_cus_generated: usize,
pub line_entries_merged: usize,
pub size_reduction_ratio: f64,
pub bytes_saved_by_dedup: u64,
}
impl X86LTODebugInfo {
pub fn new() -> Self {
X86LTODebugInfo {
merge_mode: LTODebugMergeMode::Full,
compile_units: Vec::new(),
type_units: Vec::new(),
dedup_type_units: std::collections::HashMap::new(),
skeleton_cus: Vec::new(),
line_table: Vec::new(),
address_ranges: Vec::new(),
string_table: std::collections::HashMap::new(),
abbrev_table: Vec::new(),
frame_data: Vec::new(),
info_data: Vec::new(),
types_data: Vec::new(),
total_debug_size: 0,
debug_stats: DebugInfoStats::default(),
dedup_strategy: TypeUnitDedupStrategy::Combined,
}
}
pub fn set_merge_mode(&mut self, mode: LTODebugMergeMode) {
self.merge_mode = mode;
}
pub fn set_dedup_strategy(&mut self, strategy: TypeUnitDedupStrategy) {
self.dedup_strategy = strategy;
}
pub fn add_compile_unit(&mut self, cu: DwarfCompileUnitData) {
self.compile_units.push(cu);
}
pub fn add_type_units(&mut self, units: Vec<DwarfTypeUnit>) {
self.type_units.extend(units);
}
pub fn merge_debug_info(&mut self, modules: &[ModuleSummaryIndex]) -> Vec<u8> {
if self.merge_mode == LTODebugMergeMode::None {
return Vec::new();
}
self.deduplicate_type_units();
let merged_info = self.merge_compile_units(modules);
self.merge_line_tables();
self.merge_address_ranges();
self.build_string_table();
self.build_abbrev_table();
let merged_frame = self.merge_frame_data();
let mut output = Vec::new();
output.extend_from_slice(&merged_info);
output.extend_from_slice(&self.types_data);
output.extend_from_slice(&merged_frame);
self.total_debug_size = output.len() as u64;
self.debug_stats.cus_merged = self.compile_units.len();
self.debug_stats.size_reduction_ratio = self.compute_size_reduction();
output
}
pub fn deduplicate_type_units(&mut self) {
let before_count = self.type_units.len();
self.dedup_type_units.clear();
match self.dedup_strategy {
TypeUnitDedupStrategy::HashBased | TypeUnitDedupStrategy::Combined => {
for tu in &self.type_units {
self.dedup_type_units
.entry(tu.type_signature)
.or_insert_with(|| {
let mut data = Vec::new();
data.extend_from_slice(&tu.type_signature.to_le_bytes());
data.extend_from_slice(&tu.offset.to_le_bytes());
data.extend_from_slice(&tu.length.to_le_bytes());
data
});
}
}
TypeUnitDedupStrategy::Structural => {
let mut sig_groups: std::collections::HashMap<u64, Vec<&DwarfTypeUnit>> =
std::collections::HashMap::new();
for tu in &self.type_units {
sig_groups.entry(tu.type_signature).or_default().push(tu);
}
for (sig, _group) in sig_groups {
let mut data = Vec::new();
data.extend_from_slice(&sig.to_le_bytes());
self.dedup_type_units.insert(sig, data);
}
}
TypeUnitDedupStrategy::NameBased => {
for tu in &self.type_units {
let key = tu.type_name.as_ref().map_or(tu.type_signature, |name| {
let mut h: u64 = FNV64_OFFSET_BASIS;
for b in name.bytes() {
h ^= b as u64;
h = h.wrapping_mul(FNV64_PRIME);
}
h
});
self.dedup_type_units.entry(key).or_insert_with(|| {
let mut data = Vec::new();
data.extend_from_slice(&key.to_le_bytes());
data
});
}
}
}
self.debug_stats.type_units_deduplicated =
before_count.saturating_sub(self.dedup_type_units.len());
self.debug_stats.type_units_kept = self.dedup_type_units.len();
self.debug_stats.bytes_saved_by_dedup =
(before_count.saturating_sub(self.dedup_type_units.len()) as u64) * 128;
}
fn merge_compile_units(&mut self, _modules: &[ModuleSummaryIndex]) -> Vec<u8> {
let mut output = Vec::new();
if self.merge_mode == LTODebugMergeMode::Skeleton {
return self.generate_skeleton_cus();
}
for cu in &self.compile_units {
output.extend_from_slice(b"DWARF_CU_HEADER");
output.extend_from_slice(&cu.length.to_le_bytes());
output.push(cu.version as u8);
output.push((cu.version >> 8) as u8);
output.extend_from_slice(&cu.abbrev_offset.to_le_bytes());
output.push(cu.address_size);
output.extend_from_slice(cu.comp_dir.as_bytes());
output.push(0);
output.extend_from_slice(cu.source_file.as_bytes());
output.push(0);
output.extend_from_slice(cu.producer.as_bytes());
output.push(0);
output.extend_from_slice(&cu.low_pc.to_le_bytes());
output.extend_from_slice(&cu.high_pc.to_le_bytes());
output.extend_from_slice(&cu.line_table_data.len().to_le_bytes());
output.extend_from_slice(&cu.line_table_data);
}
output
}
fn generate_skeleton_cus(&mut self) -> Vec<u8> {
let mut output = Vec::new();
self.skeleton_cus.clear();
for cu in &self.compile_units {
let skeleton = SkeletonCompileUnit {
module_hash: cu.module_hash,
object_path: cu.source_file.clone(),
comp_dir: cu.comp_dir.clone(),
dwo_name: Some(format!("{}.dwo", cu.source_file)),
dwarf_version: cu.version,
address_size: cu.address_size,
};
self.skeleton_cus.push(skeleton);
output.extend_from_slice(b"SKEL_CU_");
output.extend_from_slice(&cu.module_hash.to_le_bytes());
output.extend_from_slice(&cu.low_pc.to_le_bytes());
output.extend_from_slice(&cu.high_pc.to_le_bytes());
output.push(cu.version as u8);
output.push(cu.address_size);
}
self.debug_stats.skeleton_cus_generated = self.skeleton_cus.len();
output
}
pub fn merge_line_tables(&mut self) {
self.line_table.clear();
for (mod_idx, cu) in self.compile_units.iter().enumerate() {
let mut pos = 0;
let data = &cu.line_table_data;
while pos + 16 <= data.len() {
let file_len = data[pos] as usize;
if pos + 1 + file_len + 12 > data.len() {
break;
}
let file = String::from_utf8_lossy(&data[pos + 1..pos + 1 + file_len]).to_string();
pos += 1 + file_len;
let line =
u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
pos += 4;
let column =
u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
pos += 4;
let address = u64::from_le_bytes([
data[pos],
data[pos + 1],
data[pos + 2],
data[pos + 3],
data[pos + 4],
data[pos + 5],
data[pos + 6],
data[pos + 7],
]);
pos += 8;
self.line_table.push(CrossModuleLineEntry {
file,
line,
column,
address,
module_index: mod_idx,
inline_depth: 0,
});
}
}
self.line_table.sort_by_key(|e| e.address);
self.debug_stats.line_entries_merged = self.line_table.len();
}
fn merge_address_ranges(&mut self) {
self.address_ranges.clear();
for (mod_idx, cu) in self.compile_units.iter().enumerate() {
self.address_ranges.push((cu.low_pc, cu.high_pc, mod_idx));
}
self.address_ranges.sort_by_key(|(start, _, _)| *start);
}
fn build_string_table(&mut self) {
self.string_table.clear();
let mut offset: u64 = 1; self.string_table.insert(String::new(), 0);
for cu in &self.compile_units {
for s in &[&cu.comp_dir, &cu.source_file, &cu.producer] {
if !self.string_table.contains_key(*s) {
self.string_table.insert((*s).clone(), offset);
offset += s.len() as u64 + 1; }
}
}
}
fn build_abbrev_table(&mut self) {
let mut seen: std::collections::HashSet<u64> = std::collections::HashSet::new();
self.abbrev_table.clear();
for cu in &self.compile_units {
let hash = compute_fnv1a_64_full(&cu.line_table_data);
if seen.insert(hash) {
self.abbrev_table.push(1); self.abbrev_table.push(1); self.abbrev_table.push(0x10); self.abbrev_table.push(0x10); self.abbrev_table.push(0x12); self.abbrev_table.push(0x25); self.abbrev_table.push(0); self.abbrev_table.push(0); break;
}
}
self.abbrev_table.push(0);
}
fn merge_frame_data(&mut self) -> Vec<u8> {
let mut merged = Vec::new();
for cu in &self.compile_units {
if !cu.line_table_data.is_empty() {
merged.push(0xFF); merged.extend_from_slice(&cu.low_pc.to_le_bytes());
merged.extend_from_slice(&cu.high_pc.to_le_bytes());
merged.extend_from_slice(&cu.module_hash.to_le_bytes());
}
}
self.frame_data = merged.clone();
merged
}
fn compute_size_reduction(&self) -> f64 {
let total_before = self.type_units.len() as f64 * 128.0;
let total_after = self.dedup_type_units.len() as f64 * 128.0;
if total_before == 0.0 {
return 0.0;
}
1.0 - (total_after / total_before)
}
pub fn generate_skeleton_for_module(&self, module_hash: u64) -> Option<SkeletonCompileUnit> {
self.skeleton_cus
.iter()
.find(|scu| scu.module_hash == module_hash)
.cloned()
}
pub fn lookup_address(&self, address: u64) -> Option<&CrossModuleLineEntry> {
self.line_table
.binary_search_by_key(&address, |e| e.address)
.ok()
.map(|idx| &self.line_table[idx])
}
pub fn get_stats(&self) -> &DebugInfoStats {
&self.debug_stats
}
pub fn clear(&mut self) {
self.compile_units.clear();
self.type_units.clear();
self.dedup_type_units.clear();
self.skeleton_cus.clear();
self.line_table.clear();
self.address_ranges.clear();
self.string_table.clear();
self.abbrev_table.clear();
self.frame_data.clear();
self.info_data.clear();
self.types_data.clear();
self.total_debug_size = 0;
self.debug_stats = DebugInfoStats::default();
}
}
impl Default for X86LTODebugInfo {
fn default() -> Self {
X86LTODebugInfo::new()
}
}
pub struct X86LTOWholeProgram {
pub alias_analyses: Vec<AliasAnalysisResult>,
pub constant_props: Vec<ConstantPropagationRecord>,
pub dead_globals: Vec<DeadGlobalResult>,
pub demoted_globals: std::collections::HashSet<u64>,
pub devirt_results: Vec<DevirtResult>,
pub enable_alias_analysis: bool,
pub enable_const_prop: bool,
pub enable_dead_global_elim: bool,
pub enable_global_opt: bool,
pub enable_devirt: bool,
pub whole_program_stats: WholeProgramStats,
}
#[derive(Debug, Clone, Default)]
pub struct WholeProgramStats {
pub alias_pairs_analyzed: usize,
pub no_alias_results: usize,
pub constants_propagated: usize,
pub dead_globals_eliminated: usize,
pub bytes_freed: u64,
pub globals_demoted: usize,
pub devirtualized_calls: usize,
pub devirt_failures: usize,
}
impl X86LTOWholeProgram {
pub fn new() -> Self {
X86LTOWholeProgram {
alias_analyses: Vec::new(),
constant_props: Vec::new(),
dead_globals: Vec::new(),
demoted_globals: std::collections::HashSet::new(),
devirt_results: Vec::new(),
enable_alias_analysis: true,
enable_const_prop: true,
enable_dead_global_elim: true,
enable_global_opt: true,
enable_devirt: true,
whole_program_stats: WholeProgramStats::default(),
}
}
pub fn run_all(
&mut self,
combined_index: &CombinedIndex,
global_data: &[(u64, Vec<u8>)], ) {
if self.enable_alias_analysis {
self.run_alias_analysis(combined_index);
}
if self.enable_const_prop {
self.run_constant_propagation(combined_index, global_data);
}
if self.enable_dead_global_elim {
self.run_dead_global_elimination(combined_index);
}
if self.enable_global_opt {
self.run_global_variable_optimization(combined_index);
}
if self.enable_devirt {
self.run_devirtualization(combined_index);
}
}
pub fn run_alias_analysis(&mut self, combined_index: &CombinedIndex) {
self.alias_analyses.clear();
let global_guids: Vec<u64> = combined_index.global_to_module.keys().copied().collect();
for i in 0..global_guids.len() {
for j in (i + 1)..global_guids.len() {
let g1 = global_guids[i];
let g2 = global_guids[j];
let result = self.analyze_alias_pair(g1, g2, combined_index);
let no_alias = result.no_alias;
self.alias_analyses.push(result);
self.whole_program_stats.alias_pairs_analyzed += 1;
if no_alias {
self.whole_program_stats.no_alias_results += 1;
}
}
}
}
fn analyze_alias_pair(
&self,
guid1: u64,
guid2: u64,
combined_index: &CombinedIndex,
) -> AliasAnalysisResult {
let mod1 = combined_index.global_to_module.get(&guid1);
let mod2 = combined_index.global_to_module.get(&guid2);
if mod1 == mod2 {
let module = &combined_index.modules[*mod1.unwrap_or(&0)];
let g1_local = module
.global_summaries
.iter()
.any(|gs| gs.guid == guid1 && !gs.is_external);
let g2_local = module
.global_summaries
.iter()
.any(|gs| gs.guid == guid2 && !gs.is_external);
if g1_local && g2_local {
return AliasAnalysisResult {
may_alias: false,
no_alias: true,
must_alias: false,
partial_alias: false,
alias_set_id: guid1 ^ guid2,
};
}
}
AliasAnalysisResult {
may_alias: true,
no_alias: false,
must_alias: false,
partial_alias: false,
alias_set_id: guid1 ^ guid2,
}
}
pub fn run_constant_propagation(
&mut self,
combined_index: &CombinedIndex,
global_data: &[(u64, Vec<u8>)],
) {
self.constant_props.clear();
let constant_globals: std::collections::HashMap<u64, &[u8]> = global_data
.iter()
.map(|(guid, data)| (*guid, data.as_slice()))
.collect();
for module in &combined_index.modules {
for func in &module.function_summaries {
for global in &module.global_summaries {
if global.is_constant && constant_globals.contains_key(&global.guid) {
self.constant_props.push(ConstantPropagationRecord {
global_guid: global.guid,
function_guid: func.guid,
instruction_offset: 0,
constant_value: constant_globals[&global.guid].to_vec(),
});
self.whole_program_stats.constants_propagated += 1;
}
}
}
}
}
pub fn run_dead_global_elimination(&mut self, combined_index: &CombinedIndex) {
self.dead_globals.clear();
for module in &combined_index.modules {
for global in &module.global_summaries {
let is_referenced = combined_index.modules.iter().any(|m| {
m.function_summaries
.iter()
.any(|f| f.callees.contains(&global.guid))
|| m.global_summaries
.iter()
.any(|g| g.refs.contains(&global.guid))
});
let is_exported = combined_index.export_guids.contains(&global.guid);
if !is_referenced && !is_exported && !global.is_external {
self.dead_globals.push(DeadGlobalResult {
name: global.name.clone(),
guid: global.guid,
size_freed: global.size,
reason: DeadGlobalReason::Unreferenced,
});
self.whole_program_stats.dead_globals_eliminated += 1;
self.whole_program_stats.bytes_freed += global.size;
}
}
}
}
pub fn run_global_variable_optimization(&mut self, combined_index: &CombinedIndex) {
self.demoted_globals.clear();
for module in &combined_index.modules {
for global in &module.global_summaries {
let is_exported = combined_index.export_guids.contains(&global.guid);
if !is_exported {
let external_refs = combined_index
.modules
.iter()
.filter(|m| {
m.global_summaries
.iter()
.any(|g| g.refs.contains(&global.guid))
})
.count();
if external_refs <= 1 {
self.demoted_globals.insert(global.guid);
self.whole_program_stats.globals_demoted += 1;
}
}
}
}
}
pub fn run_devirtualization(&mut self, combined_index: &CombinedIndex) {
self.devirt_results.clear();
for module in &combined_index.modules {
for vtable in &module.vtable_summaries {
if self.can_devirtualize(vtable, combined_index) {
for (idx, &entry_guid) in vtable.entries.iter().enumerate() {
if let Some(target_func) = self.resolve_virtual_call_target(
vtable,
entry_guid,
idx,
combined_index,
) {
self.devirt_results.push(DevirtResult::Devirtualized {
vtable_name: vtable.name.clone(),
target_function: target_func,
offset: (idx * 8) as u64,
});
self.whole_program_stats.devirtualized_calls += 1;
} else {
self.devirt_results.push(DevirtResult::NotDevirtualized {
reason: "Ambiguous target".to_string(),
});
self.whole_program_stats.devirt_failures += 1;
}
}
} else if !vtable.has_subclasses {
self.devirt_results.push(DevirtResult::AbstractClass);
}
}
}
}
fn can_devirtualize(&self, vtable: &VTableSummary, combined_index: &CombinedIndex) -> bool {
if !vtable.has_subclasses {
return true;
}
let all_known = combined_index.modules.iter().all(|m| {
m.vtable_summaries
.iter()
.all(|vt| combined_index.vtable_to_module.contains_key(&vt.guid))
});
all_known
}
fn resolve_virtual_call_target(
&self,
vtable: &VTableSummary,
entry_guid: u64,
_index: usize,
combined_index: &CombinedIndex,
) -> Option<String> {
for module in &combined_index.modules {
if let Some(func) = module
.function_summaries
.iter()
.find(|f| f.guid == entry_guid)
{
return Some(func.name.clone());
}
}
if let Some(&mod_idx) = combined_index.alias_to_module.get(&entry_guid) {
let module = &combined_index.modules[mod_idx];
for alias in &module.alias_summaries {
if alias.guid == entry_guid {
for func in &module.function_summaries {
if func.guid == alias.aliasee_guid {
return Some(func.name.clone());
}
}
}
}
}
None
}
pub fn is_demoted(&self, guid: u64) -> bool {
self.demoted_globals.contains(&guid)
}
pub fn get_stats(&self) -> &WholeProgramStats {
&self.whole_program_stats
}
pub fn get_devirt_results(&self) -> &[DevirtResult] {
&self.devirt_results
}
pub fn get_dead_globals(&self) -> &[DeadGlobalResult] {
&self.dead_globals
}
}
impl Default for X86LTOWholeProgram {
fn default() -> Self {
X86LTOWholeProgram::new()
}
}
pub struct X86LTOPlugin {
pub status: LTOPluginStatus,
pub input_files: Vec<PluginClaimRequest>,
pub claimed_files: std::collections::HashSet<String>,
pub plugin_symbols: Vec<PluginSymbolInfo>,
pub symbol_modules: std::collections::HashMap<String, usize>,
pub optimized_objects: Vec<Vec<u8>>,
pub lto_opt_level: u8,
pub cpu: String,
pub features: String,
pub preserve_debug: bool,
pub api_version: u32,
pub cache_dir: Option<String>,
pub cleanup_callbacks: Vec<Box<dyn FnOnce() + Send>>,
pub coff_lto_enabled: bool,
pub macho_lto_library: Option<String>,
pub plugin_diags: Vec<String>,
}
impl X86LTOPlugin {
pub fn new() -> Self {
X86LTOPlugin {
status: LTOPluginStatus::Ready,
input_files: Vec::new(),
claimed_files: std::collections::HashSet::new(),
plugin_symbols: Vec::new(),
symbol_modules: std::collections::HashMap::new(),
optimized_objects: Vec::new(),
lto_opt_level: 2,
cpu: String::from("x86-64"),
features: String::new(),
preserve_debug: false,
api_version: LTO_API_VERSION,
cache_dir: None,
cleanup_callbacks: Vec::new(),
coff_lto_enabled: false,
macho_lto_library: None,
plugin_diags: Vec::new(),
}
}
pub fn onload(&mut self) -> LTOPluginStatus {
self.status = LTOPluginStatus::Ready;
self.plugin_diags.push("X86 LTO plugin loaded.".to_string());
self.log_diag("LTOPlugin", "onload called, plugin ready");
self.status.clone()
}
pub fn claim_file(&mut self, request: &PluginClaimRequest) -> ClaimResult {
self.log_diag("claim_file", &format!("Examining: {}", request.file_path));
if self.is_bitcode(&request.file_data) {
self.claimed_files.insert(request.file_path.clone());
self.input_files.push(request.clone());
self.log_diag("claim_file", &format!("Claimed: {}", request.file_path));
self.status = LTOPluginStatus::Processing;
return ClaimResult::Claimed;
}
if self.is_thin_lto_bitcode(&request.file_data) {
self.claimed_files.insert(request.file_path.clone());
self.input_files.push(request.clone());
self.log_diag(
"claim_file",
&format!("Claimed (ThinLTO): {}", request.file_path),
);
self.status = LTOPluginStatus::Processing;
return ClaimResult::Claimed;
}
ClaimResult::NotClaimed
}
pub fn all_symbols_read(&mut self) -> Vec<Vec<u8>> {
self.log_diag("all_symbols_read", "Starting LTO optimization pipeline");
if self.input_files.is_empty() {
self.status = LTOPluginStatus::Completed;
return Vec::new();
}
let modules: Vec<Vec<u8>> = self
.input_files
.iter()
.map(|f| f.file_data.clone())
.collect();
let optimized = self.run_lto_pipeline(&modules);
let native_objects: Vec<Vec<u8>> = optimized
.iter()
.map(|opt_data| self.codegen_to_native(opt_data))
.collect();
self.optimized_objects = native_objects.clone();
self.status = LTOPluginStatus::Completed;
self.log_diag(
"all_symbols_read",
&format!(
"Completed: {} optimized objects generated",
native_objects.len()
),
);
native_objects
}
pub fn cleanup(&mut self) {
self.log_diag("cleanup", "Releasing plugin resources");
while let Some(cb) = self.cleanup_callbacks.pop() {
cb();
}
self.input_files.clear();
self.claimed_files.clear();
self.plugin_symbols.clear();
self.optimized_objects.clear();
self.status = LTOPluginStatus::Ready;
}
pub fn register_cleanup<F: FnOnce() + Send + 'static>(&mut self, callback: F) {
self.cleanup_callbacks.push(Box::new(callback));
}
fn is_bitcode(&self, data: &[u8]) -> bool {
if data.len() < 4 {
return false;
}
&data[0..4] == b"BC\xC0\xDE"
}
fn is_thin_lto_bitcode(&self, data: &[u8]) -> bool {
if data.len() < 20 {
return false;
}
&data[0..4] == b"BC\xC0\xDE" && data.get(16..20) == Some(b"THIN")
}
fn run_lto_pipeline(&self, modules: &[Vec<u8>]) -> Vec<Vec<u8>> {
modules
.iter()
.map(|m| {
let mut opt = m.clone();
if opt.len() >= 4 {
opt[3] |= 0x80; }
opt
})
.collect()
}
fn codegen_to_native(&self, bitcode: &[u8]) -> Vec<u8> {
let mut obj = Vec::new();
obj.extend_from_slice(&[0x7f, b'E', b'L', b'F']); obj.push(2); obj.push(1); obj.push(1); obj.push(0); obj.push(0); obj.extend_from_slice(&[0; 7]);
obj.extend_from_slice(&[0x01, 0x00]); obj.extend_from_slice(&[0x3E, 0x00]); obj.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); obj.extend_from_slice(&[0; 40]);
obj.extend_from_slice(b".text");
obj.extend_from_slice(&[0; 4]);
obj.extend_from_slice(&(bitcode.len() as u32).to_le_bytes());
obj.extend_from_slice(bitcode);
obj
}
pub fn parse_coff_option(&mut self, opt: &str) {
if opt == "/opt:lldlto" {
self.coff_lto_enabled = true;
self.log_diag("coff_plugin", "COFF LTO enabled via /opt:lldlto");
}
}
pub fn set_macho_lto_library(&mut self, path: &str) {
self.macho_lto_library = Some(path.to_string());
self.log_diag("macho_plugin", &format!("Mach-O LTO library set: {}", path));
}
pub fn add_plugin_symbol(&mut self, sym: PluginSymbolInfo) {
self.plugin_symbols.push(sym);
}
pub fn get_plugin_symbols(&self) -> &[PluginSymbolInfo] {
&self.plugin_symbols
}
pub fn get_api_version(&self) -> u32 {
self.api_version
}
pub fn set_opt_level(&mut self, level: u8) {
self.lto_opt_level = level.min(3);
}
pub fn set_cpu(&mut self, cpu: &str) {
self.cpu = cpu.to_string();
}
pub fn set_features(&mut self, features: &str) {
self.features = features.to_string();
}
pub fn set_preserve_debug(&mut self, preserve: bool) {
self.preserve_debug = preserve;
}
pub fn is_error(&self) -> bool {
matches!(self.status, LTOPluginStatus::Error(_))
}
pub fn get_status(&self) -> <OPluginStatus {
&self.status
}
pub fn get_diagnostics(&self) -> &[String] {
&self.plugin_diags
}
fn log_diag(&mut self, source: &str, message: &str) {
self.plugin_diags.push(format!("[{}] {}", source, message));
}
pub fn claimed_count(&self) -> usize {
self.claimed_files.len()
}
pub fn optimized_count(&self) -> usize {
self.optimized_objects.len()
}
pub fn extract_symbols(&self, data: &[u8]) -> Vec<PluginSymbolInfo> {
let mut syms = Vec::new();
if data.len() < 4 || &data[0..4] != b"BC\xC0\xDE" {
return syms;
}
let mut pos = 4;
while pos + 8 <= data.len() {
if data[pos] == 0x0C {
let name_len = data[pos + 1] as usize;
if pos + 2 + name_len + 8 <= data.len() {
let name =
String::from_utf8_lossy(&data[pos + 2..pos + 2 + name_len]).to_string();
let flags = data[pos + 2 + name_len];
let size = u64::from_le_bytes([
data[pos + 2 + name_len + 1],
data[pos + 2 + name_len + 2],
data[pos + 2 + name_len + 3],
data[pos + 2 + name_len + 4],
data[pos + 2 + name_len + 5],
data[pos + 2 + name_len + 6],
data[pos + 2 + name_len + 7],
data[pos + 2 + name_len + 8],
]);
let section_kind = match flags & 0x0F {
0 => PluginSectionKind::Text,
1 => PluginSectionKind::Data,
2 => PluginSectionKind::BSS,
3 => PluginSectionKind::ReadOnly,
4 => PluginSectionKind::TLS,
_ => PluginSectionKind::Other,
};
syms.push(PluginSymbolInfo {
name,
section_kind,
visibility: SymbolVisibility::Default,
is_global: (flags & 0x10) != 0,
is_definition: (flags & 0x20) != 0,
is_common: (flags & 0x40) != 0,
is_weak: (flags & 0x80) != 0,
size,
alignment: 16,
comdat_key: None,
});
}
pos += 2;
}
pos += 1;
}
syms
}
}
impl Default for X86LTOPlugin {
fn default() -> Self {
X86LTOPlugin::new()
}
}
pub fn compute_fnv1a_64_full(data: &[u8]) -> u64 {
let mut hash: u64 = FNV64_OFFSET_BASIS;
for &byte in data {
hash ^= byte as u64;
hash = hash.wrapping_mul(FNV64_PRIME);
}
hash
}
pub fn compute_guid_from_name(name: &str) -> u64 {
compute_fnv1a_64_full(name.as_bytes())
}
pub fn align_up_full(value: u64, align: u64) -> u64 {
if align == 0 {
return value;
}
((value + align - 1) / align) * align
}
pub fn align_down_full(value: u64, align: u64) -> u64 {
if align == 0 {
return value;
}
(value / align) * align
}
pub fn compute_summary_hash(data: &[u8]) -> u64 {
let mut a: u64 = 0x6745_2301;
let mut b: u64 = 0xEFCD_AB89;
let mut c: u64 = 0x98BA_DCFE;
let mut d: u64 = 0x1032_5476;
for chunk in data.chunks(8) {
let mut buf = [0u8; 8];
for (i, &bval) in chunk.iter().enumerate() {
buf[i] = bval;
}
let val = u64::from_le_bytes(buf);
a = a.wrapping_add(val).wrapping_mul(0x9E37_79B9);
a = a.rotate_left(13);
b = b.wrapping_sub(val).wrapping_mul(0x79B9_9E37);
b = b.rotate_left(17);
c = c.wrapping_add(a).rotate_left(7);
d = d.wrapping_sub(b).rotate_left(11);
}
a ^ c ^ (b.wrapping_add(d)).rotate_left(23)
}
pub fn hash_call_graph_edge(caller: u64, callee: u64) -> u64 {
let mut hash: u64 = caller;
hash ^= callee.wrapping_mul(FNV64_PRIME);
hash = hash.rotate_left(31);
hash ^= callee;
hash.rotate_left(17)
}
pub fn is_eligible_for_import(func: &FunctionSummary) -> bool {
!func.has_inline_asm
&& !func.has_varargs
&& func.inst_count <= 10000 && !func.cfi_enabled }
pub fn is_constant_propagatable(global: &GlobalVarSummary) -> bool {
global.is_constant
&& !global.is_tls
&& global.size <= 256 && global.alignment <= 64
}
pub fn hotness_percentile(guid: u64, combined_index: &CombinedIndex) -> f64 {
let total_calls: u64 = combined_index
.modules
.iter()
.flat_map(|m| &m.function_summaries)
.map(|f| f.call_count as u64)
.sum();
if total_calls == 0 {
return 0.0;
}
let func_calls: u64 = combined_index
.modules
.iter()
.flat_map(|m| &m.function_summaries)
.filter(|f| f.guid == guid)
.map(|f| f.call_count as u64)
.sum();
func_calls as f64 / total_calls as f64
}
#[cfg(test)]
mod tests {
use super::*;
fn make_module_summary(path: &str, funcs: &[(&str, u64)]) -> ModuleSummaryIndex {
let mut summary = ModuleSummaryIndex {
module_hash: compute_fnv1a_64_full(path.as_bytes()),
module_path: path.to_string(),
target_triple: String::from("x86_64-unknown-linux-gnu"),
function_summaries: Vec::new(),
global_summaries: Vec::new(),
vtable_summaries: Vec::new(),
alias_summaries: Vec::new(),
instruction_count: 0,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
for (i, (name, callee)) in funcs.iter().enumerate() {
let mut callees = Vec::new();
if *callee != 0 {
callees.push(*callee);
}
let fs = FunctionSummary {
name: name.to_string(),
guid: compute_guid_from_name(name),
inst_count: 100 + i as u32 * 10,
call_count: 1 + i as u32,
callees,
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: i == 0,
is_local: false,
param_count: 2,
return_type: ReturnTypeClass::Integer,
call_profile: Vec::new(),
cfi_enabled: false,
};
summary.function_summaries.push(fs);
}
summary
}
fn make_global_summary(name: &str, size: u64, is_const: bool) -> GlobalVarSummary {
GlobalVarSummary {
name: name.to_string(),
guid: compute_guid_from_name(name),
size,
alignment: 8,
is_constant: is_const,
is_read_only: is_const,
is_external: true,
refs: Vec::new(),
init_hash: 0,
is_tls: false,
linkage: GlobalLinkageKind::External,
}
}
fn make_vtable_summary(name: &str, entries: Vec<u64>, class_name: &str) -> VTableSummary {
VTableSummary {
name: name.to_string(),
guid: compute_guid_from_name(name),
entries,
class_name: class_name.to_string(),
has_subclasses: false,
hierarchy_depth: 0,
}
}
fn make_bitcode_data() -> Vec<u8> {
let mut data = vec![
b'B', b'C', 0xC0, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, b'T', b'H', b'I', b'N', ];
data.push(0x14);
data.push(0x00);
data.push(3); data.push(b'f');
data.push(b'o');
data.push(b'o');
data.extend_from_slice(&100u32.to_le_bytes());
data.extend_from_slice(&5u32.to_le_bytes());
data.push(220);
data.push(0x04); data.push(0);
data
}
fn make_thin_bitcode_data() -> Vec<u8> {
let mut data = vec![
b'B', b'C', 0xC0, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, b'T', b'H', b'I', b'N',
];
data.push(0x14);
data.push(0x00);
data.push(3);
data.push(b'b');
data.push(b'a');
data.push(b'r');
data.extend_from_slice(&50u32.to_le_bytes());
data.extend_from_slice(&3u32.to_le_bytes());
data.push(150);
data.push(0x04);
data.push(0);
data
}
#[test]
fn test_thin_lto_new() {
let thin = X86ThinLTOFull::new();
assert_eq!(thin.opt_level, 2);
assert_eq!(thin.num_threads, 4);
assert_eq!(thin.hotness_threshold, THINLTO_HOTNESS_THRESHOLD);
assert_eq!(thin.max_import_depth, THINLTO_MAX_IMPORT_DEPTH);
}
#[test]
fn test_thin_lto_new_aggressive() {
let thin = X86ThinLTOFull::new_aggressive();
assert_eq!(thin.hotness_threshold, 100);
assert_eq!(thin.max_import_depth, 10);
assert_eq!(thin.num_threads, 8);
}
#[test]
fn test_thin_lto_set_cache_dir() {
let mut thin = X86ThinLTOFull::new();
thin.set_cache_dir("/tmp/thinlto_cache");
assert_eq!(thin.cache_dir, Some("/tmp/thinlto_cache".to_string()));
assert_eq!(thin.cache_mode, ThinLTOCacheMode::ComprehensiveKey);
}
#[test]
fn test_thin_lto_set_cache_mode() {
let mut thin = X86ThinLTOFull::new();
thin.set_cache_mode(ThinLTOCacheMode::Disabled);
assert_eq!(thin.cache_mode, ThinLTOCacheMode::Disabled);
thin.set_cache_mode(ThinLTOCacheMode::Incremental);
assert_eq!(thin.cache_mode, ThinLTOCacheMode::Incremental);
}
#[test]
fn test_thin_lto_set_opt_level() {
let mut thin = X86ThinLTOFull::new();
thin.set_opt_level(3);
assert_eq!(thin.opt_level, 3);
thin.set_opt_level(10); assert_eq!(thin.opt_level, 3);
}
#[test]
fn test_thin_lto_set_num_threads() {
let mut thin = X86ThinLTOFull::new();
thin.set_num_threads(16);
assert_eq!(thin.num_threads, 16);
thin.set_num_threads(0); assert_eq!(thin.num_threads, 1);
thin.set_num_threads(500); assert_eq!(thin.num_threads, 256);
}
#[test]
fn test_compute_module_summary() {
let mut thin = X86ThinLTOFull::new();
let data = make_bitcode_data();
let summary = thin.compute_module_summary("test.o", &data);
assert!(!summary.module_path.is_empty());
assert!(summary.module_hash != 0);
assert!(summary.is_thin_lto);
}
#[test]
fn test_compute_module_summary_non_thin() {
let mut thin = X86ThinLTOFull::new();
let data = {
let mut v = vec![0u8; 16 + 4];
v[0] = b'B';
v[1] = b'C';
v[2] = 0xC0;
v[3] = 0xDE;
v
}; let summary = thin.compute_module_summary("regular.o", &data);
assert!(!summary.is_thin_lto);
}
#[test]
fn test_compute_module_summary_empty_data() {
let mut thin = X86ThinLTOFull::new();
let summary = thin.compute_module_summary("empty.o", &[]);
assert!(summary.function_summaries.is_empty());
assert_eq!(summary.instruction_count, 0);
}
#[test]
fn test_extract_function_summaries() {
let thin = X86ThinLTOFull::new();
let data = make_thin_bitcode_data();
let summaries = thin.extract_function_summaries(&data, 0);
let has_bar = summaries.iter().any(|fs| fs.name == "bar");
assert!(has_bar || summaries.is_empty()); }
#[test]
fn test_build_combined_index() {
let mut thin = X86ThinLTOFull::new();
let m1 = make_module_summary("mod1.o", &[("main", 0), ("helper", 0)]);
let m2 = make_module_summary("mod2.o", &[("other", 0)]);
thin.build_combined_index(vec![m1, m2]);
let ci = thin.get_combined_index();
assert!(ci.is_some());
let ci_ref = ci.unwrap();
assert_eq!(ci_ref.modules.len(), 2);
assert_eq!(ci_ref.total_functions, 3);
}
#[test]
fn test_combined_index_function_to_module() {
let mut thin = X86ThinLTOFull::new();
let m1 = make_module_summary("mod1.o", &[("main", 0)]);
let m2 = make_module_summary("mod2.o", &[("helper", 0)]);
let guid_main = compute_guid_from_name("main");
let guid_helper = compute_guid_from_name("helper");
thin.build_combined_index(vec![m1.clone(), m2.clone()]);
let ci = thin.get_combined_index().unwrap();
assert_eq!(ci.function_to_module.get(&guid_main), Some(&0));
assert_eq!(ci.function_to_module.get(&guid_helper), Some(&1));
}
#[test]
fn test_combined_index_export_guids() {
let mut thin = X86ThinLTOFull::new();
let m1 = make_module_summary("mod1.o", &[("main", 0), ("helper", 0)]);
thin.build_combined_index(vec![m1]);
let ci = thin.get_combined_index().unwrap();
let guid_main = compute_guid_from_name("main");
let guid_helper = compute_guid_from_name("helper");
assert!(ci.export_guids.contains(&guid_main));
assert!(ci.export_guids.contains(&guid_helper));
}
#[test]
fn test_decide_imports_empty() {
let mut thin = X86ThinLTOFull::new();
thin.decide_imports();
assert!(thin.import_decisions.is_empty());
}
#[test]
fn test_decide_imports_basic() {
let mut thin = X86ThinLTOFull::new();
thin.hotness_threshold = 50;
let guid_other = compute_guid_from_name("other");
let m1 = make_module_summary("mod1.o", &[("main", guid_other)]);
let m2 = make_module_summary("mod2.o", &[("other", 0)]);
thin.build_combined_index(vec![m1, m2]);
thin.decide_imports();
}
#[test]
fn test_decide_imports_with_pgo() {
let mut thin = X86ThinLTOFull::new();
thin.hotness_threshold = 50;
let guid_other = compute_guid_from_name("other");
let m1 = make_module_summary("mod1.o", &[("main", guid_other)]);
let m2 = make_module_summary("mod2.o", &[("other", 0)]);
thin.pgo_profiles.insert(
guid_other,
PGOProfileEntry {
guid: guid_other,
entry_count: 100_000,
total_frequency: 500_000,
max_frequency: 100_000,
},
);
thin.build_combined_index(vec![m1, m2]);
thin.decide_imports();
}
#[test]
fn test_run_distributed_backend() {
let mut thin = X86ThinLTOFull::new();
let modules = vec![
{
let mut v = vec![0u8; 32 + 4];
v[0] = b'B';
v[1] = b'C';
v[2] = 0xC0;
v[3] = 0xDE;
v
},
{
let mut v = vec![0u8; 32 + 4];
v[0] = b'B';
v[1] = b'C';
v[2] = 0xC0;
v[3] = 0xDE;
v
},
];
let results = thin.run_distributed_backend(&modules);
assert_eq!(results.len(), 2);
for r in results {
assert!(r.len() >= 4);
assert_eq!(&r[0..4], b"\x7fELF");
}
}
#[test]
fn test_thin_lto_cache_lookup_and_store() {
let mut thin = X86ThinLTOFull::new();
let hash = 0xABCDEF0123456789;
let data = vec![0x90; 64];
assert!(thin.cache_lookup(hash).is_none());
thin.cache_store(hash, data.clone());
let cached = thin.cache_lookup(hash);
assert!(cached.is_some());
assert_eq!(cached.unwrap(), &data);
}
#[test]
fn test_cache_hit_ratio() {
let mut thin = X86ThinLTOFull::new();
assert_eq!(thin.cache_hit_ratio(), 0.0);
thin.stats.cache_hits = 3;
thin.stats.cache_misses = 1;
assert_eq!(thin.cache_hit_ratio(), 0.75);
thin.stats.cache_hits = 5;
thin.stats.cache_misses = 5;
assert_eq!(thin.cache_hit_ratio(), 0.5);
}
#[test]
fn test_clear_cache() {
let mut thin = X86ThinLTOFull::new();
thin.cache_store(1, vec![1, 2, 3]);
thin.cache_store(2, vec![4, 5, 6]);
thin.clear_cache();
assert!(thin.cache_lookup(1).is_none());
assert!(thin.cache_lookup(2).is_none());
}
#[test]
fn test_reset_stats() {
let mut thin = X86ThinLTOFull::new();
thin.stats.modules_processed = 10;
thin.stats.functions_imported = 50;
thin.stats.cache_hits = 5;
thin.reset_stats();
assert_eq!(thin.stats.modules_processed, 0);
assert_eq!(thin.stats.functions_imported, 0);
assert_eq!(thin.stats.cache_hits, 0);
}
#[test]
fn test_get_import_decisions() {
let mut thin = X86ThinLTOFull::new();
let hash = 0xDEAD;
let decisions = thin.get_import_decisions(hash);
assert!(decisions.is_none());
}
#[test]
fn test_load_pgo_profile() {
let mut thin = X86ThinLTOFull::new();
let mut buf = Vec::new();
buf.extend_from_slice(&[0; 8]); buf.extend_from_slice(&1u64.to_le_bytes());
buf.extend_from_slice(&5000u64.to_le_bytes());
buf.extend_from_slice(&25000u64.to_le_bytes());
thin.load_pgo_profile(&buf);
assert!(thin.pgo_profiles.contains_key(&1));
let entry = &thin.pgo_profiles[&1];
assert_eq!(entry.entry_count, 5000);
assert_eq!(entry.total_frequency, 25000);
}
#[test]
fn test_load_pgo_profile_empty() {
let mut thin = X86ThinLTOFull::new();
thin.load_pgo_profile(&[]);
assert!(thin.pgo_profiles.is_empty());
}
#[test]
fn test_thin_lto_default() {
let thin = X86ThinLTOFull::default();
assert_eq!(thin.opt_level, 2);
}
#[test]
fn test_thin_lto_compute_module_summary_multiple_functions() {
let mut thin = X86ThinLTOFull::new();
let mut data = {
let mut v = vec![0u8; 12 + 4];
v[0] = b'B';
v[1] = b'C';
v[2] = 0xC0;
v[3] = 0xDE;
v
};
data.extend_from_slice(b"THIN");
data.push(0x14);
data.push(0x00);
data.push(6);
data.extend_from_slice(b"func_a");
data.extend_from_slice(&50u32.to_le_bytes()); data.extend_from_slice(&2u32.to_le_bytes()); data.push(180); data.push(0x04); data.push(0); data.push(0x14);
data.push(0x00);
data.push(6);
data.extend_from_slice(b"func_b");
data.extend_from_slice(&75u32.to_le_bytes());
data.extend_from_slice(&1u32.to_le_bytes());
data.push(190);
data.push(0x04);
data.push(0);
let summary = thin.compute_module_summary("multi.o", &data);
assert!(summary.function_summaries.len() >= 1);
assert!(summary.instruction_count > 0);
}
#[test]
fn test_internalization_new() {
let intern = X86LTOInternalization::new();
assert!(intern.resolution_table.is_empty());
assert!(intern.export_list.is_empty());
assert!(intern.internalized.is_empty());
assert_eq!(intern.internalization_stats.total_symbols, 0);
}
#[test]
fn test_build_resolution_table() {
let mut intern = X86LTOInternalization::new();
let m1 = make_module_summary("mod1.o", &[("main", 0), ("helper", 0)]);
let m2 = make_module_summary("mod2.o", &[("other", 0)]);
let mut combined = CombinedIndex {
modules: vec![m1, m2],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 3,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
if f.is_external {
combined.export_guids.insert(f.guid);
}
}
}
intern.build_resolution_table(&combined);
assert_eq!(intern.resolution_table.len(), 3);
assert_eq!(intern.internalization_stats.total_symbols, 3);
}
#[test]
fn test_compute_export_list() {
let mut intern = X86LTOInternalization::new();
let m1 = make_module_summary("mod1.o", &[("main", 0), ("internal_static", 0)]);
let mut module = m1;
if let Some(fs) = module
.function_summaries
.iter_mut()
.find(|f| f.name == "internal_static")
{
fs.is_external = false;
}
let mut combined = CombinedIndex {
modules: vec![module],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 2,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
if f.is_external {
combined.export_guids.insert(f.guid);
}
}
}
let visibility_overrides = std::collections::HashMap::new();
let dllexport = vec![];
intern.compute_export_list(&combined, &visibility_overrides, &dllexport);
let guid_main = compute_guid_from_name("main");
let guid_internal = compute_guid_from_name("internal_static");
assert!(intern.export_list.contains(&guid_main));
assert!(!intern.export_list.contains(&guid_internal));
}
#[test]
fn test_internalize_non_exported() {
let mut intern = X86LTOInternalization::new();
let m1 = make_module_summary("mod1.o", &[("main", 0), ("helper", 0)]);
let mut combined = CombinedIndex {
modules: vec![m1],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 2,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
}
}
combined.export_guids.insert(compute_guid_from_name("main"));
intern.build_resolution_table(&combined);
intern.compute_export_list(&combined, &std::collections::HashMap::new(), &[]);
intern.internalize_non_exported();
let guid_main = compute_guid_from_name("main");
let guid_helper = compute_guid_from_name("helper");
assert!(intern.export_list.contains(&guid_main));
assert!(intern.is_internalized(guid_helper));
assert!(!intern.is_internalized(guid_main));
}
#[test]
fn test_select_comdat_leaders_first() {
let mut intern = X86LTOInternalization::new();
intern.add_comdat_group("group1".to_string(), vec![10, 20, 30]);
intern.select_comdat_leaders(ComdatSelectionKind::Any);
assert_eq!(intern.comdat_leaders.get("group1"), Some(&10));
assert_eq!(intern.internalization_stats.comdat_groups_resolved, 1);
}
#[test]
fn test_select_comdat_leaders_largest() {
let mut intern = X86LTOInternalization::new();
intern.add_comdat_group("group1".to_string(), vec![10, 20, 30]);
intern.select_comdat_leaders(ComdatSelectionKind::Largest);
assert_eq!(intern.comdat_leaders.get("group1"), Some(&30));
}
#[test]
fn test_prune_linkonce_odr() {
let mut intern = X86LTOInternalization::new();
let m1 = make_module_summary("a.o", &[("dup_func", 0)]);
let m2 = make_module_summary("b.o", &[("dup_func", 0)]);
let mut combined = CombinedIndex {
modules: vec![m1, m2],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 2,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
}
}
intern.build_resolution_table(&combined);
intern.prune_linkonce_odr();
assert!(intern.linkonce_odr_pruned.len() > 0);
assert_eq!(intern.internalization_stats.linkonce_odr_pruned, 1);
}
#[test]
fn test_is_internalized() {
let mut intern = X86LTOInternalization::new();
let entry = SymbolResolutionEntry {
name: "test".to_string(),
guid: 42,
decision: InternalizationDecision::Internalize,
visibility: SymbolVisibility::Default,
is_exported: false,
is_imported: false,
is_dllexport: false,
comdat_leader: None,
external_refs: 0,
survives: true,
};
intern.resolution_table.push(entry);
intern.internalized.insert(42);
assert!(intern.is_internalized(42));
assert!(!intern.is_internalized(99));
}
#[test]
fn test_get_decision() {
let mut intern = X86LTOInternalization::new();
let entry = SymbolResolutionEntry {
name: "keep".to_string(),
guid: 100,
decision: InternalizationDecision::KeepGlobal,
visibility: SymbolVisibility::Default,
is_exported: true,
is_imported: false,
is_dllexport: false,
comdat_leader: None,
external_refs: 0,
survives: true,
};
intern.resolution_table.push(entry);
assert_eq!(
intern.get_decision(100),
Some(InternalizationDecision::KeepGlobal)
);
assert_eq!(intern.get_decision(999), None);
}
#[test]
fn test_clear_internalization() {
let mut intern = X86LTOInternalization::new();
intern.export_list.insert(1);
intern.internalized.insert(2);
intern.build_resolution_table(&CombinedIndex {
modules: vec![],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 0,
});
intern.clear();
assert!(intern.resolution_table.is_empty());
assert!(intern.export_list.is_empty());
assert!(intern.internalized.is_empty());
assert_eq!(intern.internalization_stats.total_symbols, 0);
}
#[test]
fn test_internalization_default() {
let intern = X86LTOInternalization::default();
assert!(intern.resolution_table.is_empty());
}
#[test]
fn test_internalization_dllexport() {
let mut intern = X86LTOInternalization::new();
let m1 = make_module_summary("mod1.o", &[("dllexport_func", 0)]);
let mut combined = CombinedIndex {
modules: vec![m1],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 1,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
}
}
let dllexport = vec!["dllexport_func".to_string()];
intern.compute_export_list(&combined, &std::collections::HashMap::new(), &dllexport);
let guid = compute_guid_from_name("dllexport_func");
assert!(intern.export_list.contains(&guid));
}
#[test]
fn test_debug_info_new() {
let di = X86LTODebugInfo::new();
assert_eq!(di.merge_mode, LTODebugMergeMode::Full);
assert!(di.compile_units.is_empty());
assert!(di.type_units.is_empty());
}
#[test]
fn test_set_merge_mode() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::Skeleton);
assert_eq!(di.merge_mode, LTODebugMergeMode::Skeleton);
di.set_merge_mode(LTODebugMergeMode::None);
assert_eq!(di.merge_mode, LTODebugMergeMode::None);
}
#[test]
fn test_set_dedup_strategy() {
let mut di = X86LTODebugInfo::new();
di.set_dedup_strategy(TypeUnitDedupStrategy::Structural);
assert_eq!(di.dedup_strategy, TypeUnitDedupStrategy::Structural);
}
#[test]
fn test_add_compile_unit() {
let mut di = X86LTODebugInfo::new();
let cu = DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "main.c".to_string(),
producer: "rustc".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: vec![0; 32],
range_list_data: vec![0; 16],
};
di.add_compile_unit(cu);
assert_eq!(di.compile_units.len(), 1);
}
#[test]
fn test_add_type_units() {
let mut di = X86LTODebugInfo::new();
let tus = vec![
DwarfTypeUnit {
type_signature: 0xABCD,
offset: 0,
length: 64,
module_hash: 1,
type_name: Some("struct Foo".to_string()),
},
DwarfTypeUnit {
type_signature: 0xABCD, offset: 64,
length: 64,
module_hash: 2,
type_name: Some("struct Foo".to_string()),
},
];
di.add_type_units(tus);
assert_eq!(di.type_units.len(), 2);
}
#[test]
fn test_deduplicate_type_units_hash() {
let mut di = X86LTODebugInfo::new();
di.set_dedup_strategy(TypeUnitDedupStrategy::HashBased);
di.add_type_units(vec![
DwarfTypeUnit {
type_signature: 111,
offset: 0,
length: 64,
module_hash: 1,
type_name: Some("A".to_string()),
},
DwarfTypeUnit {
type_signature: 111, offset: 0,
length: 64,
module_hash: 2,
type_name: Some("A".to_string()),
},
DwarfTypeUnit {
type_signature: 222,
offset: 0,
length: 64,
module_hash: 3,
type_name: Some("B".to_string()),
},
]);
di.deduplicate_type_units();
assert_eq!(di.dedup_type_units.len(), 2); assert_eq!(di.debug_stats.type_units_deduplicated, 1);
assert_eq!(di.debug_stats.type_units_kept, 2);
}
#[test]
fn test_deduplicate_type_units_name_based() {
let mut di = X86LTODebugInfo::new();
di.set_dedup_strategy(TypeUnitDedupStrategy::NameBased);
di.add_type_units(vec![
DwarfTypeUnit {
type_signature: 111,
offset: 0,
length: 64,
module_hash: 1,
type_name: Some("struct Foo".to_string()),
},
DwarfTypeUnit {
type_signature: 222, offset: 0,
length: 64,
module_hash: 2,
type_name: Some("struct Foo".to_string()),
},
]);
di.deduplicate_type_units();
assert_eq!(di.dedup_type_units.len(), 1);
}
#[test]
fn test_merge_debug_info_full() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::Full);
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "main.c".to_string(),
producer: "clang".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: vec![0; 32],
range_list_data: vec![0; 16],
});
let merged = di.merge_debug_info(&[]);
assert!(!merged.is_empty());
assert_eq!(di.debug_stats.cus_merged, 1);
}
#[test]
fn test_merge_debug_info_none() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::None);
let merged = di.merge_debug_info(&[]);
assert!(merged.is_empty());
}
#[test]
fn test_merge_debug_info_skeleton() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::Skeleton);
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 42,
comp_dir: "/src".to_string(),
source_file: "foo.c".to_string(),
producer: "rustc".to_string(),
low_pc: 0x4000,
high_pc: 0x5000,
line_table_data: vec![],
range_list_data: vec![],
});
let merged = di.merge_debug_info(&[]);
assert!(!merged.is_empty());
assert_eq!(di.skeleton_cus.len(), 1);
assert_eq!(di.skeleton_cus[0].module_hash, 42);
assert_eq!(di.debug_stats.skeleton_cus_generated, 1);
}
#[test]
fn test_merge_line_tables() {
let mut di = X86LTODebugInfo::new();
let mut line_data = Vec::new();
line_data.push(7); line_data.extend_from_slice(b"main.rs");
line_data.extend_from_slice(&42u32.to_le_bytes());
line_data.extend_from_slice(&10u32.to_le_bytes());
line_data.extend_from_slice(&0x1000u64.to_le_bytes());
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "main.rs".to_string(),
producer: "rustc".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: line_data,
range_list_data: vec![],
});
di.merge_line_tables();
assert!(!di.line_table.is_empty());
assert_eq!(di.line_table[0].line, 42);
assert_eq!(di.line_table[0].address, 0x1000);
assert_eq!(di.debug_stats.line_entries_merged, 1);
}
#[test]
fn test_lookup_address() {
let mut di = X86LTODebugInfo::new();
di.line_table.push(CrossModuleLineEntry {
file: "test.rs".to_string(),
line: 100,
column: 5,
address: 0x4000,
module_index: 0,
inline_depth: 0,
});
di.line_table.push(CrossModuleLineEntry {
file: "test.rs".to_string(),
line: 200,
column: 5,
address: 0x5000,
module_index: 0,
inline_depth: 0,
});
let entry = di.lookup_address(0x4000);
assert!(entry.is_some());
assert_eq!(entry.unwrap().line, 100);
let entry2 = di.lookup_address(0x5000);
assert!(entry2.is_some());
assert_eq!(entry2.unwrap().line, 200);
let missing = di.lookup_address(0x9999);
assert!(missing.is_none());
}
#[test]
fn test_generate_skeleton_for_module() {
let mut di = X86LTODebugInfo::new();
di.skeleton_cus.push(SkeletonCompileUnit {
module_hash: 0xCAFE,
object_path: "foo.o".to_string(),
comp_dir: "/src".to_string(),
dwo_name: Some("foo.o.dwo".to_string()),
dwarf_version: 5,
address_size: 8,
});
let sk = di.generate_skeleton_for_module(0xCAFE);
assert!(sk.is_some());
assert_eq!(sk.unwrap().object_path, "foo.o");
let missing = di.generate_skeleton_for_module(0xDEAD);
assert!(missing.is_none());
}
#[test]
fn test_clear_debug_info() {
let mut di = X86LTODebugInfo::new();
di.compile_units.push(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "main.c".to_string(),
producer: "clang".to_string(),
low_pc: 0,
high_pc: 1000,
line_table_data: vec![],
range_list_data: vec![],
});
di.type_units.push(DwarfTypeUnit {
type_signature: 1,
offset: 0,
length: 64,
module_hash: 1,
type_name: None,
});
di.clear();
assert!(di.compile_units.is_empty());
assert!(di.type_units.is_empty());
assert_eq!(di.debug_stats.cus_merged, 0);
}
#[test]
fn test_debug_info_default() {
let di = X86LTODebugInfo::default();
assert_eq!(di.merge_mode, LTODebugMergeMode::Full);
}
#[test]
fn test_merge_frame_data() {
let mut di = X86LTODebugInfo::new();
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "test.c".to_string(),
producer: "clang".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: vec![1; 8], range_list_data: vec![],
});
let merged = di.merge_debug_info(&[]);
assert!(!di.frame_data.is_empty());
}
#[test]
fn test_whole_program_new() {
let wp = X86LTOWholeProgram::new();
assert!(wp.enable_alias_analysis);
assert!(wp.enable_const_prop);
assert!(wp.enable_dead_global_elim);
assert!(wp.enable_global_opt);
assert!(wp.enable_devirt);
}
#[test]
fn test_run_alias_analysis() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "test.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![
make_global_summary("g1", 8, false),
make_global_summary("g2", 16, false),
],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 2,
};
for (mi, m) in combined.modules.iter().enumerate() {
for g in &m.global_summaries {
combined.global_to_module.insert(g.guid, mi);
}
}
wp.run_alias_analysis(&combined);
assert!(!wp.alias_analyses.is_empty());
assert!(wp.whole_program_stats.alias_pairs_analyzed > 0);
}
#[test]
fn test_run_constant_propagation() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "test.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "main".to_string(),
guid: compute_guid_from_name("main"),
inst_count: 100,
call_count: 1,
callees: vec![],
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: true,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Integer,
call_profile: vec![],
cfi_enabled: false,
}],
global_summaries: vec![make_global_summary("const_global", 4, true)],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 100,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let const_guid = compute_guid_from_name("const_global");
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 1,
total_globals: 1,
};
let global_data = vec![(const_guid, vec![42, 0, 0, 0])];
wp.run_constant_propagation(&combined, &global_data);
assert!(wp.whole_program_stats.constants_propagated > 0);
}
#[test]
fn test_run_dead_global_elimination() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "test.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![make_global_summary("unused_global", 1024, false)],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 1,
};
wp.run_dead_global_elimination(&combined);
assert_eq!(wp.whole_program_stats.dead_globals_eliminated, 1);
assert_eq!(wp.whole_program_stats.bytes_freed, 1024);
}
#[test]
fn test_run_global_variable_optimization() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "test.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![make_global_summary("local_global", 64, false)],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 1,
};
wp.run_global_variable_optimization(&combined);
let guid = compute_guid_from_name("local_global");
assert!(wp.is_demoted(guid));
assert_eq!(wp.whole_program_stats.globals_demoted, 1);
}
#[test]
fn test_run_devirtualization() {
let mut wp = X86LTOWholeProgram::new();
let func_guid = compute_guid_from_name("Base::foo");
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "test.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "Base::foo".to_string(),
guid: func_guid,
inst_count: 50,
call_count: 1,
callees: vec![],
hotness: 180,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 1,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![make_vtable_summary("Base_vtable", vec![func_guid], "Base")],
alias_summaries: vec![],
instruction_count: 50,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 1,
total_globals: 0,
};
wp.run_devirtualization(&combined);
assert!(wp.whole_program_stats.devirtualized_calls > 0);
}
#[test]
fn test_is_demoted() {
let mut wp = X86LTOWholeProgram::new();
wp.demoted_globals.insert(42);
assert!(wp.is_demoted(42));
assert!(!wp.is_demoted(99));
}
#[test]
fn test_run_all() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "test.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![make_global_summary("g", 8, false)],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 1,
};
for (mi, m) in combined.modules.iter().enumerate() {
for g in &m.global_summaries {
combined.global_to_module.insert(g.guid, mi);
}
}
wp.run_all(&combined, &[]);
}
#[test]
fn test_whole_program_default() {
let wp = X86LTOWholeProgram::default();
assert!(wp.enable_alias_analysis);
}
#[test]
fn test_devirt_abstract_class() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "test.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![],
vtable_summaries: vec![VTableSummary {
name: "Abstract_vtable".to_string(),
guid: compute_guid_from_name("Abstract_vtable"),
entries: vec![],
class_name: "Abstract".to_string(),
has_subclasses: true,
hierarchy_depth: 1,
}],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 0,
};
wp.run_devirtualization(&combined);
}
#[test]
fn test_plugin_new() {
let plugin = X86LTOPlugin::new();
assert_eq!(plugin.get_api_version(), LTO_API_VERSION);
assert!(matches!(plugin.status, LTOPluginStatus::Ready));
assert!(plugin.input_files.is_empty());
}
#[test]
fn test_plugin_onload() {
let mut plugin = X86LTOPlugin::new();
let status = plugin.onload();
assert!(matches!(status, LTOPluginStatus::Ready));
assert!(!plugin.plugin_diags.is_empty());
}
#[test]
fn test_plugin_claim_file_bitcode() {
let mut plugin = X86LTOPlugin::new();
let data = make_bitcode_data();
let request = PluginClaimRequest {
file_path: "test.bc".to_string(),
file_data: data,
file_descriptor: None,
file_size: 20,
symbol_table_offset: 0,
symbol_count: 0,
};
let result = plugin.claim_file(&request);
assert_eq!(result, ClaimResult::Claimed);
assert!(plugin.claimed_files.contains("test.bc"));
assert_eq!(plugin.claimed_count(), 1);
}
#[test]
fn test_plugin_claim_file_non_bitcode() {
let mut plugin = X86LTOPlugin::new();
let request = PluginClaimRequest {
file_path: "test.o".to_string(),
file_data: {
let mut v = vec![0u8; 16 + 5];
v[0] = 0x7f;
v[1] = b'E';
v[2] = b'L';
v[3] = b'F';
v[4] = 0;
v
}, file_descriptor: None,
file_size: 20,
symbol_table_offset: 0,
symbol_count: 0,
};
let result = plugin.claim_file(&request);
assert_eq!(result, ClaimResult::NotClaimed);
assert_eq!(plugin.claimed_count(), 0);
}
#[test]
fn test_plugin_claim_file_thin_lto() {
let mut plugin = X86LTOPlugin::new();
let data = make_thin_bitcode_data();
let request = PluginClaimRequest {
file_path: "thin.bc".to_string(),
file_data: data,
file_descriptor: None,
file_size: 20,
symbol_table_offset: 0,
symbol_count: 0,
};
let result = plugin.claim_file(&request);
assert_eq!(result, ClaimResult::Claimed);
}
#[test]
fn test_plugin_all_symbols_read_empty() {
let mut plugin = X86LTOPlugin::new();
let objects = plugin.all_symbols_read();
assert!(objects.is_empty());
assert!(matches!(plugin.status, LTOPluginStatus::Completed));
}
#[test]
fn test_plugin_all_symbols_read_with_inputs() {
let mut plugin = X86LTOPlugin::new();
let data = make_bitcode_data();
plugin.input_files.push(PluginClaimRequest {
file_path: "test.bc".to_string(),
file_data: data,
file_descriptor: None,
file_size: 20,
symbol_table_offset: 0,
symbol_count: 0,
});
let objects = plugin.all_symbols_read();
assert_eq!(plugin.optimized_count(), objects.len());
assert!(matches!(plugin.status, LTOPluginStatus::Completed));
}
#[test]
fn test_plugin_cleanup() {
let mut plugin = X86LTOPlugin::new();
plugin.input_files.push(PluginClaimRequest {
file_path: "test.bc".to_string(),
file_data: vec![0; 20],
file_descriptor: None,
file_size: 20,
symbol_table_offset: 0,
symbol_count: 0,
});
plugin.claimed_files.insert("test.bc".to_string());
plugin.optimized_objects.push(vec![1, 2, 3]);
plugin.cleanup();
assert!(plugin.input_files.is_empty());
assert!(plugin.claimed_files.is_empty());
assert!(plugin.optimized_objects.is_empty());
assert!(matches!(plugin.status, LTOPluginStatus::Ready));
}
#[test]
fn test_plugin_register_cleanup() {
let mut plugin = X86LTOPlugin::new();
let called = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let called_clone = called.clone();
plugin.register_cleanup(move || {
called_clone.store(true, std::sync::atomic::Ordering::SeqCst);
});
plugin.cleanup();
assert!(called.load(std::sync::atomic::Ordering::SeqCst));
}
#[test]
fn test_plugin_coff_option() {
let mut plugin = X86LTOPlugin::new();
assert!(!plugin.coff_lto_enabled);
plugin.parse_coff_option("/opt:lldlto");
assert!(plugin.coff_lto_enabled);
}
#[test]
fn test_plugin_macho_library() {
let mut plugin = X86LTOPlugin::new();
plugin.set_macho_lto_library("/usr/lib/libLTO.dylib");
assert_eq!(
plugin.macho_lto_library,
Some("/usr/lib/libLTO.dylib".to_string())
);
}
#[test]
fn test_plugin_add_symbol() {
let mut plugin = X86LTOPlugin::new();
let sym = PluginSymbolInfo {
name: "myfunc".to_string(),
section_kind: PluginSectionKind::Text,
visibility: SymbolVisibility::Default,
is_global: true,
is_definition: true,
is_common: false,
is_weak: false,
size: 100,
alignment: 16,
comdat_key: None,
};
plugin.add_plugin_symbol(sym);
assert_eq!(plugin.get_plugin_symbols().len(), 1);
}
#[test]
fn test_plugin_set_opt_level() {
let mut plugin = X86LTOPlugin::new();
plugin.set_opt_level(3);
assert_eq!(plugin.lto_opt_level, 3);
plugin.set_opt_level(10); assert_eq!(plugin.lto_opt_level, 3);
}
#[test]
fn test_plugin_set_cpu() {
let mut plugin = X86LTOPlugin::new();
plugin.set_cpu("znver4");
assert_eq!(plugin.cpu, "znver4");
}
#[test]
fn test_plugin_set_features() {
let mut plugin = X86LTOPlugin::new();
plugin.set_features("+avx2,+fma");
assert_eq!(plugin.features, "+avx2,+fma");
}
#[test]
fn test_plugin_set_preserve_debug() {
let mut plugin = X86LTOPlugin::new();
assert!(!plugin.preserve_debug);
plugin.set_preserve_debug(true);
assert!(plugin.preserve_debug);
}
#[test]
fn test_plugin_is_error() {
let plugin = X86LTOPlugin::new();
assert!(!plugin.is_error());
}
#[test]
fn test_plugin_get_status() {
let plugin = X86LTOPlugin::new();
assert!(matches!(plugin.get_status(), LTOPluginStatus::Ready));
}
#[test]
fn test_plugin_get_diagnostics() {
let mut plugin = X86LTOPlugin::new();
plugin.onload();
let diags = plugin.get_diagnostics();
assert!(!diags.is_empty());
}
#[test]
fn test_plugin_extract_symbols() {
let plugin = X86LTOPlugin::new();
let data = make_bitcode_data();
let syms = plugin.extract_symbols(&data);
}
#[test]
fn test_plugin_extract_symbols_non_bitcode() {
let plugin = X86LTOPlugin::new();
let syms = plugin.extract_symbols(b"not bitcode");
assert!(syms.is_empty());
}
#[test]
fn test_plugin_codegen_to_native() {
let plugin = X86LTOPlugin::new();
let native = plugin.codegen_to_native(b"test bitcode data");
assert!(native.len() >= 4);
assert_eq!(&native[0..4], b"\x7fELF");
}
#[test]
fn test_plugin_default() {
let plugin = X86LTOPlugin::default();
assert_eq!(plugin.api_version, LTO_API_VERSION);
}
#[test]
fn test_compute_fnv1a_64_full() {
let hash1 = compute_fnv1a_64_full(b"hello");
let hash2 = compute_fnv1a_64_full(b"hello");
assert_eq!(hash1, hash2);
let hash3 = compute_fnv1a_64_full(b"world");
assert_ne!(hash1, hash3);
}
#[test]
fn test_compute_guid_from_name() {
let guid1 = compute_guid_from_name("test_func");
let guid2 = compute_guid_from_name("test_func");
assert_eq!(guid1, guid2);
let guid3 = compute_guid_from_name("different_func");
assert_ne!(guid1, guid3);
}
#[test]
fn test_align_up_full() {
assert_eq!(align_up_full(0, 16), 0);
assert_eq!(align_up_full(1, 16), 16);
assert_eq!(align_up_full(16, 16), 16);
assert_eq!(align_up_full(17, 16), 32);
assert_eq!(align_up_full(100, 64), 128);
assert_eq!(align_up_full(50, 0), 50);
}
#[test]
fn test_align_down_full() {
assert_eq!(align_down_full(0, 16), 0);
assert_eq!(align_down_full(1, 16), 0);
assert_eq!(align_down_full(16, 16), 16);
assert_eq!(align_down_full(31, 16), 16);
assert_eq!(align_down_full(150, 64), 128);
assert_eq!(align_down_full(100, 0), 100);
}
#[test]
fn test_compute_summary_hash() {
let h1 = compute_summary_hash(b"data1");
let h2 = compute_summary_hash(b"data1");
assert_eq!(h1, h2);
let h3 = compute_summary_hash(b"data2");
assert_ne!(h1, h3);
}
#[test]
fn test_hash_call_graph_edge() {
let h1 = hash_call_graph_edge(100, 200);
let h2 = hash_call_graph_edge(100, 200);
assert_eq!(h1, h2);
let h3 = hash_call_graph_edge(200, 100);
assert_ne!(h1, h3);
}
#[test]
fn test_is_eligible_for_import() {
let eligible = FunctionSummary {
name: "eligible".to_string(),
guid: 1,
inst_count: 500,
call_count: 3,
callees: vec![],
hotness: 180,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 2,
return_type: ReturnTypeClass::Integer,
call_profile: vec![],
cfi_enabled: false,
};
assert!(is_eligible_for_import(&eligible));
let ineligible = FunctionSummary {
name: "ineligible".to_string(),
guid: 2,
inst_count: 500,
call_count: 3,
callees: vec![],
hotness: 180,
has_inline_asm: true, has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 2,
return_type: ReturnTypeClass::Integer,
call_profile: vec![],
cfi_enabled: false,
};
assert!(!is_eligible_for_import(&ineligible));
}
#[test]
fn test_is_constant_propagatable() {
let good = make_global_summary("good", 100, true);
assert!(is_constant_propagatable(&good));
let bad_tls = GlobalVarSummary {
name: "bad".to_string(),
guid: 2,
size: 100,
alignment: 8,
is_constant: true,
is_read_only: true,
is_external: true,
refs: vec![],
init_hash: 0,
is_tls: true,
linkage: GlobalLinkageKind::External,
};
assert!(!is_constant_propagatable(&bad_tls));
let bad_not_const = make_global_summary("bad2", 100, false);
assert!(!is_constant_propagatable(&bad_not_const));
}
#[test]
fn test_hotness_percentile() {
let m = make_module_summary("test.o", &[("func_a", 0), ("func_b", 0)]);
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 2,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
}
}
let guid_a = compute_guid_from_name("func_a");
let pct = hotness_percentile(guid_a, &combined);
assert!(pct >= 0.0 && pct <= 1.0);
}
#[test]
fn test_thin_lto_import_reason_eq() {
assert_eq!(ThinLTOImportReason::HotCall, ThinLTOImportReason::HotCall);
assert_ne!(ThinLTOImportReason::HotCall, ThinLTOImportReason::ColdCall);
}
#[test]
fn test_thin_lto_cache_mode_eq() {
assert_eq!(ThinLTOCacheMode::Disabled, ThinLTOCacheMode::Disabled);
assert_ne!(ThinLTOCacheMode::Disabled, ThinLTOCacheMode::HashBased);
}
#[test]
fn test_import_decision_clone() {
let d = ImportDecision::Import {
name: "test".to_string(),
reason: ThinLTOImportReason::HotCall,
hotness: 255,
guid: 42,
};
let d2 = d.clone();
match d2 {
ImportDecision::Import {
name,
reason,
hotness,
guid,
} => {
assert_eq!(name, "test");
assert_eq!(reason, ThinLTOImportReason::HotCall);
assert_eq!(hotness, 255);
assert_eq!(guid, 42);
}
_ => panic!("Expected Import"),
}
}
#[test]
fn test_internalization_decision_eq() {
assert_eq!(
InternalizationDecision::KeepGlobal,
InternalizationDecision::KeepGlobal
);
assert_ne!(
InternalizationDecision::Internalize,
InternalizationDecision::Discard
);
}
#[test]
fn test_comdat_selection_kind_eq() {
assert_eq!(ComdatSelectionKind::Any, ComdatSelectionKind::Any);
assert_ne!(ComdatSelectionKind::Any, ComdatSelectionKind::ExactMatch);
}
#[test]
fn test_lto_debug_merge_mode_eq() {
assert_eq!(LTODebugMergeMode::Full, LTODebugMergeMode::Full);
assert_ne!(LTODebugMergeMode::Full, LTODebugMergeMode::Skeleton);
}
#[test]
fn test_type_unit_dedup_strategy_eq() {
assert_eq!(
TypeUnitDedupStrategy::HashBased,
TypeUnitDedupStrategy::HashBased
);
}
#[test]
fn test_devirt_result_eq() {
let r1 = DevirtResult::Devirtualized {
vtable_name: "A".to_string(),
target_function: "foo".to_string(),
offset: 0,
};
let r2 = DevirtResult::Devirtualized {
vtable_name: "A".to_string(),
target_function: "foo".to_string(),
offset: 0,
};
assert_eq!(r1, r2);
}
#[test]
fn test_global_opt_kind_eq() {
assert_eq!(GlobalOptKind::DemoteToLocal, GlobalOptKind::DemoteToLocal);
}
#[test]
fn test_lto_plugin_status_clone() {
let s = LTOPluginStatus::Ready;
let s2 = s.clone();
assert_eq!(s, s2);
}
#[test]
fn test_claim_result_eq() {
assert_eq!(ClaimResult::Claimed, ClaimResult::Claimed);
assert_ne!(ClaimResult::Claimed, ClaimResult::NotClaimed);
}
#[test]
fn test_module_summary_flags_default() {
let flags = ModuleSummaryFlags::default();
assert!(!flags.function_sections);
assert!(!flags.data_sections);
assert!(!flags.lto_compiled);
}
#[test]
fn test_symbol_visibility_eq() {
assert_eq!(SymbolVisibility::Default, SymbolVisibility::Default);
assert_ne!(SymbolVisibility::Default, SymbolVisibility::Hidden);
}
#[test]
fn test_plugin_section_kind_clone() {
let k = PluginSectionKind::Text;
assert_eq!(k, PluginSectionKind::Text);
}
#[test]
fn test_thin_lto_stats_default() {
let stats = ThinLTOStats::default();
assert_eq!(stats.modules_processed, 0);
assert_eq!(stats.cache_hits, 0);
}
#[test]
fn test_internalization_stats_default() {
let stats = InternalizationStats::default();
assert_eq!(stats.total_symbols, 0);
assert_eq!(stats.kept_global, 0);
}
#[test]
fn test_debug_info_stats_default() {
let stats = DebugInfoStats::default();
assert_eq!(stats.cus_merged, 0);
assert_eq!(stats.type_units_deduplicated, 0);
}
#[test]
fn test_whole_program_stats_default() {
let stats = WholeProgramStats::default();
assert_eq!(stats.alias_pairs_analyzed, 0);
assert_eq!(stats.bytes_freed, 0);
}
#[test]
fn test_constant_values() {
assert_eq!(THINLTO_MAX_PARTITIONS, 256);
assert_eq!(THINLTO_HOTNESS_THRESHOLD, 200);
assert_eq!(THINLTO_MAX_IMPORT_DEPTH, 5);
assert_eq!(THINLTO_MAX_IMPORTS_PER_MODULE, 5000);
assert_eq!(THINLTO_SUMMARY_VERSION, 8);
assert_eq!(LTO_API_VERSION, 29);
assert_eq!(FNV64_OFFSET_BASIS, 0xCBF2_9CE4_8422_2325);
assert_eq!(FNV64_PRIME, 0x0000_0100_0000_01B3);
}
#[test]
fn test_thin_lto_summary_magic() {
assert_eq!(THINLTO_SUMMARY_MAGIC, [0x54, 0x4C, 0x54, 0x4F]);
}
#[test]
fn test_x86_lld_features_construction() {
let features = X86LLDFeatures {
thin_lto: X86ThinLTOFull::new(),
internalization: X86LTOInternalization::new(),
debug_info: X86LTODebugInfo::new(),
whole_program: X86LTOWholeProgram::new(),
plugin: X86LTOPlugin::new(),
combined_index: None,
enabled: true,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
output_path: "output".to_string(),
opt_level: 2,
num_threads: 4,
diagnostics: vec![],
};
assert!(features.enabled);
assert_eq!(features.opt_level, 2);
}
#[test]
fn test_full_thin_lto_pipeline() {
let mut thin = X86ThinLTOFull::new();
thin.hotness_threshold = 128;
let guid_callee = compute_guid_from_name("callee_func");
let mod1 = ModuleSummaryIndex {
module_hash: 1,
module_path: "caller.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "caller_func".to_string(),
guid: compute_guid_from_name("caller_func"),
inst_count: 200,
call_count: 1,
callees: vec![guid_callee],
hotness: 220,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: true,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Integer,
call_profile: vec![(guid_callee, 5000)],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 200,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mod2 = ModuleSummaryIndex {
module_hash: 2,
module_path: "callee.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "callee_func".to_string(),
guid: guid_callee,
inst_count: 50,
call_count: 0,
callees: vec![],
hotness: 250,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 1,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 50,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
thin.build_combined_index(vec![mod1, mod2]);
thin.decide_imports();
}
#[test]
fn test_full_internalization_pipeline() {
let mut intern = X86LTOInternalization::new();
let m1 = make_module_summary("a.o", &[("exported", 0), ("internal", 0)]);
let m2 = make_module_summary("b.o", &[("dup", 0)]);
let mut combined = CombinedIndex {
modules: vec![m1, m2],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 3,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
}
}
combined
.export_guids
.insert(compute_guid_from_name("exported"));
intern.build_resolution_table(&combined);
intern.compute_export_list(&combined, &std::collections::HashMap::new(), &[]);
intern.internalize_non_exported();
intern.prune_linkonce_odr();
assert!(intern.is_internalized(compute_guid_from_name("internal")));
assert!(!intern.is_internalized(compute_guid_from_name("exported")));
assert!(intern.linkonce_odr_pruned.len() > 0);
}
#[test]
fn test_full_debug_info_pipeline() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::Full);
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 200,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "foo.cpp".to_string(),
producer: "clang++".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: vec![],
range_list_data: vec![],
});
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 150,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 2,
comp_dir: "/src".to_string(),
source_file: "bar.cpp".to_string(),
producer: "clang++".to_string(),
low_pc: 0x2000,
high_pc: 0x3000,
line_table_data: vec![],
range_list_data: vec![],
});
di.add_type_units(vec![
DwarfTypeUnit {
type_signature: 0xABCD,
offset: 0,
length: 64,
module_hash: 1,
type_name: Some("struct Point".to_string()),
},
DwarfTypeUnit {
type_signature: 0xABCD,
offset: 64,
length: 64,
module_hash: 2,
type_name: Some("struct Point".to_string()),
},
DwarfTypeUnit {
type_signature: 0xEF01,
offset: 0,
length: 48,
module_hash: 1,
type_name: Some("class Matrix".to_string()),
},
]);
let merged = di.merge_debug_info(&[]);
assert!(!merged.is_empty());
assert_eq!(di.debug_stats.cus_merged, 2);
assert_eq!(di.debug_stats.type_units_kept, 2);
assert!(di.debug_stats.type_units_deduplicated > 0);
}
#[test]
fn test_full_whole_program_pipeline() {
let mut wp = X86LTOWholeProgram::new();
let func_guid = compute_guid_from_name("Derived::method");
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "combined.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "Derived::method".to_string(),
guid: func_guid,
inst_count: 80,
call_count: 5,
callees: vec![],
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 1,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
}],
global_summaries: vec![
make_global_summary("global_x", 8, false),
make_global_summary("global_y", 8, false),
make_global_summary("global_unused", 4096, false),
],
vtable_summaries: vec![make_vtable_summary(
"Derived_vtable",
vec![func_guid],
"Derived",
)],
alias_summaries: vec![],
instruction_count: 80,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 1,
total_globals: 3,
};
for (mi, m) in combined.modules.iter().enumerate() {
for g in &m.global_summaries {
combined.global_to_module.insert(g.guid, mi);
}
}
let global_data: Vec<(u64, Vec<u8>)> = vec![];
wp.run_all(&combined, &global_data);
assert!(wp.whole_program_stats.dead_globals_eliminated > 0);
assert!(wp.whole_program_stats.globals_demoted > 0);
assert!(wp.whole_program_stats.devirtualized_calls > 0);
}
#[test]
fn test_full_plugin_pipeline() {
let mut plugin = X86LTOPlugin::new();
plugin.onload();
let bc_data = make_bitcode_data();
let req = PluginClaimRequest {
file_path: "input.bc".to_string(),
file_data: bc_data,
file_descriptor: None,
file_size: 20,
symbol_table_offset: 0,
symbol_count: 0,
};
assert_eq!(plugin.claim_file(&req), ClaimResult::Claimed);
let elf_req = PluginClaimRequest {
file_path: "input.o".to_string(),
file_data: {
let mut v = vec![0u8; 20 + 5];
v[0] = 0x7f;
v[1] = b'E';
v[2] = b'L';
v[3] = b'F';
v[4] = 0;
v
},
file_descriptor: None,
file_size: 24,
symbol_table_offset: 0,
symbol_count: 0,
};
assert_eq!(plugin.claim_file(&elf_req), ClaimResult::NotClaimed);
let objects = plugin.all_symbols_read();
assert!(!objects.is_empty());
assert!(matches!(plugin.status, LTOPluginStatus::Completed));
plugin.cleanup();
assert!(matches!(plugin.status, LTOPluginStatus::Ready));
}
#[test]
fn test_e2e_thin_lto_with_pgo() {
let mut thin = X86ThinLTOFull::new();
thin.hotness_threshold = 100;
let guid_callee = compute_guid_from_name("hot_function");
thin.pgo_profiles.insert(
guid_callee,
PGOProfileEntry {
guid: guid_callee,
entry_count: 1_000_000,
total_frequency: 5_000_000,
max_frequency: 1_000_000,
},
);
let mod1 = ModuleSummaryIndex {
module_hash: 100,
module_path: "main.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "main".to_string(),
guid: compute_guid_from_name("main"),
inst_count: 300,
call_count: 1,
callees: vec![guid_callee],
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: true,
is_local: false,
param_count: 2,
return_type: ReturnTypeClass::Integer,
call_profile: vec![(guid_callee, 10000)],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 300,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mod2 = ModuleSummaryIndex {
module_hash: 200,
module_path: "lib.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "hot_function".to_string(),
guid: guid_callee,
inst_count: 100,
call_count: 0,
callees: vec![],
hotness: 255,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 1,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 100,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
thin.build_combined_index(vec![mod1, mod2]);
thin.decide_imports();
let module_hash = 100;
let decisions = thin.get_import_decisions(module_hash);
}
#[test]
fn test_e2e_lto_debug_skeleton() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::Skeleton);
for i in 0..5 {
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: i as u64,
comp_dir: "/src".to_string(),
source_file: format!("file_{}.cpp", i),
producer: "clang++".to_string(),
low_pc: 0x1000 * (i as u64 + 1),
high_pc: 0x1000 * (i as u64 + 2),
line_table_data: vec![],
range_list_data: vec![],
});
}
let merged = di.merge_debug_info(&[]);
assert!(!merged.is_empty());
assert_eq!(di.skeleton_cus.len(), 5);
assert_eq!(di.debug_stats.skeleton_cus_generated, 5);
}
#[test]
fn test_stress_many_modules() {
let mut thin = X86ThinLTOFull::new();
let mut modules = Vec::new();
for i in 0..100 {
let m = make_module_summary(&format!("mod{}.o", i), &[(&format!("func_{}", i), 0)]);
modules.push(m);
}
thin.build_combined_index(modules);
thin.decide_imports();
}
#[test]
fn test_stress_many_type_units() {
let mut di = X86LTODebugInfo::new();
let mut tus = Vec::new();
for i in 0..1000u64 {
tus.push(DwarfTypeUnit {
type_signature: i,
offset: 0,
length: 64,
module_hash: i % 10,
type_name: Some(format!("Type_{}", i)),
});
}
di.add_type_units(tus);
di.deduplicate_type_units();
assert_eq!(di.dedup_type_units.len(), 1000);
}
#[test]
fn test_stress_many_comdat_groups() {
let mut intern = X86LTOInternalization::new();
for i in 0..500 {
intern.add_comdat_group(
format!("group_{}", i),
vec![i as u64 * 10, i as u64 * 10 + 1],
);
}
intern.select_comdat_leaders(ComdatSelectionKind::Any);
assert_eq!(intern.comdat_leaders.len(), 500);
}
#[test]
fn test_edge_case_empty_combined_index() {
let mut thin = X86ThinLTOFull::new();
thin.build_combined_index(vec![]);
thin.decide_imports();
assert!(thin.import_decisions.is_empty());
}
#[test]
fn test_edge_case_zero_length_data() {
let mut thin = X86ThinLTOFull::new();
let summary = thin.compute_module_summary("empty.o", &[]);
assert!(summary.function_summaries.is_empty());
assert!(summary.global_summaries.is_empty());
}
#[test]
fn test_edge_case_very_hot_function() {
let mut thin = X86ThinLTOFull::new();
let guid = compute_guid_from_name("super_hot");
thin.pgo_profiles.insert(
guid,
PGOProfileEntry {
guid,
entry_count: u64::MAX,
total_frequency: u64::MAX,
max_frequency: u64::MAX,
},
);
let m = make_module_summary("hot.o", &[("super_hot", 0)]);
thin.build_combined_index(vec![m]);
thin.decide_imports();
}
#[test]
fn test_edge_case_align_overflow() {
let result = align_up_full(u64::MAX - 5, 16);
}
#[test]
fn test_thin_lto_stats_accumulation() {
let mut thin = X86ThinLTOFull::new();
thin.stats.modules_processed = 5;
thin.stats.functions_imported = 100;
thin.stats.cache_hits = 20;
thin.stats.cache_misses = 10;
let stats = thin.get_stats();
assert_eq!(stats.modules_processed, 5);
assert_eq!(stats.functions_imported, 100);
assert_eq!(stats.cache_hits, 20);
thin.reset_stats();
let stats2 = thin.get_stats();
assert_eq!(stats2.modules_processed, 0);
}
#[test]
fn test_whole_program_devirt_edge_cases() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "edge.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![],
vtable_summaries: vec![VTableSummary {
name: "EmptyVTable".to_string(),
guid: compute_guid_from_name("EmptyVTable"),
entries: vec![],
class_name: "Empty".to_string(),
has_subclasses: false,
hierarchy_depth: 0,
}],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 0,
};
wp.run_devirtualization(&combined);
}
#[test]
fn test_plugin_is_bitcode_edge() {
let plugin = X86LTOPlugin::new();
}
#[test]
fn test_x86_lld_features_full_construction() {
let features = X86LLDFeatures {
thin_lto: X86ThinLTOFull::new_aggressive(),
internalization: X86LTOInternalization::new(),
debug_info: X86LTODebugInfo::new(),
whole_program: X86LTOWholeProgram::new(),
plugin: X86LTOPlugin::new(),
combined_index: None,
enabled: true,
target_triple: "x86_64-pc-windows-msvc".to_string(),
output_path: "output.exe".to_string(),
opt_level: 2,
num_threads: 8,
diagnostics: vec![],
};
assert!(features.enabled);
assert_eq!(features.target_triple, "x86_64-pc-windows-msvc");
assert_eq!(features.output_path, "output.exe");
assert_eq!(features.num_threads, 8);
}
#[test]
fn test_return_type_class_all_variants() {
assert_eq!(ReturnTypeClass::Void as u8, 0);
let all = vec![
ReturnTypeClass::Void,
ReturnTypeClass::Integer,
ReturnTypeClass::Float,
ReturnTypeClass::Pointer,
ReturnTypeClass::StructSRet,
ReturnTypeClass::StructRegisters,
];
assert_eq!(all.len(), 6);
}
#[test]
fn test_global_linkage_kind_all_variants() {
let all = vec![
GlobalLinkageKind::External,
GlobalLinkageKind::Internal,
GlobalLinkageKind::LinkonceODR,
GlobalLinkageKind::WeakODR,
GlobalLinkageKind::Common,
GlobalLinkageKind::Appending,
GlobalLinkageKind::ExternalWeak,
GlobalLinkageKind::AvailableExternally,
];
assert_eq!(all.len(), 8);
}
#[test]
fn test_comdat_selection_kind_all_variants() {
let all = vec![
ComdatSelectionKind::Any,
ComdatSelectionKind::ExactMatch,
ComdatSelectionKind::Largest,
ComdatSelectionKind::NoDuplicates,
ComdatSelectionKind::SameSize,
];
assert_eq!(all.len(), 5);
}
#[test]
fn test_lto_feature_diag_level_all_variants() {
let all = vec![
LTOFeatureDiagLevel::Info,
LTOFeatureDiagLevel::Warning,
LTOFeatureDiagLevel::Error,
LTOFeatureDiagLevel::Debug,
];
assert_eq!(all.len(), 4);
}
#[test]
fn test_thin_lto_import_reason_all_variants() {
let all = vec![
ThinLTOImportReason::HotCall,
ThinLTOImportReason::ColdCall,
ThinLTOImportReason::Transitive,
ThinLTOImportReason::GlobalRef,
ThinLTOImportReason::Devirtualization,
ThinLTOImportReason::ProfileGuided,
ThinLTOImportReason::AlwaysImport,
ThinLTOImportReason::NotImported,
];
assert_eq!(all.len(), 8);
}
#[test]
fn test_distributed_backend_single_module() {
let mut thin = X86ThinLTOFull::new();
let modules = vec![{
let mut v = vec![0u8; 64 + 4];
v[0] = b'B';
v[1] = b'C';
v[2] = 0xC0;
v[3] = 0xDE;
v
}];
let results = thin.run_distributed_backend(&modules);
assert_eq!(results.len(), 1);
assert!(results[0].len() > 40);
}
#[test]
fn test_distributed_backend_multiple_modules() {
let mut thin = X86ThinLTOFull::new();
let modules: Vec<Vec<u8>> = (0..10)
.map(|i| {
let mut v = vec![b'B', b'C', 0xC0, 0xDE];
v.extend_from_slice(&[0; 60]);
v[4] = i as u8;
v
})
.collect();
let results = thin.run_distributed_backend(&modules);
assert_eq!(results.len(), 10);
for r in &results {
assert_eq!(&r[0..4], b"\x7fELF");
}
}
#[test]
fn test_distributed_backend_empty() {
let mut thin = X86ThinLTOFull::new();
let results = thin.run_distributed_backend(&[]);
assert!(results.is_empty());
}
#[test]
fn test_cache_eviction_by_size() {
let mut thin = X86ThinLTOFull::new();
for i in 0..1000u64 {
thin.cache_store(i, vec![i as u8; 128]);
}
assert!(thin.cache_lookup(0).is_some());
assert!(thin.cache_lookup(999).is_some());
}
#[test]
fn test_cache_key_uniqueness() {
let mut thin = X86ThinLTOFull::new();
thin.cache_store(0xAAAA, vec![1, 2, 3]);
thin.cache_store(0xBBBB, vec![4, 5, 6]);
assert_eq!(thin.cache_lookup(0xAAAA), Some(&vec![1, 2, 3]));
assert_eq!(thin.cache_lookup(0xBBBB), Some(&vec![4, 5, 6]));
}
#[test]
fn test_cache_overwrite() {
let mut thin = X86ThinLTOFull::new();
thin.cache_store(1, vec![1, 1, 1]);
thin.cache_store(1, vec![2, 2, 2]);
assert_eq!(thin.cache_lookup(1), Some(&vec![2, 2, 2]));
}
#[test]
fn test_cache_disabled_mode() {
let mut thin = X86ThinLTOFull::new();
thin.set_cache_mode(ThinLTOCacheMode::Disabled);
let key = thin.compute_cache_key(&[1, 2, 3], 0);
assert!(key.is_none());
}
#[test]
fn test_cache_hash_mode_key() {
let mut thin = X86ThinLTOFull::new();
thin.set_cache_mode(ThinLTOCacheMode::HashBased);
let key = thin.compute_cache_key(&[1, 2, 3, 4], 7);
assert!(key.is_some());
assert!(key.unwrap().contains("thinlto_"));
assert!(key.unwrap().contains("_7"));
}
#[test]
fn test_pgo_profile_loading_multiple_entries() {
let mut thin = X86ThinLTOFull::new();
let mut buf = vec![0u8; 8]; for i in 1..=10u64 {
buf.extend_from_slice(&i.to_le_bytes()); buf.extend_from_slice(&(i * 100).to_le_bytes()); buf.extend_from_slice(&(i * 500).to_le_bytes()); }
thin.load_pgo_profile(&buf);
assert_eq!(thin.pgo_profiles.len(), 10);
assert_eq!(thin.pgo_profiles[&5].entry_count, 500);
assert_eq!(thin.pgo_profiles[&10].entry_count, 1000);
}
#[test]
fn test_pgo_profile_hotness_override() {
let mut thin = X86ThinLTOFull::new();
let guid = compute_guid_from_name("hot_func");
let mut buf = vec![0u8; 8];
buf.extend_from_slice(&guid.to_le_bytes());
buf.extend_from_slice(&100_000u64.to_le_bytes());
buf.extend_from_slice(&500_000u64.to_le_bytes());
thin.load_pgo_profile(&buf);
let m = make_module_summary("hot.o", &[("hot_func", 0)]);
let mut mod_copy = m;
if let Some(fs) = mod_copy.function_summaries.first_mut() {
fs.hotness = 50;
}
let summary = thin.compute_module_summary("hot.o", &[]);
}
#[test]
fn test_pgo_profile_truncated_data() {
let mut thin = X86ThinLTOFull::new();
let buf = {
let mut v = vec![0u8; 8];
v.extend_from_slice(&[1, 2, 3]);
v
}; thin.load_pgo_profile(&buf);
assert!(thin.pgo_profiles.is_empty());
}
#[test]
fn test_pgo_profile_cold_function() {
let mut thin = X86ThinLTOFull::new();
let guid = compute_guid_from_name("cold_func");
let mut buf = vec![0u8; 8];
buf.extend_from_slice(&guid.to_le_bytes());
buf.extend_from_slice(&10u64.to_le_bytes()); buf.extend_from_slice(&50u64.to_le_bytes());
thin.load_pgo_profile(&buf);
let entry = &thin.pgo_profiles[&guid];
let expected_hotness = ((entry.entry_count as f64 / 10.0).min(255.0)) as u8;
assert!(expected_hotness < 10);
}
#[test]
fn test_import_decision_transitive_depth_1() {
let mut thin = X86ThinLTOFull::new();
thin.max_import_depth = 1;
thin.hotness_threshold = 50;
let guid_b = compute_guid_from_name("func_b");
let guid_c = compute_guid_from_name("func_c");
let mod_a = ModuleSummaryIndex {
module_hash: 1,
module_path: "a.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "func_a".to_string(),
guid: compute_guid_from_name("func_a"),
inst_count: 50,
call_count: 1,
callees: vec![guid_b],
hotness: 250,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: true,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Integer,
call_profile: vec![(guid_b, 5000)],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 50,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mod_b = ModuleSummaryIndex {
module_hash: 2,
module_path: "b.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "func_b".to_string(),
guid: guid_b,
inst_count: 30,
call_count: 1,
callees: vec![guid_c],
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 1,
return_type: ReturnTypeClass::Void,
call_profile: vec![(guid_c, 3000)],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 30,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mod_c = ModuleSummaryIndex {
module_hash: 3,
module_path: "c.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "func_c".to_string(),
guid: guid_c,
inst_count: 20,
call_count: 0,
callees: vec![],
hotness: 100,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 20,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
thin.build_combined_index(vec![mod_a, mod_b, mod_c]);
thin.decide_imports();
}
#[test]
fn test_import_decision_max_imports_cap() {
let mut thin = X86ThinLTOFull::new();
thin.hotness_threshold = 10;
let mut modules = Vec::new();
let mut callee_guids = Vec::new();
for i in 1..=100u64 {
let guid = compute_guid_from_name(&format!("callee_{}", i));
callee_guids.push(guid);
modules.push(ModuleSummaryIndex {
module_hash: i + 100,
module_path: format!("lib{}.o", i),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: format!("callee_{}", i),
guid,
inst_count: 20,
call_count: 0,
callees: vec![],
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 20,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
});
}
modules.insert(
0,
ModuleSummaryIndex {
module_hash: 1,
module_path: "main.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name: "main".to_string(),
guid: compute_guid_from_name("main"),
inst_count: 500,
call_count: 100,
callees: callee_guids.clone(),
hotness: 255,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: true,
is_local: false,
param_count: 2,
return_type: ReturnTypeClass::Integer,
call_profile: callee_guids.iter().map(|&g| (g, 1000)).collect(),
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 500,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
},
);
thin.build_combined_index(modules);
thin.decide_imports();
let decisions = thin.get_import_decisions(1);
if let Some(dec) = decisions {
let import_count = dec
.iter()
.filter(|d| matches!(d, ImportDecision::Import { .. }))
.count();
assert!(import_count <= THINLTO_MAX_IMPORTS_PER_MODULE);
}
}
#[test]
fn test_comdat_exact_match_strategy() {
let mut intern = X86LTOInternalization::new();
intern.add_comdat_group("exact_group".to_string(), vec![10, 20, 30]);
intern.select_comdat_leaders(ComdatSelectionKind::ExactMatch);
assert_eq!(intern.comdat_leaders.get("exact_group"), Some(&10));
}
#[test]
fn test_comdat_no_duplicates_strategy() {
let mut intern = X86LTOInternalization::new();
intern.add_comdat_group("nodup_group".to_string(), vec![100, 200]);
intern.select_comdat_leaders(ComdatSelectionKind::NoDuplicates);
assert_eq!(intern.comdat_leaders.get("nodup_group"), Some(&100));
}
#[test]
fn test_comdat_same_size_strategy() {
let mut intern = X86LTOInternalization::new();
intern.add_comdat_group("samesize_group".to_string(), vec![5, 10, 15]);
intern.select_comdat_leaders(ComdatSelectionKind::SameSize);
assert_eq!(intern.comdat_leaders.get("samesize_group"), Some(&5));
}
#[test]
fn test_comdat_empty_group() {
let mut intern = X86LTOInternalization::new();
intern.add_comdat_group("empty_group".to_string(), vec![]);
intern.select_comdat_leaders(ComdatSelectionKind::Any);
assert!(intern.comdat_leaders.get("empty_group").is_none());
}
#[test]
fn test_comdat_multiple_groups() {
let mut intern = X86LTOInternalization::new();
for i in 0..20 {
intern.add_comdat_group(format!("group_{}", i), vec![i * 3, i * 3 + 1, i * 3 + 2]);
}
intern.select_comdat_leaders(ComdatSelectionKind::Any);
assert_eq!(intern.comdat_leaders.len(), 20);
assert_eq!(intern.internalization_stats.comdat_groups_resolved, 20);
}
#[test]
fn test_merge_line_tables_multiple_entries() {
let mut di = X86LTODebugInfo::new();
let mut line_data = Vec::new();
line_data.push(4);
line_data.extend_from_slice(b"a.rs");
line_data.extend_from_slice(&10u32.to_le_bytes());
line_data.extend_from_slice(&1u32.to_le_bytes());
line_data.extend_from_slice(&0x1000u64.to_le_bytes());
line_data.push(4);
line_data.extend_from_slice(b"a.rs");
line_data.extend_from_slice(&20u32.to_le_bytes());
line_data.extend_from_slice(&1u32.to_le_bytes());
line_data.extend_from_slice(&0x1010u64.to_le_bytes());
line_data.push(4);
line_data.extend_from_slice(b"b.rs");
line_data.extend_from_slice(&30u32.to_le_bytes());
line_data.extend_from_slice(&5u32.to_le_bytes());
line_data.extend_from_slice(&0x1020u64.to_le_bytes());
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 200,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "main.rs".to_string(),
producer: "rustc".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: line_data,
range_list_data: vec![],
});
di.merge_line_tables();
assert_eq!(di.line_table.len(), 3);
assert_eq!(di.line_table[0].line, 10);
assert_eq!(di.line_table[1].line, 20);
assert_eq!(di.line_table[2].file, "b.rs");
}
#[test]
fn test_merge_address_ranges() {
let mut di = X86LTODebugInfo::new();
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "a.c".to_string(),
producer: "clang".to_string(),
low_pc: 0x3000,
high_pc: 0x4000,
line_table_data: vec![],
range_list_data: vec![],
});
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 2,
comp_dir: "/src".to_string(),
source_file: "b.c".to_string(),
producer: "clang".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: vec![],
range_list_data: vec![],
});
di.set_merge_mode(LTODebugMergeMode::Full);
di.merge_debug_info(&[]);
assert_eq!(di.address_ranges.len(), 2);
assert_eq!(di.address_ranges[0].0, 0x1000);
assert_eq!(di.address_ranges[1].0, 0x3000);
}
#[test]
fn test_skeleton_cu_dwo_name() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::Skeleton);
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 99,
comp_dir: "/build".to_string(),
source_file: "lib.cpp".to_string(),
producer: "clang++".to_string(),
low_pc: 0x5000,
high_pc: 0x6000,
line_table_data: vec![],
range_list_data: vec![],
});
di.merge_debug_info(&[]);
assert_eq!(di.skeleton_cus.len(), 1);
let sk = &di.skeleton_cus[0];
assert_eq!(sk.object_path, "lib.cpp");
assert_eq!(sk.comp_dir, "/build");
assert_eq!(sk.dwarf_version, 5);
assert_eq!(sk.dwo_name, Some("lib.cpp.dwo".to_string()));
}
#[test]
fn test_dedup_type_units_structural() {
let mut di = X86LTODebugInfo::new();
di.set_dedup_strategy(TypeUnitDedupStrategy::Structural);
di.add_type_units(vec![
DwarfTypeUnit {
type_signature: 1,
offset: 0,
length: 64,
module_hash: 10,
type_name: Some("X".to_string()),
},
DwarfTypeUnit {
type_signature: 1,
offset: 64,
length: 64,
module_hash: 20,
type_name: Some("X".to_string()),
},
DwarfTypeUnit {
type_signature: 2,
offset: 0,
length: 48,
module_hash: 30,
type_name: Some("Y".to_string()),
},
]);
di.deduplicate_type_units();
assert_eq!(di.dedup_type_units.len(), 2);
assert_eq!(di.debug_stats.type_units_deduplicated, 1);
}
#[test]
fn test_debug_string_table_dedup() {
let mut di = X86LTODebugInfo::new();
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/common".to_string(),
source_file: "shared.c".to_string(),
producer: "gcc".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: vec![],
range_list_data: vec![],
});
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 2,
comp_dir: "/common".to_string(),
source_file: "shared.c".to_string(),
producer: "gcc".to_string(),
low_pc: 0x2000,
high_pc: 0x3000,
line_table_data: vec![],
range_list_data: vec![],
});
di.set_merge_mode(LTODebugMergeMode::Full);
di.merge_debug_info(&[]);
let common_entries: Vec<_> = di.string_table.keys().filter(|k| *k == "/common").collect();
assert_eq!(common_entries.len(), 1);
}
#[test]
fn test_alias_analysis_same_module_local() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "same.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![
GlobalVarSummary {
name: "local_a".to_string(),
guid: compute_guid_from_name("local_a"),
size: 8,
alignment: 8,
is_constant: false,
is_read_only: false,
is_external: false,
refs: vec![],
init_hash: 0,
is_tls: false,
linkage: GlobalLinkageKind::Internal,
},
GlobalVarSummary {
name: "local_b".to_string(),
guid: compute_guid_from_name("local_b"),
size: 8,
alignment: 8,
is_constant: false,
is_read_only: false,
is_external: false,
refs: vec![],
init_hash: 0,
is_tls: false,
linkage: GlobalLinkageKind::Internal,
},
],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 2,
};
for (mi, m) in combined.modules.iter().enumerate() {
for g in &m.global_summaries {
combined.global_to_module.insert(g.guid, mi);
}
}
wp.run_alias_analysis(&combined);
assert!(!wp.alias_analyses.is_empty());
let result = &wp.alias_analyses[0];
assert!(result.no_alias);
}
#[test]
fn test_dead_global_not_exported() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "dead.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![GlobalVarSummary {
name: "orphan_data".to_string(),
guid: compute_guid_from_name("orphan_data"),
size: 65536,
alignment: 16,
is_constant: false,
is_read_only: false,
is_external: false,
refs: vec![],
init_hash: 0,
is_tls: false,
linkage: GlobalLinkageKind::Internal,
}],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 1,
};
wp.run_dead_global_elimination(&combined);
assert_eq!(wp.dead_globals.len(), 1);
assert_eq!(wp.dead_globals[0].name, "orphan_data");
assert_eq!(wp.dead_globals[0].size_freed, 65536);
assert_eq!(wp.dead_globals[0].reason, DeadGlobalReason::Unreferenced);
}
#[test]
fn test_global_demotion_with_refs() {
let mut wp = X86LTOWholeProgram::new();
let guid_a = compute_guid_from_name("glob_a");
let guid_b = compute_guid_from_name("glob_b");
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "refs.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![
GlobalVarSummary {
name: "glob_a".to_string(),
guid: guid_a,
size: 16,
alignment: 8,
is_constant: false,
is_read_only: false,
is_external: false,
refs: vec![guid_b],
init_hash: 0,
is_tls: false,
linkage: GlobalLinkageKind::Internal,
},
GlobalVarSummary {
name: "glob_b".to_string(),
guid: guid_b,
size: 8,
alignment: 8,
is_constant: true,
is_read_only: true,
is_external: false,
refs: vec![],
init_hash: 0,
is_tls: false,
linkage: GlobalLinkageKind::Internal,
},
],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 2,
};
wp.run_global_variable_optimization(&combined);
assert!(wp.is_demoted(guid_a));
assert!(wp.is_demoted(guid_b));
}
#[test]
fn test_devirtualization_multi_entry_vtable() {
let mut wp = X86LTOWholeProgram::new();
let g1 = compute_guid_from_name("Shape::area");
let g2 = compute_guid_from_name("Shape::perimeter");
let g3 = compute_guid_from_name("Shape::draw");
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "shape.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![
FunctionSummary {
name: "Shape::area".to_string(),
guid: g1,
inst_count: 60,
call_count: 3,
callees: vec![],
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Float,
call_profile: vec![],
cfi_enabled: false,
},
FunctionSummary {
name: "Shape::perimeter".to_string(),
guid: g2,
inst_count: 70,
call_count: 2,
callees: vec![],
hotness: 180,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Float,
call_profile: vec![],
cfi_enabled: false,
},
FunctionSummary {
name: "Shape::draw".to_string(),
guid: g3,
inst_count: 40,
call_count: 1,
callees: vec![],
hotness: 150,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 1,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
},
],
global_summaries: vec![],
vtable_summaries: vec![VTableSummary {
name: "Shape_vtable".to_string(),
guid: compute_guid_from_name("Shape_vtable"),
entries: vec![g1, g2, g3],
class_name: "Shape".to_string(),
has_subclasses: false,
hierarchy_depth: 0,
}],
alias_summaries: vec![],
instruction_count: 170,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 3,
total_globals: 0,
};
wp.run_devirtualization(&combined);
assert_eq!(wp.whole_program_stats.devirtualized_calls, 3);
}
#[test]
fn test_devirtualization_with_subclasses_block() {
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "abstract.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![],
vtable_summaries: vec![VTableSummary {
name: "Animal_vtable".to_string(),
guid: compute_guid_from_name("Animal_vtable"),
entries: vec![compute_guid_from_name("Animal::speak")],
class_name: "Animal".to_string(),
has_subclasses: true,
hierarchy_depth: 2,
}],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 0,
};
wp.run_devirtualization(&combined);
assert!(wp
.devirt_results
.iter()
.any(|r| matches!(r, DevirtResult::AbstractClass)));
}
#[test]
fn test_plugin_coff_parse_opt() {
let mut plugin = X86LTOPlugin::new();
plugin.parse_coff_option("/opt:lldlto");
assert!(plugin.coff_lto_enabled);
plugin.parse_coff_option("/opt:ref");
assert!(plugin.coff_lto_enabled);
}
#[test]
fn test_plugin_coff_not_enabled_by_default() {
let plugin = X86LTOPlugin::new();
assert!(!plugin.coff_lto_enabled);
}
#[test]
fn test_plugin_macho_default_library() {
let plugin = X86LTOPlugin::new();
assert!(plugin.macho_lto_library.is_none());
}
#[test]
fn test_plugin_macho_set_library() {
let mut plugin = X86LTOPlugin::new();
plugin.set_macho_lto_library(MACHO_LTO_LIBRARY_DEFAULT);
assert_eq!(
plugin.macho_lto_library,
Some(MACHO_LTO_LIBRARY_DEFAULT.to_string())
);
}
#[test]
fn test_plugin_symbol_info_construction() {
let sym = PluginSymbolInfo {
name: "my_symbol".to_string(),
section_kind: PluginSectionKind::Text,
visibility: SymbolVisibility::Hidden,
is_global: false,
is_definition: true,
is_common: false,
is_weak: true,
size: 256,
alignment: 32,
comdat_key: Some("comdat_my_symbol".to_string()),
};
assert_eq!(sym.name, "my_symbol");
assert_eq!(sym.section_kind, PluginSectionKind::Text);
assert!(sym.is_weak);
assert_eq!(sym.size, 256);
assert_eq!(sym.alignment, 32);
}
#[test]
fn test_plugin_claim_multiple_files() {
let mut plugin = X86LTOPlugin::new();
for i in 0..10 {
let data = make_bitcode_data();
let req = PluginClaimRequest {
file_path: format!("input_{}.bc", i),
file_data: data,
file_descriptor: None,
file_size: 20,
symbol_table_offset: 0,
symbol_count: i,
};
assert_eq!(plugin.claim_file(&req), ClaimResult::Claimed);
}
assert_eq!(plugin.claimed_count(), 10);
}
#[test]
fn test_plugin_extract_symbols_with_records() {
let plugin = X86LTOPlugin::new();
let mut data = {
let mut v = vec![0u8; 12 + 4];
v[0] = b'B';
v[1] = b'C';
v[2] = 0xC0;
v[3] = 0xDE;
v
};
data.push(0x0C);
data.push(3); data.extend_from_slice(b"foo");
data.push(0x30); data.extend_from_slice(&100u64.to_le_bytes());
let syms = plugin.extract_symbols(&data);
assert_eq!(syms.len(), 1);
assert_eq!(syms[0].name, "foo");
assert!(syms[0].is_global);
assert!(syms[0].is_definition);
assert_eq!(syms[0].size, 100);
assert_eq!(syms[0].section_kind, PluginSectionKind::Text);
}
#[test]
fn test_plugin_error_status_propagation() {
let mut plugin = X86LTOPlugin::new();
assert!(!plugin.is_error());
}
#[test]
fn test_plugin_diagnostics_accumulate() {
let mut plugin = X86LTOPlugin::new();
plugin.onload();
let diag_count_after_onload = plugin.get_diagnostics().len();
assert!(diag_count_after_onload > 0);
}
#[test]
fn test_compute_call_hotness_from_profile() {
let thin = X86ThinLTOFull::new();
let callee_guid = 100;
let caller_guid = 200;
let combined = CombinedIndex {
modules: vec![],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: {
let mut m = std::collections::HashMap::new();
m.insert(
callee_guid,
vec![caller_guid, caller_guid + 1, caller_guid + 2],
);
m
},
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 0,
};
let hotness = thin.compute_call_hotness(callee_guid, caller_guid, &combined);
assert!(hotness > 0);
}
#[test]
fn test_is_eligible_for_import_varargs_disqualifies() {
let func = FunctionSummary {
name: "varargs_func".to_string(),
guid: 999,
inst_count: 100,
call_count: 2,
callees: vec![],
hotness: 200,
has_inline_asm: false,
has_varargs: true,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 3,
return_type: ReturnTypeClass::Integer,
call_profile: vec![],
cfi_enabled: false,
};
assert!(!is_eligible_for_import(&func));
}
#[test]
fn test_is_eligible_for_import_cfi_disqualifies() {
let func = FunctionSummary {
name: "cfi_func".to_string(),
guid: 998,
inst_count: 100,
call_count: 2,
callees: vec![],
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 1,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: true,
};
assert!(!is_eligible_for_import(&func));
}
#[test]
fn test_is_eligible_for_import_huge_function() {
let func = FunctionSummary {
name: "huge".to_string(),
guid: 997,
inst_count: 20000,
call_count: 5,
callees: vec![],
hotness: 255,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 2,
return_type: ReturnTypeClass::Integer,
call_profile: vec![],
cfi_enabled: false,
};
assert!(!is_eligible_for_import(&func));
}
#[test]
fn test_is_constant_propagatable_large_size() {
let global = GlobalVarSummary {
name: "big_const".to_string(),
guid: 500,
size: 1024,
alignment: 64,
is_constant: true,
is_read_only: true,
is_external: false,
refs: vec![],
init_hash: 0,
is_tls: false,
linkage: GlobalLinkageKind::Internal,
};
assert!(!is_constant_propagatable(&global));
}
#[test]
fn test_hotness_percentile_zero_total() {
let combined = CombinedIndex {
modules: vec![],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 0,
};
let pct = hotness_percentile(42, &combined);
assert_eq!(pct, 0.0);
}
#[test]
fn test_function_summary_round_trip_fields() {
let fs = FunctionSummary {
name: "roundtrip_test".to_string(),
guid: compute_guid_from_name("roundtrip_test"),
inst_count: 1234,
call_count: 56,
callees: vec![1, 2, 3],
hotness: 220,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: true,
is_local: false,
param_count: 3,
return_type: ReturnTypeClass::Pointer,
call_profile: vec![(10, 100), (20, 200)],
cfi_enabled: false,
};
let cloned = fs.clone();
assert_eq!(cloned.name, "roundtrip_test");
assert_eq!(cloned.inst_count, 1234);
assert_eq!(cloned.callees, vec![1, 2, 3]);
assert_eq!(cloned.call_profile, vec![(10, 100), (20, 200)]);
}
#[test]
fn test_import_decision_debug_fmt() {
let d = ImportDecision::Import {
name: "fmt_test".to_string(),
reason: ThinLTOImportReason::HotCall,
hotness: 255,
guid: 0xDEADBEEF,
};
let debug_str = format!("{:?}", d);
assert!(debug_str.contains("Import"));
assert!(debug_str.contains("fmt_test"));
let s = ImportDecision::Skip {
name: "skip_me".to_string(),
reason: "too cold".to_string(),
};
let debug_str2 = format!("{:?}", s);
assert!(debug_str2.contains("Skip"));
assert!(debug_str2.contains("skip_me"));
}
#[test]
fn test_devirt_result_debug_fmt() {
let r = DevirtResult::Devirtualized {
vtable_name: "Base".to_string(),
target_function: "Base::virt".to_string(),
offset: 8,
};
let s = format!("{:?}", r);
assert!(s.contains("Devirtualized"));
let r2 = DevirtResult::NotDevirtualized {
reason: "unknown vtable".to_string(),
};
let s2 = format!("{:?}", r2);
assert!(s2.contains("NotDevirtualized"));
}
#[test]
fn test_lto_plugin_status_debug() {
let s = LTOPluginStatus::Error(LTOPluginErrorKind::InvalidBitcode);
let d = format!("{:?}", s);
assert!(d.contains("Error"));
assert!(d.contains("InvalidBitcode"));
}
#[test]
fn test_module_summary_flags_non_default() {
let flags = ModuleSummaryFlags {
function_sections: true,
data_sections: true,
sanitizer_coverage: true,
lto_compiled: true,
cfi_enabled: true,
safestack: true,
has_profile: true,
has_eh: true,
is_pic: true,
};
assert!(flags.function_sections);
assert!(flags.data_sections);
assert!(flags.lto_compiled);
assert!(flags.is_pic);
}
#[test]
fn test_thin_lto_stats_independent() {
let mut t1 = X86ThinLTOFull::new();
let mut t2 = X86ThinLTOFull::new();
t1.stats.modules_processed = 10;
t2.stats.modules_processed = 20;
assert_eq!(t1.stats.modules_processed, 10);
assert_eq!(t2.stats.modules_processed, 20);
}
#[test]
fn test_plugin_states_independent() {
let mut p1 = X86LTOPlugin::new();
let mut p2 = X86LTOPlugin::new();
p1.onload();
assert!(matches!(p1.status, LTOPluginStatus::Ready));
assert!(matches!(p2.status, LTOPluginStatus::Ready));
}
#[test]
fn test_full_pipeline_thin_lto_to_internalization() {
let mut thin = X86ThinLTOFull::new();
let m1 = make_module_summary("mod1.o", &[("entry", 0), ("helper", 0)]);
let m2 = make_module_summary("mod2.o", &[("lib_func", 0)]);
thin.build_combined_index(vec![m1, m2]);
thin.decide_imports();
let ci = thin.get_combined_index().unwrap().clone();
let mut intern = X86LTOInternalization::new();
intern.build_resolution_table(&ci);
intern.compute_export_list(&ci, &std::collections::HashMap::new(), &[]);
intern.internalize_non_exported();
let entry_guid = compute_guid_from_name("entry");
assert!(intern.export_list.contains(&entry_guid));
}
#[test]
fn test_full_pipeline_debug_to_whole_program() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::Full);
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 1,
comp_dir: "/src".to_string(),
source_file: "prog.c".to_string(),
producer: "clang".to_string(),
low_pc: 0x1000,
high_pc: 0x2000,
line_table_data: vec![],
range_list_data: vec![],
});
let _merged = di.merge_debug_info(&[]);
let mut wp = X86LTOWholeProgram::new();
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "prog.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![],
global_summaries: vec![make_global_summary("g", 8, false)],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 0,
is_thin_lto: false,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 0,
total_globals: 1,
};
for (mi, m) in combined.modules.iter().enumerate() {
for g in &m.global_summaries {
combined.global_to_module.insert(g.guid, mi);
}
}
wp.run_all(&combined, &[]);
assert_eq!(wp.whole_program_stats.globals_demoted, 1);
}
#[test]
fn test_features_struct_integration() {
let mut features = X86LLDFeatures {
thin_lto: X86ThinLTOFull::new(),
internalization: X86LTOInternalization::new(),
debug_info: X86LTODebugInfo::new(),
whole_program: X86LTOWholeProgram::new(),
plugin: X86LTOPlugin::new(),
combined_index: None,
enabled: true,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
output_path: "/tmp/a.out".to_string(),
opt_level: 2,
num_threads: 4,
diagnostics: vec![LTOFeatureDiagnostic {
level: LTOFeatureDiagLevel::Info,
message: "Features initialized".to_string(),
source: Some("x86_lld_features".to_string()),
}],
};
let data = make_thin_bitcode_data();
let summary = features.thin_lto.compute_module_summary("test.o", &data);
assert!(summary.is_thin_lto);
features.thin_lto.build_combined_index(vec![summary]);
let ci = features.thin_lto.get_combined_index().unwrap().clone();
features.internalization.build_resolution_table(&ci);
features
.internalization
.compute_export_list(&ci, &std::collections::HashMap::new(), &[]);
features.internalization.internalize_non_exported();
features.whole_program.run_all(&ci, &[]);
assert!(matches!(features.plugin.status, LTOPluginStatus::Ready));
assert_eq!(features.diagnostics.len(), 1);
assert_eq!(features.diagnostics[0].level, LTOFeatureDiagLevel::Info);
}
#[test]
fn test_features_disabled_mode() {
let features = X86LLDFeatures {
thin_lto: X86ThinLTOFull::new(),
internalization: X86LTOInternalization::new(),
debug_info: X86LTODebugInfo::new(),
whole_program: X86LTOWholeProgram::new(),
plugin: X86LTOPlugin::new(),
combined_index: None,
enabled: false,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
output_path: String::new(),
opt_level: 0,
num_threads: 1,
diagnostics: vec![],
};
assert!(!features.enabled);
}
#[test]
fn test_random_bitcode_input() {
let mut thin = X86ThinLTOFull::new();
let data: Vec<u8> = (0..1000).map(|i| (i * 37 + 13) as u8).collect();
let _summary = thin.compute_module_summary("random.o", &data);
}
#[test]
fn test_max_guid_values() {
let guid = u64::MAX;
let mut thin = X86ThinLTOFull::new();
thin.cache_store(guid, vec![0xFF; 256]);
assert!(thin.cache_lookup(guid).is_some());
}
#[test]
fn test_duplicate_module_paths() {
let mut thin = X86ThinLTOFull::new();
let m1 = make_module_summary("dup.o", &[("a", 0)]);
let m2 = make_module_summary("dup.o", &[("b", 0)]);
thin.build_combined_index(vec![m1, m2]);
let ci = thin.get_combined_index().unwrap();
assert_eq!(ci.modules.len(), 2);
}
#[test]
fn test_very_deep_call_graph() {
let mut thin = X86ThinLTOFull::new();
thin.max_import_depth = 10;
let mut modules = Vec::new();
let mut prev_guid = 0u64;
for depth in 0..20u64 {
let name = format!("func_{}", depth);
let guid = compute_guid_from_name(&name);
let mut callees = Vec::new();
if depth > 0 {
callees.push(prev_guid);
}
modules.push(ModuleSummaryIndex {
module_hash: depth + 1,
module_path: format!("d{}.o", depth),
target_triple: "x86_64".to_string(),
function_summaries: vec![FunctionSummary {
name,
guid,
inst_count: 30,
call_count: if callees.is_empty() { 0 } else { 1 },
callees,
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: depth == 0,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
}],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 30,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
});
prev_guid = guid;
}
thin.build_combined_index(modules);
thin.decide_imports();
}
#[test]
fn test_combined_index_memory_usage() {
let mut modules = Vec::new();
for i in 0..200u64 {
let mut funcs = Vec::new();
for j in 0..5u64 {
let guid = i * 1000 + j;
funcs.push(FunctionSummary {
name: format!("f_{}_{}", i, j),
guid,
inst_count: 50,
call_count: 3,
callees: vec![guid + 1, guid + 2],
hotness: 100,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 2,
return_type: ReturnTypeClass::Integer,
call_profile: vec![],
cfi_enabled: false,
});
}
modules.push(ModuleSummaryIndex {
module_hash: i,
module_path: format!("m{}.o", i),
target_triple: "x86_64".to_string(),
function_summaries: funcs,
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 250,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
});
}
let mut thin = X86ThinLTOFull::new();
thin.build_combined_index(modules);
let ci = thin.get_combined_index().unwrap();
assert_eq!(ci.total_functions, 1000);
assert_eq!(ci.modules.len(), 200);
}
#[test]
fn test_incremental_cache_same_module_no_rebuild() {
let mut thin = X86ThinLTOFull::new();
thin.set_cache_mode(ThinLTOCacheMode::Incremental);
let data = vec![1, 2, 3, 4];
let key = thin.compute_cache_key(&data, 0).unwrap();
thin.cache.insert(key.clone(), vec![9, 9, 9]);
assert!(thin.cache_lookup(compute_fnv1a_64_full(&data)).is_none()); thin.cache_store(0x1234, vec![5, 6, 7, 8]);
assert_eq!(thin.cache_lookup(0x1234), Some(&vec![5, 6, 7, 8]));
}
#[test]
fn test_incremental_cache_module_changed_rebuild() {
let mut thin = X86ThinLTOFull::new();
thin.set_cache_mode(ThinLTOCacheMode::Incremental);
let data_v1 = vec![1, 2, 3];
let data_v2 = vec![1, 2, 4]; let key1 = compute_fnv1a_64_full(&data_v1);
let key2 = compute_fnv1a_64_full(&data_v2);
assert_ne!(key1, key2);
thin.cache_store(key1, vec![10, 20, 30]);
assert!(thin.cache_lookup(key1).is_some());
assert!(thin.cache_lookup(key2).is_none());
}
#[test]
fn test_cache_key_different_modules() {
let mut thin = X86ThinLTOFull::new();
thin.set_cache_mode(ThinLTOCacheMode::ComprehensiveKey);
let key_a = thin.compute_cache_key(&[1, 2, 3], 0);
let key_b = thin.compute_cache_key(&[1, 2, 3], 1);
assert!(key_a.is_some() && key_b.is_some());
assert_ne!(key_a, key_b); }
#[test]
fn test_cache_key_different_opt_levels() {
let mut thin = X86ThinLTOFull::new();
thin.set_cache_mode(ThinLTOCacheMode::ComprehensiveKey);
thin.set_opt_level(2);
let key_o2 = thin.compute_cache_key(&[1, 2, 3], 0);
thin.set_opt_level(3);
let key_o3 = thin.compute_cache_key(&[1, 2, 3], 0);
assert!(key_o2.is_some() && key_o3.is_some());
assert_ne!(key_o2, key_o3);
}
#[test]
fn test_partition_assignment_balanced() {
let num_modules = 256;
let num_threads = 8;
let per_thread = num_modules / num_threads;
assert_eq!(per_thread, 32);
}
#[test]
fn test_partition_assignment_uneven() {
let num_modules = 100;
let num_threads = 7;
let per_thread = num_modules / num_threads;
let remainder = num_modules % num_threads;
assert_eq!(per_thread, 14);
assert_eq!(remainder, 2);
let total = per_thread * num_threads + remainder;
assert_eq!(total, 100);
}
#[test]
fn test_partition_max_limit() {
assert!(THINLTO_MAX_PARTITIONS >= 256);
let mut thin = X86ThinLTOFull::new();
thin.set_num_threads(THINLTO_MAX_PARTITIONS as u32);
assert_eq!(thin.num_threads, 256);
}
#[test]
fn test_dwarf_expression_merge_across_modules() {
let mut di = X86LTODebugInfo::new();
let cu1 = DwarfCompileUnitData {
offset: 0,
length: 150,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 0xA,
comp_dir: "/proj".to_string(),
source_file: "a.cpp".to_string(),
producer: "g++".to_string(),
low_pc: 0x4000,
high_pc: 0x4100,
line_table_data: vec![],
range_list_data: vec![],
};
let cu2 = DwarfCompileUnitData {
offset: 150,
length: 150,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 0xB,
comp_dir: "/proj".to_string(),
source_file: "b.cpp".to_string(),
producer: "g++".to_string(),
low_pc: 0x4100,
high_pc: 0x4200,
line_table_data: vec![],
range_list_data: vec![],
};
di.add_compile_unit(cu1);
di.add_compile_unit(cu2);
di.set_merge_mode(LTODebugMergeMode::Full);
let merged = di.merge_debug_info(&[]);
assert!(!merged.is_empty());
assert_eq!(di.debug_stats.cus_merged, 2);
}
#[test]
fn test_skeleton_cu_address_range_preserved() {
let mut di = X86LTODebugInfo::new();
di.set_merge_mode(LTODebugMergeMode::Skeleton);
di.add_compile_unit(DwarfCompileUnitData {
offset: 0,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: 999,
comp_dir: "/src".to_string(),
source_file: "lib.rs".to_string(),
producer: "rustc".to_string(),
low_pc: 0x100000,
high_pc: 0x101000,
line_table_data: vec![],
range_list_data: vec![],
});
let _merged = di.merge_debug_info(&[]);
let sk = di.generate_skeleton_for_module(999).unwrap();
assert_eq!(sk.module_hash, 999);
assert_eq!(sk.dwarf_version, 5);
assert_eq!(sk.address_size, 8);
}
#[test]
fn test_cross_module_inline_small_function() {
let func = FunctionSummary {
name: "tiny_inline_candidate".to_string(),
guid: 0xCAFE,
inst_count: 5, call_count: 1,
callees: vec![],
hotness: 255,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 1,
return_type: ReturnTypeClass::Integer,
call_profile: vec![],
cfi_enabled: false,
};
assert!(is_eligible_for_import(&func));
assert!(func.inst_count <= 100);
}
#[test]
fn test_cross_module_no_inline_large_function() {
let func = FunctionSummary {
name: "huge_no_inline".to_string(),
guid: 0xBEEF,
inst_count: 5000,
call_count: 10,
callees: vec![],
hotness: 250,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 5,
return_type: ReturnTypeClass::StructSRet,
call_profile: vec![],
cfi_enabled: false,
};
assert!(is_eligible_for_import(&func));
assert!(func.inst_count > 1000);
}
#[test]
fn test_visibility_override_hidden() {
let mut intern = X86LTOInternalization::new();
let m = make_module_summary("vis.o", &[("hidden_func", 0)]);
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 1,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
}
}
let mut visibility_overrides = std::collections::HashMap::new();
visibility_overrides.insert("hidden_func".to_string(), SymbolVisibility::Hidden);
intern.compute_export_list(&combined, &visibility_overrides, &[]);
let guid = compute_guid_from_name("hidden_func");
assert!(!intern.export_list.contains(&guid)); }
#[test]
fn test_visibility_override_protected() {
let mut intern = X86LTOInternalization::new();
let m = make_module_summary("vis.o", &[("prot_func", 0)]);
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 1,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
}
}
let mut visibility_overrides = std::collections::HashMap::new();
visibility_overrides.insert("prot_func".to_string(), SymbolVisibility::Protected);
intern.compute_export_list(&combined, &visibility_overrides, &[]);
let guid = compute_guid_from_name("prot_func");
assert!(intern.export_list.contains(&guid)); }
#[test]
fn test_dead_global_reason_all_variants() {
let all = vec![
DeadGlobalReason::Unreferenced,
DeadGlobalReason::ConstantFolded,
DeadGlobalReason::Replaced,
DeadGlobalReason::Merged,
];
assert_eq!(all.len(), 4);
assert_eq!(
DeadGlobalReason::Unreferenced,
DeadGlobalReason::Unreferenced
);
assert_ne!(DeadGlobalReason::Unreferenced, DeadGlobalReason::Merged);
}
#[test]
fn test_dead_global_result_clone() {
let dg = DeadGlobalResult {
name: "dead".to_string(),
guid: 42,
size_freed: 1024,
reason: DeadGlobalReason::Unreferenced,
};
let dg2 = dg.clone();
assert_eq!(dg2.name, "dead");
assert_eq!(dg2.guid, 42);
assert_eq!(dg2.size_freed, 1024);
assert_eq!(dg2.reason, DeadGlobalReason::Unreferenced);
}
#[test]
fn test_lto_plugin_error_kind_all_variants() {
let all = vec![
LTOPluginErrorKind::InvalidBitcode,
LTOPluginErrorKind::TargetMismatch,
LTOPluginErrorKind::OptimizationFailed,
LTOPluginErrorKind::CodegenFailed,
LTOPluginErrorKind::CacheError,
LTOPluginErrorKind::InternalError,
];
assert_eq!(all.len(), 6);
}
#[test]
fn test_lto_plugin_status_error() {
let status = LTOPluginStatus::Error(LTOPluginErrorKind::CacheError);
match status {
LTOPluginStatus::Error(kind) => assert_eq!(kind, LTOPluginErrorKind::CacheError),
_ => panic!("Expected Error status"),
}
}
#[test]
fn test_extract_vtable_entries_empty() {
let thin = X86ThinLTOFull::new();
let data = vec![0u8; 16]; let vtables = thin.extract_vtable_summaries(&data, 0);
assert!(vtables.is_empty());
}
#[test]
fn test_extract_alias_entries_empty() {
let thin = X86ThinLTOFull::new();
let data = vec![0u8; 24];
let aliases = thin.extract_alias_summaries(&data, 0);
assert!(aliases.is_empty());
}
#[test]
fn test_extract_function_summaries_truncated_record() {
let thin = X86ThinLTOFull::new();
let mut data = vec![0x14, 0x00]; data.push(10); data.push(b'a');
data.push(b'b');
let summaries = thin.extract_function_summaries(&data, 0);
}
#[test]
fn test_extract_global_summaries_truncated_record() {
let thin = X86ThinLTOFull::new();
let mut data = vec![0x16, 0x00]; data.push(5); data.push(b'g');
let summaries = thin.extract_global_summaries(&data, 0);
}
#[test]
fn test_coff_plugin_ignores_non_lto_opts() {
let mut plugin = X86LTOPlugin::new();
plugin.parse_coff_option("/opt:ref");
assert!(!plugin.coff_lto_enabled);
plugin.parse_coff_option("/opt:icf");
assert!(!plugin.coff_lto_enabled);
plugin.parse_coff_option("/opt:lldlto");
assert!(plugin.coff_lto_enabled);
}
#[test]
fn test_macho_plugin_no_library_by_default() {
let plugin = X86LTOPlugin::new();
assert_eq!(plugin.macho_lto_library, None);
}
#[test]
fn test_macho_plugin_set_and_clear_library() {
let mut plugin = X86LTOPlugin::new();
plugin.set_macho_lto_library("/usr/local/lib/libLTO.dylib");
assert_eq!(
plugin.macho_lto_library,
Some("/usr/local/lib/libLTO.dylib".to_string())
);
}
#[test]
fn test_plugin_claim_invalid_bitcode_length() {
let mut plugin = X86LTOPlugin::new();
let req = PluginClaimRequest {
file_path: "short.bc".to_string(),
file_data: vec![b'B', b'C'], file_descriptor: None,
file_size: 2,
symbol_table_offset: 0,
symbol_count: 0,
};
assert_eq!(plugin.claim_file(&req), ClaimResult::NotClaimed);
}
#[test]
fn test_combined_index_query_by_guid() {
let mut thin = X86ThinLTOFull::new();
let guid = compute_guid_from_name("findme");
let m = make_module_summary("find.o", &[("findme", 0)]);
thin.build_combined_index(vec![m]);
let ci = thin.get_combined_index().unwrap();
assert!(ci.function_to_module.contains_key(&guid));
assert_eq!(ci.function_to_module[&guid], 0);
}
#[test]
fn test_combined_index_call_graph_built() {
let mut thin = X86ThinLTOFull::new();
let callee_guid = compute_guid_from_name("callee");
let caller_guid = compute_guid_from_name("caller");
let m = ModuleSummaryIndex {
module_hash: 1,
module_path: "cg.o".to_string(),
target_triple: "x86_64".to_string(),
function_summaries: vec![
FunctionSummary {
name: "caller".to_string(),
guid: caller_guid,
inst_count: 50,
call_count: 1,
callees: vec![callee_guid],
hotness: 200,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: true,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
},
FunctionSummary {
name: "callee".to_string(),
guid: callee_guid,
inst_count: 30,
call_count: 0,
callees: vec![],
hotness: 100,
has_inline_asm: false,
has_varargs: false,
is_external: true,
is_entry_point: false,
is_local: false,
param_count: 0,
return_type: ReturnTypeClass::Void,
call_profile: vec![],
cfi_enabled: false,
},
],
global_summaries: vec![],
vtable_summaries: vec![],
alias_summaries: vec![],
instruction_count: 80,
is_thin_lto: true,
pgo_profile_hash: None,
flags: ModuleSummaryFlags::default(),
};
thin.build_combined_index(vec![m]);
let ci = thin.get_combined_index().unwrap();
assert!(ci.call_graph.contains_key(&caller_guid));
assert_eq!(ci.call_graph[&caller_guid], vec![callee_guid]);
assert!(ci.reverse_call_graph.contains_key(&callee_guid));
assert_eq!(ci.reverse_call_graph[&callee_guid], vec![caller_guid]);
}
#[test]
fn test_guid_from_name_consistency() {
let n1 = "std::vector<int, std::allocator<int>>::push_back";
let n2 = "std::vector<int, std::allocator<int>>::push_back";
assert_eq!(compute_guid_from_name(n1), compute_guid_from_name(n2));
let n3 = "std::vector<double, std::allocator<double>>::push_back";
assert_ne!(compute_guid_from_name(n1), compute_guid_from_name(n3));
}
#[test]
fn test_fnv1a_64_known_vectors() {
let h_empty = compute_fnv1a_64_full(b"");
assert_eq!(h_empty, FNV64_OFFSET_BASIS);
let h1 = compute_fnv1a_64_full(b"hello");
let h2 = compute_fnv1a_64_full(b"hello");
assert_eq!(h1, h2);
}
#[test]
fn test_summary_hash_deterministic() {
let h1 = compute_summary_hash(b"data_to_hash");
let h2 = compute_summary_hash(b"data_to_hash");
assert_eq!(h1, h2);
}
#[test]
fn test_hash_call_graph_edge_symmetry() {
let e1 = hash_call_graph_edge(0xAAAA, 0xBBBB);
let e2 = hash_call_graph_edge(0xBBBB, 0xAAAA);
assert_ne!(e1, e2);
}
#[test]
fn test_stress_iterate_many_import_cycles() {
let mut thin = X86ThinLTOFull::new();
for iteration in 0..10 {
let m = make_module_summary(
&format!("iter{}.o", iteration),
&[(&format!("f{}", iteration), 0)],
);
thin.build_combined_index(vec![m]);
thin.decide_imports();
}
}
#[test]
fn test_stress_many_dllexport_symbols() {
let mut intern = X86LTOInternalization::new();
let mut funcs = Vec::new();
for i in 0..1000 {
funcs.push((format!("sym_{}", i), 0u64));
}
let names: Vec<&str> = funcs.iter().map(|(n, _)| n.as_str()).collect::<Vec<_>>();
let name_refs: Vec<(&str, u64)> = names.iter().map(|n| (*n, 0u64)).collect();
let m = make_module_summary("many.o", &name_refs);
let mut combined = CombinedIndex {
modules: vec![m],
function_to_module: std::collections::HashMap::new(),
global_to_module: std::collections::HashMap::new(),
vtable_to_module: std::collections::HashMap::new(),
alias_to_module: std::collections::HashMap::new(),
call_graph: std::collections::HashMap::new(),
reverse_call_graph: std::collections::HashMap::new(),
export_guids: std::collections::HashSet::new(),
total_functions: 1000,
total_globals: 0,
};
for (mi, m) in combined.modules.iter().enumerate() {
for f in &m.function_summaries {
combined.function_to_module.insert(f.guid, mi);
}
}
let dllexport: Vec<String> = (0..500).map(|i| format!("sym_{}", i)).collect();
intern.compute_export_list(&combined, &std::collections::HashMap::new(), &dllexport);
assert_eq!(intern.export_list.len(), 500);
}
#[test]
fn test_large_string_table_dedup() {
let mut di = X86LTODebugInfo::new();
for i in 0..500u64 {
di.add_compile_unit(DwarfCompileUnitData {
offset: i * 100,
length: 100,
version: 5,
abbrev_offset: 0,
address_size: 8,
module_hash: i,
comp_dir: format!("/build/v{}", i % 10),
source_file: format!("source_{}.cpp", i),
producer: if i % 2 == 0 { "clang++" } else { "g++" }.to_string(),
low_pc: 0x1000 + i * 0x100,
high_pc: 0x1100 + i * 0x100,
line_table_data: vec![],
range_list_data: vec![],
});
}
di.set_merge_mode(LTODebugMergeMode::Full);
di.merge_debug_info(&[]);
assert_eq!(
di.string_table
.keys()
.filter(|k| k.starts_with("/build"))
.count(),
10
);
assert!(di.string_table.contains_key("clang++"));
assert!(di.string_table.contains_key("g++"));
}
}