diff --git a/src/runner/modes/llvm/error.rs b/src/runner/modes/llvm/error.rs new file mode 100644 index 00000000..3b541c9f --- /dev/null +++ b/src/runner/modes/llvm/error.rs @@ -0,0 +1,33 @@ +//! LLVM mode error types + +use std::fmt; + +/// LLVM mode execution error +#[derive(Debug)] +pub struct LlvmRunError { + pub code: i32, + pub msg: String, +} + +impl LlvmRunError { + /// Create new error + pub fn new(code: i32, msg: impl Into) -> Self { + Self { + code, + msg: msg.into(), + } + } + + /// Create error with exit code 1 + pub fn fatal(msg: impl Into) -> Self { + Self::new(1, msg) + } +} + +impl fmt::Display for LlvmRunError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.msg) + } +} + +impl std::error::Error for LlvmRunError {} diff --git a/src/runner/modes/llvm/fallback_executor.rs b/src/runner/modes/llvm/fallback_executor.rs index d6a81608..1a9d40ad 100644 --- a/src/runner/modes/llvm/fallback_executor.rs +++ b/src/runner/modes/llvm/fallback_executor.rs @@ -3,12 +3,13 @@ //! Handles fallback execution when LLVM backends are not available. use nyash_rust::{mir::MirModule, mir::MirInstruction}; +use super::error::LlvmRunError; /// Fallback executor Box /// /// **Responsibility**: Execute fallback path (feature check + mock) /// **Input**: &MirModule -/// **Output**: i32 (exit code) +/// **Output**: Result (Ok(exit_code) on success, Err on failure) pub struct FallbackExecutorBox; impl FallbackExecutorBox { @@ -20,18 +21,14 @@ impl FallbackExecutorBox { /// /// Otherwise, executes mock execution that inspects the MIR /// and returns a deterministic exit code based on Return instructions. - pub fn execute(module: &MirModule) -> i32 { + pub fn execute(module: &MirModule) -> Result { // Fail-fast: if the user explicitly requested the llvmlite harness // but this binary was built without the `llvm-harness` feature, // do not silently fall back to mock. if crate::config::env::env_bool("NYASH_LLVM_USE_HARNESS") { - crate::console_println!( - "❌ LLVM harness requested (NYASH_LLVM_USE_HARNESS=1), but this binary was built without `--features llvm` (llvm-harness)." - ); - crate::console_println!( - " Fix: cargo build --release --features llvm" - ); - return 1; + return Err(LlvmRunError::fatal( + "LLVM harness requested (NYASH_LLVM_USE_HARNESS=1), but this binary was built without `--features llvm` (llvm-harness). Fix: cargo build --release --features llvm" + )); } crate::console_println!("🔧 Mock LLVM Backend Execution:"); @@ -46,11 +43,11 @@ impl FallbackExecutorBox { match inst { MirInstruction::Return { value: Some(_) } => { crate::console_println!("✅ Mock exit code: 42"); - return 42; + return Ok(42); } MirInstruction::Return { value: None } => { crate::console_println!("✅ Mock exit code: 0"); - return 0; + return Ok(0); } _ => {} } @@ -59,6 +56,6 @@ impl FallbackExecutorBox { } crate::console_println!("✅ Mock exit code: 0"); - 0 + Ok(0) } } diff --git a/src/runner/modes/llvm/harness_executor.rs b/src/runner/modes/llvm/harness_executor.rs index c1386035..cc8e956c 100644 --- a/src/runner/modes/llvm/harness_executor.rs +++ b/src/runner/modes/llvm/harness_executor.rs @@ -3,12 +3,13 @@ //! Handles execution via LLVM harness when available (feature-gated). use nyash_rust::mir::MirModule; +use super::error::LlvmRunError; /// Harness executor Box /// /// **Responsibility**: Execute via LLVM harness (native executable generation and execution) /// **Input**: &MirModule -/// **Output**: Option (Some(exit_code) if executed, None otherwise) +/// **Output**: Result (Ok(exit_code) if executed, Err if failed) pub struct HarnessExecutorBox; impl HarnessExecutorBox { @@ -19,59 +20,48 @@ impl HarnessExecutorBox { /// 2. Executes the generated executable /// 3. Returns the exit code /// - /// Returns Some(exit_code) if executed, None if harness not available. + /// Returns Ok(exit_code) on success, Err(LlvmRunError) on failure. #[cfg(feature = "llvm-harness")] - pub fn try_execute(module: &MirModule) -> Option { + pub fn try_execute(module: &MirModule) -> Result { if !crate::config::env::llvm_use_harness() { - return None; + return Err(LlvmRunError::fatal("LLVM harness not enabled (NYASH_LLVM_USE_HARNESS not set)")); } // Prefer producing a native executable via ny-llvmc, then execute it let exe_out = "tmp/nyash_llvm_run"; let libs = std::env::var("NYASH_LLVM_EXE_LIBS").ok(); - match crate::runner::modes::common_util::exec::ny_llvmc_emit_exe_lib( + crate::runner::modes::common_util::exec::ny_llvmc_emit_exe_lib( module, exe_out, None, libs.as_deref(), - ) { - Ok(()) => { - match crate::runner::modes::common_util::exec::run_executable( - exe_out, - &[], - 20_000, - ) { - Ok((code, _timed_out, stdout_text)) => { - // Forward program stdout so parity tests can compare outputs - if !stdout_text.is_empty() { - print!("{}", stdout_text); - } - crate::console_println!( - "✅ LLVM (harness) execution completed (exit={})", - code - ); + ).map_err(|e| LlvmRunError::fatal(format!("ny-llvmc emit-exe error: {} (Hint: build ny-llvmc: cargo build -p nyash-llvm-compiler --release)", e)))?; - Some(code) - } - Err(e) => { - crate::console_println!("❌ run executable error: {}", e); - Some(1) - } + match crate::runner::modes::common_util::exec::run_executable( + exe_out, + &[], + 20_000, + ) { + Ok((code, _timed_out, stdout_text)) => { + // Forward program stdout so parity tests can compare outputs + if !stdout_text.is_empty() { + print!("{}", stdout_text); } + crate::console_println!( + "✅ LLVM (harness) execution completed (exit={})", + code + ); + Ok(code) } Err(e) => { - crate::console_println!("❌ ny-llvmc emit-exe error: {}", e); - crate::console_println!( - " Hint: build ny-llvmc: cargo build -p nyash-llvm-compiler --release" - ); - Some(1) + Err(LlvmRunError::fatal(format!("run executable error: {}", e))) } } } #[cfg(not(feature = "llvm-harness"))] - pub fn try_execute(_module: &MirModule) -> Option { - None + pub fn try_execute(_module: &MirModule) -> Result { + Err(LlvmRunError::fatal("LLVM harness feature not enabled (built without --features llvm)")) } } diff --git a/src/runner/modes/llvm/mod.rs b/src/runner/modes/llvm/mod.rs index 1ba3b682..06933dc1 100644 --- a/src/runner/modes/llvm/mod.rs +++ b/src/runner/modes/llvm/mod.rs @@ -13,22 +13,25 @@ mod joinir_experiment; mod object_emitter; mod harness_executor; mod fallback_executor; +mod error; +mod report; + +// Re-export error types for convenience +use self::error::LlvmRunError; impl NyashRunner { /// Execute LLVM mode (split) pub(crate) fn execute_llvm_mode(&self, filename: &str) { // Step 1: Plugin initialization if let Err(e) = plugin_init::PluginInitBox::init() { - crate::console_println!("❌ Plugin init error: {}", e); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("Plugin init error: {}", e))); } // Read the file let code = match fs::read_to_string(filename) { Ok(content) => content, Err(e) => { - crate::console_println!("❌ Error reading file {}: {}", filename, e); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("Error reading file {}: {}", filename, e))); } }; @@ -36,8 +39,7 @@ impl NyashRunner { let (clean_code, prelude_asts) = match using_resolver::UsingResolverBox::resolve(self, &code, filename) { Ok(result) => result, Err(e) => { - crate::console_println!("❌ {}", e); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("{}", e))); } }; @@ -68,7 +70,7 @@ impl NyashRunner { .debug(&format!(" ... ({} more)", preludes.len() - show)); } } - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("Parse error: {}", e))); } }; // Merge preludes + main when enabled @@ -89,8 +91,7 @@ impl NyashRunner { let mut module = match mir_compiler::MirCompilerBox::compile(ast, Some(filename)) { Ok(m) => m, Err(e) => { - crate::console_println!("❌ {}", e); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("{}", e))); } }; @@ -102,8 +103,12 @@ impl NyashRunner { let module = joinir_experiment::JoinIrExperimentBox::apply(module); // Dev/Test helper: allow executing via PyVM harness when requested - if let Some(code) = pyvm_executor::PyVmExecutorBox::try_execute(&module) { - exit_reporter::ExitReporterBox::emit_and_exit(code); + match pyvm_executor::PyVmExecutorBox::try_execute(&module) { + Ok(code) => exit_reporter::ExitReporterBox::emit_and_exit(code), + Err(e) if e.code == 0 && e.msg == "PyVM not requested" => { + // Continue to next executor + } + Err(e) => report::emit_error_and_exit(e), } // If explicit object path is requested, emit object only @@ -113,8 +118,7 @@ impl NyashRunner { // Harness path (optional): if NYASH_LLVM_USE_HARNESS=1, try Python/llvmlite first. if crate::config::env::llvm_use_harness() { if let Err(e) = object_emitter::ObjectEmitterBox::try_emit(&module) { - crate::console_println!("❌ {}", e); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("{}", e))); } return; } @@ -122,8 +126,7 @@ impl NyashRunner { match std::fs::metadata(&_out_path) { Ok(meta) => { if meta.len() == 0 { - crate::console_println!("❌ harness object is empty: {}", _out_path); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("harness object is empty: {}", _out_path))); } if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { crate::console_println!( @@ -134,12 +137,7 @@ impl NyashRunner { } } Err(e) => { - crate::console_println!( - "❌ harness output not found after emit: {} ({})", - _out_path, - e - ); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("harness output not found after emit: {} ({})", _out_path, e))); } } return; @@ -159,8 +157,7 @@ impl NyashRunner { .unwrap_or_default() ); if let Err(e) = llvm_compile_to_object(&module, &_out_path) { - crate::console_println!("❌ LLVM object emit error: {}", e); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("LLVM object emit error: {}", e))); } match std::fs::metadata(&_out_path) { Ok(meta) if meta.len() > 0 => { @@ -171,22 +168,27 @@ impl NyashRunner { ); } _ => { - crate::console_println!("❌ LLVM object not found or empty: {}", _out_path); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("LLVM object not found or empty: {}", _out_path))); } } return; } #[cfg(all(not(feature = "llvm-harness"), not(feature = "llvm-inkwell-legacy")))] { - crate::console_println!("❌ LLVM backend not available (object emit)."); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal("LLVM backend not available (object emit)")); } } // Execute via LLVM backend (harness preferred) - if let Some(code) = harness_executor::HarnessExecutorBox::try_execute(&module) { - exit_reporter::ExitReporterBox::emit_and_exit(code); + match harness_executor::HarnessExecutorBox::try_execute(&module) { + Ok(code) => exit_reporter::ExitReporterBox::emit_and_exit(code), + Err(e) => { + // If harness failed, try fallback path + match fallback_executor::FallbackExecutorBox::execute(&module) { + Ok(code) => exit_reporter::ExitReporterBox::emit_and_exit(code), + Err(fallback_err) => report::emit_error_and_exit(fallback_err), + } + } } // Execute via LLVM backend (mock or real) @@ -209,16 +211,10 @@ impl NyashRunner { } } Err(e) => { - crate::console_println!("❌ LLVM execution error: {}", e); - exit_reporter::ExitReporterBox::emit_and_exit(1); + report::emit_error_and_exit(LlvmRunError::fatal(format!("LLVM execution error: {}", e))); } } } - #[cfg(all(not(feature = "llvm-inkwell-legacy")))] - { - let code = fallback_executor::FallbackExecutorBox::execute(&module); - exit_reporter::ExitReporterBox::emit_and_exit(code); - } } } diff --git a/src/runner/modes/llvm/pyvm_executor.rs b/src/runner/modes/llvm/pyvm_executor.rs index 35c441e8..b593c4f2 100644 --- a/src/runner/modes/llvm/pyvm_executor.rs +++ b/src/runner/modes/llvm/pyvm_executor.rs @@ -3,12 +3,13 @@ //! Handles execution via PyVM harness when requested for development/testing. use nyash_rust::mir::MirModule; +use super::error::LlvmRunError; /// PyVM executor Box /// /// **Responsibility**: Execute via PyVM harness when requested (dev/test helper) /// **Input**: &MirModule -/// **Output**: Option (Some(exit_code) if executed, None otherwise) +/// **Output**: Result (Ok(exit_code) on success, Err if failed or not requested) /// /// **IMPORTANT**: This Box is used by 8 JSON AST smoke tests. DO NOT REMOVE! pub struct PyVmExecutorBox; @@ -19,18 +20,14 @@ impl PyVmExecutorBox { /// This function checks the SMOKES_USE_PYVM environment variable. /// If set to "1", it executes the module via PyVM harness and returns the exit code. /// - /// Returns Some(exit_code) if executed, None otherwise. - pub fn try_execute(module: &MirModule) -> Option { + /// Returns Ok(exit_code) on success, Err(LlvmRunError) on failure. + /// If SMOKES_USE_PYVM is not set, returns Err with a special "not requested" message. + pub fn try_execute(module: &MirModule) -> Result { if std::env::var("SMOKES_USE_PYVM").ok().as_deref() != Some("1") { - return None; + return Err(LlvmRunError::new(0, "PyVM not requested")); } - match super::super::common_util::pyvm::run_pyvm_harness_lib(module, "llvm-ast") { - Ok(code) => Some(code), - Err(e) => { - crate::console_println!("❌ PyVM harness error: {}", e); - Some(1) - } - } + super::super::common_util::pyvm::run_pyvm_harness_lib(module, "llvm-ast") + .map_err(|e| LlvmRunError::fatal(format!("PyVM harness error: {}", e))) } } diff --git a/src/runner/modes/llvm/report.rs b/src/runner/modes/llvm/report.rs new file mode 100644 index 00000000..8defb578 --- /dev/null +++ b/src/runner/modes/llvm/report.rs @@ -0,0 +1,18 @@ +//! Error reporting for LLVM mode +//! +//! This is the ONLY place that calls ExitReporterBox::emit_and_exit. + +use super::error::LlvmRunError; +use super::exit_reporter::ExitReporterBox; + +/// Emit error and exit process +/// +/// This function: +/// 1. Prints error message +/// 2. Calls ExitReporterBox::emit_and_exit (which includes leak report) +/// +/// This is the SINGLE exit point for all LLVM mode errors. +pub fn emit_error_and_exit(err: LlvmRunError) -> ! { + crate::console_println!("❌ {}", err.msg); + ExitReporterBox::emit_and_exit(err.code); +}