use std::collections::{HashMap, HashSet};
use inkwell::module::Module;
use llvm_sys::LLVMLinkage;
use llvm_sys::LLVMTypeKind;
use llvm_sys::core::{
LLVMCountBasicBlocks, LLVMDeleteBasicBlock, LLVMGetFirstBasicBlock, LLVMGetFirstFunction,
LLVMGetFirstGlobal, LLVMGetFirstGlobalAlias, LLVMGetFirstGlobalIFunc, LLVMGetFirstInstruction,
LLVMGetInitializer, LLVMGetLinkage, LLVMGetNextBasicBlock, LLVMGetNextFunction,
LLVMGetNextGlobal, LLVMGetNextInstruction, LLVMGetNumOperands, LLVMGetOperand, LLVMGetTypeKind,
LLVMGetUndef, LLVMGetValueName2, LLVMInstructionEraseFromParent, LLVMIsAConstant,
LLVMIsAGlobalValue, LLVMIsDeclaration, LLVMIsGlobalConstant, LLVMIsThreadLocal,
LLVMReplaceAllUsesWith, LLVMSetInitializer, LLVMSetLinkage, LLVMTypeOf,
};
use llvm_sys::prelude::LLVMValueRef;
pub struct SplitPlan {
pub bins: Vec<Vec<String>>,
pub shared_globals: HashSet<String>,
}
fn value_name(v: LLVMValueRef) -> String {
let mut len = 0usize;
let p = unsafe { LLVMGetValueName2(v, &mut len) };
let bytes = unsafe { std::slice::from_raw_parts(p.cast::<u8>(), len) };
String::from_utf8_lossy(bytes).into_owned()
}
fn functions(module: &Module) -> Vec<LLVMValueRef> {
let mut out = Vec::new();
let mut f = unsafe { LLVMGetFirstFunction(module.as_mut_ptr()) };
while !f.is_null() {
out.push(f);
f = unsafe { LLVMGetNextFunction(f) };
}
out
}
fn instructions(f: LLVMValueRef) -> Vec<LLVMValueRef> {
let mut out = Vec::new();
let mut bb = unsafe { LLVMGetFirstBasicBlock(f) };
while !bb.is_null() {
let mut i = unsafe { LLVMGetFirstInstruction(bb) };
while !i.is_null() {
out.push(i);
i = unsafe { LLVMGetNextInstruction(i) };
}
bb = unsafe { LLVMGetNextBasicBlock(bb) };
}
out
}
fn wide_op_cost(inst: LLVMValueRef) -> usize {
use llvm_sys::LLVMOpcode::*;
let opcode = unsafe { llvm_sys::core::LLVMGetInstructionOpcode(inst) };
let width = if opcode == LLVMICmp {
int_width(unsafe { LLVMGetOperand(inst, 0) })
} else {
int_width(inst)
};
if width <= 128 {
return 0;
}
let words = width.div_ceil(64);
let variable = |k: u32| {
unsafe { LLVMIsAConstant(LLVMGetOperand(inst, k)).is_null() }
};
match opcode {
LLVMShl | LLVMLShr | LLVMAShr if variable(1) => words * 8,
LLVMAdd | LLVMSub | LLVMMul | LLVMUDiv | LLVMSDiv | LLVMURem | LLVMSRem | LLVMICmp
| LLVMSelect | LLVMPHI => words * 8,
_ => words / 32,
}
}
fn int_width(v: LLVMValueRef) -> usize {
let t = unsafe { LLVMTypeOf(v) };
if unsafe { LLVMGetTypeKind(t) } == LLVMTypeKind::LLVMIntegerTypeKind {
unsafe { llvm_sys::core::LLVMGetIntTypeWidth(t) as usize }
} else {
0
}
}
fn find(parent: &mut [usize], mut x: usize) -> usize {
while parent[x] != x {
parent[x] = parent[parent[x]];
x = parent[x];
}
x
}
const GIANT_SPLIT_MIN_INSTRS: usize = 500_000;
const SHARED_GLOBAL_USERS: usize = 2;
const PIECES_PER_BIN: usize = 4;
const CONST_WALK_DEPTH: u32 = 8;
fn collect_mutable_globals(
v: LLVMValueRef,
mutable_local: &HashMap<LLVMValueRef, usize>,
out: &mut Vec<usize>,
depth: u32,
) {
if let Some(&gi) = mutable_local.get(&v) {
out.push(gi);
return;
}
unsafe {
if depth >= CONST_WALK_DEPTH
|| LLVMIsAConstant(v).is_null()
|| !LLVMIsAGlobalValue(v).is_null()
{
return;
}
for k in 0..LLVMGetNumOperands(v).max(0) as u32 {
collect_mutable_globals(LLVMGetOperand(v, k), mutable_local, out, depth + 1);
}
}
}
fn constant_names_global(
v: LLVMValueRef,
mutable_local: &HashMap<LLVMValueRef, usize>,
depth: u32,
) -> bool {
let mut hits = Vec::new();
collect_mutable_globals(v, mutable_local, &mut hits, depth);
!hits.is_empty()
}
fn decline(reason: &str) -> Option<SplitPlan> {
if crate::timing::enabled() {
eprintln!("[timing] jit split declined: {reason}");
}
None
}
pub fn plan(module: &Module, nbins: usize) -> Option<SplitPlan> {
let m = module.as_mut_ptr();
if unsafe { !LLVMGetFirstGlobalAlias(m).is_null() || !LLVMGetFirstGlobalIFunc(m).is_null() } {
return decline("alias or ifunc present");
}
let mut g = unsafe { LLVMGetFirstGlobal(m) };
let mut mutable_local: HashMap<LLVMValueRef, usize> = HashMap::new();
while !g.is_null() {
let (link, tls, constant) = unsafe {
(
LLVMGetLinkage(g),
LLVMIsThreadLocal(g) != 0,
LLVMIsGlobalConstant(g) != 0,
)
};
let ok = match link {
LLVMLinkage::LLVMExternalLinkage => true,
LLVMLinkage::LLVMInternalLinkage | LLVMLinkage::LLVMPrivateLinkage => {
if !constant && !tls {
let n = mutable_local.len();
mutable_local.insert(g, n);
}
true
}
_ => false,
};
if tls || !ok {
return decline("a global is thread-local or has unsupported linkage/mutability");
}
g = unsafe { LLVMGetNextGlobal(g) };
}
let mut defined: Vec<LLVMValueRef> = Vec::new();
for f in functions(module) {
if unsafe { LLVMIsDeclaration(f) } != 0 {
continue;
}
if unsafe { LLVMGetLinkage(f) } != LLVMLinkage::LLVMExternalLinkage {
return decline("a defined function has non-external linkage");
}
defined.push(f);
}
if defined.len() < 2 {
return decline("fewer than 2 defined functions");
}
let index: HashMap<LLVMValueRef, usize> =
defined.iter().enumerate().map(|(i, &f)| (f, i)).collect();
let nf = defined.len();
if !mutable_local.is_empty() {
let mut g = unsafe { LLVMGetFirstGlobal(m) };
while !g.is_null() {
let init = unsafe { LLVMGetInitializer(g) };
if !init.is_null() && constant_names_global(init, &mutable_local, 0) {
return decline("a global initializer references a mutable local global");
}
g = unsafe { LLVMGetNextGlobal(g) };
}
}
let mut parent: Vec<usize> = (0..nf + mutable_local.len()).collect();
let mut atom: Vec<usize> = (0..nf + mutable_local.len()).collect();
let mut size = vec![0usize; nf];
let mut fn_globals: Vec<Vec<usize>> = vec![Vec::new(); nf];
for (i, &f) in defined.iter().enumerate() {
let insts = instructions(f);
size[i] = insts.len();
for &inst in &insts {
size[i] += wide_op_cost(inst);
}
for inst in insts {
let n = unsafe { LLVMGetNumOperands(inst) };
for k in 0..n.max(0) as u32 {
let op = unsafe { LLVMGetOperand(inst, k) };
if !mutable_local.is_empty() {
collect_mutable_globals(op, &mutable_local, &mut fn_globals[i], 0);
}
if let Some(&j) = index.get(&op) {
let (a, b) = (find(&mut parent, i), find(&mut parent, j));
if a != b {
parent[b] = a;
}
}
}
}
}
let mut users = vec![0usize; mutable_local.len()];
for gs in &mut fn_globals {
gs.sort_unstable();
gs.dedup();
for &gi in gs.iter() {
users[gi] += 1;
}
}
for (i, gs) in fn_globals.iter().enumerate() {
for &gi in gs {
if users[gi] > SHARED_GLOBAL_USERS {
continue;
}
let (a, b) = (find(&mut parent, i), find(&mut parent, nf + gi));
if a != b {
parent[b] = a;
}
let (a, b) = (find(&mut atom, i), find(&mut atom, nf + gi));
if a != b {
atom[b] = a;
}
}
}
let mut shared_globals: HashSet<String> = HashSet::new();
for (&g, &gi) in &mutable_local {
if users[gi] > SHARED_GLOBAL_USERS {
shared_globals.insert(value_name(g));
}
}
let mut comp_of_root: HashMap<usize, usize> = HashMap::new();
let mut comps: Vec<(usize, Vec<usize>)> = Vec::new();
for (i, &sz) in size.iter().enumerate() {
let r = find(&mut parent, i);
let c = *comp_of_root.entry(r).or_insert_with(|| {
comps.push((0, Vec::new()));
comps.len() - 1
});
comps[c].0 += sz;
comps[c].1.push(i);
}
let total: usize = size.iter().sum();
if total >= GIANT_SPLIT_MIN_INSTRS {
let target = total.div_ceil(nbins.max(1) * PIECES_PER_BIN);
let mut c = 0;
while c < comps.len() {
if comps[c].0 <= target * 3 / 2 {
c += 1;
continue;
}
let (_, fns) = std::mem::take(&mut comps[c]);
let mut atoms: Vec<(usize, Vec<usize>)> = Vec::new();
let mut atom_of_root: HashMap<usize, usize> = HashMap::new();
for &i in &fns {
let r = find(&mut atom, i);
let a = *atom_of_root.entry(r).or_insert_with(|| {
atoms.push((0, Vec::new()));
atoms.len() - 1
});
atoms[a].0 += size[i];
atoms[a].1.push(i);
}
if crate::timing::enabled() {
let mut a: Vec<(usize, usize)> = atoms.iter().map(|x| (x.0, x.1.len())).collect();
a.sort_unstable_by_key(|x| std::cmp::Reverse(x.0));
a.truncate(5);
eprintln!(
"[timing] jit split: cut component of {} atoms, target {target}, largest atoms {a:?}",
atoms.len()
);
}
let mut pieces: Vec<(usize, Vec<usize>)> = vec![(0, Vec::new())];
for (sz, members) in atoms {
let heavy = sz >= target / 2;
if pieces
.last()
.is_some_and(|p| p.0 >= target || (heavy && p.0 > 0))
{
pieces.push((0, Vec::new()));
}
if let Some(p) = pieces.last_mut() {
p.0 += sz;
p.1.extend(members);
}
if heavy {
pieces.push((0, Vec::new()));
}
}
let mut it = pieces.into_iter();
comps[c] = it.next().unwrap_or_default();
comps.extend(it);
c += 1;
}
comps.retain(|c| !c.1.is_empty());
}
let mut order: Vec<usize> = (0..comps.len()).collect();
order.sort_by_key(|&c| std::cmp::Reverse(comps[c].0));
if crate::timing::enabled() {
let top: Vec<(usize, usize)> = order
.iter()
.take(5)
.map(|&c| (comps[c].0, comps[c].1.len()))
.collect();
eprintln!(
"[timing] jit split: {} components of {} functions; largest 5 (instrs, functions): {top:?}",
comps.len(),
defined.len()
);
}
let nbins = nbins.min(comps.len());
if nbins < 2 {
return decline("fewer than 2 call-graph components");
}
let mut load = vec![0usize; nbins];
let mut bins: Vec<Vec<usize>> = vec![Vec::new(); nbins];
for c in order {
let b = (0..nbins).min_by_key(|&b| (load[b], b)).unwrap_or(0);
load[b] += comps[c].0;
bins[b].extend(&comps[c].1);
}
if crate::timing::enabled() {
eprintln!("[timing] jit split: bin loads (weighted instrs) {load:?}");
}
let bins = bins
.into_iter()
.map(|mut ids| {
ids.sort_unstable();
ids.into_iter().map(|i| value_name(defined[i])).collect()
})
.collect();
Some(SplitPlan {
bins,
shared_globals,
})
}
pub fn strip_to_bin(
module: &Module,
bin: usize,
owned: &HashSet<String>,
shared_globals: &HashSet<String>,
) {
let m = module.as_mut_ptr();
if !shared_globals.is_empty() {
let mut g = unsafe { LLVMGetFirstGlobal(m) };
while !g.is_null() {
if shared_globals.contains(&value_name(g)) {
unsafe { LLVMSetLinkage(g, LLVMLinkage::LLVMExternalLinkage) };
}
g = unsafe { LLVMGetNextGlobal(g) };
}
}
for f in functions(module) {
if unsafe { LLVMIsDeclaration(f) } != 0 || owned.contains(&value_name(f)) {
continue;
}
let insts = instructions(f);
for &i in &insts {
unsafe {
let ty = LLVMTypeOf(i);
if LLVMGetTypeKind(ty) != LLVMTypeKind::LLVMVoidTypeKind {
LLVMReplaceAllUsesWith(i, LLVMGetUndef(ty));
}
}
}
for i in insts {
unsafe { LLVMInstructionEraseFromParent(i) };
}
unsafe {
while LLVMCountBasicBlocks(f) > 0 {
LLVMDeleteBasicBlock(LLVMGetFirstBasicBlock(f));
}
}
}
if bin == 0 {
return;
}
let mut g = unsafe { LLVMGetFirstGlobal(m) };
while !g.is_null() {
unsafe {
if LLVMIsDeclaration(g) == 0 && LLVMGetLinkage(g) == LLVMLinkage::LLVMExternalLinkage {
LLVMSetInitializer(g, std::ptr::null_mut());
}
g = LLVMGetNextGlobal(g);
}
}
}