use std::collections::{HashMap, HashSet, VecDeque};
use llvm_native_core::codegen_opt::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
pub struct MachineOutliner {
pub min_length: usize,
pub min_occurrences: usize,
pub allow_cross_block: bool,
}
#[derive(Debug, Clone)]
pub struct OutlineCandidate {
pub hash: u64,
pub length: usize,
pub savings: usize,
pub instructions: Vec<MachineInstr>,
pub occurrences: Vec<(usize, usize, usize)>,
}
impl MachineOutliner {
pub fn new() -> Self {
Self {
min_length: 3,
min_occurrences: 2,
allow_cross_block: false,
}
}
pub fn scan_function(&self, mf: &MachineFunction) -> Vec<OutlineCandidate> {
let mut candidates: Vec<OutlineCandidate> = Vec::new();
let mut sequence_hashes: HashMap<u64, Vec<(usize, usize, usize)>> = HashMap::new();
for (block_idx, block) in mf.blocks.iter().enumerate() {
let num_instrs = block.instructions.len();
if num_instrs < self.min_length {
continue;
}
for start in 0..=num_instrs - self.min_length {
let end = num_instrs.min(start + 16); for window_len in self.min_length..=(end - start) {
let window = &block.instructions[start..start + window_len];
let hash = Self::hash_sequence(window);
sequence_hashes
.entry(hash)
.or_default()
.push((mf.id, block_idx, start));
}
}
}
for (hash, occurrences) in &sequence_hashes {
if occurrences.len() >= self.min_occurrences {
let first = occurrences[0];
candidates.push(OutlineCandidate {
hash: *hash,
length: self.min_length,
savings: self.estimate_savings(self.min_length, occurrences.len()),
instructions: Vec::new(),
occurrences: occurrences.clone(),
});
let _ = first;
}
}
candidates.sort_by_key(|c| -(c.savings as i64));
candidates
}
pub fn scan_functions(&self, mfs: &[MachineFunction]) -> Vec<OutlineCandidate> {
let mut combined: Vec<OutlineCandidate> = Vec::new();
for mf in mfs {
combined.extend(self.scan_function(mf));
}
combined
}
fn hash_sequence(seq: &[MachineInstr]) -> u64 {
let mut hash: u64 = 5381;
for instr in seq {
hash = hash.wrapping_mul(33).wrapping_add(instr.opcode as u64);
for op in &instr.operands {
match op {
MachineOperand::Reg(r) => hash = hash.wrapping_mul(33).wrapping_add(r.0 as u64),
MachineOperand::Imm(i) => hash = hash.wrapping_mul(33).wrapping_add(*i as u64),
MachineOperand::FPImm(_) => hash = hash.wrapping_mul(33).wrapping_add(42),
MachineOperand::Mem { offset, .. } => {
hash = hash.wrapping_mul(33).wrapping_add(*offset as u64)
}
_ => hash = hash.wrapping_mul(33).wrapping_add(1),
}
}
}
hash
}
fn estimate_savings(&self, length: usize, occurrences: usize) -> usize {
let call_size = 5; let ret_size = 1; let instr_size = 3; let total_orig = occurrences * length * instr_size;
let total_outline = length * instr_size + call_size * occurrences + ret_size;
if total_orig > total_outline {
total_orig - total_outline
} else {
0
}
}
}
impl Default for MachineOutliner {
fn default() -> Self {
Self::new()
}
}
pub struct MachineFunctionSplitter {
pub cold_threshold: f64,
pub use_profile: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionKind {
Hot,
Cold,
Unlikely,
}
impl MachineFunctionSplitter {
pub fn new() -> Self {
Self {
cold_threshold: 0.1,
use_profile: true,
}
}
pub fn classify_blocks(&self, mf: &MachineFunction) -> HashMap<u32, SectionKind> {
let mut sections = HashMap::new();
let entry_block = mf.blocks.iter().find(|b| b.is_entry);
if let Some(entry) = entry_block {
sections.insert(entry.id, SectionKind::Hot);
}
let mut hot_queue: VecDeque<u32> = VecDeque::new();
if let Some(entry) = entry_block {
hot_queue.push_back(entry.id);
}
while let Some(block_id) = hot_queue.pop_front() {
if let Some(block) = mf.block_by_id(block_id) {
for &succ in &block.successors {
if !sections.contains_key(&succ) {
sections.insert(succ, SectionKind::Hot);
hot_queue.push_back(succ);
}
}
}
}
for block in &mf.blocks {
if !sections.contains_key(&block.id) {
sections.insert(block.id, SectionKind::Cold);
}
}
sections
}
pub fn insert_trampolines(
&self,
mf: &mut MachineFunction,
sections: &HashMap<u32, SectionKind>,
) -> usize {
let mut trampolines_added = 0;
for block in &mut mf.blocks {
let block_section = sections.get(&block.id).copied().unwrap_or(SectionKind::Cold);
let mut new_succs = Vec::new();
for &succ in &block.successors {
let succ_section = sections.get(&succ).copied().unwrap_or(SectionKind::Cold);
if block_section != succ_section {
let tramp_id = mf.next_block_id();
let mut trampoline = MachineBasicBlock::new(tramp_id, "trampoline");
trampoline.successors.push(succ);
trampoline.instructions.push(MachineInstr::new(15)); new_succs.push(tramp_id);
mf.add_block(trampoline);
trampolines_added += 1;
} else {
new_succs.push(succ);
}
}
block.successors = new_succs;
}
trampolines_added
}
pub fn run(&self, mf: &mut MachineFunction) -> usize {
let sections = self.classify_blocks(mf);
self.insert_trampolines(mf, §ions)
}
}
impl Default for MachineFunctionSplitter {
fn default() -> Self {
Self::new()
}
}
pub struct MachineSanitizerBinaryMetadata {
pub enabled: bool,
pub track_loads: bool,
pub track_stores: bool,
pub min_access_size: usize,
}
#[derive(Debug, Clone)]
pub struct SanitizerMetadataEntry {
pub instruction_offset: usize,
pub access_size: usize,
pub is_write: bool,
pub is_atomic: bool,
pub source_location: Option<String>,
}
impl MachineSanitizerBinaryMetadata {
pub fn new() -> Self {
Self {
enabled: true,
track_loads: true,
track_stores: true,
min_access_size: 1,
}
}
pub fn analyze(&self, mf: &MachineFunction) -> Vec<SanitizerMetadataEntry> {
let mut entries = Vec::new();
let mut offset = 0usize;
for block in &mf.blocks {
for instr in &block.instructions {
let instr_size = self.estimate_instr_size(instr);
let is_load = instr.flags.may_load || instr.flags.is_load;
let is_store = instr.flags.may_store || instr.flags.is_store;
if (is_load && self.track_loads) || (is_store && self.track_stores) {
let access_size = self.infer_access_size(instr);
if access_size >= self.min_access_size {
entries.push(SanitizerMetadataEntry {
instruction_offset: offset,
access_size,
is_write: is_store,
is_atomic: instr.flags.has_side_effects,
source_location: instr.debug_loc.clone(),
});
}
}
offset += instr_size;
}
}
entries
}
fn estimate_instr_size(&self, instr: &MachineInstr) -> usize {
let base = 2; let operand_bytes: usize = instr
.operands
.iter()
.map(|op| match op {
MachineOperand::Reg(_) => 1,
MachineOperand::Imm(v) if *v >= -128 && *v <= 127 => 1,
MachineOperand::Imm(_) => 4,
MachineOperand::Mem { .. } => 6,
_ => 4,
})
.sum();
base + operand_bytes
}
fn infer_access_size(&self, instr: &MachineInstr) -> usize {
for op in &instr.operands {
if let MachineOperand::Mem { size, .. } = op {
return *size as usize;
}
}
4 }
}
impl Default for MachineSanitizerBinaryMetadata {
fn default() -> Self {
Self::new()
}
}
pub struct MachineCFGPrinter {
pub show_instructions: bool,
pub show_hot_cold: bool,
}
impl MachineCFGPrinter {
pub fn new() -> Self {
Self {
show_instructions: true,
show_hot_cold: false,
}
}
pub fn print_to_dot(&self, mf: &MachineFunction) -> String {
let mut dot = String::new();
dot.push_str(&format!("digraph \"CFG for '{}'\" {{\n", mf.name));
dot.push_str(" label=\"Control Flow Graph\";\n");
dot.push_str(" node [shape=record, fontname=\"Courier\"];\n");
for block in &mf.blocks {
let label = if self.show_instructions {
let mut instrs = String::from("{\\\\<b>");
instrs.push_str(&block.name);
instrs.push_str("</b>");
for instr in &block.instructions {
instrs.push_str(&format!(
"|op:{} [{} ops]",
instr.opcode,
instr.operands.len()
));
}
instrs.push_str("}");
instrs
} else {
block.name.clone()
};
let color = if block.is_entry {
"style=filled, fillcolor=lightgreen"
} else {
""
};
dot.push_str(&format!(
" bb{} [label=\"{}\", {}];\n",
block.id, label, color
));
}
for block in &mf.blocks {
for &succ in &block.successors {
let edge_style = if block.instructions.last().map_or(false, |i| i.flags.is_branch)
{
"style=dashed"
} else {
""
};
dot.push_str(&format!(
" bb{} -> bb{} [{}];\n",
block.id, succ, edge_style
));
}
}
dot.push_str("}\n");
dot
}
}
impl Default for MachineCFGPrinter {
fn default() -> Self {
Self::new()
}
}
pub struct MachineDominatorTree {
pub idom: HashMap<u32, Option<u32>>,
pub dom_tree: HashMap<u32, Vec<u32>>,
pub dom_frontier: HashMap<u32, Vec<u32>>,
}
impl MachineDominatorTree {
pub fn new() -> Self {
Self {
idom: HashMap::new(),
dom_tree: HashMap::new(),
dom_frontier: HashMap::new(),
}
}
pub fn compute(&mut self, mf: &MachineFunction) {
let n = mf.blocks.len();
if n == 0 {
return;
}
let entry = mf.blocks.iter().find(|b| b.is_entry);
if entry.is_none() {
return;
}
let entry_id = entry.unwrap().id;
let id_to_idx: HashMap<u32, usize> = mf
.blocks
.iter()
.enumerate()
.map(|(i, b)| (b.id, i))
.collect();
let idx_to_id: Vec<u32> = mf.blocks.iter().map(|b| b.id).collect();
let mut preds: Vec<Vec<usize>> = vec![Vec::new(); n];
for (i, block) in mf.blocks.iter().enumerate() {
for &succ_id in &block.successors {
if let Some(&succ_idx) = id_to_idx.get(&succ_id) {
preds[succ_idx].push(i);
}
}
}
let entry_idx = id_to_idx[&entry_id];
let mut doms: Vec<Vec<bool>> = vec![vec![true; n]; n];
for i in 0..n {
doms[entry_idx][i] = (i == entry_idx);
}
let mut changed = true;
while changed {
changed = false;
for i in 0..n {
if i == entry_idx {
continue;
}
let mut new_dom: Vec<bool> = vec![true; n];
for &p in &preds[i] {
for j in 0..n {
new_dom[j] = new_dom[j] && doms[p][j];
}
}
new_dom[i] = true;
if new_dom != doms[i] {
doms[i] = new_dom;
changed = true;
}
}
}
self.idom.clear();
self.dom_tree.clear();
for i in 0..n {
let id = idx_to_id[i];
if i == entry_idx {
self.idom.insert(id, None);
} else {
let mut idom_idx = entry_idx;
for j in 0..n {
if j != i && doms[j][i] {
if doms[idom_idx][j] == false || j == idom_idx {
}
}
}
let mut best = None;
for j in 0..n {
if j != i && doms[j][i] {
let mut is_strict = false;
for k in 0..n {
if k != i && k != j && doms[j][k] && doms[k][i] {
is_strict = true;
break;
}
}
if !is_strict {
best = Some(idx_to_id[j]);
break;
}
}
}
self.idom
.insert(id, Some(best.unwrap_or(idx_to_id[entry_idx])));
let _ = idom_idx;
}
}
for (&id, &idom_opt) in &self.idom.clone() {
if let Some(idom_id) = idom_opt {
self.dom_tree.entry(idom_id).or_default().push(id);
}
}
self.compute_dominance_frontiers(mf, &id_to_idx, &idx_to_id);
}
fn compute_dominance_frontiers(
&mut self,
mf: &MachineFunction,
_id_to_idx: &HashMap<u32, usize>,
_idx_to_id: &[u32],
) {
self.dom_frontier.clear();
for block in &mf.blocks {
let pred_count = mf
.blocks
.iter()
.filter(|b| b.successors.contains(&block.id))
.count();
if pred_count >= 2 {
for other in &mf.blocks {
if other.successors.contains(&block.id) {
let mut runner = other.id;
while let Some(&Some(idom_id)) = self.idom.get(&runner) {
if idom_id == block.id {
break;
}
self.dom_frontier
.entry(idom_id)
.or_default()
.push(block.id);
runner = idom_id;
}
}
}
}
}
let _ = id_to_idx;
let _ = idx_to_id;
}
pub fn dominates(&self, a: u32, b: u32) -> bool {
if a == b {
return true;
}
let mut current = b;
while let Some(&Some(idom)) = self.idom.get(¤t) {
if idom == a {
return true;
}
if idom == current {
break;
}
current = idom;
}
false
}
pub fn get_idom(&self, block: u32) -> Option<u32> {
self.idom.get(&block).and_then(|o| *o)
}
}
impl Default for MachineDominatorTree {
fn default() -> Self {
Self::new()
}
}
pub struct MachineLoopInfo {
pub loops: Vec<MachineLoop>,
pub block_loop_map: HashMap<u32, usize>,
}
#[derive(Debug, Clone)]
pub struct MachineLoop {
pub id: usize,
pub header: usize,
pub blocks: Vec<u32>,
pub latches: Vec<u32>,
pub exiting: Vec<u32>,
pub parent_loop: Option<usize>,
pub child_loops: Vec<u32>,
}
impl MachineLoopInfo {
pub fn new() -> Self {
Self {
loops: Vec::new(),
block_loop_map: HashMap::new(),
}
}
pub fn compute(&mut self, mf: &MachineFunction, dom_tree: &MachineDominatorTree) {
self.loops.clear();
self.block_loop_map.clear();
for block in &mf.blocks {
for &succ in &block.successors {
if dom_tree.dominates(succ, block.id) {
self.find_loop_body(mf, succ, block.id);
}
}
}
}
fn find_loop_body(&mut self, mf: &MachineFunction, header: u32, back_edge_src: u32) {
let loop_id = self.loops.len();
let mut loop_blocks = vec![header, back_edge_src];
let mut worklist = vec![back_edge_src];
let mut visited: HashSet<u32> = [header, back_edge_src].iter().copied().collect();
while let Some(current) = worklist.pop() {
for block in &mf.blocks {
if block.successors.contains(¤t) && visited.insert(block.id) {
if block.id != header {
worklist.push(block.id);
loop_blocks.push(block.id);
}
}
}
}
let mut latches = Vec::new();
for block in &mf.blocks {
if loop_blocks.contains(&block.id) && block.successors.contains(&header) {
latches.push(block.id);
}
}
self.loops.push(MachineLoop {
id: loop_id,
header,
blocks: loop_blocks,
latches,
exiting: Vec::new(),
parent_loop: None,
child_loops: Vec::new(),
});
for &b in &self.loops.last().unwrap().blocks {
self.block_loop_map.insert(b, loop_id);
}
}
pub fn get_loop_for(&self, block: usize) -> Option<&MachineLoop> {
self.block_loop_map
.get(&block)
.and_then(|&id| self.loops.get(id))
}
pub fn is_back_edge(&self, src: usize, dst: usize, dom_tree: &MachineDominatorTree) -> bool {
dom_tree.dominates(dst, src)
}
}
impl Default for MachineLoopInfo {
fn default() -> Self {
Self::new()
}
}
pub struct MachinePostDominatorTree {
pub idom: HashMap<u32, Option<u32>>,
pub dom_tree: HashMap<u32, Vec<u32>>,
}
impl MachinePostDominatorTree {
pub fn new() -> Self {
Self {
idom: HashMap::new(),
dom_tree: HashMap::new(),
}
}
pub fn compute(&mut self, mf: &MachineFunction) {
let exit_blocks: Vec<usize> = mf
.blocks
.iter()
.filter(|b| b.successors.is_empty())
.map(|b| b.id)
.collect();
if exit_blocks.is_empty() {
return;
}
let virtual_exit = exit_blocks[0];
let mut rev_successors: HashMap<u32, Vec<u32>> = HashMap::new();
for block in &mf.blocks {
for &succ in &block.successors {
rev_successors.entry(succ).or_default().push(block.id);
}
}
let all_blocks: Vec<usize> = mf.blocks.iter().map(|b| b.id).collect();
let n = all_blocks.len();
let id_to_idx: HashMap<u32, usize> =
all_blocks.iter().enumerate().map(|(i, &id)| (id, i)).collect();
let entry_idx = id_to_idx[&virtual_exit];
let mut doms: Vec<Vec<bool>> = vec![vec![true; n]; n];
for i in 0..n {
doms[entry_idx][i] = (i == entry_idx);
}
let mut changed = true;
while changed {
changed = false;
for i in 0..n {
if i == entry_idx {
continue;
}
let preds = rev_successors
.get(&all_blocks[i])
.cloned()
.unwrap_or_default();
let mut new_dom: Vec<bool> = vec![true; n];
for &p_id in &preds {
if let Some(&p_idx) = id_to_idx.get(&p_id) {
for j in 0..n {
new_dom[j] = new_dom[j] && doms[p_idx][j];
}
}
}
new_dom[i] = true;
if new_dom != doms[i] {
doms[i] = new_dom;
changed = true;
}
}
}
self.idom.clear();
for (i, &id) in all_blocks.iter().enumerate() {
if i == entry_idx {
self.idom.insert(id, None);
} else {
let mut best = None;
for j in 0..n {
if j != i && doms[j][i] {
let mut is_strict = false;
for k in 0..n {
if k != i && k != j && doms[j][k] && doms[k][i] {
is_strict = true;
break;
}
}
if !is_strict {
best = Some(all_blocks[j]);
break;
}
}
}
self.idom.insert(id, best.or(Some(virtual_exit)));
}
}
self.dom_tree.clear();
for (&id, &pdom_opt) in &self.idom.clone() {
if let Some(pdom_id) = pdom_opt {
self.dom_tree.entry(pdom_id).or_default().push(id);
}
}
}
pub fn post_dominates(&self, a: u32, b: u32) -> bool {
if a == b {
return true;
}
let mut current = b;
while let Some(&Some(pdom)) = self.idom.get(¤t) {
if pdom == a {
return true;
}
if pdom == current {
break;
}
current = pdom;
}
false
}
}
impl Default for MachinePostDominatorTree {
fn default() -> Self {
Self::new()
}
}
pub struct MachineRegionInfo {
pub regions: Vec<MachineRegion>,
}
#[derive(Debug, Clone)]
pub struct MachineRegion {
pub id: usize,
pub entry: u32,
pub exit: u32,
pub blocks: Vec<u32>,
pub children: Vec<u32>,
pub parent: Option<u32>,
}
impl MachineRegionInfo {
pub fn new() -> Self {
Self {
regions: Vec::new(),
}
}
pub fn compute(
&mut self,
mf: &MachineFunction,
dom_tree: &MachineDominatorTree,
pdom_tree: &MachinePostDominatorTree,
) {
self.regions.clear();
if let Some(entry) = mf.blocks.iter().find(|b| b.is_entry) {
let exit_blocks: Vec<usize> = mf
.blocks
.iter()
.filter(|b| b.successors.is_empty())
.map(|b| b.id)
.collect();
let exit = exit_blocks.first().copied().unwrap_or(entry.id);
let blocks: Vec<usize> = mf.blocks.iter().map(|b| b.id).collect();
self.regions.push(MachineRegion {
id: 0,
entry: entry.id,
exit,
blocks,
children: Vec::new(),
parent: None,
});
}
for block in &mf.blocks {
for &succ in &block.successors {
if dom_tree.dominates(succ, block.id) && pdom_tree.post_dominates(block.id, succ) {
let rid = self.regions.len();
let region = MachineRegion {
id: rid,
entry: succ,
exit: block.id,
blocks: vec![succ, block.id],
children: Vec::new(),
parent: Some(0),
};
self.regions.push(region);
}
}
}
}
}
impl Default for MachineRegionInfo {
fn default() -> Self {
Self::new()
}
}
pub struct MachineBranchProbabilityInfo {
pub edge_probs: HashMap<(usize, usize), f64>,
}
impl MachineBranchProbabilityInfo {
pub fn new() -> Self {
Self {
edge_probs: HashMap::new(),
}
}
pub fn compute(
&mut self,
mf: &MachineFunction,
loop_info: &MachineLoopInfo,
) {
self.edge_probs.clear();
for block in &mf.blocks {
let num_succs = block.successors.len();
if num_succs == 0 {
continue;
}
let base_prob = 1.0 / num_succs as f64;
for &succ in &block.successors {
let prob = if block.instructions.last().map_or(false, |i| {
i.flags.is_branch || i.flags.is_terminator
}) {
if loop_info
.get_loop_for(block.id)
.map_or(false, |l| l.header == succ)
{
0.9
} else if loop_info.get_loop_for(succ).is_some()
&& loop_info.get_loop_for(block.id).is_none()
{
0.1
} else {
base_prob
}
} else {
1.0 };
self.edge_probs.insert((block.id, succ), prob);
}
if num_succs > 1 {
let total: f64 = block
.successors
.iter()
.map(|s| self.edge_probs.get(&(block.id, *s)).copied().unwrap_or(0.0))
.sum();
if total > 0.0 {
for &succ in &block.successors {
if let Some(prob) = self.edge_probs.get_mut(&(block.id, succ)) {
*prob /= total;
}
}
}
}
}
}
pub fn get_edge_probability(&self, src: usize, dst: usize) -> f64 {
self.edge_probs.get(&(src, dst)).copied().unwrap_or(0.5)
}
}
impl Default for MachineBranchProbabilityInfo {
fn default() -> Self {
Self::new()
}
}
pub struct MachineBlockFrequencyInfo {
pub frequencies: HashMap<u32, f64>,
}
impl MachineBlockFrequencyInfo {
pub fn new() -> Self {
Self {
frequencies: HashMap::new(),
}
}
pub fn compute(
&mut self,
mf: &MachineFunction,
branch_probs: &MachineBranchProbabilityInfo,
) {
self.frequencies.clear();
if let Some(entry) = mf.blocks.iter().find(|b| b.is_entry) {
self.frequencies.insert(entry.id, 1.0);
}
for _ in 0..20 {
for block in &mf.blocks {
let block_freq = self.frequencies.get(&block.id).copied().unwrap_or(0.0);
if block_freq <= 0.0 {
continue;
}
for &succ in &block.successors {
let prob = branch_probs.get_edge_probability(block.id, succ);
let incoming = block_freq * prob;
let current = self.frequencies.get(&succ).copied().unwrap_or(0.0);
self.frequencies.insert(succ, current + incoming);
}
}
}
}
pub fn get_block_freq(&self, block: usize) -> f64 {
self.frequencies.get(&block).copied().unwrap_or(0.0)
}
pub fn get_hottest_block(&self) -> Option<usize> {
self.frequencies
.iter()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map(|(&id, _)| id)
}
}
impl Default for MachineBlockFrequencyInfo {
fn default() -> Self {
Self::new()
}
}
pub struct MachineOptimizationRemarkEmitter {
pub remarks: Vec<MachineOptRemark>,
pub enabled: bool,
}
#[derive(Debug, Clone)]
pub struct MachineOptRemark {
pub pass_name: String,
pub kind: RemarkKind,
pub function_name: String,
pub message: String,
pub block_id: Option<u32>,
pub debug_loc: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemarkKind {
Passed,
Missed,
Analysis,
Always,
}
impl MachineOptimizationRemarkEmitter {
pub fn new() -> Self {
Self {
remarks: Vec::new(),
enabled: true,
}
}
pub fn emit(
&mut self,
kind: RemarkKind,
pass_name: &str,
func_name: &str,
msg: &str,
) {
if self.enabled {
self.remarks.push(MachineOptRemark {
pass_name: pass_name.to_string(),
kind,
function_name: func_name.to_string(),
message: msg.to_string(),
block_id: None,
debug_loc: None,
});
}
}
pub fn missed(&mut self, pass: &str, func: &str, msg: &str) {
self.emit(RemarkKind::Missed, pass, func, msg);
}
pub fn passed(&mut self, pass: &str, func: &str, msg: &str) {
self.emit(RemarkKind::Passed, pass, func, msg);
}
pub fn remarks_of_kind(&self, kind: RemarkKind) -> Vec<&MachineOptRemark> {
self.remarks.iter().filter(|r| r.kind == kind).collect()
}
}
impl Default for MachineOptimizationRemarkEmitter {
fn default() -> Self {
Self::new()
}
}
pub struct MachineDebugify {
pub counter: u32,
}
impl MachineDebugify {
pub fn new() -> Self {
Self { counter: 0 }
}
pub fn debugify_function(&mut self, mf: &mut MachineFunction) {
let source_file = format!("synthetic_debugify_{}.ll", mf.name);
self.counter = 0;
for block in &mut mf.blocks {
for instr in &mut block.instructions {
self.counter += 1;
let line = (self.counter % 1000) + 1;
let col = (self.counter % 80) + 1;
instr.debug_loc = Some(format!("{}:{}:{}", source_file, line, col));
}
}
}
pub fn strip_debugify(&self, mf: &mut MachineFunction) {
for block in &mut mf.blocks {
for instr in &mut block.instructions {
instr.debug_loc = None;
}
}
}
}
impl Default for MachineDebugify {
fn default() -> Self {
Self::new()
}
}
pub struct MachineCheckDebugify {
pub errors: Vec<String>,
}
impl MachineCheckDebugify {
pub fn new() -> Self {
Self {
errors: Vec::new(),
}
}
pub fn check(&mut self, mf: &MachineFunction) -> bool {
self.errors.clear();
let mut seen_lines: HashSet<String> = HashSet::new();
for block in &mf.blocks {
for instr in &block.instructions {
match &instr.debug_loc {
None => {
self.errors.push(format!(
"Instruction op:{} in block {} missing debug location",
instr.opcode, block.id
));
}
Some(loc) => {
if !loc.starts_with("synthetic_debugify_") {
self.errors.push(format!(
"Instruction op:{} has non-synthetic debug location: {}",
instr.opcode, loc
));
}
seen_lines.insert(loc.clone());
}
}
}
}
self.errors.is_empty()
}
pub fn report(&self) -> String {
if self.errors.is_empty() {
"All debug info checks passed.".to_string()
} else {
format!(
"Debug info validation found {} error(s):\n{}",
self.errors.len(),
self.errors.join("\n")
)
}
}
}
impl Default for MachineCheckDebugify {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::codegen_opt::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
fn make_test_mf(name: &str) -> MachineFunction {
let mut mf = MachineFunction::new(name);
let mut entry = MachineBasicBlock::new(0, "entry");
entry.is_entry = true;
entry.instructions.push(MachineInstr::new(1));
entry.instructions.push(
MachineInstr::new(15)
.with_operand(MachineOperand::Block(1)),
);
entry.successors.push(1);
mf.add_block(entry);
let mut exit = MachineBasicBlock::new(1, "exit");
exit.instructions.push(
MachineInstr::new(14), );
mf.add_block(exit);
mf
}
#[test]
fn test_machine_outliner_empty() {
let outliner = MachineOutliner::new();
let mf = make_test_mf("test_outline");
let candidates = outliner.scan_function(&mf);
assert!(candidates.is_empty());
}
#[test]
fn test_machine_outliner_hash() {
let seq = vec![
MachineInstr::new(1).with_operand(MachineOperand::Reg(llvm_native_core::codegen_opt::MCRegister(1))),
MachineInstr::new(2).with_operand(MachineOperand::Reg(llvm_native_core::codegen_opt::MCRegister(2))),
];
let hash = MachineOutliner::hash_sequence(&seq);
assert!(hash != 0);
}
#[test]
fn test_machine_function_splitter() {
let splitter = MachineFunctionSplitter::new();
let mf = make_test_mf("test_split");
let sections = splitter.classify_blocks(&mf);
assert!(sections.contains_key(&0));
assert!(sections.contains_key(&1));
assert_eq!(sections.get(&0), Some(&SectionKind::Hot));
}
#[test]
fn test_cfg_printer() {
let printer = MachineCFGPrinter::new();
let mf = make_test_mf("test_cfg");
let dot = printer.print_to_dot(&mf);
assert!(dot.contains("digraph"));
assert!(dot.contains("bb0"));
assert!(dot.contains("bb1"));
}
#[test]
fn test_dominator_tree() {
let mut dt = MachineDominatorTree::new();
let mf = make_test_mf("test_dom");
dt.compute(&mf);
assert_eq!(dt.idom.get(&0), Some(&None));
}
#[test]
fn test_loop_info() {
let mut loop_info = MachineLoopInfo::new();
let mut dt = MachineDominatorTree::new();
let mf = make_test_mf("test_loop");
dt.compute(&mf);
loop_info.compute(&mf, &dt);
assert!(loop_info.loops.is_empty()); }
#[test]
fn test_post_dominator_tree() {
let mut pdt = MachinePostDominatorTree::new();
let mf = make_test_mf("test_pdom");
pdt.compute(&mf);
assert!(!pdt.idom.is_empty());
}
#[test]
fn test_region_info() {
let mut ri = MachineRegionInfo::new();
let mut dt = MachineDominatorTree::new();
let mut pdt = MachinePostDominatorTree::new();
let mf = make_test_mf("test_regions");
dt.compute(&mf);
pdt.compute(&mf);
ri.compute(&mf, &dt, &pdt);
assert!(!ri.regions.is_empty());
}
#[test]
fn test_branch_probability_info() {
let mut bpi = MachineBranchProbabilityInfo::new();
let mut loop_info = MachineLoopInfo::new();
let mut dt = MachineDominatorTree::new();
let mf = make_test_mf("test_probs");
dt.compute(&mf);
loop_info.compute(&mf, &dt);
bpi.compute(&mf, &loop_info);
assert!(bpi.get_edge_probability(0, 1) > 0.0);
}
#[test]
fn test_block_frequency_info() {
let mut bfi = MachineBlockFrequencyInfo::new();
let mut bpi = MachineBranchProbabilityInfo::new();
let mut loop_info = MachineLoopInfo::new();
let mut dt = MachineDominatorTree::new();
let mf = make_test_mf("test_freq");
dt.compute(&mf);
loop_info.compute(&mf, &dt);
bpi.compute(&mf, &loop_info);
bfi.compute(&mf, &bpi);
assert!(bfi.get_block_freq(0) > 0.0);
}
#[test]
fn test_optimization_remark_emitter() {
let mut emitter = MachineOptimizationRemarkEmitter::new();
emitter.missed("TestPass", "test_func", "Unable to optimize");
emitter.passed("TestPass", "test_func", "Applied optimization");
assert_eq!(emitter.remarks.len(), 2);
assert_eq!(emitter.remarks_of_kind(RemarkKind::Passed).len(), 1);
}
#[test]
fn test_machine_debugify() {
let mut debugify = MachineDebugify::new();
let mut mf = make_test_mf("test_debug");
debugify.debugify_function(&mut mf);
for block in &mf.blocks {
for instr in &block.instructions {
assert!(instr.debug_loc.is_some(), "Missing debug location");
}
}
}
#[test]
fn test_check_debugify() {
let mut debugify = MachineDebugify::new();
let mut checker = MachineCheckDebugify::new();
let mut mf = make_test_mf("test_check_debug");
debugify.debugify_function(&mut mf);
let ok = checker.check(&mf);
assert!(ok, "Debug info check failed: {}", checker.report());
}
#[test]
fn test_check_debugify_fails_on_missing() {
let mut checker = MachineCheckDebugify::new();
let mf = make_test_mf("test_missing_debug");
let ok = checker.check(&mf);
assert!(!ok, "Should detect missing debug locations");
}
#[test]
fn test_sanitizer_metadata() {
let sbm = MachineSanitizerBinaryMetadata::new();
let mf = make_test_mf("test_san");
let entries = sbm.analyze(&mf);
let _ = entries;
}
#[test]
fn test_machine_outliner_cross_function() {
let outliner = MachineOutliner::new();
let mf1 = make_test_mf("func1");
let mf2 = make_test_mf("func2");
let candidates = outliner.scan_functions(&[mf1, mf2]);
assert!(candidates.is_empty()); }
#[test]
fn test_section_kind_default() {
let splitter = MachineFunctionSplitter::new();
assert_eq!(splitter.cold_threshold, 0.1);
assert!(splitter.use_profile);
}
#[test]
fn test_estimate_savings() {
let outliner = MachineOutliner::new();
let savings = outliner.estimate_savings(5, 3);
assert_eq!(savings, 14);
}
}