use crate::bolt::bolt_profile::BoltProfile;
use std::cmp::Reverse;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayoutAlgorithm {
HFSort,
HFSortPlus,
CacheSort,
PettisHansen,
CallChainClustering,
}
#[derive(Debug, Clone)]
pub struct BoltBlock {
pub address: u64,
pub offset: usize,
pub size: usize,
pub successors: Vec<usize>,
pub execution_count: u64,
pub is_entry: bool,
pub is_exit: bool,
}
impl BoltBlock {
pub fn new(offset: usize, size: usize) -> Self {
Self {
address: 0,
offset,
size,
successors: Vec::new(),
execution_count: 0,
is_entry: false,
is_exit: false,
}
}
}
#[derive(Debug, Clone)]
pub struct BoltFunction {
pub name: String,
pub address: u64,
pub size: u64,
pub blocks: Vec<BoltBlock>,
pub execution_count: u64,
pub is_hot: bool,
}
impl BoltFunction {
pub fn new(name: String, address: u64, size: u64) -> Self {
Self {
name,
address,
size,
blocks: Vec::new(),
execution_count: 0,
is_hot: false,
}
}
pub fn block_count(&self) -> usize {
self.blocks.len()
}
pub fn get_block(&self, index: usize) -> Option<&BoltBlock> {
self.blocks.get(index)
}
pub fn get_block_mut(&mut self, index: usize) -> Option<&mut BoltBlock> {
self.blocks.get_mut(index)
}
pub fn edge_weight_to(&self, callee_name: &str) -> u64 {
if self.is_hot {
self.execution_count / 10
} else {
0
}
}
}
pub struct BOLTBinaryRewriter {
pub input: Vec<u8>,
pub output: Vec<u8>,
pub functions: Vec<BoltFunction>,
pub reordered_functions: Vec<String>,
pub layout_algorithm: LayoutAlgorithm,
profile: Option<BoltProfile>,
}
impl BOLTBinaryRewriter {
pub fn new(data: Vec<u8>) -> Self {
Self {
input: data,
output: Vec::new(),
functions: Vec::new(),
reordered_functions: Vec::new(),
layout_algorithm: LayoutAlgorithm::HFSort,
profile: None,
}
}
pub fn with_layout(mut self, algo: LayoutAlgorithm) -> Self {
self.layout_algorithm = algo;
self
}
pub fn with_profile(mut self, profile: BoltProfile) -> Self {
self.profile = Some(profile);
self
}
pub fn disassemble(&mut self) -> Result<(), String> {
self.functions.clear();
let mut offset = 0usize;
let data = &self.input;
while offset < data.len() {
if self.is_function_start(&data[offset..]) {
if let Some(func) = self.disassemble_function_from(offset) {
offset = (func.address as usize + func.size as usize).min(data.len());
self.functions.push(func);
continue;
}
}
offset += 1;
}
Ok(())
}
fn is_function_start(&self, bytes: &[u8]) -> bool {
if bytes.len() < 4 {
return false;
}
bytes[0] == 0x55
&& ((bytes.len() >= 4 && bytes[1] == 0x48 && bytes[2] == 0x89 && bytes[3] == 0xE5)
|| (bytes.len() >= 2 && bytes[1] == 0x48 && bytes[2] == 0x8B && bytes[3] == 0xEC))
}
fn disassemble_function_from(&self, start: usize) -> Option<BoltFunction> {
let data = &self.input;
if start >= data.len() {
return None;
}
let address = start as u64;
let mut end = start;
let max_scan = (data.len() - start).min(0x100000);
for i in start..data.len().min(start + max_scan) {
if data[i] == 0xC3 || data[i] == 0xCB {
end = i + 1;
while end < data.len()
&& (data[end] == 0x90 || data[end] == 0xCC || data[end] == 0x00)
{
end += 1;
}
break;
}
}
if end == start {
end = (start + 64).min(data.len()); }
let size = (end - start) as u64;
let name = format!("func_{:016X}", address);
let mut func = BoltFunction::new(name, address, size);
self.identify_basic_blocks(&mut func);
Some(func)
}
fn identify_basic_blocks(&self, func: &mut BoltFunction) {
let data = &self.input;
let start = func.address as usize;
let end = (func.address + func.size) as usize;
if start >= data.len() || end > data.len() || end <= start {
return;
}
let mut targets: HashSet<usize> = HashSet::new();
targets.insert(0);
let mut pos = start;
while pos < end {
let offset = pos - start;
let byte = data[pos];
match byte {
0x70..=0x7F => {
if pos + 1 < end {
let rel = data[pos + 1] as i8;
let target = (pos as i64 + 2 + rel as i64) as usize;
if pos + 2 <= end {
targets.insert(offset + 2);
}
if target >= start && target < end {
targets.insert(target - start);
}
pos += 2;
} else {
pos += 1;
}
}
0x0F => {
if pos + 1 < end {
let next = data[pos + 1];
if (0x80..=0x8F).contains(&next) {
if pos + 5 < end {
let rel = i32::from_le_bytes([
data[pos + 2],
data[pos + 3],
data[pos + 4],
data[pos + 5],
]);
let target = (pos as i64 + 6 + rel as i64) as usize;
targets.insert(offset + 6); if target >= start && target < end {
targets.insert(target - start);
}
pos += 6;
continue;
}
}
}
pos += 2;
}
0xE9 => {
if pos + 4 < end {
let rel = i32::from_le_bytes([
data[pos + 1],
data[pos + 2],
data[pos + 3],
data[pos + 4],
]);
let target = (pos as i64 + 5 + rel as i64) as usize;
if target >= start && target < end {
targets.insert(target - start);
}
pos += 5;
} else {
pos += 1;
}
}
0xEB => {
if pos + 1 < end {
let rel = data[pos + 1] as i8;
let target = (pos as i64 + 2 + rel as i64) as usize;
if target >= start && target < end {
targets.insert(target - start);
}
pos += 2;
} else {
pos += 1;
}
}
0xC3 => {
if pos + 1 <= end {
targets.insert(offset + 1); }
pos += 1;
}
0xE8 => {
pos += if pos + 4 < end { 5 } else { 1 };
if pos <= end {
targets.insert(pos - start);
}
}
0x40..=0x4F => {
pos += 1;
}
_ => {
pos += 1; }
}
}
let mut sorted_targets: Vec<usize> = targets.into_iter().collect();
sorted_targets.sort();
for i in 0..sorted_targets.len() {
let block_start = sorted_targets[i];
let block_end = if i + 1 < sorted_targets.len() {
sorted_targets[i + 1]
} else {
func.size as usize
};
let size = block_end.saturating_sub(block_start);
let mut block = BoltBlock::new(block_start, size);
block.address = func.address + block_start as u64;
block.is_entry = block_start == 0;
func.blocks.push(block);
}
self.connect_block_successors(func);
}
fn connect_block_successors(&self, func: &mut BoltFunction) {
let data = &self.input;
let block_count = func.blocks.len();
let offset_to_idx: HashMap<usize, usize> = func
.blocks
.iter()
.enumerate()
.map(|(i, b)| (b.offset, i))
.collect();
for i in 0..block_count {
let block = &func.blocks[i];
let block_end = block.offset + block.size;
if block_end < 2 || block_end > data.len() {
continue;
}
let last_byte_idx = block_end.saturating_sub(1);
if block.size >= 5 {
let jmp_pos = block_end - 5;
if data[jmp_pos] == 0xE9 {
let rel = i32::from_le_bytes([
data[jmp_pos + 1],
data[jmp_pos + 2],
data[jmp_pos + 3],
data[jmp_pos + 4],
]);
let target_abs = jmp_pos as i64 + 5 + rel as i64;
let target_off = target_abs as usize - func.address as usize;
if let Some(&idx) = offset_to_idx.get(&target_off) {
func.blocks[i].successors.push(idx);
}
continue;
}
}
if block.size >= 2 {
let jmp_pos = block_end - 2;
if data[jmp_pos] == 0xEB {
let rel = data[jmp_pos + 1] as i8;
let target_abs = jmp_pos as i64 + 2 + rel as i64;
let target_off = target_abs as usize - func.address as usize;
if let Some(&idx) = offset_to_idx.get(&target_off) {
func.blocks[i].successors.push(idx);
}
continue;
}
}
if block.size >= 6 {
let jmp_pos = block_end - 6;
if data[jmp_pos] == 0x0F && (0x80..=0x8F).contains(&data[jmp_pos + 1]) {
let rel = i32::from_le_bytes([
data[jmp_pos + 2],
data[jmp_pos + 3],
data[jmp_pos + 4],
data[jmp_pos + 5],
]);
let target_abs = jmp_pos as i64 + 6 + rel as i64;
let target_off = target_abs as usize - func.address as usize;
if let Some(&idx) = offset_to_idx.get(&target_off) {
func.blocks[i].successors.push(idx);
}
if i + 1 < block_count {
func.blocks[i].successors.push(i + 1);
}
continue;
}
}
if block.size >= 2 {
let jmp_pos = block_end - 2;
if (0x70..=0x7F).contains(&data[jmp_pos]) {
let rel = data[jmp_pos + 1] as i8;
let target_abs = jmp_pos as i64 + 2 + rel as i64;
let target_off = target_abs as usize - func.address as usize;
if let Some(&idx) = offset_to_idx.get(&target_off) {
func.blocks[i].successors.push(idx);
}
if i + 1 < block_count {
func.blocks[i].successors.push(i + 1);
}
continue;
}
}
if data[last_byte_idx] == 0xC3 {
func.blocks[i].is_exit = true;
} else if i + 1 < block_count {
func.blocks[i].successors.push(i + 1);
}
}
}
pub fn build_cfg(&mut self) {
for func in &mut self.functions {
build_function_cfg_inner(func);
}
}
pub fn run_optimizations(&mut self) {
{
let profile_clone = self.profile.clone();
if let Some(ref profile) = profile_clone {
annotate_from_profile_static(profile, &mut self.functions);
}
}
self.optimize_function_layout();
let mut block_orders: Vec<(usize, Vec<usize>)> = Vec::new();
for (i, func) in self.functions.iter().enumerate() {
if func.is_hot {
let new_order = self.optimize_basic_block_layout(func);
block_orders.push((i, new_order));
}
}
for (i, order) in block_orders {
reorder_blocks_static(&mut self.functions[i], &order);
}
self.optimize_branches();
self.optimize_nops();
self.optimize_indirect_branches();
self.stitch_cold_code();
}
fn annotate_from_profile(&mut self, profile: &BoltProfile) {
for func in &mut self.functions {
if let Some(fp) = profile.functions.get(&func.name) {
func.execution_count = fp.entry_count;
func.is_hot = fp.is_hot;
for block in &mut func.blocks {
if let Some(&count) = fp.block_counts.get(&block.address) {
block.execution_count = count;
}
}
}
}
}
fn optimize_function_layout(&mut self) {
let order: Vec<String> = match self.layout_algorithm {
LayoutAlgorithm::HFSort => self.compute_hfsort_clusters(),
LayoutAlgorithm::HFSortPlus => self.compute_hfsort_plus_clusters(),
LayoutAlgorithm::CacheSort => self.compute_cache_sort(),
LayoutAlgorithm::PettisHansen => self.compute_pettis_hansen(),
LayoutAlgorithm::CallChainClustering => self.compute_call_chain_clusters(),
};
self.reordered_functions = order;
}
fn compute_hfsort_clusters(&self) -> Vec<String> {
let func_count = self.functions.len();
if func_count == 0 {
return Vec::new();
}
let mut edges: Vec<(usize, usize, u64)> = Vec::new();
for i in 0..func_count {
for j in 0..func_count {
if i == j {
continue;
}
let w = self.functions[i].execution_count + self.functions[j].execution_count;
if w > 0 {
edges.push((i, j, w));
}
}
}
edges.sort_by_key(|(_, _, w)| Reverse(*w));
let mut parent: Vec<usize> = (0..func_count).collect();
let mut rank: Vec<usize> = vec![0; func_count];
fn find(parent: &mut [usize], x: usize) -> usize {
if parent[x] != x {
parent[x] = find(parent, parent[x]);
}
parent[x]
}
fn union(parent: &mut [usize], rank: &mut [usize], x: usize, y: usize) {
let rx = find(parent, x);
let ry = find(parent, y);
if rx == ry {
return;
}
if rank[rx] < rank[ry] {
parent[rx] = ry;
} else if rank[rx] > rank[ry] {
parent[ry] = rx;
} else {
parent[ry] = rx;
rank[rx] += 1;
}
}
let cluster_limit = func_count.min(100);
let mut clusters_found = func_count;
for (i, j, _w) in &edges {
if clusters_found <= cluster_limit {
break;
}
if find(&mut parent, *i) != find(&mut parent, *j) {
union(&mut parent, &mut rank, *i, *j);
clusters_found -= 1;
}
}
let mut cluster_map: HashMap<usize, Vec<usize>> = HashMap::new();
for i in 0..func_count {
let root = find(&mut parent, i);
cluster_map.entry(root).or_default().push(i);
}
let mut result: Vec<String> = Vec::new();
for indices in cluster_map.values_mut() {
indices.sort_by_key(|&i| {
let func = &self.functions[i];
(!func.is_hot, Reverse(func.size))
});
for &i in indices.iter() {
result.push(self.functions[i].name.clone());
}
}
result
}
fn compute_hfsort_plus_clusters(&self) -> Vec<String> {
let func_count = self.functions.len();
if func_count == 0 {
return Vec::new();
}
let mut edges: Vec<(usize, usize, f64)> = Vec::new();
for i in 0..func_count {
for j in 0..func_count {
if i == j {
continue;
}
let fi = &self.functions[i];
let fj = &self.functions[j];
let base = fi.execution_count + fj.execution_count;
let mut weight = base as f64;
if fi.is_hot && fj.is_hot {
weight *= 2.0;
}
if weight > 0.0 {
edges.push((i, j, weight));
}
}
}
edges.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
let mut parent: Vec<usize> = (0..func_count).collect();
let mut rank: Vec<usize> = vec![0; func_count];
let cluster_limit = func_count.min(80);
let mut merged = func_count;
for (i, j, _) in &edges {
if merged <= cluster_limit {
break;
}
let ri = find(&mut parent, *i);
let rj = find(&mut parent, *j);
if ri != rj {
union(&mut parent, &mut rank, *i, *j);
merged -= 1;
}
}
let mut cluster_map: HashMap<usize, Vec<usize>> = HashMap::new();
for i in 0..func_count {
let root = find(&mut parent, i);
cluster_map.entry(root).or_default().push(i);
}
let mut result: Vec<String> = Vec::new();
for indices in cluster_map.values_mut() {
indices.sort_by_key(|&i| {
let f = &self.functions[i];
(!f.is_hot, Reverse(f.execution_count))
});
for &i in indices.iter() {
result.push(self.functions[i].name.clone());
}
}
result
}
fn compute_cache_sort(&self) -> Vec<String> {
let mut indices: Vec<usize> = (0..self.functions.len()).collect();
indices.sort_by_key(|&i| {
let f = &self.functions[i];
(!f.is_hot, Reverse(f.execution_count))
});
indices
.iter()
.map(|&i| self.functions[i].name.clone())
.collect()
}
fn compute_pettis_hansen(&self) -> Vec<String> {
let n = self.functions.len();
if n == 0 {
return Vec::new();
}
let mut graph: Vec<Vec<u64>> = vec![vec![0; n]; n];
for i in 0..n {
for j in (i + 1)..n {
let w = self.functions[i].execution_count + self.functions[j].execution_count;
graph[i][j] = w;
graph[j][i] = w;
}
}
let mut chains: Vec<Vec<usize>> = (0..n).map(|i| vec![i]).collect();
let mut active: HashSet<usize> = (0..n).collect();
while active.len() > 1 {
let mut best: Option<(usize, usize, u64)> = None;
let act: Vec<usize> = active.iter().copied().collect();
for a_idx in 0..act.len() {
for b_idx in (a_idx + 1)..act.len() {
let ci = act[a_idx];
let cj = act[b_idx];
let mut total = 0u64;
for &ni in &chains[ci] {
for &nj in &chains[cj] {
total += graph[ni][nj];
}
}
if best.is_none() || total > best.unwrap().2 {
best = Some((ci, cj, total));
}
}
}
if let Some((ci, cj, _)) = best {
let cj_nodes = chains[cj].clone();
chains[ci].extend(cj_nodes);
chains[cj].clear();
active.remove(&cj);
} else {
break;
}
}
let mut result: Vec<String> = Vec::new();
for &idx in &active {
for &fi in &chains[idx] {
result.push(self.functions[fi].name.clone());
}
}
result
}
fn compute_call_chain_clusters(&self) -> Vec<String> {
let n = self.functions.len();
if n == 0 {
return Vec::new();
}
let mut roots: Vec<usize> = (0..n).filter(|&i| self.functions[i].is_hot).collect();
roots.sort_by_key(|&i| Reverse(self.functions[i].execution_count));
let mut visited: HashSet<usize> = HashSet::new();
let mut result: Vec<String> = Vec::new();
for root in roots {
if visited.contains(&root) {
continue;
}
let mut queue: VecDeque<usize> = VecDeque::new();
queue.push_back(root);
while let Some(cur) = queue.pop_front() {
if visited.contains(&cur) {
continue;
}
visited.insert(cur);
result.push(self.functions[cur].name.clone());
for j in 0..n {
if !visited.contains(&j)
&& self.functions[j].is_hot
&& self.functions[cur].execution_count > 0
{
queue.push_back(j);
}
}
}
}
for i in 0..n {
if !visited.contains(&i) {
result.push(self.functions[i].name.clone());
}
}
result
}
pub fn apply_code_reordering(&mut self, order: &[String]) {
self.reordered_functions = order.to_vec();
let mut output = Vec::new();
let name_to_func: HashMap<&str, &BoltFunction> = self
.functions
.iter()
.map(|f| (f.name.as_str(), f))
.collect();
for name in order {
if let Some(func) = name_to_func.get(name.as_str()) {
let start = func.address as usize;
let end = (func.address + func.size) as usize;
if start < self.input.len() && end <= self.input.len() {
output.extend_from_slice(&self.input[start..end]);
}
}
}
self.output = output;
}
fn optimize_basic_block_layout(&self, func: &BoltFunction) -> Vec<usize> {
let n = func.blocks.len();
if n <= 1 {
return (0..n).collect();
}
let entry = func.blocks.iter().position(|b| b.is_entry).unwrap_or(0);
let mut order: Vec<usize> = Vec::with_capacity(n);
let mut visited: HashSet<usize> = HashSet::new();
self.trace_hot_path(func, entry, &mut order, &mut visited);
for i in 0..n {
if !visited.contains(&i) {
order.push(i);
visited.insert(i);
}
}
order
}
fn trace_hot_path(
&self,
func: &BoltFunction,
current: usize,
order: &mut Vec<usize>,
visited: &mut HashSet<usize>,
) {
if visited.contains(¤t) {
return;
}
visited.insert(current);
order.push(current);
let block = &func.blocks[current];
let mut best: Option<(usize, u64)> = None;
for &succ in &block.successors {
if succ < func.blocks.len() && !visited.contains(&succ) {
let count = func.blocks[succ].execution_count;
if best.is_none() || count > best.unwrap().1 {
best = Some((succ, count));
}
}
}
if let Some((next, _)) = best {
self.trace_hot_path(func, next, order, visited);
}
for &succ in &block.successors {
if succ < func.blocks.len() && !visited.contains(&succ) {
self.trace_hot_path(func, succ, order, visited);
}
}
}
fn reorder_blocks(&mut self, _func: &mut BoltFunction, _new_order: &[usize]) {
}
fn optimize_branches(&mut self) {
for func in &mut self.functions {
let n = func.blocks.len();
for i in 0..n {
let block = &func.blocks[i];
if block.successors.len() == 1 {
let target = block.successors[0];
if target == i + 1 && i + 1 < n {
}
}
if block.successors.len() == 1 && block.successors[0] == i + 1 {
}
if block.successors.len() == 2 {
let s0 = block.successors[0];
let s1 = block.successors[1];
if s0 < n && s1 < n {
let c0 = func.blocks[s0].execution_count;
let c1 = func.blocks[s1].execution_count;
if c0 == 0 && c1 > 0 {
} else if c0 > 0 && c1 == 0 {
}
}
}
}
}
}
fn optimize_nops(&mut self) {
let _data = &mut self.output;
}
fn optimize_indirect_branches(&mut self) {
for func in &self.functions {
for block in &func.blocks {
let _ = block;
let _ = func;
}
}
}
fn stitch_cold_code(&mut self) {
for func in &self.functions {
if !func.is_hot {
continue;
}
let max_count = func
.blocks
.iter()
.map(|b| b.execution_count)
.max()
.unwrap_or(1);
let threshold = (max_count as f64 * 0.10) as u64;
let mut cold_indices: Vec<usize> = func
.blocks
.iter()
.enumerate()
.filter(|(_, b)| b.execution_count < threshold && !b.is_entry)
.map(|(i, _)| i)
.collect();
cold_indices.sort();
let _ = cold_indices;
}
}
fn update_relocations(&mut self) {
}
fn update_symbols(&mut self) {
for func in &self.functions {
let _ = func;
}
}
fn update_debug_info(&mut self) {
}
pub fn emit(&mut self) -> Vec<u8> {
if !self.reordered_functions.is_empty() {
let order = self.reordered_functions.clone();
self.apply_code_reordering(&order);
}
self.update_relocations();
self.update_symbols();
self.update_debug_info();
if self.output.is_empty() {
self.output = self.input.clone();
}
std::mem::take(&mut self.output)
}
}
fn build_function_cfg_inner(func: &mut BoltFunction) {
let max_count = func
.blocks
.iter()
.map(|b| b.execution_count)
.max()
.unwrap_or(0);
for block in &mut func.blocks {
block.is_entry = block.offset == 0;
if max_count > 0 {
block.execution_count = block.execution_count.max(1);
}
}
let total: u64 = func.blocks.iter().map(|b| b.execution_count).sum();
func.execution_count = total;
func.is_hot = total > 100;
}
fn annotate_from_profile_static(profile: &BoltProfile, functions: &mut [BoltFunction]) {
for func in functions.iter_mut() {
if let Some(fp) = profile.functions.get(&func.name) {
func.execution_count = fp.entry_count;
func.is_hot = fp.is_hot;
for block in &mut func.blocks {
if let Some(&count) = fp.block_counts.get(&block.address) {
block.execution_count = count;
}
}
}
}
}
fn reorder_blocks_static(_func: &mut BoltFunction, _new_order: &[usize]) {
}
fn find(parent: &mut [usize], x: usize) -> usize {
if parent[x] != x {
parent[x] = find(parent, parent[x]);
}
parent[x]
}
fn union(parent: &mut [usize], rank: &mut [usize], x: usize, y: usize) {
let rx = find(parent, x);
let ry = find(parent, y);
if rx == ry {
return;
}
if rank[rx] < rank[ry] {
parent[rx] = ry;
} else if rank[rx] > rank[ry] {
parent[ry] = rx;
} else {
parent[ry] = rx;
rank[rx] += 1;
}
}
impl BOLTBinaryRewriter {
pub fn reorder_functions(&mut self) {
self.functions
.sort_by(|a, b| b.execution_count.cmp(&a.execution_count));
let mut hot_funcs: Vec<String> = Vec::new();
let mut cold_funcs: Vec<String> = Vec::new();
for func in self.functions.iter() {
if func.is_hot {
hot_funcs.push(func.name.clone());
} else {
cold_funcs.push(func.name.clone());
}
}
self.reordered_functions = hot_funcs;
self.reordered_functions.append(&mut cold_funcs);
}
pub fn optimize_basic_block_layout_extended(&self, func: &mut BoltFunction) {
let total_count: u64 = func.blocks.iter().map(|b| b.execution_count).sum();
if total_count == 0 {
return;
}
let threshold = total_count / 10;
let mut hot_blocks = Vec::new();
let mut cold_blocks = Vec::new();
for block in &func.blocks {
if block.execution_count >= threshold {
hot_blocks.push(block.clone());
} else {
cold_blocks.push(block.clone());
}
}
let mut layout = hot_blocks;
layout.append(&mut cold_blocks);
func.blocks = layout;
}
pub fn nop_pad_for_alignment(&self, func: &mut BoltFunction, alignment: u64) -> Vec<u8> {
let mut padding = Vec::new();
let current_size: u64 = func.blocks.iter().map(|b| b.size as u64).sum();
let remainder = current_size % alignment;
if remainder != 0 {
let pad_size = alignment - remainder;
padding.resize(pad_size as usize, 0x90u8);
}
padding
}
pub fn hot_cold_split(
&self,
func: &BoltFunction,
threshold: u64,
) -> (Vec<BoltBlock>, Vec<BoltBlock>) {
let mut hot = Vec::new();
let mut cold = Vec::new();
for block in &func.blocks {
if block.execution_count >= threshold {
hot.push(block.clone());
} else {
cold.push(block.clone());
}
}
(hot, cold)
}
pub fn optimize_jump_tables(&self, func: &mut BoltFunction) {
let exec_counts: Vec<u64> = func.blocks.iter().map(|b| b.execution_count).collect();
for block in &mut func.blocks {
if block.successors.len() > 2 {
let mut hot_successor = None;
let max_count = block
.successors
.iter()
.enumerate()
.max_by_key(|&(_, &idx)| exec_counts.get(idx).copied().unwrap_or(0));
if let Some((hot_idx, _)) = max_count {
hot_successor = Some(block.successors[hot_idx]);
}
if let Some(hot) = hot_successor {
block.successors = vec![hot];
}
}
}
}
pub fn compute_function_size(&self, func: &BoltFunction) -> u64 {
func.blocks.iter().map(|b| b.size as u64).sum()
}
pub fn estimate_icache_improvement(&self) -> f64 {
let original_layout: u64 = self
.functions
.iter()
.map(|f| self.compute_function_size(f))
.sum();
let reordered_layout: u64 = original_layout;
let _ = &self.reordered_functions;
if original_layout == 0 {
return 0.0;
}
let reduction = 1.0 - (reordered_layout as f64 / original_layout as f64);
(reduction * 10.0).max(0.0).min(30.0)
}
pub fn merge_adjacent_blocks(&self, func: &mut BoltFunction) -> usize {
let mut merged = 0;
let mut i = 0;
while i + 1 < func.blocks.len() {
let current = &func.blocks[i];
let next = &func.blocks[i + 1];
if current.successors.len() == 1 && current.successors[0] == i + 1 && !next.is_entry {
let next_block = func.blocks.remove(i + 1);
func.blocks[i].size += next_block.size;
func.blocks[i].successors = next_block.successors.clone();
merged += 1;
} else {
i += 1;
}
}
merged
}
pub fn align_hot_blocks(
&self,
func: &mut BoltFunction,
alignment: u64,
threshold: u64,
) -> Vec<u64> {
let mut padding_sizes = Vec::new();
for block in &func.blocks {
if block.execution_count >= threshold {
let remainder = (block.offset as u64) % alignment;
if remainder != 0 {
padding_sizes.push(alignment - remainder);
} else {
padding_sizes.push(0);
}
} else {
padding_sizes.push(0);
}
}
padding_sizes
}
pub fn prune_cold_blocks(&self, func: &mut BoltFunction) -> usize {
let before = func.blocks.len();
func.blocks.retain(|b| b.execution_count > 0);
before - func.blocks.len()
}
pub fn function_execution_count(&self, func: &BoltFunction) -> u64 {
func.blocks.iter().map(|b| b.execution_count).sum()
}
pub fn summary(&self) -> RewriteSummary {
let total_functions = self.functions.len();
let total_blocks: usize = self.functions.iter().map(|f| f.blocks.len()).sum();
let hot_functions = self.functions.iter().filter(|f| f.is_hot).count();
let reordered = self.reordered_functions.len();
RewriteSummary {
total_functions,
total_blocks,
hot_functions,
reordered_functions: reordered,
}
}
}
#[derive(Debug, Clone)]
pub struct RewriteSummary {
pub total_functions: usize,
pub total_blocks: usize,
pub hot_functions: usize,
pub reordered_functions: usize,
}
impl RewriteSummary {
pub fn reorder_percentage(&self) -> f64 {
if self.total_functions == 0 {
0.0
} else {
(self.reordered_functions as f64 / self.total_functions as f64) * 100.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_simple_func(name: &str, addr: u64, size: u64) -> BoltFunction {
BoltFunction::new(name.to_string(), addr, size)
}
fn make_test_data() -> Vec<u8> {
let mut data = vec![0u8; 1024];
data[0] = 0x55; data[1] = 0x48;
data[2] = 0x89;
data[3] = 0xE5; data[4] = 0x90; data[5] = 0xC3; data[6] = 0x90;
data[7] = 0x90;
data[8] = 0x55; data[9] = 0x48;
data[10] = 0x89;
data[11] = 0xE5; data[12] = 0xC3; data
}
#[test]
fn test_new_rewriter() {
let data = vec![0xC3];
let rewriter = BOLTBinaryRewriter::new(data.clone());
assert_eq!(rewriter.input, data);
assert!(rewriter.output.is_empty());
assert!(rewriter.functions.is_empty());
}
#[test]
fn test_with_layout() {
let rewriter = BOLTBinaryRewriter::new(vec![]).with_layout(LayoutAlgorithm::CacheSort);
assert_eq!(rewriter.layout_algorithm, LayoutAlgorithm::CacheSort);
}
#[test]
fn test_disassemble_simple() {
let data = make_test_data();
let mut rewriter = BOLTBinaryRewriter::new(data);
let result = rewriter.disassemble();
assert!(result.is_ok());
assert!(rewriter.functions.len() >= 1);
}
#[test]
fn test_function_new() {
let f = BoltFunction::new("main".into(), 0x400000, 128);
assert_eq!(f.name, "main");
assert_eq!(f.address, 0x400000);
assert_eq!(f.size, 128);
assert!(!f.is_hot);
assert_eq!(f.execution_count, 0);
}
#[test]
fn test_block_new() {
let b = BoltBlock::new(0, 16);
assert_eq!(b.offset, 0);
assert_eq!(b.size, 16);
assert!(!b.is_entry);
assert!(!b.is_exit);
assert!(b.successors.is_empty());
}
#[test]
fn test_function_block_count() {
let mut f = make_simple_func("test", 0x1000, 64);
f.blocks.push(BoltBlock::new(0, 16));
f.blocks.push(BoltBlock::new(16, 16));
assert_eq!(f.block_count(), 2);
}
#[test]
fn test_build_cfg() {
let mut rewriter = BOLTBinaryRewriter::new(vec![0xC3]);
let mut f = make_simple_func("f", 0, 64);
f.blocks.push(BoltBlock::new(0, 8));
f.blocks.push(BoltBlock::new(8, 8));
rewriter.functions.push(f);
rewriter.build_cfg();
assert_eq!(rewriter.functions.len(), 1);
}
#[test]
fn test_cache_sort() {
let mut rewriter = BOLTBinaryRewriter::new(vec![]);
let mut f1 = make_simple_func("a", 0, 64);
f1.is_hot = true;
f1.execution_count = 1000;
let f2 = make_simple_func("b", 64, 32);
rewriter.functions.push(f1);
rewriter.functions.push(f2);
let order = rewriter.compute_cache_sort();
assert_eq!(order[0], "a");
}
#[test]
fn test_pettis_hansen() {
let mut rewriter = BOLTBinaryRewriter::new(vec![]);
rewriter.functions.push(make_simple_func("f1", 0, 64));
rewriter.functions.push(make_simple_func("f2", 64, 64));
rewriter.functions.push(make_simple_func("f3", 128, 64));
let order = rewriter.compute_pettis_hansen();
assert_eq!(order.len(), 3);
}
#[test]
fn test_call_chain_clusters() {
let mut rewriter = BOLTBinaryRewriter::new(vec![]);
let mut f1 = make_simple_func("hot1", 0, 64);
f1.is_hot = true;
f1.execution_count = 500;
let f2 = make_simple_func("cold1", 64, 32);
let mut f3 = make_simple_func("hot2", 96, 64);
f3.is_hot = true;
f3.execution_count = 300;
rewriter.functions.push(f1);
rewriter.functions.push(f2);
rewriter.functions.push(f3);
let order = rewriter.compute_call_chain_clusters();
assert_eq!(order.len(), 3);
}
#[test]
fn test_hfsort_clusters_empty() {
let rewriter = BOLTBinaryRewriter::new(vec![]);
let clusters = rewriter.compute_hfsort_clusters();
assert!(clusters.is_empty());
}
#[test]
fn test_emit_no_reorder() {
let data = vec![0x55, 0xC3];
let mut rewriter = BOLTBinaryRewriter::new(data.clone());
let output = rewriter.emit();
assert_eq!(output, data);
}
#[test]
fn test_apply_code_reordering() {
let data: Vec<u8> = (0..256).map(|i| i as u8).collect();
let mut rewriter = BOLTBinaryRewriter::new(data);
let f1 = BoltFunction::new("f1".into(), 10, 20);
let f2 = BoltFunction::new("f2".into(), 100, 30);
rewriter.functions.push(f1);
rewriter.functions.push(f2);
rewriter.apply_code_reordering(&["f2".to_string(), "f1".to_string()]);
assert_eq!(rewriter.output.len(), 50);
assert_eq!(rewriter.output[0], 100);
assert_eq!(rewriter.output[30], 10);
}
#[test]
fn test_optimize_function_layout_hfsort() {
let mut rewriter = BOLTBinaryRewriter::new(vec![]).with_layout(LayoutAlgorithm::HFSort);
rewriter.functions.push(make_simple_func("a", 0, 4));
rewriter.functions.push(make_simple_func("b", 4, 4));
rewriter.optimize_function_layout();
assert!(!rewriter.reordered_functions.is_empty());
}
#[test]
fn test_optimize_function_layout_cachesort() {
let mut rewriter = BOLTBinaryRewriter::new(vec![]).with_layout(LayoutAlgorithm::CacheSort);
rewriter.functions.push(make_simple_func("a", 0, 4));
rewriter.functions.push(make_simple_func("b", 4, 4));
rewriter.optimize_function_layout();
assert_eq!(rewriter.reordered_functions.len(), 2);
}
#[test]
fn test_optimize_blocks_single() {
let rewriter = BOLTBinaryRewriter::new(vec![]);
let mut func = make_simple_func("f", 0, 4);
func.blocks.push(BoltBlock::new(0, 4));
let order = rewriter.optimize_basic_block_layout(&func);
assert_eq!(order, vec![0]);
}
#[test]
fn test_optimize_blocks_two() {
let rewriter = BOLTBinaryRewriter::new(vec![]);
let mut func = make_simple_func("f", 0, 8);
let mut b1 = BoltBlock::new(0, 4);
b1.is_entry = true;
b1.successors.push(1);
let mut b2 = BoltBlock::new(4, 4);
b2.is_exit = true;
func.blocks.push(b1);
func.blocks.push(b2);
let order = rewriter.optimize_basic_block_layout(&func);
assert_eq!(order.len(), 2);
assert_eq!(order[0], 0); }
#[test]
fn test_run_optimizations_no_panic() {
let data = make_test_data();
let mut rewriter = BOLTBinaryRewriter::new(data);
let _ = rewriter.disassemble();
rewriter.build_cfg();
rewriter.run_optimizations();
}
#[test]
fn test_hfsort_plus_clusters() {
let mut rewriter = BOLTBinaryRewriter::new(vec![]);
let mut f1 = make_simple_func("hot1", 0, 4);
f1.is_hot = true;
f1.execution_count = 100;
let mut f2 = make_simple_func("hot2", 4, 4);
f2.is_hot = true;
f2.execution_count = 200;
rewriter.functions.push(f1);
rewriter.functions.push(f2);
let order = rewriter.compute_hfsort_plus_clusters();
assert_eq!(order.len(), 2);
}
}