vj-codegen 0.1.0

LLVM code generator of the vj SystemVerilog simulator.
//! Legacy MCJIT-style wrapper (inkwell's plain `ExecutionEngine`, one per
//! `Module`): build an `ExecutionEngine` for a lowered `Module` and recover
//! a raw function pointer / typed extern-C shim by name.
//!
//! **BINDING CORRECTION 4 (Task 24, `docs/superpowers/plans/2026-09-23-
//! wave-A-codegen.md`):** `Jit`/`ExecutionEngine` is not the production backend.
//! It is kept only where an existing test still references it (`tests/
//! jit_add.rs`, `tests/power.rs`'s pre-Task-24 merge test, `tests/
//! session_parallel.rs`'s pre-Task-24 multi-module JIT test). New code must
//! use [`crate::orc::JitBackend`], the real `LLJIT`/`JITDylib`-based backend
//! CG-062/CG-064/CG-124 ask for.
use inkwell::OptimizationLevel;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::module::Module;
use inkwell::targets::{InitializationConfig, Target};
use inkwell::values::FunctionValue;

pub struct Jit<'ctx> {
    ee: ExecutionEngine<'ctx>,
}

impl<'ctx> Jit<'ctx> {
    pub fn from_module(module: Module<'ctx>) -> Result<Self, String> {
        // Required before any JIT engine can be created (idempotent;
        // `create_jit_execution_engine` also calls this internally, but
        // calling it here keeps this constructor correct standalone).
        // Verified against inkwell 0.10 + LLVM 22.1.8 on this host.
        Target::initialize_native(&InitializationConfig::default())?;
        let ee = module
            .create_jit_execution_engine(OptimizationLevel::Default)
            .map_err(|e| e.to_string())?;
        Ok(Jit { ee })
    }

    pub fn get_fn2_i32(&self, name: &str) -> Option<extern "C" fn(i32, i32) -> i32> {
        self.get_raw(name)
            .map(|p| unsafe { std::mem::transmute::<*const (), extern "C" fn(i32, i32) -> i32>(p) })
    }

    /// CG-125: retrieves a compiled process kernel's callable function
    /// pointer by its mangled (LLVM) name -- `Some(fn_ptr)` when `mangled`
    /// names a function this `Jit`'s module was built with (whether or not
    /// it has actually run yet), `None` for an unknown name.
    ///
    /// **Superseded-ABI reconciliation (this crate's own "superseded-ABI
    /// note", `docs/superpowers/plans/2026-09-26-wave-A2-slice.md`'s global
    /// constraints section):** CG-125's literal ledger text asks for a
    /// `SvResumeStatus`-returning signature (`docs/design/codegen.md`'s
    /// superseded §9 draft) -- that type does not exist anywhere in this
    /// tree. This function instead returns the frozen, ARCHITECTURE §5 ABI's
    /// own [`sv_sim_abi::ProcessFn`] (`extern "C" fn(SimHandle, *mut u8, u32)
    /// -> EvalResult`), the same reconciliation `sv-sim-codegen::
    /// process_lower::lower_kernel_fn`'s own doc comment and
    /// `sv-sim-sim::sched::kernel_adapter::SV_KERNEL_ABI_RECONCILIATION`
    /// already record for the rest of this same ABI boundary -- never the
    /// literal superseded struct name.
    pub fn get_process_fn(&self, mangled: &str) -> Option<sv_sim_abi::ProcessFn> {
        self.get_raw(mangled)
            .map(|p| unsafe { std::mem::transmute::<*const (), sv_sim_abi::ProcessFn>(p) })
    }

    // Corrected vs. plan: `ExecutionEngine::get_function_address` is a safe
    // fn in inkwell 0.10 (not `unsafe fn`), so wrapping the call itself in
    // `unsafe` is an unnecessary block that trips `unused_unsafe` under
    // `#![deny(warnings)]`. The genuinely unsafe operation stays the
    // `mem::transmute` in `get_fn2_i32`.
    pub fn get_raw(&self, name: &str) -> Option<*const ()> {
        self.ee
            .get_function_address(name)
            .ok()
            .map(|a| a as *const ())
    }

    // Deviation from the plan (Task S1, discovered writing its own tests,
    // not in the plan's `Files:` list for `jit.rs`): the accessor-call
    // tests need to bind a declared-but-undefined `extern "C"` accessor
    // symbol (e.g. `sv_get_scalar`) to a Rust test-double function before
    // JIT-executing anything that calls it -- there is no real STATE arena
    // for the JIT to resolve that symbol against otherwise. inkwell's
    // `ExecutionEngine::add_global_mapping` is exactly this binding; `Jit`
    // had no way to reach it since `ee` is a private field. This thin
    // wrapper is the minimal addition that unblocks that test shape and is
    // equally what the real `sv-sim-bin` JIT path will need later to bind
    // the compiled kernel's declared accessor calls to
    // `sv-sim-sim::state::accessor_impl`'s real `extern "C" fn`s.
    pub fn add_global_mapping(&self, function: FunctionValue<'ctx>, addr: usize) {
        self.ee.add_global_mapping(&function, addr);
    }
}