vj 0.2.0

vj: a SystemVerilog (IEEE 1800-2023) simulator built on slang and LLVM.
//! Task 17 (`docs/superpowers/plans/2026-09-23-wave-A-abi.md`; ledger
//! AB-012): the CLI's pipeline-**order** contract -- "elaborate -> codegen ->
//! (JIT run | AOT compile+link+run)" -- as a small, independently testable
//! surface, deliberately decoupled from any one pipeline's real bodies.
//!
//! **Why this is a separate stub-staged module, not an instrumented copy of
//! `run::run_design` (deviation note, tracked here per this track's own
//! "adapt minimally... list it in deviations" instruction):** this plan's
//! Task 17 text was written against a `vj` that was still a no-op
//! stub (`main.rs` touching one real item per dependency edge, per this
//! file's own crate-level `docs/CONTRACTS.md` entry at the time). By the
//! time this task actually ran, a real, single-mode (JIT-only) pipeline had
//! already landed from the parallel wave-A2 "slice"/"slice-finish" plans
//! (`run::run_design`, ledger CG-125/NL-035) -- a full elaborate -> STATE
//! layout -> per-process CFG/liveness/codegen -> ORC-JIT -> `sched::Engine`
//! run, with no AOT leg and no CLI mode flag at all. Retrofitting a real,
//! general source-to-`.so` AOT execution path (a second, coarser-grained
//! design-level ABI realized through `loader::LoadedKernel`'s whole-of-
//! simulation `sim_init`/`sim_eval_region`/... entry points, as opposed to
//! `run_design`'s finer-grained per-process-kernel-function-plus-Rust-
//! `Engine` realization) is a real architectural decision spanning the
//! `codegen`/`sched`/`state` tracks' own ownership, not something this one
//! `abi`-track task should improvise inside `run_design` -- exactly the
//! "real bodies are later waves' work" scoping this plan's Task 17 already
//! states explicitly for its own stub stages. So AB-012's pipeline-*order*
//! contract is proven here, on its own, honestly stub-staged surface (this
//! module), while `run::run_design` keeps being the real, separately tested
//! (`tests/run_cli.rs`) JIT-only production path; neither module claims to
//! be the other. A future wave that builds a real general AOT-from-source
//! path should either replace this module's `run_aot_stub` with a real call
//! into that path, or fold `run_pipeline`'s order-selection logic directly
//! into `run_design` once both modes are real -- at which point this
//! module's own tests (`tests/cli_pipeline.rs`) continue to hold as the
//! order regression they always were.
//!
//! Every stage below is a private, loudly-named stub: it records its own
//! name onto the caller's `trace` and returns `Ok(())`, never silently doing
//! nothing under a name that implies real work (each doc comment says so).

use std::path::Path;

/// Which of AB-012's two run legs `run_pipeline` should reach after
/// `elaborate`/`codegen`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
    /// `elaborate -> codegen -> run_jit`.
    Jit,
    /// `elaborate -> codegen -> run_aot` (compile the codegen output to an
    /// object file, link it, then run the linked kernel).
    Aot,
}

/// Every failure mode `run_pipeline` can report. The stub stages below never
/// actually produce one (they are unconditional `Ok`), but the real stages a
/// future wave substitutes in will -- this type exists now so the pipeline's
/// public signature does not have to change again when that happens.
#[derive(Debug, PartialEq, Eq)]
pub struct PipelineError(pub String);

impl std::fmt::Display for PipelineError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "pipeline error: {}", self.0)
    }
}

impl std::error::Error for PipelineError {}

/// Stub: records that the `elaborate` stage ran, for the given `source_path`.
/// A real implementation reads and elaborates the design at `source_path`
/// (see `run::run_design`'s own `compile_source`/`netlist::elaborate` for
/// what that real work looks like today, in the separate production path);
/// this stub never touches the filesystem and never fails, by design -- it
/// exists only to occupy the `elaborate` position in the traced call order.
fn elaborate_stub(_source_path: &Path, trace: &mut Vec<&'static str>) -> Result<(), PipelineError> {
    trace.push("elaborate");
    Ok(())
}

/// Stub: records that the `codegen` stage ran. A real implementation lowers
/// the elaborated design to an LLVM module (`vj_codegen::pipeline`/
/// `aot::AotBackend`); this stub does no lowering.
fn codegen_stub(trace: &mut Vec<&'static str>) -> Result<(), PipelineError> {
    trace.push("codegen");
    Ok(())
}

/// Stub: records that the JIT run leg ran. A real implementation resolves
/// and calls the JIT-compiled entry points (`vj_codegen::orc::
/// JitBackend`); this stub runs nothing.
fn run_jit_stub(trace: &mut Vec<&'static str>) -> Result<(), PipelineError> {
    trace.push("run_jit");
    Ok(())
}

/// Stub: records that the AOT run leg ran. A real implementation emits an
/// object file, links it into a `.so` (`vj_codegen::aot::AotBackend::
/// emit_object`/`link`), loads it (`loader::LoadedKernel::load_dynamic`),
/// and drives it; this stub compiles and links nothing.
fn run_aot_stub(trace: &mut Vec<&'static str>) -> Result<(), PipelineError> {
    trace.push("run_aot");
    Ok(())
}

/// AB-012's pipeline-order contract: `elaborate` and `codegen` always run
/// first, in that order, regardless of `mode`; `mode` selects exactly one of
/// `run_jit`/`run_aot` as the third and final stage. Every stage pushes its
/// own name onto `trace` as it runs, so a caller (this module's own tests)
/// can assert on the exact sequence rather than only on the end result.
pub fn run_pipeline(
    mode: Mode,
    source_path: &Path,
    trace: &mut Vec<&'static str>,
) -> Result<(), PipelineError> {
    elaborate_stub(source_path, trace)?;
    codegen_stub(trace)?;
    match mode {
        Mode::Jit => run_jit_stub(trace),
        Mode::Aot => run_aot_stub(trace),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn jit_mode_order_is_elaborate_codegen_run_jit() {
        let mut trace = Vec::new();
        run_pipeline(Mode::Jit, Path::new("x.sv"), &mut trace).expect("stub never fails");
        assert_eq!(trace, vec!["elaborate", "codegen", "run_jit"]);
    }

    #[test]
    fn aot_mode_order_is_elaborate_codegen_run_aot() {
        let mut trace = Vec::new();
        run_pipeline(Mode::Aot, Path::new("x.sv"), &mut trace).expect("stub never fails");
        assert_eq!(trace, vec!["elaborate", "codegen", "run_aot"]);
    }
}