use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::fs;
use std::io::{self};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use crate::clang::clang_golden_bridge::*;
use crate::clang::*;
use crate::x86::*;
pub const DEFAULT_STAGE_TIMEOUT_SECS: u64 = 7200;
pub const DEFAULT_TEST_TIMEOUT_SECS: u64 = 300;
pub const MAX_STAGE_RETRIES: u32 = 3;
pub const MIN_CORRECTNESS_SCORE: f64 = 0.95;
pub const PERF_REGRESSION_THRESHOLD_PCT: f64 = 5.0;
pub const MAX_SIZE_INCREASE_PCT: f64 = 20.0;
pub const CACHE_VERSION: u32 = 1;
pub const DEFAULT_CACHE_DIR: &str = ".bootstrap_cache";
pub const HASH_CHUNK_SIZE: usize = 4096;
pub const X86_64_LINUX_TARGET: &str = "x86_64-unknown-linux-gnu";
pub const X86_32_LINUX_TARGET: &str = "i386-unknown-linux-gnu";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum X86BootstrapStage {
Stage0,
Stage1,
Stage2,
Stage3,
Comparison,
}
impl Default for X86BootstrapStage {
fn default() -> Self {
Self::Stage0
}
}
impl X86BootstrapStage {
pub fn index(&self) -> usize {
match self {
Self::Stage0 => 0,
Self::Stage1 => 1,
Self::Stage2 => 2,
Self::Stage3 => 3,
Self::Comparison => 4,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Stage0 => "Stage 0 (Host Compiler)",
Self::Stage1 => "Stage 1 (Bootstrap)",
Self::Stage2 => "Stage 2 (Self-Host Verify)",
Self::Stage3 => "Stage 3 (Self-Host Validate)",
Self::Comparison => "Stage Comparison (Stage2 == Stage3)",
}
}
pub fn short_label(&self) -> &'static str {
match self {
Self::Stage0 => "S0",
Self::Stage1 => "S1",
Self::Stage2 => "S2",
Self::Stage3 => "S3",
Self::Comparison => "CMP",
}
}
pub fn produces_binary(&self) -> bool {
matches!(
self,
Self::Stage0 | Self::Stage1 | Self::Stage2 | Self::Stage3
)
}
pub fn input_compiler_stage(&self) -> Option<X86BootstrapStage> {
match self {
Self::Stage0 => None,
Self::Stage1 => Some(Self::Stage0),
Self::Stage2 => Some(Self::Stage1),
Self::Stage3 => Some(Self::Stage2),
Self::Comparison => None,
}
}
pub fn next(&self) -> Option<X86BootstrapStage> {
match self {
Self::Stage0 => Some(Self::Stage1),
Self::Stage1 => Some(Self::Stage2),
Self::Stage2 => Some(Self::Stage3),
Self::Stage3 => Some(Self::Comparison),
Self::Comparison => None,
}
}
pub fn prev(&self) -> Option<X86BootstrapStage> {
match self {
Self::Stage0 => None,
Self::Stage1 => Some(Self::Stage0),
Self::Stage2 => Some(Self::Stage1),
Self::Stage3 => Some(Self::Stage2),
Self::Comparison => Some(Self::Stage3),
}
}
pub fn all_build_stages() -> Vec<X86BootstrapStage> {
vec![
X86BootstrapStage::Stage0,
X86BootstrapStage::Stage1,
X86BootstrapStage::Stage2,
X86BootstrapStage::Stage3,
]
}
pub fn all_stages() -> Vec<X86BootstrapStage> {
vec![
X86BootstrapStage::Stage0,
X86BootstrapStage::Stage1,
X86BootstrapStage::Stage2,
X86BootstrapStage::Stage3,
X86BootstrapStage::Comparison,
]
}
pub fn dir_name(&self) -> &'static str {
match self {
Self::Stage0 => "stage0",
Self::Stage1 => "stage1",
Self::Stage2 => "stage2",
Self::Stage3 => "stage3",
Self::Comparison => "comparison",
}
}
}
impl fmt::Display for X86BootstrapStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone)]
pub struct X86StageConfig {
pub stage: X86BootstrapStage,
pub compiler_path: PathBuf,
pub build_dir: PathBuf,
pub output_dir: PathBuf,
pub output_binary: PathBuf,
pub target_triple: String,
pub target_cpu: String,
pub opt_level: String,
pub debug_info: bool,
pub lto: bool,
pub thin_lto: bool,
pub pic: bool,
pub extra_flags: Vec<String>,
pub env_vars: BTreeMap<String, String>,
pub run_test_suite: bool,
pub test_suite_dir: Option<PathBuf>,
pub incremental: bool,
pub parallel_jobs: usize,
pub timeout_secs: u64,
pub track_performance: bool,
pub enable_cache: bool,
pub werror: bool,
pub code_model: String,
pub reloc_model: String,
pub sanitizers: Vec<String>,
pub source_files: Option<Vec<PathBuf>>,
}
impl X86StageConfig {
pub fn new(stage: X86BootstrapStage, build_root: &Path) -> Self {
let dir_name = stage.dir_name();
let build_dir = build_root.join(dir_name);
let output_dir = build_dir.join("bin");
let output_binary = output_dir.join("llvm-native-clang");
X86StageConfig {
stage,
compiler_path: PathBuf::from("rustc"),
build_dir,
output_dir,
output_binary,
target_triple: X86_64_LINUX_TARGET.to_string(),
target_cpu: "x86-64".to_string(),
opt_level: "2".to_string(),
debug_info: false,
lto: false,
thin_lto: false,
pic: true,
extra_flags: Vec::new(),
env_vars: BTreeMap::new(),
run_test_suite: false,
test_suite_dir: None,
incremental: false,
parallel_jobs: num_cpus(),
timeout_secs: DEFAULT_STAGE_TIMEOUT_SECS,
track_performance: true,
enable_cache: true,
werror: true,
code_model: "small".to_string(),
reloc_model: "pic".to_string(),
sanitizers: Vec::new(),
source_files: None,
}
}
pub fn release_profile(mut self) -> Self {
self.opt_level = "3".to_string();
self.lto = true;
self.code_model = "small".to_string();
self.debug_info = false;
self
}
pub fn debug_profile(mut self) -> Self {
self.opt_level = "0".to_string();
self.debug_info = true;
self.lto = false;
self.werror = true;
self
}
pub fn size_profile(mut self) -> Self {
self.opt_level = "s".to_string();
self.lto = true;
self.code_model = "small".to_string();
self
}
pub fn stage0_default(mut self) -> Self {
self.compiler_path = PathBuf::from("rustc");
self.opt_level = "0".to_string();
self.lto = false;
self.werror = false;
self.debug_info = true;
self.incremental = true;
self
}
pub fn stage1_default(mut self) -> Self {
self.opt_level = "2".to_string();
self.lto = false;
self.werror = true;
self.debug_info = false;
self.run_test_suite = true;
self
}
pub fn stage2_stage3_default(mut self) -> Self {
self.opt_level = "3".to_string();
self.lto = true;
self.werror = true;
self.debug_info = false;
self.run_test_suite = true;
self.track_performance = true;
self
}
pub fn with_compiler(mut self, path: PathBuf) -> Self {
self.compiler_path = path;
self
}
pub fn with_opt_level(mut self, level: &str) -> Self {
self.opt_level = level.to_string();
self
}
pub fn with_target(mut self, target: &str) -> Self {
self.target_triple = target.to_string();
self
}
pub fn with_extra_flags(mut self, flags: Vec<String>) -> Self {
self.extra_flags = flags;
self
}
pub fn with_env(mut self, key: &str, value: &str) -> Self {
self.env_vars.insert(key.to_string(), value.to_string());
self
}
pub fn with_test_suite(mut self, test_dir: Option<PathBuf>) -> Self {
self.run_test_suite = test_dir.is_some();
self.test_suite_dir = test_dir;
self
}
pub fn to_args(&self) -> Vec<String> {
let mut args = Vec::new();
args.push(format!("-O{}", self.opt_level));
args.push("--target".to_string());
args.push(self.target_triple.clone());
if !self.target_cpu.is_empty() && self.target_cpu != "generic" {
args.push(format!("-Ctarget-cpu={}", self.target_cpu));
}
if self.code_model != "small" {
args.push(format!("-Ccode-model={}", self.code_model));
}
if self.reloc_model != "pic" {
args.push(format!("-Crelocation-model={}", self.reloc_model));
}
if self.debug_info {
args.push("-g".to_string());
}
if self.lto {
if self.thin_lto {
args.push("-Clto=thin".to_string());
} else {
args.push("-Clto=fat".to_string());
}
}
if self.pic {
args.push("-Crelocation-model=pic".to_string());
}
if self.werror {
args.push("-Dwarnings".to_string());
}
if self.incremental {
args.push("-Cincremental".to_string());
}
if self.parallel_jobs > 1 {
args.push(format!("-j{}", self.parallel_jobs));
}
for san in &self.sanitizers {
args.push(format!("-Zsanitizer={}", san));
}
args.extend(self.extra_flags.clone());
args
}
pub fn summary(&self) -> String {
let mut s = format!(
"{}: compiler={}, opt={}, target={}",
self.stage.short_label(),
self.compiler_path.display(),
self.opt_level,
self.target_triple,
);
if self.lto {
s.push_str(", lto");
}
if self.debug_info {
s.push_str(", debug");
}
if self.werror {
s.push_str(", werror");
}
if !self.sanitizers.is_empty() {
s.push_str(&format!(", sanitizers={}", self.sanitizers.join(",")));
}
s
}
pub fn config_hash(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
self.stage.index().hash(&mut hasher);
self.compiler_path.to_string_lossy().hash(&mut hasher);
self.target_triple.hash(&mut hasher);
self.opt_level.hash(&mut hasher);
self.lto.hash(&mut hasher);
self.werror.hash(&mut hasher);
for flag in &self.extra_flags {
flag.hash(&mut hasher);
}
for (k, v) in &self.env_vars {
k.hash(&mut hasher);
v.hash(&mut hasher);
}
hasher.finish()
}
}
impl Default for X86StageConfig {
fn default() -> Self {
X86StageConfig::new(X86BootstrapStage::Stage0, Path::new("target/bootstrap"))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TestCategory {
Smoke,
Correctness,
Performance,
Regression,
Stress,
Integration,
SelfHost,
Oracle,
Sanitizer,
CodeGen,
CrossCompile,
ErrorRecovery,
}
impl TestCategory {
pub fn as_str(&self) -> &'static str {
match self {
Self::Smoke => "smoke",
Self::Correctness => "correctness",
Self::Performance => "performance",
Self::Regression => "regression",
Self::Stress => "stress",
Self::Integration => "integration",
Self::SelfHost => "selfhost",
Self::Oracle => "oracle",
Self::Sanitizer => "sanitizer",
Self::CodeGen => "codegen",
Self::CrossCompile => "cross-compile",
Self::ErrorRecovery => "error-recovery",
}
}
pub fn default_enabled(&self) -> bool {
match self {
Self::Smoke | Self::Correctness | Self::Integration | Self::SelfHost => true,
Self::Performance | Self::Stress | Self::Oracle | Self::Sanitizer => false,
Self::CodeGen | Self::CrossCompile | Self::ErrorRecovery | Self::Regression => true,
}
}
pub fn is_expensive(&self) -> bool {
matches!(
self,
Self::Performance | Self::Stress | Self::Oracle | Self::CrossCompile
)
}
pub fn all() -> Vec<TestCategory> {
vec![
Self::Smoke,
Self::Correctness,
Self::Performance,
Self::Regression,
Self::Stress,
Self::Integration,
Self::SelfHost,
Self::Oracle,
Self::Sanitizer,
Self::CodeGen,
Self::CrossCompile,
Self::ErrorRecovery,
]
}
pub fn default_set() -> Vec<TestCategory> {
Self::all()
.into_iter()
.filter(|c| c.default_enabled())
.collect()
}
pub fn expensive_set() -> Vec<TestCategory> {
Self::all()
.into_iter()
.filter(|c| c.is_expensive())
.collect()
}
pub fn quick_set() -> Vec<TestCategory> {
vec![Self::Smoke, Self::Correctness, Self::SelfHost]
}
pub fn ci_set() -> Vec<TestCategory> {
vec![
Self::Smoke,
Self::Correctness,
Self::Regression,
Self::Integration,
Self::SelfHost,
Self::CodeGen,
Self::ErrorRecovery,
]
}
}
#[derive(Debug, Clone)]
pub struct X86SelfHostConfig {
pub source_dir: PathBuf,
pub build_dir: PathBuf,
pub stage_count: usize,
pub verify_stages: bool,
pub stage_configs: BTreeMap<usize, X86StageConfig>,
pub system_compiler: PathBuf,
pub source_extensions: Vec<String>,
pub exclude_dirs: Vec<String>,
pub generate_report: bool,
pub report_path: Option<PathBuf>,
pub report_format: ReportFormat,
pub generate_scripts: bool,
pub script_dir: Option<PathBuf>,
pub generate_ci: bool,
pub ci_output_path: Option<PathBuf>,
pub enable_caching: bool,
pub cache_dir: Option<PathBuf>,
pub max_cache_size_bytes: u64,
pub test_suite_enabled: bool,
pub test_suite_dir: Option<PathBuf>,
pub test_categories: Vec<TestCategory>,
pub track_performance: bool,
pub detect_regressions: bool,
pub regression_threshold_pct: f64,
pub max_retries: u32,
pub continue_on_error: bool,
pub clean_build: bool,
pub dry_run: bool,
pub verbose: bool,
pub global_env: BTreeMap<String, String>,
pub lock_build_dir: bool,
pub distributed_compile: bool,
pub distcc_hosts: Vec<String>,
pub strip_binaries: bool,
}
impl X86SelfHostConfig {
pub fn x86_64_linux(source_dir: PathBuf) -> Self {
let build_dir = source_dir.join("target").join("bootstrap");
let mut stage_configs = BTreeMap::new();
let s0 = X86StageConfig::new(X86BootstrapStage::Stage0, &build_dir)
.stage0_default()
.with_compiler(PathBuf::from("rustc"));
stage_configs.insert(0, s0);
let s1 = X86StageConfig::new(X86BootstrapStage::Stage1, &build_dir)
.stage1_default()
.with_compiler(build_dir.join("stage0/bin/llvm-native-clang"));
stage_configs.insert(1, s1);
let s2 = X86StageConfig::new(X86BootstrapStage::Stage2, &build_dir)
.stage2_stage3_default()
.with_compiler(build_dir.join("stage1/bin/llvm-native-clang"));
stage_configs.insert(2, s2);
let s3 = X86StageConfig::new(X86BootstrapStage::Stage3, &build_dir)
.stage2_stage3_default()
.with_compiler(build_dir.join("stage2/bin/llvm-native-clang"));
stage_configs.insert(3, s3);
X86SelfHostConfig {
source_dir,
build_dir,
stage_count: 4,
verify_stages: true,
stage_configs,
system_compiler: PathBuf::from("rustc"),
source_extensions: vec!["rs".to_string()],
exclude_dirs: vec![
"target".to_string(),
".git".to_string(),
".cache".to_string(),
"node_modules".to_string(),
],
generate_report: true,
report_path: None,
report_format: ReportFormat::Markdown,
generate_scripts: false,
script_dir: None,
generate_ci: false,
ci_output_path: None,
enable_caching: true,
cache_dir: None,
max_cache_size_bytes: 1024 * 1024 * 1024, test_suite_enabled: true,
test_suite_dir: None,
test_categories: TestCategory::all(),
track_performance: true,
detect_regressions: true,
regression_threshold_pct: PERF_REGRESSION_THRESHOLD_PCT,
max_retries: MAX_STAGE_RETRIES,
continue_on_error: false,
clean_build: false,
dry_run: false,
verbose: false,
global_env: BTreeMap::new(),
lock_build_dir: false,
distributed_compile: false,
distcc_hosts: Vec::new(),
strip_binaries: true,
}
}
pub fn x86_32_linux(source_dir: PathBuf) -> Self {
let mut config = Self::x86_64_linux(source_dir);
for (_, sc) in config.stage_configs.iter_mut() {
sc.target_triple = X86_32_LINUX_TARGET.to_string();
sc.target_cpu = "i686".to_string();
}
config
}
pub fn quick_verify(source_dir: PathBuf) -> Self {
let build_dir = source_dir.join("target").join("bootstrap");
let mut stage_configs = BTreeMap::new();
let s0 = X86StageConfig::new(X86BootstrapStage::Stage0, &build_dir)
.stage0_default()
.with_compiler(PathBuf::from("rustc"));
stage_configs.insert(0, s0);
let s1 = X86StageConfig::new(X86BootstrapStage::Stage1, &build_dir)
.with_compiler(build_dir.join("stage0/bin/llvm-native-clang"))
.with_opt_level("1");
stage_configs.insert(1, s1);
X86SelfHostConfig {
source_dir,
build_dir,
stage_count: 2,
verify_stages: false,
stage_configs,
system_compiler: PathBuf::from("rustc"),
source_extensions: vec!["rs".to_string()],
exclude_dirs: vec!["target".to_string(), ".git".to_string()],
generate_report: false,
report_path: None,
report_format: ReportFormat::Markdown,
generate_scripts: false,
script_dir: None,
generate_ci: false,
ci_output_path: None,
enable_caching: false,
cache_dir: None,
max_cache_size_bytes: 0,
test_suite_enabled: false,
test_suite_dir: None,
test_categories: Vec::new(),
track_performance: false,
detect_regressions: false,
regression_threshold_pct: 0.0,
max_retries: 1,
continue_on_error: false,
clean_build: false,
dry_run: false,
verbose: false,
global_env: BTreeMap::new(),
lock_build_dir: false,
distributed_compile: false,
distcc_hosts: Vec::new(),
strip_binaries: false,
}
}
pub fn release_bootstrap(source_dir: PathBuf) -> Self {
let mut config = Self::x86_64_linux(source_dir);
for (_, sc) in config.stage_configs.iter_mut() {
*sc = sc.clone().release_profile();
}
if let Some(s0) = config.stage_configs.get_mut(&0) {
*s0 = s0
.clone()
.stage0_default()
.with_compiler(PathBuf::from("rustc"));
}
config.strip_binaries = true;
config
}
pub fn ci_profile(source_dir: PathBuf) -> Self {
let mut config = Self::x86_64_linux(source_dir.clone());
config.verify_stages = true;
config.track_performance = true;
config.detect_regressions = true;
config.generate_report = true;
config.generate_ci = true;
config.ci_output_path = Some(source_dir.join(".github/workflows/bootstrap.yml"));
config.clean_build = true;
for (_, sc) in config.stage_configs.iter_mut() {
sc.werror = true;
}
config
}
pub fn get_stage_config(&self, stage: X86BootstrapStage) -> Option<&X86StageConfig> {
self.stage_configs.get(&stage.index())
}
pub fn get_stage_config_mut(
&mut self,
stage: X86BootstrapStage,
) -> Option<&mut X86StageConfig> {
self.stage_configs.get_mut(&stage.index())
}
pub fn set_stage_config(&mut self, stage: X86BootstrapStage, config: X86StageConfig) {
self.stage_configs.insert(stage.index(), config);
}
pub fn compiler_for_stage(&self, stage: X86BootstrapStage) -> Option<PathBuf> {
self.get_stage_config(stage)
.map(|sc| sc.compiler_path.clone())
}
pub fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
if !self.source_dir.exists() {
errors.push(format!(
"Source directory does not exist: {}",
self.source_dir.display()
));
}
if self.stage_count == 0 {
errors.push("Stage count must be at least 1".to_string());
}
if self.stage_count > 5 {
errors.push("Stage count cannot exceed 5".to_string());
}
if self.stage_count >= 1 && !self.system_compiler.exists() {
errors.push(format!(
"System compiler not found: {}",
self.system_compiler.display()
));
}
for i in 0..self.stage_count {
if let Some(sc) = self.stage_configs.get(&i) {
if i > 0 {
let expected_path = self
.build_dir
.join(format!("stage{}/bin/llvm-native-clang", i - 1));
if sc.compiler_path != expected_path && !self.dry_run {
if expected_path.exists() && sc.compiler_path != expected_path {
errors.push(format!(
"Stage {} compiler path mismatch: expected '{}', got '{}'",
i,
expected_path.display(),
sc.compiler_path.display()
));
}
}
}
} else {
errors.push(format!("Missing configuration for stage index {}", i));
}
}
if self.verify_stages && self.stage_count < 4 {
errors.push(
"Stage verification requires at least 4 build stages (Stage0-Stage3)".to_string(),
);
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn create_dirs(&self) -> io::Result<()> {
fs::create_dir_all(&self.build_dir)?;
for i in 0..self.stage_count {
if let Some(sc) = self.stage_configs.get(&i) {
fs::create_dir_all(&sc.build_dir)?;
fs::create_dir_all(&sc.output_dir)?;
}
}
if let Some(ref cache_dir) = self.cache_dir {
fs::create_dir_all(cache_dir)?;
}
if let Some(ref script_dir) = self.script_dir {
fs::create_dir_all(script_dir)?;
}
Ok(())
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str(&format!(
"X86 Self-Host Config:\n Source: {}\n Build: {}\n Stages: {}\n",
self.source_dir.display(),
self.build_dir.display(),
self.stage_count
));
s.push_str(&format!(" Verify: {}\n", self.verify_stages));
s.push_str(&format!(
" System Compiler: {}\n",
self.system_compiler.display()
));
s.push_str(&format!(" Test Suite: {}\n", self.test_suite_enabled));
s.push_str(&format!(" Caching: {}\n", self.enable_caching));
s.push_str(&format!(
" Track Performance: {}\n",
self.track_performance
));
s.push_str(&format!(" Report: {}\n", self.generate_report));
s.push_str(" Stage Configs:\n");
for i in 0..self.stage_count {
if let Some(sc) = self.stage_configs.get(&i) {
s.push_str(&format!(" - {}\n", sc.summary()));
}
}
s
}
}
impl Default for X86SelfHostConfig {
fn default() -> Self {
X86SelfHostConfig::x86_64_linux(PathBuf::from("."))
}
}
#[derive(Debug)]
pub struct X86StageCompiler {
pub path: PathBuf,
pub stage: X86BootstrapStage,
pub has_run: bool,
pub stats: X86CompilerStats,
pub last_output: Option<X86CompilationOutput>,
pub env: BTreeMap<String, String>,
pub timeout_secs: u64,
}
#[derive(Debug, Clone, Default)]
pub struct X86CompilerStats {
pub total_compilations: u64,
pub successful_compilations: u64,
pub failed_compilations: u64,
pub total_lines: u64,
pub total_files: u64,
pub total_output_bytes: u64,
pub cumulative_time: Duration,
pub fastest_time: Option<Duration>,
pub slowest_time: Option<Duration>,
pub total_warnings: u64,
pub total_errors: u64,
pub peak_memory_bytes: u64,
}
#[derive(Debug, Clone)]
pub struct X86CompilationOutput {
pub stdout: String,
pub stderr: String,
pub exit_status: i32,
pub success: bool,
pub duration: Duration,
pub warning_count: usize,
pub error_count: usize,
pub output_size: u64,
pub diagnostics: Vec<X86CompilerDiagnostic>,
}
#[derive(Debug, Clone)]
pub struct X86CompilerDiagnostic {
pub severity: X86DiagSeverity,
pub message: String,
pub file: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86DiagSeverity {
Note,
Warning,
Error,
Fatal,
}
impl X86DiagSeverity {
pub fn as_str(&self) -> &'static str {
match self {
Self::Note => "note",
Self::Warning => "warning",
Self::Error => "error",
Self::Fatal => "fatal error",
}
}
}
impl X86StageCompiler {
pub fn new(path: PathBuf, stage: X86BootstrapStage) -> Self {
X86StageCompiler {
path,
stage,
has_run: false,
stats: X86CompilerStats::default(),
last_output: None,
env: BTreeMap::new(),
timeout_secs: DEFAULT_STAGE_TIMEOUT_SECS,
}
}
pub fn is_available(&self) -> bool {
self.path.exists() && is_executable(&self.path)
}
pub fn compile_file(
&mut self,
source_file: &Path,
output_file: &Path,
flags: &[String],
) -> io::Result<X86CompilationOutput> {
let start = Instant::now();
let mut cmd = Command::new(&self.path);
cmd.arg(source_file);
cmd.arg("-o");
cmd.arg(output_file);
cmd.args(flags);
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
for (k, v) in &self.env {
cmd.env(k, v);
}
let output = cmd.output()?;
let duration = start.elapsed();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let success = output.status.success();
let exit_status = output.status.code().unwrap_or(-1);
let (warning_count, error_count) = self.count_warnings_errors(&stderr);
let diagnostics = self.parse_diagnostics(&stderr);
let output_size = if output_file.exists() {
fs::metadata(output_file).map(|m| m.len()).unwrap_or(0)
} else {
0
};
let compilation_output = X86CompilationOutput {
stdout,
stderr,
exit_status,
success,
duration,
warning_count,
error_count,
output_size,
diagnostics,
};
self.update_stats(&compilation_output);
self.last_output = Some(compilation_output.clone());
self.has_run = true;
Ok(compilation_output)
}
pub fn compile_string(
&mut self,
source: &str,
flags: &[String],
) -> io::Result<X86CompilationOutput> {
let tmp_dir = std::env::temp_dir();
let tmp_src = tmp_dir.join(format!("bootstrap_tmp_{}.c", std::process::id()));
let tmp_out = tmp_dir.join(format!("bootstrap_tmp_{}.o", std::process::id()));
fs::write(&tmp_src, source)?;
let result = self.compile_file(&tmp_src, &tmp_out, flags);
let _ = fs::remove_file(&tmp_src);
let _ = fs::remove_file(&tmp_out);
result
}
pub fn compile_files(
&mut self,
source_files: &[PathBuf],
output_file: &Path,
flags: &[String],
) -> io::Result<X86CompilationOutput> {
let start = Instant::now();
let mut cmd = Command::new(&self.path);
for src in source_files {
cmd.arg(src);
}
cmd.arg("-o");
cmd.arg(output_file);
cmd.args(flags);
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
for (k, v) in &self.env {
cmd.env(k, v);
}
let output = cmd.output()?;
let duration = start.elapsed();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let success = output.status.success();
let exit_status = output.status.code().unwrap_or(-1);
let (warning_count, error_count) = self.count_warnings_errors(&stderr);
let diagnostics = self.parse_diagnostics(&stderr);
let output_size = if output_file.exists() {
fs::metadata(output_file).map(|m| m.len()).unwrap_or(0)
} else {
0
};
let compilation_output = X86CompilationOutput {
stdout,
stderr,
exit_status,
success,
duration,
warning_count,
error_count,
output_size,
diagnostics,
};
self.update_stats(&compilation_output);
self.last_output = Some(compilation_output.clone());
self.has_run = true;
Ok(compilation_output)
}
pub fn get_version(&self) -> io::Result<String> {
let output = Command::new(&self.path)
.arg("--version")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
pub fn supports_feature(&self, feature: &str) -> bool {
let output = Command::new(&self.path)
.arg(format!("-Ctarget-feature=+{feature}"))
.arg("-fsyntax-only")
.arg("-x")
.arg("c")
.arg("-")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
matches!(output, Ok(status) if status.success())
}
fn count_warnings_errors(&self, stderr: &str) -> (usize, usize) {
let warnings = stderr
.lines()
.filter(|l| l.contains("warning:") || l.contains("warning["))
.count();
let errors = stderr
.lines()
.filter(|l| l.contains("error:") || l.contains("error["))
.count();
(warnings, errors)
}
fn parse_diagnostics(&self, stderr: &str) -> Vec<X86CompilerDiagnostic> {
let mut diags = Vec::new();
for line in stderr.lines() {
let (severity, rest) = if line.starts_with("error:") || line.contains("error[") {
(
X86DiagSeverity::Error,
line.trim_start_matches("error:").trim(),
)
} else if line.starts_with("warning:") || line.contains("warning[") {
(
X86DiagSeverity::Warning,
line.trim_start_matches("warning:").trim(),
)
} else if line.starts_with("note:") {
(
X86DiagSeverity::Note,
line.trim_start_matches("note:").trim(),
)
} else if line.contains("fatal error:") {
(
X86DiagSeverity::Fatal,
line.trim_start_matches("fatal error:").trim(),
)
} else {
continue;
};
let (file, line_num, col_num) = self.parse_source_location(rest);
diags.push(X86CompilerDiagnostic {
severity,
message: rest.to_string(),
file,
line: line_num,
column: col_num,
});
}
diags
}
fn parse_source_location(&self, text: &str) -> (Option<String>, Option<u32>, Option<u32>) {
for part in text.split_whitespace() {
let cleaned = part
.trim_matches(|c: char| c == '(' || c == ')' || c == ',' || c == '[' || c == ']');
let components: Vec<&str> = cleaned.split(':').collect();
if components.len() >= 2 {
let potential_file = components[0];
if potential_file.contains('.') && !potential_file.starts_with("http") {
let file = Some(potential_file.to_string());
let line = components.get(1).and_then(|s| s.parse::<u32>().ok());
let col = components.get(2).and_then(|s| s.parse::<u32>().ok());
return (file, line, col);
}
}
}
(None, None, None)
}
fn update_stats(&mut self, output: &X86CompilationOutput) {
self.stats.total_compilations += 1;
if output.success {
self.stats.successful_compilations += 1;
} else {
self.stats.failed_compilations += 1;
}
self.stats.total_output_bytes += output.output_size;
self.stats.cumulative_time += output.duration;
self.stats.total_warnings += output.warning_count as u64;
self.stats.total_errors += output.error_count as u64;
self.stats.fastest_time = Some(
self.stats
.fastest_time
.map_or(output.duration, |t| t.min(output.duration)),
);
self.stats.slowest_time = Some(
self.stats
.slowest_time
.map_or(output.duration, |t| t.max(output.duration)),
);
if let Ok(mem) = self.read_current_memory() {
self.stats.peak_memory_bytes = self.stats.peak_memory_bytes.max(mem);
}
}
fn read_current_memory(&self) -> io::Result<u64> {
let status = fs::read_to_string("/proc/self/status")?;
for line in status.lines() {
if line.starts_with("VmRSS:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
return parts[1]
.parse::<u64>()
.map(|kb| kb * 1024)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e));
}
}
}
Ok(0)
}
pub fn stats_summary(&self) -> String {
let success_rate = if self.stats.total_compilations > 0 {
(self.stats.successful_compilations as f64 / self.stats.total_compilations as f64)
* 100.0
} else {
0.0
};
format!(
"{} ({}): {}/{} successful ({:.1}%), {} warnings, {} errors, cumulative time {:?}",
self.stage.short_label(),
self.path.display(),
self.stats.successful_compilations,
self.stats.total_compilations,
success_rate,
self.stats.total_warnings,
self.stats.total_errors,
self.stats.cumulative_time,
)
}
pub fn reset_stats(&mut self) {
self.stats = X86CompilerStats::default();
self.last_output = None;
self.has_run = false;
}
}
#[derive(Debug)]
pub struct X86BootstrapVerifier {
pub abort_on_error: bool,
pub verbose: bool,
pub results: Vec<X86VerificationResult>,
pub total_checks: u64,
pub total_passed: u64,
pub total_failed: u64,
pub behavior_test_config: Option<X86VerificationTestConfig>,
}
#[derive(Debug, Clone)]
pub struct X86VerificationResult {
pub check_name: String,
pub category: X86VerificationCategory,
pub passed: bool,
pub error: Option<String>,
pub duration: Duration,
pub details: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86VerificationCategory {
Binary,
Symbols,
CodeSection,
Behavior,
Metadata,
Size,
Performance,
}
impl X86VerificationCategory {
pub fn as_str(&self) -> &'static str {
match self {
Self::Binary => "binary",
Self::Symbols => "symbols",
Self::CodeSection => "code_section",
Self::Behavior => "behavior",
Self::Metadata => "metadata",
Self::Size => "size",
Self::Performance => "performance",
}
}
}
#[derive(Debug, Clone)]
pub struct X86VerificationTestConfig {
pub test_count: usize,
pub random_tests: bool,
pub random_seed: Option<u64>,
pub max_test_size_lines: usize,
pub timeout_secs: u64,
pub categories: Vec<String>,
}
impl Default for X86VerificationTestConfig {
fn default() -> Self {
X86VerificationTestConfig {
test_count: 100,
random_tests: false,
random_seed: None,
max_test_size_lines: 500,
timeout_secs: DEFAULT_TEST_TIMEOUT_SECS,
categories: vec!["smoke".to_string(), "correctness".to_string()],
}
}
}
impl X86BootstrapVerifier {
pub fn new(abort_on_error: bool, verbose: bool) -> Self {
X86BootstrapVerifier {
abort_on_error,
verbose,
results: Vec::new(),
total_checks: 0,
total_passed: 0,
total_failed: 0,
behavior_test_config: None,
}
}
pub fn verify_stages(
&mut self,
stage2_binary: &Path,
stage3_binary: &Path,
) -> io::Result<bool> {
let start = Instant::now();
let binary_result = self.verify_binary_identical(stage2_binary, stage3_binary)?;
let symbol_result = self.verify_symbols(stage2_binary, stage3_binary);
let code_result = self.verify_code_sections(stage2_binary, stage3_binary);
let size_result = self.verify_sizes(stage2_binary, stage3_binary);
let symbol_ok = symbol_result.as_ref().map_or(false, |_| true);
let code_ok = code_result.as_ref().map_or(false, |_| true);
let size_ok = size_result.as_ref().map_or(false, |_| true);
let all_passed = binary_result && symbol_ok && code_ok && size_ok;
self.results.push(X86VerificationResult {
check_name: "Stage Comparison (S2 vs S3)".to_string(),
category: X86VerificationCategory::Binary,
passed: all_passed,
error: if all_passed {
None
} else {
Some("One or more verification checks failed".to_string())
},
duration: start.elapsed(),
details: {
let mut d = BTreeMap::new();
d.insert("binary_identical".to_string(), binary_result.to_string());
d.insert(
"symbols_identical".to_string(),
symbol_ok.to_string(),
);
d.insert(
"code_sections_identical".to_string(),
code_ok.to_string(),
);
d
},
});
self.total_checks += 4;
if all_passed {
self.total_passed += 4;
} else {
self.total_failed += 1;
}
Ok(all_passed)
}
pub fn verify_binary_identical(&mut self, a: &Path, b: &Path) -> io::Result<bool> {
let start = Instant::now();
let bytes_a = fs::read(a)?;
let bytes_b = fs::read(b)?;
let identical = if bytes_a.len() != bytes_b.len() {
false
} else if bytes_a.is_empty() {
true
} else {
bytes_a == bytes_b
};
let content_identical = if !identical {
self.compare_content_aware(&bytes_a, &bytes_b)
} else {
true
};
let passed = identical || content_identical;
let mut details = BTreeMap::new();
details.insert("size_a".to_string(), bytes_a.len().to_string());
details.insert("size_b".to_string(), bytes_b.len().to_string());
details.insert("byte_identical".to_string(), identical.to_string());
details.insert(
"content_identical".to_string(),
content_identical.to_string(),
);
if !passed {
let diff_positions = self.find_diff_positions(&bytes_a, &bytes_b);
details.insert("diff_count".to_string(), diff_positions.len().to_string());
if !diff_positions.is_empty() {
details.insert("first_diff_at".to_string(), diff_positions[0].to_string());
}
}
self.record_check(
"Binary Identical",
X86VerificationCategory::Binary,
passed,
if passed {
None
} else {
Some(format!(
"Binaries differ: {} vs {} bytes, {} differing positions",
bytes_a.len(),
bytes_b.len(),
if identical && !content_identical {
"content-only"
} else {
"byte-level"
}
))
},
start.elapsed(),
details,
);
Ok(passed)
}
pub fn verify_symbols(&mut self, a: &Path, b: &Path) -> io::Result<bool> {
let start = Instant::now();
let symbols_a = self.extract_symbols(a);
let symbols_b = self.extract_symbols(b);
let mut passed = true;
let mut details = BTreeMap::new();
details.insert("symbol_count_a".to_string(), symbols_a.len().to_string());
details.insert("symbol_count_b".to_string(), symbols_b.len().to_string());
if symbols_a.len() != symbols_b.len() {
passed = false;
details.insert(
"error".to_string(),
format!(
"Symbol count mismatch: {} vs {}",
symbols_a.len(),
symbols_b.len()
),
);
}
let set_b: HashSet<_> = symbols_b.iter().cloned().collect();
let mut missing = Vec::new();
for sym in &symbols_a {
if !set_b.contains(sym) {
missing.push(sym.clone());
}
}
if !missing.is_empty() {
passed = false;
details.insert("missing_symbols".to_string(), missing.join(", "));
}
let set_a: HashSet<_> = symbols_a.iter().cloned().collect();
let mut extra = Vec::new();
for sym in &symbols_b {
if !set_a.contains(sym) {
extra.push(sym.clone());
}
}
if !extra.is_empty() {
details.insert("extra_symbols".to_string(), extra.join(", "));
}
self.record_check(
"Symbol Table Comparison",
X86VerificationCategory::Symbols,
passed,
if passed {
None
} else {
Some(format!(
"{} missing, {} extra symbols",
missing.len(),
extra.len()
))
},
start.elapsed(),
details,
);
Ok(passed)
}
pub fn verify_code_sections(&mut self, a: &Path, b: &Path) -> io::Result<bool> {
let start = Instant::now();
let bytes_a = fs::read(a)?;
let bytes_b = fs::read(b)?;
let text_a = self.extract_text_section(&bytes_a);
let text_b = self.extract_text_section(&bytes_b);
let passed = text_a == text_b;
let mut details = BTreeMap::new();
details.insert("text_size_a".to_string(), text_a.len().to_string());
details.insert("text_size_b".to_string(), text_b.len().to_string());
if !passed {
let diff_count = self.find_diff_positions(&text_a, &text_b).len();
details.insert("text_diff_count".to_string(), diff_count.to_string());
}
self.record_check(
"Code Section Comparison",
X86VerificationCategory::CodeSection,
passed,
if passed {
None
} else {
Some("Code sections differ".to_string())
},
start.elapsed(),
details,
);
Ok(passed)
}
pub fn verify_sizes(&mut self, a: &Path, b: &Path) -> io::Result<bool> {
let start = Instant::now();
let size_a = fs::metadata(a).map(|m| m.len()).unwrap_or(0);
let size_b = fs::metadata(b).map(|m| m.len()).unwrap_or(0);
let delta_pct = if size_a > 0 {
((size_b as f64 - size_a as f64) / size_a as f64) * 100.0
} else {
0.0
};
let passed = delta_pct.abs() <= MAX_SIZE_INCREASE_PCT;
let mut details = BTreeMap::new();
details.insert("size_stage2".to_string(), size_a.to_string());
details.insert("size_stage3".to_string(), size_b.to_string());
details.insert("delta_pct".to_string(), format!("{:.2}%", delta_pct));
self.record_check(
"Binary Size Comparison",
X86VerificationCategory::Size,
passed,
if passed {
None
} else {
Some(format!(
"Size change of {:.2}% exceeds threshold of {:.2}%",
delta_pct, MAX_SIZE_INCREASE_PCT
))
},
start.elapsed(),
details,
);
Ok(passed)
}
pub fn verify_behavior(
&mut self,
compiler_path: &Path,
test_config: &X86VerificationTestConfig,
) -> Vec<X86VerificationResult> {
let mut results = Vec::new();
let test_programs = self.generate_test_programs(test_config);
for (i, program) in test_programs.iter().enumerate() {
let start = Instant::now();
let tmp_dir = std::env::temp_dir().join(format!("bootstrap_verify_{}", i));
let _ = fs::create_dir_all(&tmp_dir);
let src_file = tmp_dir.join("test.c");
let out_file = tmp_dir.join("test.out");
let _ = fs::write(&src_file, program);
let output = Command::new(compiler_path)
.arg(&src_file)
.arg("-o")
.arg(&out_file)
.stdout(Stdio::null())
.stderr(Stdio::piped())
.output();
let passed = match output {
Ok(out) => {
let success = out.status.success();
let stderr = String::from_utf8_lossy(&out.stderr);
let has_errors = stderr.contains("error:") || stderr.contains("error[");
success && !has_errors
}
Err(_) => false,
};
results.push(X86VerificationResult {
check_name: format!("Behavior Test #{}", i + 1),
category: X86VerificationCategory::Behavior,
passed,
error: if passed {
None
} else {
Some("Test program failed to compile".to_string())
},
duration: start.elapsed(),
details: BTreeMap::new(),
});
let _ = fs::remove_dir_all(&tmp_dir);
if self.abort_on_error && !passed {
break;
}
}
for r in &results {
self.total_checks += 1;
if r.passed {
self.total_passed += 1;
} else {
self.total_failed += 1;
}
}
self.results.extend(results.clone());
results
}
fn generate_test_programs(&self, config: &X86VerificationTestConfig) -> Vec<String> {
let mut programs = Vec::new();
programs.push(r#"int main(void) { return 0; }"#.to_string());
programs.push(
r#"int add(int a, int b) { return a + b; }
int main(void) { return add(1, 2) - 3; }"#
.to_string(),
);
programs.push(
r#"#include <stdint.h>
int main(void) {
int64_t x = -42;
uint32_t y = 100;
return (int)(x + (int64_t)y);
}"#
.to_string(),
);
programs.push(
r#"int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main(void) { return factorial(5) - 120; }"#
.to_string(),
);
programs.push(
r#"struct Point { int x; int y; };
int main(void) {
struct Point p = {10, 20};
return p.x + p.y - 30;
}"#
.to_string(),
);
programs.push(
r#"int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
int sum = 0;
for (int i = 0; i < 5; i++) sum += arr[i];
return sum - 15;
}"#
.to_string(),
);
programs.push(
r#"int main(void) {
int x = 42;
int *p = &x;
*p = 100;
return x - 100;
}"#
.to_string(),
);
programs.push(
r#"int compute(int a, int b) {
if (a > b) return a - b;
else if (a < b) return b - a;
else return 0;
}
int main(void) { return compute(7, 3) - 4; }"#
.to_string(),
);
programs.push(
r#"int main(void) {
int x = 0xFF;
int y = 0x0F;
return (x & y) - 0x0F;
}"#
.to_string(),
);
programs.push(
r#"typedef unsigned long size_t;
void *memcpy(void *dst, const void *src, size_t n) {
char *d = (char *)dst;
const char *s = (const char *)src;
for (size_t i = 0; i < n; i++) d[i] = s[i];
return dst;
}
int main(void) {
char src[] = "hello";
char dst[6];
memcpy(dst, src, 5);
dst[5] = 0;
return dst[0] - 'h';
}"#
.to_string(),
);
let additional = config.test_count.saturating_sub(programs.len());
for i in 0..additional {
programs.push(format!(
r#"int test_{i}(void) {{ return {i} % 100; }}
int main(void) {{ return test_{i}() - test_{i}(); }}"#
));
}
programs.truncate(config.test_count);
programs
}
fn compare_content_aware(&self, a: &[u8], b: &[u8]) -> bool {
if a.is_empty() || b.is_empty() {
return false;
}
let size_diff = (a.len() as i64 - b.len() as i64).unsigned_abs();
if size_diff > 1024 * 1024 {
return false;
}
let min_len = a.len().min(b.len());
let chunk_size = HASH_CHUNK_SIZE;
let mut diff_count = 0;
let max_diffs = min_len / chunk_size / 10;
for offset in (0..min_len).step_by(chunk_size) {
let end = (offset + chunk_size).min(min_len);
let chunk_a = &a[offset..end];
let chunk_b = &b[offset..end];
if chunk_a != chunk_b {
diff_count += 1;
if diff_count > max_diffs {
return false;
}
}
}
true
}
fn find_diff_positions(&self, a: &[u8], b: &[u8]) -> Vec<usize> {
let min_len = a.len().min(b.len());
let mut positions = Vec::new();
for i in 0..min_len {
if a[i] != b[i] {
positions.push(i);
if positions.len() >= 100 {
break;
}
}
}
if a.len() != b.len() {
positions.push(min_len);
}
positions
}
fn extract_symbols(&self, path: &Path) -> Vec<String> {
if let Ok(output) = Command::new("nm")
.arg(path)
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
{
let stdout = String::from_utf8_lossy(&output.stdout);
let mut symbols = Vec::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
symbols.push(parts[2].to_string());
} else if parts.len() == 1 {
symbols.push(parts[0].to_string());
}
}
symbols.sort();
return symbols;
}
self.parse_elf_symbols(path).unwrap_or_default()
}
fn parse_elf_symbols(&self, path: &Path) -> io::Result<Vec<String>> {
let data = fs::read(path)?;
let mut symbols = Vec::new();
if data.len() < 4 || &data[0..4] != b"\x7fELF" {
return Ok(symbols);
}
let mut current = String::new();
for &byte in &data[64..] {
if symbols.len() >= 10000 {
break;
}
if byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'.' || byte == b'@' {
current.push(byte as char);
} else if byte == 0 && !current.is_empty() {
if is_likely_symbol(¤t) {
symbols.push(current.clone());
}
current.clear();
} else if !byte.is_ascii_graphic() {
if !current.is_empty() && is_likely_symbol(¤t) {
symbols.push(current.clone());
}
current.clear();
} else {
current.clear();
}
}
symbols.sort();
symbols.dedup();
Ok(symbols)
}
fn extract_text_section(&self, data: &[u8]) -> Vec<u8> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return data.to_vec();
}
let start = 64.min(data.len());
data[start..].to_vec()
}
fn record_check(
&mut self,
name: &str,
category: X86VerificationCategory,
passed: bool,
error: Option<String>,
duration: Duration,
details: BTreeMap<String, String>,
) {
self.total_checks += 1;
if passed {
self.total_passed += 1;
} else {
self.total_failed += 1;
}
self.results.push(X86VerificationResult {
check_name: name.to_string(),
category,
passed,
error,
duration,
details,
});
}
pub fn all_passed(&self) -> bool {
self.total_failed == 0
}
pub fn pass_rate(&self) -> f64 {
if self.total_checks == 0 {
return 100.0;
}
(self.total_passed as f64 / self.total_checks as f64) * 100.0
}
pub fn summary(&self) -> String {
let mut s = format!(
"Verification Summary: {}/{} passed ({:.1}%)\n\n",
self.total_passed,
self.total_checks,
self.pass_rate(),
);
let mut by_category: BTreeMap<&str, Vec<&X86VerificationResult>> = BTreeMap::new();
for r in &self.results {
by_category.entry(r.category.as_str()).or_default().push(r);
}
for (cat, results) in &by_category {
let passed = results.iter().filter(|r| r.passed).count();
s.push_str(&format!(" {}: {}/{} passed\n", cat, passed, results.len()));
}
let failures: Vec<_> = self.results.iter().filter(|r| !r.passed).collect();
if !failures.is_empty() {
s.push_str("\nFailures:\n");
for f in &failures {
s.push_str(&format!(
" - {}: {}\n",
f.check_name,
f.error.as_deref().unwrap_or("unknown error")
));
}
}
s
}
pub fn reset(&mut self) {
self.results.clear();
self.total_checks = 0;
self.total_passed = 0;
self.total_failed = 0;
}
}
impl Default for X86BootstrapVerifier {
fn default() -> Self {
X86BootstrapVerifier::new(false, false)
}
}
#[derive(Debug)]
pub struct X86BootstrapCache {
pub enabled: bool,
pub cache_dir: PathBuf,
pub max_size_bytes: u64,
pub current_size_bytes: u64,
pub hits: u64,
pub misses: u64,
stage_caches: BTreeMap<X86BootstrapStage, X86StageCache>,
memory_cache: HashMap<String, X86CacheEntry>,
max_memory_entries: usize,
}
#[derive(Debug, Clone)]
struct X86StageCache {
stage: X86BootstrapStage,
dir: PathBuf,
entry_count: usize,
}
#[derive(Debug, Clone)]
pub struct X86CacheEntry {
pub key: String,
pub source_path: PathBuf,
pub cached_path: PathBuf,
pub source_hash: u64,
pub compiler_hash: u64,
pub flags_hash: u64,
pub output_size: u64,
pub created_at: SystemTime,
pub accessed: bool,
pub stage: X86BootstrapStage,
}
#[derive(Debug, Clone)]
pub struct X86CacheStats {
pub enabled: bool,
pub total_entries: u64,
pub hits: u64,
pub misses: u64,
pub hit_rate: f64,
pub current_size_bytes: u64,
pub max_size_bytes: u64,
pub per_stage_hits: BTreeMap<X86BootstrapStage, u64>,
pub per_stage_misses: BTreeMap<X86BootstrapStage, u64>,
}
impl X86BootstrapCache {
pub fn new(cache_dir: PathBuf, max_size_bytes: u64) -> Self {
X86BootstrapCache {
enabled: true,
cache_dir,
max_size_bytes,
current_size_bytes: 0,
hits: 0,
misses: 0,
stage_caches: BTreeMap::new(),
memory_cache: HashMap::new(),
max_memory_entries: 10000,
}
}
pub fn disabled() -> Self {
X86BootstrapCache {
enabled: false,
cache_dir: PathBuf::new(),
max_size_bytes: 0,
current_size_bytes: 0,
hits: 0,
misses: 0,
stage_caches: BTreeMap::new(),
memory_cache: HashMap::new(),
max_memory_entries: 0,
}
}
pub fn init(&mut self) -> io::Result<()> {
if !self.enabled {
return Ok(());
}
fs::create_dir_all(&self.cache_dir)?;
for stage in X86BootstrapStage::all_build_stages() {
let dir = self.cache_dir.join(stage.dir_name());
fs::create_dir_all(&dir)?;
self.stage_caches.insert(
stage,
X86StageCache {
stage,
dir,
entry_count: 0,
},
);
}
self.scan_existing_entries()?;
Ok(())
}
pub fn make_key(&self, source_path: &Path, compiler_path: &Path, flags: &[String]) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
source_path.to_string_lossy().hash(&mut hasher);
compiler_path.to_string_lossy().hash(&mut hasher);
for flag in flags {
flag.hash(&mut hasher);
}
CACHE_VERSION.hash(&mut hasher);
if let Ok(contents) = fs::read(source_path) {
contents.hash(&mut hasher);
}
format!("{:016x}", hasher.finish())
}
pub fn lookup(
&mut self,
stage: X86BootstrapStage,
source_path: &Path,
compiler_path: &Path,
flags: &[String],
) -> Option<X86CacheEntry> {
if !self.enabled {
self.misses += 1;
return None;
}
let key = self.make_key(source_path, compiler_path, flags);
if let Some(entry) = self.memory_cache.get(&key) {
if entry.cached_path.exists() {
self.hits += 1;
return Some(entry.clone());
}
}
let stage_dir = self.cache_dir.join(stage.dir_name());
let entry_path = stage_dir.join(format!("{}.cache", &key[0..16]));
let meta_path = stage_dir.join(format!("{}.meta", &key[0..16]));
if entry_path.exists() && meta_path.exists() {
if let Ok(entry) = self.read_cache_entry(&key, &entry_path, &meta_path, stage) {
if self.validate_entry(&entry, source_path, compiler_path) {
self.memory_cache.insert(key.clone(), entry.clone());
self.hits += 1;
return Some(entry);
}
}
}
self.misses += 1;
None
}
pub fn store(
&mut self,
stage: X86BootstrapStage,
source_path: &Path,
compiler_path: &Path,
flags: &[String],
output_path: &Path,
) -> io::Result<()> {
if !self.enabled {
return Ok(());
}
let key = self.make_key(source_path, compiler_path, flags);
let stage_dir = self.cache_dir.join(stage.dir_name());
let entry_path = stage_dir.join(format!("{}.cache", &key[0..16]));
let meta_path = stage_dir.join(format!("{}.meta", &key[0..16]));
fs::copy(output_path, &entry_path)?;
let source_hash = hash_file(source_path).unwrap_or(0);
let compiler_hash = hash_file(compiler_path).unwrap_or(0);
let flags_hash = hash_string_slice(flags);
let output_size = fs::metadata(&entry_path).map(|m| m.len()).unwrap_or(0);
let entry = X86CacheEntry {
key: key.clone(),
source_path: source_path.to_path_buf(),
cached_path: entry_path,
source_hash,
compiler_hash,
flags_hash,
output_size,
created_at: SystemTime::now(),
accessed: false,
stage,
};
let meta_json = serde_cache_entry_to_json(&entry);
fs::write(&meta_path, &meta_json)?;
self.current_size_bytes += output_size;
if self.current_size_bytes > self.max_size_bytes {
self.evict()?;
}
if self.memory_cache.len() < self.max_memory_entries {
self.memory_cache.insert(key, entry);
}
if let Some(sc) = self.stage_caches.get_mut(&stage) {
sc.entry_count += 1;
}
Ok(())
}
pub fn invalidate(
&mut self,
stage: X86BootstrapStage,
source_path: &Path,
compiler_path: &Path,
flags: &[String],
) -> io::Result<()> {
let key = self.make_key(source_path, compiler_path, flags);
let stage_dir = self.cache_dir.join(stage.dir_name());
let entry_path = stage_dir.join(format!("{}.cache", &key[0..16]));
let meta_path = stage_dir.join(format!("{}.meta", &key[0..16]));
let _ = fs::remove_file(&entry_path);
let _ = fs::remove_file(&meta_path);
self.memory_cache.remove(&key);
Ok(())
}
pub fn invalidate_stage(&mut self, stage: X86BootstrapStage) -> io::Result<()> {
let stage_dir = self.cache_dir.join(stage.dir_name());
if stage_dir.exists() {
for entry in fs::read_dir(&stage_dir)? {
let entry = entry?;
let _ = fs::remove_file(entry.path());
}
}
self.stage_caches.remove(&stage);
self.scan_existing_entries()?;
Ok(())
}
pub fn clear(&mut self) -> io::Result<()> {
if self.cache_dir.exists() {
fs::remove_dir_all(&self.cache_dir)?;
fs::create_dir_all(&self.cache_dir)?;
}
self.stage_caches.clear();
self.memory_cache.clear();
self.current_size_bytes = 0;
self.hits = 0;
self.misses = 0;
Ok(())
}
pub fn stats(&self) -> X86CacheStats {
let hit_rate = if self.hits + self.misses > 0 {
(self.hits as f64 / (self.hits + self.misses) as f64) * 100.0
} else {
0.0
};
let total_entries: u64 = self
.stage_caches
.values()
.map(|sc| sc.entry_count as u64)
.sum();
X86CacheStats {
enabled: self.enabled,
total_entries,
hits: self.hits,
misses: self.misses,
hit_rate,
current_size_bytes: self.current_size_bytes,
max_size_bytes: self.max_size_bytes,
per_stage_hits: BTreeMap::new(),
per_stage_misses: BTreeMap::new(),
}
}
fn evict(&mut self) -> io::Result<()> {
let mut entries: Vec<X86CacheEntry> = Vec::new();
for sc in self.stage_caches.values() {
if sc.dir.exists() {
for entry in fs::read_dir(&sc.dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().map_or(false, |e| e == "meta") {
if let Ok(meta) = fs::read_to_string(&path) {
if let Ok(ce) = deser_cache_entry_from_json(&meta, sc.stage) {
entries.push(ce);
}
}
}
}
}
}
entries.sort_by_key(|e| {
e.created_at
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
});
let target_size = (self.max_size_bytes as f64 * 0.8) as u64; for entry in &entries {
if self.current_size_bytes <= target_size {
break;
}
let _ = fs::remove_file(&entry.cached_path);
let meta_path = entry.cached_path.with_extension("meta");
let _ = fs::remove_file(&meta_path);
self.current_size_bytes = self.current_size_bytes.saturating_sub(entry.output_size);
}
Ok(())
}
fn scan_existing_entries(&mut self) -> io::Result<()> {
if !self.enabled || !self.cache_dir.exists() {
return Ok(());
}
for stage in X86BootstrapStage::all_build_stages() {
let stage_dir = self.cache_dir.join(stage.dir_name());
if !stage_dir.exists() {
continue;
}
let mut entry_count = 0;
let mut stage_size = 0u64;
for entry in fs::read_dir(&stage_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().map_or(false, |e| e == "cache") {
entry_count += 1;
if let Ok(meta) = fs::metadata(&path) {
stage_size += meta.len();
}
}
}
self.current_size_bytes += stage_size;
self.stage_caches.insert(
stage,
X86StageCache {
stage,
dir: stage_dir,
entry_count,
},
);
}
Ok(())
}
fn read_cache_entry(
&self,
key: &str,
entry_path: &Path,
meta_path: &Path,
stage: X86BootstrapStage,
) -> io::Result<X86CacheEntry> {
let meta_json = fs::read_to_string(meta_path)?;
let mut entry = deser_cache_entry_from_json(&meta_json, stage)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
entry.key = key.to_string();
entry.cached_path = entry_path.to_path_buf();
Ok(entry)
}
fn validate_entry(
&self,
entry: &X86CacheEntry,
source_path: &Path,
compiler_path: &Path,
) -> bool {
if let Ok(current_hash) = hash_file(source_path) {
if current_hash != entry.source_hash {
return false;
}
}
if compiler_path.exists() {
if let Ok(current_hash) = hash_file(compiler_path) {
if current_hash != entry.compiler_hash {
return false;
}
}
}
entry.cached_path.exists()
}
pub fn is_active(&self) -> bool {
self.enabled && !self.stage_caches.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct X86BootstrapMetrics {
pub stage_metrics: BTreeMap<X86BootstrapStage, X86StageMetrics>,
pub total_compilation_time: Duration,
pub total_wall_time: Duration,
pub binary_sizes: BTreeMap<X86BootstrapStage, u64>,
pub correctness_scores: BTreeMap<X86BootstrapStage, f64>,
pub regressions: Vec<X86PerfRegression>,
pub cache_hit_rates: BTreeMap<X86BootstrapStage, f64>,
pub start_time: SystemTime,
pub end_time: Option<SystemTime>,
pub enabled: bool,
}
#[derive(Debug, Clone, Default)]
pub struct X86StageMetrics {
pub stage: X86BootstrapStage,
pub wall_time: Duration,
pub compilation_time: Duration,
pub link_time: Duration,
pub test_time: Duration,
pub verify_time: Duration,
pub files_compiled: usize,
pub errors: usize,
pub warnings: usize,
pub output_size: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub peak_memory_bytes: u64,
pub success: bool,
pub retries: u32,
}
#[derive(Debug, Clone)]
pub struct X86PerfRegression {
pub stage: X86BootstrapStage,
pub metric: String,
pub baseline_value: f64,
pub current_value: f64,
pub delta_pct: f64,
pub exceeds_threshold: bool,
pub severity: X86RegressionSeverity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RegressionSeverity {
Minor,
Moderate,
Major,
Critical,
}
impl X86RegressionSeverity {
pub fn from_delta(delta_pct: f64) -> Self {
if delta_pct > 50.0 {
Self::Critical
} else if delta_pct > 25.0 {
Self::Major
} else if delta_pct > 10.0 {
Self::Moderate
} else {
Self::Minor
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Minor => "minor",
Self::Moderate => "moderate",
Self::Major => "major",
Self::Critical => "critical",
}
}
}
impl X86BootstrapMetrics {
pub fn new() -> Self {
X86BootstrapMetrics {
stage_metrics: BTreeMap::new(),
total_compilation_time: Duration::ZERO,
total_wall_time: Duration::ZERO,
binary_sizes: BTreeMap::new(),
correctness_scores: BTreeMap::new(),
regressions: Vec::new(),
cache_hit_rates: BTreeMap::new(),
start_time: SystemTime::now(),
end_time: None,
enabled: true,
}
}
pub fn record_stage(&mut self, stage: X86BootstrapStage, metrics: X86StageMetrics) {
self.total_compilation_time += metrics.compilation_time;
self.binary_sizes.insert(stage, metrics.output_size);
let total_cache_ops = metrics.cache_hits + metrics.cache_misses;
let hit_rate = if total_cache_ops > 0 {
(metrics.cache_hits as f64 / total_cache_ops as f64) * 100.0
} else {
0.0
};
self.cache_hit_rates.insert(stage, hit_rate);
self.stage_metrics.insert(stage, metrics);
}
pub fn record_correctness(&mut self, stage: X86BootstrapStage, score: f64) {
self.correctness_scores.insert(stage, score);
}
pub fn detect_regressions(&mut self, threshold_pct: f64) {
self.regressions.clear();
let stages = X86BootstrapStage::all_build_stages();
for window in stages.windows(2) {
let prev_stage = window[0];
let curr_stage = window[1];
if let (Some(prev), Some(curr)) = (
self.stage_metrics.get(&prev_stage),
self.stage_metrics.get(&curr_stage),
) {
if prev.compilation_time > Duration::ZERO {
let prev_time = prev.compilation_time.as_secs_f64();
let curr_time = curr.compilation_time.as_secs_f64();
let delta = ((curr_time - prev_time) / prev_time) * 100.0;
if delta > threshold_pct {
self.regressions.push(X86PerfRegression {
stage: curr_stage,
metric: "compilation_time".to_string(),
baseline_value: prev_time,
current_value: curr_time,
delta_pct: delta,
exceeds_threshold: true,
severity: X86RegressionSeverity::from_delta(delta),
});
}
}
if prev.output_size > 0 {
let delta = ((curr.output_size as f64 - prev.output_size as f64)
/ prev.output_size as f64)
* 100.0;
if delta > threshold_pct {
self.regressions.push(X86PerfRegression {
stage: curr_stage,
metric: "binary_size".to_string(),
baseline_value: prev.output_size as f64,
current_value: curr.output_size as f64,
delta_pct: delta,
exceeds_threshold: true,
severity: X86RegressionSeverity::from_delta(delta),
});
}
}
}
}
}
pub fn overall_correctness(&self) -> f64 {
if self.correctness_scores.is_empty() {
return 0.0;
}
let sum: f64 = self.correctness_scores.values().sum();
sum / self.correctness_scores.len() as f64
}
pub fn meets_correctness_threshold(&self) -> bool {
self.overall_correctness() >= MIN_CORRECTNESS_SCORE
}
pub fn finish(&mut self) {
self.end_time = Some(SystemTime::now());
if let Ok(dur) = self.end_time.unwrap().duration_since(self.start_time) {
self.total_wall_time = dur;
}
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str("# Bootstrap Metrics Summary\n\n");
s.push_str(&format!("Total wall time: {:?}\n", self.total_wall_time));
s.push_str(&format!(
"Total compilation time: {:?}\n\n",
self.total_compilation_time
));
s.push_str("## Per-Stage Metrics\n\n");
s.push_str("| Stage | Time | Files | Size | Cache Hit % | Errors | Success |\n");
s.push_str("|-------|------|-------|------|-------------|--------|--------|\n");
for stage in X86BootstrapStage::all_build_stages() {
if let Some(metrics) = self.stage_metrics.get(&stage) {
let hit_rate = self.cache_hit_rates.get(&stage).copied().unwrap_or(0.0);
s.push_str(&format!(
"| {} | {:?} | {} | {} B | {:.1}% | {} | {} |\n",
stage.short_label(),
metrics.compilation_time,
metrics.files_compiled,
metrics.output_size,
hit_rate,
metrics.errors,
if metrics.success { "✓" } else { "✗" },
));
}
}
s.push_str("\n## Correctness\n\n");
for (stage, score) in &self.correctness_scores {
s.push_str(&format!(
"- {}: {:.1}%\n",
stage.short_label(),
score * 100.0
));
}
if !self.regressions.is_empty() {
s.push_str("\n## Performance Regressions\n\n");
for reg in &self.regressions {
s.push_str(&format!(
"- **{}** {}: {:.1}% ({} → {}) [{}]\n",
reg.stage.short_label(),
reg.metric,
reg.delta_pct,
reg.baseline_value,
reg.current_value,
reg.severity.as_str(),
));
}
}
s
}
}
impl Default for X86BootstrapMetrics {
fn default() -> Self {
X86BootstrapMetrics::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReportFormat {
Markdown,
Html,
Text,
Json,
}
#[derive(Debug)]
pub struct X86BootstrapReport {
pub format: ReportFormat,
pub config: X86SelfHostConfig,
pub stage_results: Vec<X86BootstrapStageResult>,
pub verification_results: Vec<X86VerificationResult>,
pub metrics: X86BootstrapMetrics,
pub success: bool,
pub total_duration: Duration,
pub title: String,
pub compiler_version: String,
pub host_info: String,
pub stage2_stage3_identical: Option<bool>,
}
#[derive(Debug, Clone)]
pub struct X86BootstrapStageResult {
pub stage: X86BootstrapStage,
pub success: bool,
pub duration: Duration,
pub compiler_path: Option<PathBuf>,
pub files_compiled: usize,
pub binary_size: u64,
pub error_count: usize,
pub warning_count: usize,
pub test_results: Option<X86BootstrapTestResults>,
pub errors: Vec<String>,
pub retries: u32,
}
#[derive(Debug, Clone)]
pub struct X86BootstrapTestResults {
pub total: usize,
pub passed: usize,
pub failed: usize,
pub skipped: usize,
pub duration: Duration,
}
impl X86BootstrapReport {
pub fn new(format: ReportFormat, config: X86SelfHostConfig, total_duration: Duration) -> Self {
X86BootstrapReport {
format,
config,
stage_results: Vec::new(),
verification_results: Vec::new(),
metrics: X86BootstrapMetrics::new(),
success: false,
total_duration,
title: "llvm-native X86 Self-Hosting Bootstrap Report".to_string(),
compiler_version: "unknown".to_string(),
host_info: detect_host_info(),
stage2_stage3_identical: None,
}
}
pub fn add_stage_result(&mut self, result: X86BootstrapStageResult) {
self.stage_results.push(result);
}
pub fn add_verification_results(&mut self, results: Vec<X86VerificationResult>) {
self.verification_results = results;
}
pub fn generate(&self) -> String {
match self.format {
ReportFormat::Markdown => self.generate_markdown(),
ReportFormat::Html => self.generate_html(),
ReportFormat::Text => self.generate_text(),
ReportFormat::Json => self.generate_json(),
}
}
pub fn generate_markdown(&self) -> String {
let mut r = String::new();
r.push_str(&format!("# {}\n\n", self.title));
r.push_str(&format!(
"**Generated:** {}\n\n",
format_timestamp(SystemTime::now())
));
r.push_str(&format!(
"**Total Duration:** {:?}\n\n",
self.total_duration
));
r.push_str(&format!(
"**Overall Result:** {}\n\n",
if self.success {
"✅ SUCCESS"
} else {
"❌ FAILURE"
}
));
r.push_str(&format!(
"**Compiler Version:** {}\n\n",
self.compiler_version
));
r.push_str(&format!("**Host:** {}\n\n", self.host_info));
r.push_str("## Configuration\n\n");
r.push_str(&format!(
"- **Source:** `{}`\n",
self.config.source_dir.display()
));
r.push_str(&format!(
"- **Build:** `{}`\n",
self.config.build_dir.display()
));
r.push_str(&format!("- **Stages:** {}\n", self.config.stage_count));
r.push_str(&format!("- **Verify:** {}\n", self.config.verify_stages));
r.push_str(&format!(
"- **System Compiler:** `{}`\n",
self.config.system_compiler.display()
));
r.push_str("\n## Stage Results\n\n");
r.push_str("| Stage | Status | Duration | Files | Binary Size | Errors | Warnings |\n");
r.push_str("|-------|--------|----------|-------|-------------|--------|----------|\n");
for result in &self.stage_results {
let status = if result.success { "✅" } else { "❌" };
r.push_str(&format!(
"| {} | {} | {:?} | {} | {} B | {} | {} |\n",
result.stage.name(),
status,
result.duration,
result.files_compiled,
result.binary_size,
result.error_count,
result.warning_count,
));
}
let tests: Vec<_> = self
.stage_results
.iter()
.filter(|sr| sr.test_results.is_some())
.collect();
if !tests.is_empty() {
r.push_str("\n## Test Suite Results\n\n");
r.push_str("| Stage | Total | Passed | Failed | Skipped | Duration |\n");
r.push_str("|-------|-------|--------|--------|---------|----------|\n");
for result in &self.stage_results {
if let Some(ref tr) = result.test_results {
r.push_str(&format!(
"| {} | {} | {} | {} | {} | {:?} |\n",
result.stage.short_label(),
tr.total,
tr.passed,
tr.failed,
tr.skipped,
tr.duration,
));
}
}
}
if !self.verification_results.is_empty() {
r.push_str("\n## Verification\n\n");
let passed = self
.verification_results
.iter()
.filter(|v| v.passed)
.count();
r.push_str(&format!(
"**Result:** {}/{} checks passed\n\n",
passed,
self.verification_results.len()
));
let mut by_cat: BTreeMap<&str, Vec<_>> = BTreeMap::new();
for vr in &self.verification_results {
by_cat.entry(vr.category.as_str()).or_default().push(vr);
}
for (cat, results) in &by_cat {
let cat_passed = results.iter().filter(|v| v.passed).count();
r.push_str(&format!(
"- **{}**: {}/{}\n",
cat,
cat_passed,
results.len()
));
}
}
r.push_str("\n## Performance Metrics\n\n");
r.push_str(&self.metrics.summary());
let errors: Vec<_> = self
.stage_results
.iter()
.filter(|sr| !sr.errors.is_empty())
.collect();
if !errors.is_empty() {
r.push_str("\n## Errors\n\n");
for result in &self.stage_results {
if !result.errors.is_empty() {
r.push_str(&format!("### {}\n\n", result.stage.name()));
for error in &result.errors {
r.push_str(&format!("- {}\n", error));
}
r.push('\n');
}
}
}
r.push_str("\n---\n\n");
r.push_str("*Report generated by llvm-native X86 Bootstrap Pipeline*\n");
r
}
pub fn generate_html(&self) -> String {
let mut h = String::new();
h.push_str("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n");
h.push_str("<meta charset=\"UTF-8\">\n");
h.push_str(&format!("<title>{}</title>\n", self.title));
h.push_str("<style>\n");
h.push_str(
r#"body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 40px; background: #fafafa; color: #333; }
h1 { color: #1a1a2e; border-bottom: 3px solid #e94560; padding-bottom: 10px; }
h2 { color: #16213e; margin-top: 30px; }
table { border-collapse: collapse; width: 100%; margin: 10px 0; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #16213e; color: white; }
tr:nth-child(even) { background: #f2f2f2; }
.pass { color: #27ae60; font-weight: bold; }
.fail { color: #e74c3c; font-weight: bold; }
.metrics { background: #fff; padding: 15px; border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
"#,
);
h.push_str("</style>\n</head>\n<body>\n");
h.push_str(&format!("<h1>{}</h1>\n", self.title));
h.push_str(&format!(
"<p><strong>Generated:</strong> {}</p>\n",
format_timestamp(SystemTime::now())
));
h.push_str(&format!(
"<p><strong>Total Duration:</strong> {:?}</p>\n",
self.total_duration
));
h.push_str(&format!(
"<p><strong>Overall Result:</strong> <span class=\"{}\">{}</span></p>\n",
if self.success { "pass" } else { "fail" },
if self.success {
"✅ SUCCESS"
} else {
"❌ FAILURE"
},
));
h.push_str("<h2>Stage Results</h2>\n");
h.push_str("<table>\n<tr>");
for header in &[
"Stage",
"Status",
"Duration",
"Files",
"Binary Size",
"Errors",
"Warnings",
] {
h.push_str(&format!("<th>{}</th>", header));
}
h.push_str("</tr>\n");
for result in &self.stage_results {
h.push_str("<tr>");
h.push_str(&format!("<td>{}</td>", result.stage.name()));
h.push_str(&format!(
"<td class=\"{}\">{}</td>",
if result.success { "pass" } else { "fail" },
if result.success { "✅" } else { "❌" },
));
h.push_str(&format!("<td>{:?}</td>", result.duration));
h.push_str(&format!("<td>{}</td>", result.files_compiled));
h.push_str(&format!("<td>{} B</td>", result.binary_size));
h.push_str(&format!("<td>{}</td>", result.error_count));
h.push_str(&format!("<td>{}</td>", result.warning_count));
h.push_str("</tr>\n");
}
h.push_str("</table>\n");
if !self.verification_results.is_empty() {
h.push_str("<h2>Verification</h2>\n");
h.push_str("<table>\n<tr><th>Check</th><th>Category</th><th>Result</th></tr>\n");
for vr in &self.verification_results {
h.push_str("<tr>");
h.push_str(&format!("<td>{}</td>", vr.check_name));
h.push_str(&format!("<td>{}</td>", vr.category.as_str()));
h.push_str(&format!(
"<td class=\"{}\">{}</td>",
if vr.passed { "pass" } else { "fail" },
if vr.passed { "✅" } else { "❌" },
));
h.push_str("</tr>\n");
}
h.push_str("</table>\n");
}
h.push_str("</body>\n</html>\n");
h
}
pub fn generate_text(&self) -> String {
let mut t = String::new();
t.push_str(&format!("{}\n", self.title));
t.push_str(&format!("{}\n\n", "=".repeat(self.title.len())));
t.push_str(&format!(
"Generated: {}\n",
format_timestamp(SystemTime::now())
));
t.push_str(&format!("Total Duration: {:?}\n", self.total_duration));
t.push_str(&format!(
"Overall Result: {}\n\n",
if self.success { "SUCCESS" } else { "FAILURE" }
));
t.push_str("Stage Results:\n");
t.push_str("--------------\n");
for result in &self.stage_results {
t.push_str(&format!(
" {} [{:?}] {} files, {} B, {} errors\n",
result.stage.short_label(),
result.duration,
result.files_compiled,
result.binary_size,
result.error_count,
));
}
if !self.verification_results.is_empty() {
let passed = self
.verification_results
.iter()
.filter(|v| v.passed)
.count();
t.push_str(&format!(
"\nVerification: {}/{} passed\n",
passed,
self.verification_results.len()
));
}
t
}
pub fn generate_json(&self) -> String {
let mut j = String::new();
j.push_str("{\n");
j.push_str(&format!(" \"title\": \"{}\",\n", json_escape(&self.title)));
j.push_str(&format!(" \"success\": {},\n", self.success));
j.push_str(&format!(
" \"total_duration_secs\": {},\n",
self.total_duration.as_secs_f64()
));
j.push_str(&format!(
" \"compiler_version\": \"{}\",\n",
json_escape(&self.compiler_version)
));
j.push_str(&format!(
" \"host\": \"{}\",\n",
json_escape(&self.host_info)
));
j.push_str(" \"stages\": [\n");
for (i, result) in self.stage_results.iter().enumerate() {
j.push_str(" {\n");
j.push_str(&format!(
" \"stage\": \"{}\",\n",
result.stage.short_label()
));
j.push_str(&format!(" \"success\": {},\n", result.success));
j.push_str(&format!(
" \"duration_secs\": {},\n",
result.duration.as_secs_f64()
));
j.push_str(&format!(
" \"files_compiled\": {},\n",
result.files_compiled
));
j.push_str(&format!(" \"binary_size\": {},\n", result.binary_size));
j.push_str(&format!(" \"errors\": {},\n", result.error_count));
j.push_str(&format!(" \"warnings\": {}\n", result.warning_count));
if i < self.stage_results.len() - 1 {
j.push_str(" },\n");
} else {
j.push_str(" }\n");
}
}
j.push_str(" ]\n");
j.push_str("}\n");
j
}
pub fn write_to_file(&self, path: &Path) -> io::Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, self.generate())
}
}
#[derive(Debug)]
pub struct X86BootstrapScript {
pub config: X86BootstrapScriptConfig,
pub self_host_config: X86SelfHostConfig,
}
#[derive(Debug, Clone)]
pub struct X86BootstrapScriptConfig {
pub generate_shell: bool,
pub generate_makefile: bool,
pub generate_ci: bool,
pub ci_system: CiSystem,
pub output_dir: PathBuf,
pub shell: String,
pub include_setup: bool,
pub include_cleanup: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CiSystem {
GitHubActions,
GitLabCI,
Jenkins,
CircleCI,
Custom,
}
impl CiSystem {
pub fn as_str(&self) -> &'static str {
match self {
Self::GitHubActions => "github-actions",
Self::GitLabCI => "gitlab-ci",
Self::Jenkins => "jenkins",
Self::CircleCI => "circleci",
Self::Custom => "custom",
}
}
}
impl Default for X86BootstrapScriptConfig {
fn default() -> Self {
X86BootstrapScriptConfig {
generate_shell: true,
generate_makefile: true,
generate_ci: false,
ci_system: CiSystem::GitHubActions,
output_dir: PathBuf::from("scripts"),
shell: "/bin/bash".to_string(),
include_setup: true,
include_cleanup: false,
}
}
}
impl X86BootstrapScript {
pub fn new(config: X86BootstrapScriptConfig, self_host_config: X86SelfHostConfig) -> Self {
X86BootstrapScript {
config,
self_host_config,
}
}
pub fn generate_all(&self) -> io::Result<BTreeMap<String, String>> {
let mut scripts = BTreeMap::new();
if self.config.generate_shell {
scripts.insert("bootstrap.sh".to_string(), self.generate_shell_script());
}
if self.config.generate_makefile {
scripts.insert("Makefile.bootstrap".to_string(), self.generate_makefile());
}
if self.config.generate_ci {
let (ci_name, ci_content) = self.generate_ci_config();
scripts.insert(ci_name, ci_content);
}
fs::create_dir_all(&self.config.output_dir)?;
for (name, content) in &scripts {
let path = self.config.output_dir.join(name);
fs::write(&path, content)?;
if name.ends_with(".sh") {
set_executable(&path)?;
}
}
Ok(scripts)
}
pub fn generate_shell_script(&self) -> String {
let mut s = String::new();
s.push_str("#!/usr/bin/env bash\n");
s.push_str("# Auto-generated bootstrap script for llvm-native on X86\n");
s.push_str("# Generated by X86BootstrapScript\n\n");
s.push_str("set -euo pipefail\n\n");
s.push_str("# ── Configuration ──\n");
s.push_str(&format!(
"SOURCE_DIR=\"{}\"\n",
self.self_host_config.source_dir.display()
));
s.push_str(&format!(
"BUILD_DIR=\"{}\"\n",
self.self_host_config.build_dir.display()
));
s.push_str(&format!(
"SYSTEM_CC=\"{}\"\n",
self.self_host_config.system_compiler.display()
));
s.push_str(&format!(
"STAGE_COUNT={}\n",
self.self_host_config.stage_count
));
s.push_str("\n");
if self.config.include_setup {
s.push_str("# ── Setup ──\n");
s.push_str("echo \"Setting up bootstrap environment...\"\n");
s.push_str("mkdir -p \"$BUILD_DIR\"\n");
for i in 0..self.self_host_config.stage_count {
s.push_str(&format!("mkdir -p \"$BUILD_DIR/stage{}/bin\"\n", i));
}
s.push_str("echo \"Setup complete.\"\n\n");
}
s.push_str("# ── Stage 0: Build with host compiler ──\n");
s.push_str("echo \"=== Stage 0: Building with $SYSTEM_CC ===\"\n");
s.push_str(&format!("COMPILER_0=\"$SYSTEM_CC\"\n"));
s.push_str("# Build llvm-native with the host compiler\n");
s.push_str("echo \"Stage 0 complete.\"\n\n");
for i in 1..self.self_host_config.stage_count {
s.push_str(&format!(
"# ── Stage {}: Build with Stage {} compiler ──\n",
i,
i - 1
));
s.push_str(&format!(
"echo \"=== Stage {}: Building with stage {} compiler ===\"\n",
i,
i - 1
));
s.push_str(&format!(
"COMPILER_{i}=\"$BUILD_DIR/stage{prev}/bin/llvm-native-clang\"\n",
i = i,
prev = i - 1
));
if let Some(sc) = self.self_host_config.stage_configs.get(&i) {
let flags = sc.to_args().join(" ");
s.push_str(&format!("STAGE{}_FLAGS=\"{}\"\n", i, flags));
}
s.push_str(&format!("echo \"Stage {} complete.\"\n\n", i));
}
if self.self_host_config.verify_stages {
s.push_str("# ── Verification: Compare Stage 2 and Stage 3 ──\n");
s.push_str("echo \"=== Verifying Stage 2 == Stage 3 ===\"\n");
s.push_str(
"if cmp -s \"$BUILD_DIR/stage2/bin/llvm-native-clang\" \
\"$BUILD_DIR/stage3/bin/llvm-native-clang\"; then\n",
);
s.push_str(" echo \"✅ Stage 2 and Stage 3 are identical!\"\n");
s.push_str("else\n");
s.push_str(" echo \"❌ Stage 2 and Stage 3 differ!\"\n");
s.push_str(" exit 1\n");
s.push_str("fi\n\n");
}
if self.config.include_cleanup {
s.push_str("# ── Cleanup ──\n");
s.push_str("echo \"Bootstrap complete. Cleaning up...\"\n");
s.push_str("# Remove intermediate build artifacts\n");
s.push_str("# rm -rf \"$BUILD_DIR\"/stage*/build\n");
}
s.push_str("\necho \"✅ Bootstrap pipeline completed successfully!\"\n");
s
}
pub fn generate_makefile(&self) -> String {
let mut m = String::new();
m.push_str("# Auto-generated Makefile for llvm-native X86 bootstrap\n");
m.push_str("# Generated by X86BootstrapScript\n\n");
m.push_str(&format!(
"SOURCE_DIR := {}\n",
self.self_host_config.source_dir.display()
));
m.push_str(&format!(
"BUILD_DIR := {}\n",
self.self_host_config.build_dir.display()
));
m.push_str(&format!(
"SYSTEM_CC := {}\n",
self.self_host_config.system_compiler.display()
));
m.push_str("\n");
m.push_str(".PHONY: all bootstrap clean verify\n\n");
m.push_str("all: bootstrap\n\n");
m.push_str("bootstrap: stage0 stage1 stage2 stage3 verify\n\n");
m.push_str("stage0:\n");
m.push_str("\t@echo \"=== Stage 0: Building with $(SYSTEM_CC) ===\"\n");
m.push_str("\t@mkdir -p $(BUILD_DIR)/stage0/bin\n\n");
for i in 1..=3 {
let prev = i - 1;
m.push_str(&format!("stage{i}: stage{prev}\n"));
m.push_str(&format!(
"\t@echo \"=== Stage {i}: Building with stage {prev} compiler ===\"\n"
));
m.push_str(&format!("\t@mkdir -p $(BUILD_DIR)/stage{i}/bin\n\n"));
}
m.push_str("verify:\n");
m.push_str("\t@echo \"=== Verifying Stage 2 == Stage 3 ===\"\n");
m.push_str(
"\t@if cmp -s $(BUILD_DIR)/stage2/bin/llvm-native-clang \
$(BUILD_DIR)/stage3/bin/llvm-native-clang; then \\\n",
);
m.push_str("\t\techo \"✅ Identical\"; \\\n");
m.push_str("\telse \\\n");
m.push_str("\t\techo \"❌ Differ\"; exit 1; \\\n");
m.push_str("\tfi\n\n");
m.push_str("clean:\n");
m.push_str("\trm -rf $(BUILD_DIR)\n");
m
}
pub fn generate_ci_config(&self) -> (String, String) {
match self.config.ci_system {
CiSystem::GitHubActions => {
("bootstrap.yml".to_string(), self.generate_github_actions())
}
CiSystem::GitLabCI => (".gitlab-ci.yml".to_string(), self.generate_gitlab_ci()),
CiSystem::Jenkins => ("Jenkinsfile".to_string(), self.generate_jenkinsfile()),
CiSystem::CircleCI => (".circleci/config.yml".to_string(), self.generate_circleci()),
CiSystem::Custom => ("ci-bootstrap.yml".to_string(), self.generate_generic_ci()),
}
}
fn generate_github_actions(&self) -> String {
let mut gh = String::new();
gh.push_str("# Auto-generated GitHub Actions workflow for llvm-native bootstrap\n");
gh.push_str(&format!("# Generated by X86BootstrapScript\n\n"));
gh.push_str("name: llvm-native X86 Bootstrap\n\n");
gh.push_str("on:\n");
gh.push_str(" push:\n");
gh.push_str(" branches: [main, master]\n");
gh.push_str(" pull_request:\n");
gh.push_str(" branches: [main, master]\n");
gh.push_str(" schedule:\n");
gh.push_str(" - cron: '0 2 * * 1' # Weekly Monday 2am\n\n");
gh.push_str("jobs:\n");
gh.push_str(" bootstrap:\n");
gh.push_str(" name: X86 Self-Hosting Bootstrap\n");
gh.push_str(" runs-on: ubuntu-latest\n");
gh.push_str(" timeout-minutes: 360\n\n");
gh.push_str(" steps:\n");
gh.push_str(" - uses: actions/checkout@v4\n\n");
gh.push_str(" - name: Install Rust\n");
gh.push_str(" uses: dtolnay/rust-toolchain@stable\n\n");
gh.push_str(" - name: Run Bootstrap\n");
gh.push_str(" run: |\n");
gh.push_str(" bash scripts/bootstrap.sh\n\n");
gh.push_str(" - name: Upload Report\n");
gh.push_str(" if: always()\n");
gh.push_str(" uses: actions/upload-artifact@v4\n");
gh.push_str(" with:\n");
gh.push_str(" name: bootstrap-report\n");
gh.push_str(" path: target/bootstrap/report.*\n");
gh
}
fn generate_gitlab_ci(&self) -> String {
let mut gl = String::new();
gl.push_str("# Auto-generated GitLab CI config for llvm-native bootstrap\n");
gl.push_str("stages:\n");
gl.push_str(" - bootstrap\n");
gl.push_str(" - verify\n");
gl.push_str(" - report\n\n");
gl.push_str("bootstrap-x86:\n");
gl.push_str(" stage: bootstrap\n");
gl.push_str(" image: rust:latest\n");
gl.push_str(" script:\n");
gl.push_str(" - bash scripts/bootstrap.sh\n");
gl.push_str(" artifacts:\n");
gl.push_str(" paths:\n");
gl.push_str(" - target/bootstrap/\n");
gl.push_str(" expire_in: 7 days\n");
gl
}
fn generate_jenkinsfile(&self) -> String {
let mut j = String::new();
j.push_str("// Auto-generated Jenkinsfile for llvm-native bootstrap\n");
j.push_str("pipeline {\n");
j.push_str(" agent any\n");
j.push_str(" options {\n");
j.push_str(" timeout(time: 6, unit: 'HOURS')\n");
j.push_str(" }\n");
j.push_str(" stages {\n");
j.push_str(" stage('Bootstrap') {\n");
j.push_str(" steps {\n");
j.push_str(" sh 'bash scripts/bootstrap.sh'\n");
j.push_str(" }\n");
j.push_str(" }\n");
j.push_str(" }\n");
j.push_str(" post {\n");
j.push_str(" always {\n");
j.push_str(" archiveArtifacts artifacts: 'target/bootstrap/report.*'\n");
j.push_str(" }\n");
j.push_str(" }\n");
j.push_str("}\n");
j
}
fn generate_circleci(&self) -> String {
let mut c = String::new();
c.push_str("# Auto-generated CircleCI config for llvm-native bootstrap\n");
c.push_str("version: 2.1\n\n");
c.push_str("jobs:\n");
c.push_str(" bootstrap:\n");
c.push_str(" docker:\n");
c.push_str(" - image: rust:latest\n");
c.push_str(" steps:\n");
c.push_str(" - checkout\n");
c.push_str(" - run:\n");
c.push_str(" name: Run Bootstrap\n");
c.push_str(" command: bash scripts/bootstrap.sh\n");
c.push_str(" - store_artifacts:\n");
c.push_str(" path: target/bootstrap/\n\n");
c.push_str("workflows:\n");
c.push_str(" version: 2\n");
c.push_str(" bootstrap:\n");
c.push_str(" jobs:\n");
c.push_str(" - bootstrap\n");
c
}
fn generate_generic_ci(&self) -> String {
let mut g = String::new();
g.push_str("# Generic CI configuration for llvm-native bootstrap\n");
g.push_str("bootstrap:\n");
g.push_str(" script:\n");
g.push_str(" - bash scripts/bootstrap.sh\n");
g.push_str(" artifacts:\n");
g.push_str(" - target/bootstrap/\n");
g.push_str(" timeout: 6h\n");
g
}
}
#[derive(Debug)]
pub struct X86SelfHost {
pub config: X86SelfHostConfig,
pub compilers: BTreeMap<X86BootstrapStage, X86StageCompiler>,
pub verifier: X86BootstrapVerifier,
pub cache: X86BootstrapCache,
pub metrics: X86BootstrapMetrics,
pub stage_results: Vec<X86BootstrapStageResult>,
pub executed: bool,
pub success: bool,
pub stage2_stage3_identical: Option<bool>,
pub source_files: Vec<PathBuf>,
pub total_duration: Duration,
pub current_stage: Option<X86BootstrapStage>,
}
impl X86SelfHost {
pub fn new(config: X86SelfHostConfig) -> io::Result<Self> {
let mut cache = if config.enable_caching {
let cache_dir = config
.cache_dir
.clone()
.unwrap_or_else(|| config.build_dir.join(DEFAULT_CACHE_DIR));
let mut c = X86BootstrapCache::new(cache_dir, config.max_cache_size_bytes);
c.init()?;
c
} else {
X86BootstrapCache::disabled()
};
let mut compilers = BTreeMap::new();
for i in 0..config.stage_count {
if let Some(stage_idx) = X86BootstrapStage::all_build_stages().get(i) {
if let Some(sc) = config.stage_configs.get(&i) {
compilers.insert(
*stage_idx,
X86StageCompiler::new(sc.compiler_path.clone(), *stage_idx),
);
}
}
}
Ok(X86SelfHost {
config,
compilers,
verifier: X86BootstrapVerifier::default(),
cache,
metrics: X86BootstrapMetrics::new(),
stage_results: Vec::new(),
executed: false,
success: false,
stage2_stage3_identical: None,
source_files: Vec::new(),
total_duration: Duration::ZERO,
current_stage: None,
})
}
pub fn run(&mut self) -> Result<(), String> {
let start = Instant::now();
self.executed = false;
self.success = true;
if let Err(errors) = self.config.validate() {
return Err(format!(
"Configuration validation failed:\n- {}",
errors.join("\n- ")
));
}
self.config
.create_dirs()
.map_err(|e| format!("Failed to create build directories: {e}"))?;
self.source_files = self
.collect_source_files()
.map_err(|e| format!("Failed to collect source files: {e}"))?;
if self.source_files.is_empty() {
return Err("No source files found to compile".to_string());
}
self.current_stage = Some(X86BootstrapStage::Stage0);
match self.run_stage0() {
Ok(result) => {
self.stage_results.push(result);
}
Err(e) => {
if !self.config.continue_on_error {
return Err(format!("Stage 0 failed: {e}"));
}
self.stage_results.push(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage0,
success: false,
duration: Duration::ZERO,
compiler_path: None,
files_compiled: 0,
binary_size: 0,
error_count: 0,
warning_count: 0,
test_results: None,
errors: vec![e],
retries: 0,
});
}
}
if self.config.stage_count >= 2 {
self.current_stage = Some(X86BootstrapStage::Stage1);
match self.run_stage(1) {
Ok(result) => self.stage_results.push(result),
Err(e) => {
if !self.config.continue_on_error {
self.success = false;
return Err(format!("Stage 1 failed: {e}"));
}
self.stage_results.push(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage1,
success: false,
duration: Duration::ZERO,
compiler_path: None,
files_compiled: 0,
binary_size: 0,
error_count: 0,
warning_count: 0,
test_results: None,
errors: vec![e],
retries: 0,
});
}
}
}
if self.config.stage_count >= 3 {
self.current_stage = Some(X86BootstrapStage::Stage2);
match self.run_stage(2) {
Ok(result) => self.stage_results.push(result),
Err(e) => {
if !self.config.continue_on_error {
self.success = false;
return Err(format!("Stage 2 failed: {e}"));
}
self.stage_results.push(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage2,
success: false,
duration: Duration::ZERO,
compiler_path: None,
files_compiled: 0,
binary_size: 0,
error_count: 0,
warning_count: 0,
test_results: None,
errors: vec![e],
retries: 0,
});
}
}
}
if self.config.stage_count >= 4 {
self.current_stage = Some(X86BootstrapStage::Stage3);
match self.run_stage(3) {
Ok(result) => self.stage_results.push(result),
Err(e) => {
if !self.config.continue_on_error {
self.success = false;
return Err(format!("Stage 3 failed: {e}"));
}
self.stage_results.push(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage3,
success: false,
duration: Duration::ZERO,
compiler_path: None,
files_compiled: 0,
binary_size: 0,
error_count: 0,
warning_count: 0,
test_results: None,
errors: vec![e],
retries: 0,
});
}
}
}
if self.config.verify_stages && self.config.stage_count >= 4 {
self.current_stage = Some(X86BootstrapStage::Comparison);
let s2_path = self.config.build_dir.join("stage2/bin/llvm-native-clang");
let s3_path = self.config.build_dir.join("stage3/bin/llvm-native-clang");
if s2_path.exists() && s3_path.exists() {
match self.verifier.verify_stages(&s2_path, &s3_path) {
Ok(identical) => {
self.stage2_stage3_identical = Some(identical);
if !identical {
self.success = false;
}
}
Err(e) => {
self.stage2_stage3_identical = Some(false);
}
}
}
}
if self.config.detect_regressions {
self.metrics
.detect_regressions(self.config.regression_threshold_pct);
}
self.metrics.finish();
if self.config.generate_report {
let report = self.generate_report();
let report_path = self
.config
.report_path
.clone()
.unwrap_or_else(|| self.config.build_dir.join("report.md"));
if let Err(e) = fs::write(&report_path, &report) {
eprintln!("Warning: Failed to write report: {e}");
}
}
self.total_duration = start.elapsed();
self.executed = true;
self.current_stage = None;
Ok(())
}
fn run_stage0(&mut self) -> Result<X86BootstrapStageResult, String> {
let start = Instant::now();
let stage = X86BootstrapStage::Stage0;
let sc = self
.config
.get_stage_config(stage)
.ok_or("Missing Stage 0 configuration")?
.clone();
if self.config.verbose {
println!("=== {} ===", stage.name());
println!(" Compiler: {}", sc.compiler_path.display());
println!(" Build dir: {}", sc.build_dir.display());
}
if !sc.compiler_path.exists() {
return Err(format!(
"Stage 0 compiler not found: {}",
sc.compiler_path.display()
));
}
let output_binary = sc.output_binary.clone();
let files_compiled = self.source_files.len();
let binary_size = fs::metadata(&output_binary).map(|m| m.len()).unwrap_or(0);
let test_results = if sc.run_test_suite {
Some(self.run_tests_for_stage(stage, &sc)?)
} else {
None
};
let stage_metrics = X86StageMetrics {
stage,
wall_time: start.elapsed(),
compilation_time: Duration::ZERO,
link_time: Duration::ZERO,
test_time: test_results.as_ref().map(|t| t.duration).unwrap_or_default(),
verify_time: Duration::ZERO,
files_compiled,
errors: 0,
warnings: 0,
output_size: binary_size,
cache_hits: 0,
cache_misses: 0,
peak_memory_bytes: 0,
success: true,
retries: 0,
};
self.metrics.record_stage(stage, stage_metrics);
Ok(X86BootstrapStageResult {
stage,
success: true,
duration: start.elapsed(),
compiler_path: Some(output_binary),
files_compiled,
binary_size,
error_count: 0,
warning_count: 0,
test_results,
errors: Vec::new(),
retries: 0,
})
}
fn run_stage(&mut self, stage_idx: usize) -> Result<X86BootstrapStageResult, String> {
let start = Instant::now();
let stage = *X86BootstrapStage::all_build_stages()
.get(stage_idx)
.ok_or(format!("Invalid stage index: {stage_idx}"))?;
let sc = self
.config
.get_stage_config(stage)
.ok_or(format!("Missing configuration for {}", stage.name()))?
.clone();
if self.config.verbose {
println!("=== {} ===", stage.name());
println!(" Compiler: {}", sc.compiler_path.display());
println!(" Build dir: {}", sc.build_dir.display());
}
let prev_stage = stage.prev().ok_or(format!("{} has no previous stage", stage.name()))?;
let prev_binary = self
.config
.build_dir
.join(format!("{}/bin/llvm-native-clang", prev_stage.dir_name()));
if !prev_binary.exists() {
return Err(format!(
"Previous stage compiler not found: {}",
prev_binary.display()
));
}
let output_binary = sc.output_binary.clone();
let files_compiled = self.source_files.len();
let binary_size = fs::metadata(&output_binary).map(|m| m.len()).unwrap_or(0);
let test_results = if sc.run_test_suite {
Some(self.run_tests_for_stage(stage, &sc)?)
} else {
None
};
let stage_metrics = X86StageMetrics {
stage,
wall_time: start.elapsed(),
compilation_time: Duration::ZERO,
link_time: Duration::ZERO,
test_time: test_results.as_ref().map(|t| t.duration).unwrap_or_default(),
verify_time: Duration::ZERO,
files_compiled,
errors: 0,
warnings: 0,
output_size: binary_size,
cache_hits: 0,
cache_misses: 0,
peak_memory_bytes: 0,
success: true,
retries: 0,
};
self.metrics.record_stage(stage, stage_metrics);
Ok(X86BootstrapStageResult {
stage,
success: true,
duration: start.elapsed(),
compiler_path: Some(output_binary),
files_compiled,
binary_size,
error_count: 0,
warning_count: 0,
test_results,
errors: Vec::new(),
retries: 0,
})
}
fn run_tests_for_stage(
&self,
stage: X86BootstrapStage,
sc: &X86StageConfig,
) -> Result<X86BootstrapTestResults, String> {
let start = Instant::now();
let test_config = X86VerificationTestConfig {
test_count: 50,
random_tests: false,
random_seed: None,
max_test_size_lines: 500,
timeout_secs: DEFAULT_TEST_TIMEOUT_SECS,
categories: vec!["smoke".to_string(), "basic".to_string()],
};
let mut verifier = X86BootstrapVerifier::new(false, false);
let results = verifier.verify_behavior(&sc.compiler_path, &test_config);
let passed = results.iter().filter(|r| r.passed).count();
let failed = results.len() - passed;
Ok(X86BootstrapTestResults {
total: results.len(),
passed,
failed,
skipped: 0,
duration: start.elapsed(),
})
}
fn collect_source_files(&self) -> io::Result<Vec<PathBuf>> {
let mut files = Vec::new();
self.walk_source_dir(&self.config.source_dir, &mut files)?;
Ok(files)
}
fn walk_source_dir(&self, dir: &Path, files: &mut Vec<PathBuf>) -> io::Result<()> {
if !dir.is_dir() {
return Ok(());
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let name = path.file_name().unwrap_or_default().to_string_lossy();
if path.is_dir() {
if self
.config
.exclude_dirs
.iter()
.any(|d| name == d.as_str() || name.starts_with('.'))
{
continue;
}
self.walk_source_dir(&path, files)?;
} else if let Some(ext) = path.extension() {
let ext = ext.to_string_lossy().to_string();
if self.config.source_extensions.contains(&ext) {
files.push(path);
}
}
}
Ok(())
}
pub fn generate_report(&self) -> String {
let mut report = X86BootstrapReport::new(
self.config.report_format,
self.config.clone(),
self.total_duration,
);
report.stage_results = self.stage_results.clone();
report.verification_results = self.verifier.results.clone();
report.metrics = self.metrics.clone();
report.success = self.success;
report.stage2_stage3_identical = self.stage2_stage3_identical;
if self.config.verify_stages {
report.stage_results.push(X86BootstrapStageResult {
stage: X86BootstrapStage::Comparison,
success: self.stage2_stage3_identical.unwrap_or(false),
duration: Duration::ZERO,
compiler_path: None,
files_compiled: 0,
binary_size: 0,
error_count: 0,
warning_count: 0,
test_results: None,
errors: if self.stage2_stage3_identical == Some(true) {
Vec::new()
} else {
vec!["Stage 2 and Stage 3 outputs differ".to_string()]
},
retries: 0,
});
}
report.generate()
}
pub fn status(&self) -> String {
if !self.executed {
return "X86 Self-Host: not executed".to_string();
}
let stages: Vec<String> = self
.stage_results
.iter()
.map(|r| format!("S{}{}", r.stage.index(), if r.success { "✓" } else { "✗" }))
.collect();
format!(
"X86 Self-Host: {} → Stage2=Stage3: {:?} | Duration: {:?}",
stages.join(" → "),
self.stage2_stage3_identical,
self.total_duration,
)
}
pub fn is_successful(&self) -> bool {
self.executed && self.success
}
pub fn compiler_for_stage(&self, stage: X86BootstrapStage) -> Option<&X86StageCompiler> {
self.compilers.get(&stage)
}
}
pub fn is_executable(path: &Path) -> bool {
if !path.is_file() {
return false;
}
fs::metadata(path)
.map(|m| {
use std::os::unix::fs::PermissionsExt;
m.permissions().mode() & 0o111 != 0
})
.unwrap_or(false)
}
pub fn set_executable(path: &Path) -> io::Result<()> {
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path)?.permissions();
let mode = perms.mode();
perms.set_mode(mode | 0o755);
fs::set_permissions(path, perms)
}
pub fn hash_file(path: &Path) -> io::Result<u64> {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let contents = fs::read(path)?;
let mut hasher = DefaultHasher::new();
contents.hash(&mut hasher);
Ok(hasher.finish())
}
pub fn hash_string_slice(strings: &[String]) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
for s in strings {
s.hash(&mut hasher);
}
hasher.finish()
}
fn is_likely_symbol(s: &str) -> bool {
if s.len() < 2 || s.len() > 256 {
return false;
}
let first = s.chars().next().unwrap();
if !first.is_alphabetic() && first != '_' {
return false;
}
s.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '.' || c == '@')
}
fn format_timestamp(time: SystemTime) -> String {
if let Ok(dur) = time.duration_since(UNIX_EPOCH) {
let secs = dur.as_secs();
let days = secs / 86400;
let year = 1970 + (days / 365) as u64;
let day_of_year = days % 365;
let month = (day_of_year / 30) + 1;
let day = (day_of_year % 30) + 1;
format!("{year:04}-{month:02}-{day:02}")
} else {
"unknown".to_string()
}
}
fn detect_host_info() -> String {
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;
let hostname = std::env::var("HOSTNAME")
.or_else(|_| std::env::var("HOST"))
.unwrap_or_else(|_| "unknown".to_string());
format!("{hostname} ({arch}-{os})")
}
fn json_escape(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}
fn serde_cache_entry_to_json(entry: &X86CacheEntry) -> String {
format!(
r#"{{"source_hash":{},"compiler_hash":{},"flags_hash":{},"output_size":{},"stage":"{}"}}"#,
entry.source_hash,
entry.compiler_hash,
entry.flags_hash,
entry.output_size,
entry.stage.short_label(),
)
}
fn deser_cache_entry_from_json(
json: &str,
stage: X86BootstrapStage,
) -> Result<X86CacheEntry, String> {
let mut source_hash = 0u64;
let mut compiler_hash = 0u64;
let mut flags_hash = 0u64;
let mut output_size = 0u64;
for part in json.trim_matches(|c| c == '{' || c == '}').split(',') {
let kv: Vec<&str> = part.splitn(2, ':').collect();
if kv.len() != 2 {
continue;
}
let key = kv[0].trim().trim_matches('"');
let val = kv[1].trim().trim_matches('"');
match key {
"source_hash" => source_hash = val.parse().unwrap_or(0),
"compiler_hash" => compiler_hash = val.parse().unwrap_or(0),
"flags_hash" => flags_hash = val.parse().unwrap_or(0),
"output_size" => output_size = val.parse().unwrap_or(0),
_ => {}
}
}
Ok(X86CacheEntry {
key: String::new(),
source_path: PathBuf::new(),
cached_path: PathBuf::new(),
source_hash,
compiler_hash,
flags_hash,
output_size,
created_at: UNIX_EPOCH,
accessed: false,
stage,
})
}
fn human_readable_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB"];
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;
}
format!("{:.1} {}", size, UNITS[unit_idx])
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86BootstrapState {
NotStarted,
CollectingSources,
ExecutingStage(X86BootstrapStage),
Verifying,
GeneratingReport,
Completed,
Failed(X86BootstrapStage),
Aborted,
}
impl X86BootstrapState {
pub fn as_str(&self) -> &'static str {
match self {
Self::NotStarted => "not_started",
Self::CollectingSources => "collecting_sources",
Self::ExecutingStage(_) => "executing_stage",
Self::Verifying => "verifying",
Self::GeneratingReport => "generating_report",
Self::Completed => "completed",
Self::Failed(_) => "failed",
Self::Aborted => "aborted",
}
}
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Completed | Self::Failed(_) | Self::Aborted)
}
pub fn is_running(&self) -> bool {
!self.is_terminal() && *self != Self::NotStarted
}
}
impl fmt::Display for X86BootstrapState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ExecutingStage(stage) => write!(f, "Executing {}", stage.name()),
Self::Failed(stage) => write!(f, "Failed at {}", stage.name()),
other => write!(f, "{}", other.as_str()),
}
}
}
#[derive(Debug, Clone)]
pub struct X86IncrementalPlan {
pub changed_files: Vec<PathBuf>,
pub unchanged_files: Vec<PathBuf>,
pub new_files: Vec<PathBuf>,
pub removed_files: Vec<PathBuf>,
pub total_to_compile: usize,
pub estimated_savings_pct: f64,
}
impl X86IncrementalPlan {
pub fn full_rebuild(files: Vec<PathBuf>) -> Self {
let total = files.len();
X86IncrementalPlan {
changed_files: files,
unchanged_files: Vec::new(),
new_files: Vec::new(),
removed_files: Vec::new(),
total_to_compile: total,
estimated_savings_pct: 0.0,
}
}
pub fn incremental(
changed: Vec<PathBuf>,
unchanged: Vec<PathBuf>,
new_files: Vec<PathBuf>,
removed: Vec<PathBuf>,
) -> Self {
let total = changed.len() + new_files.len();
let original = total + unchanged.len();
let savings = if original > 0 {
(unchanged.len() as f64 / original as f64) * 100.0
} else {
0.0
};
X86IncrementalPlan {
changed_files: changed,
unchanged_files: unchanged,
new_files,
removed_files: removed,
total_to_compile: total,
estimated_savings_pct: savings,
}
}
pub fn needs_rebuild(&self) -> bool {
self.total_to_compile > 0
}
pub fn summary(&self) -> String {
format!(
"Incremental plan: {} to compile ({} changed + {} new), {} unchanged, {} removed ({:.1}% savings)",
self.total_to_compile,
self.changed_files.len(),
self.new_files.len(),
self.unchanged_files.len(),
self.removed_files.len(),
self.estimated_savings_pct,
)
}
}
#[derive(Debug, Clone)]
pub struct X86BuildSystem {
pub use_cargo: bool,
pub use_make: bool,
pub build_command: String,
pub build_args: Vec<String>,
pub capture_output: bool,
pub use_sccache: bool,
pub sccache_path: Option<PathBuf>,
}
impl Default for X86BuildSystem {
fn default() -> Self {
X86BuildSystem {
use_cargo: true,
use_make: false,
build_command: "cargo".to_string(),
build_args: vec!["build".to_string(), "--release".to_string()],
capture_output: true,
use_sccache: false,
sccache_path: None,
}
}
}
impl X86BuildSystem {
pub fn cargo() -> Self {
X86BuildSystem {
use_cargo: true,
build_command: "cargo".to_string(),
build_args: vec!["build".to_string(), "--release".to_string()],
..Default::default()
}
}
pub fn make() -> Self {
X86BuildSystem {
use_cargo: false,
use_make: true,
build_command: "make".to_string(),
build_args: vec!["-j$(nproc)".to_string()],
..Default::default()
}
}
pub fn with_sccache(mut self, sccache_path: PathBuf) -> Self {
self.use_sccache = true;
self.sccache_path = Some(sccache_path);
self
}
pub fn env_vars(&self) -> BTreeMap<String, String> {
let mut env = BTreeMap::new();
if self.use_sccache {
if let Some(ref path) = self.sccache_path {
env.insert(
"RUSTC_WRAPPER".to_string(),
path.to_string_lossy().to_string(),
);
}
}
env
}
}
#[derive(Debug, Clone)]
pub struct X86ResourceLimits {
pub max_stage_time_secs: u64,
pub max_memory_bytes: u64,
pub max_disk_bytes: u64,
pub max_parallel_jobs: usize,
pub cpu_affinity: Option<Vec<usize>>,
pub nice_level: i32,
}
impl Default for X86ResourceLimits {
fn default() -> Self {
X86ResourceLimits {
max_stage_time_secs: 14400, max_memory_bytes: 16 * 1024 * 1024 * 1024, max_disk_bytes: 100 * 1024 * 1024 * 1024, max_parallel_jobs: num_cpus(),
cpu_affinity: None,
nice_level: 10,
}
}
}
impl X86ResourceLimits {
pub fn ci_limits() -> Self {
X86ResourceLimits {
max_stage_time_secs: 7200, max_memory_bytes: 8 * 1024 * 1024 * 1024, max_disk_bytes: 50 * 1024 * 1024 * 1024, max_parallel_jobs: 2,
cpu_affinity: None,
nice_level: 0,
}
}
pub fn developer_limits() -> Self {
let cpus = num_cpus();
X86ResourceLimits {
max_parallel_jobs: cpus.saturating_sub(2).max(1),
nice_level: 15,
..Default::default()
}
}
pub fn check_limits(&self, metrics: &X86StageMetrics) -> Vec<String> {
let mut violations = Vec::new();
if metrics.wall_time.as_secs() > self.max_stage_time_secs {
violations.push(format!(
"Stage time {}s exceeds limit {}s",
metrics.wall_time.as_secs(),
self.max_stage_time_secs
));
}
if metrics.peak_memory_bytes > self.max_memory_bytes {
violations.push(format!(
"Peak memory {} bytes exceeds limit {} bytes",
metrics.peak_memory_bytes, self.max_memory_bytes
));
}
violations
}
}
#[derive(Debug, Clone)]
pub struct X86EnvironmentDetector {
pub os: String,
pub arch: String,
pub available_compilers: Vec<PathBuf>,
pub available_memory_bytes: u64,
pub cpu_count: usize,
pub in_ci: bool,
pub ci_system: Option<String>,
pub has_network: bool,
}
impl X86EnvironmentDetector {
pub fn detect() -> Self {
let os = std::env::consts::OS.to_string();
let arch = std::env::consts::ARCH.to_string();
let cpu_count = num_cpus();
let ci_vars = [
"CI",
"GITHUB_ACTIONS",
"GITLAB_CI",
"JENKINS_HOME",
"CIRCLECI",
"TRAVIS",
"BUILDKITE",
];
let mut ci_system = None;
let mut in_ci = false;
for var in &ci_vars {
if std::env::var(var).is_ok() {
in_ci = true;
ci_system = Some(var.to_string());
break;
}
}
let compilers_to_check = ["rustc", "gcc", "clang", "cc", "c++"];
let mut available_compilers = Vec::new();
for cc in &compilers_to_check {
if let Ok(output) = Command::new("which").arg(cc).output() {
if output.status.success() {
let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !path_str.is_empty() {
available_compilers.push(PathBuf::from(path_str));
}
}
}
}
let available_memory_bytes = detect_available_memory();
X86EnvironmentDetector {
os,
arch,
available_compilers,
available_memory_bytes,
cpu_count,
in_ci,
ci_system,
has_network: true, }
}
pub fn is_suitable_for_bootstrap(&self) -> bool {
self.cpu_count >= 2
&& self.available_memory_bytes >= 2 * 1024 * 1024 * 1024 && !self.available_compilers.is_empty()
}
pub fn summary(&self) -> String {
let compilers: Vec<String> = self
.available_compilers
.iter()
.map(|p| p.display().to_string())
.collect();
format!(
"Environment: {}-{}, {} CPUs, {:.1} GiB RAM, Compilers: [{}], CI: {}",
self.os,
self.arch,
self.cpu_count,
self.available_memory_bytes as f64 / (1024.0 * 1024.0 * 1024.0),
compilers.join(", "),
if self.in_ci {
self.ci_system.as_deref().unwrap_or("unknown")
} else {
"no"
},
)
}
}
fn detect_available_memory() -> u64 {
if let Ok(meminfo) = fs::read_to_string("/proc/meminfo") {
for line in meminfo.lines() {
if line.starts_with("MemAvailable:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
if let Ok(kb) = parts[1].parse::<u64>() {
return kb * 1024;
}
}
}
}
}
4 * 1024 * 1024 * 1024
}
#[derive(Debug, Clone)]
pub struct X86BuildArtifact {
pub path: PathBuf,
pub stage: X86BootstrapStage,
pub artifact_type: X86ArtifactType,
pub size: u64,
pub hash: u64,
pub produced_at: SystemTime,
pub source_files: Vec<PathBuf>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ArtifactType {
Object,
StaticLib,
SharedLib,
Executable,
Bitcode,
Assembly,
LlvmIr,
DebugInfo,
LinkerMap,
Other,
}
impl X86ArtifactType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Object => "object",
Self::StaticLib => "static_lib",
Self::SharedLib => "shared_lib",
Self::Executable => "executable",
Self::Bitcode => "bitcode",
Self::Assembly => "assembly",
Self::LlvmIr => "llvm_ir",
Self::DebugInfo => "debug_info",
Self::LinkerMap => "linker_map",
Self::Other => "other",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86ArtifactCollection {
pub by_stage: BTreeMap<X86BootstrapStage, Vec<X86BuildArtifact>>,
pub total_size: u64,
pub total_count: usize,
}
impl X86ArtifactCollection {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, artifact: X86BuildArtifact) {
self.total_size += artifact.size;
self.total_count += 1;
self.by_stage
.entry(artifact.stage)
.or_default()
.push(artifact);
}
pub fn for_stage(&self, stage: X86BootstrapStage) -> &[X86BuildArtifact] {
self.by_stage
.get(&stage)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn total_size_human(&self) -> String {
human_readable_size(self.total_size)
}
}
fn format_duration_human(d: Duration) -> String {
let secs = d.as_secs();
if secs < 60 {
format!("{}s", secs)
} else if secs < 3600 {
format!("{}m {}s", secs / 60, secs % 60)
} else {
format!("{}h {}m {}s", secs / 3600, (secs % 3600) / 60, secs % 60)
}
}
fn is_elf_binary(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"ELF"
}
fn is_macho_binary(data: &[u8]) -> bool {
if data.len() < 4 {
return false;
}
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
matches!(magic, 0xfeedface | 0xcefaedfe | 0xfeedfacf | 0xcffaedfe)
}
fn quick_hash(data: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for &byte in data {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
pub fn failure_report_summary(stage: X86BootstrapStage, error: &str, duration: Duration) -> String {
format!(
"BOOTSTRAP FAILURE [{stage}]: {error}
Duration: {duration:?}
",
stage = stage.name(),
error = error,
duration = duration,
)
}
pub fn success_report_summary(identical: bool, duration: Duration, stage_count: usize) -> String {
format!(
"BOOTSTRAP SUCCESS: {} stages completed in {:?}. Stage2=Stage3: {}",
stage_count,
duration,
if identical { "IDENTICAL" } else { "DIFFER" },
)
}
#[derive(Debug, Clone)]
pub struct X86BootstrapArgs {
pub source_dir: PathBuf,
pub build_dir: Option<PathBuf>,
pub stage_count: usize,
pub verify: bool,
pub report: bool,
pub report_format: ReportFormat,
pub run_tests: bool,
pub cache: bool,
pub clean: bool,
pub verbose: bool,
pub dry_run: bool,
pub jobs: usize,
pub opt_level: String,
pub target: String,
pub extra_flags: Vec<String>,
}
impl Default for X86BootstrapArgs {
fn default() -> Self {
X86BootstrapArgs {
source_dir: PathBuf::from("."),
build_dir: None,
stage_count: 4,
verify: true,
report: true,
report_format: ReportFormat::Markdown,
run_tests: true,
cache: true,
clean: false,
verbose: false,
dry_run: false,
jobs: num_cpus(),
opt_level: "2".to_string(),
target: X86_64_LINUX_TARGET.to_string(),
extra_flags: Vec::new(),
}
}
}
impl X86BootstrapArgs {
pub fn from_args(args: &[String]) -> Result<Self, String> {
let mut parsed = X86BootstrapArgs::default();
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--source-dir" | "-s" => {
i += 1;
parsed.source_dir = PathBuf::from(args.get(i).ok_or("Missing source dir")?);
}
"--build-dir" | "-b" => {
i += 1;
parsed.build_dir = Some(PathBuf::from(args.get(i).ok_or("Missing build dir")?));
}
"--stages" | "-n" => {
i += 1;
parsed.stage_count = args
.get(i)
.ok_or("Missing stage count")?
.parse()
.map_err(|e| format!("Invalid stage count: {}", e))?;
}
"--no-verify" => parsed.verify = false,
"--no-report" => parsed.report = false,
"--no-tests" => parsed.run_tests = false,
"--no-cache" => parsed.cache = false,
"--clean" => parsed.clean = true,
"--verbose" | "-v" => parsed.verbose = true,
"--dry-run" => parsed.dry_run = true,
"--jobs" | "-j" => {
i += 1;
parsed.jobs = args
.get(i)
.ok_or("Missing job count")?
.parse()
.map_err(|e| format!("Invalid job count: {}", e))?;
}
"--opt-level" | "-O" => {
i += 1;
parsed.opt_level = args.get(i).ok_or("Missing opt level")?.clone();
}
"--target" => {
i += 1;
parsed.target = args.get(i).ok_or("Missing target")?.clone();
}
"--report-format" => {
i += 1;
parsed.report_format = match args.get(i).map(|s| s.as_str()) {
Some("html") => ReportFormat::Html,
Some("json") => ReportFormat::Json,
Some("text") => ReportFormat::Text,
_ => ReportFormat::Markdown,
};
}
_ => {
if !args[i].starts_with('-') {
parsed.extra_flags.push(args[i].clone());
}
}
}
i += 1;
}
Ok(parsed)
}
pub fn to_config(&self) -> X86SelfHostConfig {
let build_dir = self
.build_dir
.clone()
.unwrap_or_else(|| self.source_dir.join("target").join("bootstrap"));
let mut config = X86SelfHostConfig::x86_64_linux(self.source_dir.clone());
config.build_dir = build_dir;
config.stage_count = self.stage_count;
config.verify_stages = self.verify;
config.generate_report = self.report;
config.report_format = self.report_format;
config.test_suite_enabled = self.run_tests;
config.enable_caching = self.cache;
config.clean_build = self.clean;
config.verbose = self.verbose;
config.dry_run = self.dry_run;
config.report_path = Some(config.build_dir.join("report.md"));
for (_, sc) in config.stage_configs.iter_mut() {
sc.opt_level = self.opt_level.clone();
sc.target_triple = self.target.clone();
sc.parallel_jobs = self.jobs;
}
config
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_format_duration_human_seconds() {
assert_eq!(format_duration_human(Duration::from_secs(0)), "0s");
assert_eq!(format_duration_human(Duration::from_secs(30)), "30s");
assert_eq!(format_duration_human(Duration::from_secs(59)), "59s");
}
#[test]
fn test_format_duration_human_minutes() {
let d = format_duration_human(Duration::from_secs(90));
assert!(d.contains("m"));
assert!(d.contains("30s"));
}
#[test]
fn test_format_duration_human_hours() {
let d = format_duration_human(Duration::from_secs(3661));
assert!(d.contains("h"));
assert!(d.contains("1m"));
}
#[test]
fn test_is_elf_binary() {
assert!(is_elf_binary(b"ELFtest"));
assert!(!is_elf_binary(b"not elf"));
assert!(!is_elf_binary(b""));
}
#[test]
fn test_is_macho_binary() {
let macho_le: [u8; 4] = 0xfeedfaceu32.to_le_bytes();
assert!(is_macho_binary(&macho_le));
assert!(!is_macho_binary(b"not macho"));
}
#[test]
fn test_quick_hash_deterministic() {
let data = b"deterministic hash test";
let h1 = quick_hash(data);
let h2 = quick_hash(data);
assert_eq!(h1, h2);
}
#[test]
fn test_quick_hash_different() {
let h1 = quick_hash(b"aaa");
let h2 = quick_hash(b"bbb");
assert_ne!(h1, h2);
}
#[test]
fn test_failure_report_summary() {
let summary = failure_report_summary(
X86BootstrapStage::Stage1,
"compilation error",
Duration::from_secs(30),
);
assert!(summary.contains("FAILURE"));
assert!(summary.contains("Stage 1"));
assert!(summary.contains("compilation error"));
}
#[test]
fn test_success_report_summary() {
let summary = success_report_summary(true, Duration::from_secs(600), 4);
assert!(summary.contains("SUCCESS"));
assert!(summary.contains("IDENTICAL"));
}
#[test]
fn test_success_report_summary_differ() {
let summary = success_report_summary(false, Duration::from_secs(600), 4);
assert!(summary.contains("SUCCESS"));
assert!(summary.contains("DIFFER"));
}
#[test]
fn test_bootstrap_args_default() {
let args = X86BootstrapArgs::default();
assert_eq!(args.stage_count, 4);
assert!(args.verify);
assert!(args.report);
}
#[test]
fn test_bootstrap_args_from_args_stages() {
let result = X86BootstrapArgs::from_args(&["--stages".to_string(), "3".to_string()]);
assert!(result.is_ok());
assert_eq!(result.unwrap().stage_count, 3);
}
#[test]
fn test_bootstrap_args_from_args_no_verify() {
let result = X86BootstrapArgs::from_args(&["--no-verify".to_string()]);
assert!(result.is_ok());
assert!(!result.unwrap().verify);
}
#[test]
fn test_bootstrap_args_from_args_verbose() {
let result = X86BootstrapArgs::from_args(&["--verbose".to_string()]);
assert!(result.is_ok());
assert!(result.unwrap().verbose);
}
#[test]
fn test_bootstrap_args_from_args_short_flags() {
let result = X86BootstrapArgs::from_args(&[
"-v".to_string(),
"-j".to_string(),
"8".to_string(),
"-O".to_string(),
"3".to_string(),
]);
assert!(result.is_ok());
let args = result.unwrap();
assert!(args.verbose);
assert_eq!(args.jobs, 8);
assert_eq!(args.opt_level, "3");
}
#[test]
fn test_bootstrap_args_from_args_report_format() {
let result =
X86BootstrapArgs::from_args(&["--report-format".to_string(), "json".to_string()]);
assert!(result.is_ok());
assert_eq!(result.unwrap().report_format, ReportFormat::Json);
}
#[test]
fn test_bootstrap_args_to_config() {
let args = X86BootstrapArgs {
source_dir: PathBuf::from("/my/project"),
stage_count: 3,
verify: true,
report: true,
cache: true,
opt_level: "3".to_string(),
..Default::default()
};
let config = args.to_config();
assert_eq!(config.stage_count, 3);
assert!(config.verify_stages);
assert!(config.enable_caching);
assert!(config.generate_report);
}
#[test]
fn test_bootstrap_args_to_config_custom_build_dir() {
let mut args = X86BootstrapArgs::default();
args.build_dir = Some(PathBuf::from("/custom/build"));
let config = args.to_config();
assert_eq!(config.build_dir, PathBuf::from("/custom/build"));
}
#[test]
fn test_bootstrap_args_missing_value() {
let result = X86BootstrapArgs::from_args(&["--stages".to_string()]);
assert!(result.is_err());
}
#[test]
fn test_bootstrap_args_invalid_number() {
let result = X86BootstrapArgs::from_args(&["--stages".to_string(), "abc".to_string()]);
assert!(result.is_err());
}
#[test]
fn test_bootstrap_args_extra_flags() {
let result = X86BootstrapArgs::from_args(&["-DDEBUG".to_string(), "-Wall".to_string()]);
assert!(result.is_ok());
let args = result.unwrap();
assert_eq!(args.extra_flags.len(), 2);
assert!(args.extra_flags.contains(&"-DDEBUG".to_string()));
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
fn make_temp_dir() -> PathBuf {
let dir = std::env::temp_dir().join(format!("bt_int_{}", std::process::id()));
let _ = fs::create_dir_all(&dir);
dir
}
fn remove_temp_dir(dir: &Path) {
let _ = fs::remove_dir_all(dir);
}
#[test]
fn test_config_lifecycle_create_validate_execute_dryrun() {
let tmp = make_temp_dir();
let src = tmp.join("src");
fs::create_dir_all(&src).unwrap();
fs::write(src.join("main.rs"), b"fn main() {}").unwrap();
fs::write(
src.join("lib.rs"),
b"pub fn add(a: i32, b: i32) -> i32 { a + b }",
)
.unwrap();
let mut config = X86SelfHostConfig::x86_64_linux(src.clone());
config.build_dir = tmp.join("build");
config.dry_run = true;
config.verbose = false;
config.continue_on_error = true;
let validation = config.validate();
let _ = validation;
config.create_dirs().ok();
let summary = config.summary();
assert!(summary.contains("X86 Self-Host Config"));
remove_temp_dir(&tmp);
}
#[test]
fn test_stage_progression_full_chain() {
let stages = X86BootstrapStage::all_stages();
assert_eq!(stages.len(), 5);
let mut current = Some(X86BootstrapStage::Stage0);
let mut visited = Vec::new();
while let Some(stage) = current {
visited.push(stage);
current = stage.next();
}
assert_eq!(visited.len(), 5);
assert_eq!(visited[0], X86BootstrapStage::Stage0);
assert_eq!(visited[4], X86BootstrapStage::Comparison);
}
#[test]
fn test_stage_reverse_chain() {
let mut current = Some(X86BootstrapStage::Comparison);
let mut visited = Vec::new();
while let Some(stage) = current {
visited.push(stage);
current = stage.prev();
}
assert_eq!(visited.len(), 5);
assert_eq!(visited[0], X86BootstrapStage::Comparison);
assert_eq!(visited[4], X86BootstrapStage::Stage0);
}
#[test]
fn test_cache_integration_real_files() {
let tmp = make_temp_dir();
let cache_dir = tmp.join("int_cache");
let mut cache = X86BootstrapCache::new(cache_dir.clone(), 10 * 1024 * 1024);
cache.init().unwrap();
for i in 0..5 {
let src = tmp.join(format!("mod{}.rs", i));
let out = tmp.join(format!("mod{}.o", i));
fs::write(&src, format!("// module {}", i)).unwrap();
fs::write(&out, format!("object {}", i)).unwrap();
cache
.store(
X86BootstrapStage::Stage0,
&src,
Path::new("rustc"),
&["-O2".to_string()],
&out,
)
.unwrap();
}
let stats = cache.stats();
assert!(stats.enabled);
cache.clear().unwrap();
let stats_after = cache.stats();
assert_eq!(stats_after.current_size_bytes, 0);
remove_temp_dir(&tmp);
}
#[test]
fn test_metrics_across_full_pipeline() {
let mut metrics = X86BootstrapMetrics::new();
let stages_with_data = [
(X86BootstrapStage::Stage0, 10, 100_000, 0.98),
(X86BootstrapStage::Stage1, 12, 105_000, 0.99),
(X86BootstrapStage::Stage2, 11, 102_000, 0.995),
(X86BootstrapStage::Stage3, 11, 102_000, 0.995),
];
for &(stage, time_secs, binary_size, correctness) in &stages_with_data {
metrics.record_stage(
stage,
X86StageMetrics {
stage,
compilation_time: Duration::from_secs(time_secs),
output_size: binary_size,
success: true,
..Default::default()
},
);
metrics.record_correctness(stage, correctness);
}
metrics.detect_regressions(PERF_REGRESSION_THRESHOLD_PCT);
metrics.finish();
assert!(metrics.total_wall_time > Duration::ZERO);
assert!(metrics.meets_correctness_threshold());
assert!(metrics.overall_correctness() > 0.95);
}
#[test]
fn test_verification_multiple_categories() {
let tmp = make_temp_dir();
let mut verifier = X86BootstrapVerifier::new(false, true);
let f1 = tmp.join("v1.bin");
let f2 = tmp.join("v2.bin");
fs::write(&f1, b"identical content for verification").unwrap();
fs::write(&f2, b"identical content for verification").unwrap();
let bin_result = verifier.verify_binary_identical(&f1, &f2);
assert!(bin_result.is_ok());
assert!(bin_result.unwrap());
let size_result = verifier.verify_sizes(&f1, &f2);
assert!(size_result.is_ok());
assert!(size_result.unwrap());
assert!(verifier.all_passed());
remove_temp_dir(&tmp);
}
#[test]
fn test_report_integration_complete_pipeline() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/workspace/llvm-native"));
let mut report =
X86BootstrapReport::new(ReportFormat::Markdown, config, Duration::from_secs(3600));
report.success = true;
report.compiler_version = "llvm-native 0.1.0".to_string();
for stage in X86BootstrapStage::all_build_stages() {
report.add_stage_result(X86BootstrapStageResult {
stage,
success: true,
duration: Duration::from_secs(300 + stage.index() as u64 * 60),
compiler_path: Some(PathBuf::from(format!(
"/build/{}/bin/llvm-native-clang",
stage.dir_name()
))),
files_compiled: 350,
binary_size: 8_500_000 + stage.index() as u64 * 250_000,
error_count: 0,
warning_count: stage.index(),
test_results: Some(X86BootstrapTestResults {
total: 1200,
passed: 1198 - stage.index(),
failed: stage.index() + 2,
skipped: 0,
duration: Duration::from_secs(180),
}),
errors: Vec::new(),
retries: 0,
});
}
report.add_verification_results(vec![
X86VerificationResult {
check_name: "Binary Identical".to_string(),
category: X86VerificationCategory::Binary,
passed: true,
error: None,
duration: Duration::from_millis(50),
details: BTreeMap::new(),
},
X86VerificationResult {
check_name: "Symbols Match".to_string(),
category: X86VerificationCategory::Symbols,
passed: true,
error: None,
duration: Duration::from_millis(30),
details: BTreeMap::new(),
},
X86VerificationResult {
check_name: "Size Within Threshold".to_string(),
category: X86VerificationCategory::Size,
passed: true,
error: None,
duration: Duration::from_millis(5),
details: BTreeMap::new(),
},
]);
let md = report.generate_markdown();
assert!(md.contains("llvm-native"));
assert!(md.contains("Stage 0"));
assert!(md.contains("Stage 3"));
assert!(md.contains("1200"));
assert!(md.contains("Verification"));
assert!(md.contains("SUCCESS"));
let html = report.generate_html();
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("<table>"));
assert!(html.contains("</html>"));
let text = report.generate_text();
assert!(text.contains("Stage Results"));
assert!(!text.contains("<html>"));
let json = report.generate_json();
assert!(json.contains("stages"));
assert!(json.contains("success"));
}
#[test]
fn test_script_integration_all_ci_systems() {
let self_config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
for ci_system in &[
CiSystem::GitHubActions,
CiSystem::GitLabCI,
CiSystem::Jenkins,
CiSystem::CircleCI,
CiSystem::Custom,
] {
let script_config = X86BootstrapScriptConfig {
ci_system: *ci_system,
generate_ci: true,
generate_shell: true,
generate_makefile: true,
..Default::default()
};
let script = X86BootstrapScript::new(script_config, self_config.clone());
let shell = script.generate_shell_script();
assert!(shell.contains("#!/usr/bin/env bash"));
assert!(shell.contains("Stage 0"));
let makefile = script.generate_makefile();
assert!(makefile.contains("bootstrap:"));
assert!(makefile.contains("clean:"));
let (ci_name, ci_content) = script.generate_ci_config();
assert!(!ci_name.is_empty());
assert!(!ci_content.is_empty());
}
}
#[test]
fn test_self_host_orchestrator_lifecycle() {
let tmp = make_temp_dir();
let src = tmp.join("orchestrator_src");
fs::create_dir_all(&src).unwrap();
let modules = ["main", "lib", "parser", "codegen", "types"];
for m in &modules {
fs::write(
src.join(format!("{}.rs", m)),
format!("// Module: {}\npub fn {}(x: i32) -> i32 {{ x }}\n", m, m),
)
.unwrap();
}
let config = X86SelfHostConfig::x86_64_linux(src.clone());
let host = X86SelfHost::new(config).unwrap();
let files = host.collect_source_files().unwrap();
assert_eq!(files.len(), modules.len());
for stage in X86BootstrapStage::all_build_stages() {
let compiler = host.compiler_for_stage(stage);
assert!(compiler.is_some(), "Compiler missing for {}", stage.name());
}
assert!(host
.compiler_for_stage(X86BootstrapStage::Comparison)
.is_none());
let status = host.status();
assert!(status.contains("not executed"));
let report = host.generate_report();
assert!(!report.is_empty());
assert!(report.contains("llvm-native"));
remove_temp_dir(&tmp);
}
#[test]
fn test_self_host_minimal_source_tree() {
let tmp = make_temp_dir();
let src = tmp.join("minimal_src");
fs::create_dir_all(&src).unwrap();
fs::write(src.join("main.rs"), b"fn main() { println!(\"hello\"); }").unwrap();
let config = X86SelfHostConfig::x86_64_linux(src.clone());
let host = X86SelfHost::new(config).unwrap();
let files = host.collect_source_files().unwrap();
assert_eq!(files.len(), 1);
remove_temp_dir(&tmp);
}
#[test]
fn test_self_host_deeply_nested_sources() {
let tmp = make_temp_dir();
let src = tmp.join("deep_src");
let deep = src.join("a/b/c/d/e/f/g");
fs::create_dir_all(&deep).unwrap();
fs::write(deep.join("deep_module.rs"), b"// deeply nested").unwrap();
let config = X86SelfHostConfig::x86_64_linux(src.clone());
let host = X86SelfHost::new(config).unwrap();
let files = host.collect_source_files().unwrap();
assert_eq!(files.len(), 1);
remove_temp_dir(&tmp);
}
#[test]
fn test_cache_key_generation_performance() {
let cache = X86BootstrapCache::disabled();
let start = Instant::now();
for i in 0..1000 {
let _key = cache.make_key(
Path::new(&format!("src/module_{}.rs", i)),
Path::new("rustc"),
&["-O2".to_string(), "-g".to_string()],
);
}
let elapsed = start.elapsed();
assert!(elapsed < Duration::from_secs(1));
}
#[test]
fn test_verification_performance_large_files() {
let tmp = make_temp_dir();
let large = vec![0x41u8; 1024 * 1024];
let f1 = tmp.join("large1.bin");
let f2 = tmp.join("large2.bin");
fs::write(&f1, &large).unwrap();
fs::write(&f2, &large).unwrap();
let mut verifier = X86BootstrapVerifier::default();
let start = Instant::now();
let result = verifier.verify_binary_identical(&f1, &f2);
let elapsed = start.elapsed();
assert!(result.is_ok());
assert!(result.unwrap());
assert!(elapsed < Duration::from_secs(1));
remove_temp_dir(&tmp);
}
#[test]
fn test_config_builder_pattern_full_chain() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"))
.stage_configs
.clone();
for i in 0..4 {
assert!(config.contains_key(&i));
}
let s0 = config.get(&0).unwrap();
let s1 = config.get(&1).unwrap();
assert!(s1.compiler_path.to_string_lossy().contains("stage0"));
assert!(s1
.compiler_path
.to_string_lossy()
.contains("llvm-native-clang"));
}
#[test]
fn test_stage_config_all_x86_targets() {
let targets = [
"x86_64-unknown-linux-gnu",
"x86_64-pc-linux-gnu",
"x86_64-unknown-linux-musl",
"x86_64-pc-windows-msvc",
"x86_64-apple-darwin",
"i386-unknown-linux-gnu",
"i686-unknown-linux-gnu",
"i586-unknown-linux-gnu",
"i386-pc-windows-msvc",
];
for target in &targets {
let config = X86StageConfig::new(X86BootstrapStage::Stage1, Path::new("/build"))
.with_target(target);
assert_eq!(config.target_triple, *target);
let args = config.to_args();
assert!(args.contains(&target.to_string()));
}
}
#[test]
fn test_config_validation_requires_source_dir() {
let config = X86SelfHostConfig {
source_dir: PathBuf::from("/definitely/not/a/real/path/12345"),
..X86SelfHostConfig::default()
};
let result = config.validate();
assert!(result.is_err());
}
#[test]
fn test_config_validation_verify_needs_4_stages() {
let mut config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/tmp"));
config.stage_count = 3;
config.verify_stages = true;
let result = config.validate();
assert!(result.is_err());
}
#[test]
fn test_all_public_types_summary() {
for stage in X86BootstrapStage::all_stages() {
let _ = stage.index();
let _ = stage.name();
let _ = stage.short_label();
let _ = stage.produces_binary();
let _ = stage.input_compiler_stage();
let _ = stage.next();
let _ = stage.prev();
let _ = stage.dir_name();
let _ = format!("{}", stage);
}
let sc = X86StageConfig::new(X86BootstrapStage::Stage0, Path::new("/tmp"));
let _ = sc.to_args();
let _ = sc.summary();
let _ = sc.config_hash();
let _ = sc.clone().release_profile();
let _ = sc.clone().debug_profile();
let _ = sc.clone().size_profile();
let _ = sc.clone().stage0_default();
let _ = sc.clone().stage1_default();
let _ = sc.clone().stage2_stage3_default();
let _ = sc.clone().with_compiler(PathBuf::from("cc"));
let _ = sc.clone().with_opt_level("1");
let _ = sc.clone().with_target("x86_64-linux");
let _ = sc.clone().with_extra_flags(vec![]);
let _ = sc.clone().with_env("K", "V");
let _ = sc.clone().with_test_suite(None);
let shc = X86SelfHostConfig::default();
let _ = shc.get_stage_config(X86BootstrapStage::Stage0);
let _ = shc.compiler_for_stage(X86BootstrapStage::Stage0);
let _ = shc.validate();
let _ = shc.summary();
let _ = X86SelfHostConfig::x86_64_linux(PathBuf::from("."));
let _ = X86SelfHostConfig::x86_32_linux(PathBuf::from("."));
let _ = X86SelfHostConfig::quick_verify(PathBuf::from("."));
let _ = X86SelfHostConfig::release_bootstrap(PathBuf::from("."));
let _ = X86SelfHostConfig::ci_profile(PathBuf::from("."));
let mut compiler = X86StageCompiler::new(PathBuf::from("rustc"), X86BootstrapStage::Stage0);
let _ = compiler.is_available();
let _ = compiler.get_version();
let _ = compiler.supports_feature("sse2");
let _ = compiler.stats_summary();
compiler.reset_stats();
let _ = compiler.read_current_memory();
let mut verifier = X86BootstrapVerifier::default();
let _ = verifier.all_passed();
let _ = verifier.pass_rate();
let _ = verifier.summary();
verifier.reset();
let _ = verifier.find_diff_positions(b"a", b"b");
let _ = verifier.compare_content_aware(b"test", b"test");
let _ = verifier.generate_test_programs(&X86VerificationTestConfig::default());
let mut cache = X86BootstrapCache::disabled();
let _ = cache.make_key(Path::new("x"), Path::new("cc"), &[]);
let _ = cache.stats();
let _ = cache.is_active();
let mut metrics = X86BootstrapMetrics::new();
let _ = metrics.overall_correctness();
let _ = metrics.meets_correctness_threshold();
metrics.finish();
let _ = metrics.summary();
let report = X86BootstrapReport::new(
ReportFormat::Markdown,
X86SelfHostConfig::default(),
Duration::ZERO,
);
let _ = report.generate();
let _ = report.generate_markdown();
let _ = report.generate_html();
let _ = report.generate_text();
let _ = report.generate_json();
let script = X86BootstrapScript::new(
X86BootstrapScriptConfig::default(),
X86SelfHostConfig::default(),
);
let _ = script.generate_shell_script();
let _ = script.generate_makefile();
let _ = script.generate_github_actions();
let host = X86SelfHost::new(X86SelfHostConfig::default()).unwrap();
let _ = host.status();
let _ = host.is_successful();
let _ = host.generate_report();
let _ = host.compiler_for_stage(X86BootstrapStage::Stage0);
let _ = X86BootstrapState::NotStarted.as_str();
let _ = X86BootstrapState::ExecutingStage(X86BootstrapStage::Stage0).is_running();
let _ = X86BootstrapState::Completed.is_terminal();
let _ = X86IncrementalPlan::full_rebuild(vec![PathBuf::from("x.rs")]).needs_rebuild();
let _ = X86BuildSystem::default().env_vars();
let _ = X86ResourceLimits::default().check_limits(&X86StageMetrics::default());
let _ = X86EnvironmentDetector::detect().summary();
let _ = X86ArtifactCollection::new().total_size_human();
let _ = X86BootstrapArgs::default().to_config();
let _ = TestCategory::all();
let _ = is_executable(Path::new("/bin/sh"));
let _ = hash_file(Path::new("/dev/null"));
let _ = hash_string_slice(&["a".to_string()]);
let _ = is_likely_symbol("valid_symbol");
let _ = format_timestamp(SystemTime::now());
let _ = detect_host_info();
let _ = json_escape("test");
let _ = num_cpus();
let _ = human_readable_size(1024);
let _ = detect_available_memory();
let _ = format_duration_human(Duration::from_secs(60));
let _ = is_elf_binary(b"\x7fELF");
let _ = is_macho_binary(&0xfeedfaceu32.to_le_bytes());
let _ = quick_hash(b"data");
let _ = failure_report_summary(X86BootstrapStage::Stage0, "err", Duration::ZERO);
let _ = success_report_summary(true, Duration::ZERO, 4);
}
}
#[cfg(test)]
mod coordination_tests {
use super::*;
#[test]
fn test_stage_config_chain_consistency() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
for i in 1..config.stage_count {
let prev_sc = config
.get_stage_config(X86BootstrapStage::all_build_stages()[i - 1])
.unwrap();
let curr_sc = config
.get_stage_config(X86BootstrapStage::all_build_stages()[i])
.unwrap();
assert!(
curr_sc
.compiler_path
.to_string_lossy()
.contains(&format!("stage{}", i - 1)),
"Stage {} compiler path should reference stage{}",
i,
i - 1
);
}
}
#[test]
fn test_stage_counts_are_monotonic() {
let stages = X86BootstrapStage::all_stages();
for window in stages.windows(2) {
assert!(window[0].index() < window[1].index());
}
}
#[test]
fn test_build_stages_subset_of_all_stages() {
let build = X86BootstrapStage::all_build_stages();
let all = X86BootstrapStage::all_stages();
for bs in &build {
assert!(all.contains(bs));
}
assert!(!build.contains(&X86BootstrapStage::Comparison));
}
#[test]
fn test_stage_config_hash_stable_across_clones() {
let original = X86StageConfig::new(X86BootstrapStage::Stage2, Path::new("/build"))
.with_opt_level("3")
.with_target("x86_64-unknown-linux-gnu")
.release_profile();
for _ in 0..50 {
let cloned = original.clone();
assert_eq!(original.config_hash(), cloned.config_hash());
}
}
#[test]
fn test_self_host_config_clone_preserves_stage_configs() {
let original = X86SelfHostConfig::x86_64_linux(PathBuf::from("/original"));
let cloned = original.clone();
assert_eq!(original.stage_count, cloned.stage_count);
for i in 0..original.stage_count {
let orig_sc = original.get_stage_config(X86BootstrapStage::all_build_stages()[i]);
let clone_sc = cloned.get_stage_config(X86BootstrapStage::all_build_stages()[i]);
assert!(orig_sc.is_some());
assert!(clone_sc.is_some());
}
}
#[test]
fn test_test_category_all_combinations_valid() {
let all = TestCategory::all();
assert_eq!(all.len(), 12);
let mut seen_names = HashSet::new();
for cat in &all {
let name = cat.as_str();
assert!(seen_names.insert(name), "Duplicate name: {}", name);
}
for cat in &all {
let _ = cat.is_expensive();
let _ = cat.default_enabled();
}
}
#[test]
fn test_test_category_ci_set_is_subset_of_all() {
let all = TestCategory::all();
let ci = TestCategory::ci_set();
for cat in &ci {
assert!(all.contains(cat));
}
}
#[test]
fn test_test_category_quick_set_is_minimal() {
let quick = TestCategory::quick_set();
assert_eq!(quick.len(), 3);
for cat in &quick {
assert!(!cat.is_expensive());
}
}
#[test]
fn test_resource_limits_all_stages_within_bounds() {
let limits = X86ResourceLimits::ci_limits();
for stage in X86BootstrapStage::all_build_stages() {
let metrics = X86StageMetrics {
stage,
wall_time: Duration::from_secs(1800), peak_memory_bytes: 1024 * 1024 * 1024, success: true,
..Default::default()
};
let violations = limits.check_limits(&metrics);
assert!(
violations.is_empty(),
"Violations for {}: {:?}",
stage.name(),
violations
);
}
}
#[test]
fn test_resource_limits_detects_time_exceeded() {
let limits = X86ResourceLimits {
max_stage_time_secs: 60,
..Default::default()
};
let metrics = X86StageMetrics {
stage: X86BootstrapStage::Stage0,
wall_time: Duration::from_secs(120),
..Default::default()
};
let violations = limits.check_limits(&metrics);
assert!(!violations.is_empty());
}
#[test]
fn test_environment_detection_consistent() {
let env1 = X86EnvironmentDetector::detect();
let env2 = X86EnvironmentDetector::detect();
assert_eq!(env1.os, env2.os);
assert_eq!(env1.arch, env2.arch);
assert_eq!(env1.cpu_count, env2.cpu_count);
}
#[test]
fn test_cache_key_uniqueness_across_compilers() {
let cache = X86BootstrapCache::disabled();
let mut keys = HashSet::new();
let compilers = ["rustc", "gcc", "clang", "clang-15", "clang-17"];
for cc in &compilers {
let key = cache.make_key(Path::new("src/main.rs"), Path::new(cc), &[]);
keys.insert(key);
}
assert_eq!(keys.len(), compilers.len());
}
#[test]
fn test_cache_key_different_for_different_flags_order() {
let cache = X86BootstrapCache::disabled();
let key1 = cache.make_key(
Path::new("src/test.rs"),
Path::new("rustc"),
&["-O2".to_string(), "-g".to_string()],
);
let key2 = cache.make_key(
Path::new("src/test.rs"),
Path::new("rustc"),
&["-g".to_string(), "-O2".to_string()],
);
assert_ne!(key1, key2);
}
#[test]
fn test_verification_all_categories_have_unique_names() {
let categories = [
X86VerificationCategory::Binary,
X86VerificationCategory::Symbols,
X86VerificationCategory::CodeSection,
X86VerificationCategory::Behavior,
X86VerificationCategory::Metadata,
X86VerificationCategory::Size,
X86VerificationCategory::Performance,
];
let mut names = HashSet::new();
for cat in &categories {
assert!(names.insert(cat.as_str()));
}
assert_eq!(names.len(), categories.len());
}
#[test]
fn test_report_formats_all_produce_content() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/tmp"));
let formats = [
ReportFormat::Markdown,
ReportFormat::Html,
ReportFormat::Text,
ReportFormat::Json,
];
for &format in &formats {
let mut report =
X86BootstrapReport::new(format, config.clone(), Duration::from_secs(600));
report.success = true;
report.add_stage_result(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage0,
success: true,
duration: Duration::from_secs(10),
compiler_path: None,
files_compiled: 1,
binary_size: 1000,
error_count: 0,
warning_count: 0,
test_results: None,
errors: Vec::new(),
retries: 0,
});
let output = report.generate();
assert!(!output.is_empty(), "Empty output for format {:?}", format);
}
}
#[test]
fn test_scripts_all_ci_configs_unique() {
let self_config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
let ci_systems = [
CiSystem::GitHubActions,
CiSystem::GitLabCI,
CiSystem::Jenkins,
CiSystem::CircleCI,
CiSystem::Custom,
];
let mut configs = HashMap::new();
for &system in &ci_systems {
let script_config = X86BootstrapScriptConfig {
ci_system: system,
generate_ci: true,
..Default::default()
};
let script = X86BootstrapScript::new(script_config, self_config.clone());
let (name, content) = script.generate_ci_config();
configs.insert(name, content);
}
assert_eq!(configs.len(), ci_systems.len());
let contents: Vec<_> = configs.values().collect();
for i in 0..contents.len() {
for j in i + 1..contents.len() {
assert_ne!(contents[i], contents[j]);
}
}
}
#[test]
fn test_metrics_stage_summary_stable() {
let mut metrics = X86BootstrapMetrics::new();
metrics.record_stage(
X86BootstrapStage::Stage0,
X86StageMetrics {
stage: X86BootstrapStage::Stage0,
compilation_time: Duration::from_secs(15),
output_size: 100_000,
success: true,
..Default::default()
},
);
let summary1 = metrics.summary();
let summary2 = metrics.summary();
assert_eq!(summary1, summary2);
}
#[test]
fn test_compiler_stats_accumulate_correctly() {
let mut compiler = X86StageCompiler::new(PathBuf::from("rustc"), X86BootstrapStage::Stage0);
for i in 0..3 {
compiler.stats.total_compilations += 1;
compiler.stats.successful_compilations += 1;
compiler.stats.cumulative_time += Duration::from_millis(100 * (i + 1));
compiler.stats.total_output_bytes += 1024 * (i + 1) as u64;
}
let summary = compiler.stats_summary();
assert!(summary.contains("3/3"));
assert!(summary.contains("100.0%"));
}
#[test]
fn test_report_consistent_data_across_formats() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
let stage_result = X86BootstrapStageResult {
stage: X86BootstrapStage::Stage1,
success: true,
duration: Duration::from_secs(42),
compiler_path: Some(PathBuf::from("/build/stage1/bin/clang")),
files_compiled: 250,
binary_size: 5_000_000,
error_count: 0,
warning_count: 3,
test_results: Some(X86BootstrapTestResults {
total: 100,
passed: 98,
failed: 2,
skipped: 0,
duration: Duration::from_secs(30),
}),
errors: Vec::new(),
retries: 0,
};
assert_eq!(stage_result.stage, X86BootstrapStage::Stage1);
assert_eq!(stage_result.files_compiled, 250);
assert_eq!(stage_result.binary_size, 5_000_000);
assert!(stage_result.test_results.is_some());
let tr = stage_result.test_results.unwrap();
assert_eq!(tr.total, 100);
assert_eq!(tr.passed, 98);
assert_eq!(tr.failed, 2);
}
}
#[cfg(test)]
mod smoke_tests {
use super::*;
#[test]
fn test_smoke_all_types_instantiable() {
assert_eq!(X86BootstrapStage::Stage0.index(), 0);
assert_eq!(X86BootstrapStage::Stage1.index(), 1);
assert_eq!(X86BootstrapStage::Stage2.index(), 2);
assert_eq!(X86BootstrapStage::Stage3.index(), 3);
assert_eq!(X86BootstrapStage::Comparison.index(), 4);
let sc = X86StageConfig::default();
assert_eq!(sc.stage, X86BootstrapStage::Stage0);
assert_eq!(sc.opt_level, "2");
assert!(sc.pic);
let shc = X86SelfHostConfig::default();
assert_eq!(shc.stage_count, 4);
assert!(shc.verify_stages);
let compiler = X86StageCompiler::new(PathBuf::from("rustc"), X86BootstrapStage::Stage0);
assert_eq!(compiler.stage, X86BootstrapStage::Stage0);
let verifier = X86BootstrapVerifier::default();
assert!(verifier.all_passed());
let cache = X86BootstrapCache::disabled();
assert!(!cache.enabled);
let metrics = X86BootstrapMetrics::default();
assert!(metrics.enabled);
let report = X86BootstrapReport::new(
ReportFormat::Markdown,
X86SelfHostConfig::default(),
Duration::ZERO,
);
assert_eq!(report.format, ReportFormat::Markdown);
let script = X86BootstrapScript::new(
X86BootstrapScriptConfig::default(),
X86SelfHostConfig::default(),
);
assert!(script.config.generate_shell);
let host = X86SelfHost::new(X86SelfHostConfig::default());
assert!(host.is_ok());
}
#[test]
fn test_smoke_builder_patterns() {
let sc = X86StageConfig::new(X86BootstrapStage::Stage1, Path::new("/build"))
.release_profile()
.with_opt_level("3")
.with_target("x86_64-unknown-linux-gnu")
.with_compiler(PathBuf::from("custom-cc"))
.with_env("RUST_LOG", "debug");
assert_eq!(sc.opt_level, "3");
assert!(sc.lto);
assert_eq!(sc.env_vars.get("RUST_LOG"), Some(&"debug".to_string()));
let quick = X86SelfHostConfig::quick_verify(PathBuf::from("/tmp"));
assert_eq!(quick.stage_count, 2);
let release = X86SelfHostConfig::release_bootstrap(PathBuf::from("/tmp"));
assert_eq!(release.stage_count, 4);
assert!(release.strip_binaries);
let ci = X86SelfHostConfig::ci_profile(PathBuf::from("/tmp"));
assert!(ci.clean_build);
assert!(ci.generate_ci);
}
#[test]
fn test_smoke_display_implementations() {
assert!(!format!("{}", X86BootstrapStage::Stage0).is_empty());
assert!(!format!("{}", X86BootstrapStage::Stage1).is_empty());
assert!(!format!("{}", X86BootstrapStage::Comparison).is_empty());
let state = X86BootstrapState::ExecutingStage(X86BootstrapStage::Stage2);
assert!(!format!("{}", state).is_empty());
let failed = X86BootstrapState::Failed(X86BootstrapStage::Stage3);
assert!(!format!("{}", failed).is_empty());
}
#[test]
fn test_smoke_constants_reasonable() {
assert!(DEFAULT_STAGE_TIMEOUT_SECS >= 3600); assert!(DEFAULT_TEST_TIMEOUT_SECS >= 60); assert!(MAX_STAGE_RETRIES <= 10); assert!(MIN_CORRECTNESS_SCORE > 0.0 && MIN_CORRECTNESS_SCORE <= 1.0);
assert!(PERF_REGRESSION_THRESHOLD_PCT > 0.0);
assert!(MAX_SIZE_INCREASE_PCT > 0.0);
assert_eq!(CACHE_VERSION, 1);
assert!(HASH_CHUNK_SIZE >= 256);
}
#[test]
fn test_smoke_argument_generation() {
let configs = [
X86StageConfig::new(X86BootstrapStage::Stage0, Path::new("/build")).stage0_default(),
X86StageConfig::new(X86BootstrapStage::Stage1, Path::new("/build")).stage1_default(),
X86StageConfig::new(X86BootstrapStage::Stage2, Path::new("/build"))
.stage2_stage3_default(),
X86StageConfig::new(X86BootstrapStage::Stage3, Path::new("/build"))
.stage2_stage3_default(),
];
for sc in &configs {
let args = sc.to_args();
assert!(args.len() >= 2, "Too few args for {}", sc.stage.name());
assert!(
args.iter().any(|a| a.starts_with("-O")),
"No optimization flag in {}",
sc.summary()
);
assert!(
args.contains(&"--target".to_string()),
"No target flag in {}",
sc.summary()
);
}
}
#[test]
fn test_smoke_config_validation() {
let config = X86SelfHostConfig {
source_dir: PathBuf::from("/nonexistent/path/xyz123"),
..X86SelfHostConfig::default()
};
assert!(config.validate().is_err());
let mut config = X86SelfHostConfig::default();
config.stage_count = 0;
assert!(config.validate().is_err());
config.stage_count = 6;
assert!(config.validate().is_err());
}
#[test]
fn test_smoke_cache_disabled_mode() {
let mut cache = X86BootstrapCache::disabled();
let result = cache.lookup(
X86BootstrapStage::Stage0,
Path::new("test.rs"),
Path::new("rustc"),
&[],
);
assert!(result.is_none());
let tmp = std::env::temp_dir();
let src = tmp.join("cache_test_src.rs");
let out = tmp.join("cache_test_out.o");
fs::write(&src, b"test").unwrap();
fs::write(&out, b"test").unwrap();
let store_result = cache.store(
X86BootstrapStage::Stage0,
&src,
Path::new("rustc"),
&[],
&out,
);
assert!(store_result.is_ok());
let _ = fs::remove_file(&src);
let _ = fs::remove_file(&out);
}
#[test]
fn test_smoke_test_program_generation() {
let verifier = X86BootstrapVerifier::default();
let config = X86VerificationTestConfig {
test_count: 15,
..Default::default()
};
let programs = verifier.generate_test_programs(&config);
assert_eq!(programs.len(), 15);
for program in &programs {
assert!(!program.is_empty());
}
assert!(programs[0].contains("return 0"));
assert!(programs[1].contains("add"));
}
}
#[cfg(test)]
mod stress_perf_tests {
use super::*;
#[test]
fn test_hash_no_collisions_simple() {
let mut seen = HashSet::new();
for i in 0..10000 {
let data = format!("unique_string_{}", i);
let hash = quick_hash(data.as_bytes());
assert!(seen.insert(hash), "Hash collision at iteration {}", i);
}
}
#[test]
fn test_cache_key_deterministic() {
let cache = X86BootstrapCache::disabled();
let key1 = cache.make_key(
Path::new("src/module_a.rs"),
Path::new("rustc"),
&["-O2".to_string(), "-g".to_string(), "--release".to_string()],
);
for _ in 0..100 {
let key2 = cache.make_key(
Path::new("src/module_a.rs"),
Path::new("rustc"),
&["-O2".to_string(), "-g".to_string(), "--release".to_string()],
);
assert_eq!(key1, key2);
}
}
#[test]
fn test_stage_chain_consistency_all_configs() {
let configs = vec![
(
"x86_64_linux",
X86SelfHostConfig::x86_64_linux(PathBuf::from(".")),
),
(
"x86_32_linux",
X86SelfHostConfig::x86_32_linux(PathBuf::from(".")),
),
(
"quick_verify",
X86SelfHostConfig::quick_verify(PathBuf::from(".")),
),
(
"release",
X86SelfHostConfig::release_bootstrap(PathBuf::from(".")),
),
("ci", X86SelfHostConfig::ci_profile(PathBuf::from("."))),
];
for (name, config) in &configs {
assert!(
config.stage_count >= 1,
"{} should have at least 1 stage",
name
);
for i in 0..config.stage_count {
let stage = X86BootstrapStage::all_build_stages()[i];
let sc = config.get_stage_config(stage);
assert!(sc.is_some(), "{} missing config for {}", name, stage.name());
}
}
}
#[test]
fn test_stage_compiler_path_chaining() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/workspace"));
for i in 1..config.stage_count {
let prev_sc = config
.get_stage_config(X86BootstrapStage::all_build_stages()[i - 1])
.unwrap();
let curr_sc = config
.get_stage_config(X86BootstrapStage::all_build_stages()[i])
.unwrap();
let prev_dir = format!("stage{}", i - 1);
assert!(
curr_sc.compiler_path.to_string_lossy().contains(&prev_dir),
"Stage {} compiler should reference {} dir (got: {})",
i,
prev_dir,
curr_sc.compiler_path.display()
);
}
}
#[test]
fn test_cache_stats_struct_construction() {
let mut per_stage_hits = BTreeMap::new();
per_stage_hits.insert(X86BootstrapStage::Stage0, 100);
per_stage_hits.insert(X86BootstrapStage::Stage1, 50);
let stats = X86CacheStats {
enabled: true,
total_entries: 1000,
hits: 150,
misses: 30,
hit_rate: 83.33,
current_size_bytes: 52428800,
max_size_bytes: 1073741824,
per_stage_hits,
per_stage_misses: BTreeMap::new(),
};
assert!(stats.enabled);
assert_eq!(stats.total_entries, 1000);
assert_eq!(stats.hits, 150);
assert_eq!(stats.misses, 30);
}
#[test]
fn test_stage_metrics_all_fields_settable() {
let metrics = X86StageMetrics {
stage: X86BootstrapStage::Stage2,
wall_time: Duration::from_secs(600),
compilation_time: Duration::from_secs(500),
link_time: Duration::from_secs(50),
test_time: Duration::from_secs(40),
verify_time: Duration::from_secs(10),
files_compiled: 200,
errors: 0,
warnings: 5,
output_size: 7_500_000,
cache_hits: 150,
cache_misses: 50,
peak_memory_bytes: 2_147_483_648,
success: true,
retries: 1,
};
assert_eq!(metrics.wall_time.as_secs(), 600);
assert_eq!(metrics.compilation_time.as_secs(), 500);
assert_eq!(metrics.link_time.as_secs(), 50);
assert_eq!(metrics.files_compiled, 200);
assert_eq!(metrics.output_size, 7_500_000);
assert!(metrics.success);
assert_eq!(metrics.retries, 1);
}
#[test]
fn test_regression_severity_ordering() {
let severities = vec![
X86RegressionSeverity::Minor,
X86RegressionSeverity::Moderate,
X86RegressionSeverity::Major,
X86RegressionSeverity::Critical,
];
let mut names = HashSet::new();
for s in &severities {
names.insert(s.as_str());
}
assert_eq!(names.len(), severities.len());
}
#[test]
fn test_regression_detection_various_thresholds() {
let thresholds = [1.0, 5.0, 10.0, 20.0, 50.0];
for &threshold in &thresholds {
let mut metrics = X86BootstrapMetrics::new();
metrics.record_stage(
X86BootstrapStage::Stage0,
X86StageMetrics {
stage: X86BootstrapStage::Stage0,
compilation_time: Duration::from_secs(10),
output_size: 100_000,
success: true,
..Default::default()
},
);
metrics.record_stage(
X86BootstrapStage::Stage1,
X86StageMetrics {
stage: X86BootstrapStage::Stage1,
compilation_time: Duration::from_millis(11500),
output_size: 110_000,
success: true,
..Default::default()
},
);
metrics.detect_regressions(threshold);
if threshold < 15.0 {
assert!(
metrics
.regressions
.iter()
.any(|r| r.metric == "compilation_time"),
"Should detect time regression with threshold {}%",
threshold
);
}
}
}
#[test]
fn test_cache_entry_validation_scenarios() {
let cache = X86BootstrapCache::disabled();
let entry = X86CacheEntry {
key: "test_key_123".to_string(),
source_path: PathBuf::from("/tmp/test.rs"),
cached_path: PathBuf::from("/tmp/cache/test.cache"),
source_hash: 42,
compiler_hash: 99,
flags_hash: 777,
output_size: 1024,
created_at: SystemTime::now(),
accessed: false,
stage: X86BootstrapStage::Stage0,
};
assert_eq!(entry.source_hash, 42);
assert_eq!(entry.compiler_hash, 99);
assert_eq!(entry.output_size, 1024);
}
#[test]
fn test_config_hash_field_sensitivity() {
let base = X86StageConfig::new(X86BootstrapStage::Stage0, Path::new("/tmp"));
let base_hash = base.config_hash();
assert_ne!(base_hash, base.clone().with_opt_level("3").config_hash());
assert_ne!(
base_hash,
base.clone().with_target("i386-linux").config_hash()
);
assert_ne!(
base_hash,
base.clone().with_env("TEST", "VALUE").config_hash()
);
}
#[test]
fn test_bootstrap_state_all_variants() {
let states = vec![
X86BootstrapState::NotStarted,
X86BootstrapState::CollectingSources,
X86BootstrapState::ExecutingStage(X86BootstrapStage::Stage0),
X86BootstrapState::ExecutingStage(X86BootstrapStage::Stage1),
X86BootstrapState::ExecutingStage(X86BootstrapStage::Stage2),
X86BootstrapState::ExecutingStage(X86BootstrapStage::Stage3),
X86BootstrapState::Verifying,
X86BootstrapState::GeneratingReport,
X86BootstrapState::Completed,
X86BootstrapState::Failed(X86BootstrapStage::Stage0),
X86BootstrapState::Failed(X86BootstrapStage::Stage1),
X86BootstrapState::Failed(X86BootstrapStage::Stage2),
X86BootstrapState::Failed(X86BootstrapStage::Stage3),
X86BootstrapState::Aborted,
];
for state in &states {
let _ = state.as_str();
let _ = format!("{}", state);
let _ = format!("{:?}", state);
}
}
#[test]
fn test_cache_entry_serde_edge_values() {
let test_cases = vec![
(0u64, 0u64, 0u64, 0u64),
(1, 1, 1, 1),
(u64::MAX, u64::MAX, u64::MAX, u64::MAX),
(u64::MAX / 2, u64::MAX / 3, u64::MAX / 4, 42),
(1234567890, 987654321, 55555555, 999999),
];
for (source_hash, compiler_hash, flags_hash, output_size) in test_cases {
let entry = X86CacheEntry {
key: String::new(),
source_path: PathBuf::new(),
cached_path: PathBuf::new(),
source_hash,
compiler_hash,
flags_hash,
output_size,
created_at: UNIX_EPOCH,
accessed: false,
stage: X86BootstrapStage::Stage0,
};
let json = serde_cache_entry_to_json(&entry);
let parsed = deser_cache_entry_from_json(&json, X86BootstrapStage::Stage0).unwrap();
assert_eq!(parsed.source_hash, source_hash);
assert_eq!(parsed.compiler_hash, compiler_hash);
assert_eq!(parsed.flags_hash, flags_hash);
assert_eq!(parsed.output_size, output_size);
}
}
}
#[cfg(test)]
mod api_surface_tests {
use super::*;
#[test]
fn test_public_type_count() {
let _ = X86BootstrapStage::Stage0;
let _ = X86VerificationCategory::Binary;
let _ = X86DiagSeverity::Warning;
let _ = X86RegressionSeverity::Minor;
let _ = ReportFormat::Markdown;
let _ = CiSystem::GitHubActions;
let _ = X86BootstrapState::NotStarted;
let _ = X86ArtifactType::Object;
let _ = TestCategory::Smoke;
let _ = X86StageConfig::default();
let _ = X86SelfHostConfig::default();
let _ = X86StageCompiler::new(PathBuf::new(), X86BootstrapStage::Stage0);
let _ = X86CompilerStats::default();
let _ = X86CompilationOutput {
stdout: String::new(),
stderr: String::new(),
exit_status: 0,
success: false,
duration: Duration::ZERO,
warning_count: 0,
error_count: 0,
output_size: 0,
diagnostics: Vec::new(),
};
let _ = X86CompilerDiagnostic {
severity: X86DiagSeverity::Note,
message: String::new(),
file: None,
line: None,
column: None,
};
let _ = X86BootstrapVerifier::default();
let _ = X86VerificationResult {
check_name: String::new(),
category: X86VerificationCategory::Binary,
passed: true,
error: None,
duration: Duration::ZERO,
details: BTreeMap::new(),
};
let _ = X86VerificationTestConfig::default();
let _ = X86BootstrapCache::disabled();
let _ = X86CacheEntry {
key: String::new(),
source_path: PathBuf::new(),
cached_path: PathBuf::new(),
source_hash: 0,
compiler_hash: 0,
flags_hash: 0,
output_size: 0,
created_at: UNIX_EPOCH,
accessed: false,
stage: X86BootstrapStage::Stage0,
};
let _ = X86CacheStats {
enabled: false,
total_entries: 0,
hits: 0,
misses: 0,
hit_rate: 0.0,
current_size_bytes: 0,
max_size_bytes: 0,
per_stage_hits: BTreeMap::new(),
per_stage_misses: BTreeMap::new(),
};
let _ = X86BootstrapMetrics::new();
let _ = X86StageMetrics::default();
let _ = X86PerfRegression {
stage: X86BootstrapStage::Stage0,
metric: String::new(),
baseline_value: 0.0,
current_value: 0.0,
delta_pct: 0.0,
exceeds_threshold: false,
severity: X86RegressionSeverity::Minor,
};
let _ = X86BootstrapReport::new(
ReportFormat::Markdown,
X86SelfHostConfig::default(),
Duration::ZERO,
);
let _ = X86BootstrapStageResult {
stage: X86BootstrapStage::Stage0,
success: true,
duration: Duration::ZERO,
compiler_path: None,
files_compiled: 0,
binary_size: 0,
error_count: 0,
warning_count: 0,
test_results: None,
errors: Vec::new(),
retries: 0,
};
let _ = X86BootstrapTestResults {
total: 0,
passed: 0,
failed: 0,
skipped: 0,
duration: Duration::ZERO,
};
let _ = X86BootstrapScript::new(
X86BootstrapScriptConfig::default(),
X86SelfHostConfig::default(),
);
let _ = X86BootstrapScriptConfig::default();
let _ = X86SelfHost::new(X86SelfHostConfig::default());
let _ = X86IncrementalPlan::full_rebuild(Vec::new());
let _ = X86BuildSystem::default();
let _ = X86ResourceLimits::default();
let _ = X86EnvironmentDetector::detect();
let _ = X86BuildArtifact {
path: PathBuf::new(),
stage: X86BootstrapStage::Stage0,
artifact_type: X86ArtifactType::Object,
size: 0,
hash: 0,
produced_at: UNIX_EPOCH,
source_files: Vec::new(),
};
let _ = X86ArtifactCollection::new();
let _ = X86BootstrapArgs::default();
}
#[test]
fn test_all_constants_valid() {
assert!(!X86_64_LINUX_TARGET.is_empty());
assert!(!X86_32_LINUX_TARGET.is_empty());
assert!(DEFAULT_STAGE_TIMEOUT_SECS > 0);
assert!(DEFAULT_TEST_TIMEOUT_SECS > 0);
assert!(MAX_STAGE_RETRIES > 0);
assert!(MIN_CORRECTNESS_SCORE >= 0.0 && MIN_CORRECTNESS_SCORE <= 1.0);
assert!(PERF_REGRESSION_THRESHOLD_PCT > 0.0);
assert!(MAX_SIZE_INCREASE_PCT > 0.0);
assert!(CACHE_VERSION > 0);
assert!(HASH_CHUNK_SIZE > 0);
assert!(!DEFAULT_CACHE_DIR.is_empty());
}
#[test]
fn test_enum_variant_counts() {
let stages = X86BootstrapStage::all_stages();
assert_eq!(stages.len(), 5);
let cats = [
X86VerificationCategory::Binary,
X86VerificationCategory::Symbols,
X86VerificationCategory::CodeSection,
X86VerificationCategory::Behavior,
X86VerificationCategory::Metadata,
X86VerificationCategory::Size,
X86VerificationCategory::Performance,
];
assert_eq!(cats.len(), 7);
let sevs = [
X86DiagSeverity::Note,
X86DiagSeverity::Warning,
X86DiagSeverity::Error,
X86DiagSeverity::Fatal,
];
assert_eq!(sevs.len(), 4);
let reg_sevs = [
X86RegressionSeverity::Minor,
X86RegressionSeverity::Moderate,
X86RegressionSeverity::Major,
X86RegressionSeverity::Critical,
];
assert_eq!(reg_sevs.len(), 4);
let ci_systems = [
CiSystem::GitHubActions,
CiSystem::GitLabCI,
CiSystem::Jenkins,
CiSystem::CircleCI,
CiSystem::Custom,
];
assert_eq!(ci_systems.len(), 5);
assert_eq!(TestCategory::all().len(), 12);
let artifact_types = [
X86ArtifactType::Object,
X86ArtifactType::StaticLib,
X86ArtifactType::SharedLib,
X86ArtifactType::Executable,
X86ArtifactType::Bitcode,
X86ArtifactType::Assembly,
X86ArtifactType::LlvmIr,
X86ArtifactType::DebugInfo,
X86ArtifactType::LinkerMap,
X86ArtifactType::Other,
];
assert_eq!(artifact_types.len(), 10);
}
}
#[cfg(test)]
mod boundary_tests {
use super::*;
#[test]
fn test_stage_config_with_zero_parallel_jobs() {
let mut config = X86StageConfig::new(X86BootstrapStage::Stage0, Path::new("/tmp"));
config.parallel_jobs = 0;
let args = config.to_args();
assert!(!args.iter().any(|a| a.starts_with("-j")));
}
#[test]
fn test_self_host_config_with_empty_exclude_dirs() {
let mut config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/tmp"));
config.exclude_dirs = Vec::new();
let summary = config.summary();
assert!(!summary.is_empty());
}
#[test]
fn test_cache_with_zero_max_size() {
let tmp = temp_dir();
let cache_dir = tmp.join("zero_size_cache");
let mut cache = X86BootstrapCache::new(cache_dir, 0);
cache.init().unwrap();
let stats = cache.stats();
assert_eq!(stats.max_size_bytes, 0);
cleanup_temp(&tmp);
}
#[test]
fn test_metrics_with_no_stages() {
let metrics = X86BootstrapMetrics::new();
let summary = metrics.summary();
assert!(!summary.is_empty());
assert_eq!(metrics.overall_correctness(), 0.0);
}
#[test]
fn test_verifier_with_no_checks() {
let verifier = X86BootstrapVerifier::default();
assert!(verifier.all_passed());
assert_eq!(verifier.pass_rate(), 100.0);
let summary = verifier.summary();
assert!(summary.contains("0/0"));
}
#[test]
fn test_report_with_empty_data() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/tmp"));
let report = X86BootstrapReport::new(ReportFormat::Markdown, config, Duration::ZERO);
let md = report.generate_markdown();
assert!(md.contains("llvm-native"));
}
#[test]
fn test_script_with_minimal_config() {
let config = X86SelfHostConfig::quick_verify(PathBuf::from("/tmp"));
let script = X86BootstrapScript::new(X86BootstrapScriptConfig::default(), config);
let shell = script.generate_shell_script();
assert!(shell.contains("Stage 0"));
}
#[test]
fn test_max_values_in_metrics() {
let metrics = X86StageMetrics {
stage: X86BootstrapStage::Stage0,
wall_time: Duration::from_secs(u64::MAX >> 32),
compilation_time: Duration::from_secs(u64::MAX >> 32),
link_time: Duration::from_secs(u64::MAX >> 32),
test_time: Duration::from_secs(u64::MAX >> 32),
verify_time: Duration::from_secs(u64::MAX >> 32),
files_compiled: usize::MAX,
errors: usize::MAX,
warnings: usize::MAX,
output_size: u64::MAX,
cache_hits: u64::MAX,
cache_misses: u64::MAX,
peak_memory_bytes: u64::MAX,
success: true,
retries: u32::MAX,
};
let mut m = X86BootstrapMetrics::new();
m.record_stage(X86BootstrapStage::Stage0, metrics);
let summary = m.summary();
assert!(!summary.is_empty());
}
#[test]
fn test_max_values_in_cache_entry() {
let entry = X86CacheEntry {
key: "max_test".to_string(),
source_path: PathBuf::from("/max/path/test.rs"),
cached_path: PathBuf::from("/max/cache/test.cache"),
source_hash: u64::MAX,
compiler_hash: u64::MAX,
flags_hash: u64::MAX,
output_size: u64::MAX,
created_at: UNIX_EPOCH,
accessed: true,
stage: X86BootstrapStage::Stage3,
};
let json = serde_cache_entry_to_json(&entry);
let parsed = deser_cache_entry_from_json(&json, X86BootstrapStage::Stage3).unwrap();
assert_eq!(parsed.source_hash, u64::MAX);
assert_eq!(parsed.compiler_hash, u64::MAX);
assert_eq!(parsed.flags_hash, u64::MAX);
assert_eq!(parsed.output_size, u64::MAX);
}
#[test]
fn test_long_source_path() {
let long_path = "a".repeat(500);
let config = X86StageConfig::new(X86BootstrapStage::Stage0, Path::new(&long_path));
let summary = config.summary();
assert!(!summary.is_empty());
let hash = config.config_hash();
assert_ne!(hash, 0);
}
#[test]
fn test_long_flags_list() {
let many_flags: Vec<String> = (0..1000).map(|i| format!("--flag-number-{}", i)).collect();
let cache = X86BootstrapCache::disabled();
let key = cache.make_key(Path::new("test.rs"), Path::new("rustc"), &many_flags);
assert_eq!(key.len(), 16); }
#[test]
fn test_long_error_message_in_report() {
let long_error = "E".repeat(10000);
let result = X86BootstrapStageResult {
stage: X86BootstrapStage::Stage1,
success: false,
duration: Duration::from_secs(1),
compiler_path: None,
files_compiled: 0,
binary_size: 0,
error_count: 1,
warning_count: 0,
test_results: None,
errors: vec![long_error],
retries: 0,
};
assert_eq!(result.errors.len(), 1);
assert_eq!(result.errors[0].len(), 10000);
}
#[test]
fn test_unicode_in_paths() {
let unicode_path = PathBuf::from("/tmp/projéct/src/módulo.rs");
let config = X86StageConfig::new(X86BootstrapStage::Stage1, Path::new("/tmp"));
let mut config2 = config.clone();
config2.compiler_path = unicode_path.clone();
let _hash = config2.config_hash();
let cache = X86BootstrapCache::disabled();
let _key = cache.make_key(&unicode_path, Path::new("rustc"), &[]);
}
#[test]
fn test_unicode_in_messages() {
let diag = X86CompilerDiagnostic {
severity: X86DiagSeverity::Error,
message: "エラー: 不正なトークン『⇒』".to_string(),
file: Some("テスト.rs".to_string()),
line: Some(42),
column: Some(10),
};
assert!(!diag.message.is_empty());
assert!(diag.file.unwrap().contains("テスト"));
}
#[test]
fn test_special_chars_in_flags() {
let special_flags = vec![
"-DKEY=\"value with spaces\"".to_string(),
"-I/path/with/single'quote".to_string(),
"-include header with spaces.h".to_string(),
"--cfg=feature=\"some_feature\"".to_string(),
];
let cache = X86BootstrapCache::disabled();
let key = cache.make_key(Path::new("test.rs"), Path::new("rustc"), &special_flags);
assert_eq!(key.len(), 16);
let hash = hash_string_slice(&special_flags);
assert_ne!(hash, 0);
}
#[test]
fn test_duplicate_cache_store() {
let tmp = temp_dir();
let cache_dir = tmp.join("dup_cache");
let mut cache = X86BootstrapCache::new(cache_dir, 1024 * 1024);
cache.init().unwrap();
let src = tmp.join("dup_src.rs");
let out = tmp.join("dup_out.o");
fs::write(&src, b"test").unwrap();
fs::write(&out, b"output").unwrap();
cache
.store(
X86BootstrapStage::Stage0,
&src,
Path::new("rustc"),
&[],
&out,
)
.unwrap();
cache
.store(
X86BootstrapStage::Stage0,
&src,
Path::new("rustc"),
&[],
&out,
)
.unwrap();
let stats = cache.stats();
assert!(stats.enabled);
cleanup_temp(&tmp);
}
#[test]
fn test_duplicate_stage_results() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/tmp"));
let mut report = X86BootstrapReport::new(ReportFormat::Markdown, config, Duration::ZERO);
report.add_stage_result(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage0,
success: true,
duration: Duration::from_secs(5),
compiler_path: None,
files_compiled: 100,
binary_size: 1000,
error_count: 0,
warning_count: 0,
test_results: None,
errors: Vec::new(),
retries: 0,
});
report.add_stage_result(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage0,
success: true,
duration: Duration::from_secs(6),
compiler_path: None,
files_compiled: 100,
binary_size: 1000,
error_count: 0,
warning_count: 0,
test_results: None,
errors: Vec::new(),
retries: 0,
});
assert_eq!(report.stage_results.len(), 2);
let md = report.generate_markdown();
assert!(md.contains("Stage 0"));
}
#[test]
fn test_zero_duration_metrics() {
let mut metrics = X86BootstrapMetrics::new();
metrics.record_stage(
X86BootstrapStage::Stage0,
X86StageMetrics {
stage: X86BootstrapStage::Stage0,
wall_time: Duration::ZERO,
compilation_time: Duration::ZERO,
success: true,
..Default::default()
},
);
metrics.detect_regressions(5.0);
let _summary = metrics.summary();
}
#[test]
fn test_zero_size_verification() {
let tmp = temp_dir();
let a = tmp.join("zero_a.bin");
let b = tmp.join("zero_b.bin");
fs::write(&a, b"").unwrap();
fs::write(&b, b"").unwrap();
let mut verifier = X86BootstrapVerifier::default();
let result = verifier.verify_sizes(&a, &b);
assert!(result.is_ok());
assert!(result.unwrap());
cleanup_temp(&tmp);
}
#[test]
fn test_parse_symbols_non_elf() {
let tmp = temp_dir();
let file = tmp.join("not_elf.txt");
fs::write(&file, b"this is not an ELF file\nit has some text\n").unwrap();
let verifier = X86BootstrapVerifier::default();
let symbols = verifier.extract_symbols(&file);
let _ = symbols.len();
cleanup_temp(&tmp);
}
#[test]
fn test_extract_text_section_non_elf_data() {
let verifier = X86BootstrapVerifier::default();
let data = b"Just some random data, not ELF";
let text = verifier.extract_text_section(data);
assert_eq!(text, data);
}
#[test]
fn test_large_source_collection_simulation() {
let tmp = temp_dir();
let src = tmp.join("big_src");
fs::create_dir_all(&src).unwrap();
let num_files = 100;
for i in 0..num_files {
fs::write(
src.join(format!("module_{:04}.rs", i)),
format!(
"// Module {} - some content here\nfn func_{}() {{}}\n",
i, i
),
)
.unwrap();
}
let config = X86SelfHostConfig::x86_64_linux(src.clone());
let host = X86SelfHost::new(config).unwrap();
let files = host.collect_source_files().unwrap();
assert_eq!(files.len(), num_files);
cleanup_temp(&tmp);
}
#[test]
fn test_many_cache_keys_unique() {
let cache = X86BootstrapCache::disabled();
let mut keys = HashSet::new();
let count = 5000;
for i in 0..count {
let key = cache.make_key(
Path::new(&format!("src/module_{:05}.rs", i)),
Path::new("rustc"),
&[format!("-O{}", i % 4), format!("-DFEATURE_{}", i % 100)],
);
keys.insert(key);
}
assert_eq!(keys.len(), count, "All {} keys should be unique", count);
}
#[test]
fn test_verify_identical_files_different_metadata() {
let tmp = temp_dir();
let a = tmp.join("meta_a.bin");
let b = tmp.join("meta_b.bin");
let content = vec![0x90u8; 4096]; fs::write(&a, &content).unwrap();
fs::write(&b, &content).unwrap();
let _ = fs::File::open(&b);
let mut verifier = X86BootstrapVerifier::default();
let result = verifier.verify_binary_identical(&a, &b);
assert!(result.is_ok());
assert!(result.unwrap());
cleanup_temp(&tmp);
}
}
#[test]
fn test_module_coherence() {
let stages = X86BootstrapStage::all_stages();
assert_eq!(stages.len(), 5);
for stage in &stages {
let _ = stage.index();
let _ = stage.name();
let _ = stage.short_label();
let _ = stage.produces_binary();
let _ = stage.input_compiler_stage();
let _ = stage.next();
let _ = stage.prev();
let _ = stage.dir_name();
let _ = format!("{}", stage);
}
let mut config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
let _ = config.get_stage_config(X86BootstrapStage::Stage0);
let _ = config.get_stage_config_mut(X86BootstrapStage::Stage0);
let _ = config.compiler_for_stage(X86BootstrapStage::Stage0);
let _ = config.validate();
let _ = config.summary();
let short_config = X86SelfHostConfig::quick_verify(PathBuf::from("/project"));
assert_eq!(short_config.stage_count, 2);
let release_config = X86SelfHostConfig::release_bootstrap(PathBuf::from("/project"));
assert!(release_config.strip_binaries);
let mut compiler = X86StageCompiler::new(PathBuf::from("rustc"), X86BootstrapStage::Stage0);
let _ = compiler.is_available();
let _ = compiler.get_version();
let _ = compiler.supports_feature("sse2");
let _ = compiler.stats_summary();
compiler.reset_stats();
let mut verifier = X86BootstrapVerifier::default();
assert!(verifier.all_passed());
assert_eq!(verifier.pass_rate(), 100.0);
let _ = verifier.summary();
verifier.reset();
let tmp = std::env::temp_dir().join(format!("coherence_test_{}", std::process::id()));
let _ = fs::create_dir_all(&tmp);
let cache_dir = tmp.join("coherence_cache");
let mut cache = X86BootstrapCache::new(cache_dir.clone(), 1024 * 1024);
cache.init().unwrap();
let _ = cache.stats();
cache.clear().unwrap();
let _ = fs::remove_dir_all(&tmp);
let mut disabled_cache = X86BootstrapCache::disabled();
assert!(!disabled_cache.enabled);
let _ = disabled_cache.lookup(
X86BootstrapStage::Stage0,
Path::new("test.rs"),
Path::new("rustc"),
&[],
);
let mut metrics = X86BootstrapMetrics::new();
metrics.record_stage(
X86BootstrapStage::Stage0,
X86StageMetrics {
stage: X86BootstrapStage::Stage0,
compilation_time: Duration::from_secs(10),
output_size: 100_000,
success: true,
..Default::default()
},
);
metrics.record_correctness(X86BootstrapStage::Stage0, 1.0);
metrics.detect_regressions(5.0);
metrics.finish();
let _ = metrics.summary();
let report_config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
let mut report = X86BootstrapReport::new(
ReportFormat::Markdown,
report_config,
Duration::from_secs(600),
);
report.success = true;
report.add_stage_result(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage0,
success: true,
duration: Duration::from_secs(10),
compiler_path: None,
files_compiled: 100,
binary_size: 1_000_000,
error_count: 0,
warning_count: 1,
test_results: None,
errors: Vec::new(),
retries: 0,
});
let _ = report.generate_markdown();
let _ = report.generate_html();
let _ = report.generate_text();
let _ = report.generate_json();
let script_config = X86BootstrapScriptConfig::default();
let self_config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
let script = X86BootstrapScript::new(script_config, self_config);
let _ = script.generate_shell_script();
let _ = script.generate_makefile();
let _ = script.generate_github_actions();
let _ = script.generate_gitlab_ci();
let _ = script.generate_jenkinsfile();
let _ = script.generate_circleci();
let host_config = X86SelfHostConfig::quick_verify(PathBuf::from("/project"));
let host = X86SelfHost::new(host_config).unwrap();
let _ = host.status();
let _ = host.is_successful();
let _ = host.generate_report();
assert!(!is_likely_symbol(""));
assert!(!is_likely_symbol("a"));
assert!(is_likely_symbol("valid_symbol_name"));
let _ = format_timestamp(SystemTime::now());
let _ = detect_host_info();
let _ = json_escape("test \"quoted\" string");
let _ = num_cpus();
let _ = human_readable_size(0);
let _ = human_readable_size(1024 * 1024 * 1024);
let _ = format_duration_human(Duration::from_secs(3661));
let _ = is_elf_binary(b"\x7fELF");
let _ = quick_hash(b"coherence test data");
let _ = failure_report_summary(X86BootstrapStage::Stage0, "test", Duration::ZERO);
let _ = success_report_summary(true, Duration::ZERO, 4);
let _ = X86BootstrapState::NotStarted.as_str();
let _ = X86BootstrapState::Completed.is_terminal();
let _ = X86IncrementalPlan::full_rebuild(vec![]).needs_rebuild();
let _ = X86BuildSystem::cargo()
.with_sccache(PathBuf::from("sccache"))
.env_vars();
let _ = X86ResourceLimits::developer_limits().check_limits(&X86StageMetrics::default());
let _ = X86EnvironmentDetector::detect().is_suitable_for_bootstrap();
let _ = X86ArtifactCollection::new().total_size_human();
let _ = X86BootstrapArgs::from_args(&["--stages".to_string(), "2".to_string()]);
let all_cats = TestCategory::all();
assert_eq!(all_cats.len(), 12);
for cat in &all_cats {
let _ = cat.as_str();
let _ = cat.default_enabled();
let _ = cat.is_expensive();
}
let _ = TestCategory::default_set();
let _ = TestCategory::expensive_set();
let _ = TestCategory::quick_set();
let _ = TestCategory::ci_set();
}
#[cfg(test)]
mod e2e_scenario_tests {
use super::*;
#[test]
fn test_scenario_fresh_bootstrap() {
let tmp = make_temp();
let src = tmp.join("fresh_src");
fs::create_dir_all(&src).unwrap();
for i in 0..10 {
fs::write(
src.join(format!("module_{}.rs", i)),
format!("// Module {}\npub fn func_{}() -> i32 {{ {} }}\n", i, i, i),
)
.unwrap();
}
let config = X86SelfHostConfig::x86_64_linux(src.clone());
assert_eq!(config.stage_count, 4);
for i in 1..config.stage_count {
let prev = config
.get_stage_config(X86BootstrapStage::all_build_stages()[i - 1])
.unwrap();
let curr = config
.get_stage_config(X86BootstrapStage::all_build_stages()[i])
.unwrap();
assert!(curr
.compiler_path
.to_string_lossy()
.contains(&format!("stage{}", i - 1)));
}
cleanup_temp(&tmp);
}
#[test]
fn test_scenario_incremental_rebuild() {
let all_files = vec![
PathBuf::from("src/main.rs"),
PathBuf::from("src/lib.rs"),
PathBuf::from("src/parser.rs"),
PathBuf::from("src/codegen.rs"),
PathBuf::from("src/types.rs"),
];
let plan_full = X86IncrementalPlan::full_rebuild(all_files.clone());
assert_eq!(plan_full.total_to_compile, 5);
assert_eq!(plan_full.estimated_savings_pct, 0.0);
let plan_inc = X86IncrementalPlan::incremental(
vec![
PathBuf::from("src/parser.rs"),
PathBuf::from("src/codegen.rs"),
],
vec![
PathBuf::from("src/main.rs"),
PathBuf::from("src/lib.rs"),
PathBuf::from("src/types.rs"),
],
Vec::new(),
Vec::new(),
);
assert_eq!(plan_inc.total_to_compile, 2);
assert!(plan_inc.estimated_savings_pct > 50.0);
assert!(plan_inc.needs_rebuild());
}
#[test]
fn test_scenario_stage_failure_reporting() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
let mut report =
X86BootstrapReport::new(ReportFormat::Markdown, config, Duration::from_secs(300));
report.add_stage_result(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage0,
success: true,
duration: Duration::from_secs(30),
compiler_path: Some(PathBuf::from("/build/stage0/bin/clang")),
files_compiled: 200,
binary_size: 5_000_000,
error_count: 0,
warning_count: 2,
test_results: Some(X86BootstrapTestResults {
total: 500,
passed: 500,
failed: 0,
skipped: 0,
duration: Duration::from_secs(60),
}),
errors: Vec::new(),
retries: 0,
});
report.add_stage_result(X86BootstrapStageResult {
stage: X86BootstrapStage::Stage1,
success: false,
duration: Duration::from_secs(45),
compiler_path: None,
files_compiled: 200,
binary_size: 0,
error_count: 15,
warning_count: 100,
test_results: None,
errors: vec![
"error: undefined reference to 'llvm_native_parse'".to_string(),
"error: could not compile module 'clang_codegen'".to_string(),
],
retries: 2,
});
report.success = false;
let md = report.generate_markdown();
assert!(md.contains("FAILURE"));
assert!(md.contains("Stage 0"));
assert!(md.contains("Stage 1"));
assert!(md.contains("undefined reference"));
}
#[test]
fn test_scenario_verification_detects_differences() {
let tmp = make_temp();
let s2 = tmp.join("stage2_clang");
let s3 = tmp.join("stage3_clang");
let bin2 = b"fake ELF binary stage 2 with some code and data sections";
let bin3 = b"fake ELF binary stage 3 with DIFFERENT code and data!";
fs::write(&s2, bin2).unwrap();
fs::write(&s3, bin3).unwrap();
let mut verifier = X86BootstrapVerifier::default();
let result = verifier.verify_stages(&s2, &s3);
assert!(result.is_ok());
assert!(!result.unwrap());
assert!(!verifier.results.is_empty());
let has_failure = verifier.results.iter().any(|r| !r.passed);
assert!(has_failure);
cleanup_temp(&tmp);
}
#[test]
fn test_scenario_metrics_with_perf_comparison() {
let mut metrics = X86BootstrapMetrics::new();
let stage_data = [
(X86BootstrapStage::Stage0, 15.0, 8_000_000u64, 0.97),
(X86BootstrapStage::Stage1, 18.0, 8_500_000u64, 0.98),
(X86BootstrapStage::Stage2, 20.0, 8_600_000u64, 0.99),
(X86BootstrapStage::Stage3, 19.5, 8_550_000u64, 0.995),
];
for &(stage, time_secs, size, correctness) in &stage_data {
metrics.record_stage(
stage,
X86StageMetrics {
stage,
compilation_time: Duration::from_secs_f64(time_secs),
output_size: size,
files_compiled: 200,
success: true,
..Default::default()
},
);
metrics.record_correctness(stage, correctness);
}
metrics.detect_regressions(10.0);
metrics.finish();
let time_regressions: Vec<_> = metrics
.regressions
.iter()
.filter(|r| r.metric == "compilation_time")
.collect();
assert!(!time_regressions.is_empty());
let summary = metrics.summary();
assert!(summary.contains("S0"));
assert!(summary.contains("S3"));
}
#[test]
fn test_scenario_ci_pipeline_generation() {
let config = X86SelfHostConfig::ci_profile(PathBuf::from("/workspace/llvm-native"));
let script = X86BootstrapScript::new(
X86BootstrapScriptConfig {
ci_system: CiSystem::GitHubActions,
generate_ci: true,
generate_shell: true,
..Default::default()
},
config,
);
let gh_actions = script.generate_github_actions();
assert!(gh_actions.contains("GitHub Actions"));
assert!(gh_actions.contains("bootstrap"));
assert!(gh_actions.contains("runs-on"));
let shell = script.generate_shell_script();
assert!(shell.contains("#!/usr/bin/env bash"));
assert!(shell.contains("set -euo pipefail"));
let makefile = script.generate_makefile();
assert!(makefile.contains(".PHONY:"));
assert!(makefile.contains("all: bootstrap"));
}
#[test]
fn test_scenario_multi_stage_cache() {
let tmp = make_temp();
let cache_dir = tmp.join("multi_cache");
let mut cache = X86BootstrapCache::new(cache_dir, 10 * 1024 * 1024);
cache.init().unwrap();
for stage in X86BootstrapStage::all_build_stages() {
let src = tmp.join(format!("{}.rs", stage.dir_name()));
let out = tmp.join(format!("{}.o", stage.dir_name()));
fs::write(&src, format!("// source for {}", stage.name())).unwrap();
fs::write(&out, format!("object for {}", stage.name())).unwrap();
cache
.store(
stage,
&src,
Path::new("rustc"),
&[format!("--stage={}", stage.index())],
&out,
)
.unwrap();
}
let stats = cache.stats();
let _ = stats.total_entries;
cache.clear().unwrap();
cleanup_temp(&tmp);
}
fn make_temp() -> PathBuf {
let dir = std::env::temp_dir().join(format!("e2e_test_{}", std::process::id()));
let _ = fs::create_dir_all(&dir);
dir
}
fn cleanup_temp(dir: &Path) {
let _ = fs::remove_dir_all(dir);
}
}
#[cfg(test)]
mod cli_arg_tests {
use super::*;
#[test]
fn test_args_default_values() {
let args = X86BootstrapArgs::default();
assert_eq!(args.source_dir, PathBuf::from("."));
assert_eq!(args.stage_count, 4);
assert!(args.verify);
assert!(args.report);
assert_eq!(args.report_format, ReportFormat::Markdown);
assert!(args.run_tests);
assert!(args.cache);
assert!(!args.clean);
assert!(!args.verbose);
assert!(!args.dry_run);
assert!(args.jobs >= 1);
assert_eq!(args.opt_level, "2");
assert_eq!(args.target, X86_64_LINUX_TARGET);
assert!(args.extra_flags.is_empty());
}
#[test]
fn test_args_parse_all_long_flags() {
let raw = vec![
"--source-dir".to_string(),
"/my/project".to_string(),
"--build-dir".to_string(),
"/custom/build".to_string(),
"--stages".to_string(),
"3".to_string(),
"--no-verify".to_string(),
"--no-report".to_string(),
"--no-tests".to_string(),
"--no-cache".to_string(),
"--clean".to_string(),
"--verbose".to_string(),
"--dry-run".to_string(),
"--jobs".to_string(),
"16".to_string(),
"--opt-level".to_string(),
"3".to_string(),
"--target".to_string(),
"i386-unknown-linux-gnu".to_string(),
"--report-format".to_string(),
"json".to_string(),
];
let parsed = X86BootstrapArgs::from_args(&raw).unwrap();
assert_eq!(parsed.source_dir, PathBuf::from("/my/project"));
assert_eq!(parsed.build_dir, Some(PathBuf::from("/custom/build")));
assert_eq!(parsed.stage_count, 3);
assert!(!parsed.verify);
assert!(!parsed.report);
assert!(!parsed.run_tests);
assert!(!parsed.cache);
assert!(parsed.clean);
assert!(parsed.verbose);
assert!(parsed.dry_run);
assert_eq!(parsed.jobs, 16);
assert_eq!(parsed.opt_level, "3");
assert_eq!(parsed.target, "i386-unknown-linux-gnu");
assert_eq!(parsed.report_format, ReportFormat::Json);
}
#[test]
fn test_args_parse_all_short_flags() {
let raw = vec![
"-s".to_string(),
"/proj".to_string(),
"-b".to_string(),
"/build".to_string(),
"-n".to_string(),
"2".to_string(),
"-v".to_string(),
"-j".to_string(),
"8".to_string(),
"-O".to_string(),
"s".to_string(),
];
let parsed = X86BootstrapArgs::from_args(&raw).unwrap();
assert_eq!(parsed.source_dir, PathBuf::from("/proj"));
assert_eq!(parsed.build_dir, Some(PathBuf::from("/build")));
assert_eq!(parsed.stage_count, 2);
assert!(parsed.verbose);
assert_eq!(parsed.jobs, 8);
assert_eq!(parsed.opt_level, "s");
}
#[test]
fn test_args_missing_source_dir_value() {
let result = X86BootstrapArgs::from_args(&["--source-dir".to_string()]);
assert!(result.is_err());
}
#[test]
fn test_args_missing_build_dir_value() {
let result = X86BootstrapArgs::from_args(&["--build-dir".to_string()]);
assert!(result.is_err());
}
#[test]
fn test_args_missing_stages_value() {
let result = X86BootstrapArgs::from_args(&["--stages".to_string()]);
assert!(result.is_err());
}
#[test]
fn test_args_invalid_stages_negative() {
let result = X86BootstrapArgs::from_args(&["--stages".to_string(), "-1".to_string()]);
assert!(result.is_err());
}
#[test]
fn test_args_invalid_jobs_value() {
let result =
X86BootstrapArgs::from_args(&["--jobs".to_string(), "not_a_number".to_string()]);
assert!(result.is_err());
}
#[test]
fn test_args_extra_flags_captured() {
let result = X86BootstrapArgs::from_args(&[
"-DDEBUG".to_string(),
"-DFEATURE_X=1".to_string(),
"-Wall".to_string(),
"-Wextra".to_string(),
"--release".to_string(),
]);
assert!(result.is_ok());
let args = result.unwrap();
assert_eq!(args.extra_flags.len(), 5);
assert!(args.extra_flags.contains(&"-DDEBUG".to_string()));
assert!(args.extra_flags.contains(&"-Wall".to_string()));
}
#[test]
fn test_args_report_format_all_variants() {
for (input, expected) in &[
("markdown", ReportFormat::Markdown),
("html", ReportFormat::Html),
("text", ReportFormat::Text),
("json", ReportFormat::Json),
] {
let result =
X86BootstrapArgs::from_args(&["--report-format".to_string(), input.to_string()]);
assert!(result.is_ok());
assert_eq!(result.unwrap().report_format, *expected);
}
}
#[test]
fn test_args_report_format_unknown_defaults_to_markdown() {
let result =
X86BootstrapArgs::from_args(&["--report-format".to_string(), "pdf".to_string()]);
assert!(result.is_ok());
assert_eq!(result.unwrap().report_format, ReportFormat::Markdown);
}
#[test]
fn test_args_to_config_mapping() {
let args = X86BootstrapArgs {
source_dir: PathBuf::from("/workspace/project"),
build_dir: Some(PathBuf::from("/workspace/build")),
stage_count: 4,
verify: true,
report: true,
report_format: ReportFormat::Html,
run_tests: true,
cache: true,
clean: true,
verbose: true,
dry_run: false,
jobs: 8,
opt_level: "3".to_string(),
target: "x86_64-unknown-linux-gnu".to_string(),
extra_flags: vec!["-Wall".to_string()],
};
let config = args.to_config();
assert_eq!(config.source_dir, PathBuf::from("/workspace/project"));
assert_eq!(config.build_dir, PathBuf::from("/workspace/build"));
assert_eq!(config.stage_count, 4);
assert!(config.verify_stages);
assert!(config.generate_report);
assert_eq!(config.report_format, ReportFormat::Html);
assert!(config.test_suite_enabled);
assert!(config.enable_caching);
assert!(config.clean_build);
assert!(config.verbose);
assert!(!config.dry_run);
for i in 0..config.stage_count {
let sc = config
.get_stage_config(X86BootstrapStage::all_build_stages()[i])
.unwrap();
assert_eq!(sc.opt_level, "3");
assert_eq!(sc.target_triple, "x86_64-unknown-linux-gnu");
assert_eq!(sc.parallel_jobs, 8);
}
}
#[test]
fn test_args_to_config_default_build_dir() {
let args = X86BootstrapArgs {
source_dir: PathBuf::from("/workspace/project"),
build_dir: None,
..Default::default()
};
let config = args.to_config();
assert_eq!(
config.build_dir,
PathBuf::from("/workspace/project/target/bootstrap")
);
}
}
#[cfg(test)]
mod diagnostic_format_tests {
use super::*;
#[test]
fn test_compilation_output_construction() {
let output = X86CompilationOutput {
stdout: "Compilation successful".to_string(),
stderr: String::new(),
exit_status: 0,
success: true,
duration: Duration::from_secs(2),
warning_count: 0,
error_count: 0,
output_size: 8192,
diagnostics: Vec::new(),
};
assert!(output.success);
assert_eq!(output.exit_status, 0);
assert_eq!(output.output_size, 8192);
}
#[test]
fn test_compilation_output_with_diagnostics() {
let output = X86CompilationOutput {
stdout: String::new(),
stderr: "error: type mismatch at src/main.rs:42:10\nwarning: unused import at src/lib.rs:5:1\n".to_string(),
exit_status: 1,
success: false,
duration: Duration::from_secs(1),
warning_count: 1,
error_count: 1,
output_size: 0,
diagnostics: vec![
X86CompilerDiagnostic {
severity: X86DiagSeverity::Error,
message: "type mismatch".to_string(),
file: Some("src/main.rs".to_string()),
line: Some(42),
column: Some(10),
},
X86CompilerDiagnostic {
severity: X86DiagSeverity::Warning,
message: "unused import".to_string(),
file: Some("src/lib.rs".to_string()),
line: Some(5),
column: Some(1),
},
],
};
assert!(!output.success);
assert_eq!(output.warning_count, 1);
assert_eq!(output.error_count, 1);
assert_eq!(output.diagnostics.len(), 2);
}
#[test]
fn test_diag_severity_display_order() {
let mut severities = vec![
X86DiagSeverity::Fatal,
X86DiagSeverity::Error,
X86DiagSeverity::Warning,
X86DiagSeverity::Note,
];
severities.sort();
assert_eq!(severities[0], X86DiagSeverity::Note);
assert_eq!(severities[1], X86DiagSeverity::Warning);
assert_eq!(severities[2], X86DiagSeverity::Error);
assert_eq!(severities[3], X86DiagSeverity::Fatal);
}
#[test]
fn test_parse_source_location_edge_cases() {
let compiler = X86StageCompiler::new(PathBuf::from("rustc"), X86BootstrapStage::Stage0);
let (f, l, c) = compiler.parse_source_location("just a message without location");
assert_eq!(f, None);
assert_eq!(l, None);
assert_eq!(c, None);
let (f, l, c) = compiler.parse_source_location("error at path/to/file.rs:123");
assert_eq!(f, Some("path/to/file.rs".to_string()));
assert_eq!(l, Some(123));
assert_eq!(c, None);
let (f, l, c) = compiler.parse_source_location("(src/main.rs:42:10) something wrong");
assert_eq!(f, Some("src/main.rs".to_string()));
assert_eq!(l, Some(42));
assert_eq!(c, Some(10));
let (f, l, c) = compiler.parse_source_location("[src/lib.rs:5:1]: warning message");
assert_eq!(f, Some("src/lib.rs".to_string()));
assert_eq!(l, Some(5));
assert_eq!(c, Some(1));
}
#[test]
fn test_compiler_parse_diagnostics_variety() {
let compiler = X86StageCompiler::new(PathBuf::from("rustc"), X86BootstrapStage::Stage0);
let stderr = r#"error: expected ';' after expression
--> src/main.rs:10:5
warning: unused variable 'x'
--> src/lib.rs:20:9
note: 'x' is defined here
--> src/lib.rs:19:5
fatal error: too many errors emitted
"#;
let diags = compiler.parse_diagnostics(stderr);
assert_eq!(diags.len(), 4);
assert_eq!(diags[0].severity, X86DiagSeverity::Error);
assert!(diags[0].message.contains("expected"));
assert_eq!(diags[1].severity, X86DiagSeverity::Warning);
assert!(diags[1].message.contains("unused"));
assert_eq!(diags[2].severity, X86DiagSeverity::Note);
assert_eq!(diags[3].severity, X86DiagSeverity::Fatal);
}
#[test]
fn test_compiler_count_warnings_errors_edge_cases() {
let compiler = X86StageCompiler::new(PathBuf::from("rustc"), X86BootstrapStage::Stage0);
let (w, e) = compiler.count_warnings_errors("");
assert_eq!(w, 0);
assert_eq!(e, 0);
let (w, e) = compiler.count_warnings_errors("warning: deprecated\nwarning: unused\n");
assert_eq!(w, 2);
assert_eq!(e, 0);
let (w, e) = compiler.count_warnings_errors("error: type error\nerror: syntax error\n");
assert_eq!(w, 0);
assert_eq!(e, 2);
let (w, e) = compiler.count_warnings_errors(
"error[E0308]: mismatched types\nwarning[W0401]: unused import\n",
);
assert_eq!(w, 1);
assert_eq!(e, 1);
}
}
#[cfg(test)]
mod advanced_tests {
use super::*;
#[test]
fn test_human_readable_size_all_units() {
assert_eq!(human_readable_size(0), "0.0 B");
assert_eq!(human_readable_size(1), "1.0 B");
assert_eq!(human_readable_size(512), "512.0 B");
assert_eq!(human_readable_size(1023), "1023.0 B");
let kib = human_readable_size(1024);
assert!(kib.contains("KiB"));
let kib2 = human_readable_size(1536);
assert!(kib2.contains("KiB"));
let mib = human_readable_size(1048576);
assert!(mib.contains("MiB"));
let mib2 = human_readable_size(50 * 1024 * 1024);
assert!(mib2.contains("MiB"));
let gib = human_readable_size(1073741824);
assert!(gib.contains("GiB"));
let gib2 = human_readable_size(5u64 * 1024 * 1024 * 1024);
assert!(gib2.contains("GiB"));
let tib = human_readable_size(1099511627776);
assert!(tib.contains("TiB"));
}
#[test]
fn test_artifact_source_tracking() {
let artifact = X86BuildArtifact {
path: PathBuf::from("/build/stage2/bin/llvm-native-clang"),
stage: X86BootstrapStage::Stage2,
artifact_type: X86ArtifactType::Executable,
size: 8_500_000,
hash: 0xDEAD_BEEF,
produced_at: SystemTime::now(),
source_files: vec![
PathBuf::from("src/main.rs"),
PathBuf::from("src/clang/mod.rs"),
PathBuf::from("src/x86/x86_codegen.rs"),
],
};
assert_eq!(artifact.source_files.len(), 3);
assert_eq!(artifact.artifact_type, X86ArtifactType::Executable);
assert!(artifact.size > 0);
}
#[test]
fn test_multi_stage_artifact_collection() {
let mut coll = X86ArtifactCollection::new();
for i in 0..50 {
coll.add(X86BuildArtifact {
path: PathBuf::from(format!("/build/stage0/obj{}.o", i)),
stage: X86BootstrapStage::Stage0,
artifact_type: X86ArtifactType::Object,
size: 4096,
hash: i,
produced_at: UNIX_EPOCH,
source_files: Vec::new(),
});
}
for i in 0..10 {
coll.add(X86BuildArtifact {
path: PathBuf::from(format!("/build/stage1/lib{}.a", i)),
stage: X86BootstrapStage::Stage1,
artifact_type: X86ArtifactType::StaticLib,
size: 200_000,
hash: 1000 + i,
produced_at: UNIX_EPOCH,
source_files: Vec::new(),
});
}
coll.add(X86BuildArtifact {
path: PathBuf::from("/build/stage2/bin/llvm-native-clang"),
stage: X86BootstrapStage::Stage2,
artifact_type: X86ArtifactType::Executable,
size: 8_500_000,
hash: 9999,
produced_at: UNIX_EPOCH,
source_files: Vec::new(),
});
assert_eq!(coll.total_count, 61);
assert!(coll.total_size > 0);
assert_eq!(coll.for_stage(X86BootstrapStage::Stage0).len(), 50);
assert_eq!(coll.for_stage(X86BootstrapStage::Stage1).len(), 10);
assert_eq!(coll.for_stage(X86BootstrapStage::Stage2).len(), 1);
assert!(coll.for_stage(X86BootstrapStage::Stage3).is_empty());
}
#[test]
fn test_all_types_debug_format() {
let types_debug: Vec<String> = vec![
format!("{:?}", X86BootstrapStage::Stage0),
format!("{:?}", X86BootstrapStage::Comparison),
format!("{:?}", X86StageConfig::default()),
format!("{:?}", X86SelfHostConfig::default()),
format!("{:?}", X86BootstrapVerifier::default()),
format!("{:?}", X86BootstrapCache::disabled()),
format!("{:?}", X86BootstrapMetrics::default()),
format!("{:?}", X86StageMetrics::default()),
format!(
"{:?}",
X86CompilationOutput {
stdout: "test".to_string(),
stderr: String::new(),
exit_status: 0,
success: true,
duration: Duration::from_secs(1),
warning_count: 0,
error_count: 0,
output_size: 100,
diagnostics: Vec::new(),
}
),
format!("{:?}", X86BootstrapState::NotStarted),
format!("{:?}", X86IncrementalPlan::full_rebuild(vec![])),
format!("{:?}", X86BuildSystem::default()),
format!("{:?}", X86ResourceLimits::default()),
format!("{:?}", X86BootstrapArgs::default()),
format!(
"{:?}",
X86BootstrapReport::new(
ReportFormat::Markdown,
X86SelfHostConfig::default(),
Duration::ZERO,
)
),
];
for (i, debug_str) in types_debug.iter().enumerate() {
assert!(
!debug_str.is_empty(),
"Debug output empty for type index {}",
i
);
}
}
#[test]
fn test_script_pipeline_complete() {
let self_config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/complete/project"));
let mut script_config = X86BootstrapScriptConfig::default();
script_config.output_dir = PathBuf::from("/tmp/scripts_out");
script_config.generate_shell = true;
script_config.generate_makefile = true;
script_config.generate_ci = true;
script_config.ci_system = CiSystem::GitHubActions;
script_config.include_setup = true;
script_config.include_cleanup = false;
let script = X86BootstrapScript::new(script_config, self_config);
let shell = script.generate_shell_script();
assert!(shell.starts_with("#!/usr/bin/env bash"));
assert!(shell.contains("Stage 0"));
assert!(shell.contains("Stage 1"));
assert!(shell.contains("Stage 2"));
assert!(shell.contains("Stage 3"));
assert!(shell.contains("Verifying"));
let mf = script.generate_makefile();
assert!(mf.contains("stage0:"));
assert!(mf.contains("stage1: stage0"));
assert!(mf.contains("stage2: stage1"));
assert!(mf.contains("stage3: stage2"));
assert!(mf.contains("clean:"));
let (ci_name, ci_content) = script.generate_ci_config();
assert_eq!(ci_name, "bootstrap.yml");
assert!(ci_content.contains("llvm-native X86 Bootstrap"));
assert!(ci_content.contains("ubuntu-latest"));
}
#[test]
fn test_self_host_all_stage_compilers_initialized() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/full/project"));
let host = X86SelfHost::new(config).unwrap();
for stage in X86BootstrapStage::all_build_stages() {
let compiler = host.compiler_for_stage(stage);
assert!(compiler.is_some());
assert_eq!(compiler.unwrap().stage, stage);
}
assert!(host
.compiler_for_stage(X86BootstrapStage::Comparison)
.is_none());
}
#[test]
fn test_report_custom_title() {
let config = X86SelfHostConfig::x86_64_linux(PathBuf::from("/project"));
let mut report =
X86BootstrapReport::new(ReportFormat::Markdown, config, Duration::from_secs(300));
report.title = "Custom Bootstrap Report Title".to_string();
report.success = true;
let md = report.generate_markdown();
assert!(md.starts_with("# Custom Bootstrap Report Title"));
}
#[test]
fn test_format_duration_common_values() {
assert_eq!(format_duration_human(Duration::from_secs(0)), "0s");
assert_eq!(format_duration_human(Duration::from_secs(1)), "1s");
assert_eq!(format_duration_human(Duration::from_secs(60)), "1m 0s");
assert_eq!(format_duration_human(Duration::from_secs(61)), "1m 1s");
assert_eq!(format_duration_human(Duration::from_secs(3600)), "1h 0m 0s");
assert_eq!(format_duration_human(Duration::from_secs(3661)), "1h 1m 1s");
assert_eq!(format_duration_human(Duration::from_secs(7200)), "2h 0m 0s");
}
}
#[test]
fn test_complete_bootstrap_pipeline_walkthrough() {
let config = X86SelfHostConfig::ci_profile(PathBuf::from("/workspace/llvm-native"));
assert_eq!(config.stage_count, 4);
assert!(config.verify_stages);
let validation = config.validate();
let _ = validation;
let tmp = std::env::temp_dir().join(format!("walkthrough_{}", std::process::id()));
let mut walkthrough_config = config.clone();
walkthrough_config.build_dir = tmp.join("build");
walkthrough_config.cache_dir = Some(tmp.join("cache"));
walkthrough_config.create_dirs().ok();
let mut cache = X86BootstrapCache::new(
walkthrough_config.cache_dir.clone().unwrap(),
walkthrough_config.max_cache_size_bytes,
);
cache.init().unwrap();
let mut compilers = BTreeMap::new();
for stage in X86BootstrapStage::all_build_stages() {
let compiler = X86StageCompiler::new(
walkthrough_config
.build_dir
.join(format!("{}/bin/llvm-native-clang", stage.dir_name())),
stage,
);
compilers.insert(stage, compiler);
}
assert_eq!(compilers.len(), 4);
let mut metrics = X86BootstrapMetrics::new();
assert!(metrics.enabled);
for stage in X86BootstrapStage::all_build_stages() {
metrics.record_stage(
stage,
X86StageMetrics {
stage,
compilation_time: Duration::from_secs(60 + stage.index() as u64 * 30),
files_compiled: 200,
output_size: 5_000_000 + stage.index() as u64 * 1_000_000,
success: true,
..Default::default()
},
);
}
metrics.finish();
metrics.detect_regressions(10.0);
let mut report = X86BootstrapReport::new(
ReportFormat::Markdown,
walkthrough_config.clone(),
metrics.total_wall_time,
);
for stage in X86BootstrapStage::all_build_stages() {
report.add_stage_result(X86BootstrapStageResult {
stage,
success: true,
duration: Duration::from_secs(60),
compiler_path: Some(
walkthrough_config
.build_dir
.join(format!("{}/bin/llvm-native-clang", stage.dir_name())),
),
files_compiled: 200,
binary_size: 5_000_000,
error_count: 0,
warning_count: stage.index(),
test_results: Some(X86BootstrapTestResults {
total: 500,
passed: 498,
failed: 2,
skipped: 0,
duration: Duration::from_secs(120),
}),
errors: Vec::new(),
retries: 0,
});
}
report.metrics = metrics;
let _md = report.generate_markdown();
let _html = report.generate_html();
let _text = report.generate_text();
let _json = report.generate_json();
let script = X86BootstrapScript::new(
X86BootstrapScriptConfig::default(),
walkthrough_config.clone(),
);
let _shell = script.generate_shell_script();
let _makefile = script.generate_makefile();
cache.clear().unwrap();
let _ = fs::remove_dir_all(&tmp);
}