vj 0.2.0

vj: a SystemVerilog (IEEE 1800-2023) simulator built on slang and LLVM.
//! `vj build-aot` and `vj run-aot`: the ahead-of-time path.
//!
//! `build-aot FILE... [source options] -o OUT.so` runs the planner of
//! `vj run` (`crate::run::build_aot_object`) up to the point where `run`
//! would bind the lowered module to a JIT, emits the design's machine code and
//! its serialized design image as one object, and links that object against
//! `libvj_aot.a` (the slang-free runner, `vj-aot`; built by
//! `cargo xtask build-aot-runtime`) into a shared library. Whatever the image
//! cannot carry is refused by name: `AOT-REFUSED(<feature>): <reason> at
//! <file:line>`.
//!
//! `run-aot OUT.so [+plusarg]... [--seed N] [--x-initial zero|x]` loads that
//! library and calls its `vj_aot_main`: the same output, exit code and
//! determinism as `vj run` on the same design.
use std::path::{Path, PathBuf};

use vj_codegen::aot::{AotBackend, AotKernelHandle};

pub use crate::aot_legacy::{build_aot_so, build_aot_so_default_obj};
use crate::run::{RunError, build_aot_object, build_aot_object_with};
use crate::sources::SourceSet;
use crate::timing_options::TimingOptions;

/// The symbols a design `.so` exports: the ABI entry points, the runner's
/// entry (`vj_aot_*`), and the compiled process functions. Everything else
/// (the runtime's own `sv_rt_*`, the engine, std) stays private to the library.
pub const EXPORT_PATTERNS: &[&str] = &["sim_*", "vj_*", "VJ_*"];

/// The file name of the runner archive `cargo xtask build-aot-runtime` builds.
pub const RUNTIME_LIB_NAME: &str = "libvj_aot.a";

/// Where `build-aot` looks for the runner archive when `--runtime-lib` is not
/// given: `$VJ_AOT_RT`, then next to the running `vj` (cargo's target
/// directory, or its parent for a test binary in `deps/`).
pub fn find_runtime_lib() -> Option<PathBuf> {
    if let Some(p) = std::env::var_os("VJ_AOT_RT") {
        return Some(PathBuf::from(p));
    }
    let exe = std::env::current_exe().ok()?;
    let mut dir = exe.parent();
    while let Some(d) = dir {
        let candidate = d.join(RUNTIME_LIB_NAME);
        if candidate.is_file() {
            return Some(candidate);
        }
        // Only `deps/` is a directory to look through; stop at the profile dir.
        if d.file_name().is_some_and(|n| n == "deps") {
            dir = d.parent();
        } else {
            break;
        }
    }
    None
}

/// Compiles the design of `sources` into `<out_so>.o`, links it against
/// `runtime_lib` into `out_so`, and returns the linked library's handle.
pub fn build_aot_design(
    sources: &SourceSet,
    timing: TimingOptions,
    runtime_lib: &Path,
    out_so: &Path,
) -> Result<AotKernelHandle, RunError> {
    let mut out_obj: PathBuf = out_so.to_path_buf();
    out_obj.set_extension("o");
    build_aot_object(sources, timing, &out_obj)?;
    let linked = AotBackend::for_host()
        .map_err(RunError::Codegen)?
        .link_exporting(
            std::slice::from_ref(&out_obj),
            runtime_lib,
            out_so,
            Some(EXPORT_PATTERNS),
        )
        .map_err(RunError::Codegen);
    // The object is an intermediate; the library has its bytes.
    let _ = std::fs::remove_file(&out_obj);
    linked
}

/// `build-aot --exe`: [`build_aot_design`]'s standalone-executable sibling.
/// The SAME planner, SAME lowering (never a second lowering path) -- only
/// `with_main=true` (a real `main` in the object, see
/// `vj_codegen::aot::emit_main_fn`) and the link step
/// ([`AotBackend::link_exe`] instead of `link_exporting`) differ, so a
/// construct `build-aot` (without `--exe`) would refuse is refused here
/// identically (same error, same `AOT-REFUSED(<feature>)` text).
pub fn build_aot_exe(
    sources: &SourceSet,
    timing: TimingOptions,
    runtime_lib: &Path,
    out_exe: &Path,
) -> Result<(), RunError> {
    let mut out_obj: PathBuf = out_exe.to_path_buf();
    out_obj.set_extension("o");
    build_aot_object_with(sources, timing, true, &out_obj)?;
    let linked = AotBackend::for_host()
        .map_err(RunError::Codegen)?
        .link_exe(std::slice::from_ref(&out_obj), runtime_lib, out_exe)
        .map_err(RunError::Codegen);
    let _ = std::fs::remove_file(&out_obj);
    linked
}