vj-codegen 0.1.0

LLVM code generator of the vj SystemVerilog simulator.
//! Task 28 (CG-157, CG-158, CG-159): `CoverageSink` and `SupportedConstructs`.
//!
//! CG-157 (`verification.md` "Consumes From Others -- CODEGEN; GATE 4(b)"):
//! codegen follows the `CoverageSink::record_fallback` call-site convention
//! on every conservative/best-effort lowering path -- a branch that takes an
//! approximate/safe-default action instead of a fully-precise one (as
//! opposed to an unhandled construct, which must hard-error via
//! `LoweringError`, never approximate anything; see `expr.rs`'s
//! `is_side_effect_free` doc comment for the exact distinction this crate
//! draws between the two). Every such branch calls `record_fallback` with
//! the construct and a diagnosable location before taking the approximate
//! action, so an attached harness can observe exactly which construct/
//! location fell back rather than being lowered precisely.
//!
//! **Deviation from the plan text** (discovered implementing this task, per
//! this project's own "confirm the real API before coding" discipline --
//! `docs/superpowers/plans/2026-09-23-wave-A-codegen.md` Task 30 hit the
//! identical shape of staleness for `sv-sim-runtime::rtarg` and documents
//! the same fix pattern this deviation follows): the plan sketch says to
//! define `CoverageSink` LOCALLY in this crate "so no dependency on a
//! not-yet-existing `sv-sim-harness` crate is needed." That premise is
//! stale by the time this task actually runs -- `sv-sim-harness` (and its
//! `coverage.rs`) already exists, committed at ancestor commit `4140ec5`
//! ("Wave A contracts: crate skeletons + cross-track interfaces"), strictly
//! before this plan's own base commit. `docs/CONTRACTS.md`'s
//! `sv-sim-harness` table already names `ConstructId`/`SourceLoc`/
//! `CoverageSink` (`coverage.rs`) as a harness-owned cross-track contract
//! type, lists **codegen** among its consumers, and cites **CG-157** by
//! ledger ID -- with no "not this wave" deferral qualifier (contrast
//! `docs/CONTRACTS.md`'s own D4, which uses that exact qualifier language
//! where a deferral really is intended). Defining a second, locally-typed
//! `CoverageSink` here (as the plan sketch describes) would create two
//! incompatible traits under the same name for the one thing
//! `docs/CONTRACTS.md` already settled as a single cross-track contract --
//! this module instead re-exports the real, binding `sv_sim_harness::
//! coverage` types. This adds `sv-sim-harness` as a direct dependency of
//! this crate (`Cargo.toml`, D7 below); no cycle results, since
//! `sv-sim-harness` depends only on `sv-sim-abi`/`sv-sim-sim`/`sv-lang`/
//! `sv-lang-kinds`, none of which depend on `sv-sim-codegen`.
//!
//! `NoopCoverageSink` and `supported_constructs()` (CG-158/CG-159) have no
//! cross-track contract type of their own to consume -- `docs/CONTRACTS.md`
//! lists only `ConstructHistogram` (a harness-owned, different, aggregate
//! concept) as "not materialized this wave," never a per-crate "what do I
//! support" query surface or a trivial no-op sink -- so both are defined
//! locally here exactly as the plan describes.
//!
//! CG-158/CG-159 (`verification.md` "Consumes From Others -- CODEGEN" /
//! "Open Questions §134"): `supported_constructs()` exposes the exact set of
//! `ExpressionKind`/`StatementKind` variant names this crate currently
//! LOWERS (produces a real value/effect for), so a harness can pre-classify
//! a corpus file as expected-pass vs. known-gap at corpus-LOAD time, without
//! executing it first.
//!
//! Storage decision (CG-159, resolving `verification.md` Open Questions
//! §134): a compiled-in `&'static` slice, hand-maintained alongside each
//! task that adds a match arm to `lower_expr`/`lower_stmt` -- NOT a runtime
//! JSON sidecar. A sidecar's own copy of "what's supported" can silently
//! drift from the real match arms it's supposed to describe the moment a
//! future task adds or removes an arm and forgets to update it; this
//! in-crate list can't drift *silently*, because
//! `tests/coverage_sink.rs::supported_constructs_lists_every_implemented_kind`
//! independently re-derives the same set by scanning `expr.rs`/`func.rs`'s
//! own source text every time the test suite runs and fails loudly on any
//! mismatch. The harness-side pre-classification consumer itself (an actual
//! corpus-load step reading this function) does not exist yet -- only this
//! codegen-side query surface is delivered by this task.

/// The real, harness-owned `CoverageSink` contract (`docs/CONTRACTS.md`'s
/// `sv-sim-harness` table) and its `ConstructId`/`SourceLoc` argument types
/// -- re-exported here so every call site in this crate names them through
/// `crate::coverage`, matching this crate's existing convention of a single
/// module owning each cross-track type it consumes (e.g. `dpi.rs` for the
/// DPI ABI classification types).
pub use sv_sim_harness::coverage::{ConstructId, CoverageSink, SourceLoc};

/// The default `CoverageSink` for callers with no harness attached --
/// production `sv-sim run`, and every test that isn't itself exercising
/// `CoverageSink` wiring. Records nothing. (Not a cross-track contract type
/// itself -- see this module's doc comment.)
pub struct NoopCoverageSink;

impl CoverageSink for NoopCoverageSink {
    fn record_fallback(&self, _construct: ConstructId, _loc: SourceLoc) {}
}

/// CG-157's construct-id encoding for this crate: the raw `ExpressionKind`
/// discriminant (`#[repr(u16)]` in vendored `sv-lang-kinds`'s generated
/// `ast_kind.rs`), widened to `ConstructId`'s `u32`. This is collision-free
/// *within* this crate's own `ExpressionKind`-typed fallback sites (the only
/// kind this crate's one real conservative branch matches on today -- see
/// `expr.rs::is_side_effect_free`). `ConstructId` has no cross-track
/// numbering registry yet shared with `sv-sim-runtime`/`sched`'s own future
/// `record_fallback` call sites (`docs/CONTRACTS.md`'s `sv-sim-harness`
/// table lists them as consumers too) -- if a `StatementKind`-typed
/// fallback site is ever added here, it would need a disjoint numbering
/// scheme (e.g. an offset) to stay collision-free against this one; not
/// needed yet, flagged for whichever task adds the first such site.
pub fn construct_id_for_expression_kind(kind: sv_lang::kinds::ExpressionKind) -> ConstructId {
    ConstructId(kind as u16 as u32)
}

/// This crate's location convention for a `CoverageSink` call: harness Task
/// 11 (VF-062) promoted `sv_sim_harness::coverage::SourceLoc` from an ad hoc
/// `{file, line, column}` stand-in to the real `sv_lang::SourceLoc` --
/// `{buffer, offset}`, a lexer buffer id plus byte offset. That is exactly
/// what `sv_lang::Expression::source_range()` already returns (`sv-lang`
/// 0.1.9's only source-location accessor at this crate's API surface, with
/// no line/column resolution exposed) -- so the conservative-branch call
/// site now passes the expression's own real, resolvable start location
/// instead of a placeholder built from the enclosing symbol's hierarchical
/// path. `sv_lang::Symbol` exposes no location accessor of its own, so this
/// crate's previous `loc_for_fn_sym(&Symbol)` (which only ever had the
/// symbol, not the expression, in scope at its one call site prior to this
/// deviation) is replaced by `loc_for_expr(&Expression)`.
pub fn loc_for_expr(e: &sv_lang::Expression) -> SourceLoc {
    e.source_range().start
}

/// CG-158/CG-159: every `ExpressionKind`/`StatementKind` variant name this
/// crate currently lowers to a real value/effect.
///
/// This is deliberately narrower than "every variant this crate's `match`es
/// mention by name": a variant that is matched only to give it a specific,
/// diagnosable hard-error message (e.g. `StatementKind::WaitFork`/
/// `DisableFork`/`WaitOrder`/`ForeverLoop`, Task 27/CG-172 -- fork/join is
/// unresolved, see `ARCHITECTURE.md` SS6.4) is NOT "supported" in this
/// sense: it never produces a lowering, only a better error than the
/// generic `other =>` fallthrough would. Only variants whose arm actually
/// lowers the construct (even if that lowering itself validates and can
/// still error on out-of-scope sub-cases, e.g. `ElementSelect`'s
/// bounds-checking) belong on this list.
///
/// Kept in sync with `lower_expr` (`expr.rs`) and `lower_stmt` (`func.rs`)
/// by hand, one entry added per task that adds a genuinely-lowering match
/// arm; `tests/coverage_sink.rs::supported_constructs_lists_every_
/// implemented_kind` is the load-bearing regression guard that catches a
/// future task forgetting to update this list (see the module doc comment's
/// CG-159 storage-decision note above).
pub fn supported_constructs() -> &'static [&'static str] {
    &[
        // `ExpressionKind` (`lower_expr`, `expr.rs`).
        "IntegerLiteral",
        "RealLiteral",
        "TimeLiteral",
        "NamedValue",
        // A hierarchical name (`b.valid`, `top.u.r`) lowers like
        // `NamedValue`: its signal is a foreign slot of the instance.
        "HierarchicalValue",
        // `$` in a queue index (`crate::collections`).
        "UnboundedLiteral",
        "UnaryOp",
        "BinaryOp",
        "ConditionalOp",
        "Inside",
        // `x dist {...}` lowers as `inside` (16.14.2, sva T29).
        "Dist",
        "ElementSelect",
        "RangeSelect",
        "MemberAccess",
        "Concatenation",
        "Replication",
        "Conversion",
        // `min:typ:max` lowers its typical operand (11.11, expr.rs).
        "MinTypMax",
        "Call",
        "Assignment",
        // Classes plan Task 5: `new(args)`/`new` (`crate::classes::
        // lower_new_class`).
        "NewClass",
        // Functional coverage: `new(args)` of a covergroup
        // (`crate::covergroup::lower_new`).
        "NewCovergroup",
        // Classes plan Task 10: `new obj`, the shallow copy
        // (`crate::classes::lower_copy_class`).
        "CopyClass",
        "LValueReference",
        "EmptyArgument",
        "NullLiteral",
        "ArbitrarySymbol",
        "StringLiteral",
        "UnbasedUnsizedIntegerLiteral",
        "SimpleAssignmentPattern",
        "StructuredAssignmentPattern",
        "ReplicatedAssignmentPattern",
        // A tagged union expression `tagged m e` (7.3.2, `crate::tagged`).
        "TaggedUnion",
        // `StatementKind` (`lower_stmt`, `func.rs`).
        // `case ... matches` (12.6.1).
        "PatternCase",
        "List",
        "Block",
        "VariableDeclaration",
        "Return",
        "ExpressionStatement",
        "Conditional",
        "Case",
        "RandCase",
        "Empty",
        "WhileLoop",
        "DoWhileLoop",
        "ForLoop",
        "RepeatLoop",
        "ForeachLoop",
        "ForeverLoop",
        "Break",
        "Continue",
        "Disable",
        // `force`/`release` in a task or function body (10.6.2,
        // `func::lower_subroutine_force`).
        "ProceduralAssign",
        "ProceduralDeassign",
        "ImmediateAssertion",
        // `-> e` on a class-event handle inside a subroutine body
        // (15.5, `crate::class_events`).
        "EventTrigger",
    ]
}