use llvm_native_core::lto::{GlobalLinkage, GlobalValueSummary, ModuleSummary, TypeIdSummary};
use llvm_native_core::module::Module;
use llvm_native_core::value::ValueRef;
use llvm_native_core::SubclassKind;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct CombinedIndex {
pub modules: Vec<ModuleSummary>,
pub global_index: HashMap<String, GlobalValueSummary>,
pub type_ids: HashMap<String, TypeIdSummary>,
pub call_graph: HashMap<String, Vec<String>>,
pub prevailing_map: HashMap<String, usize>,
pub exported_symbols: HashSet<String>,
}
impl CombinedIndex {
pub fn new() -> Self {
Self {
modules: Vec::new(),
global_index: HashMap::new(),
type_ids: HashMap::new(),
call_graph: HashMap::new(),
prevailing_map: HashMap::new(),
exported_symbols: HashSet::new(),
}
}
pub fn build_from_modules(&mut self, modules: &[Module]) {
self.modules.clear();
self.global_index.clear();
self.type_ids.clear();
self.call_graph.clear();
self.prevailing_map.clear();
for (mod_idx, module) in modules.iter().enumerate() {
let summary = summarize_module(module, mod_idx);
self.merge_module_summary(summary, mod_idx);
}
self.resolve_prevailing();
}
fn merge_module_summary(&mut self, summary: ModuleSummary, mod_idx: usize) {
self.modules.push(summary.clone());
for func_name in &summary.function_names {
if !self.prevailing_map.contains_key(func_name) {
self.prevailing_map.insert(func_name.clone(), mod_idx);
}
}
for global_name in &summary.global_names {
if !self.prevailing_map.contains_key(global_name) {
self.prevailing_map.insert(global_name.clone(), mod_idx);
}
}
}
pub fn add_global_summary(&mut self, name: String, summary: GlobalValueSummary) {
self.global_index.insert(name, summary);
}
pub fn add_type_id(&mut self, type_id: String, summary: TypeIdSummary) {
self.type_ids.insert(type_id, summary);
}
pub fn add_call_edge(&mut self, caller: &str, callee: &str) {
self.call_graph
.entry(caller.to_string())
.or_default()
.push(callee.to_string());
}
fn resolve_prevailing(&mut self) {}
pub fn get_prevailing_module(&self, name: &str) -> Option<usize> {
self.prevailing_map.get(name).copied()
}
pub fn is_prevailing(&self, name: &str, mod_idx: usize) -> bool {
self.prevailing_map.get(name).copied() == Some(mod_idx)
}
pub fn get_global(&self, name: &str) -> Option<&GlobalValueSummary> {
self.global_index.get(name)
}
pub fn get_instruction_count(&self, name: &str) -> Option<u32> {
self.global_index.get(name).map(|s| s.instruction_count)
}
pub fn get_callees(&self, name: &str) -> Option<&Vec<String>> {
self.call_graph.get(name)
}
pub fn is_import_eligible(&self, name: &str) -> bool {
self.global_index
.get(name)
.map(|s| s.is_import_eligible)
.unwrap_or(false)
}
pub fn num_modules(&self) -> usize {
self.modules.len()
}
pub fn num_globals(&self) -> usize {
self.global_index.len()
}
pub fn set_exported_symbols(&mut self, symbols: HashSet<String>) {
self.exported_symbols = symbols;
}
pub fn is_exported(&self, name: &str) -> bool {
self.exported_symbols.contains(name)
}
}
impl Default for CombinedIndex {
fn default() -> Self {
Self::new()
}
}
pub struct ThinLTOBackend {
pub combined_index: CombinedIndex,
pub imports: HashMap<String, Vec<String>>,
pub import_instr_limit: u32,
imported_functions: HashSet<String>,
optimization_count: usize,
}
impl ThinLTOBackend {
pub fn new() -> Self {
Self {
combined_index: CombinedIndex::new(),
imports: HashMap::new(),
import_instr_limit: 100,
imported_functions: HashSet::new(),
optimization_count: 0,
}
}
pub fn build_combined_index(&mut self, modules: &[Module]) {
self.combined_index.build_from_modules(modules);
}
pub fn compute_import_decisions(&mut self) {
self.imports.clear();
let num_modules = self.combined_index.num_modules();
for mod_idx in 0..num_modules {
let module_name = self
.combined_index
.modules
.get(mod_idx)
.map(|m| m.module_name.clone())
.unwrap_or_else(|| format!("module_{}", mod_idx));
let mut to_import: Vec<String> = Vec::new();
if let Some(summary) = self.combined_index.modules.get(mod_idx) {
for func_name in &summary.function_names {
if let Some(callees) = self.combined_index.get_callees(func_name) {
for callee in callees {
if self.should_import(callee, mod_idx) && !to_import.contains(callee) {
to_import.push(callee.clone());
}
}
}
}
}
if !to_import.is_empty() {
self.imports.insert(module_name, to_import);
}
}
}
fn should_import(&self, name: &str, current_mod: usize) -> bool {
if self.imported_functions.contains(name) {
return false;
}
let summary = match self.combined_index.get_global(name) {
Some(s) => s,
None => return false,
};
if self.combined_index.get_prevailing_module(name) == Some(current_mod) {
return false;
}
if !summary.is_import_eligible {
return false;
}
summary.instruction_count <= self.import_instr_limit
}
pub fn run_backend(&self, module: &mut Module, summary: &ModuleSummary) {
let imports: Vec<String> = self
.imports
.get(&summary.module_name)
.cloned()
.unwrap_or_default();
self.import_functions(module, &imports);
self.promote_locals(module);
self.rename_globals_for_thin_lto(module);
let _ = module;
}
pub fn import_functions(&self, module: &mut Module, imports: &[String]) {
for func_name in imports {
if module.has_function(func_name) {
if !module.is_imported_function(func_name) {
continue;
}
}
module.add_imported_function(func_name);
}
}
pub fn promote_locals(&self, module: &mut Module) {
let _ = module;
}
pub fn rename_globals_for_thin_lto(&self, module: &mut Module) {
let module_hash = compute_module_hash(module);
let short_hash = &module_hash[..module_hash.len().min(8)];
for f in &mut module.functions {
let func_name = f.borrow().name.clone();
let f_kind = f.borrow().subclass;
if matches!(f_kind, SubclassKind::Function) {
let new_name = format!("{}.llvm.{}", func_name, short_hash);
f.borrow_mut().name = new_name;
}
}
}
pub fn emit_summary(&self, module: &Module) -> ModuleSummary {
summarize_module(module, 0)
}
pub fn parse_summary(data: &[u8]) -> Option<ModuleSummary> {
parse_summary_from_bytes(data)
}
pub fn optimization_count(&self) -> usize {
self.optimization_count
}
pub fn set_import_instr_limit(&mut self, limit: u32) {
self.import_instr_limit = limit;
}
pub fn set_exported_symbols(&mut self, symbols: HashSet<String>) {
self.combined_index.set_exported_symbols(symbols);
}
}
impl Default for ThinLTOBackend {
fn default() -> Self {
Self::new()
}
}
fn summarize_module(module: &Module, _mod_idx: usize) -> ModuleSummary {
let function_names: Vec<String> = module
.get_function_names()
.into_iter()
.filter(|name| {
module
.get_function(name)
.map(|fref| {
let f = fref.borrow();
f.subclass == SubclassKind::Function && !module.is_imported_function(name)
})
.unwrap_or(false)
})
.collect();
let global_names: Vec<String> = module
.globals
.iter()
.filter(|gv| {
let g = gv.borrow();
g.subclass == SubclassKind::GlobalVariable
})
.map(|gv| gv.borrow().name.clone())
.collect();
ModuleSummary {
module_name: module.name.clone(),
module_hash: compute_module_hash(module),
function_names,
global_names,
}
}
fn compute_module_hash(module: &Module) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
module.name.hash(&mut hasher);
let mut fn_names: Vec<String> = module
.functions
.iter()
.map(|f| f.borrow().name.clone())
.collect();
fn_names.sort();
for name in &fn_names {
name.hash(&mut hasher);
}
format!("{:016x}", hasher.finish())
}
fn parse_summary_from_bytes(data: &[u8]) -> Option<ModuleSummary> {
if data.len() < 4 {
return None;
}
let mut offset = 0;
let read_u32 = |data: &[u8], off: &mut usize| -> Option<u32> {
if *off + 4 > data.len() {
return None;
}
let val = u32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]);
*off += 4;
Some(val)
};
let read_string = |data: &[u8], off: &mut usize| -> Option<String> {
let len = read_u32(data, off)? as usize;
if *off + len > data.len() {
return None;
}
let s = String::from_utf8(data[*off..*off + len].to_vec()).ok()?;
*off += len;
Some(s)
};
let module_name = read_string(data, &mut offset)?;
let module_hash = read_string(data, &mut offset)?;
let num_functions = read_u32(data, &mut offset)? as usize;
let mut function_names = Vec::with_capacity(num_functions);
for _ in 0..num_functions {
function_names.push(read_string(data, &mut offset)?);
}
let num_globals = read_u32(data, &mut offset)? as usize;
let mut global_names = Vec::with_capacity(num_globals);
for _ in 0..num_globals {
global_names.push(read_string(data, &mut offset)?);
}
Some(ModuleSummary {
module_name,
module_hash,
function_names,
global_names,
})
}
#[allow(dead_code)]
fn serialize_summary_to_bytes(summary: &ModuleSummary) -> Vec<u8> {
let mut buf = Vec::new();
let write_u32 = |buf: &mut Vec<u8>, val: u32| {
buf.extend_from_slice(&val.to_le_bytes());
};
let write_string = |buf: &mut Vec<u8>, s: &str| {
write_u32(buf, s.len() as u32);
buf.extend_from_slice(s.as_bytes());
};
write_string(&mut buf, &summary.module_name);
write_string(&mut buf, &summary.module_hash);
write_u32(&mut buf, summary.function_names.len() as u32);
for name in &summary.function_names {
write_string(&mut buf, name);
}
write_u32(&mut buf, summary.global_names.len() as u32);
for name in &summary.global_names {
write_string(&mut buf, name);
}
buf
}
pub fn compute_import_cost(summary: &GlobalValueSummary) -> u32 {
summary.instruction_count
}
pub fn compute_import_benefit(summary: &GlobalValueSummary) -> u32 {
let base_benefit = if summary.is_function {
10u32.saturating_add(summary.callees.len() as u32 * 2)
} else {
1
};
if summary.entry_count > 0 {
base_benefit.saturating_add((summary.entry_count.min(1000) / 10) as u32)
} else {
base_benefit
}
}
#[derive(Debug, Clone)]
pub struct FunctionSummary {
pub name: String,
pub guid: u64,
pub instruction_count: u32,
pub is_import_eligible: bool,
pub hotness: Option<u64>,
pub callee_list: Vec<String>,
pub referenced_globals: Vec<String>,
pub type_id: Option<String>,
pub type_test_assume_virtual_calls: Vec<String>,
pub has_inline_assembly: bool,
pub is_preserved: bool,
}
impl FunctionSummary {
pub fn new(name: &str, guid: u64, inst_count: u32) -> Self {
FunctionSummary {
name: name.to_string(),
guid,
instruction_count: inst_count,
is_import_eligible: inst_count < 10000,
hotness: None,
callee_list: Vec::new(),
referenced_globals: Vec::new(),
type_id: None,
type_test_assume_virtual_calls: Vec::new(),
has_inline_assembly: false,
is_preserved: false,
}
}
pub fn add_callee(&mut self, callee: &str) {
self.callee_list.push(callee.to_string());
}
pub fn add_referenced_global(&mut self, global: &str) {
self.referenced_globals.push(global.to_string());
}
pub fn callee_count(&self) -> usize {
self.callee_list.len()
}
}
#[derive(Debug, Clone)]
pub struct ImportAnalysis {
pub function_name: String,
pub guid: u64,
pub instruction_count: u32,
pub estimated_inline_size: u32,
pub caller_count: u32,
pub import_cost: u32,
pub import_benefit: u32,
pub should_import: bool,
pub reason: String,
}
impl ImportAnalysis {
pub fn new(name: &str, guid: u64, inst_count: u32, caller_count: u32) -> Self {
let inline_size = inst_count.min(500);
let cost = inst_count;
let benefit = 10u32.saturating_add(caller_count * 2);
ImportAnalysis {
function_name: name.to_string(),
guid,
instruction_count: inst_count,
estimated_inline_size: inline_size,
caller_count,
import_cost: cost,
import_benefit: benefit,
should_import: false,
reason: String::new(),
}
}
pub fn decide(&mut self, threshold: u32) {
self.should_import =
self.import_cost <= threshold && self.import_benefit > self.import_cost;
self.reason = if self.should_import {
format!(
"benefit({}) > cost({})",
self.import_benefit, self.import_cost
)
} else if self.import_cost > threshold {
format!("cost({}) > threshold({})", self.import_cost, threshold)
} else {
"benefit <= cost".to_string()
};
}
}
#[derive(Debug, Clone, Default)]
pub struct ThinLTOCallGraph {
pub edges: HashMap<String, Vec<String>>,
pub reverse_edges: HashMap<String, Vec<String>>,
pub entry_points: Vec<String>,
pub external_calls: HashSet<String>,
}
impl ThinLTOCallGraph {
pub fn new() -> Self {
ThinLTOCallGraph::default()
}
pub fn add_edge(&mut self, caller: &str, callee: &str) {
self.edges
.entry(caller.to_string())
.or_default()
.push(callee.to_string());
self.reverse_edges
.entry(callee.to_string())
.or_default()
.push(caller.to_string());
}
pub fn add_entry_point(&mut self, name: &str) {
self.entry_points.push(name.to_string());
}
pub fn add_external_call(&mut self, name: &str) {
self.external_calls.insert(name.to_string());
}
pub fn get_callees(&self, func: &str) -> Vec<&str> {
self.edges
.get(func)
.map(|v| v.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
pub fn get_callers(&self, func: &str) -> Vec<&str> {
self.reverse_edges
.get(func)
.map(|v| v.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
pub fn caller_count(&self, func: &str) -> usize {
self.reverse_edges.get(func).map_or(0, |v| v.len())
}
pub fn compute_hotness(&self) -> HashMap<String, u64> {
let mut hotness: HashMap<String, u64> = HashMap::new();
for entry in &self.entry_points {
hotness.insert(entry.clone(), 1000);
}
for _ in 0..10 {
let current = hotness.clone();
for (caller, callees) in &self.edges {
if let Some(&h) = current.get(caller.as_str()) {
let propagated = h / 2;
for callee in callees {
let e = hotness.entry(callee.clone()).or_insert(0);
*e = (*e).max(propagated);
}
}
}
}
hotness
}
}
#[derive(Debug, Clone, Default)]
pub struct ThinLTOImportTracker {
pub imported_functions: Vec<ImportedFunction>,
pub total_imported_instructions: u64,
pub modules_imported_from: HashSet<usize>,
}
impl ThinLTOImportTracker {
pub fn new() -> Self {
ThinLTOImportTracker::default()
}
pub fn record_import(&mut self, func: ImportedFunction) {
self.total_imported_instructions += func.instruction_count as u64;
self.modules_imported_from.insert(func.from_module);
self.imported_functions.push(func);
}
pub fn import_count(&self) -> usize {
self.imported_functions.len()
}
pub fn distinct_modules(&self) -> usize {
self.modules_imported_from.len()
}
}
#[derive(Debug, Clone)]
pub struct ImportedFunction {
pub name: String,
pub from_module: usize,
pub instruction_count: u32,
pub reason: String,
}
#[derive(Debug, Default)]
pub struct ThinLTOPromoter {
pub promoted_symbols: HashMap<String, String>,
pub rename_counter: u64,
}
impl ThinLTOPromoter {
pub fn new() -> Self {
ThinLTOPromoter::default()
}
pub fn promote_local(&mut self, _module_id: &str, local_name: &str) -> String {
self.rename_counter += 1;
let promoted = format!("{}.llvm.{}", local_name, self.rename_counter);
self.promoted_symbols
.insert(local_name.to_string(), promoted.clone());
promoted
}
pub fn get_promoted(&self, local_name: &str) -> Option<&String> {
self.promoted_symbols.get(local_name)
}
pub fn is_promoted(&self, name: &str) -> bool {
self.promoted_symbols.values().any(|v| v == name)
}
}
#[derive(Debug, Default)]
pub struct ThinLTOIndexBuilder {
pub modules: Vec<ModuleSummary>,
pub global_map: HashMap<String, GlobalValueSummary>,
pub type_ids: HashMap<String, TypeIdSummary>,
pub call_edges: Vec<(String, String)>,
pub prevailing_map: HashMap<String, usize>,
pub exported_symbols: HashSet<String>,
pub is_sealed: bool,
}
impl ThinLTOIndexBuilder {
pub fn new() -> Self {
ThinLTOIndexBuilder::default()
}
pub fn add_module(&mut self, module: ModuleSummary) {
self.modules.push(module);
}
pub fn add_global_summary(&mut self, summary: GlobalValueSummary) {
self.global_map.insert(summary.name.clone(), summary);
}
pub fn add_type_id(&mut self, type_id: TypeIdSummary) {
self.type_ids.insert(type_id.type_id.clone(), type_id);
}
pub fn add_call_edge(&mut self, caller: &str, callee: &str) {
self.call_edges
.push((caller.to_string(), callee.to_string()));
}
pub fn set_prevailing(&mut self, symbol: &str, module_idx: usize) {
self.prevailing_map.insert(symbol.to_string(), module_idx);
}
pub fn set_exported(&mut self, symbol: &str) {
self.exported_symbols.insert(symbol.to_string());
}
pub fn is_exported(&self, symbol: &str) -> bool {
self.exported_symbols.contains(symbol)
}
pub fn seal(&mut self) {
self.is_sealed = true;
}
pub fn build(&self) -> CombinedIndex {
CombinedIndex {
modules: self.modules.clone(),
global_index: self.global_map.clone(),
type_ids: self.type_ids.clone(),
call_graph: {
let mut cg: HashMap<String, Vec<String>> = HashMap::new();
for (caller, callee) in &self.call_edges {
cg.entry(caller.clone()).or_default().push(callee.clone());
}
cg
},
prevailing_map: self.prevailing_map.clone(),
exported_symbols: self.exported_symbols.clone(),
}
}
pub fn num_modules(&self) -> usize {
self.modules.len()
}
pub fn num_globals(&self) -> usize {
self.global_map.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::lto::GlobalLinkage;
use llvm_native_core::module::Module;
fn make_module(name: &str) -> Module {
let mut m = Module::new(name);
m.target_triple = Some("x86_64-unknown-linux-gnu".to_string());
m
}
#[test]
fn test_combined_index_new() {
let ci = CombinedIndex::new();
assert_eq!(ci.num_modules(), 0);
assert_eq!(ci.num_globals(), 0);
assert!(ci.type_ids.is_empty());
}
#[test]
fn test_combined_index_default() {
let ci = CombinedIndex::default();
assert_eq!(ci.num_modules(), 0);
}
#[test]
fn test_add_global_summary() {
let mut ci = CombinedIndex::new();
let summary = GlobalValueSummary {
name: "foo".to_string(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 0xABCD,
callees: vec!["bar".to_string()],
is_import_eligible: true,
instruction_count: 42,
entry_count: 100,
};
ci.add_global_summary("foo".to_string(), summary);
assert_eq!(ci.num_globals(), 1);
assert!(ci.get_global("foo").is_some());
assert_eq!(ci.get_instruction_count("foo"), Some(42));
}
#[test]
fn test_add_type_id() {
let mut ci = CombinedIndex::new();
let ts = TypeIdSummary {
type_id: "_ZTI3Foo".to_string(),
vtable_defs: vec!["_ZTV3Foo".to_string()],
};
ci.add_type_id("_ZTI3Foo".to_string(), ts);
assert_eq!(ci.type_ids.len(), 1);
}
#[test]
fn test_add_call_edge() {
let mut ci = CombinedIndex::new();
ci.add_call_edge("caller", "callee");
assert_eq!(
ci.get_callees("caller").unwrap(),
&vec!["callee".to_string()]
);
}
#[test]
fn test_is_import_eligible() {
let mut ci = CombinedIndex::new();
let s1 = GlobalValueSummary {
name: "foo".to_string(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 1,
callees: vec![],
is_import_eligible: true,
instruction_count: 50,
entry_count: 0,
};
ci.add_global_summary("foo".to_string(), s1);
assert!(ci.is_import_eligible("foo"));
}
#[test]
fn test_set_exported_symbols() {
let mut ci = CombinedIndex::new();
let mut exported = HashSet::new();
exported.insert("main".to_string());
ci.set_exported_symbols(exported);
assert!(ci.is_exported("main"));
assert!(!ci.is_exported("foo"));
}
#[test]
fn test_prevailing_module_tracking() {
let mut ci = CombinedIndex::new();
let summary = ModuleSummary {
module_name: "mod0".to_string(),
module_hash: "abc".to_string(),
function_names: vec!["foo".to_string()],
global_names: vec![],
};
ci.modules.push(summary);
ci.prevailing_map.insert("foo".to_string(), 0);
assert_eq!(ci.get_prevailing_module("foo"), Some(0));
assert!(ci.is_prevailing("foo", 0));
assert!(!ci.is_prevailing("foo", 1));
}
#[test]
fn test_thin_lto_backend_new() {
let be = ThinLTOBackend::new();
assert_eq!(be.import_instr_limit, 100);
assert_eq!(be.combined_index.num_modules(), 0);
}
#[test]
fn test_build_combined_index_empty() {
let mut be = ThinLTOBackend::new();
let modules: Vec<Module> = vec![];
be.build_combined_index(&modules);
assert_eq!(be.combined_index.num_modules(), 0);
}
#[test]
fn test_build_combined_index_single_module() {
let mut be = ThinLTOBackend::new();
let m = make_module("test_mod");
be.build_combined_index(&[m]);
assert_eq!(be.combined_index.num_modules(), 1);
}
#[test]
fn test_compute_import_decisions_empty() {
let mut be = ThinLTOBackend::new();
be.compute_import_decisions();
assert!(be.imports.is_empty());
}
#[test]
fn test_set_import_instr_limit() {
let mut be = ThinLTOBackend::new();
be.set_import_instr_limit(200);
assert_eq!(be.import_instr_limit, 200);
}
#[test]
fn test_emit_summary() {
let be = ThinLTOBackend::new();
let m = make_module("test");
let summary = be.emit_summary(&m);
assert_eq!(summary.module_name, "test");
assert!(!summary.module_hash.is_empty());
}
#[test]
fn test_compute_module_hash_deterministic() {
let m1 = make_module("test");
let m2 = make_module("test");
let h1 = compute_module_hash(&m1);
let h2 = compute_module_hash(&m2);
assert_eq!(h1, h2);
}
#[test]
fn test_compute_module_hash_different_names() {
let m1 = make_module("test1");
let m2 = make_module("test2");
let h1 = compute_module_hash(&m1);
let h2 = compute_module_hash(&m2);
assert_ne!(h1, h2);
}
#[test]
fn test_parse_summary_roundtrip() {
let summary = ModuleSummary {
module_name: "my_module".to_string(),
module_hash: "deadbeef".to_string(),
function_names: vec!["foo".to_string(), "bar".to_string()],
global_names: vec!["g_x".to_string()],
};
let bytes = serialize_summary_to_bytes(&summary);
let parsed = parse_summary_from_bytes(&bytes).unwrap();
assert_eq!(parsed.module_name, summary.module_name);
assert_eq!(parsed.module_hash, summary.module_hash);
assert_eq!(parsed.function_names, summary.function_names);
assert_eq!(parsed.global_names, summary.global_names);
}
#[test]
fn test_parse_summary_empty() {
assert!(parse_summary_from_bytes(&[]).is_none());
assert!(parse_summary_from_bytes(&[0, 0, 0]).is_none());
}
#[test]
fn test_compute_import_cost() {
let summary = GlobalValueSummary {
name: "f".to_string(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 1,
callees: vec![],
is_import_eligible: true,
instruction_count: 75,
entry_count: 0,
};
assert_eq!(compute_import_cost(&summary), 75);
}
#[test]
fn test_compute_import_benefit() {
let summary = GlobalValueSummary {
name: "f".to_string(),
linkage: GlobalLinkage::External,
is_function: true,
guid: 1,
callees: vec!["g".to_string(), "h".to_string()],
is_import_eligible: true,
instruction_count: 10,
entry_count: 100,
};
let benefit = compute_import_benefit(&summary);
assert!(benefit >= 20);
}
}