vj-codegen 0.2.0

LLVM code generator of the vj SystemVerilog simulator.
//! Lowering of sv-lang `Type`s to LLVM types: iN for two-state integral, i(2N)
//! (value lane + unknown lane) for four-state integral, and (Task 8, CG-037)
//! a plain LLVM `FloatType` for `real`/`shortreal`/`realtime` -- floats have
//! no unknown plane, so they are never packed the way integral types are.
use std::num::NonZeroU32;

use inkwell::context::Context;
use inkwell::types::{BasicMetadataTypeEnum, BasicTypeEnum, FloatType, FunctionType, IntType};

use crate::error::LoweringError;
use crate::value::ValueRepr;

// Corrected vs. plan: inkwell 0.10 (LLVM22) `Context::custom_width_int_type`
// takes `NonZeroU32` and returns `Result<IntType, &'static str>`, not a bare
// `u32` -> `IntType`.
pub(crate) fn int_type(ctx: &Context, w: u32) -> IntType<'_> {
    ctx.custom_width_int_type(NonZeroU32::new(w).expect("zero-width int type"))
        .expect("custom_width_int_type")
}

/// Task 8 (CG-037): `lower_type`'s result is no longer always an `IntType`
/// -- a `real`/`shortreal` `Type` lowers to an LLVM `FloatType` instead.
/// Deviation from the plan text (which left `lower_type`'s 3 call sites in
/// `func.rs` unspecified beyond "update them"): each call site needs both a
/// `BasicTypeEnum` (for `build_alloca`) and a `BasicMetadataTypeEnum` (for
/// `fn_type`'s parameter list), so this enum carries small conversion
/// helpers for both rather than making every call site match by hand.
#[derive(Debug, Clone, Copy)]
pub enum LoweredType<'ctx> {
    Int(IntType<'ctx>),
    Float(FloatType<'ctx>),
}

impl<'ctx> LoweredType<'ctx> {
    pub fn into_basic_type_enum(self) -> BasicTypeEnum<'ctx> {
        match self {
            LoweredType::Int(t) => t.into(),
            LoweredType::Float(t) => t.into(),
        }
    }

    pub fn into_basic_metadata_type_enum(self) -> BasicMetadataTypeEnum<'ctx> {
        match self {
            LoweredType::Int(t) => t.into(),
            LoweredType::Float(t) => t.into(),
        }
    }

    pub fn fn_type(
        &self,
        param_types: &[BasicMetadataTypeEnum<'ctx>],
        is_var_args: bool,
    ) -> FunctionType<'ctx> {
        match self {
            LoweredType::Int(t) => t.fn_type(param_types, is_var_args),
            LoweredType::Float(t) => t.fn_type(param_types, is_var_args),
        }
    }
}

/// A value's width in bits: `Type::bit_width()`, except for the types
/// slang reports as 0 bits that vj stores as bits anyway -- a `chandle`,
/// `string`, class handle (64, classes plan Task 5 -- LRM 1800-2023 ยง8.4: a
/// class variable is an OBJECT HANDLE, never the object's contents, so its
/// own stored representation is a plain 64-bit reference exactly like a
/// `chandle`'s) or collection handle (64) and an unpacked struct/union or
/// fixed-size unpacked array (its flat stored form,
/// `vj_sim::state::storage`).
pub trait TypeWidth {
    fn value_width(&self) -> u32;
}

impl TypeWidth for sv_lang::Type<'_> {
    fn value_width(&self) -> u32 {
        if self.is_chandle()
            || self.is_covergroup()
            || self.is_virtual_interface()
            || self.is_string()
            || self.is_class()
            || vj_sim::state::storage::is_collection(self)
        {
            64
        } else if vj_sim::state::storage::is_unpacked_aggregate(self)
            || (self.is_unpacked_array() && !self.is_dynamically_sized_array())
        {
            vj_sim::state::storage::storage_width(self).unwrap_or(0) as u32
        } else {
            self.bit_width() as u32
        }
    }
}

pub fn lower_type<'ctx>(
    ctx: &'ctx Context,
    ty: &sv_lang::Type,
    repr: ValueRepr,
) -> Result<LoweredType<'ctx>, LoweringError> {
    // Task 8 (CG-037): `real`/`shortreal`/`realtime` -- checked before the
    // integral check below, since a floating `Type` is never `is_integral()`
    // and must not fall into that arm's error.
    if ty.is_floating() {
        return Ok(LoweredType::Float(match ty.floating_kind() {
            sv_lang::FloatKind::ShortReal => ctx.f32_type(),
            sv_lang::FloatKind::Real | sv_lang::FloatKind::RealTime => ctx.f64_type(),
        }));
    }
    if !ty.is_integral()
        && !ty.is_chandle()
        && !ty.is_covergroup()
        && !ty.is_virtual_interface()
        && !ty.is_string()
        && !ty.is_class()
        && !vj_sim::state::storage::is_collection(ty)
        && !vj_sim::state::storage::is_unpacked_aggregate(ty)
    {
        return Err(LoweringError::unsupported(
            &format!("non-integral type {}", ty.to_sv_string()),
            "<type>",
        ));
    }
    // Corrected vs. plan: `Type::bit_width()` returns `u64`, not `u32`
    // (do not confuse with `SVInt::bit_width() -> u32`). Cast at the call
    // site before building the LLVM int type.
    let n = ty.value_width();
    // A packed `i(2N)` that wide is the 50-115 s JIT compile `crate::huge`
    // exists to avoid: past `WIDE_THRESHOLD` it is memory-resident only. A
    // value between the huge threshold and that ceiling is still an LLVM
    // integer where it is stored (an argument, a return, a local, an array
    // element); the huge operators read and write its bytes as an image.
    if n > crate::value::WIDE_THRESHOLD {
        return Err(LoweringError::unsupported(
            &format!(
                "a {n}-bit value of type {} here (LRM 1800-2023 6.9.1): vectors wider than {} \
                 bits are supported as module-level signals, not as function arguments, \
                 returns or locals",
                ty.to_sv_string(),
                crate::value::WIDE_THRESHOLD,
            ),
            "<type>",
        ));
    }
    Ok(LoweredType::Int(match repr {
        ValueRepr::TwoState => int_type(ctx, n),
        ValueRepr::FourState => int_type(ctx, n * 2),
    }))
}

/// An `alloca` in the entry block of the function `b` is positioned in:
/// code inside an SV loop runs on a back-edge within one activation, so an
/// `alloca` at the use site would grow the stack on every iteration (a
/// 200000-iteration loop writing a memory row overflowed the 8 MiB stack).
pub(crate) fn entry_alloca_in<'ctx, T: inkwell::types::BasicType<'ctx>>(
    b: &inkwell::builder::Builder<'ctx>,
    ty: T,
    name: &str,
) -> inkwell::values::PointerValue<'ctx> {
    let entry = b
        .get_insert_block()
        .and_then(|bb| bb.get_parent())
        .and_then(|f| f.get_first_basic_block())
        .expect("lowering happens inside a function with an entry block");
    let tmp = entry.get_context().create_builder();
    match entry.get_first_instruction() {
        Some(first) => tmp.position_before(&first),
        None => tmp.position_at_end(entry),
    }
    tmp.build_alloca(ty, name).unwrap()
}