use std::collections::HashMap;
use crate::types::{TypeWidth, int_type};
use inkwell::AddressSpace;
use inkwell::module::Linkage;
use inkwell::values::{BasicMetadataValueEnum, PointerValue};
use sv_lang::kinds::{ExpressionKind, StatementKind, SymbolKind};
use sv_lang::{Expression, Statement};
use sv_sim_sim::process::cfg::{live_statements, subroutine_can_abort, unwrap_invalid};
use sv_sim_sim::symid::{SymbolId, SymbolUid};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, MemAccessorCtx, lower_expr};
use crate::ops::OpBuilder;
use crate::process_lower::{SignalSlot, SignalSlotClass};
pub const SV_RT_DEFERRED_CALL_SYMBOL: &str = "sv_rt_deferred_call";
pub(crate) struct DeferredEnv<'a, 'ctx> {
pub handle: PointerValue<'ctx>,
pub signals: &'a HashMap<SymbolId, SignalSlot>,
}
pub(crate) fn action_call<'d>(stmt: &Statement<'d>) -> Option<Expression<'d>> {
let s = unwrap_invalid(*stmt);
let kind = s.kind();
if kind == StatementKind::ExpressionStatement {
let e = s.expr()?;
return (e.kind() == ExpressionKind::Call && e.is_system_call() == Some(false))
.then_some(e);
}
if kind == StatementKind::List || kind == StatementKind::Block {
let kids = live_statements(&s);
return if kids.len() == 1 {
action_call(&kids[0])
} else {
None
};
}
None
}
fn refuse<'d>(cx: &LowerCtx<'_, 'd, '_>, why: &str) -> LoweringError {
LoweringError::unsupported_at(
&format!(
"a deferred assertion's action calling {why} (LRM 16.4.1: the call is made when the \
report matures; only a call of a plain task or void function with by-value \
arguments of up to 64 bits, or real, is supported)"
),
&cx.fn_sym,
)
}
pub(crate) fn lower_deferred_call<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
env: &DeferredEnv<'_, 'ctx>,
call: Expression<'d>,
is_final: bool,
control: Option<&str>,
) -> Result<(), LoweringError> {
use sv_lang::MethodFlags;
let callee = call
.call_subroutine()
.ok_or_else(|| refuse(cx, "a subroutine with no callee symbol"))?;
if callee.subroutine_flags().contains(MethodFlags::DPI_IMPORT) {
return Err(refuse(cx, "a DPI import"));
}
if sv_sim_sim::netlist::vif::call_targets(&call).is_some()
|| sv_sim_sim::netlist::itf::call_extern_targets(&call).is_some()
|| sv_sim_sim::netlist::itf::is_vif_extern_call(&call)
{
return Err(refuse(cx, "a virtual interface or extern interface method"));
}
if call.call_this_class().is_some() || callee.subroutine_this_var().is_some() {
return Err(refuse(cx, "a class method"));
}
if crate::func::array_result(&callee).is_some() {
return Err(refuse(cx, "a function returning an unpacked array"));
}
if callee
.subroutine_return_type()
.is_some_and(|t| crate::huge::is_image_type(&t))
{
return Err(refuse(cx, "a function returning more than 65535 bits"));
}
if sv_sim_sim::process::cfg::subroutine_can_suspend(&callee) {
return Err(refuse(
cx,
"a task that waits or runs a nonblocking assignment",
));
}
if subroutine_can_abort(&callee) || crate::dpi::reaches_context_import(&callee) {
return Err(refuse(
cx,
"a subroutine that disables a block or calls a context DPI import",
));
}
let abi = crate::func::subroutine_abi(&callee);
if abi.syncs_state {
return Err(refuse(cx, "a subroutine that samples a covergroup"));
}
let formals: Vec<sv_lang::Symbol<'d>> = callee.subroutine_arguments().collect();
let actuals = call.operands();
if formals.len() != actuals.len() {
return Err(refuse(cx, "a subroutine with a mismatched argument count"));
}
let mut kinds: Vec<ArgKind> = Vec::new();
for f in &formals {
let ty = f
.value_type()
.ok_or_else(|| refuse(cx, "a formal with no type"))?;
if crate::func::is_by_reference(f) {
return Err(refuse(cx, "an output, inout or ref argument"));
}
if crate::arrays::array_shape(&ty).is_some()
|| sv_sim_sim::state::storage::is_collection(&ty)
|| ty.is_string()
{
return Err(refuse(cx, "an array, queue or string argument"));
}
if ty.is_floating() {
kinds.push(ArgKind::Real {
short: ty.bit_width() == 32,
});
} else if ty.is_integral() && ty.value_width() <= 64 {
kinds.push(ArgKind::Packed {
width: ty.value_width(),
});
} else {
return Err(refuse(cx, "an argument wider than 64 bits"));
}
}
for sig in &abi.signals {
match env.signals.get(&sig.uid()) {
Some(info) => {
if matches!(info.class, SignalSlotClass::Memory { .. })
|| crate::huge::is_huge_width(info.width)
{
return Err(refuse(cx, "a subroutine using a memory or huge signal"));
}
}
None if is_static_input_formal(sig) => {}
None => {
return Err(refuse(
cx,
&format!(
"a subroutine reaching `{}`, which has no state slot in the calling \
process",
sig.name()
),
));
}
}
}
let callee_fn = crate::func::resolve_callee_fn(cx, callee)?;
let n_hidden = abi.signals.len() + usize::from(abi.needs_handle);
if callee_fn.count_params() as usize != formals.len() + n_hidden {
return Err(refuse(cx, "a subroutine with an unexpected parameter list"));
}
let ctx = cx.ctx;
let i128t = ctx.i128_type();
let i64t = ctx.i64_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let buffer = if kinds.is_empty() {
ptr_ty.const_null()
} else {
let arr = i128t.array_type(kinds.len() as u32);
crate::expr::entry_alloca(cx, arr, "deferred_args")
};
for (i, ((formal, actual), kind)) in formals.iter().zip(actuals).zip(&kinds).enumerate() {
let bits = match kind {
ArgKind::Real { short } => {
let rv = lower_expr(cx, actual)?
.into_real("a non-real value passed to a real formal", &cx.fn_sym)?;
let f = crate::real::as_f64(cx, rv.llvm);
if *short {
let s =
cx.b.build_float_trunc(f, ctx.f32_type(), "deferred_shortreal")
.unwrap();
let w =
cx.b.build_bit_cast(s, ctx.i32_type(), "deferred_real_bits")
.unwrap()
.into_int_value();
cx.b.build_int_z_extend(w, i128t, "deferred_arg").unwrap()
} else {
let w =
cx.b.build_bit_cast(f, i64t, "deferred_real_bits")
.unwrap()
.into_int_value();
cx.b.build_int_z_extend(w, i128t, "deferred_arg").unwrap()
}
}
ArgKind::Packed { width } => {
let pv = crate::real::to_storage(
cx,
crate::collections::owned(cx, actual)?,
*width,
"a >128-bit deferred action argument",
)?;
let _ = formal;
cx.b.build_int_z_extend_or_bit_cast(pv.llvm, i128t, "deferred_arg")
.unwrap()
}
};
let slot = unsafe {
cx.b.build_gep(
i128t,
buffer,
&[i64t.const_int(i as u64, false)],
"deferred_arg_slot",
)
.unwrap()
};
cx.b.build_store(slot, bits).unwrap();
}
let thunk_name = unique_thunk_name(cx, &call);
let thunk = build_thunk(cx, env, &thunk_name, callee_fn, &abi, &formals, &kinds)?;
let void_t = ctx.void_type();
let i32t = ctx.i32_type();
let f = cx
.module
.get_function(SV_RT_DEFERRED_CALL_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DEFERRED_CALL_SYMBOL,
void_t.fn_type(
&[
i32t.into(),
ptr_ty.into(),
ptr_ty.into(),
ptr_ty.into(),
ptr_ty.into(),
i64t.into(),
],
false,
),
None,
)
});
let mode = if is_final {
sv_sim_sim::sched::DEFERRED_MODE_FINAL
} else {
sv_sim_sim::sched::DEFERRED_MODE_OBSERVED
};
let control = match control {
Some(text) => {
crate::process_lower::build_c_string_global(cx.ctx, cx.module, text, "assert_site")
}
None => ptr_ty.const_null(),
};
cx.b.build_call(
f,
&[
i32t.const_int(u64::from(mode), false).into(),
control.into(),
thunk.as_global_value().as_pointer_value().into(),
env.handle.into(),
buffer.into(),
i64t.const_int(16 * kinds.len() as u64, false).into(),
],
"deferred_call",
)
.unwrap();
Ok(())
}
#[derive(Clone, Copy)]
enum ArgKind {
Packed { width: u32 },
Real { short: bool },
}
fn is_static_input_formal(sig: &sv_lang::Symbol<'_>) -> bool {
sig.kind() == SymbolKind::FormalArgument
&& sig.formal_argument_direction() == sv_lang::ArgumentDirection::In
&& sig
.value_type()
.is_some_and(|t| t.is_integral() && !crate::huge::is_huge_width(t.value_width()))
}
fn unique_thunk_name(cx: &LowerCtx<'_, '_, '_>, call: &Expression<'_>) -> String {
let base = format!(
"__svsim_deferred${}${}",
cx.fn_sym.hierarchical_path(),
call.source_range().start.offset
);
let mut name = base.clone();
let mut n = 0;
while cx.module.get_function(&name).is_some() {
n += 1;
name = format!("{base}${n}");
}
name
}
#[allow(clippy::too_many_arguments)]
fn build_thunk<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
env: &DeferredEnv<'_, 'ctx>,
name: &str,
callee_fn: inkwell::values::FunctionValue<'ctx>,
abi: &crate::func::SubroutineAbi<'d>,
formals: &[sv_lang::Symbol<'d>],
kinds: &[ArgKind],
) -> Result<inkwell::values::FunctionValue<'ctx>, LoweringError> {
let ctx = cx.ctx;
let module = cx.module;
let fns = crate::accessor::accessor_fns_of(module).ok_or_else(|| {
LoweringError::unsupported_at(
"a deferred assertion in a module without the accessor ABI",
&cx.fn_sym,
)
})?;
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let thunk = module.add_function(
name,
ctx.void_type()
.fn_type(&[ptr_ty.into(), ptr_ty.into()], false),
Some(Linkage::External),
);
let b = ctx.create_builder();
b.position_at_end(ctx.append_basic_block(thunk, "entry"));
let handle = thunk.get_nth_param(0).unwrap().into_pointer_value();
let args = thunk.get_nth_param(1).unwrap().into_pointer_value();
let tcx = LowerCtx {
ctx,
b,
repr: cx.repr,
locals: HashMap::new(),
fn_sym: cx.fn_sym,
module,
mem_signals: HashMap::new(),
mem_accessor: Some(MemAccessorCtx { handle, fns: &fns }),
lvalue_ref: Default::default(),
deferred_start: Default::default(),
unbounded: Default::default(),
coll_temp: Default::default(),
iter: Default::default(),
loop_exits: Default::default(),
block_exits: Default::default(),
write_through: None,
cells: Default::default(),
copy_out: Default::default(),
ctor_this: Default::default(),
decl_scale: Default::default(),
inline_this: None,
end_flags: Default::default(),
specify_dest: Default::default(),
elem_refs: Default::default(),
};
let op = OpBuilder { ctx, b: &tcx.b };
let i128t = ctx.i128_type();
let i64t = ctx.i64_type();
let mut call_args: Vec<BasicMetadataValueEnum<'ctx>> = Vec::new();
for (i, (formal, kind)) in formals.iter().zip(kinds).enumerate() {
let slot = unsafe {
tcx.b
.build_gep(
i128t,
args,
&[i64t.const_int(i as u64, false)],
"deferred_arg_slot",
)
.unwrap()
};
let raw = tcx
.b
.build_load(i128t, slot, "deferred_arg")
.unwrap()
.into_int_value();
match kind {
ArgKind::Real { short } => {
if *short {
let w = tcx
.b
.build_int_truncate(raw, ctx.i32_type(), "deferred_real_bits")
.unwrap();
let f = tcx
.b
.build_bit_cast(w, ctx.f32_type(), "deferred_shortreal")
.unwrap();
call_args.push(f.into());
} else {
let w = tcx
.b
.build_int_truncate(raw, i64t, "deferred_real_bits")
.unwrap();
let f = tcx
.b
.build_bit_cast(w, ctx.f64_type(), "deferred_real")
.unwrap();
call_args.push(f.into());
}
}
ArgKind::Packed { width } => {
let ty = op.packed_type(*width, tcx.repr);
let _ = formal;
call_args.push(
tcx.b
.build_int_truncate_or_bit_cast(raw, ty, "deferred_arg_value")
.unwrap()
.into(),
);
}
}
}
let mut shadows = Vec::new();
for sig in &abi.signals {
match env.signals.get(&sig.uid()) {
Some(info) => {
let raw = match info.class {
SignalSlotClass::Wide => {
crate::accessor::build_get_wide(&tcx, &fns, handle, info.slot, info.width)
}
_ => {
crate::accessor::build_get_scalar(&tcx, &fns, handle, info.slot, info.width)
}
};
let ptr = crate::expr::entry_alloca(&tcx, raw.get_type(), "deferred_shadow");
tcx.b.build_store(ptr, raw).unwrap();
call_args.push(ptr.into());
shadows.push((sig.uid(), ptr, *info));
}
None => {
let w = sig.value_type().map(|t| t.value_width()).unwrap_or(1);
let ty = op.packed_type(w, tcx.repr);
let scratch = crate::expr::entry_alloca(&tcx, ty, "deferred_formal_scratch");
tcx.b.build_store(scratch, ty.const_zero()).unwrap();
call_args.push(scratch.into());
}
}
}
if abi.needs_handle {
call_args.push(handle.into());
}
tcx.b
.build_call(callee_fn, &call_args, "deferred_action")
.unwrap();
for (id, ptr, info) in &shadows {
if !abi.written.contains(id) {
continue;
}
let cur = tcx
.b
.build_load(int_type(ctx, info.width * 2), *ptr, "deferred_flush")
.unwrap()
.into_int_value();
match info.class {
SignalSlotClass::Wide => {
crate::accessor::build_set_wide(&tcx, &fns, handle, info.slot, cur, info.width)
}
_ => crate::accessor::build_set_scalar(&tcx, &fns, handle, info.slot, cur, info.width),
}
}
tcx.b.build_return(None).unwrap();
if !thunk.verify(true) {
return Err(LoweringError::unsupported_at(
"a deferred assertion's call thunk failed LLVM verification",
&cx.fn_sym,
));
}
Ok(thunk)
}