vj 0.2.0

vj: a SystemVerilog (IEEE 1800-2023) simulator built on slang and LLVM.
//! What `vj build-aot` needs besides the lowering itself: the refusal
//! vocabulary and the source locations it reports.
//!
//! A design is AOT-buildable only when everything it needs at run time is
//! either compiled into the `.so` or serialized in its design image. Anything
//! else (primitives, covergroups, VPI, ...) is refused at build
//! time by name -- `AOT-REFUSED(<feature>): <reason> at <file:line>` -- never
//! built into a `.so` that would run it differently from `vj run`.

use sv_lang::{Symbol, SyntaxTree};

use crate::run::RunError;
use crate::sources::SourceSet;

/// The refusal for `feature`.
pub(crate) fn refused(feature: &str, reason: &str, at: impl Into<String>) -> RunError {
    RunError::AotRefused {
        feature: feature.to_owned(),
        reason: reason.to_owned(),
        at: at.into(),
    }
}

/// The standard reason text of a refused feature: what it is, and what to run.
pub(crate) fn not_supported(what: &str) -> String {
    format!("{what} is not supported in AOT output; run the design with `vj run`")
}

/// Maps `file:line` onto the symbols of the design: line numbers come from the
/// syntax node a symbol was declared by.
pub(crate) struct Locator<'t> {
    /// Each tree with the byte offset at which each of its lines starts.
    trees: Vec<(&'t SyntaxTree, Vec<usize>)>,
    /// `files[i]` names `trees[i]` when the counts agree (one tree per file).
    files: Vec<String>,
    /// What an unlocatable construct is reported against.
    fallback: String,
}

impl<'t> Locator<'t> {
    pub(crate) fn new(trees: &'t [SyntaxTree], sources: &SourceSet) -> Self {
        let line_starts = trees
            .iter()
            .map(|tree| {
                let starts = std::iter::once(0)
                    .chain(tree.text().match_indices('\n').map(|(i, _)| i + 1))
                    .collect();
                (tree, starts)
            })
            .collect();
        let files = if sources.files.len() == trees.len() {
            sources
                .files
                .iter()
                .map(|f| f.display().to_string())
                .collect()
        } else {
            Vec::new()
        };
        Locator {
            trees: line_starts,
            files,
            fallback: sources.label().display().to_string(),
        }
    }

    /// `file:line` of the declaration of `sym`; `file:1` when it has no source.
    pub(crate) fn symbol(&self, sym: &Symbol<'_>) -> String {
        for (i, (tree, starts)) in self.trees.iter().enumerate() {
            if let Some(node) = sym.syntax(tree) {
                let offset = node.byte_range().start;
                let line = starts.partition_point(|&s| s <= offset).max(1);
                let file = self.files.get(i).map_or(self.fallback.as_str(), |f| f);
                return format!("{file}:{line}");
            }
        }
        format!("{}:1", self.fallback)
    }
}

/// The refusal an AOT-undefined runtime symbol the lowered module calls maps
/// to: `(feature, what)`. `None` for a symbol the AOT runtime provides.
///
/// Nothing maps any more: VPI (IEEE 1800-2023 clause 36), the last host-only
/// construct, is carried by the design image's `vpi` section
/// (`vj_vpi::boot`) and run by the same `vj-vpi` crate the JIT uses,
/// so `sv_rt_vpi_systf` is an ordinary runtime symbol.
pub(crate) fn runtime_refusal(_symbol: &str) -> Option<(&'static str, &'static str)> {
    None
}

/// The refusal a codegen-level "not supported in AOT output" message maps to.
/// Nothing maps any more: sampled values and assertions are carried by the
/// design image's verification section (`vj_sim::image::verif`).
pub(crate) fn codegen_refusal(_message: &str) -> Option<(&'static str, &'static str)> {
    None
}

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

    #[test]
    fn no_runtime_symbol_is_refused() {
        assert_eq!(runtime_refusal("sv_rt_vpi_systf"), None);
        assert_eq!(runtime_refusal("sv_rt_dpi_call"), None);
    }
}