vj-codegen 0.1.0

LLVM code generator of the vj SystemVerilog simulator.
//! User-defined system tasks and functions (IEEE 1800-2023 36.3-36.5, 38.37):
//! a call of a `$name` a VPI library registered with `vpi_register_systf`.
//!
//! `run_design` declares each registered name to slang (so the design
//! elaborates) and hands the names here ([`set_registered`]) before building
//! the pipeline. Each call site lowers to one `sv_rt_vpi_systf(site, args,
//! n, out)` call; the site's description -- which name, and what each
//! argument is -- is recorded ([`take_sites`]) for the VPI side, which runs
//! the registered `calltf` with handles to the call and its arguments
//! (`vpi_handle(vpiSysTfCall, NULL)`, `vpi_iterate(vpiArgument, call)`).
//!
//! An argument that names a net or variable is described by its
//! hierarchical name, so the VPI handle for it is the object itself (it can
//! be written with `vpi_put_value`); a string literal by its text; anything
//! else by its value at the call, passed in the call's argument buffer.

use std::cell::RefCell;
use std::collections::HashMap;

use inkwell::AddressSpace;
use inkwell::module::Linkage;
use sv_lang::Expression;
use sv_lang::kinds::ExpressionKind;

pub use sv_sim_exec::vpi_data::{ARG_WORDS, ARITY_SUFFIX, ArgDesc, SiteDesc, SysTfDecl};

use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, entry_alloca, lower_expr};
use crate::ops::OpBuilder;
use crate::types::TypeWidth;
use crate::value::{RealValue, ValueRepr};

/// The runtime entry point: `void sv_rt_vpi_systf(u32 site, const u64 *args,
/// u32 nargs, u64 *out)`; `args` holds four words per argument (`bits`,
/// unknown bits, tag, width), `out` receives a function's result as three
/// (`bits`, unknown bits, tag: 2 for a real).
pub const SV_RT_VPI_SYSTF_SYMBOL: &str = "sv_rt_vpi_systf";

thread_local! {
    static REGISTERED: RefCell<HashMap<String, SysTfDecl>> = RefCell::new(HashMap::new());
    static SITES: RefCell<Vec<SiteDesc>> = const { RefCell::new(Vec::new()) };
}

/// The registered system tasks and functions of the design about to be
/// lowered (forgets any earlier design's call sites).
pub fn set_registered(decls: Vec<SysTfDecl>) {
    REGISTERED.with(|r| *r.borrow_mut() = decls.into_iter().map(|d| (d.name.clone(), d)).collect());
    SITES.with(|s| s.borrow_mut().clear());
}

/// The call sites lowered since [`set_registered`], indexed by site id.
pub fn take_sites() -> Vec<SiteDesc> {
    SITES.with(|s| std::mem::take(&mut *s.borrow_mut()))
}

/// Whether a VPI library registered `name`. A registered name overrides the
/// built-in of the same name (36.3.2), so statement lowering of the built-in
/// system tasks (`$display`, ...) consults this before its own dispatch.
pub fn is_registered(name: &str) -> bool {
    registered(name).is_some()
}

/// The registration a call of `name` (possibly arity-suffixed) refers to.
fn registered(name: &str) -> Option<SysTfDecl> {
    let base = name.split(ARITY_SUFFIX).next().unwrap_or(name);
    REGISTERED.with(|r| r.borrow().get(base).cloned())
}

/// The true operand under the implicit conversions slang adds to an argument
/// of a declared system function.
fn operand(mut e: Expression<'_>) -> Expression<'_> {
    while e.kind() == ExpressionKind::Conversion {
        match e.conversion_operand() {
            Some(inner) => e = inner,
            None => break,
        }
    }
    e
}

/// An argument is an omitted one, a string literal, a net or variable named
/// directly, or (every other expression) a value.
fn describe(e: &Expression<'_>) -> ArgDesc {
    use sv_lang::kinds::SymbolKind;
    let kind = e.kind();
    if kind == ExpressionKind::EmptyArgument {
        return ArgDesc::Empty;
    }
    if kind == ExpressionKind::StringLiteral {
        let text = e
            .constant_value()
            .and_then(|c| c.as_integer().map(literal_text))
            .unwrap_or_default();
        return ArgDesc::Str(text);
    }
    if [
        ExpressionKind::NamedValue,
        ExpressionKind::HierarchicalValue,
    ]
    .contains(&kind)
        && let Some(s) = e.referenced_symbol()
        && [SymbolKind::Variable, SymbolKind::Net].contains(&s.kind())
    {
        return ArgDesc::Signal(s.hierarchical_path());
    }
    ArgDesc::Value
}

/// A string literal's bytes as text (most significant byte first, NULs
/// dropped).
fn literal_text(i: &sv_lang::SVInt) -> String {
    let w = i.bit_width() as usize;
    let mut bytes = Vec::new();
    for byte in (0..w.div_ceil(8)).rev() {
        let mut v = 0u8;
        for k in 0..8 {
            if i.bit(byte * 8 + k).is_some_and(|b| b.logic_t_value() == 1) {
                v |= 1 << k;
            }
        }
        if v != 0 {
            bytes.push(v);
        }
    }
    String::from_utf8_lossy(&bytes).into_owned()
}

/// iverilog's `$simtime`: the time in global-precision ticks (the same
/// runtime read `$time` scales down per module), as a 64-bit `time` (slang
/// types the call `time` through its typed registration).
fn lower_simtime<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> Result<LoweredValue<'ctx>, LoweringError> {
    let rt_fn = crate::expr::fetch_or_declare_sv_rt_time(cx.ctx, cx.module);
    let ticks =
        cx.b.build_call(rt_fn, &[], "rt_simtime_call")
            .unwrap()
            .try_as_basic_value()
            .unwrap_basic()
            .into_int_value();
    let op = OpBuilder {
        ctx: cx.ctx,
        b: &cx.b,
    };
    let known = cx.ctx.i64_type().const_zero();
    Ok(LoweredValue::Packed(
        op.pack(ticks, known, 64, cx.repr, false),
    ))
}

/// iverilog's `$is_signed(expr)` (1-bit: is the expression's self-determined
/// type signed, LRM 11.8.1) and `$sizeof(expr)` (its width in bits). Both are
/// compile-time properties of the argument's type, read from the argument as
/// written (the `integer` formal's implicit conversion is stripped, so a
/// parameter, a packed-struct member or a shift keeps its own signedness and
/// width). A non-integral argument fails by name.
fn lower_type_query<'ctx>(
    cx: &LowerCtx<'ctx, '_, '_>,
    e: Expression<'_>,
    name: &str,
) -> Result<LoweredValue<'ctx>, LoweringError> {
    let arg = e
        .children()
        .into_iter()
        .find_map(|n| n.as_expression())
        .map(operand);
    // slang propagates the `integer` formal's width into an operator's operands, so the width of
    // an operator expression (narrower than 32 bits) is not recoverable: refuse, never guess.
    if name == crate::frontend::SIZEOF_NAME
        && arg.is_some_and(|a| {
            matches!(
                a.kind(),
                ExpressionKind::BinaryOp | ExpressionKind::UnaryOp | ExpressionKind::ConditionalOp
            ) && a.expr_type().is_some_and(|t| t.value_width() <= 32)
        })
    {
        return Err(LoweringError::unsupported(
            "$sizeof of an operator expression no wider than 32 bits",
            name,
        ));
    }
    let ty = arg
        .and_then(|a| a.expr_type())
        .filter(|t| t.is_integral())
        .ok_or_else(|| {
            LoweringError::unsupported(&format!("{name} of a non-integral argument"), name)
        })?;
    let op = OpBuilder {
        ctx: cx.ctx,
        b: &cx.b,
    };
    let (value, width, signed) = if name == crate::frontend::IS_SIGNED_NAME {
        (u64::from(ty.is_signed()), 1, false)
    } else {
        (u64::from(ty.value_width()), 32, true)
    };
    let t = if width == 1 {
        cx.ctx.bool_type()
    } else {
        cx.ctx.i32_type()
    };
    Ok(LoweredValue::Packed(op.pack(
        t.const_int(value, false),
        t.const_zero(),
        width,
        cx.repr,
        signed,
    )))
}

/// Lowers a call of a registered system task or function, or returns `None`
/// if `name` is not one.
pub fn lower_call<'ctx, 'd>(
    cx: &LowerCtx<'ctx, 'd, '_>,
    e: Expression<'d>,
    name: &str,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
    let Some(decl) = registered(name) else {
        if name == crate::frontend::SIMTIME_NAME {
            return lower_simtime(cx).map(Some);
        }
        if name == crate::frontend::IS_SIGNED_NAME || name == crate::frontend::SIZEOF_NAME {
            return lower_type_query(cx, e, name).map(Some);
        }
        return Ok(None);
    };
    let i64t = cx.ctx.i64_type();
    let i32t = cx.ctx.i32_type();
    let ptr = cx.ctx.ptr_type(AddressSpace::default());
    let op = OpBuilder {
        ctx: cx.ctx,
        b: &cx.b,
    };
    let args: Vec<Expression<'d>> = e
        .children()
        .into_iter()
        .filter_map(|n| n.as_expression())
        .map(operand)
        .collect();
    let n = args.len().max(1);
    let buf = entry_alloca(cx, i64t.array_type((n * ARG_WORDS) as u32), "systf_args");
    let mut descs = Vec::with_capacity(args.len());
    let store = |at: usize, v: inkwell::values::IntValue<'ctx>| {
        let p = unsafe {
            cx.b.build_gep(i64t, buf, &[i64t.const_int(at as u64, false)], "systf_w")
        }
        .unwrap();
        cx.b.build_store(p, v).unwrap();
    };
    for (i, a) in args.iter().enumerate() {
        let d = describe(a);
        if d == ArgDesc::Value {
            let (bits, unk, tag, width) = match lower_expr(cx, *a)? {
                LoweredValue::Real(r) => {
                    let f = crate::real::as_f64(cx, r.llvm);
                    let bits =
                        cx.b.build_bit_cast(f, i64t, "systf_real")
                            .unwrap()
                            .into_int_value();
                    (bits, i64t.const_zero(), 2u64, 64u64)
                }
                LoweredValue::Str(s) => (s.llvm, i64t.const_zero(), 3, 64),
                other => {
                    let pv = other.into_packed("a system task argument", &cx.fn_sym)?;
                    let (v, u) = (op.lane_val(pv), op.lane_unk(pv));
                    let fit = |x: inkwell::values::IntValue<'ctx>| match pv.width.cmp(&64) {
                        std::cmp::Ordering::Less => {
                            if pv.signed {
                                cx.b.build_int_s_extend(x, i64t, "systf_sx").unwrap()
                            } else {
                                cx.b.build_int_z_extend(x, i64t, "systf_zx").unwrap()
                            }
                        }
                        std::cmp::Ordering::Equal => x,
                        // Only the low 64 bits of a wider expression value
                        // travel (a wide net or variable is passed by name).
                        std::cmp::Ordering::Greater => {
                            cx.b.build_int_truncate(x, i64t, "systf_tr").unwrap()
                        }
                    };
                    let unk = match pv.width.cmp(&64) {
                        std::cmp::Ordering::Less => {
                            cx.b.build_int_z_extend(u, i64t, "systf_ux").unwrap()
                        }
                        std::cmp::Ordering::Equal => u,
                        std::cmp::Ordering::Greater => {
                            cx.b.build_int_truncate(u, i64t, "systf_ut").unwrap()
                        }
                    };
                    (
                        fit(v),
                        unk,
                        u64::from(pv.signed),
                        u64::from(pv.width.min(64)),
                    )
                }
            };
            store(i * ARG_WORDS, bits);
            store(i * ARG_WORDS + 1, unk);
            store(i * ARG_WORDS + 2, i64t.const_int(tag, false));
            store(i * ARG_WORDS + 3, i64t.const_int(width, false));
        }
        descs.push(d);
    }
    let range = {
        let r = e.source_range();
        (r.start.buffer, r.start.offset, r.end.offset)
    };
    let site = SITES.with(|s| {
        let mut s = s.borrow_mut();
        s.push(SiteDesc {
            name: decl.name.clone(),
            args: descs,
            is_func: decl.is_func,
            range,
            text: sv_sim_sim::process::cfg::source_text_range(e.source_range()),
        });
        s.len() - 1
    });
    // The result: `n` value words, `n` unknown words, the tag.
    let n_out = sv_sim_exec::vpi_data::result_words(decl.sized);
    let out = entry_alloca(cx, i64t.array_type(2 * n_out as u32 + 1), "systf_out");
    let f = cx
        .module
        .get_function(SV_RT_VPI_SYSTF_SYMBOL)
        .unwrap_or_else(|| {
            cx.module.add_function(
                SV_RT_VPI_SYSTF_SYMBOL,
                cx.ctx
                    .void_type()
                    .fn_type(&[i32t.into(), ptr.into(), i32t.into(), ptr.into()], false),
                Some(Linkage::External),
            )
        });
    cx.b.build_call(
        f,
        &[
            i32t.const_int(site as u64, false).into(),
            buf.into(),
            i32t.const_int(args.len() as u64, false).into(),
            out.into(),
        ],
        "systf",
    )
    .unwrap();
    // `calltf` may have written any signal (`vpi_put_value` on an argument,
    // or on a handle it looked up): the process's shadows read STATE again.
    crate::dpi::reload_shadows(cx);
    let word = |k: u64| {
        let p = unsafe {
            cx.b.build_gep(i64t, out, &[i64t.const_int(k, false)], "systf_o")
        }
        .unwrap();
        cx.b.build_load(i64t, p, "systf_ret")
            .unwrap()
            .into_int_value()
    };
    if decl.real {
        let f =
            cx.b.build_bit_cast(word(0), cx.ctx.f64_type(), "systf_rf")
                .unwrap()
                .into_float_value();
        return Ok(Some(LoweredValue::Real(RealValue { llvm: f })));
    }
    // A task, or an integral function at its declared return type: `integer`
    // (32-bit signed), a `vpiTimeFunc`'s 64-bit unsigned `time`, or a sized
    // function's `sizetf` width.
    let (width, signed) = decl.sized.unwrap_or_else(|| {
        e.expr_type()
            .filter(|t| t.is_integral() && t.value_width() <= 64)
            .map_or((32, true), |t| (t.value_width(), t.is_signed()))
    });
    // Plane `k` (0: value, 1: unknown) of the result, `n_out` words wide.
    let plane = |k: usize, name| {
        let wide = op.i_n(64 * n_out as u32);
        let p = unsafe {
            cx.b.build_gep(
                i64t,
                out,
                &[i64t.const_int((k * n_out) as u64, false)],
                "systf_p",
            )
        }
        .unwrap();
        let x = cx.b.build_load(wide, p, name).unwrap().into_int_value();
        if width == 64 * n_out as u32 {
            x
        } else {
            cx.b.build_int_truncate(x, op.i_n(width), name).unwrap()
        }
    };
    let (v, u) = (plane(0, "systf_v"), plane(1, "systf_u"));
    Ok(Some(LoweredValue::Packed(op.pack(
        v,
        u,
        width,
        if cx.repr == ValueRepr::FourState {
            ValueRepr::FourState
        } else {
            ValueRepr::TwoState
        },
        signed,
    ))))
}