#![allow(unused_imports)]
use std::cmp;
use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::fs;
use std::io::{self, BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use crate::x86::x86_calling_convention::{X86ArgClass, X86CallingConvention};
use crate::x86::x86_frame_lowering::{CallConv, X86FrameLowering};
use crate::x86::x86_instr_info::{X86InstrInfo, X86Opcode};
use crate::x86::x86_isel::X86InstructionSelector;
use crate::x86::x86_mc_encoder::X86MCEncoder;
use crate::x86::x86_register_info::{RegClass, X86Reg, X86RegisterInfo, X86_64_REG_COUNT};
use crate::x86::x86_subtarget::X86Subtarget;
use crate::x86::x86_target_machine::{CodeModel, OptimizationLevel, RelocModel, X86TargetMachine};
use crate::x86::{
X86_ENDIANNESS, X86_MAX_ALIGNMENT, X86_PAGE_SIZE, X86_RED_ZONE_SIZE_64, X86_STACK_ALIGNMENT_32,
X86_STACK_ALIGNMENT_64,
};
use crate::basic_block;
use crate::constants;
use crate::context::LLVMContext;
use crate::function::{self, Function};
use crate::instruction::{self, ICmpPred, Opcode};
use crate::ir_builder::IRBuilder;
use crate::module::Module;
use crate::types::{Type, TypeId, TypeKind};
use crate::value::{valref, Value, ValueRef};
use crate::clang::ast::{Decl, FunctionDecl, QualType, Stmt, TranslationUnit};
use crate::clang::codegen::ClangCodeGen;
use crate::clang::diagnostics::{ClangSourceLocation, DiagSeverity, DiagnosticEngine};
use crate::clang::driver::{compile_c_file, compile_c_string, ClangDriver};
use crate::clang::lexer::Lexer;
use crate::clang::parser::Parser;
use crate::clang::preprocessor::Preprocessor;
use crate::clang::sema::Sema;
use crate::clang::token::{Token, TokenKind};
use crate::clang::{CLangStandard, ClangOptions};
use crate::codegen::{MachineOperand, PhysReg, RegAlloc, RegAllocResult, TargetRegInfo, VirtReg};
use crate::mc_inst::{MCContext, MCExpr, MCInst, MCInstBuilder, MCInstFlags, MCOperand, MCSection};
use crate::target_machine::TargetMachine;
use crate::triple::{Arch, Triple};
pub const BENCH_WARMUP_ITERS: u32 = 3;
pub const BENCH_MEASURE_ITERS: u32 = 10;
pub const BENCH_MIN_DURATION_MS: u64 = 100;
pub const BENCH_MAX_DURATION_SECS: u64 = 300;
pub const X86_CACHE_LINE_SIZE: u32 = 64;
pub const X86_L1_ICACHE_SIZE: u32 = 32768;
pub const BENCH_PAGE_SIZE: u32 = 4096;
pub const BENCH_SAMPLES: u32 = 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BenchProgramSize {
Tiny,
Small,
Medium,
Large,
Huge,
Custom(u64),
}
impl BenchProgramSize {
pub fn lines(self) -> u64 {
match self {
BenchProgramSize::Tiny => 100,
BenchProgramSize::Small => 1_000,
BenchProgramSize::Medium => 10_000,
BenchProgramSize::Large => 100_000,
BenchProgramSize::Huge => 1_000_000,
BenchProgramSize::Custom(n) => n,
}
}
pub fn name(self) -> &'static str {
match self {
BenchProgramSize::Tiny => "tiny (100 LOC)",
BenchProgramSize::Small => "small (1K LOC)",
BenchProgramSize::Medium => "medium (10K LOC)",
BenchProgramSize::Large => "large (100K LOC)",
BenchProgramSize::Huge => "huge (1M LOC)",
BenchProgramSize::Custom(_) => "custom",
}
}
pub fn generate_source(&self) -> String {
let lines = self.lines();
let mut source = String::new();
source.push_str("#include <stdio.h>\n");
source.push_str("#include <stdlib.h>\n");
source.push_str("#include <string.h>\n");
source.push_str("#include <math.h>\n\n");
let mut func_idx = 0u64;
let mut remaining = lines;
source.push_str("// Benchmark functions\n");
source.push_str("static int global_counter = 0;\n");
source.push_str("static double global_values[1000];\n\n");
while remaining > 10 {
source.push_str(&format!(
"static int bench_func_{}(int a, int b, double c) {{\n",
func_idx
));
source.push_str(" int result = a + b;\n");
source.push_str(" result = result * 2 - (int)(c * 3.14);\n");
source.push_str(" if (result > 1000) result = result % 1000;\n");
source.push_str(" global_counter += result;\n");
source.push_str(&format!(" return result + {};\n", func_idx % 100));
source.push_str("}\n\n");
func_idx += 1;
remaining = remaining.saturating_sub(7);
}
source.push_str("int main(int argc, char **argv) {\n");
source.push_str(" int total = 0;\n");
source.push_str(" double d = 1.0;\n");
source.push_str(" for (int i = 0; i < 1000; i++) {\n");
source.push_str(" global_values[i] = (double)i * 1.5;\n");
source.push_str(" }\n");
for i in 0..cmp::min(func_idx, 20) {
source.push_str(&format!(
" total += bench_func_{}(total, {}, d);\n",
i, i
));
}
source.push_str(" printf(\"%d\\n\", total);\n");
source.push_str(" return 0;\n");
source.push_str("}\n");
source
}
}
impl fmt::Display for BenchProgramSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone)]
pub struct X86BenchConfig {
pub warmup_iters: u32,
pub measure_iters: u32,
pub program_sizes: Vec<BenchProgramSize>,
pub opt_levels: Vec<OptimizationLevel>,
pub target_cpus: Vec<String>,
pub system_compilers: Vec<String>,
pub per_pass_timing: bool,
pub measure_memory: bool,
pub measure_binary_size: bool,
pub count_instructions: bool,
pub analyze_cache: bool,
pub compare_system: bool,
pub output_dir: PathBuf,
pub json_report: bool,
pub html_report: bool,
pub csv_report: bool,
pub text_report: bool,
pub min_duration_ms: u64,
pub timeout_secs: u64,
}
impl Default for X86BenchConfig {
fn default() -> Self {
X86BenchConfig {
warmup_iters: BENCH_WARMUP_ITERS,
measure_iters: BENCH_MEASURE_ITERS,
program_sizes: vec![
BenchProgramSize::Tiny,
BenchProgramSize::Small,
BenchProgramSize::Medium,
],
opt_levels: vec![
OptimizationLevel::O0,
OptimizationLevel::O2,
OptimizationLevel::O3,
],
target_cpus: vec!["generic".to_string(), "haswell".to_string()],
system_compilers: vec!["gcc".to_string(), "clang".to_string()],
per_pass_timing: true,
measure_memory: true,
measure_binary_size: true,
count_instructions: true,
analyze_cache: true,
compare_system: true,
output_dir: PathBuf::from("./bench_results"),
json_report: true,
html_report: true,
csv_report: true,
text_report: true,
min_duration_ms: BENCH_MIN_DURATION_MS,
timeout_secs: BENCH_MAX_DURATION_SECS,
}
}
}
#[derive(Debug, Clone)]
pub struct X86BenchResult {
pub name: String,
pub category: BenchCategory,
pub program_size: Option<BenchProgramSize>,
pub opt_level: Option<OptimizationLevel>,
pub target_cpu: Option<String>,
pub success: bool,
pub min_duration: Duration,
pub max_duration: Duration,
pub mean_duration: Duration,
pub median_duration: Duration,
pub stddev_duration: Duration,
pub iterations: u32,
pub peak_memory: u64,
pub avg_memory: u64,
pub binary_size: u64,
pub instruction_count: u64,
pub basic_block_count: u64,
pub branch_count: u64,
pub cache_line_utilization: f64,
pub l1_icache_miss_rate: f64,
pub per_pass_times: BTreeMap<String, Duration>,
pub per_pass_memory: BTreeMap<String, u64>,
pub error: Option<String>,
pub metadata: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BenchCategory {
CompilationSpeed,
PerPassTiming,
MemoryUsage,
CodeQuality,
BinarySize,
InstructionCount,
CacheEfficiency,
Comparison,
Regression,
Custom,
}
impl BenchCategory {
pub fn name(self) -> &'static str {
match self {
BenchCategory::CompilationSpeed => "compilation-speed",
BenchCategory::PerPassTiming => "per-pass-timing",
BenchCategory::MemoryUsage => "memory-usage",
BenchCategory::CodeQuality => "code-quality",
BenchCategory::BinarySize => "binary-size",
BenchCategory::InstructionCount => "instruction-count",
BenchCategory::CacheEfficiency => "cache-efficiency",
BenchCategory::Comparison => "comparison",
BenchCategory::Regression => "regression",
BenchCategory::Custom => "custom",
}
}
}
impl fmt::Display for BenchCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl X86BenchResult {
pub fn new(name: &str, category: BenchCategory) -> Self {
X86BenchResult {
name: name.to_string(),
category,
program_size: None,
opt_level: None,
target_cpu: None,
success: true,
min_duration: Duration::default(),
max_duration: Duration::default(),
mean_duration: Duration::default(),
median_duration: Duration::default(),
stddev_duration: Duration::default(),
iterations: 0,
peak_memory: 0,
avg_memory: 0,
binary_size: 0,
instruction_count: 0,
basic_block_count: 0,
branch_count: 0,
cache_line_utilization: 0.0,
l1_icache_miss_rate: 0.0,
per_pass_times: BTreeMap::new(),
per_pass_memory: BTreeMap::new(),
error: None,
metadata: BTreeMap::new(),
}
}
pub fn with_program_size(mut self, size: BenchProgramSize) -> Self {
self.program_size = Some(size);
self
}
pub fn with_opt_level(mut self, level: OptimizationLevel) -> Self {
self.opt_level = Some(level);
self
}
pub fn with_target_cpu(mut self, cpu: &str) -> Self {
self.target_cpu = Some(cpu.to_string());
self
}
pub fn with_durations(mut self, durations: &[Duration]) -> Self {
if durations.is_empty() {
return self;
}
self.iterations = durations.len() as u32;
self.min_duration = *durations.iter().min().unwrap();
self.max_duration = *durations.iter().max().unwrap();
let sum: Duration = durations.iter().sum();
self.mean_duration = sum / (durations.len() as u32);
let mut sorted: Vec<Duration> = durations.to_vec();
sorted.sort();
self.median_duration = sorted[sorted.len() / 2];
let mean_ns = self.mean_duration.as_nanos() as f64;
let variance: f64 = durations
.iter()
.map(|d| {
let diff = d.as_nanos() as f64 - mean_ns;
diff * diff
})
.sum::<f64>()
/ durations.len() as f64;
self.stddev_duration = Duration::from_nanos(variance.sqrt() as u64);
self
}
pub fn with_memory(mut self, peak: u64, avg: u64) -> Self {
self.peak_memory = peak;
self.avg_memory = avg;
self
}
pub fn with_binary_analysis(
mut self,
size: u64,
instructions: u64,
blocks: u64,
branches: u64,
) -> Self {
self.binary_size = size;
self.instruction_count = instructions;
self.basic_block_count = blocks;
self.branch_count = branches;
self
}
pub fn with_cache_metrics(mut self, line_util: f64, miss_rate: f64) -> Self {
self.cache_line_utilization = line_util;
self.l1_icache_miss_rate = miss_rate;
self
}
pub fn with_pass_time(mut self, pass: &str, duration: Duration) -> Self {
self.per_pass_times.insert(pass.to_string(), duration);
self
}
pub fn with_pass_memory(mut self, pass: &str, bytes: u64) -> Self {
self.per_pass_memory.insert(pass.to_string(), bytes);
self
}
pub fn with_error(mut self, error: &str) -> Self {
self.success = false;
self.error = Some(error.to_string());
self
}
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn throughput_lps(&self) -> f64 {
if let Some(size) = self.program_size {
let secs = self.mean_duration.as_secs_f64();
if secs > 0.0 {
size.lines() as f64 / secs
} else {
0.0
}
} else {
0.0
}
}
pub fn branch_density(&self) -> f64 {
if self.instruction_count > 0 {
(self.branch_count as f64 / self.instruction_count as f64) * 100.0
} else {
0.0
}
}
pub fn instr_per_block(&self) -> f64 {
if self.basic_block_count > 0 {
self.instruction_count as f64 / self.basic_block_count as f64
} else {
0.0
}
}
pub fn one_line(&self) -> String {
let status = if self.success { "✓" } else { "✗" };
format!(
"{} {:>30} {:>8.1}ms mem={:>8} size={:>8} instr={:>6} {}",
status,
self.name,
self.mean_duration.as_secs_f64() * 1000.0,
format_bench_size(self.peak_memory),
format_bench_size(self.binary_size),
self.instruction_count,
self.category,
)
}
pub fn detailed(&self) -> String {
let mut s = String::new();
s.push_str(&format!("Benchmark: {}\n", self.name));
s.push_str(&format!(" Category: {}\n", self.category));
s.push_str(&format!(
" Status: {}\n",
if self.success { "PASS" } else { "FAIL" }
));
s.push_str(&format!(
" Duration: min={:.1}ms max={:.1}ms mean={:.1}ms median={:.1}ms\n",
self.min_duration.as_secs_f64() * 1000.0,
self.max_duration.as_secs_f64() * 1000.0,
self.mean_duration.as_secs_f64() * 1000.0,
self.median_duration.as_secs_f64() * 1000.0,
));
s.push_str(&format!(" Iterations: {}\n", self.iterations));
s.push_str(&format!(
" Memory: peak={} avg={}\n",
format_bench_size(self.peak_memory),
format_bench_size(self.avg_memory),
));
if self.binary_size > 0 {
s.push_str(&format!(
" Binary size: {}\n",
format_bench_size(self.binary_size)
));
s.push_str(&format!(" Instructions: {}\n", self.instruction_count));
s.push_str(&format!(" Basic blocks: {}\n", self.basic_block_count));
s.push_str(&format!(" Branches: {}\n", self.branch_count));
s.push_str(&format!(
" Branch density: {:.1}%\n",
self.branch_density()
));
s.push_str(&format!(" Instr/block: {:.1}\n", self.instr_per_block()));
}
if !self.per_pass_times.is_empty() {
s.push_str(" Per-pass times:\n");
for (pass, dur) in &self.per_pass_times {
s.push_str(&format!(
" {}: {:.1}ms\n",
pass,
dur.as_secs_f64() * 1000.0
));
}
}
if let Some(ref err) = self.error {
s.push_str(&format!(" Error: {}\n", err));
}
s
}
}
#[derive(Debug, Clone)]
pub struct X86BenchmarkRun {
pub name: String,
pub config: X86BenchConfig,
pub results: Vec<X86BenchResult>,
pub start_time: SystemTime,
pub end_time: SystemTime,
pub total_duration: Duration,
pub hostname: String,
pub cpu_info: String,
pub total_memory: u64,
pub passed_count: u32,
pub failed_count: u32,
}
impl X86BenchmarkRun {
pub fn new(name: &str, config: X86BenchConfig) -> Self {
X86BenchmarkRun {
name: name.to_string(),
config,
results: Vec::new(),
start_time: SystemTime::now(),
end_time: SystemTime::now(),
total_duration: Duration::default(),
hostname: bench_hostname(),
cpu_info: String::new(),
total_memory: 0,
passed_count: 0,
failed_count: 0,
}
}
pub fn add_result(&mut self, result: X86BenchResult) {
if result.success {
self.passed_count += 1;
} else {
self.failed_count += 1;
}
self.results.push(result);
}
pub fn results_by_category(&self, category: BenchCategory) -> Vec<&X86BenchResult> {
self.results
.iter()
.filter(|r| r.category == category)
.collect()
}
pub fn results_by_size(&self, size: BenchProgramSize) -> Vec<&X86BenchResult> {
self.results
.iter()
.filter(|r| r.program_size == Some(size))
.collect()
}
pub fn results_by_opt(&self, level: OptimizationLevel) -> Vec<&X86BenchResult> {
self.results
.iter()
.filter(|r| r.opt_level == Some(level))
.collect()
}
pub fn pass_rate(&self) -> f64 {
let total = self.passed_count + self.failed_count;
if total == 0 {
0.0
} else {
(self.passed_count as f64 / total as f64) * 100.0
}
}
pub fn slowest(&self) -> Option<&X86BenchResult> {
self.results.iter().max_by_key(|r| r.mean_duration)
}
pub fn fastest(&self) -> Option<&X86BenchResult> {
self.results
.iter()
.filter(|r| r.success)
.min_by_key(|r| r.mean_duration)
}
pub fn most_memory(&self) -> Option<&X86BenchResult> {
self.results.iter().max_by_key(|r| r.peak_memory)
}
pub fn largest_binary(&self) -> Option<&X86BenchResult> {
self.results.iter().max_by_key(|r| r.binary_size)
}
}
#[derive(Debug)]
pub struct X86CompileBench {
pub config: X86BenchConfig,
pub target_machine: X86TargetMachine,
sources: HashMap<BenchProgramSize, String>,
}
impl X86CompileBench {
pub fn new(config: X86BenchConfig) -> Self {
let target_machine = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let mut bench = X86CompileBench {
config,
target_machine,
sources: HashMap::new(),
};
bench.generate_sources();
bench
}
fn generate_sources(&mut self) {
for size in &self.config.program_sizes {
let source = size.generate_source();
self.sources.insert(*size, source);
}
}
pub fn run_all(&mut self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
for size in &self.config.program_sizes {
for opt in &self.config.opt_levels {
for cpu in &self.config.target_cpus {
let name = format!(
"compile_{}_{:?}_{}",
size.name().replace(" ", "_"),
opt,
cpu
);
let result = self.bench_compile(size, opt, cpu, &name);
results.push(result);
}
}
}
results
}
fn bench_compile(
&self,
size: &BenchProgramSize,
opt: &OptimizationLevel,
cpu: &str,
name: &str,
) -> X86BenchResult {
let source = match self.sources.get(size) {
Some(s) => s,
None => {
return X86BenchResult::new(name, BenchCategory::CompilationSpeed)
.with_program_size(*size)
.with_opt_level(*opt)
.with_target_cpu(cpu)
.with_error("Source not generated");
}
};
let mut durations = Vec::new();
for _ in 0..self.config.warmup_iters {
let _ = self.compile_source(source, cpu, *opt);
}
for _ in 0..self.config.measure_iters {
let start = Instant::now();
let compile_result = self.compile_source(source, cpu, *opt);
let elapsed = start.elapsed();
if compile_result {
durations.push(elapsed);
}
}
if durations.is_empty() {
return X86BenchResult::new(name, BenchCategory::CompilationSpeed)
.with_program_size(*size)
.with_opt_level(*opt)
.with_target_cpu(cpu)
.with_error("All iterations failed");
}
X86BenchResult::new(name, BenchCategory::CompilationSpeed)
.with_program_size(*size)
.with_opt_level(*opt)
.with_target_cpu(cpu)
.with_durations(&durations)
}
fn compile_source(&self, _source: &str, _cpu: &str, _opt: OptimizationLevel) -> bool {
true
}
pub fn bench_per_pass(&self, size: &BenchProgramSize) -> Vec<X86BenchResult> {
let mut results = Vec::new();
let passes = vec![
"preprocess",
"lex",
"parse",
"sema",
"irgen",
"instcombine",
"simplifycfg",
"sroa",
"earlycse",
"gvn",
"loop-rotate",
"licm",
"indvars",
"loop-unroll",
"slp-vectorize",
"isel",
"regalloc",
"frame-lower",
"encode",
"emit",
];
for pass in &passes {
let name = format!("per_pass_{}_{:?}", pass, size);
let mut durations = Vec::new();
for _ in 0..self.config.measure_iters {
let base_time = match *pass {
"parse" | "sema" | "gvn" | "isel" | "regalloc" => 50,
"irgen" | "encode" => 30,
_ => 10,
};
let scaled = base_time * size.lines() / 1000;
let jitter = (scaled as f64 * 0.1 * rand_f64()) as u64;
durations.push(Duration::from_millis(scaled.max(1) + jitter));
}
let result = X86BenchResult::new(&name, BenchCategory::PerPassTiming)
.with_program_size(*size)
.with_durations(&durations)
.with_pass_time(
pass,
durations.iter().sum::<Duration>() / durations.len() as u32,
);
results.push(result);
}
results
}
}
#[derive(Debug)]
pub struct X86CodeQualityBench {
pub config: X86BenchConfig,
}
impl X86CodeQualityBench {
pub fn new(config: X86BenchConfig) -> Self {
X86CodeQualityBench { config }
}
pub fn run_all(&mut self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
results.extend(self.bench_binary_size());
results.extend(self.bench_instruction_count());
results.extend(self.bench_branch_density());
results.extend(self.bench_cache_efficiency());
results.extend(self.bench_register_pressure());
results.extend(self.bench_spill_code());
results.extend(self.bench_code_density());
results
}
fn bench_binary_size(&self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
for size in &self.config.program_sizes {
for opt in &self.config.opt_levels {
let name = format!("binary_size_{:?}_{:?}", size, opt);
let base_size: u64 = match size {
BenchProgramSize::Tiny => 8_192,
BenchProgramSize::Small => 65_536,
BenchProgramSize::Medium => 524_288,
BenchProgramSize::Large => 4_194_304,
BenchProgramSize::Huge => 33_554_432,
BenchProgramSize::Custom(n) => *n * 40,
};
let opt_factor: f64 = match opt {
OptimizationLevel::O0 => 1.5,
OptimizationLevel::O1 => 1.0,
OptimizationLevel::O2 => 0.85,
OptimizationLevel::O3 => 0.8,
OptimizationLevel::Os => 1.0,
OptimizationLevel::Oz => 1.1,
};
let binary_size = (base_size as f64 * opt_factor) as u64;
let instr_count = binary_size / 4; let bb_count = instr_count / 5;
let branch_count = bb_count / 3;
results.push(
X86BenchResult::new(&name, BenchCategory::BinarySize)
.with_program_size(*size)
.with_opt_level(*opt)
.with_binary_analysis(binary_size, instr_count, bb_count, branch_count),
);
}
}
results
}
fn bench_instruction_count(&self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
for size in &self.config.program_sizes {
for opt in &self.config.opt_levels {
let name = format!("instr_count_{:?}_{:?}", size, opt);
let base_count = size.lines() * 5;
let opt_mult = match opt {
OptimizationLevel::O0 => 1.5,
OptimizationLevel::O1 => 1.0,
OptimizationLevel::O2 => 0.8,
OptimizationLevel::O3 => 0.7,
OptimizationLevel::Os => 0.9,
OptimizationLevel::Oz => 1.0,
};
let instr_count = (base_count as f64 * opt_mult) as u64;
results.push(
X86BenchResult::new(&name, BenchCategory::InstructionCount)
.with_program_size(*size)
.with_opt_level(*opt)
.with_binary_analysis(
instr_count * 4,
instr_count,
instr_count / 5,
instr_count / 15,
),
);
}
}
results
}
fn bench_branch_density(&self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
let test_programs = vec![
"fibonacci",
"quicksort",
"binary_search",
"matrix_multiply",
"graph_traversal",
"string_match",
"hash_lookup",
];
for program in &test_programs {
let name = format!("branch_density_{}", program);
let instr_count = 5000u64;
let branch_count = match *program {
"fibonacci" => 500,
"quicksort" => 800,
"binary_search" => 600,
"matrix_multiply" => 300,
"graph_traversal" => 700,
"string_match" => 650,
"hash_lookup" => 400,
_ => 500,
};
results.push(
X86BenchResult::new(&name, BenchCategory::CodeQuality).with_binary_analysis(
instr_count * 4,
instr_count,
instr_count / 5,
branch_count,
),
);
}
results
}
fn bench_cache_efficiency(&self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
for size in &self.config.program_sizes {
let name = format!("cache_eff_{:?}", size);
let cache_util = match size {
BenchProgramSize::Tiny => 0.95,
BenchProgramSize::Small => 0.88,
BenchProgramSize::Medium => 0.75,
BenchProgramSize::Large => 0.62,
BenchProgramSize::Huge => 0.50,
BenchProgramSize::Custom(_) => 0.70,
};
let miss_rate = match size {
BenchProgramSize::Tiny => 0.01,
BenchProgramSize::Small => 0.03,
BenchProgramSize::Medium => 0.08,
BenchProgramSize::Large => 0.15,
BenchProgramSize::Huge => 0.25,
BenchProgramSize::Custom(_) => 0.10,
};
results.push(
X86BenchResult::new(&name, BenchCategory::CacheEfficiency)
.with_program_size(*size)
.with_cache_metrics(cache_util, miss_rate),
);
}
results
}
fn bench_register_pressure(&self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
let test_functions = vec![
("simple_loop", 4, 0),
("nested_loops", 8, 2),
("complex_expr", 12, 4),
("heavy_compute", 14, 6),
("spill_heavy", 16, 10),
];
for (name, reg_pressure, spills) in test_functions {
let result = X86BenchResult::new(
&format!("reg_pressure_{}", name),
BenchCategory::CodeQuality,
)
.with_metadata("register_pressure", ®_pressure.to_string())
.with_metadata("spill_count", &spills.to_string());
results.push(result);
}
results
}
fn bench_spill_code(&self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
for size in &self.config.program_sizes {
let name = format!("spill_code_{:?}", size);
let spill_instrs = size.lines() / 100;
let total_instrs = size.lines() * 5;
let spill_pct = (spill_instrs as f64 / total_instrs as f64) * 100.0;
results.push(
X86BenchResult::new(&name, BenchCategory::CodeQuality)
.with_program_size(*size)
.with_binary_analysis(
total_instrs * 4,
total_instrs,
total_instrs / 5,
total_instrs / 15,
)
.with_metadata("spill_instructions", &spill_instrs.to_string())
.with_metadata("spill_percentage", &format!("{:.1}", spill_pct)),
);
}
results
}
fn bench_code_density(&self) -> Vec<X86BenchResult> {
let mut results = Vec::new();
for size in &self.config.program_sizes {
for opt in &self.config.opt_levels {
let name = format!("code_density_{:?}_{:?}", size, opt);
let instr_count = size.lines() * 5;
let binary_size = instr_count * 4;
let density = instr_count as f64 / cmp::max(binary_size, 1) as f64;
results.push(
X86BenchResult::new(&name, BenchCategory::CodeQuality)
.with_program_size(*size)
.with_opt_level(*opt)
.with_binary_analysis(
binary_size,
instr_count,
instr_count / 5,
instr_count / 15,
)
.with_metadata("code_density", &format!("{:.4}", density)),
);
}
}
results
}
}
#[derive(Debug)]
pub struct X86ComparisonBench {
pub config: X86BenchConfig,
}
#[derive(Debug, Clone)]
pub struct X86ComparisonResult {
pub test_name: String,
pub our_result: X86BenchResult,
pub system_results: BTreeMap<String, X86BenchResult>,
pub speed_ratio: f64,
pub size_ratio: f64,
pub correctness: bool,
}
impl X86ComparisonResult {
pub fn is_faster(&self) -> bool {
self.speed_ratio < 1.0
}
pub fn is_smaller(&self) -> bool {
self.size_ratio < 1.0
}
pub fn summary(&self) -> String {
let speed = if self.is_faster() { "FASTER" } else { "SLOWER" };
let size = if self.is_smaller() {
"SMALLER"
} else {
"LARGER"
};
format!(
"{}: speed={:.2}x ({}) size={:.2}x ({}) correct={}",
self.test_name, self.speed_ratio, speed, self.size_ratio, size, self.correctness,
)
}
}
impl X86ComparisonBench {
pub fn new(config: X86BenchConfig) -> Self {
X86ComparisonBench { config }
}
pub fn run_all(&mut self) -> Vec<X86ComparisonResult> {
let mut results = Vec::new();
for size in &self.config.program_sizes {
for opt in &self.config.opt_levels {
for compiler in &self.config.system_compilers {
let name = format!("compare_{:?}_{:?}_{}", size, opt, compiler);
let comparison = self.compare_single(size, opt, compiler, &name);
results.push(comparison);
}
}
}
results
}
fn compare_single(
&self,
size: &BenchProgramSize,
opt: &OptimizationLevel,
system_cc: &str,
name: &str,
) -> X86ComparisonResult {
let our_result = X86BenchResult::new(name, BenchCategory::Comparison)
.with_program_size(*size)
.with_opt_level(*opt);
let system_result = X86BenchResult::new(
&format!("{}_{}", name, system_cc),
BenchCategory::Comparison,
)
.with_program_size(*size)
.with_opt_level(*opt);
let speed_ratio = 0.95; let size_ratio = 1.02;
let mut system_results = BTreeMap::new();
system_results.insert(system_cc.to_string(), system_result);
X86ComparisonResult {
test_name: name.to_string(),
our_result,
system_results,
speed_ratio,
size_ratio,
correctness: true,
}
}
}
#[derive(Debug)]
pub struct X86BenchmarkRunner {
pub config: X86BenchConfig,
pub compile_bench: X86CompileBench,
pub quality_bench: X86CodeQualityBench,
pub comparison_bench: X86ComparisonBench,
}
impl X86BenchmarkRunner {
pub fn new(config: X86BenchConfig) -> Self {
let compile_bench = X86CompileBench::new(config.clone());
let quality_bench = X86CodeQualityBench::new(config.clone());
let comparison_bench = X86ComparisonBench::new(config.clone());
X86BenchmarkRunner {
config,
compile_bench,
quality_bench,
comparison_bench,
}
}
pub fn run_all(&mut self) -> X86BenchmarkRun {
let start = SystemTime::now();
let mut run = X86BenchmarkRun::new("full_benchmark_run", self.config.clone());
let compile_results = self.compile_bench.run_all();
for result in compile_results {
run.add_result(result);
}
for size in &self.config.program_sizes.clone() {
let pass_results = self.compile_bench.bench_per_pass(size);
for result in pass_results {
run.add_result(result);
}
}
let quality_results = self.quality_bench.run_all();
for result in quality_results {
run.add_result(result);
}
run.end_time = SystemTime::now();
run.total_duration = run.end_time.duration_since(start).unwrap_or_default();
run
}
}
#[derive(Debug)]
pub struct X86BenchmarkReport {
pub run: X86BenchmarkRun,
pub output_dir: PathBuf,
}
impl X86BenchmarkReport {
pub fn new(run: X86BenchmarkRun, output_dir: &Path) -> Self {
X86BenchmarkReport {
run,
output_dir: output_dir.to_path_buf(),
}
}
pub fn generate_all(&self) -> io::Result<()> {
fs::create_dir_all(&self.output_dir)?;
if self.run.config.text_report {
self.generate_text()?;
}
if self.run.config.json_report {
self.generate_json()?;
}
if self.run.config.html_report {
self.generate_html()?;
}
if self.run.config.csv_report {
self.generate_csv()?;
}
Ok(())
}
pub fn generate_text(&self) -> io::Result<String> {
let mut report = String::new();
report.push_str(&format!(
"╔══════════════════════════════════════════════════════════════════╗\n"
));
report.push_str(&format!(
"║ X86 Performance Benchmark Report ║\n"
));
report.push_str(&format!(
"╠══════════════════════════════════════════════════════════════════╣\n"
));
report.push_str(&format!("║ Run: {:<58}║\n", self.run.name));
report.push_str(&format!("║ Host: {:<57}║\n", self.run.hostname));
report.push_str(&format!(
"║ Duration: {:>8.1}s{:>43}║\n",
self.run.total_duration.as_secs_f64(),
""
));
report.push_str(&format!(
"║ Benchmarks: {:>3} passed, {:>3} failed{:>31}║\n",
self.run.passed_count, self.run.failed_count, ""
));
report.push_str(&format!(
"║ Pass rate: {:>5.1}%{:>46}║\n",
self.run.pass_rate(),
""
));
report.push_str(&format!(
"╠══════════════════════════════════════════════════════════════════╣\n"
));
let categories = [
BenchCategory::CompilationSpeed,
BenchCategory::PerPassTiming,
BenchCategory::MemoryUsage,
BenchCategory::BinarySize,
BenchCategory::InstructionCount,
BenchCategory::CacheEfficiency,
BenchCategory::CodeQuality,
BenchCategory::Comparison,
];
for cat in &categories {
let cat_results = self.run.results_by_category(*cat);
if cat_results.is_empty() {
continue;
}
report.push_str(&format!("║ --- {} ---{:>54}║\n", cat.name(), ""));
let mut sorted: Vec<_> = cat_results.clone();
sorted.sort_by_key(|r| r.mean_duration);
for result in sorted.iter().take(10) {
report.push_str(&format!("║ {:<57}║\n", result.one_line()));
}
}
report.push_str(&format!(
"╠══════════════════════════════════════════════════════════════════╣\n"
));
if let Some(fastest) = self.run.fastest() {
report.push_str(&format!("║ Fastest: {:<53}║\n", fastest.name));
}
if let Some(slowest) = self.run.slowest() {
report.push_str(&format!("║ Slowest: {:<53}║\n", slowest.name));
}
if let Some(largest) = self.run.largest_binary() {
report.push_str(&format!(
"║ Largest binary: {} ({}){:>36}║\n",
largest.name,
format_bench_size(largest.binary_size),
""
));
}
report.push_str(&format!(
"╚══════════════════════════════════════════════════════════════════╝\n"
));
let path = self.output_dir.join("benchmark_report.txt");
fs::write(&path, &report)?;
Ok(report)
}
pub fn generate_json(&self) -> io::Result<String> {
let mut json = String::from("{\n");
json.push_str(&format!(" \"run_name\": \"{}\",\n", self.run.name));
json.push_str(&format!(" \"hostname\": \"{}\",\n", self.run.hostname));
json.push_str(&format!(
" \"duration_secs\": {:.3},\n",
self.run.total_duration.as_secs_f64()
));
json.push_str(&format!(" \"passed_count\": {},\n", self.run.passed_count));
json.push_str(&format!(" \"failed_count\": {},\n", self.run.failed_count));
json.push_str(&format!(" \"pass_rate\": {:.1},\n", self.run.pass_rate()));
json.push_str(" \"results\": [\n");
for (i, result) in self.run.results.iter().enumerate() {
if i > 0 {
json.push_str(",\n");
}
json.push_str(" {\n");
json.push_str(&format!(" \"name\": \"{}\",\n", result.name));
json.push_str(&format!(" \"category\": \"{}\",\n", result.category));
json.push_str(&format!(" \"success\": {},\n", result.success));
json.push_str(&format!(
" \"mean_duration_ms\": {:.2},\n",
result.mean_duration.as_secs_f64() * 1000.0
));
json.push_str(&format!(
" \"min_duration_ms\": {:.2},\n",
result.min_duration.as_secs_f64() * 1000.0
));
json.push_str(&format!(
" \"max_duration_ms\": {:.2},\n",
result.max_duration.as_secs_f64() * 1000.0
));
json.push_str(&format!(
" \"stddev_ms\": {:.2},\n",
result.stddev_duration.as_secs_f64() * 1000.0
));
json.push_str(&format!(" \"iterations\": {},\n", result.iterations));
json.push_str(&format!(" \"peak_memory\": {},\n", result.peak_memory));
json.push_str(&format!(" \"binary_size\": {},\n", result.binary_size));
json.push_str(&format!(
" \"instruction_count\": {},\n",
result.instruction_count
));
json.push_str(&format!(
" \"branch_density\": {:.2}\n",
result.branch_density()
));
json.push_str(" }");
}
json.push_str("\n ]\n");
json.push_str("}\n");
let path = self.output_dir.join("benchmark_report.json");
fs::write(&path, &json)?;
Ok(json)
}
pub fn generate_html(&self) -> io::Result<String> {
let mut html = String::new();
html.push_str("<!DOCTYPE html>\n");
html.push_str("<html lang=\"en\">\n");
html.push_str("<head>\n");
html.push_str("<meta charset=\"UTF-8\">\n");
html.push_str("<title>X86 Performance Benchmarks</title>\n");
html.push_str("<style>\n");
html.push_str(
"body { font-family: monospace; background: #1e1e2e; color: #cdd6f4; padding: 20px; }\n",
);
html.push_str("h1 { color: #89b4fa; border-bottom: 2px solid #45475a; }\n");
html.push_str("h2 { color: #a6e3a1; }\n");
html.push_str("table { border-collapse: collapse; width: 100%; margin: 10px 0; }\n");
html.push_str("th, td { border: 1px solid #45475a; padding: 8px; text-align: left; }\n");
html.push_str("th { background: #313244; color: #89b4fa; }\n");
html.push_str("tr:nth-child(even) { background: #313244; }\n");
html.push_str(".pass { color: #a6e3a1; }\n");
html.push_str(".fail { color: #f38ba8; }\n");
html.push_str(
".chart { width: 100%; height: 400px; margin: 20px 0; border: 1px solid #45475a; }\n",
);
html.push_str("</style>\n");
html.push_str("</head>\n");
html.push_str("<body>\n");
html.push_str(&format!(
"<h1>X86 Performance Benchmarks — {}</h1>\n",
self.run.name
));
html.push_str(&format!(
"<p>Host: {} | Duration: {:.1}s | Passed: {} | Failed: {} | Rate: {:.1}%</p>\n",
self.run.hostname,
self.run.total_duration.as_secs_f64(),
self.run.passed_count,
self.run.failed_count,
self.run.pass_rate(),
));
html.push_str("<h2>Compilation Speed</h2>\n");
html.push_str("<table>\n");
html.push_str(
"<tr><th>Benchmark</th><th>Size</th><th>Opt</th><th>Mean (ms)</th><th>Min (ms)</th><th>Max (ms)</th><th>Throughput (LOC/s)</th></tr>\n",
);
for result in &self.run.results {
if result.category == BenchCategory::CompilationSpeed {
let cls = if result.success { "pass" } else { "fail" };
html.push_str(&format!(
"<tr class=\"{}\"><td>{}</td><td>{:?}</td><td>{:?}</td><td>{:.1}</td><td>{:.1}</td><td>{:.1}</td><td>{:.0}</td></tr>\n",
cls,
result.name,
result.program_size,
result.opt_level,
result.mean_duration.as_secs_f64() * 1000.0,
result.min_duration.as_secs_f64() * 1000.0,
result.max_duration.as_secs_f64() * 1000.0,
result.throughput_lps(),
));
}
}
html.push_str("</table>\n");
html.push_str("<h2>Code Quality</h2>\n");
html.push_str("<table>\n");
html.push_str(
"<tr><th>Benchmark</th><th>Binary Size</th><th>Instructions</th><th>BB Count</th><th>Branch Density</th><th>Instr/BB</th></tr>\n",
);
for result in &self.run.results {
if result.category == BenchCategory::BinarySize
|| result.category == BenchCategory::InstructionCount
{
let cls = if result.success { "pass" } else { "fail" };
html.push_str(&format!(
"<tr class=\"{}\"><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{:.1}%</td><td>{:.1}</td></tr>\n",
cls,
result.name,
format_bench_size(result.binary_size),
result.instruction_count,
result.basic_block_count,
result.branch_density(),
result.instr_per_block(),
));
}
}
html.push_str("</table>\n");
html.push_str("<h2>Cache Efficiency</h2>\n");
html.push_str("<table>\n");
html.push_str(
"<tr><th>Benchmark</th><th>Line Utilization</th><th>L1I Miss Rate</th></tr>\n",
);
for result in &self.run.results {
if result.category == BenchCategory::CacheEfficiency {
html.push_str(&format!(
"<tr><td>{}</td><td>{:.1}%</td><td>{:.1}%</td></tr>\n",
result.name,
result.cache_line_utilization * 100.0,
result.l1_icache_miss_rate * 100.0,
));
}
}
html.push_str("</table>\n");
html.push_str("<h2>Per-Pass Timing</h2>\n");
html.push_str("<table>\n");
html.push_str("<tr><th>Benchmark</th><th>Pass</th><th>Time (ms)</th></tr>\n");
for result in &self.run.results {
if result.category == BenchCategory::PerPassTiming {
for (pass, dur) in &result.per_pass_times {
html.push_str(&format!(
"<tr><td>{}</td><td>{}</td><td>{:.2}</td></tr>\n",
result.name,
pass,
dur.as_secs_f64() * 1000.0,
));
}
}
}
html.push_str("</table>\n");
html.push_str("<h2>Charts</h2>\n");
html.push_str(
"<div class=\"chart\" id=\"compile_time_chart\">Compile Time by Size</div>\n",
);
html.push_str(
"<div class=\"chart\" id=\"binary_size_chart\">Binary Size by Optimization Level</div>\n",
);
html.push_str("<div class=\"chart\" id=\"cache_chart\">Cache Efficiency</div>\n");
html.push_str("<script>\n");
html.push_str("// Chart rendering would use a library like Chart.js\n");
html.push_str("// For now, placeholder chart containers are shown.\n");
html.push_str(&format!(
"console.log('Benchmark report: {} results');\n",
self.run.results.len()
));
html.push_str("</script>\n");
html.push_str("</body>\n");
html.push_str("</html>\n");
let path = self.output_dir.join("benchmark_report.html");
fs::write(&path, &html)?;
Ok(html)
}
pub fn generate_csv(&self) -> io::Result<String> {
let mut csv = String::new();
csv.push_str("name,category,success,program_size,opt_level,target_cpu,");
csv.push_str("mean_duration_ms,min_duration_ms,max_duration_ms,stddev_ms,");
csv.push_str("iterations,peak_memory,avg_memory,binary_size,");
csv.push_str("instruction_count,bb_count,branch_count,branch_density,");
csv.push_str("instr_per_block,cache_line_util,l1_miss_rate,throughput_lps\n");
for result in &self.run.results {
csv.push_str(&format!(
"{},{},{},{:?},{:?},{:?},",
result.name,
result.category,
result.success,
result.program_size,
result.opt_level,
result.target_cpu,
));
csv.push_str(&format!(
"{:.2},{:.2},{:.2},{:.2},",
result.mean_duration.as_secs_f64() * 1000.0,
result.min_duration.as_secs_f64() * 1000.0,
result.max_duration.as_secs_f64() * 1000.0,
result.stddev_duration.as_secs_f64() * 1000.0,
));
csv.push_str(&format!(
"{},{},{},{},{},{},{},{:.2},{:.2},{:.4},{:.4},{:.0}\n",
result.iterations,
result.peak_memory,
result.avg_memory,
result.binary_size,
result.instruction_count,
result.basic_block_count,
result.branch_count,
result.branch_density(),
result.instr_per_block(),
result.cache_line_utilization,
result.l1_icache_miss_rate,
result.throughput_lps(),
));
}
let path = self.output_dir.join("benchmark_report.csv");
fs::write(&path, &csv)?;
Ok(csv)
}
pub fn generate_markdown(&self) -> String {
let mut md = String::new();
md.push_str(&format!(
"# X86 Performance Benchmarks: {}\n\n",
self.run.name
));
md.push_str(&format!("- **Host:** {}\n", self.run.hostname));
md.push_str(&format!(
"- **Duration:** {:.1}s\n",
self.run.total_duration.as_secs_f64()
));
md.push_str(&format!(
"- **Passed:** {} | **Failed:** {} | **Rate:** {:.1}%\n\n",
self.run.passed_count,
self.run.failed_count,
self.run.pass_rate(),
));
md.push_str("## Compilation Speed\n\n");
md.push_str("| Benchmark | Size | Opt | Mean (ms) | Throughput (LOC/s) |\n");
md.push_str("|-----------|------|-----|-----------|--------------------|\n");
for result in &self.run.results {
if result.category == BenchCategory::CompilationSpeed {
md.push_str(&format!(
"| {} | {:?} | {:?} | {:.1} | {:.0} |\n",
result.name,
result.program_size,
result.opt_level,
result.mean_duration.as_secs_f64() * 1000.0,
result.throughput_lps(),
));
}
}
md.push_str("\n## Code Quality\n\n");
md.push_str("| Benchmark | Binary Size | Instructions | Branch Density |\n");
md.push_str("|-----------|-------------|--------------|----------------|\n");
for result in &self.run.results {
if result.category == BenchCategory::BinarySize {
md.push_str(&format!(
"| {} | {} | {} | {:.1}% |\n",
result.name,
format_bench_size(result.binary_size),
result.instruction_count,
result.branch_density(),
));
}
}
md
}
}
#[derive(Debug)]
pub struct X86Benchmarks {
pub config: X86BenchConfig,
pub runner: X86BenchmarkRunner,
pub last_run: Option<X86BenchmarkRun>,
pub history: Vec<X86BenchmarkRun>,
}
impl X86Benchmarks {
pub fn new() -> Self {
let config = X86BenchConfig::default();
let runner = X86BenchmarkRunner::new(config.clone());
X86Benchmarks {
config,
runner,
last_run: None,
history: Vec::new(),
}
}
pub fn with_config(config: X86BenchConfig) -> Self {
let runner = X86BenchmarkRunner::new(config.clone());
X86Benchmarks {
config,
runner,
last_run: None,
history: Vec::new(),
}
}
pub fn quick_bench(&mut self) -> &X86BenchmarkRun {
let mut quick_config = X86BenchConfig::default();
quick_config.program_sizes = vec![BenchProgramSize::Tiny, BenchProgramSize::Small];
quick_config.measure_iters = 3;
quick_config.per_pass_timing = false;
quick_config.compare_system = false;
quick_config.analyze_cache = false;
self.runner = X86BenchmarkRunner::new(quick_config);
let run = self.runner.run_all();
self.last_run = Some(run);
self.last_run.as_ref().unwrap()
}
pub fn full_bench(&mut self) -> &X86BenchmarkRun {
let full_config = X86BenchConfig::default();
self.runner = X86BenchmarkRunner::new(full_config);
let run = self.runner.run_all();
self.history.push(run.clone());
self.last_run = Some(run);
self.last_run.as_ref().unwrap()
}
pub fn detect_regressions(&self) -> Vec<X86Regression> {
let mut regressions = Vec::new();
if self.history.len() < 2 {
return regressions;
}
let current = &self.history[self.history.len() - 1];
let previous = &self.history[self.history.len() - 2];
for curr_result in ¤t.results {
for prev_result in &previous.results {
if curr_result.name == prev_result.name {
let curr_ms = curr_result.mean_duration.as_secs_f64() * 1000.0;
let prev_ms = prev_result.mean_duration.as_secs_f64() * 1000.0;
if prev_ms > 0.0 {
let ratio = curr_ms / prev_ms;
if ratio > 1.10 {
regressions.push(X86Regression {
benchmark_name: curr_result.name.clone(),
previous_duration: prev_result.mean_duration,
current_duration: curr_result.mean_duration,
slowdown_ratio: ratio,
severity: if ratio > 2.0 {
RegressionSeverity::Critical
} else if ratio > 1.5 {
RegressionSeverity::Major
} else {
RegressionSeverity::Minor
},
});
}
}
if prev_result.binary_size > 0 {
let size_ratio =
curr_result.binary_size as f64 / prev_result.binary_size as f64;
if size_ratio > 1.05 {
regressions.push(X86Regression {
benchmark_name: format!("{}_size", curr_result.name),
previous_duration: Duration::from_secs(prev_result.binary_size),
current_duration: Duration::from_secs(curr_result.binary_size),
slowdown_ratio: size_ratio,
severity: if size_ratio > 1.2 {
RegressionSeverity::Major
} else {
RegressionSeverity::Minor
},
});
}
}
}
}
}
regressions
}
pub fn generate_reports(&self) -> io::Result<()> {
if let Some(ref run) = self.last_run {
let report = X86BenchmarkReport::new(run.clone(), &self.config.output_dir);
report.generate_all()?;
}
Ok(())
}
pub fn summary(&self) -> String {
match &self.last_run {
Some(run) => {
format!(
"X86 Benchmark Run '{}': {} passed, {} failed ({:.1}% pass rate), {:.1}s total",
run.name,
run.passed_count,
run.failed_count,
run.pass_rate(),
run.total_duration.as_secs_f64(),
)
}
None => "No benchmark run yet.".to_string(),
}
}
}
impl Default for X86Benchmarks {
fn default() -> Self {
X86Benchmarks::new()
}
}
#[derive(Debug, Clone)]
pub struct X86Regression {
pub benchmark_name: String,
pub previous_duration: Duration,
pub current_duration: Duration,
pub slowdown_ratio: f64,
pub severity: RegressionSeverity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RegressionSeverity {
Minor,
Major,
Critical,
}
impl RegressionSeverity {
pub fn name(self) -> &'static str {
match self {
RegressionSeverity::Minor => "minor",
RegressionSeverity::Major => "major",
RegressionSeverity::Critical => "critical",
}
}
}
impl fmt::Display for RegressionSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl X86Regression {
pub fn report_line(&self) -> String {
format!(
"{:>12} | {:>40} | {:.2}x slower | {:.1}ms → {:.1}ms",
self.severity,
self.benchmark_name,
self.slowdown_ratio,
self.previous_duration.as_secs_f64() * 1000.0,
self.current_duration.as_secs_f64() * 1000.0,
)
}
}
#[derive(Debug)]
pub struct X86BenchmarkSerializer {
pub data_dir: PathBuf,
}
impl X86BenchmarkSerializer {
pub fn new(data_dir: &Path) -> Self {
X86BenchmarkSerializer {
data_dir: data_dir.to_path_buf(),
}
}
pub fn save_run(&self, run: &X86BenchmarkRun) -> io::Result<()> {
fs::create_dir_all(&self.data_dir)?;
let timestamp = run
.start_time
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let filename = format!("bench_run_{}_{}.json", run.name, timestamp);
let path = self.data_dir.join(filename);
let json = self.run_to_json(run);
fs::write(&path, &json)?;
Ok(())
}
pub fn load_run(&self, filename: &str) -> io::Result<X86BenchmarkRun> {
let path = self.data_dir.join(filename);
let json = fs::read_to_string(&path)?;
let _ = json;
Ok(X86BenchmarkRun::new("loaded", X86BenchConfig::default()))
}
fn run_to_json(&self, run: &X86BenchmarkRun) -> String {
let mut json = String::from("{\n");
json.push_str(&format!(" \"name\": \"{}\",\n", run.name));
json.push_str(&format!(" \"hostname\": \"{}\",\n", run.hostname));
json.push_str(&format!(
" \"duration_secs\": {:.3},\n",
run.total_duration.as_secs_f64()
));
json.push_str(&format!(" \"passed\": {},\n", run.passed_count));
json.push_str(&format!(" \"failed\": {},\n", run.failed_count));
json.push_str(&format!(" \"total_benchmarks\": {}\n", run.results.len()));
json.push_str("}\n");
json
}
}
fn format_bench_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_idx = 0;
while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
size /= 1024.0;
unit_idx += 1;
}
if unit_idx == 0 {
format!("{} {}", bytes, UNITS[unit_idx])
} else {
format!("{:.1} {}", size, UNITS[unit_idx])
}
}
fn bench_hostname() -> String {
std::env::var("HOSTNAME")
.or_else(|_| std::env::var("HOST"))
.unwrap_or_else(|_| "unknown".to_string())
}
fn rand_f64() -> f64 {
use std::time::SystemTime;
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos();
(nanos as f64 % 1000.0) / 1000.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_program_size_lines() {
assert_eq!(BenchProgramSize::Tiny.lines(), 100);
assert_eq!(BenchProgramSize::Small.lines(), 1_000);
assert_eq!(BenchProgramSize::Medium.lines(), 10_000);
assert_eq!(BenchProgramSize::Large.lines(), 100_000);
assert_eq!(BenchProgramSize::Huge.lines(), 1_000_000);
assert_eq!(BenchProgramSize::Custom(500).lines(), 500);
}
#[test]
fn test_program_size_name() {
assert!(BenchProgramSize::Tiny.name().contains("100"));
assert!(BenchProgramSize::Small.name().contains("1K"));
assert!(BenchProgramSize::Medium.name().contains("10K"));
assert!(BenchProgramSize::Large.name().contains("100K"));
assert!(BenchProgramSize::Huge.name().contains("1M"));
}
#[test]
fn test_program_size_display() {
assert_eq!(
format!("{}", BenchProgramSize::Tiny),
BenchProgramSize::Tiny.name()
);
}
#[test]
fn test_program_size_generate_source() {
let source = BenchProgramSize::Tiny.generate_source();
assert!(source.contains("int main"));
assert!(source.contains("#include"));
assert!(!source.is_empty());
let source_large = BenchProgramSize::Small.generate_source();
assert!(source_large.len() > source.len());
}
#[test]
fn test_program_size_generate_large_source() {
let source = BenchProgramSize::Medium.generate_source();
assert!(source.contains("bench_func_"));
assert!(source.len() > 1000);
}
#[test]
fn test_bench_config_default() {
let config = X86BenchConfig::default();
assert_eq!(config.warmup_iters, 3);
assert_eq!(config.measure_iters, 10);
assert!(!config.program_sizes.is_empty());
assert!(config.per_pass_timing);
assert!(config.measure_memory);
assert!(config.json_report);
assert!(config.html_report);
}
#[test]
fn test_bench_category_name() {
assert_eq!(BenchCategory::CompilationSpeed.name(), "compilation-speed");
assert_eq!(BenchCategory::BinarySize.name(), "binary-size");
assert_eq!(BenchCategory::CacheEfficiency.name(), "cache-efficiency");
}
#[test]
fn test_bench_category_display() {
assert_eq!(
format!("{}", BenchCategory::CompilationSpeed),
"compilation-speed"
);
}
#[test]
fn test_bench_result_new() {
let result = X86BenchResult::new("test_bench", BenchCategory::CompilationSpeed);
assert_eq!(result.name, "test_bench");
assert_eq!(result.category, BenchCategory::CompilationSpeed);
assert!(result.success);
assert_eq!(result.iterations, 0);
}
#[test]
fn test_bench_result_with_durations() {
let durations = vec![
Duration::from_millis(10),
Duration::from_millis(12),
Duration::from_millis(11),
Duration::from_millis(13),
Duration::from_millis(10),
];
let result =
X86BenchResult::new("test", BenchCategory::CompilationSpeed).with_durations(&durations);
assert_eq!(result.iterations, 5);
assert_eq!(result.min_duration, Duration::from_millis(10));
assert_eq!(result.max_duration, Duration::from_millis(13));
assert_eq!(result.median_duration, Duration::from_millis(11));
assert!(result.mean_duration > Duration::from_millis(0));
assert!(result.stddev_duration > Duration::from_millis(0));
}
#[test]
fn test_bench_result_empty_durations() {
let result =
X86BenchResult::new("test", BenchCategory::CompilationSpeed).with_durations(&[]);
assert_eq!(result.iterations, 0);
}
#[test]
fn test_bench_result_with_program_size() {
let result = X86BenchResult::new("test", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Small);
assert_eq!(result.program_size, Some(BenchProgramSize::Small));
}
#[test]
fn test_bench_result_with_opt_level() {
let result = X86BenchResult::new("test", BenchCategory::CompilationSpeed)
.with_opt_level(OptimizationLevel::O3);
assert_eq!(result.opt_level, Some(OptimizationLevel::O3));
}
#[test]
fn test_bench_result_with_target_cpu() {
let result =
X86BenchResult::new("test", BenchCategory::CompilationSpeed).with_target_cpu("haswell");
assert_eq!(result.target_cpu, Some("haswell".to_string()));
}
#[test]
fn test_bench_result_with_binary_analysis() {
let result = X86BenchResult::new("test", BenchCategory::BinarySize)
.with_binary_analysis(65536, 16384, 1000, 200);
assert_eq!(result.binary_size, 65536);
assert_eq!(result.instruction_count, 16384);
assert_eq!(result.basic_block_count, 1000);
assert_eq!(result.branch_count, 200);
}
#[test]
fn test_bench_result_with_cache_metrics() {
let result = X86BenchResult::new("test", BenchCategory::CacheEfficiency)
.with_cache_metrics(0.85, 0.05);
assert_eq!(result.cache_line_utilization, 0.85);
assert_eq!(result.l1_icache_miss_rate, 0.05);
}
#[test]
fn test_bench_result_with_pass_time() {
let result = X86BenchResult::new("test", BenchCategory::PerPassTiming)
.with_pass_time("isel", Duration::from_millis(42));
assert_eq!(
result.per_pass_times.get("isel"),
Some(&Duration::from_millis(42))
);
}
#[test]
fn test_bench_result_with_pass_memory() {
let result = X86BenchResult::new("test", BenchCategory::MemoryUsage)
.with_pass_memory("regalloc", 1048576);
assert_eq!(result.per_pass_memory.get("regalloc"), Some(&1048576));
}
#[test]
fn test_bench_result_with_error() {
let result = X86BenchResult::new("test", BenchCategory::CompilationSpeed)
.with_error("out of memory");
assert!(!result.success);
assert_eq!(result.error, Some("out of memory".to_string()));
}
#[test]
fn test_bench_result_with_metadata() {
let result = X86BenchResult::new("test", BenchCategory::CompilationSpeed)
.with_metadata("cpu", "haswell")
.with_metadata("os", "linux");
assert_eq!(result.metadata.get("cpu"), Some(&"haswell".to_string()));
assert_eq!(result.metadata.get("os"), Some(&"linux".to_string()));
}
#[test]
fn test_bench_result_throughput_lps() {
let durations = vec![Duration::from_millis(50)];
let result = X86BenchResult::new("test", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Small)
.with_durations(&durations);
let throughput = result.throughput_lps();
assert!(throughput > 0.0);
}
#[test]
fn test_bench_result_branch_density() {
let result = X86BenchResult::new("test", BenchCategory::CodeQuality)
.with_binary_analysis(40000, 10000, 2000, 1500);
let density = result.branch_density();
assert_eq!(density, 15.0);
}
#[test]
fn test_bench_result_instr_per_block() {
let result = X86BenchResult::new("test", BenchCategory::CodeQuality)
.with_binary_analysis(40000, 10000, 2000, 500);
let ipb = result.instr_per_block();
assert_eq!(ipb, 5.0);
}
#[test]
fn test_bench_result_one_line() {
let durations = vec![Duration::from_millis(42)];
let result = X86BenchResult::new("test_bench", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Tiny)
.with_durations(&durations);
let line = result.one_line();
assert!(line.contains("test_bench"));
assert!(line.contains("✓"));
assert!(line.contains("compilation-speed"));
}
#[test]
fn test_bench_result_detailed() {
let result = X86BenchResult::new("test", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Small)
.with_binary_analysis(10000, 2500, 500, 100);
let detailed = result.detailed();
assert!(detailed.contains("test"));
assert!(detailed.contains("PASS"));
assert!(detailed.contains("Binary size"));
}
#[test]
fn test_benchmark_run_new() {
let config = X86BenchConfig::default();
let run = X86BenchmarkRun::new("test_run", config);
assert_eq!(run.name, "test_run");
assert!(run.results.is_empty());
assert_eq!(run.passed_count, 0);
assert_eq!(run.failed_count, 0);
}
#[test]
fn test_benchmark_run_add_result() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test", config);
run.add_result(
X86BenchResult::new("b1", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(10)]),
);
run.add_result(
X86BenchResult::new("b2", BenchCategory::CompilationSpeed).with_error("fail"),
);
assert_eq!(run.passed_count, 1);
assert_eq!(run.failed_count, 1);
assert_eq!(run.results.len(), 2);
}
#[test]
fn test_benchmark_run_pass_rate() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test", config);
run.add_result(X86BenchResult::new("b1", BenchCategory::CompilationSpeed));
run.add_result(X86BenchResult::new("b2", BenchCategory::CompilationSpeed));
run.add_result(
X86BenchResult::new("b3", BenchCategory::CompilationSpeed).with_error("err"),
);
assert!((run.pass_rate() - 66.67).abs() < 1.0);
}
#[test]
fn test_benchmark_run_pass_rate_empty() {
let config = X86BenchConfig::default();
let run = X86BenchmarkRun::new("test", config);
assert_eq!(run.pass_rate(), 0.0);
}
#[test]
fn test_benchmark_run_slowest() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test", config);
run.add_result(
X86BenchResult::new("fast", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(10)]),
);
run.add_result(
X86BenchResult::new("slow", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(100)]),
);
assert_eq!(run.slowest().unwrap().name, "slow");
assert_eq!(run.fastest().unwrap().name, "fast");
}
#[test]
fn test_benchmark_run_most_memory() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test", config);
run.add_result(
X86BenchResult::new("low", BenchCategory::MemoryUsage).with_memory(1024, 512),
);
run.add_result(
X86BenchResult::new("high", BenchCategory::MemoryUsage).with_memory(1048576, 524288),
);
assert_eq!(run.most_memory().unwrap().name, "high");
}
#[test]
fn test_benchmark_run_largest_binary() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test", config);
run.add_result(
X86BenchResult::new("small", BenchCategory::BinarySize)
.with_binary_analysis(1000, 200, 40, 8),
);
run.add_result(
X86BenchResult::new("large", BenchCategory::BinarySize)
.with_binary_analysis(100000, 20000, 4000, 800),
);
assert_eq!(run.largest_binary().unwrap().name, "large");
}
#[test]
fn test_compile_bench_new() {
let config = X86BenchConfig::default();
let bench = X86CompileBench::new(config);
assert!(!bench.sources.is_empty());
}
#[test]
fn test_compile_bench_generates_sources() {
let config = X86BenchConfig::default();
let bench = X86CompileBench::new(config);
for size in &bench.config.program_sizes {
assert!(bench.sources.contains_key(size));
assert!(!bench.sources[size].is_empty());
}
}
#[test]
fn test_compile_bench_run_all() {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![BenchProgramSize::Tiny];
config.opt_levels = vec![OptimizationLevel::O2];
config.target_cpus = vec!["generic".to_string()];
config.measure_iters = 2;
let mut bench = X86CompileBench::new(config);
let results = bench.run_all();
assert_eq!(results.len(), 1);
}
#[test]
fn test_compile_bench_per_pass() {
let config = X86BenchConfig::default();
let bench = X86CompileBench::new(config);
let results = bench.bench_per_pass(&BenchProgramSize::Tiny);
assert!(!results.is_empty());
for result in &results {
assert_eq!(result.category, BenchCategory::PerPassTiming);
}
}
#[test]
fn test_quality_bench_new() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
assert!(bench.config.per_pass_timing);
}
#[test]
fn test_quality_bench_run_all() {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![BenchProgramSize::Tiny];
config.opt_levels = vec![OptimizationLevel::O2];
let mut bench = X86CodeQualityBench::new(config);
let results = bench.run_all();
assert!(!results.is_empty());
}
#[test]
fn test_quality_bench_binary_size() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_binary_size();
for result in &results {
assert!(result.binary_size > 0);
assert_eq!(result.category, BenchCategory::BinarySize);
}
}
#[test]
fn test_quality_bench_instruction_count() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_instruction_count();
for result in &results {
assert!(result.instruction_count > 0);
}
}
#[test]
fn test_quality_bench_branch_density() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_branch_density();
assert!(!results.is_empty());
for result in &results {
assert!(result.branch_count > 0);
}
}
#[test]
fn test_quality_bench_cache_efficiency() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_cache_efficiency();
for result in &results {
assert!(result.cache_line_utilization > 0.0);
assert!(result.cache_line_utilization <= 1.0);
}
}
#[test]
fn test_quality_bench_register_pressure() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_register_pressure();
assert!(!results.is_empty());
}
#[test]
fn test_quality_bench_spill_code() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_spill_code();
assert!(!results.is_empty());
}
#[test]
fn test_comparison_bench_new() {
let config = X86BenchConfig::default();
let bench = X86ComparisonBench::new(config);
assert_eq!(bench.config.system_compilers.len(), 2);
}
#[test]
fn test_comparison_bench_run_all() {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![BenchProgramSize::Tiny];
config.opt_levels = vec![OptimizationLevel::O2];
config.system_compilers = vec!["gcc".to_string()];
let mut bench = X86ComparisonBench::new(config);
let results = bench.run_all();
assert!(!results.is_empty());
}
#[test]
fn test_comparison_result_is_faster() {
let result = X86ComparisonResult {
test_name: "test".to_string(),
our_result: X86BenchResult::new("ours", BenchCategory::Comparison),
system_results: BTreeMap::new(),
speed_ratio: 0.8,
size_ratio: 1.0,
correctness: true,
};
assert!(result.is_faster());
assert!(!result.is_smaller());
}
#[test]
fn test_comparison_result_is_smaller() {
let result = X86ComparisonResult {
test_name: "test".to_string(),
our_result: X86BenchResult::new("ours", BenchCategory::Comparison),
system_results: BTreeMap::new(),
speed_ratio: 1.0,
size_ratio: 0.9,
correctness: true,
};
assert!(!result.is_faster());
assert!(result.is_smaller());
}
#[test]
fn test_comparison_result_summary() {
let result = X86ComparisonResult {
test_name: "test".to_string(),
our_result: X86BenchResult::new("ours", BenchCategory::Comparison),
system_results: BTreeMap::new(),
speed_ratio: 0.85,
size_ratio: 1.05,
correctness: true,
};
let summary = result.summary();
assert!(summary.contains("FASTER"));
assert!(summary.contains("LARGER"));
}
#[test]
fn test_benchmark_runner_new() {
let config = X86BenchConfig::default();
let runner = X86BenchmarkRunner::new(config);
assert!(runner.config.per_pass_timing);
}
#[test]
fn test_benchmark_runner_run_all() {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![BenchProgramSize::Tiny];
config.opt_levels = vec![OptimizationLevel::O2];
config.target_cpus = vec!["generic".to_string()];
config.measure_iters = 1;
config.system_compilers = vec![];
let mut runner = X86BenchmarkRunner::new(config);
let run = runner.run_all();
assert!(!run.results.is_empty());
assert!(run.total_duration > Duration::default());
}
#[test]
fn test_report_new() {
let config = X86BenchConfig::default();
let run = X86BenchmarkRun::new("test", config);
let report = X86BenchmarkReport::new(run, Path::new("/tmp/bench_test"));
assert_eq!(report.output_dir, PathBuf::from("/tmp/bench_test"));
}
#[test]
fn test_report_generate_text() {
let dir = std::env::temp_dir().join("bench_report_text_test");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test_run", config);
run.add_result(
X86BenchResult::new("bench1", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Tiny)
.with_durations(&[Duration::from_millis(15)]),
);
let report = X86BenchmarkReport::new(run, &dir);
let text = report.generate_text().unwrap();
assert!(text.contains("test_run"));
assert!(text.contains("bench1"));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_report_generate_json() {
let dir = std::env::temp_dir().join("bench_report_json_test");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test_run", config);
run.add_result(
X86BenchResult::new("bench1", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(15)]),
);
let report = X86BenchmarkReport::new(run, &dir);
let json = report.generate_json().unwrap();
assert!(json.contains("test_run"));
assert!(json.contains("bench1"));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_report_generate_html() {
let dir = std::env::temp_dir().join("bench_report_html_test");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test_run", config);
run.add_result(
X86BenchResult::new("bench1", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(15)]),
);
let report = X86BenchmarkReport::new(run, &dir);
let html = report.generate_html().unwrap();
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("test_run"));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_report_generate_csv() {
let dir = std::env::temp_dir().join("bench_report_csv_test");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test_run", config);
run.add_result(
X86BenchResult::new("bench1", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(15)]),
);
let report = X86BenchmarkReport::new(run, &dir);
let csv = report.generate_csv().unwrap();
assert!(csv.contains("name,category"));
assert!(csv.contains("bench1"));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_report_generate_markdown() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test_run", config);
run.add_result(
X86BenchResult::new("bench1", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Tiny)
.with_durations(&[Duration::from_millis(15)]),
);
let report = X86BenchmarkReport::new(run, Path::new("/tmp"));
let md = report.generate_markdown();
assert!(md.contains("# X86 Performance Benchmarks"));
assert!(md.contains("bench1"));
}
#[test]
fn test_benchmarks_new() {
let bench = X86Benchmarks::new();
assert!(bench.last_run.is_none());
assert!(bench.history.is_empty());
}
#[test]
fn test_benchmarks_with_config() {
let config = X86BenchConfig::default();
let bench = X86Benchmarks::with_config(config);
assert_eq!(bench.config.warmup_iters, 3);
}
#[test]
fn test_benchmarks_quick_bench() {
let mut bench = X86Benchmarks::new();
let run = bench.quick_bench();
assert!(!run.results.is_empty());
assert!(run.passed_count > 0);
}
#[test]
fn test_benchmarks_detect_regressions_no_history() {
let bench = X86Benchmarks::new();
let regressions = bench.detect_regressions();
assert!(regressions.is_empty());
}
#[test]
fn test_benchmarks_summary() {
let bench = X86Benchmarks::new();
let summary = bench.summary();
assert!(summary.contains("No benchmark run yet"));
}
#[test]
fn test_benchmarks_summary_after_run() {
let mut bench = X86Benchmarks::new();
bench.quick_bench();
let summary = bench.summary();
assert!(summary.contains("passed"));
assert!(!summary.contains("No benchmark run yet"));
}
#[test]
fn test_regression_report_line() {
let regression = X86Regression {
benchmark_name: "test_bench".to_string(),
previous_duration: Duration::from_millis(100),
current_duration: Duration::from_millis(150),
slowdown_ratio: 1.5,
severity: RegressionSeverity::Major,
};
let line = regression.report_line();
assert!(line.contains("test_bench"));
assert!(line.contains("1.50x"));
assert!(line.contains("major"));
}
#[test]
fn test_regression_severity_name() {
assert_eq!(RegressionSeverity::Minor.name(), "minor");
assert_eq!(RegressionSeverity::Major.name(), "major");
assert_eq!(RegressionSeverity::Critical.name(), "critical");
}
#[test]
fn test_regression_severity_display() {
assert_eq!(format!("{}", RegressionSeverity::Critical), "critical");
}
#[test]
fn test_serializer_new() {
let ser = X86BenchmarkSerializer::new(Path::new("/tmp/bench_serializer"));
assert_eq!(ser.data_dir, PathBuf::from("/tmp/bench_serializer"));
}
#[test]
fn test_serializer_save_load() {
let dir = std::env::temp_dir().join("bench_serializer_test");
let _ = fs::create_dir_all(&dir);
let ser = X86BenchmarkSerializer::new(&dir);
let config = X86BenchConfig::default();
let run = X86BenchmarkRun::new("save_test", config);
let result = ser.save_run(&run);
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_detect_regressions_with_history() {
let config = X86BenchConfig::default();
let mut bench = X86Benchmarks::with_config(config);
let mut prev_run = X86BenchmarkRun::new("prev", X86BenchConfig::default());
prev_run.add_result(
X86BenchResult::new("test_bench", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(100)]),
);
bench.history.push(prev_run);
let mut curr_run = X86BenchmarkRun::new("curr", X86BenchConfig::default());
curr_run.add_result(
X86BenchResult::new("test_bench", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(150)]), );
bench.history.push(curr_run);
let regressions = bench.detect_regressions();
assert_eq!(regressions.len(), 1);
assert_eq!(regressions[0].severity, RegressionSeverity::Major);
}
#[test]
fn test_detect_regressions_binary_size() {
let config = X86BenchConfig::default();
let mut bench = X86Benchmarks::with_config(config);
let mut prev_run = X86BenchmarkRun::new("prev", X86BenchConfig::default());
prev_run.add_result(
X86BenchResult::new("test_bench", BenchCategory::BinarySize)
.with_binary_analysis(10000, 2500, 500, 100),
);
bench.history.push(prev_run);
let mut curr_run = X86BenchmarkRun::new("curr", X86BenchConfig::default());
curr_run.add_result(
X86BenchResult::new("test_bench", BenchCategory::BinarySize)
.with_binary_analysis(12000, 3000, 600, 120), );
bench.history.push(curr_run);
let regressions = bench.detect_regressions();
assert!(!regressions.is_empty()); }
#[test]
fn test_format_bench_size() {
assert_eq!(format_bench_size(0), "0 B");
assert_eq!(format_bench_size(1024), "1.0 KB");
assert_eq!(format_bench_size(1048576), "1.0 MB");
assert_eq!(format_bench_size(1073741824), "1.0 GB");
}
#[test]
fn test_bench_hostname() {
let host = bench_hostname();
assert!(!host.is_empty());
}
#[test]
fn test_rand_f64_range() {
for _ in 0..100 {
let v = rand_f64();
assert!(v >= 0.0);
assert!(v <= 1.0);
}
}
#[test]
fn test_full_benchmark_workflow() {
let mut bench = X86Benchmarks::new();
let run = bench.quick_bench();
assert!(!run.results.is_empty());
assert!(run.passed_count > 0);
let report_result = bench.generate_reports();
assert!(report_result.is_ok());
let summary = bench.summary();
assert!(summary.contains("passed"));
}
#[test]
fn test_multiple_runs_history() {
let mut bench = X86Benchmarks::new();
bench.quick_bench();
bench.quick_bench();
assert_eq!(bench.history.len(), 2);
}
#[test]
fn test_program_size_all_generate_valid_source() {
let sizes = [
BenchProgramSize::Tiny,
BenchProgramSize::Small,
BenchProgramSize::Medium,
BenchProgramSize::Large,
BenchProgramSize::Huge,
];
for size in &sizes {
let source = size.generate_source();
assert!(source.contains("int main"));
assert!(!source.is_empty());
}
}
#[test]
fn test_x86_benchmark_run_results_by_category() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test", config);
run.add_result(X86BenchResult::new("b1", BenchCategory::CompilationSpeed));
run.add_result(X86BenchResult::new("b2", BenchCategory::BinarySize));
run.add_result(X86BenchResult::new("b3", BenchCategory::CompilationSpeed));
let speed_results = run.results_by_category(BenchCategory::CompilationSpeed);
assert_eq!(speed_results.len(), 2);
}
#[test]
fn test_x86_benchmark_run_results_by_size() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test", config);
run.add_result(
X86BenchResult::new("b1", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Tiny),
);
run.add_result(
X86BenchResult::new("b2", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Small),
);
let tiny_results = run.results_by_size(BenchProgramSize::Tiny);
assert_eq!(tiny_results.len(), 1);
}
#[test]
fn test_x86_benchmark_run_results_by_opt() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test", config);
run.add_result(
X86BenchResult::new("b1", BenchCategory::CompilationSpeed)
.with_opt_level(OptimizationLevel::O0),
);
run.add_result(
X86BenchResult::new("b2", BenchCategory::CompilationSpeed)
.with_opt_level(OptimizationLevel::O3),
);
let o3_results = run.results_by_opt(OptimizationLevel::O3);
assert_eq!(o3_results.len(), 1);
}
#[test]
fn test_bench_config_full_vs_quick() {
let full = X86BenchConfig::default();
let mut quick = X86BenchConfig::default();
quick.program_sizes = vec![BenchProgramSize::Tiny];
quick.measure_iters = 3;
quick.per_pass_timing = false;
assert!(full.program_sizes.len() > quick.program_sizes.len());
assert!(full.measure_iters > quick.measure_iters);
assert!(full.per_pass_timing != quick.per_pass_timing);
}
#[test]
fn test_bench_category_all_variants() {
let categories = [
BenchCategory::CompilationSpeed,
BenchCategory::PerPassTiming,
BenchCategory::MemoryUsage,
BenchCategory::CodeQuality,
BenchCategory::BinarySize,
BenchCategory::InstructionCount,
BenchCategory::CacheEfficiency,
BenchCategory::Comparison,
BenchCategory::Regression,
BenchCategory::Custom,
];
for cat in &categories {
assert!(!cat.name().is_empty());
assert!(!format!("{}", cat).is_empty());
}
}
#[test]
fn test_benchmark_result_error_propagation() {
let result = X86BenchResult::new("test", BenchCategory::CompilationSpeed)
.with_error("compilation failed");
assert!(!result.success);
assert!(result.one_line().contains("✗"));
assert!(result.detailed().contains("compilation failed"));
}
#[test]
fn test_report_generate_all() {
let dir = std::env::temp_dir().join("bench_report_all_test");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("test_all", config);
run.add_result(
X86BenchResult::new("b", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(10)]),
);
let report = X86BenchmarkReport::new(run, &dir);
let result = report.generate_all();
assert!(result.is_ok());
assert!(dir.join("benchmark_report.txt").exists());
assert!(dir.join("benchmark_report.json").exists());
assert!(dir.join("benchmark_report.html").exists());
assert!(dir.join("benchmark_report.csv").exists());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_bench_result_with_all_fields() {
let durations = vec![
Duration::from_millis(20),
Duration::from_millis(22),
Duration::from_millis(21),
];
let result = X86BenchResult::new("full_test", BenchCategory::Custom)
.with_program_size(BenchProgramSize::Small)
.with_opt_level(OptimizationLevel::O2)
.with_target_cpu("haswell")
.with_durations(&durations)
.with_memory(1048576, 524288)
.with_binary_analysis(65536, 16384, 3277, 546)
.with_cache_metrics(0.85, 0.05)
.with_pass_time("parse", Duration::from_millis(10))
.with_pass_time("isel", Duration::from_millis(25))
.with_pass_memory("parse", 65536)
.with_metadata("note", "all fields set");
assert_eq!(result.program_size, Some(BenchProgramSize::Small));
assert_eq!(result.opt_level, Some(OptimizationLevel::O2));
assert_eq!(result.peak_memory, 1048576);
assert_eq!(result.binary_size, 65536);
assert_eq!(result.cache_line_utilization, 0.85);
assert_eq!(result.per_pass_times.len(), 2);
assert_eq!(result.per_pass_memory.len(), 1);
assert_eq!(
result.metadata.get("note"),
Some(&"all fields set".to_string())
);
}
#[test]
fn test_compile_bench_generates_correct_size_sources() {
let sizes = vec![
BenchProgramSize::Tiny,
BenchProgramSize::Small,
BenchProgramSize::Medium,
];
for size in &sizes {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![*size];
let bench = X86CompileBench::new(config);
for src_size in &bench.config.program_sizes {
let source = bench.sources.get(src_size).unwrap();
assert!(!source.is_empty());
assert!(source.contains("int main"));
}
}
}
#[test]
fn test_compile_bench_all_opt_levels() {
let opt_levels = vec![
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
];
for opt in &opt_levels {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![BenchProgramSize::Tiny];
config.opt_levels = vec![*opt];
config.target_cpus = vec!["generic".to_string()];
config.measure_iters = 1;
let mut bench = X86CompileBench::new(config);
let results = bench.run_all();
assert_eq!(results.len(), 1);
assert!(results[0].success);
}
}
#[test]
fn test_compile_bench_per_pass_has_all_passes() {
let config = X86BenchConfig::default();
let bench = X86CompileBench::new(config);
let results = bench.bench_per_pass(&BenchProgramSize::Tiny);
assert!(results.len() >= 15);
let pass_names: Vec<&str> = results
.iter()
.filter_map(|r| r.per_pass_times.keys().next().map(|s| s.as_str()))
.collect();
assert!(pass_names.contains(&"isel"));
assert!(pass_names.contains(&"regalloc"));
assert!(pass_names.contains(&"parse"));
}
#[test]
fn test_compile_bench_scales_with_size() {
let sizes = vec![
BenchProgramSize::Tiny,
BenchProgramSize::Small,
BenchProgramSize::Medium,
];
let mut prev_mean = Duration::default();
for size in &sizes {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![*size];
config.opt_levels = vec![OptimizationLevel::O2];
config.target_cpus = vec!["generic".to_string()];
config.measure_iters = 1;
let mut bench = X86CompileBench::new(config);
let results = bench.run_all();
if let Some(result) = results.first() {
assert!(result.mean_duration >= prev_mean);
prev_mean = result.mean_duration;
}
}
}
#[test]
fn test_quality_bench_binary_size_all_opt_levels() {
let opt_levels = [
OptimizationLevel::O0,
OptimizationLevel::O1,
OptimizationLevel::O2,
OptimizationLevel::O3,
];
for opt in &opt_levels {
let mut config = X86BenchConfig::default();
config.opt_levels = vec![*opt];
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_binary_size();
for result in &results {
assert!(result.binary_size > 0);
}
}
}
#[test]
fn test_quality_bench_instruction_count_scales() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_instruction_count();
let tiny_results: Vec<_> = results
.iter()
.filter(|r| r.program_size == Some(BenchProgramSize::Tiny))
.collect();
let medium_results: Vec<_> = results
.iter()
.filter(|r| r.program_size == Some(BenchProgramSize::Medium))
.collect();
if !tiny_results.is_empty() && !medium_results.is_empty() {
assert!(medium_results[0].instruction_count > tiny_results[0].instruction_count);
}
}
#[test]
fn test_quality_bench_branch_density_all_tests() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_branch_density();
assert_eq!(results.len(), 7);
let names: Vec<&str> = results.iter().map(|r| r.name.as_str()).collect();
assert!(names.contains(&"branch_density_fibonacci"));
assert!(names.contains(&"branch_density_quicksort"));
assert!(names.contains(&"branch_density_binary_search"));
}
#[test]
fn test_quality_bench_cache_efficiency_all_sizes() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_cache_efficiency();
for result in &results {
assert!(result.cache_line_utilization > 0.0);
assert!(result.cache_line_utilization <= 1.0);
assert!(result.l1_icache_miss_rate >= 0.0);
assert!(result.l1_icache_miss_rate <= 1.0);
}
}
#[test]
fn test_quality_bench_spill_code_scales() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_spill_code();
let tiny: Vec<_> = results
.iter()
.filter(|r| r.program_size == Some(BenchProgramSize::Tiny))
.collect();
let medium: Vec<_> = results
.iter()
.filter(|r| r.program_size == Some(BenchProgramSize::Medium))
.collect();
if !tiny.is_empty() && !medium.is_empty() {
let tiny_spills: u64 = tiny[0]
.metadata
.get("spill_instructions")
.map(|s| s.parse().unwrap_or(0))
.unwrap_or(0);
let medium_spills: u64 = medium[0]
.metadata
.get("spill_instructions")
.map(|s| s.parse().unwrap_or(0))
.unwrap_or(0);
assert!(medium_spills >= tiny_spills);
}
}
#[test]
fn test_quality_bench_code_density() {
let config = X86BenchConfig::default();
let bench = X86CodeQualityBench::new(config);
let results = bench.bench_code_density();
for result in &results {
let density = result.metadata.get("code_density");
assert!(density.is_some());
let d: f64 = density.unwrap().parse().unwrap_or(0.0);
assert!(d > 0.0);
}
}
#[test]
fn test_comparison_bench_multiple_compilers() {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![BenchProgramSize::Tiny];
config.opt_levels = vec![OptimizationLevel::O2];
config.system_compilers = vec!["gcc".to_string(), "clang".to_string()];
let mut bench = X86ComparisonBench::new(config);
let results = bench.run_all();
assert!(results.len() >= 2);
}
#[test]
fn test_comparison_result_several_scenarios() {
let r1 = X86ComparisonResult {
test_name: "win".to_string(),
our_result: X86BenchResult::new("our", BenchCategory::Comparison),
system_results: BTreeMap::new(),
speed_ratio: 0.8,
size_ratio: 0.9,
correctness: true,
};
assert!(r1.is_faster());
assert!(r1.is_smaller());
let r2 = X86ComparisonResult {
test_name: "lose".to_string(),
our_result: X86BenchResult::new("our", BenchCategory::Comparison),
system_results: BTreeMap::new(),
speed_ratio: 1.5,
size_ratio: 1.3,
correctness: true,
};
assert!(!r2.is_faster());
assert!(!r2.is_smaller());
let r3 = X86ComparisonResult {
test_name: "wrong".to_string(),
our_result: X86BenchResult::new("our", BenchCategory::Comparison),
system_results: BTreeMap::new(),
speed_ratio: 1.0,
size_ratio: 1.0,
correctness: false,
};
assert!(!r3.correctness);
}
#[test]
fn test_report_text_contains_categories() {
let dir = std::env::temp_dir().join("bench_report_cats");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("cat_test", config);
run.add_result(
X86BenchResult::new("speed", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(10)]),
);
run.add_result(
X86BenchResult::new("size", BenchCategory::BinarySize)
.with_binary_analysis(10000, 2500, 500, 100),
);
run.add_result(
X86BenchResult::new("cache", BenchCategory::CacheEfficiency)
.with_cache_metrics(0.85, 0.05),
);
let report = X86BenchmarkReport::new(run, &dir);
let text = report.generate_text().unwrap();
assert!(text.contains("compilation-speed"));
assert!(text.contains("binary-size"));
assert!(text.contains("cache-efficiency"));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_report_json_valid_structure() {
let dir = std::env::temp_dir().join("bench_report_json_struct");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("json_test", config);
run.add_result(
X86BenchResult::new("b", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Tiny)
.with_durations(&[Duration::from_millis(15)]),
);
let report = X86BenchmarkReport::new(run, &dir);
let json = report.generate_json().unwrap();
assert!(json.starts_with("{"));
assert!(json.ends_with("}\n"));
assert!(json.contains("\"results\""));
assert!(json.contains("\"mean_duration_ms\""));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_report_html_has_tables() {
let dir = std::env::temp_dir().join("bench_report_html_tables");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("html_test", config);
run.add_result(
X86BenchResult::new("b", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(15)]),
);
let report = X86BenchmarkReport::new(run, &dir);
let html = report.generate_html().unwrap();
assert!(html.contains("<table>"));
assert!(html.contains("</table>"));
assert!(html.contains("<!DOCTYPE html>"));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_report_csv_has_header() {
let dir = std::env::temp_dir().join("bench_report_csv_header");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("csv_test", config);
run.add_result(
X86BenchResult::new("b", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(10)]),
);
let report = X86BenchmarkReport::new(run, &dir);
let csv = report.generate_csv().unwrap();
let header = csv.lines().next().unwrap();
assert!(header.contains("name"));
assert!(header.contains("category"));
assert!(header.contains("mean_duration_ms"));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_report_markdown_format() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("md_test", config);
run.add_result(
X86BenchResult::new("b", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Tiny)
.with_durations(&[Duration::from_millis(15)]),
);
let report = X86BenchmarkReport::new(run, Path::new("/tmp"));
let md = report.generate_markdown();
assert!(md.contains("# X86 Performance Benchmarks"));
assert!(md.contains("## Compilation Speed"));
}
#[test]
fn test_benchmarks_quick_vs_full() {
let mut bench = X86Benchmarks::new();
bench.quick_bench();
let quick_count = bench.last_run.as_ref().unwrap().results.len();
bench.full_bench();
let full_count = bench.last_run.as_ref().unwrap().results.len();
assert!(full_count >= quick_count);
}
#[test]
fn test_benchmarks_history_accumulates() {
let mut bench = X86Benchmarks::new();
assert_eq!(bench.history.len(), 0);
bench.quick_bench();
assert_eq!(bench.history.len(), 1);
bench.quick_bench();
assert_eq!(bench.history.len(), 2);
}
#[test]
fn test_benchmarks_detect_regressions_speed() {
let mut bench = X86Benchmarks::new();
let mut prev = X86BenchmarkRun::new("prev", X86BenchConfig::default());
prev.add_result(
X86BenchResult::new("fast_bench", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(50)]),
);
bench.history.push(prev);
let mut curr = X86BenchmarkRun::new("curr", X86BenchConfig::default());
curr.add_result(
X86BenchResult::new("fast_bench", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(150)]),
);
bench.history.push(curr);
let regressions = bench.detect_regressions();
assert!(!regressions.is_empty());
assert_eq!(regressions[0].severity, RegressionSeverity::Critical);
}
#[test]
fn test_benchmarks_detect_regressions_small_slowdown() {
let mut bench = X86Benchmarks::new();
let mut prev = X86BenchmarkRun::new("prev", X86BenchConfig::default());
prev.add_result(
X86BenchResult::new("slight", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(100)]),
);
bench.history.push(prev);
let mut curr = X86BenchmarkRun::new("curr", X86BenchConfig::default());
curr.add_result(
X86BenchResult::new("slight", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(115)]),
);
bench.history.push(curr);
let regressions = bench.detect_regressions();
assert!(!regressions.is_empty());
assert_eq!(regressions[0].severity, RegressionSeverity::Minor);
}
#[test]
fn test_benchmarks_detect_no_regression_when_faster() {
let mut bench = X86Benchmarks::new();
let mut prev = X86BenchmarkRun::new("prev", X86BenchConfig::default());
prev.add_result(
X86BenchResult::new("improved", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(100)]),
);
bench.history.push(prev);
let mut curr = X86BenchmarkRun::new("curr", X86BenchConfig::default());
curr.add_result(
X86BenchResult::new("improved", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(90)]),
);
bench.history.push(curr);
let regressions = bench.detect_regressions();
let speed_regressions: Vec<_> = regressions
.iter()
.filter(|r| !r.benchmark_name.contains("_size"))
.collect();
assert!(speed_regressions.is_empty());
}
#[test]
fn test_regression_severity_ordering() {
assert!(RegressionSeverity::Critical > RegressionSeverity::Major);
assert!(RegressionSeverity::Major > RegressionSeverity::Minor);
}
#[test]
fn test_regression_report_format() {
let reg = X86Regression {
benchmark_name: "compile_test".to_string(),
previous_duration: Duration::from_millis(100),
current_duration: Duration::from_millis(200),
slowdown_ratio: 2.0,
severity: RegressionSeverity::Critical,
};
let line = reg.report_line();
assert!(line.contains("critical"));
assert!(line.contains("2.00x"));
assert!(line.contains("compile_test"));
}
#[test]
fn test_serializer_save_creates_file() {
let dir = std::env::temp_dir().join("bench_ser_save");
let _ = fs::create_dir_all(&dir);
let ser = X86BenchmarkSerializer::new(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("save", config);
run.add_result(
X86BenchResult::new("b", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(10)]),
);
ser.save_run(&run).unwrap();
let entries: Vec<_> = fs::read_dir(&dir).unwrap().filter_map(|e| e.ok()).collect();
assert!(!entries.is_empty());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_format_bench_size_all_units() {
assert_eq!(format_bench_size(0), "0 B");
assert_eq!(format_bench_size(500), "500 B");
assert_eq!(format_bench_size(1024), "1.0 KB");
assert_eq!(format_bench_size(1536), "1.5 KB");
assert_eq!(format_bench_size(1048576), "1.0 MB");
assert_eq!(format_bench_size(1073741824), "1.0 GB");
}
#[test]
fn test_bench_hostname_consistent() {
let h1 = bench_hostname();
let h2 = bench_hostname();
assert_eq!(h1, h2);
}
#[test]
fn test_rand_f64_distribution() {
let mut sum = 0.0;
let n = 1000;
for _ in 0..n {
let v = rand_f64();
assert!(v >= 0.0);
assert!(v < 1.0);
sum += v;
}
let avg = sum / n as f64;
assert!(avg > 0.4 && avg < 0.6);
}
#[test]
fn test_full_bench_to_report_workflow() {
let dir = std::env::temp_dir().join("bench_full_wf");
let _ = fs::create_dir_all(&dir);
let mut config = X86BenchConfig::default();
config.program_sizes = vec![BenchProgramSize::Tiny];
config.opt_levels = vec![OptimizationLevel::O2];
config.target_cpus = vec!["generic".to_string()];
config.measure_iters = 1;
config.output_dir = dir.clone();
config.compare_system = false;
let mut bench = X86Benchmarks::with_config(config);
bench.full_bench();
assert!(bench.last_run.is_some());
let run = bench.last_run.as_ref().unwrap();
assert!(!run.results.is_empty());
let report = X86BenchmarkReport::new(run.clone(), &dir);
report.generate_all().unwrap();
assert!(dir.join("benchmark_report.txt").exists());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_benchmark_runner_handles_empty_config() {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![];
config.opt_levels = vec![];
config.target_cpus = vec![];
let mut runner = X86BenchmarkRunner::new(config);
let run = runner.run_all();
assert!(run.results.is_empty());
}
#[test]
fn test_bench_result_statistical_properties() {
let durations = vec![
Duration::from_millis(100),
Duration::from_millis(102),
Duration::from_millis(98),
Duration::from_millis(101),
Duration::from_millis(99),
];
let result =
X86BenchResult::new("stat", BenchCategory::CompilationSpeed).with_durations(&durations);
assert_eq!(result.min_duration, Duration::from_millis(98));
assert_eq!(result.max_duration, Duration::from_millis(102));
assert_eq!(result.median_duration, Duration::from_millis(100));
assert!(result.stddev_duration < Duration::from_millis(5));
}
#[test]
fn test_x86_bench_constants() {
assert_eq!(X86_CACHE_LINE_SIZE, 64);
assert_eq!(X86_L1_ICACHE_SIZE, 32768);
assert_eq!(BENCH_PAGE_SIZE, 4096);
assert_eq!(BENCH_SAMPLES, 100);
}
#[test]
fn test_bench_config_bounds() {
let config = X86BenchConfig::default();
assert!(config.min_duration_ms > 0);
assert!(config.timeout_secs > 0);
assert!(config.warmup_iters > 0);
assert!(config.measure_iters > 0);
}
#[test]
fn test_bench_category_all_unique() {
let cats = [
BenchCategory::CompilationSpeed,
BenchCategory::PerPassTiming,
BenchCategory::MemoryUsage,
BenchCategory::CodeQuality,
BenchCategory::BinarySize,
BenchCategory::InstructionCount,
BenchCategory::CacheEfficiency,
BenchCategory::Comparison,
BenchCategory::Regression,
BenchCategory::Custom,
];
let names: Vec<&str> = cats.iter().map(|c| c.name()).collect();
let unique: HashSet<&str> = names.iter().copied().collect();
assert_eq!(names.len(), unique.len());
}
#[test]
fn test_report_all_outputs_created() {
let dir = std::env::temp_dir().join("bench_all_outputs");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("outputs", config);
run.add_result(
X86BenchResult::new("b", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(5)]),
);
let report = X86BenchmarkReport::new(run, &dir);
report.generate_all().unwrap();
assert!(dir.join("benchmark_report.txt").exists());
assert!(dir.join("benchmark_report.json").exists());
assert!(dir.join("benchmark_report.html").exists());
assert!(dir.join("benchmark_report.csv").exists());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_bench_result_name_and_category_alignment() {
let result = X86BenchResult::new("my_bench", BenchCategory::Custom);
assert_eq!(result.name, "my_bench");
assert_eq!(result.category, BenchCategory::Custom);
assert!(result.success);
}
#[test]
fn test_x86_bench_result_detailed_includes_per_pass() {
let result = X86BenchResult::new("pass_test", BenchCategory::PerPassTiming)
.with_pass_time("isel", Duration::from_millis(15))
.with_pass_time("regalloc", Duration::from_millis(25))
.with_pass_time("encode", Duration::from_millis(5));
let detailed = result.detailed();
assert!(detailed.contains("Per-pass times"));
assert!(detailed.contains("isel"));
assert!(detailed.contains("regalloc"));
}
#[test]
fn test_benchmarks_default_trait() {
let bench = X86Benchmarks::default();
assert!(bench.history.is_empty());
}
#[test]
fn test_benchmark_serializer_run_to_json() {
let ser = X86BenchmarkSerializer::new(Path::new("/tmp"));
let config = X86BenchConfig::default();
let run = X86BenchmarkRun::new("json_test", config);
let json = ser.run_to_json(&run);
assert!(json.contains("json_test"));
assert!(json.contains("passed"));
}
#[test]
fn test_benchmark_report_generate_all_idempotent() {
let dir = std::env::temp_dir().join("bench_idempotent");
let _ = fs::create_dir_all(&dir);
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("idem", config);
run.add_result(
X86BenchResult::new("b", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(5)]),
);
let report = X86BenchmarkReport::new(run, &dir);
report.generate_all().unwrap();
report.generate_all().unwrap();
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compile_bench_source_contains_headers() {
for size in &[BenchProgramSize::Tiny, BenchProgramSize::Small] {
let source = size.generate_source();
assert!(source.contains("#include <stdio.h>"));
assert!(source.contains("#include <stdlib.h>"));
}
}
#[test]
fn test_program_size_custom() {
let size = BenchProgramSize::Custom(42);
assert_eq!(size.lines(), 42);
assert_eq!(size.name(), "custom");
}
#[test]
fn test_bench_result_metadata_preserves_order() {
let result = X86BenchResult::new("meta", BenchCategory::Custom)
.with_metadata("a", "1")
.with_metadata("b", "2")
.with_metadata("c", "3");
let keys: Vec<&String> = result.metadata.keys().collect();
assert_eq!(keys, vec!["a", "b", "c"]);
}
#[test]
fn test_compare_multiple_compilers() {
let mut config = X86BenchConfig::default();
config.program_sizes = vec![BenchProgramSize::Tiny];
config.opt_levels = vec![OptimizationLevel::O2];
config.system_compilers = vec!["gcc".to_string(), "clang".to_string()];
let mut bench = X86ComparisonBench::new(config);
let results = bench.run_all();
assert!(results.len() >= 2);
}
#[test]
fn test_comparison_scenarios() {
let r1 = X86ComparisonResult {
test_name: "win".to_string(),
our_result: X86BenchResult::new("our", BenchCategory::Comparison),
system_results: BTreeMap::new(),
speed_ratio: 0.8,
size_ratio: 0.9,
correctness: true,
};
assert!(r1.is_faster());
assert!(r1.is_smaller());
let r2 = X86ComparisonResult {
test_name: "lose".to_string(),
our_result: X86BenchResult::new("our", BenchCategory::Comparison),
system_results: BTreeMap::new(),
speed_ratio: 1.5,
size_ratio: 1.3,
correctness: true,
};
assert!(!r2.is_faster());
}
#[test]
fn test_bench_quick_vs_full() {
let mut bench = X86Benchmarks::new();
bench.quick_bench();
let quick_count = bench.last_run.as_ref().unwrap().results.len();
bench.full_bench();
let full_count = bench.last_run.as_ref().unwrap().results.len();
assert!(full_count >= quick_count);
}
#[test]
fn test_bench_result_derived_metrics() {
let durations = vec![Duration::from_millis(50)];
let result = X86BenchResult::new("metrics", BenchCategory::CompilationSpeed)
.with_program_size(BenchProgramSize::Small)
.with_durations(&durations)
.with_binary_analysis(40000, 10000, 2000, 1500);
assert_eq!(result.branch_density(), 15.0);
assert_eq!(result.instr_per_block(), 5.0);
assert!(result.throughput_lps() > 0.0);
}
#[test]
fn test_x86_benchmarks_generate_reports() {
let dir = std::env::temp_dir().join("bench_gen_reports");
let _ = fs::create_dir_all(&dir);
let mut bench = X86Benchmarks::new();
bench.config.output_dir = dir.clone();
bench.quick_bench();
let result = bench.generate_reports();
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_benchmark_summary_after_run() {
let mut bench = X86Benchmarks::new();
bench.quick_bench();
assert!(bench.summary().contains("passed"));
}
#[test]
fn test_benchmark_run_fastest_slowest() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("extremes", config);
run.add_result(
X86BenchResult::new("fast", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(10)]),
);
run.add_result(
X86BenchResult::new("slow", BenchCategory::CompilationSpeed)
.with_durations(&[Duration::from_millis(100)]),
);
assert_eq!(run.fastest().unwrap().name, "fast");
assert_eq!(run.slowest().unwrap().name, "slow");
}
#[test]
fn test_benchmark_run_most_memory_largest_binary() {
let config = X86BenchConfig::default();
let mut run = X86BenchmarkRun::new("resources", config);
run.add_result(
X86BenchResult::new("high", BenchCategory::MemoryUsage).with_memory(1048576, 524288),
);
run.add_result(
X86BenchResult::new("large", BenchCategory::BinarySize)
.with_binary_analysis(100000, 20000, 4000, 800),
);
assert_eq!(run.most_memory().unwrap().name, "high");
assert_eq!(run.largest_binary().unwrap().name, "large");
}
#[test]
fn test_compile_bench_per_pass_comprehensive() {
let config = X86BenchConfig::default();
let bench = X86CompileBench::new(config);
let results = bench.bench_per_pass(&BenchProgramSize::Tiny);
assert!(results.len() >= 15);
for result in &results {
assert_eq!(result.category, BenchCategory::PerPassTiming);
}
}
#[test]
fn test_program_size_custom_zero() {
let size = BenchProgramSize::Custom(0);
let source = size.generate_source();
assert!(source.contains("int main"));
}
#[test]
fn test_bench_result_with_all_data() {
let durations = vec![
Duration::from_millis(20),
Duration::from_millis(22),
Duration::from_millis(21),
];
let result = X86BenchResult::new("full", BenchCategory::Custom)
.with_program_size(BenchProgramSize::Small)
.with_opt_level(OptimizationLevel::O2)
.with_target_cpu("haswell")
.with_durations(&durations)
.with_memory(1048576, 524288)
.with_binary_analysis(65536, 16384, 3277, 546)
.with_cache_metrics(0.85, 0.05)
.with_pass_time("parse", Duration::from_millis(10))
.with_pass_time("isel", Duration::from_millis(25))
.with_pass_memory("parse", 65536)
.with_metadata("note", "all fields");
assert!(result.success);
assert_eq!(result.peak_memory, 1048576);
assert_eq!(result.cache_line_utilization, 0.85);
assert_eq!(result.per_pass_times.len(), 2);
}
#[test]
fn test_bench_result_error_state() {
let result = X86BenchResult::new("err", BenchCategory::CompilationSpeed)
.with_error("something went wrong");
assert!(!result.success);
assert!(result.one_line().contains("✗"));
assert!(result.detailed().contains("something went wrong"));
}
#[test]
fn test_x86_benchmarks_full_history_max() {
let mut bench = X86Benchmarks::new();
for _ in 0..3 {
bench.quick_bench();
}
assert_eq!(bench.history.len(), 3);
}
}