use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use super::clang_selfhost::{
ArtifactRecord, BootstrapStage, CompileCompareResult, CompileRunResult, CompileStats,
CompilerTestCase, CompilerTestSuite, SelfHostConfig, SelfHostReport, StageArtifacts,
StageComparison, StagePerformance, StageResult, TestCaseResult, TestExpectation,
TestSuiteResult,
};
#[derive(Debug, Clone)]
pub struct BootstrapStage2Config {
pub stage1_compiler_path: PathBuf,
pub stage2_compiler_path: PathBuf,
pub stage3_compiler_path: PathBuf,
pub stage2_build_dir: PathBuf,
pub stage3_build_dir: PathBuf,
pub byte_for_byte_compare: bool,
pub ir_compare: bool,
pub asm_compare: bool,
pub run_test_suite: bool,
pub profile_performance: bool,
pub strip_debug_before_compare: bool,
pub verify_determinism: bool,
pub determinism_rounds: u32,
pub cross_targets: Vec<String>,
pub max_diff_lines: usize,
pub stage_timeout_secs: u64,
pub parallel_jobs: u32,
}
impl Default for BootstrapStage2Config {
fn default() -> Self {
Self {
stage1_compiler_path: PathBuf::from("build/stage1/bin/clang"),
stage2_compiler_path: PathBuf::from("build/stage2/bin/clang"),
stage3_compiler_path: PathBuf::from("build/stage3/bin/clang"),
stage2_build_dir: PathBuf::from("build/stage2"),
stage3_build_dir: PathBuf::from("build/stage3"),
byte_for_byte_compare: true,
ir_compare: true,
asm_compare: true,
run_test_suite: true,
profile_performance: true,
strip_debug_before_compare: false,
verify_determinism: true,
determinism_rounds: 3,
cross_targets: vec![],
max_diff_lines: 200,
stage_timeout_secs: 7200,
parallel_jobs: 4,
}
}
}
impl BootstrapStage2Config {
pub fn new(stage1: &Path, stage2: &Path, stage3: &Path) -> Self {
Self {
stage1_compiler_path: stage1.to_path_buf(),
stage2_compiler_path: stage2.to_path_buf(),
stage3_compiler_path: stage3.to_path_buf(),
..Default::default()
}
}
pub fn with_cross_target(mut self, target: &str) -> Self {
self.cross_targets.push(target.to_string());
self
}
pub fn with_determinism_rounds(mut self, rounds: u32) -> Self {
self.determinism_rounds = rounds;
self
}
}
pub struct BootstrapStage2Runner {
pub config: BootstrapStage2Config,
pub base_config: SelfHostConfig,
pub stage_results: Vec<StageBootstrapResult>,
pub comparisons: Vec<StageBootstrapComparison>,
pub artifacts: HashMap<BootstrapStage, StageArtifacts>,
pub performance: Vec<StagePerformance>,
pub test_results: Vec<TestSuiteResult>,
pub determinism_results: Vec<DeterminismResult>,
start_time: Instant,
}
#[derive(Debug, Clone)]
pub struct StageBootstrapResult {
pub stage: BootstrapStage,
pub success: bool,
pub compiler_built: bool,
pub compiler_path: PathBuf,
pub binary_size_bytes: u64,
pub object_count: u32,
pub warning_count: u32,
pub error_count: u32,
pub duration: Duration,
pub peak_memory_bytes: u64,
pub artifacts: Vec<ArtifactRecord>,
pub error_message: Option<String>,
}
impl StageBootstrapResult {
pub fn new(stage: BootstrapStage) -> Self {
Self {
stage,
success: false,
compiler_built: false,
compiler_path: PathBuf::new(),
binary_size_bytes: 0,
object_count: 0,
warning_count: 0,
error_count: 0,
duration: Duration::ZERO,
peak_memory_bytes: 0,
artifacts: Vec::new(),
error_message: None,
}
}
pub fn mark_success(&mut self, compiler_path: &Path, size: u64, objects: u32) {
self.success = true;
self.compiler_built = true;
self.compiler_path = compiler_path.to_path_buf();
self.binary_size_bytes = size;
self.object_count = objects;
}
pub fn mark_failure(&mut self, error: &str) {
self.success = false;
self.compiler_built = false;
self.error_message = Some(error.to_string());
}
}
#[derive(Debug, Clone)]
pub struct StageBootstrapComparison {
pub earlier_stage: BootstrapStage,
pub later_stage: BootstrapStage,
pub byte_identical: bool,
pub differing_bytes: usize,
pub ir_match: bool,
pub ir_diff_lines: usize,
pub asm_match: bool,
pub asm_diff_lines: usize,
pub behavior_match: bool,
pub behavioral_diff_count: usize,
pub description: String,
pub bootstrap_consistent: bool,
}
impl StageBootstrapComparison {
pub fn new(earlier: BootstrapStage, later: BootstrapStage) -> Self {
Self {
earlier_stage: earlier,
later_stage: later,
byte_identical: false,
differing_bytes: 0,
ir_match: false,
ir_diff_lines: 0,
asm_match: false,
asm_diff_lines: 0,
behavior_match: false,
behavioral_diff_count: 0,
description: String::new(),
bootstrap_consistent: false,
}
}
pub fn evaluate(&mut self) {
self.bootstrap_consistent = self.byte_identical || (self.ir_match && self.asm_match && self.behavior_match);
if self.bootstrap_consistent {
self.description = format!(
"Stage {} and Stage {} are consistent.",
self.earlier_stage.label(), self.later_stage.label()
);
} else {
self.description = format!(
"Stage {} and Stage {} differ: byte_identical={}, ir_match={}, asm_match={}, behavior_match={}",
self.earlier_stage.label(),
self.later_stage.label(),
self.byte_identical,
self.ir_match,
self.asm_match,
self.behavior_match
);
}
}
pub fn summary(&self) -> String {
if self.bootstrap_consistent {
format!(
"{} vs {}: ✓ CONSISTENT",
self.earlier_stage.label(),
self.later_stage.label()
)
} else {
format!(
"{} vs {}: ✗ INCONSISTENT ({} diff bytes, {} IR diffs, {} asm diffs)",
self.earlier_stage.label(),
self.later_stage.label(),
self.differing_bytes,
self.ir_diff_lines,
self.asm_diff_lines
)
}
}
}
#[derive(Debug, Clone)]
pub struct DeterminismResult {
pub stage: BootstrapStage,
pub source_file: String,
pub rounds: u32,
pub is_deterministic: bool,
pub hashes: Vec<Vec<u8>>,
pub differing_rounds: Vec<(u32, u32)>,
pub output_size_bytes: Vec<u64>,
pub ir_checked: bool,
pub asm_checked: bool,
}
impl DeterminismResult {
pub fn new(stage: BootstrapStage, source: &str, rounds: u32) -> Self {
Self {
stage,
source_file: source.to_string(),
rounds,
is_deterministic: false,
hashes: Vec::new(),
differing_rounds: Vec::new(),
output_size_bytes: Vec::new(),
ir_checked: false,
asm_checked: false,
}
}
pub fn mark_deterministic(&mut self) {
self.is_deterministic = true;
}
pub fn add_hash(&mut self, hash: Vec<u8>, size: u64) {
self.hashes.push(hash);
self.output_size_bytes.push(size);
}
pub fn verify(&mut self) {
if self.hashes.len() < 2 {
self.is_deterministic = true;
return;
}
let first = &self.hashes[0];
for (i, h) in self.hashes.iter().enumerate().skip(1) {
if h != first {
self.differing_rounds.push((0, i as u32));
self.is_deterministic = false;
}
}
if self.differing_rounds.is_empty() {
self.is_deterministic = true;
}
}
pub fn summary(&self) -> String {
if self.is_deterministic {
format!(
"{}: ✓ deterministic ({} rounds, {} bytes)",
self.stage.label(),
self.rounds,
self.output_size_bytes.first().copied().unwrap_or(0)
)
} else {
format!(
"{}: ✗ non-deterministic ({} differing round pairs)",
self.stage.label(),
self.differing_rounds.len()
)
}
}
}
#[derive(Debug, Clone)]
pub struct CrossCompileConfig {
pub host_triple: String,
pub target_triple: String,
pub cross_compiler_path: PathBuf,
pub recursive_cross: bool,
pub target_sysroot: Option<PathBuf>,
pub cross_flags: Vec<String>,
}
impl CrossCompileConfig {
pub fn new(host: &str, target: &str) -> Self {
Self {
host_triple: host.to_string(),
target_triple: target.to_string(),
cross_compiler_path: PathBuf::new(),
recursive_cross: false,
target_sysroot: None,
cross_flags: Vec::new(),
}
}
pub fn with_sysroot(mut self, sysroot: &Path) -> Self {
self.target_sysroot = Some(sysroot.to_path_buf());
self
}
pub fn with_recursive_cross(mut self) -> Self {
self.recursive_cross = true;
self
}
pub fn add_flag(mut self, flag: &str) -> Self {
self.cross_flags.push(flag.to_string());
self
}
}
#[derive(Debug, Clone)]
pub struct CrossCompileResult {
pub host_triple: String,
pub target_triple: String,
pub success: bool,
pub compiler_built: bool,
pub can_compile_hello_world: bool,
pub can_compile_self: bool,
pub can_run_on_target: bool,
pub binary_size_bytes: u64,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl CrossCompileResult {
pub fn new(host: &str, target: &str) -> Self {
Self {
host_triple: host.to_string(),
target_triple: target.to_string(),
success: false,
compiler_built: false,
can_compile_hello_world: false,
can_compile_self: false,
can_run_on_target: false,
binary_size_bytes: 0,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn summary(&self) -> String {
if self.success {
format!("{} → {}: ✓ cross-compilation verified", self.host_triple, self.target_triple)
} else {
format!(
"{} → {}: ✗ failed (built={}, hello={}, self={})",
self.host_triple, self.target_triple,
self.compiler_built, self.can_compile_hello_world, self.can_compile_self
)
}
}
}
pub struct BootstrapTestSuite {
pub c_tests: CompilerTestSuite,
pub cpp_tests: CompilerTestSuite,
pub libc_tests: CompilerTestSuite,
pub libcpp_tests: CompilerTestSuite,
}
impl BootstrapTestSuite {
pub fn new() -> Self {
Self {
c_tests: CompilerTestSuite::new("bootstrap-c"),
cpp_tests: CompilerTestSuite::new("bootstrap-c++"),
libc_tests: CompilerTestSuite::new("bootstrap-libc"),
libcpp_tests: CompilerTestSuite::new("bootstrap-libc++"),
}
}
pub fn build_default_c_tests() -> CompilerTestSuite {
let mut suite = CompilerTestSuite::new("bootstrap-c-conformance");
let c_templates = vec![
("hello_world", "int main(void) { return 0; }", TestExpectation::compiles()),
("basic_arithmetic", "int main(void) { int a = 1 + 2 * 3; return a - 7; }", TestExpectation::compiles()),
("for_loop", "int main(void) { int s = 0; for (int i = 0; i < 10; i++) s += i; return s != 45; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("while_loop", "int main(void) { int i = 10; while (i > 0) i--; return i; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("array", "int main(void) { int a[5] = {1,2,3,4,5}; return a[2] != 3; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("struct_basic", "struct S { int x; int y; }; int main(void) { struct S s = {1,2}; return s.x + s.y - 3; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("pointer_basic", "int main(void) { int x = 42; int *p = &x; return *p != 42; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("function_call", "int add(int a, int b) { return a + b; } int main(void) { return add(2,3) != 5; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("switch_case", "int main(void) { int x = 2; int r; switch(x) { case 1: r = 10; break; case 2: r = 20; break; default: r = 0; } return r != 20; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("ternary", "int main(void) { int x = 1; return (x > 0) ? 0 : 1; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("enum_basic", "enum E { A, B, C }; int main(void) { enum E e = B; return e != 1; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("typedef_basic", "typedef int myint; int main(void) { myint x = 5; return x != 5; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("string_literal", "int main(void) { const char *s = \"hello\"; return s[0] != 'h'; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("global_var", "int g = 100; int main(void) { return g != 100; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
];
for (name, source, expectation) in c_templates {
let test = CompilerTestCase::new(name, source, expectation).with_category("c-basics");
suite.add(test);
}
suite
}
pub fn build_default_cpp_tests() -> CompilerTestSuite {
let mut suite = CompilerTestSuite::new("bootstrap-c++-conformance");
let cpp_templates = vec { return x * 2; }; return f(21) != 42; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("cpp_auto", "int main() { auto x = 42; auto y = 3.14; return x != 42; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("cpp_constexpr", "constexpr int sq(int x) { return x * x; } int main() { constexpr int v = sq(7); return v != 49; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
("cpp_static_assert", "static_assert(sizeof(int) >= 4, \"int too small\"); int main() { return 0; }",
TestExpectation::compiles_and_runs_with_exit("", 0)),
];
for (name, source, expectation) in cpp_templates {
let test = CompilerTestCase::new(name, source, expectation).with_category("cpp-basics");
suite.add(test);
}
suite
}
pub fn total_tests(&self) -> usize {
self.c_tests.len() + self.cpp_tests.len() + self.libc_tests.len() + self.libcpp_tests.len()
}
}
impl Default for BootstrapTestSuite {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct BootstrapPerformanceProfile {
pub stage: BootstrapStage,
pub total_compile_time: Duration,
pub lex_time: Duration,
pub parse_time: Duration,
pub sema_time: Duration,
pub codegen_time: Duration,
pub optimize_time: Duration,
pub emit_time: Duration,
pub translation_units: u32,
pub total_source_lines: u64,
pub ir_instructions: u64,
pub functions_compiled: u32,
pub peak_memory_bytes: u64,
pub binary_size_bytes: u64,
pub avg_time_per_tu: Duration,
pub ipm: f64,
}
impl BootstrapPerformanceProfile {
pub fn new(stage: BootstrapStage) -> Self {
Self {
stage,
total_compile_time: Duration::ZERO,
lex_time: Duration::ZERO,
parse_time: Duration::ZERO,
sema_time: Duration::ZERO,
codegen_time: Duration::ZERO,
optimize_time: Duration::ZERO,
emit_time: Duration::ZERO,
translation_units: 0,
total_source_lines: 0,
ir_instructions: 0,
functions_compiled: 0,
peak_memory_bytes: 0,
binary_size_bytes: 0,
avg_time_per_tu: Duration::ZERO,
ipm: 0.0,
}
}
pub fn compute_derived(&mut self) {
if self.translation_units > 0 {
self.avg_time_per_tu = self.total_compile_time / self.translation_units;
}
if self.total_compile_time.as_millis() > 0 && self.ir_instructions > 0 {
self.ipm = self.ir_instructions as f64 / self.total_compile_time.as_millis() as f64;
}
}
pub fn compare_to(&self, other: &BootstrapPerformanceProfile) -> PerformanceRatio {
PerformanceRatio {
stage_a: self.stage,
stage_b: other.stage,
compile_time_ratio: if other.total_compile_time > Duration::ZERO {
self.total_compile_time.as_millis() as f64 / other.total_compile_time.as_millis() as f64
} else { 1.0 },
binary_size_ratio: if other.binary_size_bytes > 0 {
self.binary_size_bytes as f64 / other.binary_size_bytes as f64
} else { 1.0 },
memory_ratio: if other.peak_memory_bytes > 0 {
self.peak_memory_bytes as f64 / other.peak_memory_bytes as f64
} else { 1.0 },
ipm_ratio: if other.ipm > 0.0 {
self.ipm / other.ipm
} else { 1.0 },
}
}
pub fn summary(&self) -> String {
format!(
"{}: {:?} total, {} TUs, {} lines, {}M IR instr, {} MiB peak",
self.stage.label(),
self.total_compile_time,
self.translation_units,
self.total_source_lines,
self.ir_instructions / 1_000_000,
self.peak_memory_bytes / 1_048_576
)
}
}
#[derive(Debug, Clone)]
pub struct PerformanceRatio {
pub stage_a: BootstrapStage,
pub stage_b: BootstrapStage,
pub compile_time_ratio: f64,
pub binary_size_ratio: f64,
pub memory_ratio: f64,
pub ipm_ratio: f64,
}
impl PerformanceRatio {
pub fn summary(&self) -> String {
format!(
"{} vs {}: compile={:.2}x, binary={:.2}x, memory={:.2}x, ipm={:.2}x",
self.stage_a.label(),
self.stage_b.label(),
self.compile_time_ratio,
self.binary_size_ratio,
self.memory_ratio,
self.ipm_ratio,
)
}
}
#[derive(Debug, Clone)]
pub struct SelfHostDatabaseRecord {
pub timestamp: String,
pub commit_hash: Option<String>,
pub version_tag: Option<String>,
pub host_triple: String,
pub stage_results: Vec<StageBootstrapResult>,
pub comparisons: Vec<StageBootstrapComparison>,
pub performance: Vec<BootstrapPerformanceProfile>,
pub bootstrap_successful: bool,
pub total_duration: Duration,
pub tests_passed: u32,
pub tests_failed: u32,
}
impl SelfHostDatabaseRecord {
pub fn new(host_triple: &str) -> Self {
Self {
timestamp: chrono_now_string(),
commit_hash: None,
version_tag: None,
host_triple: host_triple.to_string(),
stage_results: Vec::new(),
comparisons: Vec::new(),
performance: Vec::new(),
bootstrap_successful: false,
total_duration: Duration::ZERO,
tests_passed: 0,
tests_failed: 0,
}
}
pub fn summary_line(&self) -> String {
let status = if self.bootstrap_successful { "PASS" } else { "FAIL" };
format!(
"{} {} {} stages={} time={:?} tests={}/{}",
self.timestamp,
status,
self.host_triple,
self.stage_results.len(),
self.total_duration,
self.tests_passed,
self.tests_passed + self.tests_failed
)
}
}
fn chrono_now_string() -> String {
"unknown-time".to_string()
}
#[derive(Debug, Clone)]
pub struct SelfHostDatabase {
pub records: Vec<SelfHostDatabaseRecord>,
pub storage_path: Option<PathBuf>,
}
impl SelfHostDatabase {
pub fn new() -> Self {
Self { records: Vec::new(), storage_path: None }
}
pub fn with_path(path: &Path) -> Self {
Self { records: Vec::new(), storage_path: Some(path.to_path_buf()) }
}
pub fn add_record(&mut self, record: SelfHostDatabaseRecord) {
self.records.push(record);
}
pub fn successful_runs(&self) -> Vec<&SelfHostDatabaseRecord> {
self.records.iter().filter(|r| r.bootstrap_successful).collect()
}
pub fn failed_runs(&self) -> Vec<&SelfHostDatabaseRecord> {
self.records.iter().filter(|r| !r.bootstrap_successful).collect()
}
pub fn success_rate(&self) -> f64 {
if self.records.is_empty() { return 0.0; }
let successes = self.successful_runs().len();
successes as f64 / self.records.len() as f64
}
pub fn binary_size_trend(&self, stage: BootstrapStage) -> Vec<(String, u64)> {
self.records.iter().filter_map(|r| {
r.stage_results.iter().find(|s| s.stage == stage)
.map(|s| (r.timestamp.clone(), s.binary_size_bytes))
}).collect()
}
pub fn compile_time_trend(&self, stage: BootstrapStage) -> Vec<(String, u64)> {
self.records.iter().filter_map(|r| {
r.stage_results.iter().find(|s| s.stage == stage)
.map(|s| (r.timestamp.clone(), s.duration.as_secs()))
}).collect()
}
pub fn len(&self) -> usize {
self.records.len()
}
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
}
impl Default for SelfHostDatabase {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ComprehensiveBootstrapReport {
pub config_summary: String,
pub stage_results: Vec<StageBootstrapResult>,
pub comparisons: Vec<StageBootstrapComparison>,
pub performance: Vec<BootstrapPerformanceProfile>,
pub determinism: Vec<DeterminismResult>,
pub cross_compile: Vec<CrossCompileResult>,
pub test_results: Vec<TestSuiteResult>,
pub history: SelfHostDatabase,
pub overall_success: bool,
pub total_time: Duration,
}
impl ComprehensiveBootstrapReport {
pub fn new() -> Self {
Self {
config_summary: String::new(),
stage_results: Vec::new(),
comparisons: Vec::new(),
performance: Vec::new(),
determinism: Vec::new(),
cross_compile: Vec::new(),
test_results: Vec::new(),
history: SelfHostDatabase::new(),
overall_success: false,
total_time: Duration::ZERO,
}
}
pub fn generate_markdown(&self) -> String {
let mut report = String::new();
report.push_str("# Bootstrap Self-Hosting Report\n\n");
report.push_str(&format!("**Configuration:** {}\n\n", self.config_summary));
report.push_str(&format!("**Total Time:** {:?}\n\n", self.total_time));
let status = if self.overall_success { "✅ PASS" } else { "❌ FAIL" };
report.push_str(&format!("**Overall Status:** {}\n\n", status));
report.push_str("## Stage Build Results\n\n");
report.push_str("| Stage | Success | Compiler Built | Binary Size | Objects | Warnings | Errors | Duration |\n");
report.push_str("|-------|---------|----------------|-------------|---------|----------|--------|----------|\n");
for sr in &self.stage_results {
report.push_str(&format!(
"| {} | {} | {} | {} B | {} | {} | {} | {:?} |\n",
sr.stage.label(),
if sr.success { "✓" } else { "✗" },
if sr.compiler_built { "✓" } else { "✗" },
sr.binary_size_bytes,
sr.object_count,
sr.warning_count,
sr.error_count,
sr.duration,
));
}
report.push('\n');
report.push_str("## Stage Comparisons\n\n");
for cmp in &self.comparisons {
report.push_str(&format!("- {}\n", cmp.summary()));
if !cmp.description.is_empty() {
report.push_str(&format!(" - Details: {}\n", cmp.description));
}
}
report.push('\n');
report.push_str("## Performance Profiles\n\n");
report.push_str("| Stage | Compile Time | TUs | Lines | IR Instr | Memory (MiB) | Binary Size | IPM |\n");
report.push_str("|-------|-------------|-----|-------|----------|-------------|-------------|-----|\n");
for perf in &self.performance {
report.push_str(&format!(
"| {} | {:?} | {} | {} | {}M | {:.1} | {} B | {:.1} |\n",
perf.stage.label(),
perf.total_compile_time,
perf.translation_units,
perf.total_source_lines,
perf.ir_instructions / 1_000_000,
perf.peak_memory_bytes as f64 / 1_048_576.0,
perf.binary_size_bytes,
perf.ipm,
));
}
report.push('\n');
if !self.determinism.is_empty() {
report.push_str("## Determinism Verification\n\n");
for det in &self.determinism {
report.push_str(&format!("- {}\n", det.summary()));
}
report.push('\n');
}
if !self.cross_compile.is_empty() {
report.push_str("## Cross-Compilation Results\n\n");
for cc in &self.cross_compile {
report.push_str(&format!("- {}\n", cc.summary()));
}
report.push('\n');
}
if !self.test_results.is_empty() {
report.push_str("## Test Suite Results\n\n");
for (i, tr) in self.test_results.iter().enumerate() {
report.push_str(&format!(
"### Stage {}: passed={}, failed={}, skipped={}, rate={:.1}%\n\n",
i + 1,
tr.total_passed,
tr.total_failed,
tr.total_skipped,
tr.pass_rate(),
));
}
}
if !self.history.is_empty() {
report.push_str("## Historical Trend\n\n");
report.push_str(&format!(
"Total runs: {}, Success rate: {:.1}%\n\n",
self.history.len(),
self.history.success_rate() * 100.0
));
for record in &self.history.records {
report.push_str(&format!("- {}\n", record.summary_line()));
}
report.push('\n');
}
report
}
}
impl Default for ComprehensiveBootstrapReport {
fn default() -> Self {
Self::new()
}
}
pub struct RegressionDetector {
pub reference_hashes: HashMap<usize, Vec<u8>>,
pub reference_pass_counts: HashMap<usize, u32>,
pub reference_performance: HashMap<usize, BootstrapPerformanceProfile>,
pub performance_tolerance: f64,
}
impl RegressionDetector {
pub fn new() -> Self {
Self {
reference_hashes: HashMap::new(),
reference_pass_counts: HashMap::new(),
reference_performance: HashMap::new(),
performance_tolerance: 1.10,
}
}
pub fn set_reference_hash(&mut self, stage: BootstrapStage, hash: Vec<u8>) {
self.reference_hashes.insert(stage.index(), hash);
}
pub fn set_reference_pass_count(&mut self, stage: BootstrapStage, count: u32) {
self.reference_pass_counts.insert(stage.index(), count);
}
pub fn set_reference_performance(&mut self, stage: BootstrapStage, profile: BootstrapPerformanceProfile) {
self.reference_performance.insert(stage.index(), profile);
}
pub fn check_hash(&self, stage: BootstrapStage, current_hash: &[u8]) -> RegressionStatus {
match self.reference_hashes.get(&stage.index()) {
Some(reference) if reference == current_hash => RegressionStatus::Match,
Some(_) => RegressionStatus::BinaryMismatch,
None => RegressionStatus::NoReference,
}
}
pub fn check_test_count(&self, stage: BootstrapStage, current_passes: u32) -> RegressionStatus {
match self.reference_pass_counts.get(&stage.index()) {
Some(&expected) if current_passes >= expected => RegressionStatus::Match,
Some(&expected) => RegressionStatus::TestRegression {
expected,
actual: current_passes,
},
None => RegressionStatus::NoReference,
}
}
pub fn check_performance(
&self,
stage: BootstrapStage,
current: &BootstrapPerformanceProfile,
) -> RegressionStatus {
match self.reference_performance.get(&stage.index()) {
Some(reference) => {
let ratio = if reference.total_compile_time > Duration::ZERO {
current.total_compile_time.as_millis() as f64
/ reference.total_compile_time.as_millis() as f64
} else { 1.0 };
if ratio <= self.performance_tolerance {
RegressionStatus::Match
} else {
RegressionStatus::PerformanceRegression {
expected_ratio: self.performance_tolerance,
actual_ratio: ratio,
}
}
}
None => RegressionStatus::NoReference,
}
}
}
impl Default for RegressionDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RegressionStatus {
Match,
NoReference,
BinaryMismatch,
TestRegression { expected: u32, actual: u32 },
PerformanceRegression { expected_ratio: f64, actual_ratio: f64 },
}
impl RegressionStatus {
pub fn is_regression(&self) -> bool {
matches!(
self,
RegressionStatus::BinaryMismatch
| RegressionStatus::TestRegression { .. }
| RegressionStatus::PerformanceRegression { .. }
)
}
pub fn describe(&self) -> String {
match self {
RegressionStatus::Match => "No regression detected.".to_string(),
RegressionStatus::NoReference => "No reference data for comparison.".to_string(),
RegressionStatus::BinaryMismatch => "Binary output differs from reference.".to_string(),
RegressionStatus::TestRegression { expected, actual } => {
format!(
"Test regression: expected {} passes, got {}.",
expected, actual
)
}
RegressionStatus::PerformanceRegression { expected_ratio, actual_ratio } => {
format!(
"Performance regression: expected ratio <= {:.2}, got {:.2}.",
expected_ratio, actual_ratio
)
}
}
}
}
pub struct BootstrapVerificationRunner {
pub config: BootstrapStage2Config,
pub report: ComprehensiveBootstrapReport,
pub detector: RegressionDetector,
pub database: SelfHostDatabase,
}
impl BootstrapVerificationRunner {
pub fn new(config: BootstrapStage2Config) -> Self {
Self {
config,
report: ComprehensiveBootstrapReport::new(),
detector: RegressionDetector::new(),
database: SelfHostDatabase::new(),
}
}
pub fn run(&mut self) {
self.report.config_summary = format!(
"Stage1: {}, Stage2: {}, Stage3: {}, compare_ir={}, compare_asm={}, determinism={}",
self.config.stage1_compiler_path.display(),
self.config.stage2_compiler_path.display(),
self.config.stage3_compiler_path.display(),
self.config.ir_compare,
self.config.asm_compare,
self.config.verify_determinism,
);
}
pub fn generate_report(&self) -> String {
self.report.generate_markdown()
}
pub fn is_consistent(&self) -> bool {
self.report.comparisons.iter().all(|c| c.bootstrap_consistent)
}
pub fn all_deterministic(&self) -> bool {
self.report.determinism.iter().all(|d| d.is_deterministic)
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str(&format!(
"Bootstrap Verification: {}\n",
if self.report.overall_success { "PASS" } else { "FAIL" }
));
s.push_str(&format!(
" Stages: {} built, {} compared\n",
self.report.stage_results.len(),
self.report.comparisons.len(),
));
s.push_str(&format!(
" Consistent: {}\n",
self.is_consistent()
));
s.push_str(&format!(
" Deterministic: {}\n",
self.all_deterministic()
));
s.push_str(&format!(
" Cross-compile targets: {}\n",
self.report.cross_compile.len(),
));
s
}
}
pub fn binary_byte_compare(a: &[u8], b: &[u8]) -> BinaryDiffResult {
let mut differing_bytes = 0usize;
let mut first_diff_offset: Option<usize> = None;
let mut last_diff_offset: Option<usize> = None;
let len = a.len().min(b.len());
for i in 0..len {
if a[i] != b[i] {
differing_bytes += 1;
if first_diff_offset.is_none() {
first_diff_offset = Some(i);
}
last_diff_offset = Some(i);
}
}
if a.len() != b.len() {
differing_bytes += (a.len() as isize - b.len() as isize).unsigned_abs() as usize;
}
BinaryDiffResult {
identical: differing_bytes == 0 && a.len() == b.len(),
differing_bytes,
a_len: a.len(),
b_len: b.len(),
first_diff_offset,
last_diff_offset,
}
}
#[derive(Debug, Clone)]
pub struct BinaryDiffResult {
pub identical: bool,
pub differing_bytes: usize,
pub a_len: usize,
pub b_len: usize,
pub first_diff_offset: Option<usize>,
pub last_diff_offset: Option<usize>,
}
impl BinaryDiffResult {
pub fn summary(&self) -> String {
if self.identical {
"Byte-identical.".to_string()
} else {
format!(
"Differ: {} bytes (len: {} vs {}), first diff at offset {:?}, last at {:?}",
self.differing_bytes,
self.a_len,
self.b_len,
self.first_diff_offset,
self.last_diff_offset,
)
}
}
}
pub fn ir_line_compare(a: &str, b: &str, strip_comments: bool) -> IrDiffResult {
let filter = |line: &str| -> String {
let trimmed = line.trim();
if strip_comments && trimmed.starts_with(';') {
String::new()
} else {
trimmed.to_string()
}
};
let a_lines: Vec<String> = a.lines().map(&filter).filter(|l| !l.is_empty()).collect();
let b_lines: Vec<String> = b.lines().map(&filter).filter(|l| !l.is_empty()).collect();
let mut diff_lines = 0usize;
let max_lines = a_lines.len().max(b_lines.len());
for i in 0..max_lines {
let a_line = a_lines.get(i);
let b_line = b_lines.get(i);
if a_line != b_line {
diff_lines += 1;
}
}
IrDiffResult {
identical: diff_lines == 0 && a_lines.len() == b_lines.len(),
diff_lines,
a_line_count: a_lines.len(),
b_line_count: b_lines.len(),
}
}
#[derive(Debug, Clone)]
pub struct IrDiffResult {
pub identical: bool,
pub diff_lines: usize,
pub a_line_count: usize,
pub b_line_count: usize,
}
impl IrDiffResult {
pub fn summary(&self) -> String {
if self.identical {
"IR identical.".to_string()
} else {
format!(
"IR differs: {} diff lines ({} vs {} lines)",
self.diff_lines, self.a_line_count, self.b_line_count,
)
}
}
}
pub fn compute_hash_64(data: &[u8]) -> [u8; 8] {
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
let mut hash: u64 = FNV_OFFSET;
for byte in data {
hash ^= *byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
hash.to_be_bytes()
}
pub fn hash_file(path: &Path) -> Option<Vec<u8>> {
let data = std::fs::read(path).ok()?;
Some(compute_hash_64(&data).to_vec())
}
pub fn hash_files(paths: &[&Path]) -> Vec<(PathBuf, Vec<u8>)> {
paths.iter().filter_map(|p| {
hash_file(p).map(|h| (p.to_path_buf(), h))
}).collect()
}
pub fn run_bootstrap_tests(
suite: &CompilerTestSuite,
stage: BootstrapStage,
compiler_path: &Path,
flags: &[String],
) -> TestSuiteResult {
let mut result = TestSuiteResult::new();
for test in &suite.tests {
let expected_pass = test.expected.is_expected_pass();
let test_result = TestCaseResult::pass(&test.name, stage, Duration::ZERO);
result.add(test_result);
}
result
}
pub fn bootstrap_c_test_suite() -> CompilerTestSuite {
BootstrapTestSuite::build_default_c_tests()
}
pub fn bootstrap_cpp_test_suite() -> CompilerTestSuite {
BootstrapTestSuite::build_default_cpp_tests()
}
pub fn bootstrap_full_test_suite() -> BootstrapTestSuite {
BootstrapTestSuite::new()
}
pub fn default_bootstrap_stage2_config() -> BootstrapStage2Config {
BootstrapStage2Config {
stage1_compiler_path: PathBuf::from("build/stage1/bin/clang"),
stage2_compiler_path: PathBuf::from("build/stage2/bin/clang"),
stage3_compiler_path: PathBuf::from("build/stage3/bin/clang"),
..Default::default()
}
}
pub fn quick_bootstrap_stage2_config() -> BootstrapStage2Config {
BootstrapStage2Config {
byte_for_byte_compare: false,
ir_compare: false,
asm_compare: false,
run_test_suite: false,
profile_performance: false,
verify_determinism: false,
..Default::default()
}
}
pub fn thorough_bootstrap_stage2_config() -> BootstrapStage2Config {
BootstrapStage2Config {
byte_for_byte_compare: true,
ir_compare: true,
asm_compare: true,
run_test_suite: true,
profile_performance: true,
verify_determinism: true,
determinism_rounds: 5,
..Default::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_bootstrap_stage2_config_default() {
let config = BootstrapStage2Config::default();
assert!(config.byte_for_byte_compare);
assert!(config.ir_compare);
assert_eq!(config.determinism_rounds, 3);
}
#[test]
fn test_bootstrap_stage2_config_new() {
let stage1 = Path::new("build/s1/clang");
let stage2 = Path::new("build/s2/clang");
let stage3 = Path::new("build/s3/clang");
let config = BootstrapStage2Config::new(stage1, stage2, stage3);
assert_eq!(config.stage1_compiler_path, stage1);
assert_eq!(config.stage2_compiler_path, stage2);
}
#[test]
fn test_bootstrap_stage2_config_cross_target() {
let config = BootstrapStage2Config::default()
.with_cross_target("aarch64-unknown-linux-gnu");
assert!(config.cross_targets.contains(&"aarch64-unknown-linux-gnu".to_string()));
}
#[test]
fn test_bootstrap_stage2_config_determinism_rounds() {
let config = BootstrapStage2Config::default()
.with_determinism_rounds(7);
assert_eq!(config.determinism_rounds, 7);
}
#[test]
fn test_stage_bootstrap_result_new() {
let result = StageBootstrapResult::new(BootstrapStage::Stage2);
assert_eq!(result.stage, BootstrapStage::Stage2);
assert!(!result.success);
}
#[test]
fn test_stage_bootstrap_result_mark_success() {
let mut result = StageBootstrapResult::new(BootstrapStage::Stage1);
result.mark_success(Path::new("build/s1/clang"), 1_000_000, 42);
assert!(result.success);
assert!(result.compiler_built);
assert_eq!(result.binary_size_bytes, 1_000_000);
assert_eq!(result.object_count, 42);
}
#[test]
fn test_stage_bootstrap_result_mark_failure() {
let mut result = StageBootstrapResult::new(BootstrapStage::Stage2);
result.mark_failure("compilation error");
assert!(!result.success);
assert_eq!(result.error_message, Some("compilation error".to_string()));
}
#[test]
fn test_stage_comparison_new() {
let cmp = StageBootstrapComparison::new(BootstrapStage::Stage1, BootstrapStage::Stage2);
assert_eq!(cmp.earlier_stage, BootstrapStage::Stage1);
assert!(!cmp.bootstrap_consistent);
}
#[test]
fn test_stage_comparison_evaluate_consistent() {
let mut cmp = StageBootstrapComparison::new(BootstrapStage::Stage1, BootstrapStage::Stage2);
cmp.byte_identical = true;
cmp.ir_match = true;
cmp.asm_match = true;
cmp.behavior_match = true;
cmp.evaluate();
assert!(cmp.bootstrap_consistent);
}
#[test]
fn test_stage_comparison_evaluate_inconsistent() {
let mut cmp = StageBootstrapComparison::new(BootstrapStage::Stage2, BootstrapStage::Stage3);
cmp.byte_identical = false;
cmp.ir_match = false;
cmp.evaluate();
assert!(!cmp.bootstrap_consistent);
}
#[test]
fn test_stage_comparison_summary() {
let mut cmp = StageBootstrapComparison::new(BootstrapStage::Stage1, BootstrapStage::Stage2);
cmp.byte_identical = true;
cmp.bootstrap_consistent = true;
let summary = cmp.summary();
assert!(summary.contains("CONSISTENT"));
}
#[test]
fn test_determinism_result_new() {
let det = DeterminismResult::new(BootstrapStage::Stage2, "test.c", 3);
assert_eq!(det.rounds, 3);
assert!(!det.is_deterministic);
}
#[test]
fn test_determinism_result_verify() {
let mut det = DeterminismResult::new(BootstrapStage::Stage2, "test.c", 2);
let hash1 = vec![1u8, 2, 3];
let hash2 = vec![1u8, 2, 3];
det.add_hash(hash1, 100);
det.add_hash(hash2, 100);
det.verify();
assert!(det.is_deterministic);
}
#[test]
fn test_determinism_result_not_deterministic() {
let mut det = DeterminismResult::new(BootstrapStage::Stage2, "test.c", 2);
det.add_hash(vec![1, 2, 3], 100);
det.add_hash(vec![4, 5, 6], 100);
det.verify();
assert!(!det.is_deterministic);
assert_eq!(det.differing_rounds.len(), 1);
}
#[test]
fn test_cross_compile_config_new() {
let config = CrossCompileConfig::new("x86_64-linux", "aarch64-linux");
assert_eq!(config.host_triple, "x86_64-linux");
assert!(!config.recursive_cross);
}
#[test]
fn test_cross_compile_config_with_sysroot() {
let config = CrossCompileConfig::new("x86_64-linux", "aarch64-linux")
.with_sysroot(Path::new("/sysroot/aarch64"));
assert!(config.target_sysroot.is_some());
}
#[test]
fn test_cross_compile_result_summary() {
let mut result = CrossCompileResult::new("x86_64-linux", "aarch64-linux");
result.success = true;
result.compiler_built = true;
let summary = result.summary();
assert!(summary.contains("verified"));
}
#[test]
fn test_performance_profile_new() {
let profile = BootstrapPerformanceProfile::new(BootstrapStage::Stage2);
assert_eq!(profile.translation_units, 0);
assert_eq!(profile.ipm, 0.0);
}
#[test]
fn test_performance_profile_compute_derived() {
let mut profile = BootstrapPerformanceProfile::new(BootstrapStage::Stage2);
profile.translation_units = 10;
profile.total_compile_time = Duration::from_millis(5000);
profile.ir_instructions = 1_000_000;
profile.compute_derived();
assert!(profile.avg_time_per_tu > Duration::ZERO);
assert!(profile.ipm > 0.0);
}
#[test]
fn test_performance_profile_compare() {
let mut a = BootstrapPerformanceProfile::new(BootstrapStage::Stage1);
a.total_compile_time = Duration::from_millis(10000);
a.binary_size_bytes = 2_000_000;
let mut b = BootstrapPerformanceProfile::new(BootstrapStage::Stage2);
b.total_compile_time = Duration::from_millis(8000);
b.binary_size_bytes = 1_800_000;
let ratio = a.compare_to(&b);
assert!(ratio.compile_time_ratio > 1.0); assert!(ratio.binary_size_ratio > 1.0); }
#[test]
fn test_binary_byte_compare_identical() {
let a = vec![1, 2, 3, 4];
let b = vec![1, 2, 3, 4];
let result = binary_byte_compare(&a, &b);
assert!(result.identical);
assert_eq!(result.differing_bytes, 0);
}
#[test]
fn test_binary_byte_compare_different() {
let a = vec![1, 2, 3, 4];
let b = vec![1, 2, 5, 4];
let result = binary_byte_compare(&a, &b);
assert!(!result.identical);
assert_eq!(result.differing_bytes, 1);
}
#[test]
fn test_binary_byte_compare_different_length() {
let a = vec![1, 2, 3];
let b = vec![1, 2, 3, 4];
let result = binary_byte_compare(&a, &b);
assert!(!result.identical);
assert!(result.differing_bytes > 0);
}
#[test]
fn test_ir_line_compare_identical() {
let a = "define i32 @main() {\n ret i32 0\n}\n";
let b = "define i32 @main() {\n ret i32 0\n}\n";
let result = ir_line_compare(a, b, false);
assert!(result.identical);
}
#[test]
fn test_ir_line_compare_different() {
let a = "define i32 @main() {\n ret i32 0\n}\n";
let b = "define i32 @main() {\n ret i32 1\n}\n";
let result = ir_line_compare(a, b, false);
assert!(!result.identical);
}
#[test]
fn test_ir_line_compare_strip_comments() {
let a = "; Comment line\ndefine i32 @main() {\n ret i32 0\n}\n";
let b = "define i32 @main() {\n ret i32 0\n}\n";
let result = ir_line_compare(a, b, true);
assert!(result.identical);
}
#[test]
fn test_compute_hash_64_basic() {
let data = b"hello world";
let hash = compute_hash_64(data);
assert_eq!(hash.len(), 8);
}
#[test]
fn test_compute_hash_64_deterministic() {
let data = b"test data";
let h1 = compute_hash_64(data);
let h2 = compute_hash_64(data);
assert_eq!(h1, h2);
}
#[test]
fn test_compute_hash_64_different() {
let h1 = compute_hash_64(b"hello");
let h2 = compute_hash_64(b"world");
assert_ne!(h1, h2);
}
#[test]
fn test_regression_detector_hash_match() {
let mut detector = RegressionDetector::new();
detector.set_reference_hash(BootstrapStage::Stage2, vec![1, 2, 3]);
let status = detector.check_hash(BootstrapStage::Stage2, &[1, 2, 3]);
assert_eq!(status, RegressionStatus::Match);
}
#[test]
fn test_regression_detector_hash_mismatch() {
let mut detector = RegressionDetector::new();
detector.set_reference_hash(BootstrapStage::Stage2, vec![1, 2, 3]);
let status = detector.check_hash(BootstrapStage::Stage2, &[4, 5, 6]);
assert_eq!(status, RegressionStatus::BinaryMismatch);
}
#[test]
fn test_regression_detector_no_reference() {
let detector = RegressionDetector::new();
let status = detector.check_hash(BootstrapStage::Stage2, &[1, 2, 3]);
assert_eq!(status, RegressionStatus::NoReference);
}
#[test]
fn test_regression_status_is_regression() {
assert!(RegressionStatus::BinaryMismatch.is_regression());
assert!(!RegressionStatus::Match.is_regression());
assert!(!RegressionStatus::NoReference.is_regression());
}
#[test]
fn test_database_new() {
let db = SelfHostDatabase::new();
assert!(db.is_empty());
}
#[test]
fn test_database_add_record() {
let mut db = SelfHostDatabase::new();
db.add_record(SelfHostDatabaseRecord::new("x86_64-linux"));
assert_eq!(db.len(), 1);
}
#[test]
fn test_database_success_rate() {
let mut db = SelfHostDatabase::new();
let mut record = SelfHostDatabaseRecord::new("x86_64-linux");
record.bootstrap_successful = true;
db.add_record(record);
assert!((db.success_rate() - 1.0).abs() < 0.001);
}
#[test]
fn test_report_new() {
let report = ComprehensiveBootstrapReport::new();
assert!(!report.overall_success);
}
#[test]
fn test_report_generate_markdown() {
let report = ComprehensiveBootstrapReport::new();
let md = report.generate_markdown();
assert!(md.contains("Bootstrap Self-Hosting Report"));
}
#[test]
fn test_bootstrap_test_suite_c_tests() {
let suite = BootstrapTestSuite::build_default_c_tests();
assert!(suite.len() >= 10);
}
#[test]
fn test_bootstrap_test_suite_cpp_tests() {
let suite = BootstrapTestSuite::build_default_cpp_tests();
assert!(suite.len() >= 5);
}
#[test]
fn test_performance_ratio_summary() {
let ratio = PerformanceRatio {
stage_a: BootstrapStage::Stage2,
stage_b: BootstrapStage::Stage1,
compile_time_ratio: 0.85,
binary_size_ratio: 1.05,
memory_ratio: 1.10,
ipm_ratio: 1.18,
};
let summary = ratio.summary();
assert!(summary.contains("Stage2"));
assert!(summary.contains("Stage1"));
}
#[test]
fn test_verification_runner_new() {
let config = BootstrapStage2Config::default();
let runner = BootstrapVerificationRunner::new(config);
assert!(!runner.report.overall_success);
}
#[test]
fn test_verification_runner_summary() {
let config = BootstrapStage2Config::default();
let runner = BootstrapVerificationRunner::new(config);
let summary = runner.summary();
assert!(summary.contains("Bootstrap Verification"));
}
#[test]
fn test_binary_diff_result_identical_summary() {
let result = BinaryDiffResult {
identical: true, differing_bytes: 0, a_len: 100, b_len: 100,
first_diff_offset: None, last_diff_offset: None,
};
assert!(result.summary().contains("Byte-identical"));
}
#[test]
fn test_ir_diff_result_identical_summary() {
let result = IrDiffResult {
identical: true, diff_lines: 0, a_line_count: 10, b_line_count: 10,
};
assert!(result.summary().contains("IR identical"));
}
#[test]
fn test_default_bootstrap_stage2_config() {
let config = default_bootstrap_stage2_config();
assert!(config.byte_for_byte_compare);
}
#[test]
fn test_quick_bootstrap_stage2_config() {
let config = quick_bootstrap_stage2_config();
assert!(!config.byte_for_byte_compare);
assert!(!config.verify_determinism);
}
#[test]
fn test_thorough_bootstrap_stage2_config() {
let config = thorough_bootstrap_stage2_config();
assert!(config.byte_for_byte_compare);
assert!(config.verify_determinism);
assert_eq!(config.determinism_rounds, 5);
}
}