vj-codegen 0.1.0

LLVM code generator of the vj SystemVerilog simulator.
//! User-defined nettype resolution functions (LRM 1800-2023 6.6.7).
//!
//! A `nettype T name with resolve_fn;` net's value is the function's result
//! over the array of every driver's value. The function is an ordinary SV
//! function (lowered by `crate::func`); the simulator's resolver process
//! (`sv-sim-bin::run`, which knows the drivers' current values) needs to call
//! it with a runtime-built dynamic array and read the result back as plain
//! words. This module emits that bridge: for each resolution function a
//! wrapper
//!
//! ```text
//! void sv_nettype_res_<mangled>(i64 handle, ptr out)
//! ```
//!
//! that calls the function with the dynamic array `handle`
//! (`sv_sim_sim::collections`; 0 is the empty array) and stores the result
//! into `out` as `ceil(2W / 64)` little-endian words in the collection
//! element format -- the value lane in the low `W` bits and the unknown lane
//! in the high `W` bits (a `real` is its IEEE bits with a clear unknown lane).

use inkwell::context::Context;
use inkwell::module::Module;
use inkwell::values::BasicValueEnum;

use crate::error::LoweringError;
use crate::func::{mangled_subroutine_name, subroutine_abi};

fn int_type(ctx: &Context, w: u32) -> inkwell::types::IntType<'_> {
    ctx.custom_width_int_type(std::num::NonZeroU32::new(w).expect("zero-width int type"))
        .expect("custom_width_int_type")
}

/// The wrapper symbol of the resolution function `sym`.
pub fn resolver_symbol(sym: &sv_lang::Symbol<'_>) -> Result<String, LoweringError> {
    Ok(format!("sv_nettype_res_{}", mangled_subroutine_name(sym)?))
}

/// The calling convention of an emitted wrapper (see the module doc).
pub type ResolverFn = unsafe extern "C" fn(u64, *mut u64);

/// Emits the wrapper of resolution function `sym` (already declared in
/// `module` under its mangled name) unless it exists.
pub(crate) fn emit_resolver<'ctx>(
    ctx: &'ctx Context,
    module: &Module<'ctx>,
    sym: &sv_lang::Symbol<'_>,
) -> Result<(), LoweringError> {
    let name = resolver_symbol(sym)?;
    if module.get_function(&name).is_some() {
        return Ok(());
    }
    let err = |m: &str| LoweringError::unsupported_at(m, sym);
    let out_words = result_words(sym)
        .ok_or_else(|| err("a nettype resolution function whose result has no stored form"))?;
    let abi = subroutine_abi(sym);
    if !abi.signals.is_empty() || abi.needs_handle {
        return Err(err(
            "a nettype resolution function that reads or writes module-level state",
        ));
    }
    let mangled = mangled_subroutine_name(sym)?;
    let callee = module
        .get_function(&mangled)
        .ok_or_else(|| err("a nettype resolution function that was never lowered"))?;
    if callee.count_params() != 1 {
        return Err(err(
            "a nettype resolution function must take exactly one dynamic-array argument",
        ));
    }
    let i64_ty = ctx.i64_type();
    let ptr_ty = ctx.ptr_type(inkwell::AddressSpace::default());
    let f = module.add_function(
        &name,
        ctx.void_type()
            .fn_type(&[i64_ty.into(), ptr_ty.into()], false),
        None,
    );
    let b = ctx.create_builder();
    b.position_at_end(ctx.append_basic_block(f, "entry"));
    let handle = f.get_nth_param(0).unwrap().into_int_value();
    let out = f.get_nth_param(1).unwrap().into_pointer_value();

    // The dynamic array argument lowers to an integer that carries the
    // 64-bit handle in its low bits (the unknown lane above it stays 0).
    let arg = match callee.get_type().get_param_types()[0] {
        inkwell::types::BasicMetadataTypeEnum::IntType(t) => b
            .build_int_z_extend_or_bit_cast(handle, t, "handle")
            .map_err(|_| err("a resolution function argument of the wrong type"))?,
        _ => {
            return Err(err(
                "a nettype resolution function argument that is not a dynamic array",
            ));
        }
    };
    let call = b.build_call(callee, &[arg.into()], "res").unwrap();
    let ret = call
        .try_as_basic_value()
        .basic()
        .ok_or_else(|| err("a nettype resolution function with no result"))?;
    let out_ty = int_type(ctx, 64 * out_words);
    let bits = match ret {
        BasicValueEnum::FloatValue(v) => {
            let w = match v.get_type().get_bit_width() {
                32 => 32,
                _ => 64,
            };
            let bits = b
                .build_bit_cast(v, int_type(ctx, w), "res_bits")
                .unwrap()
                .into_int_value();
            b.build_int_z_extend(bits, out_ty, "res_ext").unwrap()
        }
        BasicValueEnum::IntValue(v) => {
            if v.get_type().get_bit_width() > 64 * out_words {
                return Err(err(
                    "a nettype resolution function result wider than the nettype's data type",
                ));
            }
            b.build_int_z_extend_or_bit_cast(v, out_ty, "res_ext")
                .unwrap()
        }
        _ => {
            return Err(err(
                "a nettype resolution function result of an unsupported type",
            ));
        }
    };
    let store = b.build_store(out, bits).unwrap();
    store.set_alignment(8).unwrap();
    b.build_return(None).unwrap();
    Ok(())
}

/// The word count of `sym`'s result in the wrapper's output format:
/// `ceil(2W / 64)` for a result stored in `W` bits.
pub fn result_words(sym: &sv_lang::Symbol<'_>) -> Option<u32> {
    let ty = sym.subroutine_return_type()?;
    let w = sv_sim_sim::state::storage::storage_width(&ty)?;
    Some((2 * w).div_ceil(64) as u32)
}