#![allow(non_upper_case_globals, dead_code)]
use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, VirtReg};
use crate::x86::x86_basic_block_utils::BlockFrequencyEstimator;
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
pub const MAX_TAIL_MERGE_COMPARE: usize = 64;
pub const MIN_COMMON_SUFFIX_LENGTH: usize = 2;
pub const MAX_TAIL_MERGE_BLOCKS: usize = 1000;
pub const MIN_TAIL_DUP_SAVINGS: usize = 2;
pub const MAX_TAIL_DUP_SIZE_INCREASE: f64 = 1.15;
pub const DEFAULT_HOT_THRESHOLD: f64 = 0.8;
pub const DEFAULT_COLD_THRESHOLD: f64 = 0.2;
pub const MAX_CRITICAL_EDGE_SPLITS: usize = 50;
pub const MAX_CRITICAL_EDGE_SIZE_INCREASE: usize = 100;
pub const MIN_JUMP_TABLE_DENSITY: f64 = 0.25;
pub const MAX_JUMP_TABLE_ENTRIES: usize = 256;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InstrHash {
pub opcode: u32,
pub operand_hash: u64,
pub is_terminator: bool,
pub has_side_effects: bool,
}
impl InstrHash {
pub fn from_instr(instr: &MachineInstr) -> Self {
let mut operand_hash: u64 = 0;
for op in &instr.operands {
match op {
MachineOperand::Reg(r) => {
operand_hash = operand_hash.wrapping_mul(31).wrapping_add(*r as u64);
}
MachineOperand::PhysReg(r) => {
operand_hash = operand_hash.wrapping_mul(31).wrapping_add(*r as u64);
}
MachineOperand::Imm(i) => {
operand_hash = operand_hash.wrapping_mul(31).wrapping_add(*i as u64);
}
MachineOperand::Label(_) | MachineOperand::Global(_) => {
operand_hash = operand_hash.wrapping_mul(31);
}
}
}
Self {
opcode: instr.opcode,
operand_hash,
is_terminator: false,
has_side_effects: false,
}
}
pub fn can_merge_with(&self, other: &InstrHash) -> bool {
self.opcode == other.opcode
&& self.operand_hash == other.operand_hash
&& !self.has_side_effects
&& !other.has_side_effects
}
}
pub fn longest_common_suffix(a: &[InstrHash], b: &[InstrHash], max_len: usize) -> usize {
let max_possible = a.len().min(b.len()).min(max_len);
let mut common_len = 0usize;
for i in 0..max_possible {
let ia = a[a.len() - 1 - i].clone();
let ib = b[b.len() - 1 - i].clone();
if ia.can_merge_with(&ib) {
common_len += 1;
} else {
break;
}
}
common_len
}
#[derive(Debug, Clone)]
pub struct TailMergeConfig {
pub enabled: bool,
pub min_common_length: usize,
pub max_compare: usize,
pub max_blocks: usize,
pub cross_block_merge: bool,
pub respect_side_effects: bool,
}
impl Default for TailMergeConfig {
fn default() -> Self {
Self {
enabled: true,
min_common_length: MIN_COMMON_SUFFIX_LENGTH,
max_compare: MAX_TAIL_MERGE_COMPARE,
max_blocks: MAX_TAIL_MERGE_BLOCKS,
cross_block_merge: true,
respect_side_effects: true,
}
}
}
#[derive(Debug, Clone)]
pub struct TailMergeCandidate {
pub block_a: usize,
pub block_b: usize,
pub common_length: usize,
pub savings: usize,
}
impl TailMergeCandidate {
pub fn new(block_a: usize, block_b: usize, common_length: usize) -> Self {
let savings = if common_length >= MIN_COMMON_SUFFIX_LENGTH {
common_length - 1 } else {
0
};
Self {
block_a,
block_b,
common_length,
savings,
}
}
pub fn is_profitable(&self) -> bool {
self.common_length >= MIN_COMMON_SUFFIX_LENGTH && self.savings > 0
}
}
#[derive(Debug, Clone)]
pub struct TailMergeResult {
pub merges_performed: usize,
pub instructions_eliminated: usize,
pub new_blocks: Vec<TailMergedBlock>,
pub modified_blocks: HashSet<usize>,
}
impl TailMergeResult {
pub fn new() -> Self {
Self {
merges_performed: 0,
instructions_eliminated: 0,
new_blocks: Vec::new(),
modified_blocks: HashSet::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct TailMergedBlock {
pub id: usize,
pub instructions: Vec<InstrHash>,
pub predecessors: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct TailDupConfig {
pub enabled: bool,
pub min_savings: usize,
pub max_size_increase: f64,
pub duplicate_loop_headers: bool,
pub max_dup_instrs: usize,
pub frequency_aware: bool,
}
impl Default for TailDupConfig {
fn default() -> Self {
Self {
enabled: true,
min_savings: MIN_TAIL_DUP_SAVINGS,
max_size_increase: MAX_TAIL_DUP_SIZE_INCREASE,
duplicate_loop_headers: true,
max_dup_instrs: 32,
frequency_aware: true,
}
}
}
#[derive(Debug, Clone)]
pub struct TailDupCandidate {
pub block: usize,
pub fallthrough: usize,
pub branch_savings: usize,
pub size_increase: usize,
pub profitable: bool,
}
impl TailDupCandidate {
pub fn new(
block: usize,
fallthrough: usize,
branch_savings: usize,
size_increase: usize,
) -> Self {
let profitable = branch_savings >= MIN_TAIL_DUP_SAVINGS
&& size_increase <= MAX_TAIL_DUP_SIZE_INCREASE as usize * 32;
Self {
block,
fallthrough,
branch_savings,
size_increase,
profitable,
}
}
}
#[derive(Debug, Clone)]
pub struct TailDupResult {
pub duplications: usize,
pub branches_eliminated: usize,
pub size_increase: usize,
pub new_blocks: Vec<usize>,
}
impl TailDupResult {
pub fn new() -> Self {
Self {
duplications: 0,
branches_eliminated: 0,
size_increase: 0,
new_blocks: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct BranchProbability {
pub src: usize,
pub dst: usize,
pub probability: f64,
pub from_profile: bool,
}
impl BranchProbability {
pub fn new(src: usize, dst: usize, probability: f64) -> Self {
Self {
src,
dst,
probability: probability.clamp(0.0, 1.0),
from_profile: false,
}
}
pub fn is_hot(&self, threshold: f64) -> bool {
self.probability >= threshold
}
pub fn is_cold(&self, threshold: f64) -> bool {
self.probability <= threshold
}
pub fn complement(&self) -> f64 {
1.0 - self.probability
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StaticBranchHeuristic {
LoopBackedge = 0,
CmpZero = 1,
PointerNonNull = 2,
ExceptionPath = 3,
ICacheHit = 4,
SwitchDefault = 5,
CmpEquality = 6,
CmpInequality = 7,
}
impl StaticBranchHeuristic {
pub fn probability(&self) -> f64 {
match self {
Self::LoopBackedge => 0.90,
Self::CmpZero => 0.25,
Self::PointerNonNull => 0.85,
Self::ExceptionPath => 0.05,
Self::ICacheHit => 0.90,
Self::SwitchDefault => 0.10,
Self::CmpEquality => 0.30,
Self::CmpInequality => 0.70,
}
}
}
#[derive(Debug, Clone)]
pub struct BlockFrequency {
pub block: usize,
pub frequency: f64,
pub in_loop: bool,
pub loop_depth: u32,
pub is_loop_header: bool,
pub exec_count: u64,
}
impl BlockFrequency {
pub fn new(block: usize) -> Self {
Self {
block,
frequency: 1.0,
in_loop: false,
loop_depth: 0,
is_loop_header: false,
exec_count: 0,
}
}
pub fn is_hot(&self, threshold: f64) -> bool {
self.frequency >= threshold
}
pub fn is_cold(&self, threshold: f64) -> bool {
self.frequency <= threshold
}
}
pub fn propagate_frequencies(
blocks: &[MachineBasicBlock],
probabilities: &[BranchProbability],
) -> Vec<BlockFrequency> {
let n = blocks.len();
let mut freqs: Vec<BlockFrequency> = (0..n).map(BlockFrequency::new).collect();
if n > 0 {
freqs[0].frequency = 1.0;
}
let mut edge_probs: HashMap<(usize, usize), f64> = HashMap::new();
for bp in probabilities {
edge_probs.insert((bp.src, bp.dst), bp.probability);
}
let mut changed = true;
let mut iterations = 0;
while changed && iterations < 100 {
changed = false;
for i in 0..n {
let mut total = 0.0f64;
let preds = &blocks[i].predecessors;
for &pred in preds {
let prob = edge_probs.get(&(pred, i)).copied().unwrap_or(1.0);
total += freqs[pred].frequency * prob;
}
if (total - freqs[i].frequency).abs() > 1e-6 {
freqs[i].frequency = if total > 0.0 {
total
} else {
freqs[i].frequency
};
changed = true;
}
}
iterations += 1;
}
freqs
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayoutStrategy {
HotColdSplit,
Topological,
ReversePostorder,
ProfileGuided,
}
#[derive(Debug, Clone)]
pub struct BranchLayoutConfig {
pub strategy: LayoutStrategy,
pub hot_threshold: f64,
pub cold_threshold: f64,
pub align_hot_blocks: bool,
pub cache_line_size: u32,
pub max_iterations: usize,
}
impl Default for BranchLayoutConfig {
fn default() -> Self {
Self {
strategy: LayoutStrategy::HotColdSplit,
hot_threshold: DEFAULT_HOT_THRESHOLD,
cold_threshold: DEFAULT_COLD_THRESHOLD,
align_hot_blocks: true,
cache_line_size: 64,
max_iterations: 100,
}
}
}
#[derive(Debug, Clone)]
pub struct ChainEdge {
pub src: usize,
pub dst: usize,
pub weight: f64,
pub is_fallthrough: bool,
}
impl ChainEdge {
pub fn new(src: usize, dst: usize, weight: f64) -> Self {
Self {
src,
dst,
weight,
is_fallthrough: false,
}
}
}
#[derive(Debug, Clone)]
pub struct BlockChain {
pub blocks: Vec<usize>,
pub total_frequency: f64,
pub is_hot: bool,
}
impl BlockChain {
pub fn new(block: usize, freq: f64, is_hot: bool) -> Self {
Self {
blocks: vec![block],
total_frequency: freq,
is_hot,
}
}
pub fn merge_after(&mut self, other: &BlockChain) {
self.blocks.extend(&other.blocks);
self.total_frequency += other.total_frequency;
}
pub fn merge_before(&mut self, other: &BlockChain) {
let mut combined = other.blocks.clone();
combined.append(&mut self.blocks);
self.blocks = combined;
self.total_frequency += other.total_frequency;
}
pub fn first(&self) -> Option<usize> {
self.blocks.first().copied()
}
pub fn last(&self) -> Option<usize> {
self.blocks.last().copied()
}
pub fn len(&self) -> usize {
self.blocks.len()
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct BlockLayoutResult {
pub block_order: Vec<usize>,
pub hot_chains: Vec<BlockChain>,
pub cold_blocks: Vec<usize>,
pub fallthrough_count: usize,
}
impl BlockLayoutResult {
pub fn new() -> Self {
Self {
block_order: Vec::new(),
hot_chains: Vec::new(),
cold_blocks: Vec::new(),
fallthrough_count: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct JumpTableConfig {
pub enabled: bool,
pub min_density: f64,
pub max_entries: usize,
pub use_indirection: bool,
pub bit_test_alternative: bool,
}
impl Default for JumpTableConfig {
fn default() -> Self {
Self {
enabled: true,
min_density: MIN_JUMP_TABLE_DENSITY,
max_entries: MAX_JUMP_TABLE_ENTRIES,
use_indirection: true,
bit_test_alternative: true,
}
}
}
#[derive(Debug, Clone)]
pub struct SwitchAnalysis {
pub num_cases: usize,
pub min_value: i64,
pub max_value: i64,
pub value_range: i64,
pub density: f64,
pub suitable_for_jump_table: bool,
pub better_as_bit_test: bool,
pub table_size_bytes: usize,
pub sorted_cases: Vec<i64>,
pub case_targets: Vec<usize>,
}
impl SwitchAnalysis {
pub fn analyze(cases: &[(i64, usize)]) -> Self {
if cases.is_empty() {
return Self {
num_cases: 0,
min_value: 0,
max_value: 0,
value_range: 0,
density: 0.0,
suitable_for_jump_table: false,
better_as_bit_test: false,
table_size_bytes: 0,
sorted_cases: Vec::new(),
case_targets: Vec::new(),
};
}
let mut sorted: Vec<(i64, usize)> = cases.to_vec();
sorted.sort_by_key(|&(v, _)| v);
let num_cases = sorted.len();
let min_value = sorted.first().unwrap().0;
let max_value = sorted.last().unwrap().0;
let value_range = max_value - min_value + 1;
let density = if value_range > 0 {
num_cases as f64 / value_range as f64
} else {
1.0
};
let table_size_bytes = (value_range as usize) * 8; let suitable = density >= MIN_JUMP_TABLE_DENSITY
&& num_cases <= MAX_JUMP_TABLE_ENTRIES
&& value_range > 0;
let better_bit = num_cases <= 16 && !suitable;
let (sorted_cases, case_targets): (Vec<i64>, Vec<usize>) = sorted.into_iter().unzip();
Self {
num_cases,
min_value,
max_value,
value_range,
density,
suitable_for_jump_table: suitable,
better_as_bit_test: better_bit,
table_size_bytes,
sorted_cases,
case_targets,
}
}
pub fn should_use_jump_table(&self) -> bool {
self.suitable_for_jump_table && !self.better_as_bit_test
}
pub fn should_use_bit_test(&self) -> bool {
self.better_as_bit_test
}
}
#[derive(Debug, Clone)]
pub struct JumpTableEntry {
pub value: i64,
pub target_block: usize,
pub table_offset: usize,
}
#[derive(Debug, Clone)]
pub struct JumpTable {
pub entries: Vec<JumpTableEntry>,
pub base_value: i64,
pub range: i64,
pub size_bytes: usize,
pub uses_indirection: bool,
}
impl JumpTable {
pub fn from_analysis(analysis: &SwitchAnalysis) -> Self {
let mut entries = Vec::with_capacity(analysis.sorted_cases.len());
let base_value = analysis.min_value;
for (&value, &target) in analysis
.sorted_cases
.iter()
.zip(analysis.case_targets.iter())
{
let table_offset = (value - base_value) as usize;
entries.push(JumpTableEntry {
value,
target_block: target,
table_offset,
});
}
Self {
entries,
base_value,
range: analysis.value_range,
size_bytes: analysis.table_size_bytes,
uses_indirection: false,
}
}
}
#[derive(Debug, Clone)]
pub struct BranchRedirectConfig {
pub enabled: bool,
pub analyze_conditions: bool,
pub branch_threading: bool,
pub max_threading_depth: usize,
}
impl Default for BranchRedirectConfig {
fn default() -> Self {
Self {
enabled: true,
analyze_conditions: true,
branch_threading: true,
max_threading_depth: 3,
}
}
}
#[derive(Debug, Clone)]
pub struct BranchSimplify {
pub block: usize,
pub condition: ConditionInfo,
pub foldable: bool,
pub folded_result: Option<bool>,
pub convert_to_unconditional: bool,
}
#[derive(Debug, Clone)]
pub struct ConditionInfo {
pub is_zero_test: bool,
pub is_nonzero_test: bool,
pub is_equality: bool,
pub is_inequality: bool,
pub is_loop_backedge: bool,
pub is_error_check: bool,
pub true_target: Option<usize>,
pub false_target: Option<usize>,
}
impl ConditionInfo {
pub fn new() -> Self {
Self {
is_zero_test: false,
is_nonzero_test: false,
is_equality: false,
is_inequality: false,
is_loop_backedge: false,
is_error_check: false,
true_target: None,
false_target: None,
}
}
pub fn heuristic(&self) -> Option<StaticBranchHeuristic> {
if self.is_loop_backedge {
Some(StaticBranchHeuristic::LoopBackedge)
} else if self.is_zero_test {
Some(StaticBranchHeuristic::CmpZero)
} else if self.is_nonzero_test {
Some(StaticBranchHeuristic::PointerNonNull)
} else if self.is_error_check {
Some(StaticBranchHeuristic::ExceptionPath)
} else if self.is_equality {
Some(StaticBranchHeuristic::CmpEquality)
} else if self.is_inequality {
Some(StaticBranchHeuristic::CmpInequality)
} else {
None
}
}
pub fn estimated_probability(&self) -> f64 {
self.heuristic().map(|h| h.probability()).unwrap_or(0.5)
}
}
#[derive(Debug, Clone)]
pub struct UnreachableElimConfig {
pub enabled: bool,
pub check_no_preds: bool,
pub check_reachability: bool,
}
impl Default for UnreachableElimConfig {
fn default() -> Self {
Self {
enabled: true,
check_no_preds: true,
check_reachability: true,
}
}
}
#[derive(Debug, Clone)]
pub struct UnreachableElimResult {
pub eliminated_blocks: Vec<usize>,
pub instructions_removed: usize,
pub cleaned_edges: usize,
}
impl UnreachableElimResult {
pub fn new() -> Self {
Self {
eliminated_blocks: Vec::new(),
instructions_removed: 0,
cleaned_edges: 0,
}
}
}
pub fn find_reachable_blocks(blocks: &[MachineBasicBlock]) -> HashSet<usize> {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
if !blocks.is_empty() {
queue.push_back(0); }
while let Some(block_id) = queue.pop_front() {
if !visited.insert(block_id) {
continue;
}
if block_id < blocks.len() {
for &succ in &blocks[block_id].successors {
if !visited.contains(&succ) {
queue.push_back(succ);
}
}
}
}
visited
}
pub fn eliminate_unreachable_blocks(blocks: &mut Vec<MachineBasicBlock>) -> UnreachableElimResult {
let mut result = UnreachableElimResult::new();
let reachable = find_reachable_blocks(blocks);
let mut to_remove: Vec<usize> = Vec::new();
for i in 0..blocks.len() {
if !reachable.contains(&i) {
to_remove.push(i);
result.instructions_removed += blocks[i].instructions.len();
}
}
for i in 0..blocks.len() {
if !to_remove.contains(&i) {
let removed_set: HashSet<usize> = to_remove.iter().copied().collect();
let old_succ_count = blocks[i].successors.len();
blocks[i].successors.retain(|s| !removed_set.contains(s));
blocks[i].predecessors.retain(|p| !removed_set.contains(p));
result.cleaned_edges += old_succ_count - blocks[i].successors.len();
}
}
result.eliminated_blocks = to_remove;
result
}
#[derive(Debug, Clone)]
pub struct BlockMergeConfig {
pub enabled: bool,
pub max_merged_size: usize,
pub merge_single_pred: bool,
pub aggressive_merge: bool,
}
impl Default for BlockMergeConfig {
fn default() -> Self {
Self {
enabled: true,
max_merged_size: 256,
merge_single_pred: true,
aggressive_merge: false,
}
}
}
#[derive(Debug, Clone)]
pub struct MergeablePair {
pub pred: usize,
pub succ: usize,
pub single_pred: bool,
pub combined_size: usize,
}
impl MergeablePair {
pub fn new(pred: usize, succ: usize, succ_pred_count: usize, combined_size: usize) -> Self {
Self {
pred,
succ,
single_pred: succ_pred_count == 1,
combined_size,
}
}
pub fn should_merge(&self, config: &BlockMergeConfig) -> bool {
if !config.enabled {
return false;
}
if self.combined_size > config.max_merged_size {
return false;
}
if config.aggressive_merge {
return true;
}
self.single_pred || config.merge_single_pred
}
}
#[derive(Debug, Clone)]
pub struct BlockMergeResult {
pub merges_performed: usize,
pub blocks_eliminated: usize,
pub merged_pairs: Vec<(usize, usize)>,
}
impl BlockMergeResult {
pub fn new() -> Self {
Self {
merges_performed: 0,
blocks_eliminated: 0,
merged_pairs: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct CriticalEdgeConfig {
pub enabled: bool,
pub max_splits: usize,
pub max_size_increase: usize,
pub min_frequency: f64,
}
impl Default for CriticalEdgeConfig {
fn default() -> Self {
Self {
enabled: true,
max_splits: MAX_CRITICAL_EDGE_SPLITS,
max_size_increase: MAX_CRITICAL_EDGE_SIZE_INCREASE,
min_frequency: 0.01,
}
}
}
#[derive(Debug, Clone)]
pub struct CriticalEdge {
pub src: usize,
pub dst: usize,
pub src_succ_count: usize,
pub dst_pred_count: usize,
pub frequency: f64,
}
impl CriticalEdge {
pub fn is_critical(&self) -> bool {
self.src_succ_count > 1 && self.dst_pred_count > 1
}
}
pub fn find_critical_edges(
blocks: &[MachineBasicBlock],
frequencies: Option<&[BlockFrequency]>,
) -> Vec<CriticalEdge> {
let mut critical = Vec::new();
for (i, block) in blocks.iter().enumerate() {
if block.successors.len() <= 1 {
continue;
}
for &succ in &block.successors {
if succ < blocks.len() && blocks[succ].predecessors.len() > 1 {
let freq = frequencies
.and_then(|f| f.get(i).map(|bf| bf.frequency))
.unwrap_or(1.0);
critical.push(CriticalEdge {
src: i,
dst: succ,
src_succ_count: block.successors.len(),
dst_pred_count: blocks[succ].predecessors.len(),
frequency: freq,
});
}
}
}
critical
}
#[derive(Debug, Clone)]
pub struct CriticalEdgeResult {
pub splits_performed: usize,
pub new_blocks: Vec<CriticalEdgeBlock>,
pub size_increase: usize,
}
impl CriticalEdgeResult {
pub fn new() -> Self {
Self {
splits_performed: 0,
new_blocks: Vec::new(),
size_increase: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct CriticalEdgeBlock {
pub id: usize,
pub src: usize,
pub dst: usize,
pub instructions: Vec<InstrHash>,
}
#[derive(Debug, Clone)]
pub struct X86BranchFoldingAdv {
pub tail_merge: TailMergeConfig,
pub tail_dup: TailDupConfig,
pub layout: BranchLayoutConfig,
pub jump_table: JumpTableConfig,
pub redirect: BranchRedirectConfig,
pub unreachable: UnreachableElimConfig,
pub block_merge: BlockMergeConfig,
pub critical_edge: CriticalEdgeConfig,
pub tail_merge_result: Option<TailMergeResult>,
pub tail_dup_result: Option<TailDupResult>,
pub layout_result: Option<BlockLayoutResult>,
pub unreachable_result: Option<UnreachableElimResult>,
pub block_merge_result: Option<BlockMergeResult>,
pub critical_edge_result: Option<CriticalEdgeResult>,
pub stats: BranchFoldingStats,
}
#[derive(Debug, Clone, Default)]
pub struct BranchFoldingStats {
pub tail_merges: usize,
pub tail_duplications: usize,
pub branches_eliminated: usize,
pub unreachable_eliminated: usize,
pub blocks_merged: usize,
pub critical_edges_split: usize,
pub instructions_saved: isize,
pub code_size_delta: isize,
pub fallthrough_opportunities: usize,
pub jump_tables_created: usize,
}
impl X86BranchFoldingAdv {
pub fn new() -> Self {
Self {
tail_merge: TailMergeConfig::default(),
tail_dup: TailDupConfig::default(),
layout: BranchLayoutConfig::default(),
jump_table: JumpTableConfig::default(),
redirect: BranchRedirectConfig::default(),
unreachable: UnreachableElimConfig::default(),
block_merge: BlockMergeConfig::default(),
critical_edge: CriticalEdgeConfig::default(),
tail_merge_result: None,
tail_dup_result: None,
layout_result: None,
unreachable_result: None,
block_merge_result: None,
critical_edge_result: None,
stats: BranchFoldingStats::default(),
}
}
pub fn analyze_tail_merge(&self, blocks: &[MachineBasicBlock]) -> Vec<TailMergeCandidate> {
let mut candidates = Vec::new();
let block_count = blocks.len().min(self.tail_merge.max_blocks);
let suffixes: Vec<Vec<InstrHash>> = blocks[..block_count]
.iter()
.map(|b| b.instructions.iter().map(InstrHash::from_instr).collect())
.collect();
for i in 0..block_count {
if blocks[i].successors.is_empty() && !blocks[i].instructions.is_empty() {
continue;
}
for j in (i + 1)..block_count {
if blocks[j].successors.is_empty() && !blocks[j].instructions.is_empty() {
continue;
}
let common =
longest_common_suffix(&suffixes[i], &suffixes[j], self.tail_merge.max_compare);
if common >= self.tail_merge.min_common_length {
candidates.push(TailMergeCandidate::new(i, j, common));
}
}
}
candidates.sort_by(|a, b| b.savings.cmp(&a.savings));
candidates
}
pub fn run_tail_merge(&mut self, blocks: &[MachineBasicBlock]) -> TailMergeResult {
if !self.tail_merge.enabled {
return TailMergeResult::new();
}
let candidates = self.analyze_tail_merge(blocks);
let mut result = TailMergeResult::new();
let mut merged_set: HashSet<usize> = HashSet::new();
for candidate in &candidates {
if !candidate.is_profitable() {
continue;
}
if merged_set.contains(&candidate.block_a) || merged_set.contains(&candidate.block_b) {
continue;
}
merged_set.insert(candidate.block_a);
merged_set.insert(candidate.block_b);
result.merges_performed += 1;
result.instructions_eliminated += candidate.savings;
result.modified_blocks.insert(candidate.block_a);
result.modified_blocks.insert(candidate.block_b);
}
self.stats.tail_merges = result.merges_performed;
self.stats.instructions_saved += result.instructions_eliminated as isize;
self.tail_merge_result = Some(result.clone());
result
}
pub fn find_tail_dup_candidates(&self, blocks: &[MachineBasicBlock]) -> Vec<TailDupCandidate> {
let mut candidates = Vec::new();
for (i, block) in blocks.iter().enumerate() {
if block.predecessors.len() <= 1 {
continue;
}
if block.instructions.len() > self.tail_dup.max_dup_instrs {
continue;
}
let branch_savings = block.predecessors.len() - 1;
let size_increase = block.instructions.len() * (block.predecessors.len() - 1);
for &fallthrough in &block.successors {
candidates.push(TailDupCandidate::new(
i,
fallthrough,
branch_savings,
size_increase,
));
}
}
candidates
}
pub fn run_tail_duplication(&mut self, blocks: &[MachineBasicBlock]) -> TailDupResult {
if !self.tail_dup.enabled {
return TailDupResult::new();
}
let candidates = self.find_tail_dup_candidates(blocks);
let mut result = TailDupResult::new();
for candidate in &candidates {
if !candidate.profitable {
continue;
}
result.duplications += 1;
result.branches_eliminated += candidate.branch_savings;
result.size_increase += candidate.size_increase;
result.new_blocks.push(candidate.block);
}
self.stats.tail_duplications = result.duplications;
self.stats.branches_eliminated += result.branches_eliminated;
self.stats.code_size_delta += result.size_increase as isize;
self.tail_dup_result = Some(result.clone());
result
}
pub fn compute_block_layout(
&mut self,
blocks: &[MachineBasicBlock],
probabilities: &[BranchProbability],
frequencies: &[BlockFrequency],
) -> BlockLayoutResult {
let mut result = BlockLayoutResult::new();
let hot_blocks: HashSet<usize> = frequencies
.iter()
.enumerate()
.filter(|(_, bf)| bf.is_hot(self.layout.hot_threshold))
.map(|(i, _)| i)
.collect();
let cold_blocks: Vec<usize> = frequencies
.iter()
.enumerate()
.filter(|(_, bf)| bf.is_cold(self.layout.cold_threshold))
.map(|(i, _)| i)
.collect();
let mut remaining: HashSet<usize> = (0..blocks.len()).collect();
let mut chains: Vec<BlockChain> = Vec::new();
for &hot in &hot_blocks {
if remaining.remove(&hot) {
let freq = frequencies.get(hot).map(|bf| bf.frequency).unwrap_or(1.0);
chains.push(BlockChain::new(hot, freq, true));
}
}
for i in 0..blocks.len() {
if remaining.remove(&i) {
let freq = frequencies.get(i).map(|bf| bf.frequency).unwrap_or(1.0);
chains.push(BlockChain::new(i, freq, false));
}
}
let mut chain_indices: HashMap<usize, usize> = HashMap::new();
for (ci, chain) in chains.iter().enumerate() {
for &block in &chain.blocks {
chain_indices.insert(block, ci);
}
}
let mut inter_chain_edges: Vec<(usize, usize, f64)> = Vec::new();
for bp in probabilities {
let src_chain = chain_indices.get(&bp.src).copied();
let dst_chain = chain_indices.get(&bp.dst).copied();
if let (Some(sc), Some(dc)) = (src_chain, dst_chain) {
if sc != dc {
inter_chain_edges.push((sc, dc, bp.probability));
}
}
}
inter_chain_edges.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(Ordering::Equal));
let mut merged: HashSet<usize> = HashSet::new();
for (sc, dc, _weight) in &inter_chain_edges {
if merged.contains(sc) || merged.contains(dc) {
continue;
}
merged.insert(*sc);
merged.insert(*dc);
}
let mut order = Vec::new();
for chain in &chains {
if chain.is_hot {
order.extend(&chain.blocks);
}
}
for chain in &chains {
if !chain.is_hot {
order.extend(&chain.blocks);
}
}
let mut fallthrough_count = 0usize;
for w in order.windows(2) {
if blocks
.get(w[0])
.map(|b: &MachineBasicBlock| b.successors.contains(&w[1]))
.unwrap_or(false)
{
fallthrough_count += 1;
}
}
result.block_order = order;
result.cold_blocks = cold_blocks;
result.fallthrough_count = fallthrough_count;
self.stats.fallthrough_opportunities = fallthrough_count;
result
}
pub fn run_block_layout(
&mut self,
blocks: &[MachineBasicBlock],
probabilities: &[BranchProbability],
) -> BlockLayoutResult {
let frequencies = propagate_frequencies(blocks, probabilities);
let result = self.compute_block_layout(blocks, probabilities, &frequencies);
self.layout_result = Some(result.clone());
result
}
pub fn analyze_switch(&self, cases: &[(i64, usize)]) -> SwitchAnalysis {
SwitchAnalysis::analyze(cases)
}
pub fn build_jump_table(&mut self, analysis: &SwitchAnalysis) -> Option<JumpTable> {
if !analysis.should_use_jump_table() {
return None;
}
let mut table = JumpTable::from_analysis(analysis);
if self.jump_table.use_indirection && analysis.value_range > 64 {
table.uses_indirection = true;
}
self.stats.jump_tables_created += 1;
Some(table)
}
pub fn analyze_branch(
&self,
block: &MachineBasicBlock,
block_idx: usize,
) -> Option<BranchSimplify> {
if !self.redirect.enabled {
return None;
}
if block.successors.len() != 2 {
return None;
}
let true_target = block.successors.first().copied();
let false_target = block.successors.get(1).copied();
let mut condition = ConditionInfo::new();
condition.true_target = true_target;
condition.false_target = false_target;
let foldable = false;
Some(BranchSimplify {
block: block_idx,
condition,
foldable,
folded_result: None,
convert_to_unconditional: false,
})
}
pub fn run_unreachable_elim(
&mut self,
blocks: &mut Vec<MachineBasicBlock>,
) -> UnreachableElimResult {
if !self.unreachable.enabled {
return UnreachableElimResult::new();
}
let result = eliminate_unreachable_blocks(blocks);
self.stats.unreachable_eliminated = result.eliminated_blocks.len();
self.stats.instructions_saved += result.instructions_removed as isize;
self.unreachable_result = Some(result.clone());
result
}
pub fn find_mergeable_pairs(&self, blocks: &[MachineBasicBlock]) -> Vec<MergeablePair> {
let mut pairs = Vec::new();
for (i, block) in blocks.iter().enumerate() {
if block.successors.len() != 1 {
continue;
}
let succ = block.successors[0];
if succ >= blocks.len() {
continue;
}
let succ_pred_count = blocks[succ].predecessors.len();
let combined_size = block.instructions.len() + blocks[succ].instructions.len();
pairs.push(MergeablePair::new(i, succ, succ_pred_count, combined_size));
}
pairs
}
pub fn run_block_merge(&mut self, blocks: &[MachineBasicBlock]) -> BlockMergeResult {
let mut result = BlockMergeResult::new();
if !self.block_merge.enabled {
return result;
}
let pairs = self.find_mergeable_pairs(blocks);
let mut merged_blocks: HashSet<usize> = HashSet::new();
for pair in &pairs {
if !pair.should_merge(&self.block_merge) {
continue;
}
if merged_blocks.contains(&pair.pred) || merged_blocks.contains(&pair.succ) {
continue;
}
merged_blocks.insert(pair.pred);
merged_blocks.insert(pair.succ);
result.merges_performed += 1;
result.blocks_eliminated += 1;
result.merged_pairs.push((pair.pred, pair.succ));
}
self.stats.blocks_merged = result.blocks_eliminated;
self.block_merge_result = Some(result.clone());
result
}
pub fn run_critical_edge_splitting(
&mut self,
blocks: &[MachineBasicBlock],
frequencies: Option<&[BlockFrequency]>,
) -> CriticalEdgeResult {
let mut result = CriticalEdgeResult::new();
if !self.critical_edge.enabled {
return result;
}
let critical_edges = find_critical_edges(blocks, frequencies);
for edge in &critical_edges {
if !edge.is_critical() {
continue;
}
if result.splits_performed >= self.critical_edge.max_splits {
break;
}
if result.size_increase >= self.critical_edge.max_size_increase {
break;
}
if edge.frequency < self.critical_edge.min_frequency {
continue;
}
result.splits_performed += 1;
result.size_increase += 8;
result.new_blocks.push(CriticalEdgeBlock {
id: blocks.len() + result.new_blocks.len(),
src: edge.src,
dst: edge.dst,
instructions: Vec::new(),
});
}
self.stats.critical_edges_split = result.splits_performed;
self.stats.code_size_delta += result.size_increase as isize;
self.critical_edge_result = Some(result.clone());
result
}
pub fn run_pipeline(
&mut self,
blocks: &mut Vec<MachineBasicBlock>,
probabilities: Option<&[BranchProbability]>,
) {
self.run_unreachable_elim(blocks);
self.run_block_merge(blocks);
self.run_tail_merge(blocks);
self.run_tail_duplication(blocks);
let _freqs: Option<Vec<BlockFrequency>> = probabilities.map(|probs| {
let mut fb = Vec::new();
for bp in probs {
while fb.len() <= bp.src.max(bp.dst) {
fb.push(BlockFrequency::new(fb.len()));
}
}
fb
});
self.run_critical_edge_splitting(blocks, _freqs.as_ref().map(|v| v.as_slice()));
if let Some(probs) = probabilities {
self.run_block_layout(blocks, probs);
}
}
}
pub fn make_x86_branch_folding_adv() -> X86BranchFoldingAdv {
X86BranchFoldingAdv::new()
}
pub fn make_x86_branch_folding_adv_aggressive() -> X86BranchFoldingAdv {
let mut bf = X86BranchFoldingAdv::new();
bf.tail_merge.min_common_length = 1;
bf.tail_merge.cross_block_merge = true;
bf.tail_dup.min_savings = 1;
bf.block_merge.aggressive_merge = true;
bf
}
pub fn make_x86_branch_folding_adv_size_opt() -> X86BranchFoldingAdv {
let mut bf = X86BranchFoldingAdv::new();
bf.tail_merge.min_common_length = 1;
bf.tail_dup.enabled = false; bf.block_merge.aggressive_merge = true;
bf.critical_edge.enabled = false; bf
}
pub fn make_x86_branch_folding_adv_perf_opt() -> X86BranchFoldingAdv {
let mut bf = X86BranchFoldingAdv::new();
bf.tail_merge.enabled = true;
bf.tail_dup.enabled = true;
bf.tail_dup.min_savings = 1;
bf.layout.strategy = LayoutStrategy::HotColdSplit;
bf.layout.align_hot_blocks = true;
bf.critical_edge.enabled = true;
bf
}
pub fn make_x86_branch_folding_adv_thresholds(
hot_threshold: f64,
cold_threshold: f64,
) -> X86BranchFoldingAdv {
let mut bf = X86BranchFoldingAdv::new();
bf.layout.hot_threshold = hot_threshold;
bf.layout.cold_threshold = cold_threshold;
bf
}
#[derive(Debug, Clone)]
pub struct TailMergeGroup {
pub blocks: Vec<usize>,
pub common_suffix: Vec<InstrHash>,
pub suffix_length: usize,
pub merged_block_id: Option<usize>,
}
impl TailMergeGroup {
pub fn new(blocks: Vec<usize>, common_suffix: Vec<InstrHash>) -> Self {
let suffix_length = common_suffix.len();
Self {
blocks,
common_suffix,
suffix_length,
merged_block_id: None,
}
}
pub fn estimated_savings(&self) -> usize {
if self.blocks.len() < 2 {
return 0;
}
(self.blocks.len() - 1) * (self.suffix_length.saturating_sub(1))
}
}
pub fn find_tail_merge_groups(
blocks: &[MachineBasicBlock],
config: &TailMergeConfig,
) -> Vec<TailMergeGroup> {
let n = blocks.len().min(config.max_blocks);
let mut groups: Vec<TailMergeGroup> = Vec::new();
let block_hashes: Vec<Vec<InstrHash>> = blocks[..n]
.iter()
.map(|b| b.instructions.iter().map(InstrHash::from_instr).collect())
.collect();
let mut merged_into_group: HashSet<usize> = HashSet::new();
for i in 0..n {
if merged_into_group.contains(&i) {
continue;
}
if blocks[i].successors.is_empty() {
continue; }
let mut group_blocks = vec![i];
let mut group_suffix: Option<Vec<InstrHash>> = None;
for j in (i + 1)..n {
if merged_into_group.contains(&j) {
continue;
}
if blocks[j].successors.is_empty() {
continue;
}
let common_len =
longest_common_suffix(&block_hashes[i], &block_hashes[j], config.max_compare);
if common_len >= config.min_common_length {
let suffix: Vec<InstrHash> =
block_hashes[i][block_hashes[i].len() - common_len..].to_vec();
match &group_suffix {
None => {
group_suffix = Some(suffix);
group_blocks.push(j);
merged_into_group.insert(j);
}
Some(existing) => {
let suffix2: Vec<InstrHash> =
block_hashes[j][block_hashes[j].len() - common_len..].to_vec();
let shared = longest_common_suffix(existing, &suffix2, config.max_compare);
if shared >= config.min_common_length {
let new_suffix = existing[existing.len() - shared..].to_vec();
group_suffix = Some(new_suffix);
group_blocks.push(j);
merged_into_group.insert(j);
}
}
}
}
}
if group_blocks.len() >= 2 {
if let Some(suffix) = group_suffix {
merged_into_group.insert(i);
groups.push(TailMergeGroup::new(group_blocks, suffix));
}
}
}
groups.sort_by(|a, b| b.estimated_savings().cmp(&a.estimated_savings()));
groups
}
#[derive(Debug, Clone)]
pub struct TailDupCostModel {
pub cost_per_byte: f64,
pub benefit_per_branch: f64,
pub benefit_per_fallthrough: f64,
pub max_dup_size: usize,
pub account_cache: bool,
pub l1_icache_size: usize,
}
impl Default for TailDupCostModel {
fn default() -> Self {
Self {
cost_per_byte: 0.1,
benefit_per_branch: 3.0,
benefit_per_fallthrough: 1.5,
max_dup_size: 32,
account_cache: true,
l1_icache_size: 32 * 1024,
}
}
}
impl TailDupCostModel {
pub fn evaluate(
&self,
block_size: usize,
num_preds: usize,
num_branches_eliminated: usize,
creates_fallthrough: bool,
) -> (bool, f64) {
if block_size > self.max_dup_size {
return (false, -1.0);
}
let code_cost = block_size as f64 * (num_preds - 1) as f64 * self.cost_per_byte;
let branch_benefit = num_branches_eliminated as f64 * self.benefit_per_branch;
let fallthrough_benefit = if creates_fallthrough {
self.benefit_per_fallthrough
} else {
0.0
};
let net_benefit = branch_benefit + fallthrough_benefit - code_cost;
(net_benefit > 0.0, net_benefit)
}
pub fn icache_impact(&self, total_code_size: usize, dup_size: usize) -> f64 {
if !self.account_cache {
return 0.0;
}
if total_code_size > self.l1_icache_size {
return dup_size as f64 * 0.5;
}
let remaining = self.l1_icache_size - total_code_size;
if dup_size as f64 > remaining as f64 * 0.1 {
return dup_size as f64 * 0.2;
}
0.0
}
}
#[derive(Debug, Clone)]
pub struct ThreadablePath {
pub path: Vec<usize>,
pub profitable: bool,
pub jumps_eliminated: usize,
pub creates_fallthrough: bool,
pub net_size_change: isize,
}
impl ThreadablePath {
pub fn new(path: Vec<usize>) -> Self {
Self {
path,
profitable: false,
jumps_eliminated: 0,
creates_fallthrough: false,
net_size_change: 0,
}
}
pub fn len(&self) -> usize {
self.path.len()
}
pub fn is_empty(&self) -> bool {
self.path.is_empty()
}
}
pub fn find_threadable_paths(
blocks: &[MachineBasicBlock],
max_depth: usize,
) -> Vec<ThreadablePath> {
let n = blocks.len();
let mut paths = Vec::new();
for src in 0..n {
if blocks[src].successors.len() != 2 {
continue;
}
for &mid in &blocks[src].successors {
if mid >= n {
continue;
}
if blocks[mid].successors.len() != 1 {
continue;
}
if blocks[mid].predecessors.len() != 1 {
continue;
}
let dst = blocks[mid].successors[0];
if dst >= n || dst == src {
continue;
}
if blocks[mid].instructions.len() > max_depth {
continue;
}
let mut path = ThreadablePath::new(vec![src, mid, dst]);
path.jumps_eliminated = 1;
path.profitable = blocks[mid].instructions.len() <= 3;
path.net_size_change = -(blocks[mid].instructions.len() as isize);
paths.push(path);
}
}
paths.sort_by(|a, b| b.jumps_eliminated.cmp(&a.jumps_eliminated));
paths
}
#[derive(Debug, Clone)]
pub struct CondToUncondResult {
pub block: usize,
pub can_convert: bool,
pub new_target: Option<usize>,
pub unreachable_target: Option<usize>,
pub reason: Option<String>,
}
impl CondToUncondResult {
pub fn convertible(block: usize, new_target: usize, unreachable: usize) -> Self {
Self {
block,
can_convert: true,
new_target: Some(new_target),
unreachable_target: Some(unreachable),
reason: None,
}
}
pub fn not_convertible(block: usize, reason: String) -> Self {
Self {
block,
can_convert: false,
new_target: None,
unreachable_target: None,
reason: Some(reason),
}
}
}
pub fn analyze_cond_to_uncond(
blocks: &[MachineBasicBlock],
reachable: &HashSet<usize>,
) -> Vec<CondToUncondResult> {
let n = blocks.len();
let mut results = Vec::new();
for i in 0..n {
let succs = &blocks[i].successors;
if succs.len() != 2 {
continue;
}
let true_target = succs[0];
let false_target = succs[1];
if true_target == false_target {
results.push(CondToUncondResult::convertible(i, true_target, true_target));
continue;
}
if true_target < n && !reachable.contains(&true_target) {
results.push(CondToUncondResult::convertible(
i,
false_target,
true_target,
));
continue;
}
if false_target < n && !reachable.contains(&false_target) {
results.push(CondToUncondResult::convertible(
i,
true_target,
false_target,
));
continue;
}
results.push(CondToUncondResult::not_convertible(
i,
"Both targets are distinct and reachable".to_string(),
));
}
results
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchPredictionHint {
None,
LikelyTaken,
LikelyNotTaken,
LoopBranch,
UnlikelyPath,
}
impl BranchPredictionHint {
pub fn probability(&self) -> f64 {
match self {
Self::None => 0.5,
Self::LikelyTaken => 0.8,
Self::LikelyNotTaken => 0.2,
Self::LoopBranch => 0.95,
Self::UnlikelyPath => 0.05,
}
}
pub fn infer(
block: usize,
target: usize,
blocks: &[MachineBasicBlock],
dom_tree: Option<&crate::x86::x86_basic_block_utils::DominatorTree>,
) -> Self {
if let Some(dt) = dom_tree {
if dt.dominates(target, block) {
return Self::LoopBranch;
}
}
if target > block && target < blocks.len() {
return Self::LikelyNotTaken;
}
Self::None
}
}
#[derive(Debug, Clone)]
pub struct FallthroughChain {
pub blocks: Vec<usize>,
pub total_freq: f64,
pub jumps_eliminated: usize,
}
impl FallthroughChain {
pub fn new(block: usize, freq: f64) -> Self {
Self {
blocks: vec![block],
total_freq: freq,
jumps_eliminated: 0,
}
}
pub fn try_extend(
&mut self,
blocks: &[MachineBasicBlock],
freq_est: &BlockFrequencyEstimator,
placed: &HashSet<usize>,
max_len: usize,
) -> bool {
if self.blocks.len() >= max_len {
return false;
}
let last = *self.blocks.last().unwrap();
if last >= blocks.len() {
return false;
}
let best_succ = blocks[last]
.successors
.iter()
.filter(|&&s| s < blocks.len() && !placed.contains(&s))
.filter(|&&s| blocks[s].predecessors.len() == 1)
.max_by(|&&a, &&b| {
freq_est
.frequency_of(a)
.partial_cmp(&freq_est.frequency_of(b))
.unwrap_or(std::cmp::Ordering::Equal)
});
if let Some(&succ) = best_succ {
self.blocks.push(succ);
self.total_freq += freq_est.frequency_of(succ);
if blocks[last].successors.len() == 1 && blocks[last].successors[0] == succ {
self.jumps_eliminated += 1;
}
true
} else {
false
}
}
pub fn net_benefit(&self) -> f64 {
let jump_benefit = self.jumps_eliminated as f64 * 2.0;
let freq_benefit = self.total_freq * 0.5;
jump_benefit + freq_benefit
}
}
pub fn build_fallthrough_chains(
blocks: &[MachineBasicBlock],
freq_est: &BlockFrequencyEstimator,
max_chain_len: usize,
) -> Vec<FallthroughChain> {
let n = blocks.len();
if n == 0 {
return Vec::new();
}
let mut chains: Vec<FallthroughChain> = Vec::new();
let mut placed: HashSet<usize> = HashSet::new();
let mut block_order: Vec<usize> = (0..n).collect();
block_order.sort_by(|&a, &b| {
freq_est
.frequency_of(b)
.partial_cmp(&freq_est.frequency_of(a))
.unwrap_or(std::cmp::Ordering::Equal)
});
for block in block_order {
if placed.contains(&block) {
continue;
}
let mut chain = FallthroughChain::new(block, freq_est.frequency_of(block));
placed.insert(block);
while chain.try_extend(blocks, freq_est, &placed, max_chain_len) {
let last = *chain.blocks.last().unwrap();
placed.insert(last);
}
chains.push(chain);
}
chains
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_instr_hash_equivalence() {
let mut instr1 = MachineInstr::new(100);
instr1.push_reg(1);
instr1.push_imm(42);
let mut instr2 = MachineInstr::new(100);
instr2.push_reg(1);
instr2.push_imm(42);
let h1 = InstrHash::from_instr(&instr1);
let h2 = InstrHash::from_instr(&instr2);
assert!(h1.can_merge_with(&h2));
}
#[test]
fn test_instr_hash_different_opcode() {
let instr1 = MachineInstr::new(100);
let instr2 = MachineInstr::new(200);
let h1 = InstrHash::from_instr(&instr1);
let h2 = InstrHash::from_instr(&instr2);
assert!(!h1.can_merge_with(&h2));
}
#[test]
fn test_instr_hash_different_operand() {
let mut instr1 = MachineInstr::new(100);
instr1.push_imm(42);
let mut instr2 = MachineInstr::new(100);
instr2.push_imm(99);
let h1 = InstrHash::from_instr(&instr1);
let h2 = InstrHash::from_instr(&instr2);
assert!(!h1.can_merge_with(&h2));
}
#[test]
fn test_longest_common_suffix_empty() {
let a: Vec<InstrHash> = vec![];
let b: Vec<InstrHash> = vec![];
assert_eq!(longest_common_suffix(&a, &b, 64), 0);
}
#[test]
fn test_longest_common_suffix_identical() {
let mut i1 = MachineInstr::new(1);
i1.push_imm(10);
let mut i2 = MachineInstr::new(2);
i2.push_imm(20);
let a = vec![InstrHash::from_instr(&i1), InstrHash::from_instr(&i2)];
let b = vec![InstrHash::from_instr(&i1), InstrHash::from_instr(&i2)];
assert_eq!(longest_common_suffix(&a, &b, 64), 2);
}
#[test]
fn test_longest_common_suffix_partial() {
let mut i1 = MachineInstr::new(1);
i1.push_imm(10);
let mut i2 = MachineInstr::new(2);
i2.push_imm(20);
let mut i3 = MachineInstr::new(3);
i3.push_imm(30);
let a = vec![InstrHash::from_instr(&i1), InstrHash::from_instr(&i2)];
let b = vec![InstrHash::from_instr(&i3), InstrHash::from_instr(&i2)];
assert_eq!(longest_common_suffix(&a, &b, 64), 1);
}
#[test]
fn test_tail_merge_candidate_profitable() {
let c = TailMergeCandidate::new(0, 1, 3);
assert!(c.is_profitable());
}
#[test]
fn test_tail_merge_candidate_not_profitable() {
let c = TailMergeCandidate::new(0, 1, 1);
assert!(!c.is_profitable());
}
#[test]
fn test_tail_dup_profitable() {
let candidate = TailDupCandidate::new(0, 1, 5, 8);
assert!(candidate.profitable);
}
#[test]
fn test_tail_dup_not_profitable_low_savings() {
let candidate = TailDupCandidate::new(0, 1, 1, 8);
assert!(!candidate.profitable); }
#[test]
fn test_branch_probability_hot_cold() {
let hot = BranchProbability::new(0, 1, 0.95);
let cold = BranchProbability::new(0, 2, 0.05);
assert!(hot.is_hot(0.8));
assert!(!hot.is_cold(0.2));
assert!(cold.is_cold(0.2));
assert!(!cold.is_hot(0.8));
}
#[test]
fn test_branch_probability_complement() {
let bp = BranchProbability::new(0, 1, 0.7);
assert!((bp.complement() - 0.3).abs() < 1e-9);
}
#[test]
fn test_heuristic_probabilities() {
assert!((StaticBranchHeuristic::LoopBackedge.probability() - 0.90).abs() < 1e-9);
assert!((StaticBranchHeuristic::CmpZero.probability() - 0.25).abs() < 1e-9);
assert!((StaticBranchHeuristic::ExceptionPath.probability() - 0.05).abs() < 1e-9);
assert!((StaticBranchHeuristic::CmpEquality.probability() - 0.30).abs() < 1e-9);
}
#[test]
fn test_block_frequency_hot_cold() {
let mut bf = BlockFrequency::new(0);
bf.frequency = 0.9;
assert!(bf.is_hot(0.8));
assert!(!bf.is_cold(0.2));
bf.frequency = 0.1;
assert!(!bf.is_hot(0.8));
assert!(bf.is_cold(0.2));
}
#[test]
fn test_propagate_frequencies_empty() {
let blocks: Vec<MachineBasicBlock> = vec![];
let probs: Vec<BranchProbability> = vec![];
let freqs = propagate_frequencies(&blocks, &probs);
assert!(freqs.is_empty());
}
#[test]
fn test_propagate_frequencies_single_block() {
let b0 = MachineBasicBlock::new(0);
let blocks = vec![b0];
let probs: Vec<BranchProbability> = vec![];
let freqs = propagate_frequencies(&blocks, &probs);
assert_eq!(freqs.len(), 1);
assert!((freqs[0].frequency - 1.0).abs() < 1e-9);
}
#[test]
fn test_block_chain_new() {
let chain = BlockChain::new(5, 3.0, true);
assert_eq!(chain.blocks, vec![5]);
assert!(chain.is_hot);
assert_eq!(chain.first(), Some(5));
assert_eq!(chain.last(), Some(5));
}
#[test]
fn test_block_chain_merge_after() {
let mut a = BlockChain::new(0, 1.0, true);
let b = BlockChain::new(1, 0.5, false);
a.merge_after(&b);
assert_eq!(a.blocks, vec![0, 1]);
assert!((a.total_frequency - 1.5).abs() < 1e-9);
}
#[test]
fn test_block_chain_merge_before() {
let mut a = BlockChain::new(3, 1.0, true);
let b = BlockChain::new(2, 0.5, false);
a.merge_before(&b);
assert_eq!(a.blocks, vec![2, 3]);
}
#[test]
fn test_switch_analysis_empty() {
let analysis = SwitchAnalysis::analyze(&[]);
assert_eq!(analysis.num_cases, 0);
assert!(!analysis.suitable_for_jump_table);
}
#[test]
fn test_switch_analysis_dense() {
let cases = vec![(1, 10), (2, 11), (3, 12), (4, 13), (5, 14)];
let analysis = SwitchAnalysis::analyze(&cases);
assert_eq!(analysis.num_cases, 5);
assert_eq!(analysis.value_range, 5);
assert!(analysis.density >= 1.0);
assert!(analysis.should_use_jump_table());
}
#[test]
fn test_switch_analysis_sparse() {
let cases = vec![(1, 10), (100, 11), (1000, 12)];
let analysis = SwitchAnalysis::analyze(&cases);
assert_eq!(analysis.num_cases, 3);
assert!(analysis.density < MIN_JUMP_TABLE_DENSITY);
assert!(!analysis.should_use_jump_table());
}
#[test]
fn test_jump_table_from_analysis() {
let cases = vec![(0, 5), (1, 6), (2, 7)];
let analysis = SwitchAnalysis::analyze(&cases);
let table = JumpTable::from_analysis(&analysis);
assert_eq!(table.entries.len(), 3);
assert_eq!(table.base_value, 0);
assert_eq!(table.range, 3);
}
#[test]
fn test_condition_info_heuristic() {
let mut ci = ConditionInfo::new();
assert!(ci.heuristic().is_none());
ci.is_loop_backedge = true;
assert_eq!(ci.heuristic(), Some(StaticBranchHeuristic::LoopBackedge));
ci.is_loop_backedge = false;
ci.is_zero_test = true;
assert_eq!(ci.heuristic(), Some(StaticBranchHeuristic::CmpZero));
}
#[test]
fn test_condition_estimated_probability() {
let mut ci = ConditionInfo::new();
assert!((ci.estimated_probability() - 0.5).abs() < 1e-9);
ci.is_loop_backedge = true;
assert!((ci.estimated_probability() - 0.90).abs() < 1e-9);
}
#[test]
fn test_find_reachable_blocks_simple() {
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1];
let mut b1 = MachineBasicBlock::new(1);
b1.successors = vec![2];
let b2 = MachineBasicBlock::new(2);
let blocks = vec![b0, b1, b2];
let reachable = find_reachable_blocks(&blocks);
assert_eq!(reachable.len(), 3);
assert!(reachable.contains(&0));
assert!(reachable.contains(&1));
assert!(reachable.contains(&2));
}
#[test]
fn test_find_reachable_blocks_with_unreachable() {
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1];
let b1 = MachineBasicBlock::new(1);
let b2 = MachineBasicBlock::new(2);
let blocks = vec![b0, b1, b2];
let reachable = find_reachable_blocks(&blocks);
assert_eq!(reachable.len(), 2);
assert!(reachable.contains(&0));
assert!(reachable.contains(&1));
assert!(!reachable.contains(&2));
}
#[test]
fn test_eliminate_unreachable_blocks() {
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1];
let mut b1 = MachineBasicBlock::new(1);
b1.predecessors = vec![0];
let b2 = MachineBasicBlock::new(2);
let mut blocks = vec![b0, b1, b2];
let result = eliminate_unreachable_blocks(&mut blocks);
assert_eq!(result.eliminated_blocks.len(), 1);
assert!(result.eliminated_blocks.contains(&2));
}
#[test]
fn test_find_critical_edges() {
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1, 2];
let mut b1 = MachineBasicBlock::new(1);
b1.predecessors = vec![0];
b1.successors = vec![3];
let mut b2 = MachineBasicBlock::new(2);
b2.predecessors = vec![0];
b2.successors = vec![3];
let mut b3 = MachineBasicBlock::new(3);
b3.predecessors = vec![1, 2];
let blocks = vec![b0, b1, b2, b3];
let edges = find_critical_edges(&blocks, None);
assert!(edges.is_empty()); }
#[test]
fn test_find_critical_edges_present() {
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1, 2];
let mut b1 = MachineBasicBlock::new(1);
b1.predecessors = vec![0, 2]; let mut b2 = MachineBasicBlock::new(2);
b2.predecessors = vec![0, 1]; b2.successors = vec![1];
let blocks = vec![b0, b1, b2];
let edges = find_critical_edges(&blocks, None);
assert_eq!(edges.len(), 2);
}
#[test]
fn test_mergeable_pair_single_pred() {
let pair = MergeablePair::new(0, 1, 1, 50);
let config = BlockMergeConfig::default();
assert!(pair.should_merge(&config));
}
#[test]
fn test_mergeable_pair_too_large() {
let pair = MergeablePair::new(0, 1, 1, 500);
let config = BlockMergeConfig::default();
assert!(!pair.should_merge(&config));
}
#[test]
fn test_mergeable_pair_multi_pred_no_merge() {
let pair = MergeablePair::new(0, 1, 3, 50);
let config = BlockMergeConfig::default();
assert!(!pair.should_merge(&config));
}
#[test]
fn test_make_branch_folding_adv() {
let bf = make_x86_branch_folding_adv();
assert!(bf.tail_merge.enabled);
assert!(bf.tail_dup.enabled);
assert!(bf.unreachable.enabled);
}
#[test]
fn test_make_aggressive() {
let bf = make_x86_branch_folding_adv_aggressive();
assert_eq!(bf.tail_merge.min_common_length, 1);
assert!(bf.block_merge.aggressive_merge);
}
#[test]
fn test_make_size_opt() {
let bf = make_x86_branch_folding_adv_size_opt();
assert!(!bf.tail_dup.enabled);
assert!(bf.block_merge.aggressive_merge);
}
#[test]
fn test_make_perf_opt() {
let bf = make_x86_branch_folding_adv_perf_opt();
assert!(bf.critical_edge.enabled);
assert!(bf.layout.align_hot_blocks);
}
#[test]
fn test_pipeline_empty_function() {
let mut bf = X86BranchFoldingAdv::new();
let mut blocks: Vec<MachineBasicBlock> = vec![];
bf.run_pipeline(&mut blocks, None);
}
#[test]
fn test_analyze_tail_merge_empty() {
let bf = X86BranchFoldingAdv::new();
let blocks: Vec<MachineBasicBlock> = vec![];
let candidates = bf.analyze_tail_merge(&blocks);
assert!(candidates.is_empty());
}
#[test]
fn test_build_jump_table_rejects_bad_switch() {
let bf = X86BranchFoldingAdv::new();
let analysis = SwitchAnalysis::analyze(&[(1, 5), (1000, 6)]);
let table = bf.build_jump_table(&analysis);
assert!(table.is_none());
}
#[test]
fn test_find_mergeable_pairs() {
let bf = X86BranchFoldingAdv::new();
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1];
b0.instructions = vec![MachineInstr::new(1)];
let mut b1 = MachineBasicBlock::new(1);
b1.predecessors = vec![0];
b1.instructions = vec![MachineInstr::new(2)];
let blocks = vec![b0, b1];
let pairs = bf.find_mergeable_pairs(&blocks);
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0].pred, 0);
assert_eq!(pairs[0].succ, 1);
}
#[test]
fn test_analyze_branch_two_successors() {
let bf = X86BranchFoldingAdv::new();
let mut block = MachineBasicBlock::new(0);
block.successors = vec![1, 2];
let result = bf.analyze_branch(&block, 0);
assert!(result.is_some());
let branch = result.unwrap();
assert_eq!(branch.block, 0);
assert_eq!(branch.condition.true_target, Some(1));
assert_eq!(branch.condition.false_target, Some(2));
}
#[test]
fn test_analyze_branch_not_branch() {
let bf = X86BranchFoldingAdv::new();
let block = MachineBasicBlock::new(0);
let result = bf.analyze_branch(&block, 0);
assert!(result.is_none());
}
#[test]
fn test_branch_folding_stats_default() {
let stats = BranchFoldingStats::default();
assert_eq!(stats.tail_merges, 0);
assert_eq!(stats.tail_duplications, 0);
assert_eq!(stats.instructions_saved, 0);
}
#[test]
fn test_tail_merge_group_savings() {
let mut i1 = MachineInstr::new(1);
i1.push_imm(10);
let hashes = vec![InstrHash::from_instr(&i1)];
let group = TailMergeGroup::new(vec![0, 1], hashes);
assert_eq!(group.blocks.len(), 2);
assert!(group.estimated_savings() == 0); }
#[test]
fn test_tail_merge_group_multi_block() {
let mut i1 = MachineInstr::new(1);
i1.push_imm(10);
let mut i2 = MachineInstr::new(2);
i2.push_imm(20);
let mut i3 = MachineInstr::new(3);
i3.push_imm(30);
let hashes = vec![
InstrHash::from_instr(&i1),
InstrHash::from_instr(&i2),
InstrHash::from_instr(&i3),
];
let group = TailMergeGroup::new(vec![0, 1, 2], hashes);
assert!(group.estimated_savings() > 0);
}
#[test]
fn test_tail_dup_cost_model_profitable() {
let model = TailDupCostModel::default();
let (profitable, benefit) = model.evaluate(5, 3, 2, true);
assert!(profitable);
assert!(benefit > 0.0);
}
#[test]
fn test_tail_dup_cost_model_too_large() {
let model = TailDupCostModel::default();
let (profitable, _) = model.evaluate(64, 3, 2, true);
assert!(!profitable);
}
#[test]
fn test_tail_dup_cost_model_icache_impact() {
let model = TailDupCostModel::default();
let impact = model.icache_impact(32 * 1024 + 1, 100);
assert!(impact > 0.0); }
#[test]
fn test_find_threadable_paths_empty() {
let blocks: Vec<MachineBasicBlock> = vec![];
let paths = find_threadable_paths(&blocks, 3);
assert!(paths.is_empty());
}
#[test]
fn test_threadable_path_new() {
let path = ThreadablePath::new(vec![0, 1, 2]);
assert_eq!(path.len(), 3);
assert!(!path.is_empty());
assert!(!path.profitable);
}
#[test]
fn test_cond_to_uncond_same_targets() {
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1, 1]; let blocks = vec![b0];
let reachable: HashSet<usize> = [0, 1].iter().copied().collect();
let results = analyze_cond_to_uncond(&blocks, &reachable);
assert_eq!(results.len(), 1);
assert!(results[0].can_convert);
}
#[test]
fn test_cond_to_uncond_unreachable_target() {
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1, 2];
let blocks = vec![b0, MachineBasicBlock::new(1), MachineBasicBlock::new(2)];
let reachable: HashSet<usize> = [0, 1].iter().copied().collect();
let results = analyze_cond_to_uncond(&blocks, &reachable);
assert_eq!(results.len(), 1);
assert!(results[0].can_convert); }
#[test]
fn test_branch_prediction_hint_probabilities() {
assert!((BranchPredictionHint::LoopBranch.probability() - 0.95).abs() < 1e-9);
assert!((BranchPredictionHint::UnlikelyPath.probability() - 0.05).abs() < 1e-9);
assert!((BranchPredictionHint::LikelyTaken.probability() - 0.8).abs() < 1e-9);
assert!((BranchPredictionHint::None.probability() - 0.5).abs() < 1e-9);
}
#[test]
fn test_branch_prediction_hint_infer() {
let blocks = make_loop_cfg();
let hint = BranchPredictionHint::infer(3, 1, &blocks, None);
assert_eq!(hint, BranchPredictionHint::None);
}
#[test]
fn test_fallthrough_chain_new() {
let chain = FallthroughChain::new(0, 1.5);
assert_eq!(chain.blocks, vec![0]);
assert!((chain.total_freq - 1.5).abs() < 1e-9);
assert_eq!(chain.jumps_eliminated, 0);
}
#[test]
fn test_build_fallthrough_chains_empty() {
let fe = BlockFrequencyEstimator::new();
let chains = build_fallthrough_chains(&[], &fe, 100);
assert!(chains.is_empty());
}
#[test]
fn test_fallthrough_chain_net_benefit() {
let mut chain = FallthroughChain::new(0, 10.0);
chain.jumps_eliminated = 2;
let benefit = chain.net_benefit();
assert!(benefit > 0.0);
}
fn make_loop_cfg() -> Vec<MachineBasicBlock> {
let mut b0 = MachineBasicBlock::new(0);
b0.successors = vec![1];
let mut b1 = MachineBasicBlock::new(1);
b1.predecessors = vec![0, 3];
b1.successors = vec![2];
let mut b2 = MachineBasicBlock::new(2);
b2.predecessors = vec![1];
b2.successors = vec![3];
let mut b3 = MachineBasicBlock::new(3);
b3.predecessors = vec![2];
b3.successors = vec![1];
vec![b0, b1, b2, b3]
}
}