vj-codegen 0.2.0

LLVM code generator of the vj SystemVerilog simulator.
//! Name mangling (Task 21, CG-055/CG-056/CG-057/CG-058/CG-059/CG-061): a
//! stable, dedup-aware internal symbol scheme, kept as a strictly separate
//! code path from DPI-visible naming (CG-059's "codegen owns the DPI C ABI
//! *and* the internal name-mangling scheme -- two distinct paths"
//! requirement; see `mangling_and_dpi_naming_are_two_distinct_code_paths`,
//! `tests/mangle.rs`, which greps `dpi.rs` for any call into this module and
//! vice versa).
//!
//! Internal (never DPI-visible) symbols follow
//! `_SV$<definition-name>$<param-binding-hash>$<entity-tag>$<local-name>`
//! (CG-056, `docs/design/codegen.md` ยง6): human-readable-prefixed for
//! debugger friendliness, hash-suffixed so dedup ("two structurally
//! identical instantiations of a parameterized module share one compiled
//! kernel, called from two call sites") never collides two *different*
//! param bindings of the same definition/tag/local (CG-058). DPI-visible
//! symbols are the opposite: never mangled by this crate at all --
//! [`dpi_linkage_name`] returns [`DpiExport::c_identifier`] verbatim
//! (CG-057), already fully resolved by slang itself (explicit
//! `export "DPI-C" alias = ...` vs. the default bare-name form).
//!
//! Cross-module calls (CG-061) resolve through this shared naming scheme,
//! not through any LLVM module-merge step: a worker lowering one
//! `InstanceBody`'s `Module` that calls a kernel defined in another
//! `InstanceBody`'s `Module` only ever needs `mangle_kernel` to reproduce
//! the callee's name and declares it `extern` (no body) in its own Module --
//! see `cross_module_call_resolves_via_mangled_extern_declaration`,
//! `tests/mangle.rs`, for the concrete two-`Module` proof.

use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

use sv_lang::{ConstantValue, DpiExport, Symbol};

/// The kind of internally-mangled entity a mangled symbol names (CG-056's
/// `<entity-tag>` template component). The derived [`std::fmt::Debug`]
/// output (`"Kernel"`, `"Process"`, `"DpiThunk"`, `"Helper"`) IS the tag's
/// literal textual form inside the mangled string -- see [`mangle_kernel`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EntityTag {
    /// A comb/leaf-function kernel (`func.rs`'s `lower_function`).
    Kernel,
    /// A lowered procedural-block/process body (`always_ff`, `initial`, an
    /// edge-sensitive `always`, ...).
    Process,
    /// A DPI IMPORT's caller-side marshaling stub (`dpi.rs`'s
    /// `lower_dpi_import`) -- note this is distinct from a DPI EXPORT
    /// thunk, which is never mangled at all (see this module's own doc
    /// comment and [`dpi_linkage_name`]).
    DpiThunk,
    /// A crate-internal shared runtime helper not itself tied to one SV
    /// definition (e.g. `ops.rs`'s `sv_ipow_{u,s}N` power helper).
    Helper,
}

/// CG-056/CG-058: the public internal-symbol mangling function. Produces
/// `_SV$<def>$<param_hash>$<tag>$<local>` exactly -- `param_hash` rendered
/// as lower-case hex (compact, and visually distinct from `def`/`local`'s
/// own alphanumeric text, so a mangled name is never ambiguous about which
/// `$`-delimited field is the hash), `tag` via its `Debug` textual form. A
/// pure function of its four arguments -- no design/compilation state is
/// read, so two calls with identical arguments always produce identical
/// strings, and (CG-058) two calls that differ only in `param_hash` always
/// produce two distinct strings (the hash's hex digits appear verbatim in
/// the output, never elided/truncated).
pub fn mangle_kernel(def: &str, param_hash: u64, tag: EntityTag, local: &str) -> String {
    format!("_SV${def}${param_hash:x}${tag:?}${local}")
}

/// CG-057: DPI-visible symbols are never mangled by this crate -- returns
/// `export.c_identifier()` verbatim (slang's own resolution of an explicit
/// `export "DPI-C" alias = ...` vs. the default bare subroutine name). A
/// deliberately trivial one-line wrapper, kept as its own named function
/// (never inlined at each call site) so `mangling_and_dpi_naming_are_two_
/// distinct_code_paths` has a single stable name to assert `mangle_kernel`
/// never appears in its call graph, and vice versa.
pub fn dpi_linkage_name(export: &DpiExport) -> String {
    export.c_identifier()
}

/// CG-055/CG-065: `DedupCache`'s key half (Task 23 reuses this unchanged
/// as-is) -- a canonical hash of every one of `sym`'s elaborated
/// `parameters()`' folded [`Symbol::constant_value`], in declaration order.
/// `Symbol::parameters()` itself already yields an empty iterator for any
/// symbol that is not an `Instance` (documented on that method: "count is 0
/// for non-instances"), so calling this on, say, a bare function symbol
/// deterministically hashes as "zero parameters" rather than erroring --
/// this is a pure hash-key helper with no lowering side effects, so it has
/// no `Result` to fail through. Two instances with structurally identical
/// (name, folded-value) parameter sequences always hash identically
/// (`two_instances_same_params_produce_one_symbol_two_call_sites`,
/// `tests/mangle.rs`), regardless of hierarchical instance path.
pub fn param_binding_hash(sym: &Symbol) -> u64 {
    let mut hasher = DefaultHasher::new();
    for p in sym.parameters() {
        p.name().hash(&mut hasher);
        hash_constant_value(p.constant_value().as_ref(), &mut hasher);
        // A type parameter has no constant value: its bound type is its binding.
        if p.kind() == sv_lang::kinds::SymbolKind::TypeParameter {
            p.parameter_value().hash(&mut hasher);
        }
    }
    hasher.finish()
}

/// Canonicalizes one (optional) folded parameter value into `hasher`. Every
/// [`ConstantValue`] variant is hashed behind its own discriminant tag byte
/// first (so, e.g., an empty `String("")` can never collide with `Null`,
/// and an absent/unfolded `None` value -- its own tag `0` -- never
/// silently conflates with `Some(ConstantValue::Bad)`'s tag `9`).
/// `ConstantValue` is `#[non_exhaustive]` upstream, so the trailing wildcard
/// arm is structurally required here (unlike this crate's own exhaustive
/// `sv_lang::kinds` AST-kind matches, e.g. `expr.rs`/`func.rs`'s
/// `ExpressionKind`/`StatementKind` dispatch, which must never carry one): a
/// future slang-added variant still hashes deterministically under its own
/// reserved tag, it just cannot yet distinguish two different values of
/// that new variant from one another until this function is extended.
fn hash_constant_value(v: Option<&ConstantValue>, hasher: &mut DefaultHasher) {
    match v {
        None => 0u8.hash(hasher),
        Some(ConstantValue::Integer(i)) => {
            1u8.hash(hasher);
            i.bit_width().hash(hasher);
            i.is_signed().hash(hasher);
            // `SVInt`'s own canonical printed form already fully captures
            // the four-state value (including any x/z digits) -- reused
            // rather than re-deriving a second bit-plane serialization.
            i.to_string().hash(hasher);
        }
        Some(ConstantValue::Real(r)) => {
            2u8.hash(hasher);
            r.to_bits().hash(hasher);
        }
        Some(ConstantValue::String(s)) => {
            3u8.hash(hasher);
            s.hash(hasher);
        }
        Some(ConstantValue::Null) => 4u8.hash(hasher),
        Some(ConstantValue::Unbounded) => 5u8.hash(hasher),
        Some(ConstantValue::Array(els)) => {
            6u8.hash(hasher);
            els.len().hash(hasher);
            for e in els {
                hash_constant_value(Some(e), hasher);
            }
        }
        Some(ConstantValue::Map(text, is_empty)) => {
            7u8.hash(hasher);
            text.hash(hasher);
            is_empty.hash(hasher);
        }
        Some(ConstantValue::Union(text)) => {
            8u8.hash(hasher);
            text.hash(hasher);
        }
        Some(ConstantValue::TaggedUnion {
            member,
            value,
            text,
        }) => {
            10u8.hash(hasher);
            member.hash(hasher);
            text.hash(hasher);
            hash_constant_value(Some(&**value), hasher);
        }
        Some(ConstantValue::Bad) => 9u8.hash(hasher),
        Some(_) => 255u8.hash(hasher),
    }
}