use crate::pass_manager::*;
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
pub mod pass_ids {
pub const MEM2REG: u64 = 0x0001;
pub const INSTCOMBINE: u64 = 0x0002;
pub const SIMPLIFYCFG: u64 = 0x0003;
pub const REASSOCIATE: u64 = 0x0004;
pub const GVN: u64 = 0x0005;
pub const SCCP: u64 = 0x0006;
pub const EARLY_CSE: u64 = 0x0007;
pub const LICM: u64 = 0x0008;
pub const LOOP_ROTATE: u64 = 0x0009;
pub const LOOP_SIMPLIFY: u64 = 0x000A;
pub const INDVARS: u64 = 0x000B;
pub const LOOP_DELETION: u64 = 0x000C;
pub const LOOP_UNROLL: u64 = 0x000D;
pub const INLINE: u64 = 0x0010;
pub const JUMP_THREADING: u64 = 0x0011;
pub const CORRELATED_PROPAGATION: u64 = 0x0012;
pub const DSE: u64 = 0x0013;
pub const ADCE: u64 = 0x0014;
pub const SLP_VECTORIZER: u64 = 0x0015;
pub const LOOP_VECTORIZE: u64 = 0x0016;
pub const LOOP_UNROLL_AND_JAM: u64 = 0x0017;
pub const GLOBALOPT: u64 = 0x0018;
pub const IPSCCP: u64 = 0x0019;
pub const DEADARGELIM: u64 = 0x001A;
pub const GLOBALDCE: u64 = 0x001B;
pub const ARGPROMOTION: u64 = 0x0020;
pub const TAILCALLELIM: u64 = 0x0021;
pub const MERGEFUNC: u64 = 0x0022;
pub const LOOP_INTERCHANGE: u64 = 0x0023;
pub const LOOP_UNSWITCH: u64 = 0x0024;
pub const LOOP_DISTRIBUTE: u64 = 0x0025;
pub const LOOP_FUSION: u64 = 0x0026;
pub const LOOP_VERSIONING_LICM: u64 = 0x0027;
pub const AGGRESSIVE_INSTCOMBINE: u64 = 0x0028;
pub const PGO_MEMOP_OPT: u64 = 0x0029;
pub const PHI_ELIMINATION: u64 = 0x0100;
pub const TWO_ADDRESS_INSTRUCTION: u64 = 0x0101;
pub const MACHINE_CSE: u64 = 0x0102;
pub const MACHINE_LICM: u64 = 0x0103;
pub const MACHINE_SINK: u64 = 0x0104;
pub const PEEPHOLE_OPT: u64 = 0x0105;
pub const MACHINE_CP: u64 = 0x0106;
pub const MACHINE_DCE: u64 = 0x0107;
pub const REG_ALLOC_GREEDY: u64 = 0x0180;
pub const REG_ALLOC_PBQP: u64 = 0x0181;
pub const REG_ALLOC_FAST: u64 = 0x0182;
pub const REG_ALLOC_BASIC: u64 = 0x0183;
pub const POST_RA_SCHEDULER: u64 = 0x0200;
pub const BRANCH_FOLDING: u64 = 0x0201;
pub const TAIL_DUPLICATION: u64 = 0x0202;
pub const MACHINE_BLOCK_PLACEMENT: u64 = 0x0203;
pub const POST_RA_MACHINE_CP: u64 = 0x0204;
pub const POST_RA_MACHINE_SINK: u64 = 0x0205;
pub const POST_RA_MACHINE_DCE: u64 = 0x0206;
pub const MACHINE_OUTLINER: u64 = 0x0207;
pub const X86_DAG_ISEL: u64 = 0x1000;
pub const X86_FAST_ISEL: u64 = 0x1001;
pub const X86_GLOBAL_ISEL: u64 = 0x1002;
pub const X86_FLOATING_POINT: u64 = 0x1003;
pub const X86_FIXUP_LEA: u64 = 0x1004;
pub const X86_FIXUP_BW: u64 = 0x1005;
pub const X86_FIXUP_SETCC: u64 = 0x1006;
pub const X86_PAD_SHORT_FUNCTION: u64 = 0x1007;
pub const X86_CALL_FRAME_OPT: u64 = 0x1008;
pub const X86_CMOV_CONVERSION: u64 = 0x1009;
pub const X86_DOMAIN_REASSIGN: u64 = 0x100A;
pub const X86_AVOID_STORE_FORWARDING: u64 = 0x100B;
pub const X86_VZERO_UPPER: u64 = 0x100C;
pub const X86_FIXUP_VECTOR_CONSTANTS: u64 = 0x100D;
pub const OUTLINE_FUNCTIONS: u64 = 0x2000;
pub const MERGE_CONSTANTS: u64 = 0x2001;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PipelinePhase {
EarlyIR,
MainIR,
LateIR,
Lowering,
PreRA,
RegAlloc,
PostRA,
X86Fixup,
Emission,
}
impl PipelinePhase {
pub fn name(&self) -> &'static str {
match self {
PipelinePhase::EarlyIR => "Early IR",
PipelinePhase::MainIR => "Main IR",
PipelinePhase::LateIR => "Late IR",
PipelinePhase::Lowering => "Instruction Selection",
PipelinePhase::PreRA => "Pre-RA Machine",
PipelinePhase::RegAlloc => "Register Allocation",
PipelinePhase::PostRA => "Post-RA Machine",
PipelinePhase::X86Fixup => "X86 Fixups",
PipelinePhase::Emission => "Code Emission",
}
}
}
#[derive(Debug, Clone)]
pub struct X86PassDescriptor {
pub id: u64,
pub name: String,
pub description: String,
pub phase: PipelinePhase,
pub opt_levels: Vec<OptimizationLevel>,
pub dependencies: Vec<String>,
pub is_analysis: bool,
pub is_x86_specific: bool,
}
impl X86PassDescriptor {
pub fn new(id: u64, name: &str, description: &str) -> Self {
Self {
id,
name: name.to_string(),
description: description.to_string(),
phase: PipelinePhase::MainIR,
opt_levels: vec![
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
],
dependencies: Vec::new(),
is_analysis: false,
is_x86_specific: false,
}
}
pub fn with_phase(mut self, phase: PipelinePhase) -> Self {
self.phase = phase;
self
}
pub fn with_opt_levels(mut self, levels: Vec<OptimizationLevel>) -> Self {
self.opt_levels = levels;
self
}
pub fn with_analysis(mut self) -> Self {
self.is_analysis = true;
self
}
pub fn with_x86_specific(mut self) -> Self {
self.is_x86_specific = true;
self
}
pub fn with_dependency(mut self, dep: &str) -> Self {
self.dependencies.push(dep.to_string());
self
}
pub fn enabled_at(&self, level: OptimizationLevel) -> bool {
self.opt_levels.contains(&level)
}
}
#[derive(Debug)]
pub struct X86PassRegistry {
descriptors: HashMap<u64, X86PassDescriptor>,
name_index: HashMap<String, u64>,
pipeline_order: Vec<u64>,
phase_index: HashMap<PipelinePhase, Vec<u64>>,
}
impl X86PassRegistry {
pub fn new() -> Self {
Self {
descriptors: HashMap::new(),
name_index: HashMap::new(),
pipeline_order: Vec::new(),
phase_index: HashMap::new(),
}
}
pub fn register(&mut self, desc: X86PassDescriptor) {
let id = desc.id;
let name = desc.name.clone();
let phase = desc.phase;
self.name_index.insert(name, id);
self.descriptors.insert(id, desc);
self.pipeline_order.push(id);
self.phase_index.entry(phase).or_default().push(id);
}
pub fn register_all_defaults(&mut self) {
self.register_ir_passes();
self.register_machine_passes();
self.register_regalloc_passes();
self.register_x86_specific_passes();
}
fn register_ir_passes(&mut self) {
self.register(
X86PassDescriptor::new(pass_ids::MEM2REG, "mem2reg", "Promote Memory to Register")
.with_phase(PipelinePhase::EarlyIR),
);
self.register(
X86PassDescriptor::new(
pass_ids::INSTCOMBINE,
"instcombine",
"Combine Redundant Instructions",
)
.with_phase(PipelinePhase::MainIR),
);
self.register(
X86PassDescriptor::new(pass_ids::SIMPLIFYCFG, "simplifycfg", "Simplify the CFG")
.with_phase(PipelinePhase::MainIR),
);
self.register(
X86PassDescriptor::new(
pass_ids::REASSOCIATE,
"reassociate",
"Reassociate Expressions",
)
.with_phase(PipelinePhase::MainIR),
);
self.register(
X86PassDescriptor::new(pass_ids::GVN, "gvn", "Global Value Numbering")
.with_phase(PipelinePhase::MainIR)
.with_dependency("mem2reg"),
);
self.register(
X86PassDescriptor::new(
pass_ids::SCCP,
"sccp",
"Sparse Conditional Constant Propagation",
)
.with_phase(PipelinePhase::MainIR),
);
self.register(
X86PassDescriptor::new(
pass_ids::EARLY_CSE,
"early-cse",
"Early Common Subexpression Elimination",
)
.with_phase(PipelinePhase::MainIR),
);
self.register(
X86PassDescriptor::new(pass_ids::LICM, "licm", "Loop Invariant Code Motion")
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops"),
);
self.register(
X86PassDescriptor::new(pass_ids::LOOP_ROTATE, "loop-rotate", "Rotate Loops")
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops"),
);
self.register(
X86PassDescriptor::new(
pass_ids::LOOP_SIMPLIFY,
"loop-simplify",
"Canonicalize Natural Loops",
)
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops"),
);
self.register(
X86PassDescriptor::new(
pass_ids::INDVARS,
"indvars",
"Canonicalize Induction Variables",
)
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops"),
);
self.register(
X86PassDescriptor::new(
pass_ids::LOOP_DELETION,
"loop-deletion",
"Delete Dead Loops",
)
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops"),
);
self.register(
X86PassDescriptor::new(pass_ids::LOOP_UNROLL, "loop-unroll", "Unroll Loops")
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops"),
);
self.register(
X86PassDescriptor::new(pass_ids::INLINE, "inline", "Function Integration/Inlining")
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(pass_ids::JUMP_THREADING, "jump-threading", "Jump Threading")
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(
pass_ids::CORRELATED_PROPAGATION,
"correlated-propagation",
"Correlated Value Propagation",
)
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(pass_ids::DSE, "dse", "Dead Store Elimination")
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(pass_ids::ADCE, "adce", "Aggressive Dead Code Elimination")
.with_phase(PipelinePhase::LateIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(
pass_ids::SLP_VECTORIZER,
"slp-vectorizer",
"Superword-Level Parallelism Vectorizer",
)
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(
pass_ids::LOOP_VECTORIZE,
"loop-vectorize",
"Loop Vectorizer",
)
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops")
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(
pass_ids::LOOP_UNROLL_AND_JAM,
"loop-unroll-and-jam",
"Loop Unroll and Jam",
)
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops")
.with_opt_levels(vec![OptimizationLevel::O2, OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(
pass_ids::GLOBALOPT,
"globalopt",
"Global Variable Optimizer",
)
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(
pass_ids::IPSCCP,
"ipsccp",
"Interprocedural Sparse Conditional Constant Propagation",
)
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(
pass_ids::DEADARGELIM,
"deadargelim",
"Dead Argument Elimination",
)
.with_phase(PipelinePhase::LateIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(
pass_ids::GLOBALDCE,
"globaldce",
"Global Dead Code Elimination",
)
.with_phase(PipelinePhase::LateIR)
.with_opt_levels(vec![
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
]),
);
self.register(
X86PassDescriptor::new(pass_ids::ARGPROMOTION, "argpromotion", "Argument Promotion")
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(
pass_ids::TAILCALLELIM,
"tailcallelim",
"Tail Call Elimination",
)
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(pass_ids::MERGEFUNC, "mergefunc", "Merge Similar Functions")
.with_phase(PipelinePhase::LateIR)
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(
pass_ids::LOOP_INTERCHANGE,
"loop-interchange",
"Loop Interchange",
)
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops")
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(pass_ids::LOOP_UNSWITCH, "loop-unswitch", "Loop Unswitching")
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops")
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(
pass_ids::LOOP_DISTRIBUTE,
"loop-distribute",
"Loop Distribution",
)
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops")
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(pass_ids::LOOP_FUSION, "loop-fusion", "Loop Fusion")
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops")
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(
pass_ids::LOOP_VERSIONING_LICM,
"loop-versioning-licm",
"Loop Versioning LICM",
)
.with_phase(PipelinePhase::MainIR)
.with_dependency("loops")
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(
pass_ids::AGGRESSIVE_INSTCOMBINE,
"aggressive-instcombine",
"Aggressive Instruction Combining",
)
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![OptimizationLevel::O3]),
);
self.register(
X86PassDescriptor::new(
pass_ids::PGO_MEMOP_OPT,
"pgo-memop-opt",
"PGO Memory Operation Optimization",
)
.with_phase(PipelinePhase::MainIR)
.with_opt_levels(vec![OptimizationLevel::O3]),
);
}
fn register_machine_passes(&mut self) {
self.register(
X86PassDescriptor::new(
pass_ids::PHI_ELIMINATION,
"phi-elimination",
"Eliminate PHI Nodes",
)
.with_phase(PipelinePhase::PreRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::TWO_ADDRESS_INSTRUCTION,
"twoaddressinstruction",
"Two-Address Instruction Pass",
)
.with_phase(PipelinePhase::PreRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::MACHINE_CSE,
"machine-cse",
"Machine Common Subexpression Elimination",
)
.with_phase(PipelinePhase::PreRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::MACHINE_LICM,
"machine-licm",
"Machine Loop Invariant Code Motion",
)
.with_phase(PipelinePhase::PreRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::MACHINE_SINK,
"machine-sink",
"Machine Instruction Sinking",
)
.with_phase(PipelinePhase::PreRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::PEEPHOLE_OPT,
"peephole-opt",
"Peephole Optimizations",
)
.with_phase(PipelinePhase::PreRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::MACHINE_CP,
"machine-cp",
"Machine Copy Propagation",
)
.with_phase(PipelinePhase::PreRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::MACHINE_DCE,
"machine-dce",
"Machine Dead Code Elimination",
)
.with_phase(PipelinePhase::PreRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::POST_RA_SCHEDULER,
"post-RA-scheduler",
"Post-Register-Allocation Scheduler",
)
.with_phase(PipelinePhase::PostRA),
);
self.register(
X86PassDescriptor::new(pass_ids::BRANCH_FOLDING, "branch-folding", "Branch Folding")
.with_phase(PipelinePhase::PostRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::TAIL_DUPLICATION,
"tail-duplication",
"Tail Duplication",
)
.with_phase(PipelinePhase::PostRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::MACHINE_BLOCK_PLACEMENT,
"machine-block-placement",
"Machine Block Placement",
)
.with_phase(PipelinePhase::PostRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::POST_RA_MACHINE_CP,
"post-RA-machine-cp",
"Post-RA Machine Copy Propagation",
)
.with_phase(PipelinePhase::PostRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::POST_RA_MACHINE_SINK,
"post-RA-machine-sink",
"Post-RA Machine Sinking",
)
.with_phase(PipelinePhase::PostRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::POST_RA_MACHINE_DCE,
"post-RA-machine-dce",
"Post-RA Machine Dead Code Elimination",
)
.with_phase(PipelinePhase::PostRA),
);
self.register(
X86PassDescriptor::new(
pass_ids::MACHINE_OUTLINER,
"machine-outliner",
"Machine Outliner",
)
.with_phase(PipelinePhase::PostRA)
.with_opt_levels(vec![OptimizationLevel::Os, OptimizationLevel::Oz]),
);
}
fn register_regalloc_passes(&mut self) {
self.register(
X86PassDescriptor::new(
pass_ids::REG_ALLOC_GREEDY,
"greedy-regalloc",
"Greedy Register Allocator",
)
.with_phase(PipelinePhase::RegAlloc),
);
self.register(
X86PassDescriptor::new(
pass_ids::REG_ALLOC_PBQP,
"pbqp-regalloc",
"PBQP Register Allocator",
)
.with_phase(PipelinePhase::RegAlloc),
);
self.register(
X86PassDescriptor::new(
pass_ids::REG_ALLOC_FAST,
"fast-regalloc",
"Fast Register Allocator",
)
.with_phase(PipelinePhase::RegAlloc)
.with_opt_levels(vec![OptimizationLevel::O0]),
);
self.register(
X86PassDescriptor::new(
pass_ids::REG_ALLOC_BASIC,
"basic-regalloc",
"Basic Register Allocator",
)
.with_phase(PipelinePhase::RegAlloc),
);
}
fn register_x86_specific_passes(&mut self) {
self.register(
X86PassDescriptor::new(
pass_ids::X86_DAG_ISEL,
"x86-dag-isel",
"X86 DAG-Based Instruction Selection",
)
.with_phase(PipelinePhase::Lowering)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_FAST_ISEL,
"x86-fast-isel",
"X86 Fast Instruction Selection",
)
.with_phase(PipelinePhase::Lowering)
.with_x86_specific()
.with_opt_levels(vec![OptimizationLevel::O0]),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_GLOBAL_ISEL,
"x86-global-isel",
"X86 GlobalISel Pipeline",
)
.with_phase(PipelinePhase::Lowering)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_FLOATING_POINT,
"x86-fp-stack",
"X86 Floating-Point Stackifier",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_FIXUP_LEA,
"x86-fixup-lea",
"X86 LEA Optimization",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_FIXUP_BW,
"x86-fixup-bw",
"X86 Byte/Word Operation Fixup",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_FIXUP_SETCC,
"x86-fixup-setcc",
"X86 SETCC Optimization",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_PAD_SHORT_FUNCTION,
"x86-pad-short-func",
"X86 NOP Padding for Short Functions",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_CALL_FRAME_OPT,
"x86-call-frame-opt",
"X86 Call Frame Optimization",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_CMOV_CONVERSION,
"x86-cmov-conversion",
"X86 CMOV-to-Branch Conversion",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_DOMAIN_REASSIGN,
"x86-domain-reassign",
"X86 FP/Integer Domain Reassignment",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_AVOID_STORE_FORWARDING,
"x86-avoid-sf",
"X86 Avoid Store-Forwarding Stalls",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_VZERO_UPPER,
"x86-vzeroupper",
"X86 VZEROUPPER Insertion",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::X86_FIXUP_VECTOR_CONSTANTS,
"x86-fixup-vector-const",
"X86 Vector Constant Pool Optimization",
)
.with_phase(PipelinePhase::X86Fixup)
.with_x86_specific(),
);
self.register(
X86PassDescriptor::new(
pass_ids::OUTLINE_FUNCTIONS,
"outline-functions",
"Outline Functions for Size",
)
.with_phase(PipelinePhase::LateIR)
.with_opt_levels(vec![OptimizationLevel::Oz]),
);
self.register(
X86PassDescriptor::new(
pass_ids::MERGE_CONSTANTS,
"merge-constants",
"Merge Identical Constants",
)
.with_phase(PipelinePhase::LateIR)
.with_opt_levels(vec![OptimizationLevel::Oz]),
);
}
pub fn get(&self, id: u64) -> Option<&X86PassDescriptor> {
self.descriptors.get(&id)
}
pub fn get_by_name(&self, name: &str) -> Option<&X86PassDescriptor> {
self.name_index
.get(name)
.and_then(|id| self.descriptors.get(id))
}
pub fn id_for_name(&self, name: &str) -> Option<u64> {
self.name_index.get(name).copied()
}
pub fn has(&self, id: u64) -> bool {
self.descriptors.contains_key(&id)
}
pub fn pipeline_order(&self) -> &[u64] {
&self.pipeline_order
}
pub fn passes_for_opt_level(&self, level: OptimizationLevel) -> Vec<u64> {
let mut result = Vec::new();
let phases = [
PipelinePhase::EarlyIR,
PipelinePhase::MainIR,
PipelinePhase::LateIR,
PipelinePhase::Lowering,
PipelinePhase::PreRA,
PipelinePhase::RegAlloc,
PipelinePhase::PostRA,
PipelinePhase::X86Fixup,
];
for phase in &phases {
if let Some(ids) = self.phase_index.get(phase) {
for id in ids {
if let Some(desc) = self.descriptors.get(id) {
if desc.enabled_at(level) {
result.push(*id);
}
}
}
}
}
result
}
pub fn len(&self) -> usize {
self.descriptors.len()
}
pub fn is_empty(&self) -> bool {
self.descriptors.is_empty()
}
pub fn all_names(&self) -> Vec<String> {
self.name_index.keys().cloned().collect()
}
pub fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
for (id, desc) in &self.descriptors {
for dep in &desc.dependencies {
if !self.name_index.contains_key(dep.as_str()) {
errors.push(format!(
"Pass '{}' (id={}) depends on unregistered pass '{}'",
desc.name, id, dep
));
}
}
}
let mut in_degree: HashMap<String, usize> = HashMap::new();
let mut graph: HashMap<String, Vec<String>> = HashMap::new();
for (_, desc) in &self.descriptors {
let name = &desc.name;
in_degree.entry(name.clone()).or_insert(0);
graph.entry(name.clone()).or_default();
for dep in &desc.dependencies {
graph.entry(dep.clone()).or_default().push(name.clone());
*in_degree.entry(name.clone()).or_default() += 1;
}
}
let mut queue: VecDeque<String> = in_degree
.iter()
.filter(|&(_, °)| deg == 0)
.map(|(name, _)| name.clone())
.collect();
let mut sorted = 0;
while let Some(node) = queue.pop_front() {
sorted += 1;
if let Some(deps) = graph.get(&node) {
for next in deps {
if let Some(deg) = in_degree.get_mut(next) {
*deg -= 1;
if *deg == 0 {
queue.push_back(next.clone());
}
}
}
}
}
if sorted < in_degree.len() {
errors.push("Circular dependency detected in pass pipeline".to_string());
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
impl Default for X86PassRegistry {
fn default() -> Self {
let mut reg = Self::new();
reg.register_all_defaults();
reg
}
}
#[derive(Debug, Clone, Default)]
pub struct PassTimingRecord {
pub total_duration: Duration,
pub invocation_count: u64,
pub change_count: u64,
pub min_duration: Option<Duration>,
pub max_duration: Option<Duration>,
pub last_duration: Option<Duration>,
sum_squares_ns: u128,
}
impl PassTimingRecord {
pub fn record(&mut self, duration: Duration, changed: bool) {
self.total_duration += duration;
self.invocation_count += 1;
if changed {
self.change_count += 1;
}
self.last_duration = Some(duration);
let ns = duration.as_nanos();
self.sum_squares_ns += ns * ns;
self.min_duration = Some(match self.min_duration {
Some(current) => current.min(duration),
None => duration,
});
self.max_duration = Some(match self.max_duration {
Some(current) => current.max(duration),
None => duration,
});
}
pub fn avg_ms(&self) -> f64 {
if self.invocation_count == 0 {
return 0.0;
}
self.total_duration.as_secs_f64() * 1000.0 / self.invocation_count as f64
}
pub fn total_ms(&self) -> f64 {
self.total_duration.as_secs_f64() * 1000.0
}
pub fn std_dev_ms(&self) -> f64 {
if self.invocation_count < 2 {
return 0.0;
}
let n = self.invocation_count as f64;
let mean_ns = self.total_duration.as_nanos() as f64 / n;
let variance = (self.sum_squares_ns as f64 / n) - (mean_ns * mean_ns);
if variance > 0.0 {
variance.sqrt() / 1_000_000.0
} else {
0.0
}
}
pub fn change_rate(&self) -> f64 {
if self.invocation_count == 0 {
return 0.0;
}
self.change_count as f64 / self.invocation_count as f64 * 100.0
}
}
pub struct X86PassTiming {
pub enabled: bool,
records: HashMap<u64, PassTimingRecord>,
current_pass: Option<(u64, Instant)>,
pub total_pipeline_time: Duration,
phase_timings: HashMap<PipelinePhase, Duration>,
registry: X86PassRegistry,
labels: HashMap<u64, String>,
}
impl X86PassTiming {
pub fn new(enabled: bool) -> Self {
Self {
enabled,
records: HashMap::new(),
current_pass: None,
total_pipeline_time: Duration::default(),
phase_timings: HashMap::new(),
registry: X86PassRegistry::default(),
labels: HashMap::new(),
}
}
pub fn with_registry(mut self, registry: X86PassRegistry) -> Self {
self.registry = registry;
self
}
pub fn start_pass(&mut self, pass_id: u64) {
if !self.enabled {
return;
}
self.current_pass = Some((pass_id, Instant::now()));
}
pub fn stop_pass(&mut self, changed: bool) {
if !self.enabled {
return;
}
if let Some((pass_id, start)) = self.current_pass.take() {
let duration = start.elapsed();
let record = self.records.entry(pass_id).or_default();
record.record(duration, changed);
if let Some(desc) = self.registry.get(pass_id) {
*self.phase_timings.entry(desc.phase).or_default() += duration;
}
}
}
pub fn record_pipeline_time(&mut self, duration: Duration) {
self.total_pipeline_time = duration;
}
pub fn get_record(&self, pass_id: u64) -> Option<&PassTimingRecord> {
self.records.get(&pass_id)
}
pub fn time_for_pass_name(&self, name: &str) -> Option<Duration> {
let id = self.registry.id_for_name(name)?;
self.records.get(&id).map(|r| r.total_duration)
}
pub fn set_label(&mut self, pass_id: u64, label: &str) {
self.labels.insert(pass_id, label.to_string());
}
pub fn report(&self) -> String {
if !self.enabled {
return "Pass timing is disabled.\n".to_string();
}
let mut entries: Vec<(u64, &PassTimingRecord)> =
self.records.iter().map(|(k, v)| (*k, v)).collect();
entries.sort_by(|a, b| b.1.total_duration.cmp(&a.1.total_duration));
let mut report = String::new();
report.push_str(
"╔══════════════════════════════════════════════════════════════════════════════╗\n",
);
report.push_str(
"║ X86 Pass Timing Report ║\n",
);
report.push_str(
"╠══════════════════════════════════════════════════════════════════════════════╣\n",
);
report.push_str(&format!(
"║ {:<30} {:>10} {:>8} {:>10} {:>10} {:>8} ║\n",
"Pass", "Total(ms)", "Count", "Avg(ms)", "Max(ms)", "Chg%"
));
report.push_str(
"╠══════════════════════════════════════════════════════════════════════════════╣\n",
);
for (id, record) in &entries {
let name = self.pass_display_name(*id);
report.push_str(&format!(
"║ {:<30} {:>10.3} {:>8} {:>10.3} {:>10.3} {:>7.1}% ║\n",
truncate_str(&name, 30),
record.total_ms(),
record.invocation_count,
record.avg_ms(),
record
.max_duration
.map(|d| d.as_secs_f64() * 1000.0)
.unwrap_or(0.0),
record.change_rate(),
));
}
report.push_str(
"╠══════════════════════════════════════════════════════════════════════════════╣\n",
);
let phases = [
PipelinePhase::EarlyIR,
PipelinePhase::MainIR,
PipelinePhase::LateIR,
PipelinePhase::Lowering,
PipelinePhase::PreRA,
PipelinePhase::RegAlloc,
PipelinePhase::PostRA,
PipelinePhase::X86Fixup,
];
report.push_str(
"║ Phase Summary: ║\n",
);
for phase in &phases {
let dur = self.phase_timings.get(phase).copied().unwrap_or_default();
let pct = if self.total_pipeline_time.as_nanos() > 0 {
dur.as_nanos() as f64 / self.total_pipeline_time.as_nanos() as f64 * 100.0
} else {
0.0
};
report.push_str(&format!(
"║ {:<25} {:>10.3} ms ({:>5.1}%)\n",
phase.name(),
dur.as_secs_f64() * 1000.0,
pct,
));
}
report.push_str(
"╚══════════════════════════════════════════════════════════════════════════════╝\n",
);
report.push_str(&format!(
"Total pipeline time: {:.3} ms\n",
self.total_pipeline_time.as_secs_f64() * 1000.0
));
report
}
pub fn compact_report(&self) -> String {
if !self.enabled {
return "Pass timing is disabled.\n".to_string();
}
let mut entries: Vec<(u64, &PassTimingRecord)> =
self.records.iter().map(|(k, v)| (*k, v)).collect();
entries.sort_by(|a, b| b.1.total_duration.cmp(&a.1.total_duration));
let mut report = String::from("--- Pass Timing (total_ms, count) ---\n");
for (id, record) in &entries {
let name = self.pass_display_name(*id);
report.push_str(&format!(
" {}: {:.3} ms ({}x)\n",
name,
record.total_ms(),
record.invocation_count,
));
}
report.push_str(&format!(
"Total: {:.3} ms\n",
self.total_pipeline_time.as_secs_f64() * 1000.0
));
report
}
pub fn ordering_analysis(&self) -> Vec<(String, f64, f64)> {
let mut results: Vec<(String, f64, f64)> = self
.records
.iter()
.map(|(id, rec)| {
let name = self.pass_display_name(*id);
(name, rec.total_ms(), rec.avg_ms())
})
.collect();
results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
results
}
pub fn reset(&mut self) {
self.records.clear();
self.current_pass = None;
self.total_pipeline_time = Duration::default();
self.phase_timings.clear();
}
pub fn merge(&mut self, other: &X86PassTiming) {
for (id, record) in &other.records {
let entry = self.records.entry(*id).or_default();
entry.total_duration += record.total_duration;
entry.invocation_count += record.invocation_count;
entry.change_count += record.change_count;
entry.sum_squares_ns += record.sum_squares_ns;
entry.min_duration = match (entry.min_duration, record.min_duration) {
(Some(a), Some(b)) => Some(a.min(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
};
entry.max_duration = match (entry.max_duration, record.max_duration) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
};
entry.last_duration = record.last_duration.or(entry.last_duration);
}
self.total_pipeline_time += other.total_pipeline_time;
for (phase, dur) in &other.phase_timings {
*self.phase_timings.entry(*phase).or_default() += *dur;
}
}
fn pass_display_name(&self, id: u64) -> String {
if let Some(label) = self.labels.get(&id) {
return label.clone();
}
if let Some(desc) = self.registry.get(id) {
return desc.name.clone();
}
format!("pass-0x{:04X}", id)
}
}
impl Default for X86PassTiming {
fn default() -> Self {
Self::new(false)
}
}
fn truncate_str(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len.saturating_sub(3)])
}
}
pub type BeforePassCallback = Box<dyn Fn(u64, &str, &str) + Send + Sync>;
pub type AfterPassCallback = Box<dyn Fn(u64, &str, &str, bool) + Send + Sync>;
pub struct X86PassInstrumentation {
before_callbacks: Vec<BeforePassCallback>,
after_callbacks: Vec<AfterPassCallback>,
pub verify_each: bool,
pub debug_before: bool,
pub debug_after: bool,
pub debug_only_changed: bool,
skip_verify_for: HashSet<String>,
verification_errors: Vec<String>,
pub pass_count: AtomicU64,
pub changed_count: AtomicU64,
pub enabled: bool,
}
impl X86PassInstrumentation {
pub fn new() -> Self {
Self {
before_callbacks: Vec::new(),
after_callbacks: Vec::new(),
verify_each: false,
debug_before: false,
debug_after: false,
debug_only_changed: true,
skip_verify_for: HashSet::new(),
verification_errors: Vec::new(),
pass_count: AtomicU64::new(0),
changed_count: AtomicU64::new(0),
enabled: true,
}
}
pub fn on_before_pass<F>(&mut self, callback: F)
where
F: Fn(u64, &str, &str) + Send + Sync + 'static,
{
self.before_callbacks.push(Box::new(callback));
}
pub fn on_after_pass<F>(&mut self, callback: F)
where
F: Fn(u64, &str, &str, bool) + Send + Sync + 'static,
{
self.after_callbacks.push(Box::new(callback));
}
pub fn enable_verify_each(&mut self) {
self.verify_each = true;
}
pub fn enable_debug_before(&mut self) {
self.debug_before = true;
}
pub fn enable_debug_after(&mut self) {
self.debug_after = true;
}
pub fn enable_all_debug(&mut self) {
self.enable_verify_each();
self.enable_debug_before();
self.enable_debug_after();
self.debug_only_changed = false;
}
pub fn skip_verify_for(&mut self, pass_name: &str) {
self.skip_verify_for.insert(pass_name.to_string());
}
pub fn before_pass(&self, pass_id: u64, pass_name: &str, ir_description: &str) {
if !self.enabled {
return;
}
for cb in &self.before_callbacks {
cb(pass_id, pass_name, ir_description);
}
}
pub fn after_pass(&self, pass_id: u64, pass_name: &str, ir_description: &str, changed: bool) {
if !self.enabled {
return;
}
for cb in &self.after_callbacks {
cb(pass_id, pass_name, ir_description, changed);
}
}
pub fn should_verify(&self, pass_name: &str) -> bool {
self.verify_each && !self.skip_verify_for.contains(pass_name)
}
pub fn record_verification_error(&mut self, pass_name: &str, error: &str) {
self.verification_errors
.push(format!("After pass '{}': {}", pass_name, error));
}
pub fn clear_verification_errors(&mut self) {
self.verification_errors.clear();
}
pub fn get_verification_errors(&self) -> &[String] {
&self.verification_errors
}
pub fn has_verification_errors(&self) -> bool {
!self.verification_errors.is_empty()
}
pub fn record_pass_execution(&self, changed: bool) {
self.pass_count.fetch_add(1, Ordering::Relaxed);
if changed {
self.changed_count.fetch_add(1, Ordering::Relaxed);
}
}
pub fn debug_header(&self, pass_name: &str, before: bool) -> String {
let timing = if before { "BEFORE" } else { "AFTER" };
format!(
"╔══ {} PASS: {} ═══════════════════════════════════════════════════╗",
timing, pass_name,
)
}
pub fn debug_footer(&self, pass_name: &str) -> String {
format!(
"╚══ END PASS: {} ═══════════════════════════════════════════════════╝",
pass_name,
)
}
pub fn reset(&mut self) {
self.verification_errors.clear();
self.pass_count.store(0, Ordering::Relaxed);
self.changed_count.store(0, Ordering::Relaxed);
}
pub fn summary(&self) -> String {
format!(
"Passes executed: {}, Changed: {}, Verification errors: {}",
self.pass_count.load(Ordering::Relaxed),
self.changed_count.load(Ordering::Relaxed),
self.verification_errors.len(),
)
}
}
impl Default for X86PassInstrumentation {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86PassContext {
pub opt_level: OptimizationLevel,
pub is_64bit: bool,
pub subtarget_features: Vec<String>,
pub enable_machine_outliner: bool,
pub prefer_size: bool,
pub max_unroll_threshold: u32,
pub inline_threshold: u32,
pub has_pgo_data: bool,
}
impl Default for X86PassContext {
fn default() -> Self {
Self {
opt_level: OptimizationLevel::O2,
is_64bit: true,
subtarget_features: Vec::new(),
enable_machine_outliner: false,
prefer_size: false,
max_unroll_threshold: 300,
inline_threshold: 225,
has_pgo_data: false,
}
}
}
impl X86PassContext {
pub fn for_opt_level(level: OptimizationLevel) -> Self {
let (prefer_size, max_unroll, inline_thresh) = match level {
OptimizationLevel::O0 => (false, 0, 0),
OptimizationLevel::O1 => (false, 150, 100),
OptimizationLevel::O2 => (false, 300, 225),
OptimizationLevel::O3 => (false, 600, 275),
OptimizationLevel::Os => (true, 50, 75),
OptimizationLevel::Oz => (true, 0, 25),
};
Self {
opt_level: level,
prefer_size,
max_unroll_threshold: max_unroll,
inline_threshold: inline_thresh,
enable_machine_outliner: matches!(level, OptimizationLevel::Os | OptimizationLevel::Oz),
..Default::default()
}
}
pub fn with_32bit(mut self) -> Self {
self.is_64bit = false;
self
}
pub fn with_features(mut self, features: Vec<String>) -> Self {
self.subtarget_features = features;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct X86PipelineStats {
pub ir_passes_executed: u64,
pub machine_passes_executed: u64,
pub functions_processed: u64,
pub blocks_processed: u64,
pub instructions_eliminated: u64,
pub spills: u64,
pub reloads: u64,
pub code_size_bytes: u64,
pub success: bool,
pub errors: Vec<String>,
}
pub struct X86PassManager {
pub registry: X86PassRegistry,
pub timing: X86PassTiming,
pub instrumentation: X86PassInstrumentation,
pub context: X86PassContext,
pub stats: X86PipelineStats,
pipeline: Vec<u64>,
built: bool,
pub reg_alloc_strategy: RegAllocStrategy,
pub use_global_isel: bool,
pub use_fast_isel: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegAllocStrategy {
Greedy,
PBQP,
Fast,
Basic,
}
impl RegAllocStrategy {
pub fn for_opt_level(level: OptimizationLevel) -> Self {
match level {
OptimizationLevel::O0 => RegAllocStrategy::Fast,
_ => RegAllocStrategy::Greedy,
}
}
pub fn name(&self) -> &'static str {
match self {
RegAllocStrategy::Greedy => "greedy",
RegAllocStrategy::PBQP => "pbqp",
RegAllocStrategy::Fast => "fast",
RegAllocStrategy::Basic => "basic",
}
}
pub fn pass_id(&self) -> u64 {
match self {
RegAllocStrategy::Greedy => pass_ids::REG_ALLOC_GREEDY,
RegAllocStrategy::PBQP => pass_ids::REG_ALLOC_PBQP,
RegAllocStrategy::Fast => pass_ids::REG_ALLOC_FAST,
RegAllocStrategy::Basic => pass_ids::REG_ALLOC_BASIC,
}
}
}
impl Default for RegAllocStrategy {
fn default() -> Self {
RegAllocStrategy::Greedy
}
}
impl X86PassManager {
pub fn new(context: X86PassContext) -> Self {
let mut timing = X86PassTiming::new(false);
timing = timing.with_registry(X86PassRegistry::default());
let opt_level = context.opt_level;
let use_fast_isel = opt_level == OptimizationLevel::O0;
Self {
registry: X86PassRegistry::default(),
timing,
instrumentation: X86PassInstrumentation::new(),
context,
stats: X86PipelineStats::default(),
pipeline: Vec::new(),
built: false,
reg_alloc_strategy: RegAllocStrategy::for_opt_level(opt_level),
use_global_isel: false,
use_fast_isel,
}
}
pub fn new_o0() -> Self {
Self::new(X86PassContext::for_opt_level(OptimizationLevel::O0))
}
pub fn new_o1() -> Self {
Self::new(X86PassContext::for_opt_level(OptimizationLevel::O1))
}
pub fn new_o2() -> Self {
Self::new(X86PassContext::for_opt_level(OptimizationLevel::O2))
}
pub fn new_o3() -> Self {
Self::new(X86PassContext::for_opt_level(OptimizationLevel::O3))
}
pub fn new_os() -> Self {
Self::new(X86PassContext::for_opt_level(OptimizationLevel::Os))
}
pub fn new_oz() -> Self {
Self::new(X86PassContext::for_opt_level(OptimizationLevel::Oz))
}
pub fn with_timing(mut self) -> Self {
self.timing.enabled = true;
self
}
pub fn with_verify_each(mut self) -> Self {
self.instrumentation.enable_verify_each();
self
}
pub fn with_debug(mut self) -> Self {
self.instrumentation.enable_all_debug();
self
}
pub fn with_global_isel(mut self) -> Self {
self.use_global_isel = true;
self.use_fast_isel = false;
self
}
pub fn with_fast_isel(mut self) -> Self {
self.use_fast_isel = true;
self.use_global_isel = false;
self
}
pub fn with_reg_alloc(mut self, strategy: RegAllocStrategy) -> Self {
self.reg_alloc_strategy = strategy;
self
}
pub fn build_pipeline(&mut self) {
if self.built {
return;
}
let level = self.context.opt_level;
self.pipeline.clear();
self.build_ir_pipeline(level);
self.build_isel_pipeline(level);
self.build_machine_pipeline(level);
self.build_x86_fixup_pipeline(level);
self.built = true;
}
fn build_ir_pipeline(&mut self, level: OptimizationLevel) {
match level {
OptimizationLevel::O0 => self.build_o0_ir(),
OptimizationLevel::O1 => self.build_o1_ir(),
OptimizationLevel::O2 => self.build_o2_ir(),
OptimizationLevel::O3 => self.build_o3_ir(),
OptimizationLevel::Os => self.build_os_ir(),
OptimizationLevel::Oz => self.build_oz_ir(),
}
}
fn build_o0_ir(&mut self) {
self.pipeline.push(pass_ids::MEM2REG);
}
fn build_o1_ir(&mut self) {
let passes = [
pass_ids::MEM2REG,
pass_ids::INSTCOMBINE,
pass_ids::SIMPLIFYCFG,
pass_ids::REASSOCIATE,
pass_ids::GVN,
pass_ids::SCCP,
pass_ids::EARLY_CSE,
pass_ids::LICM,
pass_ids::LOOP_ROTATE,
pass_ids::LOOP_SIMPLIFY,
pass_ids::INDVARS,
pass_ids::LOOP_DELETION,
pass_ids::LOOP_UNROLL,
];
for p in &passes {
if self.is_pass_enabled(*p, OptimizationLevel::O1) {
self.pipeline.push(*p);
}
}
}
fn build_o2_ir(&mut self) {
self.build_o1_ir();
let o2_passes = [
pass_ids::INLINE,
pass_ids::SIMPLIFYCFG, pass_ids::JUMP_THREADING,
pass_ids::CORRELATED_PROPAGATION,
pass_ids::DSE,
pass_ids::SLP_VECTORIZER,
pass_ids::LOOP_VECTORIZE,
pass_ids::LOOP_UNROLL_AND_JAM,
pass_ids::GLOBALOPT,
pass_ids::IPSCCP,
pass_ids::ADCE,
pass_ids::DEADARGELIM,
pass_ids::GLOBALDCE,
];
for p in &o2_passes {
if self.is_pass_enabled(*p, OptimizationLevel::O2) {
self.pipeline.push(*p);
}
}
}
fn build_o3_ir(&mut self) {
self.build_o2_ir();
let o3_passes = [
pass_ids::ARGPROMOTION,
pass_ids::TAILCALLELIM,
pass_ids::AGGRESSIVE_INSTCOMBINE,
pass_ids::LOOP_INTERCHANGE,
pass_ids::LOOP_UNSWITCH,
pass_ids::LOOP_DISTRIBUTE,
pass_ids::LOOP_FUSION,
pass_ids::LOOP_VERSIONING_LICM,
pass_ids::PGO_MEMOP_OPT,
pass_ids::MERGEFUNC,
];
for p in &o3_passes {
if self.is_pass_enabled(*p, OptimizationLevel::O3) {
self.pipeline.push(*p);
}
}
}
fn build_os_ir(&mut self) {
let passes = [
pass_ids::MEM2REG,
pass_ids::INSTCOMBINE,
pass_ids::SIMPLIFYCFG,
pass_ids::REASSOCIATE,
pass_ids::GVN,
pass_ids::SCCP,
pass_ids::EARLY_CSE,
pass_ids::LICM,
pass_ids::LOOP_ROTATE,
pass_ids::LOOP_SIMPLIFY,
pass_ids::INDVARS,
pass_ids::LOOP_DELETION,
pass_ids::LOOP_UNROLL,
pass_ids::INLINE,
pass_ids::SIMPLIFYCFG,
pass_ids::JUMP_THREADING,
pass_ids::CORRELATED_PROPAGATION,
pass_ids::DSE,
pass_ids::ADCE,
pass_ids::GLOBALOPT,
pass_ids::IPSCCP,
pass_ids::DEADARGELIM,
pass_ids::GLOBALDCE,
pass_ids::MACHINE_OUTLINER,
];
for p in &passes {
if self.is_pass_enabled(*p, OptimizationLevel::Os) {
self.pipeline.push(*p);
}
}
}
fn build_oz_ir(&mut self) {
self.build_os_ir();
let oz_passes = [pass_ids::OUTLINE_FUNCTIONS, pass_ids::MERGE_CONSTANTS];
for p in &oz_passes {
if self.is_pass_enabled(*p, OptimizationLevel::Oz) {
self.pipeline.push(*p);
}
}
}
fn build_isel_pipeline(&mut self, level: OptimizationLevel) {
if self.use_global_isel {
self.pipeline.push(pass_ids::X86_GLOBAL_ISEL);
} else if self.use_fast_isel && level == OptimizationLevel::O0 {
self.pipeline.push(pass_ids::X86_FAST_ISEL);
} else {
self.pipeline.push(pass_ids::X86_DAG_ISEL);
}
}
fn build_machine_pipeline(&mut self, level: OptimizationLevel) {
if level != OptimizationLevel::O0 {
let pre_ra = [
pass_ids::PHI_ELIMINATION,
pass_ids::TWO_ADDRESS_INSTRUCTION,
pass_ids::MACHINE_CSE,
pass_ids::MACHINE_LICM,
pass_ids::MACHINE_SINK,
pass_ids::PEEPHOLE_OPT,
pass_ids::MACHINE_CP,
pass_ids::MACHINE_DCE,
];
for p in &pre_ra {
self.pipeline.push(*p);
}
}
self.pipeline.push(self.reg_alloc_strategy.pass_id());
let post_ra = [
pass_ids::POST_RA_SCHEDULER,
pass_ids::BRANCH_FOLDING,
pass_ids::TAIL_DUPLICATION,
pass_ids::MACHINE_BLOCK_PLACEMENT,
pass_ids::POST_RA_MACHINE_CP,
pass_ids::POST_RA_MACHINE_SINK,
pass_ids::POST_RA_MACHINE_DCE,
];
for p in &post_ra {
self.pipeline.push(*p);
}
if level == OptimizationLevel::Os || level == OptimizationLevel::Oz {
if !self.pipeline.contains(&pass_ids::MACHINE_OUTLINER) {
self.pipeline.push(pass_ids::MACHINE_OUTLINER);
}
}
}
fn build_x86_fixup_pipeline(&mut self, _level: OptimizationLevel) {
let x86_fixups = [
pass_ids::X86_FIXUP_LEA,
pass_ids::X86_FIXUP_BW,
pass_ids::X86_FIXUP_SETCC,
pass_ids::X86_CMOV_CONVERSION,
pass_ids::X86_DOMAIN_REASSIGN,
pass_ids::X86_AVOID_STORE_FORWARDING,
pass_ids::X86_FIXUP_VECTOR_CONSTANTS,
pass_ids::X86_CALL_FRAME_OPT,
pass_ids::X86_FLOATING_POINT,
pass_ids::X86_VZERO_UPPER,
pass_ids::X86_PAD_SHORT_FUNCTION,
];
for p in &x86_fixups {
self.pipeline.push(*p);
}
}
fn is_pass_enabled(&self, pass_id: u64, level: OptimizationLevel) -> bool {
if let Some(desc) = self.registry.get(pass_id) {
desc.enabled_at(level)
} else {
true }
}
pub fn pipeline(&self) -> &[u64] {
&self.pipeline
}
pub fn pipeline_listing(&self) -> String {
let mut listing = String::from("=== X86 Pipeline ===\n");
let mut current_phase = None;
for pass_id in &self.pipeline {
if let Some(desc) = self.registry.get(*pass_id) {
if current_phase != Some(desc.phase) {
current_phase = Some(desc.phase);
listing.push_str(&format!("\n-- {} --\n", desc.phase.name()));
}
listing.push_str(&format!(" [{}] {}\n", pass_id, desc.name));
} else {
listing.push_str(&format!(" [{}] <unknown>\n", pass_id));
}
}
listing.push_str(&format!("\nTotal passes: {}\n", self.pipeline.len()));
listing
}
pub fn run(&mut self) -> Result<(), Vec<String>> {
if !self.built {
self.build_pipeline();
}
let pipeline_start = Instant::now();
self.stats = X86PipelineStats::default();
let pipeline: Vec<u64> = self.pipeline.clone();
for &pass_id in &pipeline {
let pass_name = self.pass_name(pass_id);
let changed = self.run_single_pass(pass_id, &pass_name);
self.instrumentation.record_pass_execution(changed);
self.stats.ir_passes_executed += 1;
if self.instrumentation.has_verification_errors() {
let errors = self.instrumentation.get_verification_errors().to_vec();
self.stats.success = false;
self.stats.errors = errors.clone();
return Err(errors);
}
}
let pipeline_duration = pipeline_start.elapsed();
self.timing.record_pipeline_time(pipeline_duration);
self.stats.success = true;
Ok(())
}
fn run_single_pass(&mut self, pass_id: u64, pass_name: &str) -> bool {
self.instrumentation.before_pass(pass_id, pass_name, "");
if self.instrumentation.debug_before && !self.instrumentation.debug_only_changed {
eprintln!("{}", self.instrumentation.debug_header(pass_name, true));
}
self.timing.start_pass(pass_id);
let changed = match pass_id {
pass_ids::MEM2REG
| pass_ids::INSTCOMBINE
| pass_ids::SIMPLIFYCFG
| pass_ids::REASSOCIATE
| pass_ids::GVN
| pass_ids::SCCP
| pass_ids::EARLY_CSE
| pass_ids::LICM
| pass_ids::LOOP_ROTATE
| pass_ids::LOOP_SIMPLIFY
| pass_ids::INDVARS
| pass_ids::LOOP_DELETION
| pass_ids::LOOP_UNROLL
| pass_ids::INLINE
| pass_ids::JUMP_THREADING
| pass_ids::CORRELATED_PROPAGATION
| pass_ids::DSE
| pass_ids::ADCE
| pass_ids::SLP_VECTORIZER
| pass_ids::LOOP_VECTORIZE
| pass_ids::LOOP_UNROLL_AND_JAM
| pass_ids::GLOBALOPT
| pass_ids::IPSCCP
| pass_ids::DEADARGELIM
| pass_ids::GLOBALDCE
| pass_ids::ARGPROMOTION
| pass_ids::TAILCALLELIM
| pass_ids::MERGEFUNC
| pass_ids::LOOP_INTERCHANGE
| pass_ids::LOOP_UNSWITCH
| pass_ids::LOOP_DISTRIBUTE
| pass_ids::LOOP_FUSION
| pass_ids::LOOP_VERSIONING_LICM
| pass_ids::AGGRESSIVE_INSTCOMBINE
| pass_ids::PGO_MEMOP_OPT
| pass_ids::OUTLINE_FUNCTIONS
| pass_ids::MERGE_CONSTANTS => self.run_ir_pass(pass_id, pass_name),
pass_ids::X86_DAG_ISEL => self.run_x86_dag_isel(),
pass_ids::X86_FAST_ISEL => self.run_x86_fast_isel(),
pass_ids::X86_GLOBAL_ISEL => self.run_x86_global_isel(),
pass_ids::PHI_ELIMINATION
| pass_ids::TWO_ADDRESS_INSTRUCTION
| pass_ids::MACHINE_CSE
| pass_ids::MACHINE_LICM
| pass_ids::MACHINE_SINK
| pass_ids::PEEPHOLE_OPT
| pass_ids::MACHINE_CP
| pass_ids::MACHINE_DCE
| pass_ids::POST_RA_SCHEDULER
| pass_ids::BRANCH_FOLDING
| pass_ids::TAIL_DUPLICATION
| pass_ids::MACHINE_BLOCK_PLACEMENT
| pass_ids::POST_RA_MACHINE_CP
| pass_ids::POST_RA_MACHINE_SINK
| pass_ids::POST_RA_MACHINE_DCE
| pass_ids::MACHINE_OUTLINER => self.run_machine_pass(pass_id, pass_name),
pass_ids::REG_ALLOC_GREEDY
| pass_ids::REG_ALLOC_PBQP
| pass_ids::REG_ALLOC_FAST
| pass_ids::REG_ALLOC_BASIC => self.run_regalloc_pass(pass_id, pass_name),
pass_ids::X86_FIXUP_LEA
| pass_ids::X86_FIXUP_BW
| pass_ids::X86_FIXUP_SETCC
| pass_ids::X86_PAD_SHORT_FUNCTION
| pass_ids::X86_CALL_FRAME_OPT
| pass_ids::X86_CMOV_CONVERSION
| pass_ids::X86_DOMAIN_REASSIGN
| pass_ids::X86_AVOID_STORE_FORWARDING
| pass_ids::X86_VZERO_UPPER
| pass_ids::X86_FIXUP_VECTOR_CONSTANTS
| pass_ids::X86_FLOATING_POINT => self.run_x86_fixup(pass_id, pass_name),
_ => {
eprintln!("Unknown pass ID: {}", pass_id);
false
}
};
self.timing.stop_pass(changed);
self.instrumentation
.after_pass(pass_id, pass_name, "", changed);
if self.instrumentation.debug_after && (!self.instrumentation.debug_only_changed || changed)
{
eprintln!("{}", self.instrumentation.debug_footer(pass_name));
}
changed
}
fn run_ir_pass(&mut self, pass_id: u64, _pass_name: &str) -> bool {
match pass_id {
pass_ids::LOOP_UNROLL if self.context.prefer_size => {
self.context.max_unroll_threshold <= 50
}
pass_ids::INLINE if self.context.opt_level == OptimizationLevel::Oz => {
self.context.inline_threshold <= 25
}
_ => pass_id % 3 == 0, }
}
fn run_x86_dag_isel(&self) -> bool {
true
}
fn run_x86_fast_isel(&self) -> bool {
true
}
fn run_x86_global_isel(&self) -> bool {
true
}
fn run_machine_pass(&self, pass_id: u64, _pass_name: &str) -> bool {
match pass_id {
pass_ids::MACHINE_OUTLINER if !self.context.enable_machine_outliner => false,
_ => pass_id % 5 == 0, }
}
fn run_regalloc_pass(&self, _pass_id: u64, _pass_name: &str) -> bool {
true
}
fn run_x86_fixup(&self, pass_id: u64, _pass_name: &str) -> bool {
match pass_id {
pass_ids::X86_VZERO_UPPER => {
self.context.subtarget_features.contains(&"avx".to_string())
}
pass_ids::X86_FIXUP_VECTOR_CONSTANTS => {
self.context.subtarget_features.contains(&"sse".to_string())
|| self.context.subtarget_features.contains(&"avx".to_string())
}
pass_ids::X86_CMOV_CONVERSION => {
self.context.prefer_size
}
_ => true,
}
}
fn pass_name(&self, id: u64) -> String {
self.registry
.get(id)
.map(|d| d.name.clone())
.unwrap_or_else(|| format!("pass-{:04X}", id))
}
pub fn describe_pipeline(&self) -> String {
self.pipeline_listing()
}
pub fn timing_report(&self) -> String {
self.timing.report()
}
pub fn reset(&mut self) {
self.pipeline.clear();
self.built = false;
self.stats = X86PipelineStats::default();
self.timing.reset();
self.instrumentation.reset();
}
}
pub struct X86PipelineBuilder {
manager: X86PassManager,
}
impl X86PipelineBuilder {
pub fn new(level: OptimizationLevel) -> Self {
Self {
manager: X86PassManager::new(X86PassContext::for_opt_level(level)),
}
}
pub fn with_timing(mut self) -> Self {
self.manager.timing.enabled = true;
self
}
pub fn with_verify_each(mut self) -> Self {
self.manager.instrumentation.enable_verify_each();
self
}
pub fn with_debug(mut self) -> Self {
self.manager.instrumentation.enable_all_debug();
self
}
pub fn with_global_isel(mut self) -> Self {
self.manager = self.manager.with_global_isel();
self
}
pub fn with_fast_isel(mut self) -> Self {
self.manager = self.manager.with_fast_isel();
self
}
pub fn with_reg_alloc(mut self, strategy: RegAllocStrategy) -> Self {
self.manager.reg_alloc_strategy = strategy;
self
}
pub fn with_32bit(mut self) -> Self {
self.manager.context.is_64bit = false;
self
}
pub fn with_features(mut self, features: Vec<String>) -> Self {
self.manager.context.subtarget_features = features;
self
}
pub fn build(mut self) -> X86PassManager {
self.manager.build_pipeline();
self.manager
}
}
pub fn create_o0_pipeline() -> X86PassManager {
X86PipelineBuilder::new(OptimizationLevel::O0)
.with_fast_isel()
.build()
}
pub fn create_o1_pipeline() -> X86PassManager {
X86PipelineBuilder::new(OptimizationLevel::O1).build()
}
pub fn create_o2_pipeline() -> X86PassManager {
X86PipelineBuilder::new(OptimizationLevel::O2).build()
}
pub fn create_o3_pipeline() -> X86PassManager {
X86PipelineBuilder::new(OptimizationLevel::O3).build()
}
pub fn create_os_pipeline() -> X86PassManager {
X86PipelineBuilder::new(OptimizationLevel::Os).build()
}
pub fn create_oz_pipeline() -> X86PassManager {
X86PipelineBuilder::new(OptimizationLevel::Oz).build()
}
pub fn run_x86_pipeline(level: OptimizationLevel) -> (bool, X86PipelineStats, String) {
let mut manager = match level {
OptimizationLevel::O0 => create_o0_pipeline(),
OptimizationLevel::O1 => create_o1_pipeline(),
OptimizationLevel::O2 => create_o2_pipeline(),
OptimizationLevel::O3 => create_o3_pipeline(),
OptimizationLevel::Os => create_os_pipeline(),
OptimizationLevel::Oz => create_oz_pipeline(),
};
let mut mgr = manager;
mgr.timing.enabled = true;
let result = mgr.run();
let stats = mgr.stats.clone();
let timing_report = mgr.timing.report();
(result.is_ok(), stats, timing_report)
}
pub fn dump_registered_passes() -> String {
let registry = X86PassRegistry::default();
let mut output = String::from("=== Registered X86 Passes ===\n\n");
let phases = [
PipelinePhase::EarlyIR,
PipelinePhase::MainIR,
PipelinePhase::LateIR,
PipelinePhase::Lowering,
PipelinePhase::PreRA,
PipelinePhase::RegAlloc,
PipelinePhase::PostRA,
PipelinePhase::X86Fixup,
PipelinePhase::Emission,
];
for phase in &phases {
output.push_str(&format!("--- {} ---\n", phase.name()));
output.push_str(&format!(
" {:<30} {:<8} {:<6} {:<6} {:<6} {:<6} {:<6} {:<6}\n",
"Name", "ID", "O0", "O1", "O2", "O3", "Os", "Oz"
));
output.push_str(&format!(" {}\n", "-".repeat(80)));
for id in registry.pipeline_order() {
if let Some(desc) = registry.get(*id) {
if desc.phase == *phase {
let levels = [
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
];
let enabled: Vec<&str> = levels
.iter()
.map(|l| if desc.enabled_at(*l) { "✓" } else { "·" })
.collect();
output.push_str(&format!(
" {:<30} {:<8} {:<6} {:<6} {:<6} {:<6} {:<6} {:<6}\n",
desc.name,
format!("{:04X}", desc.id),
enabled[0],
enabled[1],
enabled[2],
enabled[3],
enabled[4],
enabled[5],
));
}
}
}
output.push('\n');
}
output.push_str(&format!("Total passes: {}\n", registry.len()));
output
}
pub trait X86MachineFunctionPass {
fn name(&self) -> &'static str;
fn pass_id(&self) -> u64;
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool;
fn requires_liveness(&self) -> bool {
false
}
fn preserves_liveness(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct X86MachineFunctionState {
pub name: String,
pub is_64bit: bool,
pub blocks: Vec<MachineBlockState>,
pub vreg_count: u64,
pub preg_count: u64,
pub frame_size: u64,
pub has_calls: bool,
pub features: Vec<String>,
}
impl X86MachineFunctionState {
pub fn new(name: &str, is_64bit: bool) -> Self {
Self {
name: name.to_string(),
is_64bit,
blocks: Vec::new(),
vreg_count: 0,
preg_count: 0,
frame_size: 0,
has_calls: false,
features: Vec::new(),
}
}
pub fn total_instructions(&self) -> usize {
self.blocks.iter().map(|b| b.instructions.len()).sum()
}
}
#[derive(Debug, Clone)]
pub struct MachineBlockState {
pub id: usize,
pub name: String,
pub instructions: Vec<MachineInstruction>,
pub successors: Vec<usize>,
pub predecessors: Vec<usize>,
}
impl MachineBlockState {
pub fn new(id: usize, name: &str) -> Self {
Self {
id,
name: name.to_string(),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct MachineInstruction {
pub opcode: u32,
pub operands: Vec<MachineOperand>,
pub defs: Vec<usize>,
pub uses: Vec<usize>,
pub is_terminator: bool,
pub is_branch: bool,
pub is_call: bool,
pub is_move: bool,
pub may_load: bool,
pub may_store: bool,
pub size_bytes: u32,
}
impl MachineInstruction {
pub fn new(opcode: u32) -> Self {
Self {
opcode,
operands: Vec::new(),
defs: Vec::new(),
uses: Vec::new(),
is_terminator: false,
is_branch: false,
is_call: false,
is_move: false,
may_load: false,
may_store: false,
size_bytes: 0,
}
}
pub fn with_operands(mut self, ops: Vec<MachineOperand>) -> Self {
self.operands = ops;
self
}
pub fn with_defs(mut self, defs: Vec<usize>) -> Self {
self.defs = defs;
self
}
pub fn with_uses(mut self, uses: Vec<usize>) -> Self {
self.uses = uses;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MachineOperand {
Register(usize),
Immediate(i64),
Memory {
base: Option<usize>,
index: Option<usize>,
scale: u8,
displacement: i32,
},
Label(String),
Condition(u8),
}
pub struct X86FixupLEAPass {
pub instructions_optimized: u64,
pub bytes_saved: u64,
pub is_64bit: bool,
}
impl X86FixupLEAPass {
pub fn new(is_64bit: bool) -> Self {
Self {
instructions_optimized: 0,
bytes_saved: 0,
is_64bit,
}
}
pub fn can_simplify_lea(&self, instr: &MachineInstruction) -> bool {
if instr.opcode != 0x8D {
return false;
}
if instr.operands.len() < 2 {
return false;
}
if let Some(MachineOperand::Memory {
base: _base,
index,
scale,
displacement,
}) = instr.operands.get(1)
{
*displacement == 0 && index.is_none() && *scale == 1
} else {
false
}
}
pub fn estimate_savings(&self, instr: &MachineInstruction) -> u64 {
if self.can_simplify_lea(instr) {
4
} else {
0
}
}
}
impl X86MachineFunctionPass for X86FixupLEAPass {
fn name(&self) -> &'static str {
"x86-fixup-lea"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_FIXUP_LEA
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let mut changed = false;
for block in &mut mf.blocks {
for instr in &mut block.instructions {
if self.can_simplify_lea(instr) {
if let Some(reg) = match &instr.operands.get(1) {
Some(MachineOperand::Memory { base: Some(b), .. }) => Some(*b),
_ => None,
} {
instr.opcode = 0x89; instr.operands.truncate(2);
if instr.operands.first().is_some() {
instr.operands[1] = MachineOperand::Register(reg);
}
self.instructions_optimized += 1;
self.bytes_saved += self.estimate_savings(instr);
changed = true;
}
}
}
}
changed
}
}
pub struct X86FixupBWPass {
pub byte_ops_fixed: u64,
pub word_ops_fixed: u64,
pub partial_reg_stalls_prevented: u64,
}
impl X86FixupBWPass {
pub fn new() -> Self {
Self {
byte_ops_fixed: 0,
word_ops_fixed: 0,
partial_reg_stalls_prevented: 0,
}
}
pub fn could_cause_partial_stall(&self, instr: &MachineInstruction) -> bool {
instr.defs.iter().any(|d| *d < 8) && !instr.is_move
}
pub fn should_promote_byte_op(&self, instr: &MachineInstruction) -> bool {
instr.opcode < 0x40 && instr.defs.len() == 1
}
}
impl X86MachineFunctionPass for X86FixupBWPass {
fn name(&self) -> &'static str {
"x86-fixup-bw"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_FIXUP_BW
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let mut changed = false;
for block in &mut mf.blocks {
for instr in &mut block.instructions {
if self.could_cause_partial_stall(instr) {
self.partial_reg_stalls_prevented += 1;
changed = true;
}
if self.should_promote_byte_op(instr) {
self.byte_ops_fixed += 1;
changed = true;
}
}
}
changed
}
}
pub struct X86FixupSetCCPass {
pub setcc_optimized: u64,
pub xor_setcc_patterns: u64,
pub movzx_conversions: u64,
}
impl X86FixupSetCCPass {
pub fn new() -> Self {
Self {
setcc_optimized: 0,
xor_setcc_patterns: 0,
movzx_conversions: 0,
}
}
pub fn is_xor_setcc_pattern(&self, instrs: &[MachineInstruction], idx: usize) -> bool {
if idx == 0 {
return false;
}
let prev = &instrs[idx - 1];
let curr = &instrs[idx];
prev.opcode == 0x31
&& !prev.defs.is_empty()
&& !curr.defs.is_empty()
&& prev.defs[0] == curr.defs[0]
}
pub fn needs_movzx_after(&self, instr: &MachineInstruction) -> bool {
instr.opcode >= 0x90 && instr.opcode <= 0x9F && instr.defs.len() == 1
}
}
impl X86MachineFunctionPass for X86FixupSetCCPass {
fn name(&self) -> &'static str {
"x86-fixup-setcc"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_FIXUP_SETCC
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let mut changed = false;
for block in &mut mf.blocks {
let len = block.instructions.len();
for i in 0..len {
if self.is_xor_setcc_pattern(&block.instructions, i) {
self.xor_setcc_patterns += 1;
self.setcc_optimized += 1;
changed = true;
}
if self.needs_movzx_after(&block.instructions[i]) {
self.movzx_conversions += 1;
changed = true;
}
}
}
changed
}
}
pub struct X86PadShortFunctionPass {
pub functions_padded: u64,
pub nops_inserted: u64,
pub min_function_size: u32,
pub alignment: u32,
}
impl X86PadShortFunctionPass {
pub fn new() -> Self {
Self {
functions_padded: 0,
nops_inserted: 0,
min_function_size: 16,
alignment: 16,
}
}
pub fn padding_needed(&self, current_size: u32) -> u32 {
let target = current_size.max(self.min_function_size);
let remainder = target % self.alignment;
if remainder == 0 {
0
} else {
self.alignment - remainder
}
}
pub fn generate_nop_sequence(padding: u32) -> Vec<u8> {
match padding {
0 => vec![],
1 => vec![0x90],
2 => vec![0x66, 0x90],
3 => vec![0x0F, 0x1F, 0x00],
4 => vec![0x0F, 0x1F, 0x40, 0x00],
5 => vec![0x0F, 0x1F, 0x44, 0x00, 0x00],
6 => vec![0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00],
7 => vec![0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00],
8 => vec![0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
9 => {
let mut v = vec![0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00];
v.push(0x90);
v
}
_ => {
let mut v = vec![0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00];
let remaining = padding.saturating_sub(9);
v.extend(std::iter::repeat(0x90).take(remaining as usize));
v
}
}
}
}
impl X86MachineFunctionPass for X86PadShortFunctionPass {
fn name(&self) -> &'static str {
"x86-pad-short-func"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_PAD_SHORT_FUNCTION
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let current_size = mf.total_instructions() as u32;
let padding = self.padding_needed(current_size);
if padding > 0 {
self.functions_padded += 1;
self.nops_inserted += padding as u64;
true
} else {
false
}
}
}
pub struct X86CallFrameOptimizationPass {
pub leaf_functions_optimized: u64,
pub frame_pointer_eliminated: u64,
pub stack_adjustments_merged: u64,
pub push_pop_eliminated: u64,
}
impl X86CallFrameOptimizationPass {
pub fn new() -> Self {
Self {
leaf_functions_optimized: 0,
frame_pointer_eliminated: 0,
stack_adjustments_merged: 0,
push_pop_eliminated: 0,
}
}
pub fn is_leaf(&self, mf: &X86MachineFunctionState) -> bool {
!mf.has_calls
}
pub fn can_eliminate_frame_pointer(&self, mf: &X86MachineFunctionState) -> bool {
self.is_leaf(mf) && mf.frame_size < 128
}
pub fn find_mergeable_adjustments(&self, block: &MachineBlockState) -> Vec<(usize, usize)> {
let mut merges = Vec::new();
let mut i = 0;
while i + 1 < block.instructions.len() {
let a = &block.instructions[i];
let b = &block.instructions[i + 1];
if a.opcode == 0x81
&& b.opcode == 0x81
&& a.operands.len() >= 2
&& b.operands.len() >= 2
{
if let (MachineOperand::Immediate(_), MachineOperand::Immediate(_)) =
(&a.operands[1], &b.operands[1])
{
merges.push((i, i + 1));
}
}
i += 1;
}
merges
}
}
impl X86MachineFunctionPass for X86CallFrameOptimizationPass {
fn name(&self) -> &'static str {
"x86-call-frame-opt"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_CALL_FRAME_OPT
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let mut changed = false;
if self.can_eliminate_frame_pointer(mf) {
self.frame_pointer_eliminated += 1;
self.leaf_functions_optimized += 1;
changed = true;
}
for block in &mf.blocks {
let merges = self.find_mergeable_adjustments(block);
if !merges.is_empty() {
self.stack_adjustments_merged += merges.len() as u64;
changed = true;
}
}
changed
}
}
pub struct X86CmovConversionPass {
pub cmovs_converted: u64,
pub branches_created: u64,
pub mispredict_threshold: f64,
pub is_64bit: bool,
}
impl X86CmovConversionPass {
pub fn new(is_64bit: bool) -> Self {
Self {
cmovs_converted: 0,
branches_created: 0,
mispredict_threshold: 0.15,
is_64bit,
}
}
pub fn should_convert(&self, _instr: &MachineInstruction, branch_predictability: f64) -> bool {
branch_predictability < self.mispredict_threshold
|| branch_predictability > (1.0 - self.mispredict_threshold)
}
pub fn is_cmov(&self, instr: &MachineInstruction) -> bool {
instr.opcode >= 0x40 && instr.opcode <= 0x4F
}
pub fn cmov_to_branch_sequence(&self, instr: &MachineInstruction) -> Vec<MachineInstruction> {
let mut seq = Vec::new();
if self.is_cmov(instr) {
let mut jcc = MachineInstruction::new(0x0F);
jcc.is_branch = true;
jcc.is_terminator = false;
seq.push(jcc);
if let Some(dst) = instr.operands.first() {
if let Some(src) = instr.operands.get(1) {
let mut mov = MachineInstruction::new(0x89);
mov.operands = vec![dst.clone(), src.clone()];
mov.is_move = true;
seq.push(mov);
}
}
}
seq
}
}
impl X86MachineFunctionPass for X86CmovConversionPass {
fn name(&self) -> &'static str {
"x86-cmov-conversion"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_CMOV_CONVERSION
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let mut changed = false;
for block in &mut mf.blocks {
let mut new_instrs = Vec::new();
let mut converted = false;
for instr in &block.instructions {
if self.is_cmov(instr) && self.should_convert(instr, 0.95) {
let branch_seq = self.cmov_to_branch_sequence(instr);
new_instrs.extend(branch_seq);
self.cmovs_converted += 1;
self.branches_created += 1;
converted = true;
} else {
new_instrs.push(instr.clone());
}
}
if converted {
block.instructions = new_instrs;
changed = true;
}
}
changed
}
}
pub struct X86DomainReassignmentPass {
pub fp_to_int_reassignments: u64,
pub int_to_fp_reassignments: u64,
pub domain_penalties_saved: u64,
pub has_sse: bool,
pub has_avx: bool,
}
impl X86DomainReassignmentPass {
pub fn new(has_sse: bool, has_avx: bool) -> Self {
Self {
fp_to_int_reassignments: 0,
int_to_fp_reassignments: 0,
domain_penalties_saved: 0,
has_sse,
has_avx,
}
}
pub fn instruction_domain(&self, instr: &MachineInstruction) -> InstructionDomain {
match instr.opcode {
0x58..=0x5F => InstructionDomain::Float, 0x10..=0x17 => InstructionDomain::Float, 0x28..=0x2F => InstructionDomain::Float, 0x01..=0x3F => InstructionDomain::Integer, _ => InstructionDomain::Unknown,
}
}
pub fn count_domain_crossings(&self, instrs: &[MachineInstruction]) -> u64 {
let mut crossings = 0u64;
let mut prev_domain = InstructionDomain::Unknown;
for instr in instrs {
let domain = self.instruction_domain(instr);
if prev_domain != InstructionDomain::Unknown
&& domain != InstructionDomain::Unknown
&& prev_domain != domain
{
crossings += 1;
}
if domain != InstructionDomain::Unknown {
prev_domain = domain;
}
}
crossings
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstructionDomain {
Integer,
Float,
Unknown,
}
impl X86MachineFunctionPass for X86DomainReassignmentPass {
fn name(&self) -> &'static str {
"x86-domain-reassign"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_DOMAIN_REASSIGN
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let mut changed = false;
for block in &mf.blocks {
let crossings = self.count_domain_crossings(&block.instructions);
if crossings > 0 {
self.domain_penalties_saved += crossings;
changed = true;
}
}
changed
}
}
pub struct X86AvoidStoreForwardingStallsPass {
pub stalls_detected: u64,
pub stalls_prevented: u64,
pub nops_inserted: u64,
}
impl X86AvoidStoreForwardingStallsPass {
pub fn new() -> Self {
Self {
stalls_detected: 0,
stalls_prevented: 0,
nops_inserted: 0,
}
}
pub fn detect_store_forwarding_stall(
&self,
store: &MachineInstruction,
load: &MachineInstruction,
) -> bool {
if !store.may_store || !load.may_load {
return false;
}
let store_size = self.instruction_memory_size(store);
let load_size = self.instruction_memory_size(load);
store_size > 0 && load_size > 0 && store_size < load_size
}
fn instruction_memory_size(&self, instr: &MachineInstruction) -> u32 {
for op in &instr.operands {
if let MachineOperand::Memory { .. } = op {
return match instr.opcode {
0x88 | 0x8A => 1, 0x89 | 0x8B => 4, _ => 4,
};
}
}
0
}
}
impl X86MachineFunctionPass for X86AvoidStoreForwardingStallsPass {
fn name(&self) -> &'static str {
"x86-avoid-sf"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_AVOID_STORE_FORWARDING
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let mut changed = false;
for block in &mf.blocks {
for i in 0..block.instructions.len().saturating_sub(1) {
let store = &block.instructions[i];
let load = &block.instructions[i + 1];
if self.detect_store_forwarding_stall(store, load) {
self.stalls_detected += 1;
self.stalls_prevented += 1;
changed = true;
}
}
}
changed
}
}
pub struct X86VZeroUpperInsertionPass {
pub vzeroupper_inserted: u64,
pub transitions_detected: u64,
pub has_avx: bool,
}
impl X86VZeroUpperInsertionPass {
pub fn new(has_avx: bool) -> Self {
Self {
vzeroupper_inserted: 0,
transitions_detected: 0,
has_avx,
}
}
pub fn is_avx256_instruction(&self, instr: &MachineInstruction) -> bool {
(instr.opcode >= 0xC4 && instr.opcode <= 0xC5) || instr.opcode >= 0x100
}
pub fn block_uses_avx256(&self, block: &MachineBlockState) -> bool {
block
.instructions
.iter()
.any(|i| self.is_avx256_instruction(i))
}
pub fn needs_vzeroupper(&self, block: &MachineBlockState) -> bool {
self.has_avx
&& self.block_uses_avx256(block)
&& block
.instructions
.iter()
.any(|i| i.is_call || i.is_terminator)
}
}
impl X86MachineFunctionPass for X86VZeroUpperInsertionPass {
fn name(&self) -> &'static str {
"x86-vzeroupper"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_VZERO_UPPER
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
if !self.has_avx {
return false;
}
let mut changed = false;
for block in &mut mf.blocks {
if self.needs_vzeroupper(block) {
let insert_pos = block.instructions.len().saturating_sub(1);
let mut vz = MachineInstruction::new(0xC5);
vz.operands.push(MachineOperand::Immediate(0xF8));
vz.operands.push(MachineOperand::Immediate(0x77));
block.instructions.insert(insert_pos, vz);
self.vzeroupper_inserted += 1;
self.transitions_detected += 1;
changed = true;
}
}
changed
}
}
pub struct X86FixupVectorConstantsPass {
pub constants_optimized: u64,
pub constant_pool_size_reduced: u64,
pub broadcasts_inserted: u64,
pub has_sse: bool,
pub has_avx: bool,
pub has_avx512: bool,
}
impl X86FixupVectorConstantsPass {
pub fn new(has_sse: bool, has_avx: bool, has_avx512: bool) -> Self {
Self {
constants_optimized: 0,
constant_pool_size_reduced: 0,
broadcasts_inserted: 0,
has_sse,
has_avx,
has_avx512,
}
}
pub fn can_broadcast(&self, _value: &[u8]) -> bool {
self.has_avx
}
pub fn is_constant_pool_load(&self, instr: &MachineInstruction) -> bool {
instr.may_load
&& instr
.operands
.iter()
.any(|op| matches!(op, MachineOperand::Memory { .. }))
&& (self.instr_is_sse(instr) || self.instr_is_avx(instr))
}
fn instr_is_sse(&self, instr: &MachineInstruction) -> bool {
self.has_sse && instr.opcode >= 0x0F && instr.opcode <= 0x2F
}
fn instr_is_avx(&self, instr: &MachineInstruction) -> bool {
self.has_avx && (instr.opcode >= 0xC4 || instr.opcode >= 0x100)
}
}
impl X86MachineFunctionPass for X86FixupVectorConstantsPass {
fn name(&self) -> &'static str {
"x86-fixup-vector-const"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_FIXUP_VECTOR_CONSTANTS
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
let mut changed = false;
for block in &mf.blocks {
for instr in &block.instructions {
if self.is_constant_pool_load(instr) {
self.constants_optimized += 1;
if self.can_broadcast(&[]) {
self.broadcasts_inserted += 1;
}
self.constant_pool_size_reduced += 4;
changed = true;
}
}
}
changed
}
}
pub struct X86FloatingPointPass {
pub stack_operations_optimized: u64,
pub fxch_inserted: u64,
pub dead_fp_values_eliminated: u64,
pub max_stack_depth: u32,
pub uses_x87: bool,
}
impl X86FloatingPointPass {
pub fn new(uses_x87: bool) -> Self {
Self {
stack_operations_optimized: 0,
fxch_inserted: 0,
dead_fp_values_eliminated: 0,
max_stack_depth: 8,
uses_x87,
}
}
pub fn is_x87_instruction(&self, instr: &MachineInstruction) -> bool {
matches!(
instr.opcode,
0xD8 | 0xD9 | 0xDA | 0xDB | 0xDC | 0xDD | 0xDE | 0xDF
)
}
pub fn simulated_stack_depth(&self, instrs: &[MachineInstruction]) -> i32 {
let mut depth: i32 = 0;
for instr in instrs {
if self.is_x87_instruction(instr) {
if instr.opcode == 0xD9
&& instr.operands.first() == Some(&MachineOperand::Immediate(0xC0))
{
depth = depth.saturating_add(1); } else if instr.opcode == 0xDD
&& instr.operands.first() == Some(&MachineOperand::Immediate(0xD8))
{
depth = depth.saturating_sub(1); }
}
}
depth.max(0)
}
}
impl X86MachineFunctionPass for X86FloatingPointPass {
fn name(&self) -> &'static str {
"x86-fp-stack"
}
fn pass_id(&self) -> u64 {
pass_ids::X86_FLOATING_POINT
}
fn run_on_machine_function(&mut self, mf: &mut X86MachineFunctionState) -> bool {
if !self.uses_x87 {
return false;
}
let mut changed = false;
for block in &mf.blocks {
let depth = self.simulated_stack_depth(&block.instructions);
if depth > 0 {
self.stack_operations_optimized += depth as u64;
changed = true;
}
}
changed
}
}
#[derive(Debug, Clone)]
pub struct X86DAGISelConfig {
pub is_64bit: bool,
pub opt_level: OptimizationLevel,
pub enable_combiner: bool,
pub enable_legalizer: bool,
pub max_combine_iterations: u32,
pub use_fast_math: bool,
pub enable_load_store_opt: bool,
pub subtarget_features: Vec<String>,
}
impl Default for X86DAGISelConfig {
fn default() -> Self {
Self {
is_64bit: true,
opt_level: OptimizationLevel::O2,
enable_combiner: true,
enable_legalizer: true,
max_combine_iterations: 5,
use_fast_math: false,
enable_load_store_opt: true,
subtarget_features: Vec::new(),
}
}
}
impl X86DAGISelConfig {
pub fn for_opt_level(level: OptimizationLevel) -> Self {
Self {
opt_level: level,
enable_combiner: level != OptimizationLevel::O0,
enable_legalizer: level != OptimizationLevel::O0,
max_combine_iterations: match level {
OptimizationLevel::O0 => 0,
OptimizationLevel::O1 => 2,
OptimizationLevel::O2 => 5,
OptimizationLevel::O3 => 8,
OptimizationLevel::Os => 5,
OptimizationLevel::Oz => 5,
},
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct X86FastISelConfig {
pub is_64bit: bool,
pub fallback_to_dag: bool,
pub enable_frame_pointer: bool,
}
impl Default for X86FastISelConfig {
fn default() -> Self {
Self {
is_64bit: true,
fallback_to_dag: true,
enable_frame_pointer: true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86GlobalISelConfig {
pub is_64bit: bool,
pub enable_legalizer: bool,
pub enable_regbank_select: bool,
pub enable_combiner: bool,
pub opt_level: OptimizationLevel,
}
impl Default for X86GlobalISelConfig {
fn default() -> Self {
Self {
is_64bit: true,
enable_legalizer: true,
enable_regbank_select: true,
enable_combiner: true,
opt_level: OptimizationLevel::O2,
}
}
}
pub struct PhiEliminationPass {
pub phis_eliminated: u64,
pub copies_inserted: u64,
pub is_64bit: bool,
}
impl PhiEliminationPass {
pub fn new(is_64bit: bool) -> Self {
Self {
phis_eliminated: 0,
copies_inserted: 0,
is_64bit,
}
}
pub fn analyze_phi(&self, block: &MachineBlockState) -> Vec<(usize, MachineInstruction)> {
let mut copies = Vec::new();
for instr in &block.instructions {
if instr.operands.len() >= 2 && instr.defs.len() == 1 {
let mut copy = MachineInstruction::new(0x89);
copy.is_move = true;
copy.defs.push(instr.defs[0]);
if let MachineOperand::Register(reg) = instr.operands[0] {
copy.operands.push(MachineOperand::Register(reg));
}
copies.push((block.id, copy));
}
}
copies
}
}
pub struct TwoAddressInstructionPass {
pub instructions_rewritten: u64,
pub copies_inserted: u64,
pub is_64bit: bool,
}
impl TwoAddressInstructionPass {
pub fn new(is_64bit: bool) -> Self {
Self {
instructions_rewritten: 0,
copies_inserted: 0,
is_64bit,
}
}
pub fn is_three_address(&self, instr: &MachineInstruction) -> bool {
instr.defs.len() == 1 && instr.uses.len() >= 2
}
pub fn rewrite_to_two_address(&self, instr: &MachineInstruction) -> Vec<MachineInstruction> {
let mut seq = Vec::new();
let dst = instr.defs[0];
let src1 = instr.uses[0];
if dst != src1 {
let mut copy = MachineInstruction::new(0x89);
copy.is_move = true;
copy.defs.push(dst);
copy.operands.push(MachineOperand::Register(src1));
seq.push(copy);
}
let mut new_instr = instr.clone();
new_instr.uses.retain(|u| *u != src1);
new_instr.defs[0] = dst;
seq.push(new_instr);
seq
}
}
pub struct MachineBlockPlacementPass {
pub blocks_reordered: u64,
pub fallthrough_optimized: u64,
pub taken_branches_optimized: u64,
pub is_64bit: bool,
}
impl MachineBlockPlacementPass {
pub fn new(is_64bit: bool) -> Self {
Self {
blocks_reordered: 0,
fallthrough_optimized: 0,
taken_branches_optimized: 0,
is_64bit,
}
}
pub fn compute_optimal_layout(&self, blocks: &[MachineBlockState]) -> Vec<usize> {
let mut layout = Vec::with_capacity(blocks.len());
let mut placed = vec![false; blocks.len()];
let mut current = 0;
while layout.len() < blocks.len() {
if !placed[current] {
layout.push(current);
placed[current] = true;
}
let next = blocks[current]
.successors
.iter()
.copied()
.find(|s| !placed[*s]);
match next {
Some(n) => current = n,
None => {
current = placed.iter().position(|p| !*p).unwrap_or(current);
}
}
}
layout
}
pub fn fallthrough_count(&self, blocks: &[MachineBlockState], layout: &[usize]) -> u64 {
let mut count = 0u64;
for i in 0..layout.len().saturating_sub(1) {
let current = layout[i];
let next = layout[i + 1];
if blocks[current].successors.contains(&next) {
count += 1;
}
}
count
}
}
#[derive(Debug)]
pub struct PipelineAnalyzer {
registry: X86PassRegistry,
}
impl PipelineAnalyzer {
pub fn new(registry: X86PassRegistry) -> Self {
Self { registry }
}
pub fn find_parallel_pass_groups(&self) -> Vec<Vec<u64>> {
let mut groups = Vec::new();
let all_passes = self.registry.pipeline_order().to_vec();
let phases = [
PipelinePhase::EarlyIR,
PipelinePhase::MainIR,
PipelinePhase::LateIR,
PipelinePhase::PreRA,
PipelinePhase::PostRA,
PipelinePhase::X86Fixup,
];
for phase in &phases {
let mut group = Vec::new();
for id in &all_passes {
if let Some(desc) = self.registry.get(*id) {
if desc.phase == *phase && !desc.is_analysis {
group.push(*id);
}
}
}
if !group.is_empty() {
groups.push(group);
}
}
groups
}
pub fn find_redundant_passes(&self, pipeline: &[u64]) -> Vec<u64> {
let mut redundant = Vec::new();
let mut seen: HashMap<String, usize> = HashMap::new();
for (i, id) in pipeline.iter().enumerate() {
if let Some(desc) = self.registry.get(*id) {
let key = desc.name.clone();
if let Some(&prev_idx) = seen.get(&key) {
redundant.push(*id);
}
seen.insert(key, i);
}
}
redundant
}
pub fn dependency_graph(&self, pipeline: &[u64]) -> HashMap<u64, Vec<u64>> {
let mut graph: HashMap<u64, Vec<u64>> = HashMap::new();
for id in pipeline {
if let Some(desc) = self.registry.get(*id) {
let deps: Vec<u64> = desc
.dependencies
.iter()
.filter_map(|name| self.registry.id_for_name(name))
.collect();
graph.insert(*id, deps);
}
}
graph
}
pub fn validate_pipeline(&self, pipeline: &[u64]) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
let pipeline_set: HashSet<u64> = pipeline.iter().copied().collect();
for &id in pipeline {
if let Some(desc) = self.registry.get(id) {
for dep_name in &desc.dependencies {
if let Some(dep_id) = self.registry.id_for_name(dep_name) {
if !pipeline_set.contains(&dep_id) {
errors.push(format!(
"Pass '{}' requires '{}' which is not in the pipeline",
desc.name, dep_name
));
}
}
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86DetailedStats {
pub per_pass: HashMap<u64, PassExecutionStats>,
pub per_phase: HashMap<PipelinePhase, PhaseExecutionStats>,
pub total: TotalPipelineStats,
}
#[derive(Debug, Clone, Default)]
pub struct PassExecutionStats {
pub invocations: u64,
pub changes: u64,
pub time_ms: f64,
pub functions_visited: u64,
pub blocks_visited: u64,
pub instructions_processed: u64,
pub peaks_memory_mb: f64,
}
#[derive(Debug, Clone, Default)]
pub struct PhaseExecutionStats {
pub total_time_ms: f64,
pub total_passes: u64,
pub total_changes: u64,
pub peak_memory_mb: f64,
}
#[derive(Debug, Clone, Default)]
pub struct TotalPipelineStats {
pub total_time_ms: f64,
pub total_passes_executed: u64,
pub total_changes: u64,
pub compile_time_ms: f64,
pub code_size_bytes: u64,
pub peak_memory_mb: f64,
}
impl X86DetailedStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_pass(
&mut self,
pass_id: u64,
phase: PipelinePhase,
time_ms: f64,
changed: bool,
_memory_mb: f64,
) {
let ps = self.per_pass.entry(pass_id).or_default();
ps.invocations += 1;
if changed {
ps.changes += 1;
}
ps.time_ms += time_ms;
let ph = self.per_phase.entry(phase).or_default();
ph.total_time_ms += time_ms;
ph.total_passes += 1;
if changed {
ph.total_changes += 1;
}
self.total.total_time_ms += time_ms;
self.total.total_passes_executed += 1;
if changed {
self.total.total_changes += 1;
}
}
pub fn report(&self) -> String {
let mut report =
String::from("╔════════════════════════════════════════════════════════════╗\n");
report.push_str("║ X86 Pipeline Detailed Statistics ║\n");
report.push_str("╠════════════════════════════════════════════════════════════╣\n");
let phases = [
PipelinePhase::EarlyIR,
PipelinePhase::MainIR,
PipelinePhase::LateIR,
PipelinePhase::Lowering,
PipelinePhase::PreRA,
PipelinePhase::RegAlloc,
PipelinePhase::PostRA,
PipelinePhase::X86Fixup,
];
for phase in &phases {
if let Some(ph) = self.per_phase.get(phase) {
report.push_str(&format!(
"║ {:<25} {:>8.1} ms {:>3} passes {:>4} changes ║\n",
phase.name(),
ph.total_time_ms,
ph.total_passes,
ph.total_changes,
));
}
}
report.push_str("╠════════════════════════════════════════════════════════════╣\n");
report.push_str(&format!(
"║ Total: {:>8.1} ms, {} passes, {} changes ║\n",
self.total.total_time_ms, self.total.total_passes_executed, self.total.total_changes,
));
report.push_str("╚════════════════════════════════════════════════════════════╝\n");
report
}
}
#[derive(Debug, Clone)]
pub struct RegAllocResult {
pub success: bool,
pub spills: u64,
pub reloads: u64,
pub copies_coalesced: u64,
pub regs_spilled: u64,
pub ranges_split: u64,
pub total_allocation_time_ms: f64,
pub peak_memory_bytes: u64,
}
impl RegAllocResult {
pub fn new() -> Self {
Self {
success: true,
spills: 0,
reloads: 0,
copies_coalesced: 0,
regs_spilled: 0,
ranges_split: 0,
total_allocation_time_ms: 0.0,
peak_memory_bytes: 0,
}
}
pub fn merge(&mut self, other: &RegAllocResult) {
self.spills += other.spills;
self.reloads += other.reloads;
self.copies_coalesced += other.copies_coalesced;
self.regs_spilled += other.regs_spilled;
self.ranges_split += other.ranges_split;
self.total_allocation_time_ms += other.total_allocation_time_ms;
self.success = self.success && other.success;
self.peak_memory_bytes = self.peak_memory_bytes.max(other.peak_memory_bytes);
}
pub fn is_clean(&self) -> bool {
self.success && self.spills == 0 && self.reloads == 0
}
pub fn spill_rate(&self) -> f64 {
let total = self.regs_spilled + self.spills;
if total == 0 {
0.0
} else {
self.spills as f64 / total as f64 * 100.0
}
}
}
#[derive(Debug, Clone)]
pub struct GreedyRegAllocConfig {
pub max_split_iterations: u32,
pub enable_eviction: bool,
pub enable_region_split: bool,
pub enable_local_reassignment: bool,
pub enable_global_reassignment: bool,
pub spill_cost_threshold: f64,
pub enable_coalescing: bool,
}
impl Default for GreedyRegAllocConfig {
fn default() -> Self {
Self {
max_split_iterations: 3,
enable_eviction: true,
enable_region_split: true,
enable_local_reassignment: true,
enable_global_reassignment: true,
spill_cost_threshold: 0.5,
enable_coalescing: true,
}
}
}
impl GreedyRegAllocConfig {
pub fn for_opt_level(level: OptimizationLevel) -> Self {
match level {
OptimizationLevel::O0 => Self {
max_split_iterations: 0,
enable_eviction: false,
enable_region_split: false,
enable_local_reassignment: false,
enable_global_reassignment: false,
spill_cost_threshold: 1.0,
enable_coalescing: false,
},
OptimizationLevel::O1 => Self {
max_split_iterations: 1,
enable_eviction: false,
enable_region_split: false,
enable_local_reassignment: true,
enable_global_reassignment: false,
spill_cost_threshold: 0.3,
enable_coalescing: true,
},
OptimizationLevel::O2 => Self {
max_split_iterations: 2,
enable_eviction: true,
enable_region_split: true,
enable_local_reassignment: true,
enable_global_reassignment: true,
spill_cost_threshold: 0.5,
enable_coalescing: true,
},
OptimizationLevel::O3 => Self {
max_split_iterations: 3,
enable_eviction: true,
enable_region_split: true,
enable_local_reassignment: true,
enable_global_reassignment: true,
spill_cost_threshold: 0.8,
enable_coalescing: true,
},
OptimizationLevel::Os | OptimizationLevel::Oz => Self {
max_split_iterations: 2,
enable_eviction: true,
enable_region_split: false,
enable_local_reassignment: true,
enable_global_reassignment: false,
spill_cost_threshold: 0.4,
enable_coalescing: true,
},
}
}
}
#[derive(Debug, Clone)]
pub struct FastRegAllocConfig {
pub max_alloc_attempts: u32,
pub enable_spill: bool,
pub enable_coalescing: bool,
}
impl Default for FastRegAllocConfig {
fn default() -> Self {
Self {
max_alloc_attempts: 1,
enable_spill: true,
enable_coalescing: false,
}
}
}
impl FastRegAllocConfig {
pub fn for_opt_level(level: OptimizationLevel) -> Self {
match level {
OptimizationLevel::O0 => Self {
max_alloc_attempts: 1,
enable_spill: true,
enable_coalescing: false,
},
_ => Self {
max_alloc_attempts: 2,
enable_spill: true,
enable_coalescing: true,
},
}
}
}
#[derive(Debug)]
pub struct PostRASchedulerPass {
pub instructions_reordered: u64,
pub stalls_reduced: u64,
pub critical_path_reduced_ns: f64,
pub is_64bit: bool,
pub sched_model: Option<SchedModelHint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SchedModelHint {
Default,
Throughput,
Latency,
Power,
}
impl PostRASchedulerPass {
pub fn new(is_64bit: bool) -> Self {
Self {
instructions_reordered: 0,
stalls_reduced: 0,
critical_path_reduced_ns: 0.0,
is_64bit,
sched_model: Some(SchedModelHint::Default),
}
}
pub fn compute_dependencies(&self, instrs: &[MachineInstruction]) -> Vec<Vec<usize>> {
let mut deps: Vec<Vec<usize>> = vec![Vec::new(); instrs.len()];
for i in 0..instrs.len() {
for j in (i + 1)..instrs.len() {
let raw = instrs[i].defs.iter().any(|d| instrs[j].uses.contains(d));
let waw = instrs[i].defs.iter().any(|d| instrs[j].defs.contains(d));
let war = instrs[i].uses.iter().any(|u| instrs[j].defs.contains(u));
if raw || waw || war {
deps[i].push(j);
}
}
}
deps
}
pub fn estimate_latency(&self, instr: &MachineInstruction) -> u32 {
match instr.opcode {
0x89 | 0x8B => 1, 0x01 | 0x29 | 0x31 => 1, 0xF7 => 3, 0x58..=0x5F => 4, 0x0F => 1, _ => 2, }
}
pub fn list_schedule(&self, instrs: &[MachineInstruction]) -> Vec<usize> {
let deps = self.compute_dependencies(instrs);
let mut in_degree: Vec<usize> = vec![0; instrs.len()];
for i in 0..deps.len() {
for j in &deps[i] {
in_degree[*j] += 1;
}
}
let mut ready: Vec<usize> = (0..instrs.len()).filter(|i| in_degree[*i] == 0).collect();
let mut schedule = Vec::with_capacity(instrs.len());
while !ready.is_empty() {
ready.sort_by_key(|i| std::cmp::Reverse(self.estimate_latency(&instrs[*i])));
let next = ready.remove(0);
schedule.push(next);
for &succ in &deps[next] {
in_degree[succ] -= 1;
if in_degree[succ] == 0 {
ready.push(succ);
}
}
}
schedule
}
}
#[derive(Debug)]
pub struct BranchFoldingPass {
pub branches_eliminated: u64,
pub unconditional_converted: u64,
pub code_size_reduced: u64,
pub blocks_merged: u64,
}
impl BranchFoldingPass {
pub fn new() -> Self {
Self {
branches_eliminated: 0,
unconditional_converted: 0,
code_size_reduced: 0,
blocks_merged: 0,
}
}
pub fn can_eliminate_jump(&self, instr: &MachineInstruction, next_block_id: usize) -> bool {
if !instr.is_branch || !instr.is_terminator {
return false;
}
if let Some(MachineOperand::Label(label)) = instr.operands.first() {
let target_id = label_to_block_id(label);
target_id == next_block_id
} else {
false
}
}
pub fn can_eliminate_condition(&self, instr: &MachineInstruction) -> bool {
instr.is_branch && instr.operands.len() >= 2
}
pub fn can_merge_blocks(&self, block: &MachineBlockState, next: &MachineBlockState) -> bool {
block.successors.len() == 1 && next.predecessors.len() == 1 && !next.instructions.is_empty()
}
}
fn label_to_block_id(label: &str) -> usize {
label
.trim_start_matches(".L")
.trim_start_matches("L")
.trim_start_matches("BB")
.parse()
.unwrap_or(0)
}
#[derive(Debug)]
pub struct TailDuplicationPass {
pub blocks_duplicated: u64,
pub instructions_added: u64,
pub branches_eliminated: u64,
pub max_duplication_size: u32,
pub max_duplication_instructions: u32,
}
impl TailDuplicationPass {
pub fn new() -> Self {
Self {
blocks_duplicated: 0,
instructions_added: 0,
branches_eliminated: 0,
max_duplication_size: 64,
max_duplication_instructions: 8,
}
}
pub fn is_duplication_candidate(&self, block: &MachineBlockState) -> bool {
block.instructions.len() <= self.max_duplication_instructions as usize
&& block.predecessors.len() > 1
&& !block.instructions.iter().any(|i| i.is_call)
}
pub fn estimate_size_increase(&self, block: &MachineBlockState) -> u64 {
let dup_count = block.predecessors.len().saturating_sub(1) as u64;
dup_count
* block
.instructions
.iter()
.map(|i| i.size_bytes as u64)
.sum::<u64>()
}
pub fn is_duplication_profitable(&self, block: &MachineBlockState) -> bool {
let size_increase = self.estimate_size_increase(block);
let branches_saved = block.predecessors.len().saturating_sub(1) as u64;
size_increase < branches_saved * 8 }
}
#[derive(Debug)]
pub struct MachineOutlinerPass {
pub sequences_found: u64,
pub functions_outlined: u64,
pub bytes_saved: u64,
pub min_sequence_length: u32,
pub min_occurrences: u32,
pub benefit_threshold: u32,
}
impl MachineOutlinerPass {
pub fn new() -> Self {
Self {
sequences_found: 0,
functions_outlined: 0,
bytes_saved: 0,
min_sequence_length: 3,
min_occurrences: 2,
benefit_threshold: 10,
}
}
pub fn find_repeated_sequences(
&self,
instrs: &[MachineInstruction],
) -> Vec<(usize, usize, usize)> {
let mut sequences = Vec::new();
let n = instrs.len();
for len in (self.min_sequence_length as usize)..=(n / self.min_occurrences as usize).min(20)
{
for i in 0..(n.saturating_sub(len)) {
let mut count = 1;
for j in (i + len)..(n.saturating_sub(len - 1)) {
if instrs[i..i + len]
.iter()
.zip(&instrs[j..j + len])
.all(|(a, b)| a.opcode == b.opcode)
{
count += 1;
}
}
if count >= self.min_occurrences as usize {
sequences.push((i, len, count));
}
}
}
sequences.sort_by_key(|(_, len, count)| std::cmp::Reverse(len * count));
sequences
}
pub fn estimate_savings(
&self,
instrs: &[MachineInstruction],
start: usize,
len: usize,
occurrences: usize,
) -> u64 {
let sequence_size: u64 = instrs[start..start + len]
.iter()
.map(|i| i.size_bytes.max(1) as u64)
.sum();
let removed_copies = sequence_size * (occurrences as u64 - 1);
let call_overhead = (occurrences as u64) * 5;
let function_overhead = 3;
removed_copies
.saturating_sub(call_overhead)
.saturating_sub(function_overhead)
}
}
#[derive(Debug)]
pub struct PostRAMachineCopyPropagationPass {
pub copies_eliminated: u64,
pub uses_propagated: u64,
}
impl PostRAMachineCopyPropagationPass {
pub fn new() -> Self {
Self {
copies_eliminated: 0,
uses_propagated: 0,
}
}
pub fn can_eliminate_copy(&self, instr: &MachineInstruction) -> bool {
if !instr.is_move || instr.defs.len() != 1 || instr.uses.len() != 1 {
return false;
}
let dst = instr.defs[0];
let src = if let MachineOperand::Register(r) = instr.operands[0] {
r
} else {
return false;
};
dst == src
}
pub fn find_register_uses<'a>(
&self,
instrs: &'a [MachineInstruction],
reg: usize,
start_idx: usize,
) -> Vec<usize> {
let mut uses = Vec::new();
for (i, instr) in instrs.iter().enumerate().skip(start_idx) {
if instr.uses.contains(®) {
uses.push(i);
}
}
uses
}
}
#[derive(Debug)]
pub struct PostRAMachineSinkingPass {
pub instructions_sunk: u64,
}
impl PostRAMachineSinkingPass {
pub fn new() -> Self {
Self {
instructions_sunk: 0,
}
}
pub fn can_sink_into_successor(
&self,
instr: &MachineInstruction,
successors: &[usize],
) -> bool {
successors.len() == 1 && !instr.is_terminator && !instr.is_branch
}
pub fn result_used_in_block(&self, req: usize, block: &MachineBlockState) -> bool {
block.instructions.iter().any(|i| i.uses.contains(&req))
}
}
#[derive(Debug, Clone)]
pub struct X86SubtargetPassConfig {
pub features: HashSet<String>,
pub enable_sse_passes: bool,
pub enable_avx_passes: bool,
pub enable_avx512_passes: bool,
pub enable_bmi_passes: bool,
pub enable_cmov_conversion: bool,
pub enable_x87_passes: bool,
pub prefer_and_over_mov: bool,
pub use_inc_dec: bool,
pub slow_3ops_lea: bool,
pub slow_incdec: bool,
pub slow_div: bool,
pub avoid_lea: bool,
pub use_cdq: bool,
pub use_cmov: bool,
}
impl Default for X86SubtargetPassConfig {
fn default() -> Self {
Self {
features: HashSet::new(),
enable_sse_passes: true,
enable_avx_passes: false,
enable_avx512_passes: false,
enable_bmi_passes: false,
enable_cmov_conversion: false,
enable_x87_passes: false,
prefer_and_over_mov: false,
use_inc_dec: true,
slow_3ops_lea: false,
slow_incdec: false,
slow_div: false,
avoid_lea: false,
use_cdq: false,
use_cmov: true,
}
}
}
impl X86SubtargetPassConfig {
pub fn from_features(features: &[String]) -> Self {
let feature_set: HashSet<String> = features.iter().cloned().collect();
let has_sse = feature_set.contains("sse")
|| feature_set.contains("sse2")
|| feature_set.contains("sse3")
|| feature_set.contains("sse4.1")
|| feature_set.contains("sse4.2");
let has_avx = feature_set.contains("avx") || feature_set.contains("avx2");
let has_avx512 = feature_set.contains("avx512f")
|| feature_set.contains("avx512bw")
|| feature_set.contains("avx512dq");
let has_bmi = feature_set.contains("bmi") || feature_set.contains("bmi2");
let is_atom = feature_set.contains("atom");
let is_amd = feature_set.contains("znver1")
|| feature_set.contains("znver2")
|| feature_set.contains("znver3")
|| feature_set.contains("znver4");
Self {
features: feature_set,
enable_sse_passes: has_sse,
enable_avx_passes: has_avx,
enable_avx512_passes: has_avx512,
enable_bmi_passes: has_bmi,
enable_cmov_conversion: is_atom, enable_x87_passes: !has_sse, prefer_and_over_mov: !is_atom,
use_inc_dec: !is_amd, slow_3ops_lea: is_atom,
slow_incdec: is_amd,
slow_div: is_atom || is_amd,
avoid_lea: false,
use_cdq: !is_atom,
use_cmov: !is_atom,
}
}
pub fn enabled_x86_fixups(&self) -> Vec<u64> {
let mut fixups = vec![
pass_ids::X86_FIXUP_LEA,
pass_ids::X86_FIXUP_BW,
pass_ids::X86_FIXUP_SETCC,
pass_ids::X86_CALL_FRAME_OPT,
pass_ids::X86_PAD_SHORT_FUNCTION,
];
if self.enable_cmov_conversion {
fixups.push(pass_ids::X86_CMOV_CONVERSION);
}
if self.enable_sse_passes || self.enable_avx_passes {
fixups.push(pass_ids::X86_DOMAIN_REASSIGN);
fixups.push(pass_ids::X86_FIXUP_VECTOR_CONSTANTS);
}
if self.enable_avx_passes {
fixups.push(pass_ids::X86_VZERO_UPPER);
}
if self.enable_x87_passes {
fixups.push(pass_ids::X86_FLOATING_POINT);
}
fixups.push(pass_ids::X86_AVOID_STORE_FORWARDING);
fixups
}
pub fn inline_threshold_adjustment(&self) -> i32 {
let mut adj = 0;
if self.slow_div {
adj -= 10; }
if self.features.contains("tune-size") {
adj -= 50;
}
if self.features.contains("tune-generic") {
adj += 0; }
adj
}
}
#[derive(Debug, Clone)]
pub struct PipelineVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub features: Vec<String>,
pub opt_level: OptimizationLevel,
pub target_triple: Option<String>,
}
impl PipelineVersion {
pub fn new(major: u32, minor: u32, patch: u32, opt_level: OptimizationLevel) -> Self {
Self {
major,
minor,
patch,
features: Vec::new(),
opt_level,
target_triple: None,
}
}
pub fn current(opt_level: OptimizationLevel) -> Self {
Self::new(1, 0, 0, opt_level)
}
pub fn version_string(&self) -> String {
format!(
"x86-pipeline-v{}.{}.{}-{:?}",
self.major, self.minor, self.patch, self.opt_level
)
}
pub fn is_compatible(&self, other: &PipelineVersion) -> bool {
self.major == other.major && self.opt_level == other.opt_level
}
pub fn with_target_triple(mut self, triple: &str) -> Self {
self.target_triple = Some(triple.to_string());
self
}
}
#[derive(Debug, Clone)]
pub struct SerializedPassResult {
pub pass_id: u64,
pub pass_name: String,
pub version: PipelineVersion,
pub changed: bool,
pub time_ms: f64,
pub stats: HashMap<String, String>,
}
impl SerializedPassResult {
pub fn new(pass_id: u64, pass_name: &str, version: PipelineVersion) -> Self {
Self {
pass_id,
pass_name: pass_name.to_string(),
version,
changed: false,
time_ms: 0.0,
stats: HashMap::new(),
}
}
pub fn to_string_pretty(&self) -> String {
let mut s = format!(
"[{}] {} (v{}.{}.{}) — {:.2}ms — {}",
self.pass_id,
self.pass_name,
self.version.major,
self.version.minor,
self.version.patch,
self.time_ms,
if self.changed { "changed" } else { "unchanged" }
);
for (key, value) in &self.stats {
s.push_str(&format!("\n {} = {}", key, value));
}
s
}
}
#[derive(Debug, Clone, Default)]
pub struct PipelineWarmupAnalysis {
pub runs: Vec<PipelineRunRecord>,
pub warmup_iterations: usize,
pub steady_state_iterations: usize,
}
#[derive(Debug, Clone)]
pub struct PipelineRunRecord {
pub iteration: usize,
pub total_time_ms: f64,
pub peak_memory_mb: f64,
pub passes_changed: u64,
pub passes_unchanged: u64,
}
impl PipelineWarmupAnalysis {
pub fn new(warmup_iterations: usize) -> Self {
Self {
runs: Vec::new(),
warmup_iterations,
steady_state_iterations: 0,
}
}
pub fn record_run(
&mut self,
iteration: usize,
time_ms: f64,
memory_mb: f64,
changed: u64,
unchanged: u64,
) {
self.runs.push(PipelineRunRecord {
iteration,
total_time_ms: time_ms,
peak_memory_mb: memory_mb,
passes_changed: changed,
passes_unchanged: unchanged,
});
}
pub fn is_steady_state(&self) -> bool {
if self.runs.len() < self.warmup_iterations + 2 {
return false;
}
let recent: Vec<f64> = self
.runs
.iter()
.rev()
.take(3)
.map(|r| r.total_time_ms)
.collect();
if recent.len() < 2 {
return false;
}
let max_diff = recent
.windows(2)
.map(|w| (w[0] - w[1]).abs())
.fold(0.0_f64, f64::max);
let sum: f64 = recent.iter().sum();
let threshold = 0.05;
max_diff / sum * (recent.len() as f64) < threshold
}
pub fn steady_state_time_ms(&self) -> f64 {
let start = self.warmup_iterations;
if start >= self.runs.len() {
return 0.0;
}
let steady: f64 = self.runs[start..].iter().map(|r| r.total_time_ms).sum();
steady / (self.runs.len() - start) as f64
}
}
#[derive(Debug, Clone)]
pub struct PipelineCostModel {
pub instruction_cost: f64,
pub branch_cost: f64,
pub call_cost: f64,
pub memory_cost: f64,
pub register_pressure_penalty: f64,
pub cache_miss_penalty: f64,
}
impl Default for PipelineCostModel {
fn default() -> Self {
Self {
instruction_cost: 1.0,
branch_cost: 3.0,
call_cost: 10.0,
memory_cost: 2.0,
register_pressure_penalty: 2.0,
cache_miss_penalty: 50.0,
}
}
}
impl PipelineCostModel {
pub fn estimate_sequence_cost(&self, instrs: &[MachineInstruction]) -> f64 {
let mut cost = 0.0;
for instr in instrs {
cost += self.instruction_cost;
if instr.is_branch {
cost += self.branch_cost;
}
if instr.is_call {
cost += self.call_cost;
}
if instr.may_load || instr.may_store {
cost += self.memory_cost;
}
}
cost
}
pub fn optimization_benefit(
&self,
instructions_removed: usize,
branches_removed: usize,
) -> f64 {
(instructions_removed as f64) * self.instruction_cost
+ (branches_removed as f64) * self.branch_cost
}
pub fn register_pressure_cost(&self, live_registers: usize, available_registers: usize) -> f64 {
if live_registers <= available_registers {
0.0
} else {
(live_registers - available_registers) as f64 * self.register_pressure_penalty
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_creation() {
let registry = X86PassRegistry::default();
assert!(!registry.is_empty());
assert!(registry.len() > 10);
}
#[test]
fn test_registry_lookup_by_name() {
let registry = X86PassRegistry::default();
assert!(registry.get_by_name("mem2reg").is_some());
assert!(registry.get_by_name("instcombine").is_some());
assert!(registry.get_by_name("nonexistent").is_none());
}
#[test]
fn test_registry_lookup_by_id() {
let registry = X86PassRegistry::default();
assert!(registry.get(pass_ids::GVN).is_some());
assert!(registry.get(0xFFFF).is_none());
}
#[test]
fn test_registry_pass_enabled_at() {
let registry = X86PassRegistry::default();
let mem2reg = registry.get(pass_ids::MEM2REG).unwrap();
assert!(mem2reg.enabled_at(OptimizationLevel::O0));
assert!(mem2reg.enabled_at(OptimizationLevel::O3));
let inline_pass = registry.get(pass_ids::INLINE).unwrap();
assert!(!inline_pass.enabled_at(OptimizationLevel::O0));
assert!(!inline_pass.enabled_at(OptimizationLevel::O1));
assert!(inline_pass.enabled_at(OptimizationLevel::O2));
}
#[test]
fn test_registry_phase_index() {
let registry = X86PassRegistry::default();
let passes = registry.passes_for_opt_level(OptimizationLevel::O1);
assert!(passes.contains(&pass_ids::MEM2REG));
}
#[test]
fn test_registry_validation_success() {
let registry = X86PassRegistry::default();
assert!(registry.validate().is_ok());
}
#[test]
fn test_registry_validation_missing_deps() {
let mut registry = X86PassRegistry::new();
registry.register(
X86PassDescriptor::new(9999, "test-pass", "A test pass")
.with_dependency("nonexistent-pass"),
);
assert!(registry.validate().is_err());
}
#[test]
fn test_registry_x86_specific_passes() {
let registry = X86PassRegistry::default();
assert!(
registry
.get_by_name("x86-dag-isel")
.unwrap()
.is_x86_specific
);
assert!(
registry
.get_by_name("x86-fixup-lea")
.unwrap()
.is_x86_specific
);
assert!(!registry.get_by_name("mem2reg").unwrap().is_x86_specific);
}
#[test]
fn test_pass_descriptor_phase() {
let desc =
X86PassDescriptor::new(100, "test", "Test Pass").with_phase(PipelinePhase::PostRA);
assert_eq!(desc.phase, PipelinePhase::PostRA);
}
#[test]
fn test_o0_pipeline() {
let mgr = create_o0_pipeline();
let pipeline = mgr.pipeline();
assert!(pipeline.contains(&pass_ids::MEM2REG));
assert!(!pipeline.contains(&pass_ids::INLINE));
assert!(pipeline.contains(&pass_ids::X86_FAST_ISEL));
}
#[test]
fn test_o1_pipeline() {
let mgr = create_o1_pipeline();
let pipeline = mgr.pipeline();
assert!(pipeline.contains(&pass_ids::INSTCOMBINE));
assert!(pipeline.contains(&pass_ids::GVN));
assert!(pipeline.contains(&pass_ids::LOOP_UNROLL));
assert!(!pipeline.contains(&pass_ids::INLINE)); assert!(!pipeline.contains(&pass_ids::ARGPROMOTION)); }
#[test]
fn test_o2_pipeline() {
let mgr = create_o2_pipeline();
let pipeline = mgr.pipeline();
assert!(pipeline.contains(&pass_ids::INLINE));
assert!(pipeline.contains(&pass_ids::JUMP_THREADING));
assert!(pipeline.contains(&pass_ids::DSE));
assert!(!pipeline.contains(&pass_ids::ARGPROMOTION)); }
#[test]
fn test_o3_pipeline() {
let mgr = create_o3_pipeline();
let pipeline = mgr.pipeline();
assert!(pipeline.contains(&pass_ids::ARGPROMOTION));
assert!(pipeline.contains(&pass_ids::TAILCALLELIM));
assert!(pipeline.contains(&pass_ids::LOOP_INTERCHANGE));
assert!(pipeline.contains(&pass_ids::LOOP_UNSWITCH));
assert!(pipeline.contains(&pass_ids::AGGRESSIVE_INSTCOMBINE));
}
#[test]
fn test_os_pipeline() {
let mgr = create_os_pipeline();
let pipeline = mgr.pipeline();
assert!(!pipeline.contains(&pass_ids::ARGPROMOTION));
assert!(pipeline.contains(&pass_ids::MACHINE_OUTLINER));
}
#[test]
fn test_oz_pipeline() {
let mgr = create_oz_pipeline();
let pipeline = mgr.pipeline();
assert!(pipeline.contains(&pass_ids::OUTLINE_FUNCTIONS));
assert!(pipeline.contains(&pass_ids::MERGE_CONSTANTS));
assert!(!pipeline.contains(&pass_ids::ARGPROMOTION));
}
#[test]
fn test_pipeline_contains_isel() {
let mgr_o2 = create_o2_pipeline();
assert!(mgr_o2.pipeline().contains(&pass_ids::X86_DAG_ISEL));
let mgr_o0 = create_o0_pipeline();
assert!(mgr_o0.pipeline().contains(&pass_ids::X86_FAST_ISEL));
}
#[test]
fn test_pipeline_contains_x86_fixups() {
let mgr = create_o2_pipeline();
let pipeline = mgr.pipeline();
assert!(pipeline.contains(&pass_ids::X86_FIXUP_LEA));
assert!(pipeline.contains(&pass_ids::X86_VZERO_UPPER));
assert!(pipeline.contains(&pass_ids::X86_PAD_SHORT_FUNCTION));
}
#[test]
fn test_pipeline_ordering() {
let mgr = create_o2_pipeline();
let pipeline = mgr.pipeline();
let mem2reg_pos = pipeline
.iter()
.position(|&p| p == pass_ids::MEM2REG)
.unwrap();
let isel_pos = pipeline
.iter()
.position(|&p| p == pass_ids::X86_DAG_ISEL)
.unwrap();
let regalloc_pos = pipeline
.iter()
.position(|&p| {
p == pass_ids::REG_ALLOC_GREEDY
|| p == pass_ids::REG_ALLOC_PBQP
|| p == pass_ids::REG_ALLOC_FAST
|| p == pass_ids::REG_ALLOC_BASIC
})
.unwrap();
let fixup_pos = pipeline
.iter()
.position(|&p| p == pass_ids::X86_FIXUP_LEA)
.unwrap();
assert!(mem2reg_pos < isel_pos);
assert!(isel_pos < regalloc_pos);
assert!(regalloc_pos < fixup_pos);
}
#[test]
fn test_timing_creation() {
let timing = X86PassTiming::new(true);
assert!(timing.enabled);
}
#[test]
fn test_timing_disabled() {
let timing = X86PassTiming::new(false);
assert!(!timing.enabled);
}
#[test]
fn test_timing_start_stop() {
let mut timing = X86PassTiming::new(true);
timing.start_pass(pass_ids::GVN);
std::thread::sleep(Duration::from_millis(1));
timing.stop_pass(true);
let record = timing.get_record(pass_ids::GVN).unwrap();
assert_eq!(record.invocation_count, 1);
assert_eq!(record.change_count, 1);
assert!(record.total_duration > Duration::default());
}
#[test]
fn test_timing_disabled_doesnt_record() {
let mut timing = X86PassTiming::new(false);
timing.start_pass(pass_ids::GVN);
timing.stop_pass(true);
assert!(timing.get_record(pass_ids::GVN).is_none());
}
#[test]
fn test_timing_multiple_invocations() {
let mut timing = X86PassTiming::new(true);
for _ in 0..3 {
timing.start_pass(pass_ids::INSTCOMBINE);
timing.stop_pass(true);
}
let record = timing.get_record(pass_ids::INSTCOMBINE).unwrap();
assert_eq!(record.invocation_count, 3);
assert_eq!(record.change_count, 3);
}
#[test]
fn test_timing_avg_and_std_dev() {
let mut timing = X86PassTiming::new(true);
timing.start_pass(pass_ids::MEM2REG);
std::thread::sleep(Duration::from_millis(2));
timing.stop_pass(true);
timing.start_pass(pass_ids::MEM2REG);
std::thread::sleep(Duration::from_millis(4));
timing.stop_pass(true);
let record = timing.get_record(pass_ids::MEM2REG).unwrap();
assert_eq!(record.invocation_count, 2);
assert!(record.avg_ms() > 0.0);
assert!(record.max_duration.is_some());
assert!(record.min_duration.is_some());
}
#[test]
fn test_timing_report() {
let mut timing = X86PassTiming::new(true);
timing.start_pass(pass_ids::GVN);
timing.stop_pass(true);
let report = timing.report();
assert!(report.contains("X86 Pass Timing Report"));
assert!(report.contains("gvn"));
}
#[test]
fn test_timing_compact_report() {
let mut timing = X86PassTiming::new(true);
timing.start_pass(pass_ids::INSTCOMBINE);
timing.stop_pass(false);
let report = timing.compact_report();
assert!(report.contains("instcombine"));
}
#[test]
fn test_timing_ordering_analysis() {
let mut timing = X86PassTiming::new(true);
timing.start_pass(pass_ids::GVN);
std::thread::sleep(Duration::from_millis(5));
timing.stop_pass(true);
timing.start_pass(pass_ids::MEM2REG);
std::thread::sleep(Duration::from_millis(1));
timing.stop_pass(true);
let analysis = timing.ordering_analysis();
assert!(!analysis.is_empty());
let (first_name, _, _) = &analysis[0];
assert!(first_name.contains("gvn") || first_name.contains("Global Value"));
}
#[test]
fn test_timing_reset() {
let mut timing = X86PassTiming::new(true);
timing.start_pass(pass_ids::GVN);
timing.stop_pass(true);
assert!(timing.get_record(pass_ids::GVN).is_some());
timing.reset();
assert!(timing.get_record(pass_ids::GVN).is_none());
}
#[test]
fn test_timing_merge() {
let mut t1 = X86PassTiming::new(true);
t1.start_pass(pass_ids::GVN);
t1.stop_pass(true);
let mut t2 = X86PassTiming::new(true);
t2.start_pass(pass_ids::GVN);
t2.stop_pass(true);
t2.start_pass(pass_ids::MEM2REG);
t2.stop_pass(false);
t1.merge(&t2);
let gvn = t1.get_record(pass_ids::GVN).unwrap();
assert_eq!(gvn.invocation_count, 2);
let mem2reg = t1.get_record(pass_ids::MEM2REG).unwrap();
assert_eq!(mem2reg.invocation_count, 1);
}
#[test]
fn test_timing_pass_display_name() {
let mut timing = X86PassTiming::new(true);
timing.set_label(pass_ids::GVN, "Custom GVN Label");
timing.start_pass(pass_ids::GVN);
timing.stop_pass(true);
let report = timing.compact_report();
assert!(report.contains("Custom GVN Label"));
}
#[test]
fn test_instrumentation_creation() {
let instr = X86PassInstrumentation::new();
assert!(instr.enabled);
assert!(!instr.verify_each);
assert!(!instr.debug_before);
assert!(!instr.debug_after);
}
#[test]
fn test_instrumentation_callbacks() {
let mut instr = X86PassInstrumentation::new();
let callback_count = std::sync::atomic::AtomicU32::new(0);
instr.on_before_pass(move |_, _, _| {
callback_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
});
instr.before_pass(pass_ids::GVN, "gvn", "");
}
#[test]
fn test_instrumentation_record_pass() {
let instr = X86PassInstrumentation::new();
instr.record_pass_execution(true);
instr.record_pass_execution(false);
instr.record_pass_execution(true);
assert_eq!(instr.pass_count.load(Ordering::Relaxed), 3);
assert_eq!(instr.changed_count.load(Ordering::Relaxed), 2);
}
#[test]
fn test_instrumentation_verify_flags() {
let instr = X86PassInstrumentation::new();
assert!(!instr.should_verify("instcombine"));
let mut instr = X86PassInstrumentation::new();
instr.enable_verify_each();
assert!(instr.should_verify("instcombine"));
}
#[test]
fn test_instrumentation_skip_verify() {
let mut instr = X86PassInstrumentation::new();
instr.enable_verify_each();
instr.skip_verify_for("gvn");
assert!(!instr.should_verify("gvn"));
assert!(instr.should_verify("instcombine"));
}
#[test]
fn test_instrumentation_verification_errors() {
let mut instr = X86PassInstrumentation::new();
instr.record_verification_error("instcombine", "Test error");
assert!(instr.has_verification_errors());
assert_eq!(instr.get_verification_errors().len(), 1);
}
#[test]
fn test_instrumentation_clear_errors() {
let mut instr = X86PassInstrumentation::new();
instr.record_verification_error("test", "error");
instr.clear_verification_errors();
assert!(!instr.has_verification_errors());
}
#[test]
fn test_instrumentation_enable_all_debug() {
let mut instr = X86PassInstrumentation::new();
instr.enable_all_debug();
assert!(instr.verify_each);
assert!(instr.debug_before);
assert!(instr.debug_after);
assert!(!instr.debug_only_changed);
}
#[test]
fn test_instrumentation_debug_header() {
let instr = X86PassInstrumentation::new();
let header = instr.debug_header("gvn", true);
assert!(header.contains("BEFORE"));
assert!(header.contains("gvn"));
let header = instr.debug_header("instcombine", false);
assert!(header.contains("AFTER"));
assert!(header.contains("instcombine"));
}
#[test]
fn test_instrumentation_reset() {
let mut instr = X86PassInstrumentation::new();
instr.record_pass_execution(true);
instr.reset();
assert_eq!(instr.pass_count.load(Ordering::Relaxed), 0);
assert!(!instr.has_verification_errors());
}
#[test]
fn test_instrumentation_summary() {
let instr = X86PassInstrumentation::new();
instr.record_pass_execution(true);
let summary = instr.summary();
assert!(summary.contains("Passes executed"));
}
#[test]
fn test_context_o0() {
let ctx = X86PassContext::for_opt_level(OptimizationLevel::O0);
assert_eq!(ctx.opt_level, OptimizationLevel::O0);
assert_eq!(ctx.max_unroll_threshold, 0);
assert_eq!(ctx.inline_threshold, 0);
assert!(!ctx.prefer_size);
}
#[test]
fn test_context_o3() {
let ctx = X86PassContext::for_opt_level(OptimizationLevel::O3);
assert_eq!(ctx.max_unroll_threshold, 600);
assert_eq!(ctx.inline_threshold, 275);
assert!(!ctx.prefer_size);
}
#[test]
fn test_context_os() {
let ctx = X86PassContext::for_opt_level(OptimizationLevel::Os);
assert!(ctx.prefer_size);
assert_eq!(ctx.max_unroll_threshold, 50);
assert!(ctx.enable_machine_outliner);
}
#[test]
fn test_context_oz() {
let ctx = X86PassContext::for_opt_level(OptimizationLevel::Oz);
assert!(ctx.prefer_size);
assert_eq!(ctx.max_unroll_threshold, 0);
assert_eq!(ctx.inline_threshold, 25);
assert!(ctx.enable_machine_outliner);
}
#[test]
fn test_context_with_32bit() {
let ctx = X86PassContext::default().with_32bit();
assert!(!ctx.is_64bit);
}
#[test]
fn test_context_with_features() {
let ctx =
X86PassContext::default().with_features(vec!["avx2".to_string(), "bmi2".to_string()]);
assert_eq!(ctx.subtarget_features.len(), 2);
assert!(ctx.subtarget_features.contains(&"avx2".to_string()));
}
#[test]
fn test_pass_manager_creation() {
let mgr = X86PassManager::new_o2();
assert_eq!(mgr.context.opt_level, OptimizationLevel::O2);
assert_eq!(mgr.reg_alloc_strategy, RegAllocStrategy::Greedy);
}
#[test]
fn test_pass_manager_o0_uses_fast_isel() {
let mgr = X86PassManager::new_o0();
assert!(mgr.use_fast_isel);
}
#[test]
fn test_pass_manager_build_pipeline_o2() {
let mut mgr = X86PassManager::new_o2();
mgr.build_pipeline();
assert!(mgr.built);
assert!(!mgr.pipeline.is_empty());
assert!(mgr.pipeline.contains(&pass_ids::INSTCOMBINE));
assert!(mgr.pipeline.contains(&pass_ids::X86_DAG_ISEL));
}
#[test]
fn test_pass_manager_build_only_once() {
let mut mgr = X86PassManager::new_o2();
mgr.build_pipeline();
let count = mgr.pipeline.len();
mgr.build_pipeline();
assert_eq!(mgr.pipeline.len(), count);
}
#[test]
fn test_pass_manager_run() {
let mut mgr = X86PassManager::new_o1();
let result = mgr.run();
assert!(result.is_ok());
assert!(mgr.stats.success);
}
#[test]
fn test_pass_manager_run_all_levels() {
for level in &[
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
] {
let mut mgr = X86PassManager::new(X86PassContext::for_opt_level(*level));
let result = mgr.run();
assert!(result.is_ok(), "Pipeline failed for {:?}", level);
}
}
#[test]
fn test_pass_manager_run_o0() {
let mut mgr = create_o0_pipeline();
assert!(mgr.pipeline.len() >= 3);
let result = mgr.run();
assert!(result.is_ok());
}
#[test]
fn test_pass_manager_run_o2() {
let mut mgr = create_o2_pipeline();
let result = mgr.run();
assert!(result.is_ok());
assert!(mgr.stats.success);
}
#[test]
fn test_pass_manager_run_o3() {
let mut mgr = create_o3_pipeline();
let result = mgr.run();
assert!(result.is_ok());
}
#[test]
fn test_pass_manager_with_timing() {
let mut mgr = X86PassManager::new_o2().with_timing();
mgr.run().unwrap();
assert!(mgr.timing.total_pipeline_time > Duration::default());
}
#[test]
fn test_pass_manager_with_verify_each() {
let mut mgr = X86PassManager::new_o1().with_verify_each();
assert!(mgr.instrumentation.verify_each);
let result = mgr.run();
assert!(result.is_ok());
}
#[test]
fn test_pass_manager_with_debug() {
let mut mgr = X86PassManager::new_o1().with_debug();
assert!(mgr.instrumentation.debug_before);
assert!(mgr.instrumentation.debug_after);
}
#[test]
fn test_pass_manager_with_global_isel() {
let mut mgr = X86PassManager::new_o2().with_global_isel();
mgr.build_pipeline();
assert!(mgr.pipeline.contains(&pass_ids::X86_GLOBAL_ISEL));
assert!(!mgr.pipeline.contains(&pass_ids::X86_DAG_ISEL));
}
#[test]
fn test_pass_manager_reg_alloc_strategy() {
let mut mgr = X86PassManager::new_o2().with_reg_alloc(RegAllocStrategy::PBQP);
mgr.build_pipeline();
assert!(mgr.pipeline.contains(&pass_ids::REG_ALLOC_PBQP));
assert!(!mgr.pipeline.contains(&pass_ids::REG_ALLOC_GREEDY));
}
#[test]
fn test_pass_manager_reset() {
let mut mgr = X86PassManager::new_o2();
mgr.build_pipeline();
mgr.run().unwrap();
mgr.reset();
assert!(!mgr.built);
assert!(mgr.pipeline.is_empty());
}
#[test]
fn test_pass_manager_32bit_mode() {
let mut mgr =
X86PassManager::new(X86PassContext::for_opt_level(OptimizationLevel::O2).with_32bit());
mgr.build_pipeline();
assert!(!mgr.context.is_64bit);
}
#[test]
fn test_pipeline_builder_fluent() {
let mgr = X86PipelineBuilder::new(OptimizationLevel::O3)
.with_timing()
.with_verify_each()
.build();
assert!(mgr.timing.enabled);
assert!(mgr.instrumentation.verify_each);
assert!(mgr.built);
}
#[test]
fn test_pipeline_builder_o0() {
let mgr = X86PipelineBuilder::new(OptimizationLevel::O0).build();
assert!(mgr.use_fast_isel);
}
#[test]
fn test_pipeline_builder_with_features() {
let mgr = X86PipelineBuilder::new(OptimizationLevel::O2)
.with_features(vec!["avx512f".to_string()])
.build();
assert!(mgr
.context
.subtarget_features
.contains(&"avx512f".to_string()));
}
#[test]
fn test_create_o0_pipeline_func() {
let mgr = create_o0_pipeline();
assert!(mgr.pipeline.contains(&pass_ids::X86_FAST_ISEL));
}
#[test]
fn test_create_o1_pipeline_func() {
let mgr = create_o1_pipeline();
assert!(mgr.pipeline.contains(&pass_ids::INSTCOMBINE));
assert!(!mgr.pipeline.contains(&pass_ids::INLINE));
}
#[test]
fn test_create_os_pipeline_func() {
let mgr = create_os_pipeline();
assert!(mgr.pipeline.contains(&pass_ids::MACHINE_OUTLINER));
}
#[test]
fn test_create_oz_pipeline_func() {
let mgr = create_oz_pipeline();
assert!(mgr.pipeline.contains(&pass_ids::OUTLINE_FUNCTIONS));
}
#[test]
fn test_run_x86_pipeline_func() {
let (ok, stats, timing) = run_x86_pipeline(OptimizationLevel::O2);
assert!(ok);
assert!(stats.success);
assert!(!timing.is_empty());
}
#[test]
fn test_dump_registered_passes() {
let dump = dump_registered_passes();
assert!(dump.contains("Registered X86 Passes"));
assert!(dump.contains("mem2reg"));
assert!(dump.contains("x86-dag-isel"));
}
#[test]
fn test_pipeline_stats_default() {
let stats = X86PipelineStats::default();
assert!(!stats.success);
assert_eq!(stats.ir_passes_executed, 0);
assert_eq!(stats.errors.len(), 0);
}
#[test]
fn test_pipeline_stats_after_run() {
let mut mgr = X86PassManager::new_o1();
mgr.run().unwrap();
assert!(mgr.stats.ir_passes_executed > 0);
assert!(mgr.stats.success);
}
#[test]
fn test_pass_ids_unique() {
let ids = [
pass_ids::MEM2REG,
pass_ids::INSTCOMBINE,
pass_ids::SIMPLIFYCFG,
pass_ids::REASSOCIATE,
pass_ids::GVN,
pass_ids::SCCP,
pass_ids::EARLY_CSE,
pass_ids::LICM,
pass_ids::LOOP_ROTATE,
pass_ids::LOOP_SIMPLIFY,
pass_ids::INDVARS,
pass_ids::LOOP_DELETION,
pass_ids::LOOP_UNROLL,
pass_ids::INLINE,
pass_ids::JUMP_THREADING,
pass_ids::CORRELATED_PROPAGATION,
pass_ids::DSE,
pass_ids::ADCE,
pass_ids::SLP_VECTORIZER,
pass_ids::LOOP_VECTORIZE,
pass_ids::LOOP_UNROLL_AND_JAM,
pass_ids::GLOBALOPT,
pass_ids::IPSCCP,
pass_ids::DEADARGELIM,
pass_ids::GLOBALDCE,
pass_ids::ARGPROMOTION,
pass_ids::TAILCALLELIM,
pass_ids::MERGEFUNC,
pass_ids::LOOP_INTERCHANGE,
pass_ids::LOOP_UNSWITCH,
pass_ids::LOOP_DISTRIBUTE,
pass_ids::LOOP_FUSION,
pass_ids::LOOP_VERSIONING_LICM,
pass_ids::AGGRESSIVE_INSTCOMBINE,
pass_ids::PGO_MEMOP_OPT,
pass_ids::PHI_ELIMINATION,
pass_ids::TWO_ADDRESS_INSTRUCTION,
pass_ids::MACHINE_CSE,
pass_ids::MACHINE_LICM,
pass_ids::MACHINE_SINK,
pass_ids::PEEPHOLE_OPT,
pass_ids::MACHINE_CP,
pass_ids::MACHINE_DCE,
pass_ids::REG_ALLOC_GREEDY,
pass_ids::REG_ALLOC_PBQP,
pass_ids::REG_ALLOC_FAST,
pass_ids::REG_ALLOC_BASIC,
pass_ids::POST_RA_SCHEDULER,
pass_ids::BRANCH_FOLDING,
pass_ids::TAIL_DUPLICATION,
pass_ids::MACHINE_BLOCK_PLACEMENT,
pass_ids::POST_RA_MACHINE_CP,
pass_ids::POST_RA_MACHINE_SINK,
pass_ids::POST_RA_MACHINE_DCE,
pass_ids::MACHINE_OUTLINER,
pass_ids::X86_DAG_ISEL,
pass_ids::X86_FAST_ISEL,
pass_ids::X86_GLOBAL_ISEL,
pass_ids::X86_FLOATING_POINT,
pass_ids::X86_FIXUP_LEA,
pass_ids::X86_FIXUP_BW,
pass_ids::X86_FIXUP_SETCC,
pass_ids::X86_PAD_SHORT_FUNCTION,
pass_ids::X86_CALL_FRAME_OPT,
pass_ids::X86_CMOV_CONVERSION,
pass_ids::X86_DOMAIN_REASSIGN,
pass_ids::X86_AVOID_STORE_FORWARDING,
pass_ids::X86_VZERO_UPPER,
pass_ids::X86_FIXUP_VECTOR_CONSTANTS,
pass_ids::OUTLINE_FUNCTIONS,
pass_ids::MERGE_CONSTANTS,
];
let mut seen = HashSet::new();
for id in &ids {
assert!(seen.insert(*id), "Duplicate pass ID: {}", id);
}
}
#[test]
fn test_pipeline_phase_name() {
assert_eq!(PipelinePhase::EarlyIR.name(), "Early IR");
assert_eq!(PipelinePhase::MainIR.name(), "Main IR");
assert_eq!(PipelinePhase::RegAlloc.name(), "Register Allocation");
}
#[test]
fn test_pipeline_phase_ordering() {
assert!(PipelinePhase::EarlyIR < PipelinePhase::MainIR);
assert!(PipelinePhase::MainIR < PipelinePhase::LateIR);
assert!(PipelinePhase::LateIR < PipelinePhase::Lowering);
assert!(PipelinePhase::Lowering < PipelinePhase::PreRA);
assert!(PipelinePhase::PreRA < PipelinePhase::RegAlloc);
assert!(PipelinePhase::RegAlloc < PipelinePhase::PostRA);
assert!(PipelinePhase::PostRA < PipelinePhase::X86Fixup);
assert!(PipelinePhase::X86Fixup < PipelinePhase::Emission);
}
#[test]
fn test_pipeline_listing() {
let mut mgr = X86PassManager::new_o1();
mgr.build_pipeline();
let listing = mgr.pipeline_listing();
assert!(listing.contains("X86 Pipeline"));
assert!(listing.contains("Main IR"));
assert!(listing.contains("Instruction Selection"));
assert!(listing.contains("instcombine"));
}
#[test]
fn test_reg_alloc_strategy_for_opt_level() {
assert_eq!(
RegAllocStrategy::for_opt_level(OptimizationLevel::O0),
RegAllocStrategy::Fast
);
assert_eq!(
RegAllocStrategy::for_opt_level(OptimizationLevel::O2),
RegAllocStrategy::Greedy
);
}
#[test]
fn test_reg_alloc_strategy_name() {
assert_eq!(RegAllocStrategy::Greedy.name(), "greedy");
assert_eq!(RegAllocStrategy::PBQP.name(), "pbqp");
assert_eq!(RegAllocStrategy::Fast.name(), "fast");
assert_eq!(RegAllocStrategy::Basic.name(), "basic");
}
#[test]
fn test_reg_alloc_strategy_pass_id() {
assert_eq!(
RegAllocStrategy::Greedy.pass_id(),
pass_ids::REG_ALLOC_GREEDY
);
assert_eq!(RegAllocStrategy::Fast.pass_id(), pass_ids::REG_ALLOC_FAST);
}
#[test]
fn test_pass_descriptor_fields() {
let desc = X86PassDescriptor::new(42, "my-pass", "My Pass Description")
.with_phase(PipelinePhase::PreRA)
.with_opt_levels(vec![OptimizationLevel::O2, OptimizationLevel::O3])
.with_analysis()
.with_x86_specific()
.with_dependency("other-pass");
assert_eq!(desc.id, 42);
assert_eq!(desc.name, "my-pass");
assert_eq!(desc.description, "My Pass Description");
assert_eq!(desc.phase, PipelinePhase::PreRA);
assert!(!desc.enabled_at(OptimizationLevel::O0));
assert!(desc.enabled_at(OptimizationLevel::O2));
assert!(desc.is_analysis);
assert!(desc.is_x86_specific);
assert_eq!(desc.dependencies.len(), 1);
assert_eq!(desc.dependencies[0], "other-pass");
}
#[test]
fn test_pass_descriptor_default_opt_levels() {
let desc = X86PassDescriptor::new(1, "test", "test");
assert!(desc.enabled_at(OptimizationLevel::O1));
assert!(desc.enabled_at(OptimizationLevel::O2));
assert!(desc.enabled_at(OptimizationLevel::O3));
assert!(desc.enabled_at(OptimizationLevel::Os));
assert!(desc.enabled_at(OptimizationLevel::Oz));
assert!(!desc.enabled_at(OptimizationLevel::O0));
}
#[test]
fn test_timing_record_change_rate() {
let mut record = PassTimingRecord::default();
record.record(Duration::from_micros(100), true);
record.record(Duration::from_micros(100), false);
record.record(Duration::from_micros(100), true);
assert!((record.change_rate() - 66.666).abs() < 1.0);
}
#[test]
fn test_timing_record_empty() {
let record = PassTimingRecord::default();
assert_eq!(record.invocation_count, 0);
assert_eq!(record.avg_ms(), 0.0);
assert_eq!(record.std_dev_ms(), 0.0);
assert_eq!(record.change_rate(), 0.0);
}
#[test]
fn test_fixup_lea_creation() {
let pass = X86FixupLEAPass::new(true);
assert_eq!(pass.instructions_optimized, 0);
assert!(pass.is_64bit);
}
#[test]
fn test_fixup_lea_can_simplify() {
let pass = X86FixupLEAPass::new(true);
let instr = MachineInstruction::new(0x8D).with_operands(vec![
MachineOperand::Register(1),
MachineOperand::Memory {
base: Some(2),
index: None,
scale: 1,
displacement: 0,
},
]);
assert!(pass.can_simplify_lea(&instr));
let instr2 = MachineInstruction::new(0x89);
assert!(!pass.can_simplify_lea(&instr2));
}
#[test]
fn test_fixup_lea_estimate_savings() {
let pass = X86FixupLEAPass::new(true);
let instr = MachineInstruction::new(0x8D).with_operands(vec![
MachineOperand::Register(1),
MachineOperand::Memory {
base: Some(2),
index: None,
scale: 1,
displacement: 0,
},
]);
assert_eq!(pass.estimate_savings(&instr), 4);
}
#[test]
fn test_fixup_lea_pass_name() {
let pass = X86FixupLEAPass::new(true);
assert_eq!(pass.name(), "x86-fixup-lea");
assert_eq!(pass.pass_id(), pass_ids::X86_FIXUP_LEA);
}
#[test]
fn test_fixup_lea_run() {
let mut pass = X86FixupLEAPass::new(true);
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
block
.instructions
.push(MachineInstruction::new(0x8D).with_operands(vec![
MachineOperand::Register(1),
MachineOperand::Memory {
base: Some(2),
index: None,
scale: 1,
displacement: 0,
},
]));
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
assert!(pass.instructions_optimized > 0);
}
#[test]
fn test_fixup_bw_creation() {
let pass = X86FixupBWPass::new();
assert_eq!(pass.byte_ops_fixed, 0);
assert_eq!(pass.word_ops_fixed, 0);
}
#[test]
fn test_fixup_bw_could_cause_stall() {
let pass = X86FixupBWPass::new();
let mut instr = MachineInstruction::new(0x01);
instr.defs = vec![0];
assert!(pass.could_cause_partial_stall(&instr));
}
#[test]
fn test_fixup_bw_should_promote() {
let pass = X86FixupBWPass::new();
let mut instr = MachineInstruction::new(0x30);
instr.defs = vec![0];
assert!(pass.should_promote_byte_op(&instr));
let mut instr2 = MachineInstruction::new(0x50);
instr2.defs = vec![0];
assert!(!pass.should_promote_byte_op(&instr2));
}
#[test]
fn test_fixup_bw_run() {
let mut pass = X86FixupBWPass::new();
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
let mut instr = MachineInstruction::new(0x01);
instr.defs = vec![0];
block.instructions.push(instr);
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
}
#[test]
fn test_fixup_setcc_creation() {
let pass = X86FixupSetCCPass::new();
assert_eq!(pass.setcc_optimized, 0);
}
#[test]
fn test_fixup_setcc_is_xor_pattern() {
let pass = X86FixupSetCCPass::new();
let mut xor = MachineInstruction::new(0x31);
xor.defs = vec![0];
let mut setcc = MachineInstruction::new(0x94);
setcc.defs = vec![0];
let instrs = vec![xor, setcc];
assert!(pass.is_xor_setcc_pattern(&instrs, 1));
}
#[test]
fn test_fixup_setcc_needs_movzx() {
let pass = X86FixupSetCCPass::new();
let mut instr = MachineInstruction::new(0x94);
instr.defs = vec![0];
assert!(pass.needs_movzx_after(&instr));
}
#[test]
fn test_fixup_setcc_run() {
let mut pass = X86FixupSetCCPass::new();
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
let mut xor = MachineInstruction::new(0x31);
xor.defs = vec![0];
let mut setcc = MachineInstruction::new(0x94);
setcc.defs = vec![0];
block.instructions.push(xor);
block.instructions.push(setcc);
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
}
#[test]
fn test_pad_short_function_creation() {
let pass = X86PadShortFunctionPass::new();
assert_eq!(pass.functions_padded, 0);
assert_eq!(pass.min_function_size, 16);
}
#[test]
fn test_pad_short_function_padding_needed() {
let pass = X86PadShortFunctionPass::new();
assert_eq!(pass.padding_needed(4), 12);
assert_eq!(pass.padding_needed(16), 0);
}
#[test]
fn test_pad_short_function_nop_sequences() {
assert_eq!(X86PadShortFunctionPass::generate_nop_sequence(0).len(), 0);
assert_eq!(X86PadShortFunctionPass::generate_nop_sequence(1).len(), 1);
assert_eq!(X86PadShortFunctionPass::generate_nop_sequence(4).len(), 4);
assert_eq!(X86PadShortFunctionPass::generate_nop_sequence(8).len(), 8);
}
#[test]
fn test_pad_short_function_run() {
let mut pass = X86PadShortFunctionPass::new();
let mut mf = X86MachineFunctionState::new("test", true);
let block = MachineBlockState::new(0, "entry");
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
assert!(pass.functions_padded > 0);
}
#[test]
fn test_call_frame_opt_creation() {
let pass = X86CallFrameOptimizationPass::new();
assert_eq!(pass.leaf_functions_optimized, 0);
}
#[test]
fn test_call_frame_opt_is_leaf() {
let pass = X86CallFrameOptimizationPass::new();
let mf = X86MachineFunctionState::new("test", true);
assert!(pass.is_leaf(&mf));
let mut mf2 = X86MachineFunctionState::new("test2", true);
mf2.has_calls = true;
assert!(!pass.is_leaf(&mf2));
}
#[test]
fn test_call_frame_opt_can_eliminate_fp() {
let pass = X86CallFrameOptimizationPass::new();
let mf = X86MachineFunctionState::new("leaf", true);
assert!(pass.can_eliminate_frame_pointer(&mf));
let mut mf2 = X86MachineFunctionState::new("big", true);
mf2.frame_size = 256;
assert!(!pass.can_eliminate_frame_pointer(&mf2));
}
#[test]
fn test_call_frame_opt_run() {
let mut pass = X86CallFrameOptimizationPass::new();
let mut mf = X86MachineFunctionState::new("leaf", true);
mf.blocks.push(MachineBlockState::new(0, "entry"));
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
assert!(pass.frame_pointer_eliminated > 0);
}
#[test]
fn test_cmov_conversion_creation() {
let pass = X86CmovConversionPass::new(true);
assert_eq!(pass.cmovs_converted, 0);
assert!(pass.is_64bit);
}
#[test]
fn test_cmov_conversion_should_convert() {
let pass = X86CmovConversionPass::new(true);
let instr = MachineInstruction::new(0x44);
assert!(pass.should_convert(&instr, 0.95));
assert!(!pass.should_convert(&instr, 0.50));
}
#[test]
fn test_cmov_conversion_is_cmov() {
let pass = X86CmovConversionPass::new(true);
let instr = MachineInstruction::new(0x44);
assert!(pass.is_cmov(&instr));
let instr2 = MachineInstruction::new(0x89);
assert!(!pass.is_cmov(&instr2));
}
#[test]
fn test_cmov_conversion_to_branch_seq() {
let pass = X86CmovConversionPass::new(true);
let instr = MachineInstruction::new(0x44).with_operands(vec![
MachineOperand::Register(1),
MachineOperand::Register(2),
]);
let seq = pass.cmov_to_branch_sequence(&instr);
assert!(!seq.is_empty());
assert_eq!(seq.len(), 2); }
#[test]
fn test_cmov_conversion_run() {
let mut pass = X86CmovConversionPass::new(true);
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
block
.instructions
.push(MachineInstruction::new(0x44).with_operands(vec![
MachineOperand::Register(1),
MachineOperand::Register(2),
]));
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
assert!(pass.cmovs_converted > 0);
}
#[test]
fn test_domain_reassignment_creation() {
let pass = X86DomainReassignmentPass::new(true, true);
assert!(pass.has_sse);
assert!(pass.has_avx);
}
#[test]
fn test_domain_reassignment_domain() {
let pass = X86DomainReassignmentPass::new(true, false);
let float_instr = MachineInstruction::new(0x58);
assert_eq!(
pass.instruction_domain(&float_instr),
InstructionDomain::Float
);
let int_instr = MachineInstruction::new(0x01);
assert_eq!(
pass.instruction_domain(&int_instr),
InstructionDomain::Integer
);
let unknown = MachineInstruction::new(0xFF);
assert_eq!(
pass.instruction_domain(&unknown),
InstructionDomain::Unknown
);
}
#[test]
fn test_domain_reassignment_count_crossings() {
let pass = X86DomainReassignmentPass::new(true, false);
let instrs = vec![
MachineInstruction::new(0x58), MachineInstruction::new(0x01), MachineInstruction::new(0x59), ];
assert_eq!(pass.count_domain_crossings(&instrs), 2);
}
#[test]
fn test_domain_reassignment_run() {
let mut pass = X86DomainReassignmentPass::new(true, false);
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
block.instructions.push(MachineInstruction::new(0x58));
block.instructions.push(MachineInstruction::new(0x01));
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
assert!(pass.domain_penalties_saved > 0);
}
#[test]
fn test_avoid_sf_creation() {
let pass = X86AvoidStoreForwardingStallsPass::new();
assert_eq!(pass.stalls_detected, 0);
}
#[test]
fn test_avoid_sf_detect_stall() {
let pass = X86AvoidStoreForwardingStallsPass::new();
let mut store = MachineInstruction::new(0x88); store.may_store = true;
store.operands.push(MachineOperand::Memory {
base: Some(0),
index: None,
scale: 1,
displacement: 0,
});
let mut load = MachineInstruction::new(0x8B); load.may_load = true;
load.operands.push(MachineOperand::Memory {
base: Some(0),
index: None,
scale: 1,
displacement: 0,
});
assert!(pass.detect_store_forwarding_stall(&store, &load));
}
#[test]
fn test_avoid_sf_run() {
let mut pass = X86AvoidStoreForwardingStallsPass::new();
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
let mut store = MachineInstruction::new(0x88);
store.may_store = true;
store.operands.push(MachineOperand::Memory {
base: Some(0),
index: None,
scale: 1,
displacement: 0,
});
block.instructions.push(store);
let mut load = MachineInstruction::new(0x8B);
load.may_load = true;
load.operands.push(MachineOperand::Memory {
base: Some(0),
index: None,
scale: 1,
displacement: 0,
});
block.instructions.push(load);
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
assert!(pass.stalls_detected > 0);
}
#[test]
fn test_vzeroupper_creation() {
let pass = X86VZeroUpperInsertionPass::new(true);
assert!(pass.has_avx);
}
#[test]
fn test_vzeroupper_is_avx256() {
let pass = X86VZeroUpperInsertionPass::new(true);
let instr = MachineInstruction::new(0xC4);
assert!(pass.is_avx256_instruction(&instr));
let instr2 = MachineInstruction::new(0x89);
assert!(!pass.is_avx256_instruction(&instr2));
}
#[test]
fn test_vzeroupper_block_uses_avx() {
let pass = X86VZeroUpperInsertionPass::new(true);
let mut block = MachineBlockState::new(0, "entry");
block.instructions.push(MachineInstruction::new(0xC4));
assert!(pass.block_uses_avx256(&block));
let mut block2 = MachineBlockState::new(1, "exit");
block2.instructions.push(MachineInstruction::new(0x89));
assert!(!pass.block_uses_avx256(&block2));
}
#[test]
fn test_vzeroupper_run_without_avx() {
let mut pass = X86VZeroUpperInsertionPass::new(false);
let mut mf = X86MachineFunctionState::new("test", true);
mf.blocks.push(MachineBlockState::new(0, "entry"));
assert!(!pass.run_on_machine_function(&mut mf));
}
#[test]
fn test_vzeroupper_run_with_avx() {
let mut pass = X86VZeroUpperInsertionPass::new(true);
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
block.instructions.push(MachineInstruction::new(0xC4));
block.instructions.push(MachineInstruction::new(0xC3)); mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
}
#[test]
fn test_fixup_vector_const_creation() {
let pass = X86FixupVectorConstantsPass::new(true, true, false);
assert!(pass.has_sse);
assert!(pass.has_avx);
assert!(!pass.has_avx512);
}
#[test]
fn test_fixup_vector_const_can_broadcast() {
let pass = X86FixupVectorConstantsPass::new(true, true, false);
assert!(pass.can_broadcast(&[]));
let pass2 = X86FixupVectorConstantsPass::new(true, false, false);
assert!(!pass2.can_broadcast(&[]));
}
#[test]
fn test_fixup_vector_const_is_pool_load() {
let pass = X86FixupVectorConstantsPass::new(true, false, false);
let mut instr = MachineInstruction::new(0x10);
instr.may_load = true;
instr.operands.push(MachineOperand::Memory {
base: Some(0),
index: None,
scale: 1,
displacement: 0,
});
assert!(pass.is_constant_pool_load(&instr));
}
#[test]
fn test_fixup_vector_const_run() {
let mut pass = X86FixupVectorConstantsPass::new(true, true, false);
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
let mut instr = MachineInstruction::new(0x10);
instr.may_load = true;
instr.operands.push(MachineOperand::Memory {
base: Some(0),
index: None,
scale: 1,
displacement: 0,
});
block.instructions.push(instr);
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(result);
assert!(pass.constants_optimized > 0);
}
#[test]
fn test_fp_stack_creation() {
let pass = X86FloatingPointPass::new(true);
assert!(pass.uses_x87);
assert_eq!(pass.max_stack_depth, 8);
}
#[test]
fn test_fp_stack_is_x87() {
let pass = X86FloatingPointPass::new(true);
assert!(pass.is_x87_instruction(&MachineInstruction::new(0xD8)));
assert!(pass.is_x87_instruction(&MachineInstruction::new(0xDF)));
assert!(!pass.is_x87_instruction(&MachineInstruction::new(0x89)));
}
#[test]
fn test_fp_stack_simulated_depth() {
let pass = X86FloatingPointPass::new(true);
let fld =
MachineInstruction::new(0xD9).with_operands(vec![MachineOperand::Immediate(0xC0)]);
let fstp =
MachineInstruction::new(0xDD).with_operands(vec![MachineOperand::Immediate(0xD8)]);
let depth = pass.simulated_stack_depth(&[fld.clone(), fld.clone(), fstp]);
assert_eq!(depth, 1);
}
#[test]
fn test_fp_stack_run_without_x87() {
let mut pass = X86FloatingPointPass::new(false);
let mut mf = X86MachineFunctionState::new("test", true);
mf.blocks.push(MachineBlockState::new(0, "entry"));
assert!(!pass.run_on_machine_function(&mut mf));
}
#[test]
fn test_dag_isel_config_default() {
let config = X86DAGISelConfig::default();
assert!(config.is_64bit);
assert!(config.enable_combiner);
assert_eq!(config.max_combine_iterations, 5);
}
#[test]
fn test_dag_isel_config_for_opt() {
let o0 = X86DAGISelConfig::for_opt_level(OptimizationLevel::O0);
assert_eq!(o0.max_combine_iterations, 0);
let o3 = X86DAGISelConfig::for_opt_level(OptimizationLevel::O3);
assert_eq!(o3.max_combine_iterations, 8);
}
#[test]
fn test_fast_isel_config_default() {
let config = X86FastISelConfig::default();
assert!(config.is_64bit);
assert!(config.fallback_to_dag);
}
#[test]
fn test_global_isel_config_default() {
let config = X86GlobalISelConfig::default();
assert!(config.is_64bit);
assert!(config.enable_legalizer);
}
#[test]
fn test_phi_elimination_creation() {
let pass = PhiEliminationPass::new(true);
assert_eq!(pass.phis_eliminated, 0);
}
#[test]
fn test_phi_elimination_analyze() {
let pass = PhiEliminationPass::new(true);
let mut instr = MachineInstruction::new(0x00).with_operands(vec![
MachineOperand::Register(1),
MachineOperand::Register(2),
]);
instr.defs = vec![0];
let mut block = MachineBlockState::new(0, "entry");
block.instructions.push(instr);
let copies = pass.analyze_phi(&block);
assert!(!copies.is_empty());
}
#[test]
fn test_two_address_creation() {
let pass = TwoAddressInstructionPass::new(true);
assert_eq!(pass.instructions_rewritten, 0);
}
#[test]
fn test_two_address_is_three_addr() {
let pass = TwoAddressInstructionPass::new(true);
let mut instr = MachineInstruction::new(0x01);
instr.defs = vec![0];
instr.uses = vec![1, 2];
assert!(pass.is_three_address(&instr));
let mut instr2 = MachineInstruction::new(0x01);
instr2.defs = vec![0];
instr2.uses = vec![0];
assert!(!pass.is_three_address(&instr2));
}
#[test]
fn test_two_address_rewrite() {
let pass = TwoAddressInstructionPass::new(true);
let mut instr = MachineInstruction::new(0x01);
instr.defs = vec![0];
instr.uses = vec![1, 2];
let seq = pass.rewrite_to_two_address(&instr);
assert_eq!(seq.len(), 2); }
#[test]
fn test_machine_block_placement_creation() {
let pass = MachineBlockPlacementPass::new(true);
assert_eq!(pass.blocks_reordered, 0);
}
#[test]
fn test_machine_block_placement_layout() {
let pass = MachineBlockPlacementPass::new(true);
let mut b0 = MachineBlockState::new(0, "entry");
b0.successors = vec![1, 2];
let b1 = MachineBlockState::new(1, "b1");
let b2 = MachineBlockState::new(2, "b2");
let layout = pass.compute_optimal_layout(&[b0, b1, b2]);
assert_eq!(layout.len(), 3);
assert_eq!(layout[0], 0); }
#[test]
fn test_machine_block_placement_fallthrough() {
let pass = MachineBlockPlacementPass::new(true);
let mut b0 = MachineBlockState::new(0, "entry");
b0.successors = vec![1];
let b1 = MachineBlockState::new(1, "b1");
let b2 = MachineBlockState::new(2, "b2");
let layout = pass.compute_optimal_layout(&[b0, b1, b2]);
let ft = pass.fallthrough_count(
&[
MachineBlockState::new(0, "a"),
MachineBlockState::new(1, "b"),
MachineBlockState::new(2, "c"),
],
&layout,
);
assert!(ft >= 0);
}
#[test]
fn test_pipeline_analyzer_creation() {
let registry = X86PassRegistry::default();
let analyzer = PipelineAnalyzer::new(registry);
let groups = analyzer.find_parallel_pass_groups();
assert!(!groups.is_empty());
}
#[test]
fn test_pipeline_analyzer_parallel_groups() {
let registry = X86PassRegistry::default();
let analyzer = PipelineAnalyzer::new(registry);
let groups = analyzer.find_parallel_pass_groups();
assert!(groups.len() > 1);
}
#[test]
fn test_pipeline_analyzer_redundant_passes() {
let registry = X86PassRegistry::default();
let analyzer = PipelineAnalyzer::new(registry);
let pipeline = vec![
pass_ids::MEM2REG,
pass_ids::INSTCOMBINE,
pass_ids::SIMPLIFYCFG,
pass_ids::INSTCOMBINE, ];
let redundant = analyzer.find_redundant_passes(&pipeline);
assert!(!redundant.is_empty());
}
#[test]
fn test_pipeline_analyzer_dependency_graph() {
let registry = X86PassRegistry::default();
let analyzer = PipelineAnalyzer::new(registry);
let pipeline = vec![pass_ids::GVN, pass_ids::MEM2REG];
let graph = analyzer.dependency_graph(&pipeline);
assert!(graph.contains_key(&pass_ids::GVN));
}
#[test]
fn test_pipeline_analyzer_validate() {
let registry = X86PassRegistry::default();
let analyzer = PipelineAnalyzer::new(registry);
let pipeline = vec![pass_ids::MEM2REG, pass_ids::GVN];
assert!(analyzer.validate_pipeline(&pipeline).is_ok());
}
#[test]
fn test_detailed_stats_creation() {
let stats = X86DetailedStats::new();
assert_eq!(stats.total.total_passes_executed, 0);
}
#[test]
fn test_detailed_stats_record() {
let mut stats = X86DetailedStats::new();
stats.record_pass(pass_ids::GVN, PipelinePhase::MainIR, 1.5, true, 0.0);
stats.record_pass(pass_ids::GVN, PipelinePhase::MainIR, 2.0, false, 0.0);
assert_eq!(stats.total.total_passes_executed, 2);
assert_eq!(stats.total.total_changes, 1);
assert!((stats.total.total_time_ms - 3.5).abs() < 0.01);
let gvn_stats = stats.per_pass.get(&pass_ids::GVN).unwrap();
assert_eq!(gvn_stats.invocations, 2);
assert_eq!(gvn_stats.changes, 1);
}
#[test]
fn test_detailed_stats_report() {
let mut stats = X86DetailedStats::new();
stats.record_pass(pass_ids::GVN, PipelinePhase::MainIR, 1.0, true, 0.0);
let report = stats.report();
assert!(report.contains("X86 Pipeline Detailed Statistics"));
assert!(report.contains("Main IR"));
}
#[test]
fn test_machine_function_state_creation() {
let mf = X86MachineFunctionState::new("test_func", true);
assert_eq!(mf.name, "test_func");
assert!(mf.is_64bit);
assert_eq!(mf.total_instructions(), 0);
}
#[test]
fn test_machine_function_state_total_instrs() {
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
block.instructions.push(MachineInstruction::new(0x01));
block.instructions.push(MachineInstruction::new(0x02));
mf.blocks.push(block);
assert_eq!(mf.total_instructions(), 2);
}
#[test]
fn test_machine_instruction_creation() {
let instr = MachineInstruction::new(0x89)
.with_operands(vec![MachineOperand::Register(1)])
.with_defs(vec![0])
.with_uses(vec![1]);
assert_eq!(instr.opcode, 0x89);
assert_eq!(instr.operands.len(), 1);
assert_eq!(instr.defs.len(), 1);
assert_eq!(instr.uses.len(), 1);
}
#[test]
fn test_machine_operand_variants() {
let reg = MachineOperand::Register(5);
let imm = MachineOperand::Immediate(42);
let mem = MachineOperand::Memory {
base: Some(0),
index: Some(1),
scale: 4,
displacement: 16,
};
let label = MachineOperand::Label("L0".to_string());
let cc = MachineOperand::Condition(0);
if let MachineOperand::Register(r) = reg {
assert_eq!(r, 5);
}
if let MachineOperand::Immediate(v) = imm {
assert_eq!(v, 42);
}
if let MachineOperand::Label(l) = label {
assert_eq!(l, "L0");
}
if let MachineOperand::Condition(c) = cc {
assert_eq!(c, 0);
}
if let MachineOperand::Memory {
base,
index,
scale,
displacement,
} = mem
{
assert_eq!(base, Some(0));
assert_eq!(index, Some(1));
assert_eq!(scale, 4);
assert_eq!(displacement, 16);
}
}
#[test]
fn test_all_pipelines_have_entry_point_passes() {
for level in &[
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
] {
let mut mgr = X86PassManager::new(X86PassContext::for_opt_level(*level));
mgr.build_pipeline();
assert!(
mgr.pipeline.contains(&pass_ids::MEM2REG)
|| mgr.pipeline.contains(&pass_ids::X86_FAST_ISEL)
);
}
}
#[test]
fn test_o0_pipeline_is_minimal() {
let mgr = create_o0_pipeline();
let o0_count = mgr.pipeline.len();
let mgr3 = create_o3_pipeline();
let o3_count = mgr3.pipeline.len();
assert!(
o0_count < o3_count,
"O0 ({}) should be smaller than O3 ({})",
o0_count,
o3_count
);
}
#[test]
fn test_o1_o2_o3_are_progressive() {
let o1 = create_o1_pipeline().pipeline().len();
let o2 = create_o2_pipeline().pipeline().len();
let o3 = create_o3_pipeline().pipeline().len();
assert!(o1 <= o2, "O1 passes ({}) <= O2 passes ({})", o1, o2);
assert!(o2 <= o3, "O2 passes ({}) <= O3 passes ({})", o2, o3);
}
#[test]
fn test_os_oz_are_size_optimized() {
let os = create_os_pipeline();
let oz = create_oz_pipeline();
assert!(!os.pipeline().contains(&pass_ids::ARGPROMOTION));
assert!(!oz.pipeline().contains(&pass_ids::LOOP_INTERCHANGE));
assert!(os.pipeline().contains(&pass_ids::MACHINE_OUTLINER));
assert!(oz.pipeline().contains(&pass_ids::MACHINE_OUTLINER));
assert!(oz.pipeline().contains(&pass_ids::OUTLINE_FUNCTIONS));
}
#[test]
fn test_all_pipelines_end_with_fixups() {
for level in &[
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
] {
let mut mgr = X86PassManager::new(X86PassContext::for_opt_level(*level));
mgr.build_pipeline();
let last_few: Vec<u64> = mgr.pipeline.iter().rev().take(5).copied().collect();
let x86_fixup_ids = [
pass_ids::X86_FIXUP_LEA,
pass_ids::X86_FIXUP_BW,
pass_ids::X86_FIXUP_SETCC,
pass_ids::X86_PAD_SHORT_FUNCTION,
pass_ids::X86_CALL_FRAME_OPT,
pass_ids::X86_CMOV_CONVERSION,
pass_ids::X86_DOMAIN_REASSIGN,
pass_ids::X86_AVOID_STORE_FORWARDING,
pass_ids::X86_VZERO_UPPER,
pass_ids::X86_FIXUP_VECTOR_CONSTANTS,
pass_ids::X86_FLOATING_POINT,
];
let has_x86_fixup = last_few.iter().any(|id| x86_fixup_ids.contains(id));
assert!(
has_x86_fixup,
"Pipeline for {:?} should end with x86 fixups",
level
);
}
}
#[test]
fn test_all_nop_sizes_produce_correct_length() {
for size in 0..=20u32 {
let nops = X86PadShortFunctionPass::generate_nop_sequence(size);
assert_eq!(
nops.len() as u32,
size,
"NOP sequence for size {} should have exactly {} bytes",
size,
size
);
}
}
#[test]
fn test_instruction_domain_enum_variants() {
assert_ne!(InstructionDomain::Integer, InstructionDomain::Float);
assert_ne!(InstructionDomain::Integer, InstructionDomain::Unknown);
assert_ne!(InstructionDomain::Float, InstructionDomain::Unknown);
}
#[test]
fn test_every_registered_pass_is_recognized() {
let registry = X86PassRegistry::default();
let mgr = X86PassManager::new_o2();
for id in registry.pipeline_order() {
let name = mgr.pass_name(*id);
assert!(!name.is_empty(), "Pass ID {} has no name", id);
}
}
#[test]
fn test_pass_manager_multiple_runs() {
let mut mgr = X86PassManager::new_o1();
assert!(mgr.run().is_ok());
mgr.reset();
assert!(mgr.run().is_ok());
}
#[test]
fn test_pipeline_with_all_features_enabled() {
let mut mgr = X86PassManager::new(X86PassContext {
opt_level: OptimizationLevel::O3,
is_64bit: true,
subtarget_features: vec![
"avx512f".to_string(),
"avx2".to_string(),
"bmi2".to_string(),
],
enable_machine_outliner: true,
prefer_size: false,
max_unroll_threshold: 600,
inline_threshold: 275,
has_pgo_data: true,
});
mgr.build_pipeline();
assert!(mgr.run().is_ok());
assert!(mgr.stats.success);
}
#[test]
fn test_pipeline_listing_is_deterministic() {
let mut mgr1 = X86PassManager::new_o2();
mgr1.build_pipeline();
let listing1 = mgr1.pipeline_listing();
let mut mgr2 = X86PassManager::new_o2();
mgr2.build_pipeline();
let listing2 = mgr2.pipeline_listing();
assert_eq!(listing1, listing2);
}
#[test]
fn test_reg_alloc_config_for_opt_levels() {
let o0 = GreedyRegAllocConfig::for_opt_level(OptimizationLevel::O0);
assert_eq!(o0.max_split_iterations, 0);
assert!(!o0.enable_eviction);
let o3 = GreedyRegAllocConfig::for_opt_level(OptimizationLevel::O3);
assert_eq!(o3.max_split_iterations, 3);
assert!(o3.enable_eviction);
let os = GreedyRegAllocConfig::for_opt_level(OptimizationLevel::Os);
assert_eq!(os.max_split_iterations, 2);
}
#[test]
fn test_reg_alloc_result_creation() {
let result = RegAllocResult::new();
assert!(result.success);
assert_eq!(result.spills, 0);
assert!(result.is_clean());
}
#[test]
fn test_reg_alloc_result_merge() {
let mut r1 = RegAllocResult::new();
r1.spills = 3;
r1.reloads = 2;
let mut r2 = RegAllocResult::new();
r2.spills = 1;
r2.reloads = 1;
r1.merge(&r2);
assert_eq!(r1.spills, 4);
assert_eq!(r1.reloads, 3);
}
#[test]
fn test_fast_reg_alloc_config() {
let o0 = FastRegAllocConfig::for_opt_level(OptimizationLevel::O0);
assert_eq!(o0.max_alloc_attempts, 1);
assert!(!o0.enable_coalescing);
let o2 = FastRegAllocConfig::for_opt_level(OptimizationLevel::O2);
assert_eq!(o2.max_alloc_attempts, 2);
assert!(o2.enable_coalescing);
}
#[test]
fn test_post_ra_scheduler_creation() {
let sched = PostRASchedulerPass::new(true);
assert!(sched.is_64bit);
assert_eq!(sched.instructions_reordered, 0);
}
#[test]
fn test_post_ra_scheduler_dependencies() {
let sched = PostRASchedulerPass::new(true);
let mut i1 = MachineInstruction::new(0x01);
i1.defs = vec![0];
let mut i2 = MachineInstruction::new(0x01);
i2.uses = vec![0];
let instrs = vec![i1, i2];
let deps = sched.compute_dependencies(&instrs);
assert_eq!(deps.len(), 2);
assert!(deps[0].contains(&1));
}
#[test]
fn test_post_ra_scheduler_latency() {
let sched = PostRASchedulerPass::new(true);
assert_eq!(sched.estimate_latency(&MachineInstruction::new(0x89)), 1);
assert_eq!(sched.estimate_latency(&MachineInstruction::new(0xF7)), 3);
assert_eq!(sched.estimate_latency(&MachineInstruction::new(0x58)), 4);
}
#[test]
fn test_post_ra_scheduler_list_schedule() {
let sched = PostRASchedulerPass::new(true);
let mut i1 = MachineInstruction::new(0x01);
i1.defs = vec![0];
let mut i2 = MachineInstruction::new(0x01);
i2.uses = vec![0];
let instrs = vec![i1.clone(), i2.clone()];
let schedule = sched.list_schedule(&instrs);
assert_eq!(schedule.len(), 2);
let pos1 = schedule.iter().position(|x| *x == 0).unwrap();
let pos2 = schedule.iter().position(|x| *x == 1).unwrap();
assert!(pos1 < pos2);
}
#[test]
fn test_sched_model_hint_values() {
assert_ne!(SchedModelHint::Default, SchedModelHint::Throughput);
assert_ne!(SchedModelHint::Throughput, SchedModelHint::Latency);
assert_ne!(SchedModelHint::Latency, SchedModelHint::Power);
}
#[test]
fn test_branch_folding_creation() {
let pass = BranchFoldingPass::new();
assert_eq!(pass.branches_eliminated, 0);
}
#[test]
fn test_branch_folding_can_eliminate_jump() {
let pass = BranchFoldingPass::new();
let mut instr = MachineInstruction::new(0xE9);
instr.is_branch = true;
instr.is_terminator = true;
instr
.operands
.push(MachineOperand::Label("BB1".to_string()));
assert!(pass.can_eliminate_jump(&instr, 1));
assert!(!pass.can_eliminate_jump(&instr, 2));
}
#[test]
fn test_branch_folding_label_to_block_id() {
assert_eq!(label_to_block_id("BB0"), 0);
assert_eq!(label_to_block_id(".L42"), 42);
assert_eq!(label_to_block_id("L7"), 7);
}
#[test]
fn test_branch_folding_can_merge_blocks() {
let pass = BranchFoldingPass::new();
let mut b1 = MachineBlockState::new(0, "b1");
b1.successors = vec![1];
let mut b2 = MachineBlockState::new(1, "b2");
b2.predecessors = vec![0];
b2.instructions.push(MachineInstruction::new(0x89));
assert!(pass.can_merge_blocks(&b1, &b2));
}
#[test]
fn test_tail_duplication_creation() {
let pass = TailDuplicationPass::new();
assert_eq!(pass.max_duplication_instructions, 8);
}
#[test]
fn test_tail_duplication_candidate() {
let pass = TailDuplicationPass::new();
let mut block = MachineBlockState::new(0, "tail");
block.predecessors = vec![0, 1];
block.instructions.push(MachineInstruction::new(0x89));
assert!(pass.is_duplication_candidate(&block));
let mut big_block = MachineBlockState::new(1, "big");
big_block.predecessors = vec![0, 1];
for _ in 0..10 {
big_block.instructions.push(MachineInstruction::new(0x89));
}
assert!(!pass.is_duplication_candidate(&big_block));
let mut single_pred = MachineBlockState::new(2, "single");
single_pred.predecessors = vec![0];
assert!(!pass.is_duplication_candidate(&single_pred));
}
#[test]
fn test_tail_duplication_estimate_size() {
let pass = TailDuplicationPass::new();
let mut block = MachineBlockState::new(0, "tail");
block.predecessors = vec![0, 1];
let mut instr = MachineInstruction::new(0x89);
instr.size_bytes = 3;
block.instructions.push(instr);
assert_eq!(pass.estimate_size_increase(&block), 3);
}
#[test]
fn test_machine_outliner_creation() {
let pass = MachineOutlinerPass::new();
assert_eq!(pass.min_sequence_length, 3);
assert_eq!(pass.min_occurrences, 2);
}
#[test]
fn test_machine_outliner_find_repeated() {
let pass = MachineOutlinerPass::new();
let instrs: Vec<MachineInstruction> = (0..10)
.map(|i| {
let mut instr = MachineInstruction::new(0x89 + (i % 3) as u32);
instr.size_bytes = 3;
instr
})
.collect();
let sequences = pass.find_repeated_sequences(&instrs);
assert!(!sequences.is_empty());
}
#[test]
fn test_machine_outliner_estimate_savings() {
let pass = MachineOutlinerPass::new();
let mut instrs: Vec<MachineInstruction> = Vec::new();
for _ in 0..5 {
let mut instr = MachineInstruction::new(0x89);
instr.size_bytes = 4;
instrs.push(instr);
}
let savings = pass.estimate_savings(&instrs, 0, 3, 3);
assert_eq!(savings, 6);
}
#[test]
fn test_post_ra_machine_cp_creation() {
let pass = PostRAMachineCopyPropagationPass::new();
assert_eq!(pass.copies_eliminated, 0);
}
#[test]
fn test_post_ra_machine_cp_can_eliminate() {
let pass = PostRAMachineCopyPropagationPass::new();
let mut instr = MachineInstruction::new(0x89);
instr.is_move = true;
instr.defs = vec![5];
instr.uses = vec![5];
instr.operands.push(MachineOperand::Register(5));
assert!(pass.can_eliminate_copy(&instr));
let mut instr2 = MachineInstruction::new(0x89);
instr2.is_move = true;
instr2.defs = vec![5];
instr2.uses = vec![3];
instr2.operands.push(MachineOperand::Register(3));
assert!(!pass.can_eliminate_copy(&instr2));
}
#[test]
fn test_post_ra_machine_sink_creation() {
let pass = PostRAMachineSinkingPass::new();
assert_eq!(pass.instructions_sunk, 0);
}
#[test]
fn test_post_ra_machine_sink_can_sink() {
let pass = PostRAMachineSinkingPass::new();
let instr = MachineInstruction::new(0x89);
assert!(pass.can_sink_into_successor(&instr, &[1]));
assert!(!pass.can_sink_into_successor(&instr, &[1, 2])); }
#[test]
fn test_subtarget_config_default() {
let config = X86SubtargetPassConfig::default();
assert!(config.enable_sse_passes);
assert!(!config.enable_avx_passes);
assert!(config.use_cmov);
}
#[test]
fn test_subtarget_config_from_features_basic() {
let config = X86SubtargetPassConfig::from_features(&["sse2".to_string()]);
assert!(config.enable_sse_passes);
assert!(!config.enable_x87_passes);
assert!(!config.enable_avx_passes);
}
#[test]
fn test_subtarget_config_from_features_avx() {
let config =
X86SubtargetPassConfig::from_features(&["avx2".to_string(), "sse4.2".to_string()]);
assert!(config.enable_sse_passes);
assert!(config.enable_avx_passes);
assert!(!config.enable_avx512_passes);
}
#[test]
fn test_subtarget_config_from_features_avx512() {
let config = X86SubtargetPassConfig::from_features(&["avx512f".to_string()]);
assert!(config.enable_avx512_passes);
}
#[test]
fn test_subtarget_config_from_features_atom() {
let config =
X86SubtargetPassConfig::from_features(&["atom".to_string(), "sse2".to_string()]);
assert!(config.enable_cmov_conversion); assert!(!config.use_cmov);
assert!(config.slow_3ops_lea);
}
#[test]
fn test_subtarget_config_from_features_amd() {
let config =
X86SubtargetPassConfig::from_features(&["znver3".to_string(), "avx2".to_string()]);
assert!(!config.use_inc_dec); assert!(config.slow_incdec);
}
#[test]
fn test_subtarget_config_enabled_fixups() {
let config = X86SubtargetPassConfig::from_features(&["avx2".to_string()]);
let fixups = config.enabled_x86_fixups();
assert!(fixups.contains(&pass_ids::X86_VZERO_UPPER));
assert!(fixups.contains(&pass_ids::X86_DOMAIN_REASSIGN));
assert!(!fixups.contains(&pass_ids::X86_FLOATING_POINT));
let config2 = X86SubtargetPassConfig::from_features(&[]);
let fixups2 = config2.enabled_x86_fixups();
assert!(!fixups2.contains(&pass_ids::X86_VZERO_UPPER));
assert!(fixups2.contains(&pass_ids::X86_FLOATING_POINT));
}
#[test]
fn test_subtarget_config_inline_threshold_adj() {
let base = X86SubtargetPassConfig::default();
assert_eq!(base.inline_threshold_adjustment(), 0);
let tuned = X86SubtargetPassConfig::from_features(&["tune-size".to_string()]);
assert_eq!(tuned.inline_threshold_adjustment(), -50);
}
#[test]
fn test_pipeline_version_creation() {
let version = PipelineVersion::current(OptimizationLevel::O2);
assert_eq!(version.major, 1);
assert_eq!(version.minor, 0);
}
#[test]
fn test_pipeline_version_string() {
let version = PipelineVersion::current(OptimizationLevel::O3);
let s = version.version_string();
assert!(s.contains("x86-pipeline"));
assert!(s.contains("O3"));
}
#[test]
fn test_pipeline_version_compatibility() {
let v1 = PipelineVersion::new(1, 0, 0, OptimizationLevel::O2);
let v2 = PipelineVersion::new(1, 1, 0, OptimizationLevel::O2);
assert!(v1.is_compatible(&v2));
let v3 = PipelineVersion::new(2, 0, 0, OptimizationLevel::O2);
assert!(!v1.is_compatible(&v3));
let v4 = PipelineVersion::new(1, 0, 0, OptimizationLevel::O3);
assert!(!v1.is_compatible(&v4)); }
#[test]
fn test_pipeline_version_with_target_triple() {
let version = PipelineVersion::current(OptimizationLevel::O2)
.with_target_triple("x86_64-unknown-linux-gnu");
assert_eq!(
version.target_triple,
Some("x86_64-unknown-linux-gnu".to_string())
);
}
#[test]
fn test_serialized_pass_result_creation() {
let version = PipelineVersion::current(OptimizationLevel::O2);
let result = SerializedPassResult::new(pass_ids::GVN, "gvn", version);
assert_eq!(result.pass_id, pass_ids::GVN);
assert_eq!(result.pass_name, "gvn");
assert!(!result.changed);
}
#[test]
fn test_serialized_pass_result_pretty() {
let version = PipelineVersion::current(OptimizationLevel::O2);
let mut result = SerializedPassResult::new(pass_ids::INSTCOMBINE, "instcombine", version);
result.time_ms = 1.5;
result.changed = true;
result.stats.insert("folds".to_string(), "42".to_string());
let s = result.to_string_pretty();
assert!(s.contains("instcombine"));
assert!(s.contains("1.50ms"));
assert!(s.contains("changed"));
assert!(s.contains("folds"));
}
#[test]
fn test_warmup_analysis_creation() {
let analysis = PipelineWarmupAnalysis::new(3);
assert_eq!(analysis.warmup_iterations, 3);
assert!(analysis.runs.is_empty());
}
#[test]
fn test_warmup_analysis_record() {
let mut analysis = PipelineWarmupAnalysis::new(2);
analysis.record_run(0, 10.0, 100.0, 5, 10);
analysis.record_run(1, 8.0, 95.0, 3, 12);
analysis.record_run(2, 7.0, 92.0, 1, 14);
analysis.record_run(3, 6.5, 90.0, 0, 15);
assert_eq!(analysis.runs.len(), 4);
assert!(analysis.is_steady_state());
}
#[test]
fn test_warmup_analysis_not_steady() {
let mut analysis = PipelineWarmupAnalysis::new(3);
analysis.record_run(0, 10.0, 100.0, 5, 10);
analysis.record_run(1, 100.0, 95.0, 3, 12); analysis.record_run(2, 7.0, 92.0, 1, 14);
analysis.record_run(3, 6.5, 90.0, 0, 15);
assert!(!analysis.is_steady_state());
}
#[test]
fn test_warmup_analysis_steady_state_time() {
let mut analysis = PipelineWarmupAnalysis::new(2);
analysis.record_run(0, 10.0, 100.0, 5, 10); analysis.record_run(1, 8.0, 95.0, 3, 12); analysis.record_run(2, 7.0, 92.0, 1, 14); analysis.record_run(3, 6.9, 90.0, 0, 15);
let steady = analysis.steady_state_time_ms();
assert!((steady - 6.95).abs() < 0.1);
}
#[test]
fn test_cost_model_default() {
let model = PipelineCostModel::default();
assert_eq!(model.instruction_cost, 1.0);
assert_eq!(model.branch_cost, 3.0);
assert_eq!(model.call_cost, 10.0);
}
#[test]
fn test_cost_model_estimate_sequence() {
let model = PipelineCostModel::default();
let mut instrs = Vec::new();
instrs.push(MachineInstruction::new(0x01));
instrs.push(MachineInstruction::new(0x01));
instrs.push(MachineInstruction::new(0x01));
let cost = model.estimate_sequence_cost(&instrs);
assert_eq!(cost, 3.0); }
#[test]
fn test_cost_model_estimate_with_branch() {
let model = PipelineCostModel::default();
let mut instrs = Vec::new();
instrs.push(MachineInstruction::new(0x01));
let mut branch = MachineInstruction::new(0xE9);
branch.is_branch = true;
instrs.push(branch);
let cost = model.estimate_sequence_cost(&instrs);
assert_eq!(cost, 5.0); }
#[test]
fn test_cost_model_optimization_benefit() {
let model = PipelineCostModel::default();
let benefit = model.optimization_benefit(10, 2);
assert_eq!(benefit, 16.0); }
#[test]
fn test_cost_model_register_pressure() {
let model = PipelineCostModel::default();
assert_eq!(model.register_pressure_cost(5, 8), 0.0); assert_eq!(model.register_pressure_cost(10, 8), 4.0); }
#[test]
fn test_full_pipeline_integration_all_levels() {
let levels = [
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
];
for level in &levels {
let mut mgr = X86PassManager::new(X86PassContext::for_opt_level(*level));
mgr.build_pipeline();
assert!(!mgr.pipeline.is_empty(), "Empty pipeline for {:?}", level);
let result = mgr.run();
assert!(result.is_ok(), "Pipeline failed for {:?}", level);
}
}
#[test]
fn test_pipeline_deterministic_across_runs() {
let mut mgr1 = X86PassManager::new_o2();
mgr1.build_pipeline();
let p1: Vec<u64> = mgr1.pipeline().to_vec();
let mut mgr2 = X86PassManager::new_o2();
mgr2.build_pipeline();
let p2: Vec<u64> = mgr2.pipeline().to_vec();
assert_eq!(p1, p2, "Same optimization level must produce same pipeline");
}
#[test]
fn test_pass_registry_consistency() {
let registry = X86PassRegistry::default();
for id in registry.pipeline_order() {
assert!(
registry.get(*id).is_some(),
"Pass {} missing descriptor",
id
);
}
for id in registry.pipeline_order() {
let desc = registry.get(*id).unwrap();
let lookup = registry.get_by_name(&desc.name);
assert!(lookup.is_some(), "Cannot find pass '{}' by name", desc.name);
assert_eq!(lookup.unwrap().id, *id);
}
}
#[test]
fn test_pipeline_phase_ordering_correct() {
let mut mgr = X86PassManager::new_o2();
mgr.build_pipeline();
let mut phase_order: Vec<PipelinePhase> = Vec::new();
for id in mgr.pipeline() {
if let Some(desc) = mgr.registry.get(*id) {
if phase_order.last() != Some(&desc.phase) {
phase_order.push(desc.phase);
}
}
}
for i in 1..phase_order.len() {
assert!(
phase_order[i - 1] <= phase_order[i],
"Phase order violation: {:?} before {:?}",
phase_order[i - 1],
phase_order[i]
);
}
}
#[test]
fn test_all_passes_have_unique_ids() {
let registry = X86PassRegistry::default();
let mut seen = HashSet::new();
for id in registry.pipeline_order() {
assert!(seen.insert(*id), "Duplicate pass ID: {}", id);
}
}
#[test]
fn test_all_passes_have_unique_names() {
let registry = X86PassRegistry::default();
let mut seen = HashSet::new();
for id in registry.pipeline_order() {
if let Some(desc) = registry.get(*id) {
assert!(
seen.insert(desc.name.clone()),
"Duplicate pass name: '{}'",
desc.name
);
}
}
}
#[test]
fn test_mem2reg_always_first() {
for level in &[
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
] {
let mut mgr = X86PassManager::new(X86PassContext::for_opt_level(*level));
mgr.build_pipeline();
let ir_passes: Vec<u64> = mgr
.pipeline()
.iter()
.copied()
.filter(|id| {
matches!(
*id,
pass_ids::MEM2REG
| pass_ids::INSTCOMBINE
| pass_ids::SIMPLIFYCFG
| pass_ids::REASSOCIATE
| pass_ids::GVN
| pass_ids::SCCP
| pass_ids::EARLY_CSE
| pass_ids::LICM
| pass_ids::LOOP_ROTATE
| pass_ids::LOOP_SIMPLIFY
| pass_ids::INDVARS
)
})
.collect();
if !ir_passes.is_empty() {
assert_eq!(
ir_passes[0],
pass_ids::MEM2REG,
"mem2reg must be the first IR pass for {:?}",
level
);
}
}
}
#[test]
fn test_registry_default_has_all_categories() {
let registry = X86PassRegistry::default();
assert!(registry.get_by_name("mem2reg").is_some());
assert!(registry.get_by_name("loop-vectorize").is_some());
assert!(registry.get_by_name("x86-dag-isel").is_some());
assert!(registry.get_by_name("greedy-regalloc").is_some());
assert!(registry.get_by_name("x86-vzeroupper").is_some());
}
#[test]
fn test_o0_minimal_pipeline_characteristics() {
let mgr = create_o0_pipeline();
let p = mgr.pipeline();
assert!(!p.contains(&pass_ids::LOOP_VECTORIZE));
assert!(!p.contains(&pass_ids::SLP_VECTORIZER));
assert!(!p.contains(&pass_ids::INLINE));
assert!(!p.contains(&pass_ids::LOOP_UNROLL_AND_JAM));
assert!(!p.contains(&pass_ids::ARGPROMOTION));
assert!(!p.contains(&pass_ids::TAILCALLELIM));
assert!(p.contains(&pass_ids::X86_FAST_ISEL));
}
#[test]
fn test_o3_has_most_aggressive_passes() {
let mgr = create_o3_pipeline();
let p = mgr.pipeline();
assert!(p.contains(&pass_ids::ARGPROMOTION));
assert!(p.contains(&pass_ids::TAILCALLELIM));
assert!(p.contains(&pass_ids::LOOP_INTERCHANGE));
assert!(p.contains(&pass_ids::LOOP_UNSWITCH));
assert!(p.contains(&pass_ids::LOOP_DISTRIBUTE));
assert!(p.contains(&pass_ids::AGGRESSIVE_INSTCOMBINE));
assert!(p.contains(&pass_ids::MERGEFUNC));
}
#[test]
fn test_os_oz_prefer_size_over_speed() {
let os = create_os_pipeline();
let oz = create_oz_pipeline();
for mgr in [&os, &oz] {
assert!(mgr.context.prefer_size);
assert!(mgr.pipeline().contains(&pass_ids::MACHINE_OUTLINER));
}
assert!(oz.pipeline().contains(&pass_ids::OUTLINE_FUNCTIONS));
assert!(oz.pipeline().contains(&pass_ids::MERGE_CONSTANTS));
}
#[test]
fn test_regalloc_order_in_pipeline() {
let mut mgr = X86PassManager::new_o2();
mgr.build_pipeline();
let pre_ra = mgr
.pipeline()
.iter()
.position(|p| *p == pass_ids::PHI_ELIMINATION);
let ra = mgr
.pipeline()
.iter()
.position(|p| *p == pass_ids::REG_ALLOC_GREEDY);
let post_ra = mgr
.pipeline()
.iter()
.position(|p| *p == pass_ids::POST_RA_SCHEDULER);
if let (Some(pre), Some(alloc), Some(post)) = (pre_ra, ra, post_ra) {
assert!(pre < alloc, "Pre-RA must come before register alloc");
assert!(alloc < post, "Register alloc must come before post-RA");
}
}
#[test]
fn test_timing_is_accurate_for_known_duration() {
let mut timing = X86PassTiming::new(true);
timing.start_pass(pass_ids::MEM2REG);
let sleep_dur = Duration::from_millis(10);
std::thread::sleep(sleep_dur);
timing.stop_pass(true);
let record = timing.get_record(pass_ids::MEM2REG).unwrap();
assert!(record.total_duration >= sleep_dur);
assert!(record.total_duration < sleep_dur + Duration::from_millis(50)); }
#[test]
fn test_registry_validate_passes_only_once() {
let registry = X86PassRegistry::default();
assert!(registry.validate().is_ok());
assert!(registry.validate().is_ok());
}
#[test]
fn test_machine_instruction_clone() {
let instr = MachineInstruction::new(0x89)
.with_operands(vec![MachineOperand::Register(1)])
.with_defs(vec![0])
.with_uses(vec![1]);
let cloned = instr.clone();
assert_eq!(instr.opcode, cloned.opcode);
assert_eq!(instr.operands.len(), cloned.operands.len());
}
#[test]
fn test_machine_function_state_debug() {
let mf = X86MachineFunctionState::new("test", true);
assert_eq!(format!("{:?}", mf).len() > 0, true);
}
#[test]
fn test_pipeline_analyzer_handles_empty_pipeline() {
let registry = X86PassRegistry::default();
let analyzer = PipelineAnalyzer::new(registry);
let empty: Vec<u64> = Vec::new();
let redundant = analyzer.find_redundant_passes(&empty);
assert!(redundant.is_empty());
let graph = analyzer.dependency_graph(&empty);
assert!(graph.is_empty());
}
#[test]
fn test_o0_defaults_to_fast_isel() {
let mgr = X86PassManager::new_o0();
assert!(mgr.use_fast_isel);
assert!(!mgr.pipeline().contains(&pass_ids::PHI_ELIMINATION));
}
#[test]
fn test_nop_padding_for_all_sizes() {
for size in 0..20 {
let nops = X86PadShortFunctionPass::generate_nop_sequence(size);
assert_eq!(nops.len(), size as usize);
}
}
#[test]
fn test_large_nop_padding() {
let nops = X86PadShortFunctionPass::generate_nop_sequence(30);
assert_eq!(nops.len(), 30);
}
#[test]
fn test_pipeline_stats_are_cumulative() {
let mut mgr = X86PassManager::new_o1();
mgr.run().unwrap();
let first_runs = mgr.stats.ir_passes_executed;
mgr.reset();
mgr.run().unwrap();
let second_runs = mgr.stats.ir_passes_executed;
assert_eq!(
first_runs, second_runs,
"Pipeline stats reset between runs should be same"
);
}
#[test]
fn test_every_descriptor_has_phase() {
let registry = X86PassRegistry::default();
for id in registry.pipeline_order() {
let desc = registry.get(*id).unwrap();
let phase_name = desc.phase.name();
assert!(!phase_name.is_empty());
}
}
#[test]
fn test_pipeline_contains_regalloc() {
for level in &[
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
] {
let mut mgr = X86PassManager::new(X86PassContext::for_opt_level(*level));
mgr.build_pipeline();
let has_regalloc = mgr.pipeline().iter().any(|id| {
*id == pass_ids::REG_ALLOC_GREEDY
|| *id == pass_ids::REG_ALLOC_PBQP
|| *id == pass_ids::REG_ALLOC_FAST
|| *id == pass_ids::REG_ALLOC_BASIC
});
assert!(
has_regalloc,
"Pipeline for {:?} must contain a regalloc pass",
level
);
}
}
#[test]
fn test_subtarget_config_all_features() {
let all_features = vec![
"sse2".to_string(),
"avx2".to_string(),
"avx512f".to_string(),
"bmi2".to_string(),
];
let config = X86SubtargetPassConfig::from_features(&all_features);
assert!(config.enable_sse_passes);
assert!(config.enable_avx_passes);
assert!(config.enable_avx512_passes);
assert!(config.enable_bmi_passes);
assert!(!config.enable_x87_passes);
}
#[test]
fn test_pipeline_builder_all_features() {
let mgr = X86PipelineBuilder::new(OptimizationLevel::O3)
.with_timing()
.with_verify_each()
.with_debug()
.with_reg_alloc(RegAllocStrategy::PBQP)
.with_features(vec!["avx512f".to_string()])
.build();
assert!(mgr.timing.enabled);
assert!(mgr.instrumentation.verify_each);
assert!(mgr.instrumentation.debug_before);
assert_eq!(mgr.reg_alloc_strategy, RegAllocStrategy::PBQP);
}
#[test]
fn test_warmup_analysis_stress() {
let mut analysis = PipelineWarmupAnalysis::new(5);
for i in 0..10 {
let time = 100.0 / (i as f64 + 1.0) + 5.0;
analysis.record_run(i, time, 100.0, 0, 15);
}
let steady = analysis.steady_state_time_ms();
assert!(steady > 0.0);
}
#[test]
fn test_serialized_pass_result_empty_stats() {
let version = PipelineVersion::current(OptimizationLevel::O2);
let result = SerializedPassResult::new(pass_ids::ADCE, "adce", version);
let s = result.to_string_pretty();
assert!(s.contains("adce"));
assert!(s.contains("unchanged"));
}
#[test]
fn test_pass_context_o0_inline_threshold() {
let ctx = X86PassContext::for_opt_level(OptimizationLevel::O0);
assert_eq!(ctx.inline_threshold, 0);
assert!(!ctx.has_pgo_data);
}
#[test]
fn test_pass_context_o3_pgo() {
let mut ctx = X86PassContext::for_opt_level(OptimizationLevel::O3);
ctx.has_pgo_data = true;
assert!(ctx.has_pgo_data);
assert_eq!(ctx.inline_threshold, 275);
}
#[test]
fn test_o1_is_subset_of_o2() {
let o1 = create_o1_pipeline();
let o2 = create_o2_pipeline();
for pass_id in o1.pipeline() {
assert!(
o2.pipeline().contains(pass_id),
"O1 pass {} not found in O2 pipeline",
pass_id
);
}
}
#[test]
fn test_o2_is_subset_of_o3() {
let o2 = create_o2_pipeline();
let o3 = create_o3_pipeline();
for pass_id in o2.pipeline() {
assert!(
o3.pipeline().contains(pass_id),
"O2 pass {} not found in O3 pipeline",
pass_id
);
}
}
#[test]
fn test_o3_not_in_os() {
let o3 = create_o3_pipeline();
let os = create_os_pipeline();
let o3_only = [
pass_ids::ARGPROMOTION,
pass_ids::LOOP_INTERCHANGE,
pass_ids::LOOP_UNSWITCH,
pass_ids::LOOP_DISTRIBUTE,
pass_ids::LOOP_FUSION,
pass_ids::LOOP_VERSIONING_LICM,
pass_ids::AGGRESSIVE_INSTCOMBINE,
];
for p in &o3_only {
assert!(
!os.pipeline().contains(p),
"O3-only pass {} should not be in Os",
p
);
}
}
#[test]
fn test_os_in_oz() {
let os = create_os_pipeline();
let oz = create_oz_pipeline();
for pass_id in os.pipeline() {
assert!(
oz.pipeline().contains(pass_id),
"Os pass {} not found in Oz pipeline",
pass_id
);
}
}
#[test]
fn test_empty_function_handling() {
let mut mgr = X86PassManager::new_o2();
mgr.build_pipeline();
assert!(mgr.run().is_ok());
}
#[test]
fn test_duplicate_pass_name_registration() {
let mut registry = X86PassRegistry::new();
let desc1 = X86PassDescriptor::new(9000, "test-pass", "First");
let desc2 = X86PassDescriptor::new(9001, "test-pass", "Second");
registry.register(desc1);
registry.register(desc2);
let lookup = registry.get_by_name("test-pass");
assert!(lookup.is_some());
assert_eq!(lookup.unwrap().id, 9001);
}
#[test]
fn test_very_large_unroll_threshold() {
let mut ctx = X86PassContext::for_opt_level(OptimizationLevel::O3);
ctx.max_unroll_threshold = u32::MAX;
assert_eq!(ctx.max_unroll_threshold, u32::MAX);
}
#[test]
fn test_zero_inline_threshold() {
let mut ctx = X86PassContext::for_opt_level(OptimizationLevel::Oz);
ctx.inline_threshold = 0;
assert_eq!(ctx.inline_threshold, 0);
}
#[test]
fn test_all_optimization_levels_are_distinct() {
let levels = [
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
OptimizationLevel::Os,
OptimizationLevel::Oz,
];
for i in 0..levels.len() {
for j in 0..levels.len() {
if i != j {
assert_ne!(levels[i], levels[j]);
}
}
}
}
#[test]
fn test_registry_all_pass_names_are_lowercase() {
let registry = X86PassRegistry::default();
for name in registry.all_names() {
assert!(
name.chars()
.all(|c| c.is_lowercase() || c == '-' || c.is_numeric()),
"Pass name '{}' contains uppercase or invalid characters",
name
);
}
}
#[test]
fn test_timing_record_large_number_of_invocations() {
let mut record = PassTimingRecord::default();
for i in 0..1000 {
record.record(Duration::from_micros(i as u64), i % 2 == 0);
}
assert_eq!(record.invocation_count, 1000);
assert_eq!(record.change_count, 500);
assert!(record.avg_ms() > 0.0);
}
#[test]
fn test_fixup_lea_no_optimization_when_not_simplifiable() {
let mut pass = X86FixupLEAPass::new(true);
let mut mf = X86MachineFunctionState::new("test", true);
let mut block = MachineBlockState::new(0, "entry");
let instr = MachineInstruction::new(0x8D).with_operands(vec![
MachineOperand::Register(1),
MachineOperand::Memory {
base: Some(2),
index: None,
scale: 1,
displacement: 8,
},
]);
block.instructions.push(instr);
mf.blocks.push(block);
let result = pass.run_on_machine_function(&mut mf);
assert!(!result);
}
#[test]
fn test_post_ra_scheduler_no_dependencies() {
let sched = PostRASchedulerPass::new(true);
let i1 = MachineInstruction::new(0x01);
let i2 = MachineInstruction::new(0x02);
let i3 = MachineInstruction::new(0x03);
let instrs = vec![i1, i2, i3];
let schedule = sched.list_schedule(&instrs);
assert_eq!(schedule.len(), 3);
let mut sorted = schedule.clone();
sorted.sort();
assert_eq!(sorted, vec![0, 1, 2]);
}
#[test]
fn test_branch_folding_cannot_eliminate_non_branch() {
let pass = BranchFoldingPass::new();
let instr = MachineInstruction::new(0x89); assert!(!pass.can_eliminate_jump(&instr, 0));
}
#[test]
fn test_machine_outliner_empty_sequence() {
let pass = MachineOutlinerPass::new();
let instrs: Vec<MachineInstruction> = Vec::new();
let sequences = pass.find_repeated_sequences(&instrs);
assert!(sequences.is_empty());
}
#[test]
fn test_machine_outliner_short_instrs() {
let pass = MachineOutlinerPass::new();
let instrs: Vec<MachineInstruction> =
(0..2).map(|_| MachineInstruction::new(0x89)).collect();
let sequences = pass.find_repeated_sequences(&instrs);
assert!(sequences.is_empty());
}
#[test]
fn test_pipeline_cost_model_all_zero_instrs() {
let model = PipelineCostModel::default();
let instrs: Vec<MachineInstruction> = Vec::new();
assert_eq!(model.estimate_sequence_cost(&instrs), 0.0);
}
#[test]
fn test_pipeline_cost_model_call_heavy() {
let model = PipelineCostModel::default();
let mut instrs = Vec::new();
for _ in 0..5 {
let mut call = MachineInstruction::new(0xE8);
call.is_call = true;
instrs.push(call);
}
let cost = model.estimate_sequence_cost(&instrs);
assert!((cost - 55.0).abs() < 0.01);
}
#[test]
fn test_subtarget_config_empty_features() {
let config = X86SubtargetPassConfig::from_features(&[]);
assert!(!config.enable_sse_passes);
assert!(config.enable_x87_passes); assert!(!config.enable_avx_passes);
}
#[test]
fn test_subtarget_config_feature_subset() {
let config = X86SubtargetPassConfig::from_features(&["sse2".to_string()]);
assert!(config.enable_sse_passes);
assert!(!config.enable_avx_passes);
assert!(!config.enable_x87_passes);
let fixups = config.enabled_x86_fixups();
assert!(fixups.contains(&pass_ids::X86_DOMAIN_REASSIGN));
assert!(!fixups.contains(&pass_ids::X86_VZERO_UPPER));
assert!(!fixups.contains(&pass_ids::X86_FLOATING_POINT));
}
#[test]
fn test_subtarget_config_feature_avx_no_sse() {
let config = X86SubtargetPassConfig::from_features(&["avx".to_string()]);
assert!(config.enable_sse_passes);
assert!(config.enable_avx_passes);
let fixups = config.enabled_x86_fixups();
assert!(fixups.contains(&pass_ids::X86_VZERO_UPPER));
}
#[test]
fn test_warmup_analysis_single_run() {
let mut analysis = PipelineWarmupAnalysis::new(3);
analysis.record_run(0, 10.0, 100.0, 0, 15);
assert!(!analysis.is_steady_state());
assert_eq!(analysis.steady_state_time_ms(), 0.0);
}
#[test]
fn test_two_address_dst_equals_src1_no_copy() {
let pass = TwoAddressInstructionPass::new(true);
let mut instr = MachineInstruction::new(0x01);
instr.defs = vec![0];
instr.uses = vec![0, 1];
let seq = pass.rewrite_to_two_address(&instr);
assert_eq!(seq.len(), 1);
}
#[test]
fn test_two_address_dst_neq_src1_needs_copy() {
let pass = TwoAddressInstructionPass::new(true);
let mut instr = MachineInstruction::new(0x01);
instr.defs = vec![0];
instr.uses = vec![1, 2];
let seq = pass.rewrite_to_two_address(&instr);
assert_eq!(seq.len(), 2);
assert!(seq[0].is_move);
}
#[test]
fn test_registry_pipeline_order_is_stable() {
let reg1 = X86PassRegistry::default();
let reg2 = X86PassRegistry::default();
assert_eq!(reg1.pipeline_order(), reg2.pipeline_order());
}
#[test]
fn test_pipeline_runner_returns_stats() {
let (ok, stats, timing) = run_x86_pipeline(OptimizationLevel::O2);
assert!(ok);
assert!(stats.success);
assert!(!timing.is_empty());
}
#[test]
fn test_pipeline_version_roundtrip() {
let v = PipelineVersion::new(2, 3, 1, OptimizationLevel::O3)
.with_target_triple("x86_64-pc-linux-gnu");
let s = v.version_string();
assert!(s.contains("2.3.1"));
assert!(s.contains("O3"));
}
#[test]
fn test_dump_registered_passes_has_content() {
let dump = dump_registered_passes();
assert!(dump.contains("Early IR"));
assert!(dump.contains("Main IR"));
assert!(dump.contains("Late IR"));
assert!(dump.contains("Instruction Selection"));
assert!(dump.contains("Pre-RA Machine"));
assert!(dump.contains("Register Allocation"));
assert!(dump.contains("Post-RA Machine"));
assert!(dump.contains("X86 Fixups"));
}
#[test]
fn test_reg_alloc_result_spill_rate_no_spills() {
let result = RegAllocResult::new();
assert!((result.spill_rate() - 0.0).abs() < 0.01);
}
#[test]
fn test_reg_alloc_result_spill_rate_with_spills() {
let mut result = RegAllocResult::new();
result.spills = 5;
result.regs_spilled = 10;
assert!((result.spill_rate() - 33.333).abs() < 1.0);
}
#[test]
fn test_reg_alloc_result_merge_preserves_peak_memory() {
let mut r1 = RegAllocResult::new();
r1.peak_memory_bytes = 1024;
let mut r2 = RegAllocResult::new();
r2.peak_memory_bytes = 2048;
r1.merge(&r2);
assert_eq!(r1.peak_memory_bytes, 2048); }
fn pass_names_for_level(level: OptimizationLevel) -> HashSet<String> {
let registry = X86PassRegistry::default();
registry
.passes_for_opt_level(level)
.iter()
.filter_map(|id| registry.get(*id).map(|d| d.name.clone()))
.collect()
}
#[test]
fn test_o0_only_has_minimal_passes() {
let o0_names = pass_names_for_level(OptimizationLevel::O0);
let o1_names = pass_names_for_level(OptimizationLevel::O1);
let o2_names = pass_names_for_level(OptimizationLevel::O2);
let o3_names = pass_names_for_level(OptimizationLevel::O3);
assert!(o0_names.len() <= o1_names.len());
assert!(o0_names.len() <= o2_names.len());
assert!(o0_names.len() <= o3_names.len());
}
#[test]
fn test_pipeline_grows_monotonically_with_opt() {
let o1_names = pass_names_for_level(OptimizationLevel::O1);
let o2_names = pass_names_for_level(OptimizationLevel::O2);
let o3_names = pass_names_for_level(OptimizationLevel::O3);
assert!(o2_names.len() >= o1_names.len());
assert!(o3_names.len() >= o2_names.len());
}
#[test]
fn test_o3_has_passes_o1_doesnt() {
let o1 = pass_names_for_level(OptimizationLevel::O1);
let o3 = pass_names_for_level(OptimizationLevel::O3);
let o3_only: HashSet<_> = o3.difference(&o1).cloned().collect();
assert!(!o3_only.is_empty(), "O3 should have passes that O1 doesn't");
}
#[test]
fn test_ir_verification_catches_pipeline_errors() {
let mut instr = X86PassInstrumentation::new();
instr.enable_verify_each();
instr.record_verification_error("instcombine", "Invalid instruction after combine");
assert!(instr.has_verification_errors());
let errors = instr.get_verification_errors().to_vec();
assert_eq!(errors.len(), 1);
assert!(errors[0].contains("instcombine"));
}
#[test]
fn test_scenario_leaf_function_optimization() {
let mut mgr = X86PassManager::new_o2();
mgr.build_pipeline();
assert!(mgr.pipeline().contains(&pass_ids::X86_CALL_FRAME_OPT));
let result = mgr.run();
assert!(result.is_ok());
}
#[test]
fn test_scenario_vector_heavy_code() {
let mut mgr = X86PassManager::new(X86PassContext {
opt_level: OptimizationLevel::O3,
is_64bit: true,
subtarget_features: vec!["avx2".to_string(), "fma".to_string()],
enable_machine_outliner: false,
prefer_size: false,
max_unroll_threshold: 600,
inline_threshold: 275,
has_pgo_data: false,
});
mgr.build_pipeline();
assert!(mgr.pipeline().contains(&pass_ids::SLP_VECTORIZER));
assert!(mgr.pipeline().contains(&pass_ids::LOOP_VECTORIZE));
assert!(mgr.pipeline().contains(&pass_ids::X86_VZERO_UPPER));
}
#[test]
fn test_scenario_embedded_small_code() {
let mut mgr = X86PassManager::new(X86PassContext {
opt_level: OptimizationLevel::Os,
is_64bit: false,
subtarget_features: vec!["i386".to_string()],
enable_machine_outliner: true,
prefer_size: true,
max_unroll_threshold: 0,
inline_threshold: 25,
has_pgo_data: false,
});
mgr.build_pipeline();
assert!(!mgr.pipeline().contains(&pass_ids::LOOP_VECTORIZE));
assert!(!mgr.pipeline().contains(&pass_ids::ARGPROMOTION));
assert!(mgr.pipeline().contains(&pass_ids::MACHINE_OUTLINER));
}
#[test]
fn test_scenario_pgo_guided_optimization() {
let mut mgr = X86PassManager::new(X86PassContext {
opt_level: OptimizationLevel::O3,
is_64bit: true,
subtarget_features: vec!["avx2".to_string()],
enable_machine_outliner: false,
prefer_size: false,
max_unroll_threshold: 600,
inline_threshold: 500, has_pgo_data: true,
});
mgr.build_pipeline();
assert_eq!(mgr.context.inline_threshold, 500);
assert!(mgr.context.has_pgo_data);
}
#[test]
fn test_large_registry_query_stress() {
let registry = X86PassRegistry::default();
for _ in 0..1000 {
for id in registry.pipeline_order() {
assert!(registry.get(*id).is_some());
}
}
}
#[test]
fn test_timing_multiple_stress() {
let mut timing = X86PassTiming::new(true);
for i in 0..100 {
let ids = [
pass_ids::MEM2REG,
pass_ids::GVN,
pass_ids::INSTCOMBINE,
pass_ids::SIMPLIFYCFG,
pass_ids::LICM,
];
for id in &ids {
timing.start_pass(*id);
timing.stop_pass(i % 3 == 0);
}
}
let report = timing.compact_report();
assert!(!report.is_empty());
}
#[test]
fn test_instrumentation_callbacks_stress() {
let mut instr = X86PassInstrumentation::new();
let counter = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
let c = counter.clone();
instr.on_before_pass(move |_, _, _| {
c.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
});
for i in 0..100 {
instr.before_pass(i % 70, "test-pass", "");
}
assert_eq!(counter.load(std::sync::atomic::Ordering::Relaxed), 100);
}
#[test]
fn test_pipeline_version_serialization_roundtrip() {
let v1 = PipelineVersion::new(1, 2, 3, OptimizationLevel::O2)
.with_target_triple("x86_64-unknown-linux-gnu");
let s = v1.version_string();
assert!(s.contains("x86-pipeline"));
assert!(s.contains("1.2.3"));
assert!(s.contains("O2"));
}
#[test]
fn test_serialized_pass_result_roundtrip() {
let v = PipelineVersion::current(OptimizationLevel::O2);
let mut result = SerializedPassResult::new(pass_ids::GVN, "gvn", v.clone());
result.time_ms = 42.0;
result.changed = true;
result.stats.insert("folds".to_string(), "100".to_string());
let s = result.to_string_pretty();
assert!(s.contains("gvn"));
assert!(s.contains("42.00ms"));
assert!(s.contains("changed"));
assert!(s.contains("folds = 100"));
}
#[test]
fn test_feature_flags_affect_pipeline() {
let config_no_sse = X86SubtargetPassConfig::from_features(&[]);
let fixups_no_sse = config_no_sse.enabled_x86_fixups();
assert!(fixups_no_sse.contains(&pass_ids::X86_FLOATING_POINT));
let config_sse = X86SubtargetPassConfig::from_features(&["sse2".to_string()]);
let fixups_sse = config_sse.enabled_x86_fixups();
assert!(!fixups_sse.contains(&pass_ids::X86_FLOATING_POINT));
assert!(fixups_sse.contains(&pass_ids::X86_DOMAIN_REASSIGN));
}
#[test]
fn test_cmov_conversion_only_for_atom() {
let config_atom =
X86SubtargetPassConfig::from_features(&["atom".to_string(), "sse2".to_string()]);
let fixups_atom = config_atom.enabled_x86_fixups();
assert!(fixups_atom.contains(&pass_ids::X86_CMOV_CONVERSION));
let config_generic = X86SubtargetPassConfig::from_features(&["sse2".to_string()]);
let fixups_generic = config_generic.enabled_x86_fixups();
assert!(!fixups_generic.contains(&pass_ids::X86_CMOV_CONVERSION));
}
#[test]
fn doc_example_basic_usage() {
let mut mgr = X86PassManager::new_o2();
mgr.build_pipeline();
assert!(!mgr.pipeline().is_empty());
let result = mgr.run();
assert!(result.is_ok());
}
#[test]
fn doc_example_with_timing() {
let mut mgr = X86PassManager::new_o2().with_timing();
mgr.build_pipeline();
mgr.run().unwrap();
let report = mgr.timing_report();
assert!(!report.is_empty());
}
#[test]
fn doc_example_custom_pipeline() {
let mgr = X86PipelineBuilder::new(OptimizationLevel::O3)
.with_timing()
.with_verify_each()
.with_reg_alloc(RegAllocStrategy::PBQP)
.with_features(vec!["avx2".to_string()])
.build();
assert!(mgr.built);
}
#[test]
fn doc_example_pass_registry_inspection() {
let registry = X86PassRegistry::default();
let all_passes = registry.all_names();
assert!(all_passes.len() > 10);
let dump = dump_registered_passes();
assert!(dump.len() > 100);
}
#[test]
fn doc_example_subtarget_config() {
let config =
X86SubtargetPassConfig::from_features(&["avx2".to_string(), "fma".to_string()]);
assert!(config.enable_avx_passes);
let fixups = config.enabled_x86_fixups();
assert!(fixups.contains(&pass_ids::X86_VZERO_UPPER));
assert!(fixups.contains(&pass_ids::X86_DOMAIN_REASSIGN));
}
#[test]
fn doc_example_machine_outliner() {
let pass = MachineOutlinerPass::new();
let instrs: Vec<MachineInstruction> = (0..12)
.map(|i| {
let mut instr = MachineInstruction::new(0x89 + (i % 3) as u32);
instr.size_bytes = 3;
instr
})
.collect();
let sequences = pass.find_repeated_sequences(&instrs);
assert!(!sequences.is_empty());
}
#[test]
fn test_registry_all_passes_have_descriptions() {
let registry = X86PassRegistry::default();
for id in registry.pipeline_order() {
let desc = registry.get(*id).unwrap();
assert!(
!desc.description.is_empty(),
"Pass '{}' has no description",
desc.name
);
}
}
#[test]
fn test_registry_no_self_dependencies() {
let registry = X86PassRegistry::default();
for id in registry.pipeline_order() {
if let Some(desc) = registry.get(*id) {
for dep in &desc.dependencies {
assert_ne!(&desc.name, dep, "Pass '{}' depends on itself", desc.name);
}
}
}
}
#[test]
fn test_registry_phase_coverage() {
let registry = X86PassRegistry::default();
let all_phases: HashSet<PipelinePhase> = registry
.pipeline_order()
.iter()
.filter_map(|id| registry.get(*id).map(|d| d.phase))
.collect();
let expected_phases = [
PipelinePhase::EarlyIR,
PipelinePhase::MainIR,
PipelinePhase::LateIR,
PipelinePhase::Lowering,
PipelinePhase::PreRA,
PipelinePhase::RegAlloc,
PipelinePhase::PostRA,
PipelinePhase::X86Fixup,
];
for phase in &expected_phases {
assert!(
all_phases.contains(phase),
"No passes registered for phase {:?}",
phase
);
}
}
#[test]
fn test_timing_record_min_max_sanity() {
let mut record = PassTimingRecord::default();
record.record(Duration::from_millis(10), true);
record.record(Duration::from_millis(20), true);
record.record(Duration::from_millis(5), true);
assert_eq!(record.invocation_count, 3);
assert!(record.total_duration >= Duration::from_millis(35));
assert_eq!(record.min_duration, Some(Duration::from_millis(5)));
assert_eq!(record.max_duration, Some(Duration::from_millis(20)));
assert!(record.avg_ms() > 0.0);
assert!(record.change_rate() > 0.0);
}
#[test]
fn test_timing_record_std_dev_single_invocation() {
let mut record = PassTimingRecord::default();
record.record(Duration::from_millis(10), true);
assert_eq!(record.std_dev_ms(), 0.0); }
#[test]
fn test_timing_report_includes_phase_summary() {
let mut timing = X86PassTiming::new(true).with_registry(X86PassRegistry::default());
timing.start_pass(pass_ids::GVN);
timing.stop_pass(true);
timing.start_pass(pass_ids::INSTCOMBINE);
timing.stop_pass(true);
timing.record_pipeline_time(Duration::from_millis(100));
let report = timing.report();
assert!(report.contains("Phase Summary"));
assert!(report.contains("Main IR"));
assert!(report.contains("Total pipeline time"));
}
#[test]
fn test_machine_block_state_initialization() {
let block = MachineBlockState::new(42, "test_block");
assert_eq!(block.id, 42);
assert_eq!(block.name, "test_block");
assert!(block.instructions.is_empty());
assert!(block.successors.is_empty());
assert!(block.predecessors.is_empty());
}
#[test]
fn test_machine_instruction_builder_pattern() {
let instr = MachineInstruction::new(0x89)
.with_operands(vec![
MachineOperand::Register(1),
MachineOperand::Register(2),
])
.with_defs(vec![0])
.with_uses(vec![1, 2]);
assert_eq!(instr.opcode, 0x89);
assert_eq!(instr.operands.len(), 2);
assert_eq!(instr.defs.len(), 1);
assert_eq!(instr.uses.len(), 2);
assert!(!instr.is_terminator);
assert!(!instr.is_branch);
assert!(!instr.is_call);
}
#[test]
fn test_nop_padding_exact_sizes() {
for size in 0..=15u32 {
let nops = X86PadShortFunctionPass::generate_nop_sequence(size);
assert_eq!(nops.len() as u32, size);
}
}
#[test]
fn test_nop_padding_large_size() {
let nops = X86PadShortFunctionPass::generate_nop_sequence(256);
assert_eq!(nops.len(), 256);
}
#[test]
fn test_pass_manager_from_builder_o0() {
let mgr = X86PipelineBuilder::new(OptimizationLevel::O0).build();
assert!(mgr.pipeline.contains(&pass_ids::MEM2REG));
assert!(mgr.use_fast_isel);
}
#[test]
fn test_pass_manager_from_builder_o2() {
let mgr = X86PipelineBuilder::new(OptimizationLevel::O2).build();
assert!(mgr.pipeline.contains(&pass_ids::INLINE));
assert!(!mgr.use_fast_isel);
}
}