use crate::types::TypeWidth;
use std::collections::HashMap;
use sv_sim_sim::symid::SymbolUid;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::types::FunctionType;
use inkwell::values::{FunctionValue, PointerValue};
use sv_lang::MethodFlags;
use sv_lang::kinds::{ExpressionKind, StatementKind, SymbolKind};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, lower_expr};
use crate::mangle::{self, EntityTag};
use crate::ops::OpBuilder;
use crate::stmt_shared::{
current_block_and_fn, current_block_terminated, lower_case, lower_do_while_loop,
lower_for_loop, lower_foreach_loop, lower_forever_loop, lower_loop_jump, lower_repeat_loop,
lower_while_loop,
};
use crate::types::lower_type;
use crate::value::{PackedValue, ValueRepr};
pub(crate) fn subroutine_fn_type<'ctx, 'd>(
ctx: &'ctx Context,
sym: &sv_lang::Symbol<'d>,
repr: ValueRepr,
receiver: bool,
) -> Result<(FunctionType<'ctx>, bool), LoweringError> {
let args: Vec<sv_lang::Symbol<'d>> = sym.subroutine_arguments().collect();
let ret_sv = sym.subroutine_return_type();
let is_void_return = ret_sv.as_ref().is_none_or(sv_lang::Type::is_void);
let ptr_ty = ctx.ptr_type(inkwell::AddressSpace::default());
let mut param_tys = Vec::new();
if receiver {
param_tys.push(ctx.i64_type().into());
}
for a in &args {
let t = a
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("arg has no type", a))?;
if is_by_reference(a) || crate::arrays::array_shape(&t).is_some() {
param_tys.push(ptr_ty.into());
} else if crate::huge::is_image_type(&t) {
param_tys.push(ptr_ty.into());
} else {
param_tys.push(lower_type(ctx, &t, repr)?.into_basic_metadata_type_enum());
}
}
let abi = subroutine_abi(sym);
for _ in &abi.signals {
param_tys.push(ptr_ty.into());
}
if abi.needs_handle {
param_tys.push(ptr_ty.into());
}
let returns_array =
array_result(sym).is_some() || ret_sv.as_ref().is_some_and(crate::huge::is_image_type);
if returns_array {
param_tys.push(ptr_ty.into());
}
let fn_ty = if is_void_return || returns_array {
ctx.void_type().fn_type(¶m_tys, false)
} else {
lower_type(ctx, ret_sv.as_ref().unwrap(), repr)?.fn_type(¶m_tys, false)
};
Ok((fn_ty, is_void_return || returns_array))
}
pub(crate) fn lower_function_body<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
sym: &sv_lang::Symbol<'d>,
repr: ValueRepr,
f: FunctionValue<'ctx>,
this_var: Option<sv_lang::Symbol<'d>>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let base: u32 = u32::from(this_var.is_some());
let args: Vec<sv_lang::Symbol<'d>> = sym.subroutine_arguments().collect();
let ret_sv = sym.subroutine_return_type();
let ret_array = array_result(sym);
let ret_image = ret_sv.as_ref().is_some_and(crate::huge::is_image_type);
let is_void_return =
ret_sv.as_ref().is_none_or(sv_lang::Type::is_void) || ret_array.is_some() || ret_image;
let b = ctx.create_builder();
b.position_at_end(ctx.append_basic_block(f, "entry"));
let mut locals = HashMap::new();
let mut out_inits: Vec<sv_lang::Symbol<'d>> = Vec::new();
for (i, a) in args.iter().enumerate() {
let t = a
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("arg has no type", a))?;
if t.is_event() && a.formal_argument_direction() != sv_lang::ArgumentDirection::In {
return Err(LoweringError::unsupported_at(
"an output, inout or ref event argument (events are passed by value, \
LRM 1800-2023 15.5.5.1; only input event arguments are supported)",
a,
));
}
if t.is_floating() && !is_by_reference(a) {
let width = t.value_width();
let param = f.get_nth_param(base + i as u32).unwrap().into_float_value();
let bits_ty = crate::types::int_type(ctx, width);
let bits = b
.build_bit_cast(param, bits_ty, "real_arg_bits")
.unwrap()
.into_int_value();
let slot_ty = storage_type(ctx, width, repr);
let bits = b
.build_int_z_extend_or_bit_cast(bits, slot_ty, "real_arg_slot")
.unwrap();
let slot = b.build_alloca(slot_ty, a.name()).unwrap();
b.build_store(slot, bits).unwrap();
locals.insert(a.uid(), (slot, width, false));
continue;
}
if let Some(shape) = crate::arrays::array_shape(&t) {
let ptr = f
.get_nth_param(base + i as u32)
.unwrap()
.into_pointer_value();
locals.insert(
a.uid(),
(ptr, shape.elem.value_width(), shape.elem.is_signed()),
);
if a.formal_argument_direction() == sv_lang::ArgumentDirection::Out {
out_inits.push(*a);
}
continue;
}
let width = t.value_width();
if crate::huge::is_image_type(&t) {
crate::huge::require_images(repr, a)?;
let ptr = f
.get_nth_param(base + i as u32)
.unwrap()
.into_pointer_value();
locals.insert(a.uid(), (ptr, width, t.is_signed()));
if a.formal_argument_direction() == sv_lang::ArgumentDirection::Out {
out_inits.push(*a);
}
continue;
}
if is_by_reference(a) {
let ptr = f
.get_nth_param(base + i as u32)
.unwrap()
.into_pointer_value();
locals.insert(a.uid(), (ptr, width, t.is_signed()));
if a.formal_argument_direction() == sv_lang::ArgumentDirection::Out {
out_inits.push(*a);
}
continue;
}
let slot = b
.build_alloca(lower_type(ctx, &t, repr)?.into_basic_type_enum(), a.name())
.unwrap();
b.build_store(
slot,
f.get_nth_param(base + i as u32).unwrap().into_int_value(),
)
.unwrap();
locals.insert(a.uid(), (slot, width, t.is_signed()));
}
if let Some(tv) = this_var {
let slot = b.build_alloca(storage_type(ctx, 64, repr), "this").unwrap();
let recv = f.get_nth_param(0).unwrap().into_int_value();
let recv = if repr == ValueRepr::FourState {
b.build_int_z_extend(recv, storage_type(ctx, 64, repr), "this_zext")
.unwrap()
} else {
recv
};
b.build_store(slot, recv).unwrap();
locals.insert(tv.uid(), (slot, 64, false));
}
let static_formals: Vec<(sv_lang::Symbol<'d>, inkwell::values::PointerValue<'_>, u32)> = args
.iter()
.filter(|a| sv_sim_sim::netlist::is_static_procedural_var(a))
.filter_map(|a| locals.get(&a.uid()).map(|&(p, w, _)| (*a, p, w)))
.collect();
let abi = subroutine_abi(sym);
for (k, sig) in abi.signals.iter().enumerate() {
let t = sig
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("module signal has no type", sig))?;
let ptr = f
.get_nth_param(base + (args.len() + k) as u32)
.unwrap()
.into_pointer_value();
locals.insert(sig.uid(), (ptr, t.value_width(), t.is_signed()));
}
let accessor_fns = crate::accessor::accessor_fns_of(module)
.unwrap_or_else(|| crate::accessor::declare_accessor_fns(ctx, module));
let (mem_signals, mem_accessor) = if abi.needs_handle {
let handle = f
.get_nth_param(base + (args.len() + abi.signals.len()) as u32)
.unwrap()
.into_pointer_value();
let scope = sv_sim_sim::netlist::enclosing_instance_body(sym)
.or_else(|| sym.parent())
.ok_or_else(|| {
LoweringError::unsupported_at("subroutine has no enclosing module", sym)
})?;
let slots = crate::module::signal_slots_of(&scope)?;
(
crate::process_lower::mem_signals_of(&slots),
Some(crate::expr::MemAccessorCtx {
handle,
fns: &accessor_fns,
}),
)
} else {
(HashMap::new(), None)
};
let decls = subroutine_locals(sym);
let mut auto_inits: Vec<sv_lang::Symbol<'d>> = Vec::new();
let mut static_inits: Vec<sv_lang::Symbol<'d>> = Vec::new();
for (k, local) in decls.iter().enumerate() {
if this_var.is_some_and(|tv| tv.uid() == local.uid()) {
continue;
}
if sv_sim_sim::netlist::is_static_procedural_var(local) {
continue;
}
let t = local
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("local has no type", local))?;
let array = crate::arrays::array_shape(&t);
if array.is_none()
&& !t.is_integral()
&& !t.is_floating()
&& !t.is_string()
&& !sv_sim_sim::state::storage::is_unpacked_aggregate(&t)
&& !sv_sim_sim::state::storage::is_collection(&t)
&& !t.is_class()
&& !t.is_chandle()
&& !t.is_virtual_interface()
&& !t.is_event()
{
return Err(LoweringError::unsupported_at(
&format!(
"a subroutine local of type `{}` is not supported",
t.to_sv_string()
),
local,
));
}
let (width, signed, llvm_ty): (u32, bool, inkwell::types::BasicTypeEnum<'_>) = match &array
{
Some(_) if sv_sim_sim::state::event_pool::event_count(&t).is_some() => (
64,
false,
storage_type(ctx, 64, repr)
.array_type(sv_sim_sim::state::event_pool::event_count(&t).unwrap_or(0))
.into(),
),
Some(shape) => (
shape.elem.value_width(),
shape.elem.is_signed(),
crate::arrays::row_type(ctx, &shape.elem, repr)
.array_type(shape.rows() as u32)
.into(),
),
None if t.is_event() => (64, false, storage_type(ctx, 64, repr).into()),
None if crate::huge::is_image_type(&t) => {
crate::huge::require_images(repr, local)?;
(
t.value_width(),
t.is_signed(),
crate::huge::image_storage_type(ctx, t.value_width()).into(),
)
}
None => (
t.value_width(),
t.is_signed(),
storage_type(ctx, t.value_width(), repr).into(),
),
};
let ptr = if local.variable_lifetime() == sv_lang::VariableLifetime::Static {
let g = module.add_global(
llvm_ty,
None,
&format!(
"{}.static.{}.{k}",
f.get_name().to_string_lossy(),
local.name()
),
);
g.set_linkage(inkwell::module::Linkage::Internal);
g.set_initializer(&llvm_ty.const_zero());
static_inits.push(*local);
g.as_pointer_value()
} else {
if !sv_sim_sim::process::cfg::is_automatic_block_var(local) {
auto_inits.push(*local);
}
b.build_alloca(llvm_ty, local.name()).unwrap()
};
locals.insert(local.uid(), (ptr, width, signed));
}
let return_var = if is_void_return
|| !ret_sv.as_ref().is_some_and(|t| {
t.is_integral()
|| t.is_floating()
|| t.is_string()
|| t.is_class()
|| sv_sim_sim::state::storage::is_unpacked_aggregate(t)
|| sv_sim_sim::state::storage::is_collection(t)
}) {
None
} else {
sym.subroutine_return_val_var()
};
if let Some(rv) = return_var {
let t = ret_sv.as_ref().expect("non-void has a return type");
let width = t.value_width();
let is_static_fn =
(t.is_integral() || t.is_floating() || sv_sim_sim::state::storage::is_collection(t))
&& sym.subroutine_default_lifetime() == Some(sv_lang::VariableLifetime::Static);
let ptr = if is_static_fn {
let llvm_ty = storage_type(ctx, width, repr);
let g = module.add_global(
llvm_ty,
None,
&format!("{}.static.retval", f.get_name().to_string_lossy()),
);
g.set_linkage(inkwell::module::Linkage::Internal);
g.set_initializer(&llvm_ty.const_zero());
static_inits.push(rv);
g.as_pointer_value()
} else {
auto_inits.push(rv);
b.build_alloca(storage_type(ctx, width, repr), "retval")
.unwrap()
};
locals.insert(rv.uid(), (ptr, width, t.is_signed()));
}
if ret_image && let Some(rv) = sym.subroutine_return_val_var() {
let t = ret_sv.as_ref().expect("non-void has a return type");
crate::huge::require_images(repr, sym)?;
let ptr = f
.get_last_param()
.expect("an image-valued function has a result pointer")
.into_pointer_value();
locals.insert(rv.uid(), (ptr, t.value_width(), t.is_signed()));
auto_inits.push(rv);
}
if let Some(shape) = &ret_array
&& let Some(rv) = sym.subroutine_return_val_var()
{
let ptr = f
.get_last_param()
.expect("an array-valued function has a result pointer")
.into_pointer_value();
locals.insert(
rv.uid(),
(ptr, shape.elem.value_width(), shape.elem.is_signed()),
);
auto_inits.push(rv);
}
let mut elem_refs: HashMap<_, crate::collections::ElemRefSlot<'ctx>> = HashMap::new();
if takes_elem_refs(sym) {
for a in args.iter().filter(|a| is_elem_ref_formal(a)) {
let Some(&(ptr, width, signed)) = locals.get(&a.uid()) else {
continue;
};
let pair = elem_ref_pair_ty(ctx, width, repr);
let field = |i, name| b.build_struct_gep(pair, ptr, i, name).unwrap();
elem_refs.insert(
ptr,
crate::collections::ElemRefSlot {
id: field(1, "elem_ref_id"),
id_width: 64,
width,
signed,
part: Some(crate::collections::PartSlots {
lo: field(2, "elem_ref_lo"),
elem_width: field(3, "elem_ref_ew"),
}),
},
);
}
}
let mut cx = LowerCtx {
ctx,
b,
repr,
locals,
fn_sym: *sym,
module,
mem_signals,
mem_accessor,
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: Default::default(),
end_flags: Default::default(),
specify_dest: Default::default(),
elem_refs,
};
for (formal, temp, width) in &static_formals {
let Some(&(signal, _, _)) = cx.locals.get(&formal.uid()) else {
continue;
};
if signal == *temp {
continue;
}
let t = formal
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("arg has no type", formal))?;
let llvm_ty: inkwell::types::BasicTypeEnum<'_> = match crate::arrays::array_shape(&t) {
Some(shape) => crate::arrays::local_array_type(ctx, &shape, repr).into(),
None if crate::huge::is_image_width(*width) => {
crate::huge::image_storage_type(ctx, *width).into()
}
None => storage_type(ctx, *width, repr).into(),
};
let size = inkwell::types::BasicType::size_of(&llvm_ty).expect("a sized storage type");
let dir = formal.formal_argument_direction();
if dir != sv_lang::ArgumentDirection::Out {
cx.b.build_memcpy(signal, 8, *temp, 8, size).unwrap();
}
if is_by_reference(formal) {
cx.copy_out.push((signal, *temp, size));
}
}
let owned_locals = owned_handle_locals(&cx, &decls, this_var);
for local in &owned_locals {
let (ptr, width, _) = cx.locals[&local.uid()];
cx.b.build_store(ptr, storage_type(ctx, width, repr).const_zero())
.unwrap();
}
for local in out_inits.iter().chain(&auto_inits) {
init_local(&cx, local)?;
}
if !static_inits.is_empty() {
let flag = module.add_global(
ctx.bool_type(),
None,
&format!("{}.static_init_done", f.get_name().to_string_lossy()),
);
flag.set_linkage(inkwell::module::Linkage::Internal);
flag.set_initializer(&ctx.bool_type().const_zero());
let done =
cx.b.build_load(ctx.bool_type(), flag.as_pointer_value(), "static_done")
.unwrap()
.into_int_value();
let init_bb = ctx.append_basic_block(f, "static_init");
let body_bb = ctx.append_basic_block(f, "body");
cx.b.build_conditional_branch(done, body_bb, init_bb)
.unwrap();
cx.b.position_at_end(init_bb);
for local in &static_inits {
init_local(&cx, local)?;
}
cx.b.build_store(flag.as_pointer_value(), ctx.bool_type().const_all_ones())
.unwrap();
cx.b.build_unconditional_branch(body_bb).unwrap();
cx.b.position_at_end(body_bb);
}
let frame_site = if crate::vpi_dyn::enabled() {
let automatic = |v: &sv_lang::Symbol<'d>| {
!sv_sim_sim::netlist::is_static_procedural_var(v)
&& v.variable_lifetime() != sv_lang::VariableLifetime::Static
};
let mut vars: Vec<sv_lang::Symbol<'d>> = args
.iter()
.copied()
.filter(|a| {
a.formal_argument_direction() != sv_lang::ArgumentDirection::Ref && automatic(a)
})
.collect();
vars.extend(
decls
.iter()
.copied()
.filter(|l| this_var.is_none_or(|tv| tv.uid() != l.uid()) && automatic(l)),
);
if let Some(rv) = sym.subroutine_return_val_var()
&& cx.locals.contains_key(&rv.uid())
&& sym.subroutine_default_lifetime() == Some(sv_lang::VariableLifetime::Automatic)
{
vars.push(rv);
}
crate::vpi_dyn::emit_frame_enter(
&cx,
sym,
&vars,
&crate::vpi_dyn::ref_formal_placeholders(&cx, sym),
)
} else {
None
};
let block_handle = cx
.mem_accessor
.as_ref()
.filter(|_| crate::covergroup::is_block_target(sym))
.map(|m| m.handle);
if let Some(h) = block_handle {
sync_subroutine_writes(&cx)?;
crate::covergroup::emit_block_event(&cx, h, false)?;
}
let body = sv_sim_sim::process::cfg::proc_body(sym)
.ok_or_else(|| LoweringError::unsupported_at("no body", sym))?;
if this_var.is_some() {
crate::classes::emit_implicit_default_super(&cx)?;
}
lower_stmt(&mut cx, body)?;
if let Some(rv) = return_var
&& !current_block_terminated(&cx)
{
let (ptr, width, signed) = cx.locals[&rv.uid()];
let ret_ty = ret_sv.as_ref().unwrap();
let loaded =
cx.b.build_load(storage_type(ctx, width, repr), ptr, "retval_ld")
.unwrap()
.into_int_value();
if ret_ty.is_floating() {
let pv = PackedValue {
llvm: loaded,
width,
repr,
signed,
};
let rv = crate::real::bits_to_real(&cx, pv);
let v = fit_float_return(&cx, rv.llvm);
copy_out_formals(&cx);
cx.b.build_return(Some(&v)).unwrap();
} else if sv_sim_sim::state::storage::is_collection(ret_ty)
&& sym.subroutine_default_lifetime() == Some(sv_lang::VariableLifetime::Static)
{
let pv = PackedValue {
llvm: loaded,
width,
repr,
signed,
};
let copy = crate::collections::clone_stored(&cx, ret_ty, pv)?;
copy_out_formals(&cx);
cx.b.build_return(Some(©)).unwrap();
} else {
copy_out_formals(&cx);
cx.b.build_return(Some(&loaded)).unwrap();
}
}
if is_void_return && !current_block_terminated(&cx) {
copy_out_formals(&cx);
cx.b.build_return(None).unwrap();
}
let mut released = owned_locals;
released.extend(owned_handle_formals(&cx, &args));
release_locals_before_returns(&cx, f, &released);
if let Some(site) = frame_site {
crate::vpi_dyn::emit_frame_exits_before_returns(&cx, f, site);
}
if let Some(h) = block_handle {
let rets: Vec<_> = f
.get_basic_block_iter()
.filter_map(|bb| bb.get_terminator())
.filter(|t| t.get_opcode() == inkwell::values::InstructionOpcode::Return)
.collect();
for t in rets {
cx.b.position_before(&t);
sync_subroutine_writes(&cx)?;
crate::covergroup::emit_block_event(&cx, h, true)?;
}
}
if f.verify(true) {
Ok(f)
} else {
Err(LoweringError {
message: "function failed LLVM verify".into(),
at: sym.hierarchical_path(),
})
}
}
pub fn package_handle_callee(callee: &sv_lang::Symbol<'_>, caller: &sv_lang::Symbol<'_>) -> bool {
let Some(pkg) = sv_sim_sim::netlist::enclosing_package(callee) else {
return false;
};
callee.kind() == sv_lang::kinds::SymbolKind::Subroutine
&& subroutine_abi(callee).forces
&& sv_sim_sim::netlist::enclosing_package(caller).is_none_or(|p| p.uid() != pkg.uid())
}
#[derive(Clone)]
pub(crate) struct SubroutineAbi<'d> {
pub signals: Vec<sv_lang::Symbol<'d>>,
pub written: std::collections::HashSet<sv_sim_sim::symid::SymbolId>,
pub needs_handle: bool,
pub syncs_state: bool,
pub forces: bool,
pub memories: Vec<sv_lang::Symbol<'d>>,
pub memories_written: std::collections::HashSet<sv_sim_sim::symid::SymbolId>,
}
fn lower_subroutine_force<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
s: &sv_lang::Statement<'d>,
) -> Result<(), LoweringError> {
let handle = cx.mem_accessor.as_ref().map(|m| m.handle).ok_or_else(|| {
LoweringError::unsupported_at(
"a force/assign/release/deassign in a subroutine without an instance handle",
&cx.fn_sym,
)
})?;
if s.kind() == StatementKind::ProceduralAssign
&& let Some(r) = s.expressions().into_iter().next().and_then(|a| a.right())
&& r.constant_value().is_none()
{
let mut stack = vec![r.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& let Some(x) = e.referenced_symbol()
{
if !is_module_level_signal(&x)
&& matches!(x.kind(), SymbolKind::Variable | SymbolKind::FormalArgument)
{
return Err(LoweringError::unsupported_at(
"a force/assign inside a subroutine whose right-hand side reads \
one of the subroutine's own arguments or locals (the forced value \
must track it continuously, LRM 10.6.2, but it only exists during \
the call)",
&cx.fn_sym,
));
}
}
stack.extend(sv_sim_sim::process::cfg::live_children(&n));
}
}
let scope = sv_sim_sim::netlist::enclosing_instance_body(&cx.fn_sym)
.or_else(|| cx.fn_sym.parent())
.ok_or_else(|| {
LoweringError::unsupported_at("subroutine has no enclosing module", &cx.fn_sym)
})?;
let slots = crate::module::signal_slots_of(&scope)?;
sync_subroutine_writes(cx)?;
if s.kind() != StatementKind::ProceduralAssign {
return crate::force::lower_procedural_deassign(cx, handle, &slots, *s);
}
crate::force::lower_procedural_assign(cx, handle, &slots, *s)?;
let mem = cx.mem_accessor.as_ref().expect("checked above");
let mut targets: Vec<_> = s
.expressions()
.into_iter()
.next()
.and_then(|a| a.left())
.into_iter()
.collect();
while let Some(t) = targets.pop() {
if t.kind() == ExpressionKind::Concatenation {
targets.extend(t.operands());
continue;
}
for r in crate::select::lvalue_root_symbols(t) {
let (Some(info), Some(&(ptr, _, _))) = (slots.get(&r.uid()), cx.locals.get(&r.uid()))
else {
continue;
};
if matches!(
info.class,
crate::process_lower::SignalSlotClass::Memory { .. }
) {
continue;
}
let wt = crate::expr::WriteThrough {
handle: mem.handle,
fns: mem.fns,
signals: HashMap::from([(
ptr,
(
info.slot,
info.width,
info.class == crate::process_lower::SignalSlotClass::Wide,
),
)]),
reload: Default::default(),
};
crate::expr::reload_from_state(cx, &wt, ptr);
}
}
Ok(())
}
pub(crate) fn lower_subroutine_nba<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let handle = cx.mem_accessor.as_ref().map(|m| m.handle).ok_or_else(|| {
LoweringError::unsupported_at(
"a nonblocking assignment (<=) outside a process or a subroutine with an instance handle",
&cx.fn_sym,
)
})?;
let scope = sv_sim_sim::netlist::enclosing_instance_body(&cx.fn_sym)
.or_else(|| cx.fn_sym.parent())
.ok_or_else(|| {
LoweringError::unsupported_at("subroutine has no enclosing module", &cx.fn_sym)
})?;
let slots = crate::module::signal_slots_of(&scope)?;
let nba_fn = cx
.module
.get_function(sv_sim_abi::process::NBA_ENQUEUE_SYMBOL)
.unwrap_or_else(|| crate::process_lower::declare_nba_enqueue(cx.ctx, cx.module));
crate::process_lower::lower_nonblocking_assignment(cx, handle, &slots, nba_fn, e)?;
let z = cx.ctx.bool_type().const_zero();
Ok(LoweredValue::Packed(
OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.pack(z, z, 1, cx.repr, false),
))
}
pub(crate) fn array_result<'d>(sym: &sv_lang::Symbol<'d>) -> Option<crate::arrays::ArrayShape<'d>> {
sym.subroutine_return_type()
.as_ref()
.and_then(crate::arrays::array_shape)
}
pub(crate) fn lower_array_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
) -> Result<inkwell::values::PointerValue<'ctx>, LoweringError> {
if let Some(r) = crate::vif::call_arms(cx, call) {
let (arms, merge) = r?;
let callee = call.call_subroutine().ok_or_else(|| {
LoweringError::unsupported_at("an array value from a non-user call", &cx.fn_sym)
})?;
let shape = array_result(&callee).ok_or_else(|| {
LoweringError::unsupported_at(
"a call used as an array value does not return an unpacked array",
&cx.fn_sym,
)
})?;
let temp = crate::expr::entry_alloca(
cx,
crate::arrays::local_array_type(cx.ctx, &shape, cx.repr),
"vif_call_array",
);
for (callee, bb) in arms {
cx.b.position_at_end(bb);
let t = lower_array_call_of(cx, callee, call)?;
let ty = crate::arrays::local_array_type(cx.ctx, &shape, cx.repr);
let v = cx.b.build_load(ty, t, "vif_arr").unwrap();
cx.b.build_store(temp, v).unwrap();
cx.b.build_unconditional_branch(merge).unwrap();
}
cx.b.position_at_end(merge);
return Ok(temp);
}
let callee = user_callee(&call).ok_or_else(|| {
LoweringError::unsupported_at("an array value from a non-user call", &cx.fn_sym)
})?;
lower_array_call_of(cx, callee, call)
}
fn lower_array_call_of<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
callee: sv_lang::Symbol<'d>,
call: sv_lang::Expression<'d>,
) -> Result<inkwell::values::PointerValue<'ctx>, LoweringError> {
let shape = array_result(&callee).ok_or_else(|| {
LoweringError::unsupported_at(
"a call used as an array value does not return an unpacked array",
&cx.fn_sym,
)
})?;
let callee_fn = resolve_callee_fn(cx, callee)?;
let (mut llvm_args, copy_backs) = lower_call_args(cx, &callee, call)?;
let temp = crate::expr::entry_alloca(
cx,
crate::arrays::local_array_type(cx.ctx, &shape, cx.repr),
"call_array",
);
llvm_args.push(temp.into());
if llvm_args.len() != callee_fn.count_params() as usize {
return Err(LoweringError::unsupported_at(
"array-valued function call argument count does not match the callee's \
declared parameter count",
&cx.fn_sym,
));
}
cx.b.build_call(callee_fn, &llvm_args, "array_fn_call")
.unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
commit_callee_writes(cx, &callee);
if crate::dpi::reaches_context_import(&callee) {
crate::dpi::reload_shadows(cx);
}
copy_back_outputs(cx, copy_backs)?;
Ok(temp)
}
pub(crate) fn commit_callee_writes(cx: &LowerCtx<'_, '_, '_>, callee: &sv_lang::Symbol<'_>) {
if cx.write_through.is_none() {
return;
}
let abi = subroutine_abi(callee);
for sig in abi
.signals
.iter()
.filter(|s| abi.written.contains(&s.uid()))
{
if let Some(&(ptr, _, _)) = cx.locals.get(&sig.uid()) {
crate::expr::write_through_store(cx, ptr);
}
}
}
fn subroutine_write_through<'ctx, 'a>(
cx: &'a LowerCtx<'ctx, '_, '_>,
) -> Result<Option<crate::expr::WriteThrough<'ctx, 'a>>, LoweringError> {
if cx.write_through.is_some() || cx.fn_sym.kind() != sv_lang::kinds::SymbolKind::Subroutine {
return Ok(None);
}
let Some(mem) = &cx.mem_accessor else {
return Ok(None);
};
let abi = subroutine_abi(&cx.fn_sym);
if !abi.syncs_state {
return Ok(None);
}
let scope = sv_sim_sim::netlist::enclosing_instance_body(&cx.fn_sym)
.or_else(|| cx.fn_sym.parent())
.ok_or_else(|| {
LoweringError::unsupported_at("subroutine has no enclosing module", &cx.fn_sym)
})?;
let slots = crate::module::signal_slots_of(&scope)?;
let signals = abi
.signals
.iter()
.filter(|s| abi.written.contains(&s.uid()))
.filter_map(|s| {
let info = slots.get(&s.uid())?;
if matches!(
info.class,
crate::process_lower::SignalSlotClass::Memory { .. }
) {
return None;
}
let &(ptr, _, _) = cx.locals.get(&s.uid())?;
Some((
ptr,
(
info.slot,
info.width,
info.class == crate::process_lower::SignalSlotClass::Wide,
),
))
})
.collect::<HashMap<_, _>>();
Ok(Some(crate::expr::WriteThrough {
handle: mem.handle,
fns: mem.fns,
signals,
reload: Default::default(),
}))
}
pub(crate) fn sync_subroutine_writes(cx: &LowerCtx<'_, '_, '_>) -> Result<(), LoweringError> {
let Some(wt) = subroutine_write_through(cx)? else {
return Ok(());
};
let mut order: Vec<_> = wt
.signals
.iter()
.map(|(&p, &(s, w, _))| (s, w, p))
.collect();
order.sort_by_key(|&(slot, _, _)| slot);
for (slot, width, ptr) in order {
if crate::huge::is_huge_width(width) {
let i64t = cx.ctx.i64_type();
crate::huge::rt_mark(
cx,
wt.handle,
slot,
width,
i64t.const_zero(),
i64t.const_int(u64::from(width), false),
);
}
crate::expr::commit_shadow(cx, &wt, ptr);
}
Ok(())
}
pub(crate) fn is_const_ref(formal: &sv_lang::Symbol<'_>) -> bool {
formal.formal_argument_direction() == sv_lang::ArgumentDirection::Ref
&& formal
.variable_flags()
.contains(sv_lang::VariableFlags::CONST)
}
pub(crate) fn is_by_reference(formal: &sv_lang::Symbol<'_>) -> bool {
formal.formal_argument_direction() != sv_lang::ArgumentDirection::In
}
pub(crate) fn is_elem_ref_formal(formal: &sv_lang::Symbol<'_>) -> bool {
formal.formal_argument_direction() == sv_lang::ArgumentDirection::Ref
&& formal.value_type().is_some_and(|t| {
(t.is_integral() || t.is_floating())
&& crate::arrays::array_shape(&t).is_none()
&& !crate::huge::is_image_type(&t)
})
}
pub(crate) fn takes_elem_refs(sub: &sv_lang::Symbol<'_>) -> bool {
if !sub.subroutine_arguments().any(|a| is_elem_ref_formal(&a)) {
return false;
}
let Some(body) = sv_sim_sim::process::cfg::proc_body(sub) else {
return false;
};
let names_shared_storage = !sub
.subroutine_arguments()
.any(|a| sv_sim_sim::netlist::is_static_procedural_var(&a));
let mut stack = vec![body.as_sem_node()];
stack.extend(
subroutine_locals(sub)
.iter()
.filter_map(|l| l.initializer())
.map(|i| i.as_sem_node()),
);
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& ((e.kind() == ExpressionKind::Call && e.is_system_call() != Some(true))
|| e.kind() == ExpressionKind::NewClass
|| e.expr_type()
.is_some_and(|t| sv_sim_sim::state::storage::is_collection(&t)))
{
return true;
}
if names_shared_storage
&& n.as_expression()
.is_some_and(|e| names_shared_storage_node(&e))
{
return true;
}
stack.extend(sv_sim_sim::process::cfg::live_children(&n));
}
false
}
fn names_shared_storage_node(e: &sv_lang::Expression<'_>) -> bool {
matches!(
e.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) && e.referenced_symbol().is_some_and(|s| {
s.kind() == SymbolKind::ClassProperty
|| (s.kind() != SymbolKind::FormalArgument && is_module_level_signal(&s))
})
}
fn elem_ref_pair_ty(ctx: &Context, width: u32, repr: ValueRepr) -> inkwell::types::StructType<'_> {
ctx.struct_type(
&[
storage_type(ctx, width, repr).into(),
storage_type(ctx, 64, repr).into(),
storage_type(ctx, 64, repr).into(),
storage_type(ctx, 64, repr).into(),
],
false,
)
}
fn is_module_level_signal(sym: &sv_lang::Symbol<'_>) -> bool {
if sv_sim_sim::netlist::checker_output_of(sym).is_some() {
return true;
}
if sym.kind() == SymbolKind::ModportPort {
return sym
.modport_port_internal_symbol()
.is_some_and(|s| is_module_level_signal(&s));
}
if !matches!(sym.kind(), SymbolKind::Variable | SymbolKind::Net) {
return false;
}
if sv_sim_sim::netlist::is_static_procedural_var(sym) {
return true;
}
let mut cur = sym.parent();
while let Some(p) = cur {
let kind = p.kind();
if kind == SymbolKind::InstanceBody
|| kind == SymbolKind::CheckerInstanceBody
|| kind == SymbolKind::Package
|| kind == SymbolKind::CompilationUnit
{
return true;
}
if kind == SymbolKind::Subroutine || kind == SymbolKind::StatementBlock {
return false;
}
cur = p.parent();
}
false
}
pub(crate) fn user_callees<'d>(e: &sv_lang::Expression<'d>) -> Vec<sv_lang::Symbol<'d>> {
if e.kind() == ExpressionKind::NewClass && e.new_class_is_super_class() != Some(true) {
let mut out = Vec::new();
let mut level = e.expr_type().map(|t| t.canonical());
while let Some(t) = level {
if !t.is_class() {
break;
}
out.extend(t.class_constructor());
level = t.class_base();
}
return out;
}
match sv_sim_sim::netlist::vif::call_targets(e) {
Some((_, targets)) => targets.into_iter().flatten().collect(),
None => sv_sim_sim::netlist::itf::call_callees(e)
.into_iter()
.filter(|c| !c.subroutine_flags().contains(MethodFlags::DPI_IMPORT))
.collect(),
}
}
pub(crate) fn user_callee<'d>(e: &sv_lang::Expression<'d>) -> Option<sv_lang::Symbol<'d>> {
if e.kind() != ExpressionKind::Call || e.is_system_call() == Some(true) {
return None;
}
let callee = sv_sim_sim::netlist::itf::call_target(e)?;
if callee.subroutine_flags().contains(MethodFlags::DPI_IMPORT) {
return None;
}
Some(callee)
}
pub fn callee_reads(e: &sv_lang::Expression<'_>) -> Vec<sv_sim_sim::symid::SymbolId> {
let mut out = Vec::new();
for callee in user_callees(e) {
let abi = subroutine_abi(&callee);
for sig in abi.signals {
if !abi.written.contains(&sig.uid()) {
out.push(sig.uid());
}
}
for mem in abi.memories {
if !abi.memories_written.contains(&mem.uid()) {
out.push(mem.uid());
}
}
}
out
}
pub fn subroutine_forces(sym: &sv_lang::Symbol<'_>) -> bool {
subroutine_abi(sym).forces
}
pub fn subroutine_needs_handle(sym: &sv_lang::Symbol<'_>) -> bool {
subroutine_abi(sym).needs_handle
}
pub(crate) fn subroutine_abi<'d>(sym: &sv_lang::Symbol<'d>) -> SubroutineAbi<'d> {
let (mut abi, virt) = compute_abi_static(sym);
for name in &virt {
if let Some(g) = GROUP_ABI.with(|c| c.borrow().get(name).cloned()) {
let g = unsafe { std::mem::transmute::<SubroutineAbi<'static>, SubroutineAbi<'d>>(g) };
merge_abi(&mut abi, &g);
}
}
abi
}
pub(crate) fn dispatch_abi<'d>(callee: &sv_lang::Symbol<'d>) -> SubroutineAbi<'d> {
let mut abi = subroutine_abi(callee);
let dispatched = if callee.kind() == SymbolKind::MethodPrototype {
let f = callee.method_prototype_flags();
f.contains(MethodFlags::VIRTUAL) || f.contains(MethodFlags::PURE)
} else {
callee.subroutine_flags().contains(MethodFlags::VIRTUAL)
&& (callee.subroutine_this_var().is_some()
|| callee.subroutine_flags().contains(MethodFlags::PURE))
};
if dispatched
&& let Some(g) = GROUP_ABI.with(|c| c.borrow().get(&callee.name().to_string()).cloned())
{
let g = unsafe { std::mem::transmute::<SubroutineAbi<'static>, SubroutineAbi<'d>>(g) };
merge_abi(&mut abi, &g);
}
abi
}
thread_local! {
static GROUP_ABI: std::cell::RefCell<
std::collections::HashMap<String, SubroutineAbi<'static>>,
> = std::cell::RefCell::new(std::collections::HashMap::new());
}
pub(crate) fn clear_group_abis() {
GROUP_ABI.with(|c| c.borrow_mut().clear());
}
fn merge_abi<'d>(into: &mut SubroutineAbi<'d>, from: &SubroutineAbi<'d>) {
let merge = |into: &mut Vec<sv_lang::Symbol<'d>>, from: &[sv_lang::Symbol<'d>]| {
let mut have: std::collections::HashSet<_> = into.iter().map(symbol_identity).collect();
let before = into.len();
for s in from {
if have.insert(symbol_identity(s)) {
into.push(*s);
}
}
if into.len() != before {
into.sort_by_cached_key(|s| s.hierarchical_path());
}
};
merge(&mut into.signals, &from.signals);
merge(&mut into.memories, &from.memories);
into.written.extend(from.written.iter().cloned());
into.memories_written
.extend(from.memories_written.iter().cloned());
into.needs_handle |= from.needs_handle;
into.syncs_state |= from.syncs_state;
into.forces |= from.forces;
}
pub(crate) fn build_group_abis<'d>(methods: &[sv_lang::Symbol<'d>]) {
use std::collections::{BTreeSet, HashMap};
let mut group: HashMap<String, SubroutineAbi<'d>> = HashMap::new();
let mut deps: HashMap<String, BTreeSet<String>> = HashMap::new();
for m in methods {
let name = m.name().to_string();
let (abi, virt) = compute_abi_static(m);
match group.get_mut(&name) {
Some(g) => merge_abi(g, &abi),
None => {
group.insert(name.clone(), abi);
}
}
deps.entry(name).or_default().extend(virt);
}
loop {
let mut changed = false;
let names: Vec<String> = group.keys().cloned().collect();
for name in names {
let before = (
group[&name].signals.len(),
group[&name].memories.len(),
group[&name].written.len(),
group[&name].needs_handle,
);
for dep in deps.get(&name).cloned().unwrap_or_default() {
if dep == name {
continue;
}
if let Some(d) = group.get(&dep).cloned() {
merge_abi(group.get_mut(&name).unwrap(), &d);
}
}
let g = &group[&name];
if before
!= (
g.signals.len(),
g.memories.len(),
g.written.len(),
g.needs_handle,
)
{
changed = true;
}
}
if !changed {
break;
}
}
GROUP_ABI.with(|c| {
let mut c = c.borrow_mut();
c.clear();
for (k, v) in group {
c.insert(k, unsafe {
std::mem::transmute::<SubroutineAbi<'d>, SubroutineAbi<'static>>(v)
});
}
});
}
type StaticAbi<'d> = (SubroutineAbi<'d>, std::collections::BTreeSet<String>);
type AbiMemo = std::collections::HashMap<(usize, usize), StaticAbi<'static>>;
thread_local! {
static ABI_MEMO: std::cell::RefCell<Option<AbiMemo>> = const { std::cell::RefCell::new(None) };
}
pub(crate) struct AbiMemoGuard;
impl AbiMemoGuard {
pub(crate) fn enter() -> Self {
ABI_MEMO.with(|m| *m.borrow_mut() = Some(AbiMemo::new()));
AbiMemoGuard
}
}
impl Drop for AbiMemoGuard {
fn drop(&mut self) {
ABI_MEMO.with(|m| *m.borrow_mut() = None);
}
}
use sv_sim_sim::symid::symbol_identity;
fn compute_abi_static<'d>(sym: &sv_lang::Symbol<'d>) -> StaticAbi<'d> {
let key = symbol_identity(sym);
let hit = ABI_MEMO.with(|m| m.borrow().as_ref().and_then(|m| m.get(&key).cloned()));
if let Some(h) = hit {
return unsafe { std::mem::transmute::<StaticAbi<'static>, StaticAbi<'d>>(h) };
}
let r = compute_abi_static_uncached(sym);
ABI_MEMO.with(|m| {
if let Some(m) = m.borrow_mut().as_mut() {
m.insert(key, unsafe {
std::mem::transmute::<StaticAbi<'d>, StaticAbi<'static>>(r.clone())
});
}
});
r
}
fn compute_abi_static_uncached<'d>(sym: &sv_lang::Symbol<'d>) -> StaticAbi<'d> {
let mut virt_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
let mut signals: Vec<sv_lang::Symbol<'d>> = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut written = std::collections::HashSet::new();
let mut needs_handle = false;
let mut syncs_state = false;
let mut forces = false;
let mut memories: Vec<sv_lang::Symbol<'d>> = Vec::new();
let mut memory_seen = std::collections::HashSet::new();
let mut visited = std::collections::HashSet::new();
let mut work = vec![*sym];
while let Some(sub) = work.pop() {
if !visited.insert(sub.uid()) {
continue;
}
for formal in sub.subroutine_arguments() {
if sv_sim_sim::netlist::is_static_procedural_var(&formal) {
if seen.insert(formal.uid()) {
signals.push(formal);
}
written.insert(formal.uid());
}
}
if sub.subroutine_flags().contains(MethodFlags::CONSTRUCTOR)
&& let Some(base_ctor) = sub
.parent()
.and_then(|c| c.as_type())
.and_then(|t| t.class_base())
.and_then(|b| b.class_constructor())
{
work.push(base_ctor);
}
let Some(body) = sv_sim_sim::process::cfg::proc_body(&sub) else {
continue;
};
if crate::covergroup::is_block_target(&sub) {
syncs_state = true;
}
if subroutine_locals(&sub).iter().any(|l| {
l.value_type()
.as_ref()
.and_then(sv_sim_sim::state::event_pool::event_count)
.is_some()
}) {
needs_handle = true;
}
let mut stack = vec![body.as_sem_node()];
stack.extend(
subroutine_locals(&sub)
.iter()
.filter_map(|l| l.initializer())
.map(|i| i.as_sem_node()),
);
while let Some(n) = stack.pop() {
if n.as_statement().is_some_and(|st| {
st.kind() == StatementKind::Block
&& st
.block_symbol()
.is_some_and(|b| crate::covergroup::is_block_target(&b))
}) {
syncs_state = true;
}
if let Some(st) = n.as_statement()
&& st.kind() == StatementKind::ImmediateAssertion
&& st.is_deferred_assertion() == Some(true)
&& [
st.immediate_assertion_if_true(),
st.immediate_assertion_if_false(),
]
.into_iter()
.flatten()
.any(|a| crate::deferred_call::action_call(&a).is_some())
{
needs_handle = true;
}
if let Some(st) = n.as_statement()
&& matches!(
st.kind(),
StatementKind::ProceduralAssign | StatementKind::ProceduralDeassign
)
{
syncs_state = true;
forces = true;
if let Some(a) = st.expressions().into_iter().next()
&& a.kind() == ExpressionKind::Assignment
{
stack.extend(
a.left()
.into_iter()
.chain(a.right())
.map(|e| e.as_sem_node()),
);
continue;
}
}
if let Some(e) = n.as_expression() {
if sv_sim_sim::symid::is_value_name(e.kind())
&& let Some(r) = e.referenced_symbol()
&& is_module_level_signal(&r)
{
if r.value_type().is_some_and(|t| {
t.is_unpacked_array() && !sv_sim_sim::state::storage::is_collection(&t)
}) {
needs_handle = true;
if memory_seen.insert(r.uid()) {
memories.push(r);
}
} else if seen.insert(r.uid()) {
signals.push(r);
}
}
if sv_sim_sim::sva::endpoint::watched_call(&e).is_some() {
needs_handle = true;
}
if e.kind() == ExpressionKind::Call
&& matches!(sv_sim_sim::sva::sampled::decode(e), Ok(Some(_)))
{
needs_handle = true;
}
if crate::covergroup::is_sample_call(&e) {
syncs_state = true;
}
if e.kind() == ExpressionKind::Call
&& e.call_subroutine_name().as_deref() == Some("$inferred_disable")
&& let Ok(Some(Some((r, _)))) =
crate::sva::emit::resolve_checker_inferred_disable(&sub)
&& is_module_level_signal(&r)
&& seen.insert(r.uid())
{
signals.push(r);
}
for r in sv_sim_sim::netlist::vif::member_targets(&e)
.into_iter()
.flatten()
.flatten()
{
if seen.insert(r.uid()) {
signals.push(r);
}
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& e.system_subroutine().is_some_and(|s| {
matches!(
crate::systask::classify_display_family(s.known_name_id()),
Some(
crate::systask::DisplayFamily::Strobe
| crate::systask::DisplayFamily::Monitor
)
)
})
{
needs_handle = true;
}
let nonblocking =
e.kind() == ExpressionKind::Assignment && e.is_nonblocking() == Some(true);
if nonblocking {
needs_handle = true;
}
let target = if nonblocking {
None
} else if e.kind() == ExpressionKind::Assignment {
e.left()
} else if e.kind() == ExpressionKind::UnaryOp
&& matches!(
e.unary_op(),
Some(
sv_lang::UnaryOp::Preincrement
| sv_lang::UnaryOp::Predecrement
| sv_lang::UnaryOp::Postincrement
| sv_lang::UnaryOp::Postdecrement
)
)
{
e.operand()
} else {
None
};
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(this) = e.operands().first()
&& this
.expr_type()
.is_some_and(|t| sv_sim_sim::state::storage::is_collection(&t))
{
for r in crate::select::lvalue_root_symbols(*this) {
if is_module_level_signal(&r) {
written.insert(r.uid());
}
}
}
if let Some(t) = target {
for r in crate::select::lvalue_root_symbols(t) {
if is_module_level_signal(&r) {
written.insert(r.uid());
}
}
for r in sv_sim_sim::netlist::vif::member_targets(&t)
.into_iter()
.flatten()
.flatten()
{
written.insert(r.uid());
}
}
for callee in user_callees(&e) {
let proto = callee.kind() == SymbolKind::MethodPrototype;
if (proto && {
let f = callee.method_prototype_flags();
f.contains(MethodFlags::VIRTUAL) || f.contains(MethodFlags::PURE)
}) || (callee.subroutine_flags().contains(MethodFlags::VIRTUAL)
&& (callee.subroutine_this_var().is_some()
|| callee.subroutine_flags().contains(MethodFlags::PURE)))
{
virt_names.insert(callee.name().to_string());
}
work.push(callee);
}
}
stack.extend(sv_sim_sim::process::cfg::live_children(&n));
}
}
signals.sort_by_cached_key(|s| s.hierarchical_path());
memories.sort_by_cached_key(|s| s.hierarchical_path());
let memories_written = written
.iter()
.filter(|id| memory_seen.contains(*id))
.cloned()
.collect();
written.retain(|id| seen.contains(id));
(
SubroutineAbi {
signals,
written,
needs_handle: needs_handle || syncs_state,
syncs_state,
forces,
memories,
memories_written,
},
virt_names,
)
}
fn storage_type(ctx: &Context, width: u32, repr: ValueRepr) -> inkwell::types::IntType<'_> {
crate::types::int_type(
ctx,
if repr == ValueRepr::FourState {
width * 2
} else {
width
},
)
}
fn fit_float_return<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
f: inkwell::values::FloatValue<'ctx>,
) -> inkwell::values::FloatValue<'ctx> {
let ret =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.and_then(|func| func.get_type().get_return_type());
let f = crate::real::as_f64(cx, f);
match ret {
Some(inkwell::types::BasicTypeEnum::FloatType(t)) if t == cx.ctx.f32_type() => {
cx.b.build_float_trunc(f, cx.ctx.f32_type(), "ret_shortreal")
.unwrap()
}
_ => f,
}
}
pub(crate) struct CopyBack<'ctx, 'd> {
lvalue: sv_lang::Expression<'d>,
rhs: Option<sv_lang::Expression<'d>>,
formal_ty: Option<sv_lang::Type<'d>>,
temp: inkwell::values::PointerValue<'ctx>,
width: u32,
signed: bool,
array: Option<crate::arrays::ArrayShape<'d>>,
elem_ref: Option<ElemRefCall<'ctx>>,
}
#[derive(Clone, Copy)]
pub(crate) enum ElemRefCall<'ctx> {
Opened(PointerValue<'ctx>),
Passed(PointerValue<'ctx>),
Window {
id: PointerValue<'ctx>,
lo: PointerValue<'ctx>,
ew: PointerValue<'ctx>,
width: u32,
signed: bool,
},
}
pub(crate) fn lower_call_args<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
callee: &sv_lang::Symbol<'d>,
call: sv_lang::Expression<'d>,
) -> Result<
(
Vec<inkwell::values::BasicMetadataValueEnum<'ctx>>,
Vec<CopyBack<'ctx, 'd>>,
),
LoweringError,
> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let formals: Vec<sv_lang::Symbol<'d>> = callee.subroutine_arguments().collect();
let mut actuals = call.operands();
if sv_sim_sim::netlist::vif::call_targets(&call).is_some() || call.call_this_class().is_some() {
actuals.remove(0);
}
if formals.len() != actuals.len() {
return Err(LoweringError::unsupported_at(
"subroutine call argument count does not match the callee's formal count",
&cx.fn_sym,
));
}
let mut out = Vec::new();
let mut copy_backs = Vec::new();
let actuals_all = actuals.clone();
for (formal, actual) in formals.iter().zip(actuals) {
if let Some(shape) = formal
.value_type()
.as_ref()
.and_then(crate::arrays::array_shape)
{
let lvalue = if actual.kind() == ExpressionKind::Assignment {
actual.left().ok_or_else(|| {
LoweringError::unsupported_at("output argument with no target", &cx.fn_sym)
})?
} else {
actual
};
let ty = crate::arrays::local_array_type(cx.ctx, &shape, cx.repr);
let temp = crate::expr::entry_alloca(cx, ty, "arg_array");
if formal.formal_argument_direction() != sv_lang::ArgumentDirection::Out {
crate::arrays::assign_array_into_fresh(
cx,
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(temp)),
&shape,
lvalue,
)?;
}
out.push(temp.into());
if is_by_reference(formal) {
copy_backs.push(CopyBack {
lvalue,
rhs: None,
formal_ty: None,
temp,
width: shape.elem.value_width(),
signed: shape.elem.is_signed(),
array: Some(shape),
elem_ref: None,
});
}
continue;
}
if !is_by_reference(formal) && formal.value_type().is_some_and(|t| t.is_floating()) {
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);
let f = if formal.value_type().is_some_and(|t| t.bit_width() == 32) {
cx.b.build_float_trunc(f, cx.ctx.f32_type(), "shortreal_arg")
.unwrap()
} else {
f
};
out.push(f.into());
continue;
}
if !is_by_reference(formal)
&& formal
.value_type()
.is_some_and(|t| sv_sim_sim::state::storage::is_collection(&t))
{
let h = crate::collections::owned_arg(cx, actual)?;
out.push(inkwell::values::BasicMetadataValueEnum::from(h));
continue;
}
if let Some(t) = formal.value_type()
&& crate::huge::is_image_type(&t)
{
if is_by_reference(formal) {
let lvalue = if actual.kind() == ExpressionKind::Assignment {
actual.left().ok_or_else(|| {
LoweringError::unsupported_at("output argument with no target", &cx.fn_sym)
})?
} else {
actual
};
let width = t.value_width();
let temp = if formal.formal_argument_direction() == sv_lang::ArgumentDirection::Out
{
crate::huge::result_image(cx, width)?
} else {
crate::huge::pass_by_value(cx, lvalue, width)?
};
out.push(temp.into());
copy_backs.push(CopyBack {
lvalue,
rhs: None,
formal_ty: Some(t),
temp,
width,
signed: t.is_signed(),
array: None,
elem_ref: None,
});
continue;
}
out.push(crate::huge::pass_by_value(cx, actual, t.value_width())?.into());
continue;
}
if !is_by_reference(formal) {
let width = formal.value_type().map(|t| t.value_width()).unwrap_or(0);
let pv = crate::real::to_storage(
cx,
crate::collections::owned(cx, actual)?,
width,
"a >128-bit subroutine call argument",
)?;
out.push(inkwell::values::BasicMetadataValueEnum::from(pv.llvm));
continue;
}
let lvalue = if actual.kind() == ExpressionKind::Assignment {
actual.left().ok_or_else(|| {
LoweringError::unsupported_at("output argument with no target", &cx.fn_sym)
})?
} else {
actual
};
let t = formal
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("formal has no type", formal))?;
if t.is_event() && formal.formal_argument_direction() != sv_lang::ArgumentDirection::In {
return Err(LoweringError::unsupported_at(
"an output, inout or ref event argument of a value-returning function (events \
are passed by handle, LRM 1800-2023 15.5.5.1; supported for tasks and void \
functions)",
formal,
));
}
let width = t.value_width();
let slotted = takes_elem_refs(callee) && is_elem_ref_formal(formal);
let (temp, id_slot) = if slotted {
let pair = elem_ref_pair_ty(cx.ctx, width, cx.repr);
let base = crate::expr::entry_alloca(cx, pair, "arg_ref");
let id = cx.b.build_struct_gep(pair, base, 1, "arg_ref_id").unwrap();
let lo = cx.b.build_struct_gep(pair, base, 2, "arg_ref_lo").unwrap();
let ew = cx.b.build_struct_gep(pair, base, 3, "arg_ref_ew").unwrap();
(base, Some((id, lo, ew)))
} else {
(
crate::expr::entry_alloca(cx, op.packed_type(width, cx.repr), "arg_ref"),
None,
)
};
let mut elem_ref = None;
if formal.formal_argument_direction() != sv_lang::ArgumentDirection::Out {
let cur = crate::real::to_storage(
cx,
crate::expr::read_modify_target(cx, lvalue)?,
width,
"a >128-bit inout argument",
)?;
let cur = crate::select::fit_width(&op, cur, width);
cx.b.build_store(temp, cur.llvm).unwrap();
}
if let Some((id_slot, lo_slot, ew_slot)) = id_slot {
let i64t = cx.ctx.i64_type();
let (mut lov, mut ewv) = (i64t.const_zero(), i64t.const_zero());
let idv = if let Some(elem) = part_bound_element(lvalue) {
elem_ref = Some(ElemRefCall::Opened(id_slot));
let (id, lo, ew) = crate::collections::open_ref_part(cx, lvalue, elem)?;
(lov, ewv) = (lo, ew);
id
} else if lvalue.kind() == ExpressionKind::ElementSelect
&& lvalue
.select_value()
.and_then(|b| b.expr_type())
.is_some_and(|t| t.is_dynamically_sized_array() || t.is_associative_array())
{
elem_ref = Some(ElemRefCall::Opened(id_slot));
crate::collections::open_ref(cx, lvalue)?
} else if let Some(held) = crate::collections::held_ref_id(cx, lvalue) {
elem_ref = Some(ElemRefCall::Passed(id_slot));
(lov, ewv) = crate::collections::held_ref_part(cx, lvalue);
held
} else if !t.is_floating()
&& let Some((id, lo, ew)) = crate::collections::open_ref_window(cx, lvalue, width)
{
elem_ref = Some(ElemRefCall::Window {
id: id_slot,
lo: lo_slot,
ew: ew_slot,
width,
signed: t.is_signed(),
});
(lov, ewv) = (lo, ew);
id
} else {
i64t.const_zero()
};
let idv = op.pack(idv, op.i_n(64).const_zero(), 64, cx.repr, false);
cx.b.build_store(id_slot, idv.llvm).unwrap();
for (slot, v) in [(lo_slot, lov), (ew_slot, ewv)] {
let v = op.pack(v, op.i_n(64).const_zero(), 64, cx.repr, false);
cx.b.build_store(slot, v.llvm).unwrap();
}
}
out.push(temp.into());
if is_const_ref(formal) {
continue;
}
copy_backs.push(CopyBack {
lvalue,
rhs: if actual.kind() == ExpressionKind::Assignment {
actual.right()
} else {
None
},
formal_ty: Some(t),
temp,
width,
signed: t.is_signed(),
array: None,
elem_ref,
});
}
append_hidden_args(cx, callee, &mut out)?;
crate::vpi_dyn::emit_ref_bind(cx, callee, &actuals_all);
Ok((out, copy_backs))
}
pub(crate) fn append_hidden_args<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
callee: &sv_lang::Symbol<'d>,
out: &mut Vec<inkwell::values::BasicMetadataValueEnum<'ctx>>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let abi = subroutine_abi(callee);
for sig in &abi.signals {
if !cx.locals.contains_key(&sig.uid())
&& sig.kind() == SymbolKind::FormalArgument
&& (sig.formal_argument_direction() == sv_lang::ArgumentDirection::In
|| is_const_ref(sig))
&& let Some(t) = sig.value_type()
&& t.is_integral()
&& !crate::huge::is_huge_width(t.value_width())
{
let ty = op.packed_type(t.value_width(), cx.repr);
let scratch = crate::expr::entry_alloca(cx, ty, "static_formal_scratch");
cx.b.build_store(scratch, ty.const_zero()).unwrap();
out.push(scratch.into());
continue;
}
let (ptr, _, _) = *cx.locals.get(&sig.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"call to `{}` reaches module signal `{}`, which this caller cannot see \
(a call from another module?)",
callee.name(),
sig.name()
),
&cx.fn_sym,
)
})?;
if abi.written.contains(&sig.uid())
&& let Some(wt) = &cx.write_through
&& let Some(&(slot, width, _)) = wt.signals.get(&ptr)
&& crate::huge::is_huge_width(width)
{
let i64t = cx.ctx.i64_type();
crate::huge::rt_mark(
cx,
wt.handle,
slot,
width,
i64t.const_zero(),
i64t.const_int(u64::from(width), false),
);
}
out.push(ptr.into());
}
if abi.needs_handle {
let accessor = cx.mem_accessor.ok_or_else(|| {
LoweringError::unsupported_at(
"call to a subroutine that uses a module memory from a context without an \
instance handle",
&cx.fn_sym,
)
})?;
let other_instance = matches!((
sv_sim_sim::netlist::enclosing_instance_body(callee),
sv_sim_sim::netlist::enclosing_instance_body(&cx.fn_sym),
), (Some(theirs), Some(ours)) if theirs.uid() != ours.uid())
|| package_handle_callee(callee, &cx.fn_sym);
if other_instance {
let Some(info) = cx.mem_signals.get(&callee.uid()) else {
return Err(LoweringError::unsupported_at(
&format!(
"hierarchical call to `{}` in another instance, which uses a module \
memory or samples a covergroup there, from a context that cannot \
reach that instance",
callee.name()
),
&cx.fn_sym,
));
};
let ptr_ty = cx.ctx.ptr_type(inkwell::AddressSpace::default());
let i32t = cx.ctx.i32_type();
let f = cx
.module
.get_function(crate::process_lower::SV_RT_SLOT_OWNER_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
crate::process_lower::SV_RT_SLOT_OWNER_SYMBOL,
ptr_ty.fn_type(&[ptr_ty.into(), i32t.into()], false),
None,
)
});
let owner =
cx.b.build_call(
f,
&[
accessor.handle.into(),
i32t.const_int(u64::from(info.slot), false).into(),
],
"callee_instance",
)
.unwrap()
.try_as_basic_value();
out.push(owner.unwrap_basic().into());
} else {
out.push(accessor.handle.into());
}
}
Ok(())
}
pub(crate) fn copy_back_outputs<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
copy_backs: Vec<CopyBack<'ctx, 'd>>,
) -> Result<(), LoweringError> {
for cb in copy_backs {
match cb.elem_ref {
Some(ElemRefCall::Opened(slot)) => {
crate::collections::close_ref(cx, slot, 64);
continue;
}
Some(ElemRefCall::Window {
id,
lo,
ew,
width,
signed,
}) => {
crate::collections::refresh_slot(
cx,
&crate::collections::ElemRefSlot {
id,
id_width: 64,
width,
signed,
part: Some(crate::collections::PartSlots { lo, elem_width: ew }),
},
cb.temp,
);
crate::collections::close_ref(cx, id, 64);
}
Some(ElemRefCall::Passed(slot)) => {
let id = crate::collections::load_ref_slot(cx, slot);
let none =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
id,
cx.ctx.i64_type().const_zero(),
"ref_unbound",
)
.unwrap();
let f = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let run = cx.ctx.append_basic_block(f, "copy_back");
let done = cx.ctx.append_basic_block(f, "copy_back_done");
cx.b.build_conditional_branch(none, run, done).unwrap();
cx.b.position_at_end(run);
copy_back_one(cx, cb)?;
cx.b.build_unconditional_branch(done).unwrap();
cx.b.position_at_end(done);
continue;
}
None => {}
}
copy_back_one(cx, cb)?;
}
Ok(())
}
fn copy_back_one<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
cb: CopyBack<'ctx, 'd>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if let Some(shape) = &cb.array {
let Some((dst, dst_shape)) = crate::arrays::array_target(cx, cb.lvalue)? else {
if shape.rows() <= crate::arrays::MAX_UNROLLED_ROWS
&& crate::collections::handle_offsets(&shape.elem).is_empty()
&& matches!(
cb.lvalue.kind(),
ExpressionKind::MemberAccess | ExpressionKind::NamedValue
)
{
let src =
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(cb.temp));
let i64t = cx.ctx.i64_type();
let mut rows = (0..shape.rows())
.map(|r| {
crate::arrays::load_row(cx, &src, i64t.const_int(r, false), &shape.elem)
})
.collect::<Result<Vec<_>, _>>()?;
rows.reverse();
let packed = op.concat4(&rows);
crate::expr::store_to(cx, cb.lvalue, crate::expr::LoweredValue::Packed(packed))?;
return Ok(());
}
return Err(LoweringError::unsupported_at(
"an array output argument whose actual is neither an array variable nor a \
class property / struct field",
&cx.fn_sym,
));
};
crate::arrays::copy_array(
cx,
dst,
&dst_shape,
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(cb.temp)),
shape,
)?;
return Ok(());
}
if crate::huge::is_image_width(cb.width) {
crate::expr::store_to(
cx,
cb.lvalue,
crate::expr::LoweredValue::Huge(crate::huge::HugeValue {
ptr: cb.temp,
width: cb.width,
signed: cb.signed,
}),
)?;
crate::huge::release(cb.temp);
return Ok(());
}
let llvm =
cx.b.build_load(op.packed_type(cb.width, cx.repr), cb.temp, "arg_ref_out")
.unwrap()
.into_int_value();
let value = PackedValue {
llvm,
width: cb.width,
repr: cx.repr,
signed: cb.signed,
};
if let (Some(rhs), Some(fty), Some(lty)) = (cb.rhs, cb.formal_ty, cb.lvalue.expr_type())
&& rhs.kind() == ExpressionKind::Conversion
&& (fty.is_floating() != lty.is_floating()
|| (lty.is_integral()
&& (fty.value_width() != lty.value_width()
|| fty.is_signed() != lty.is_signed()
|| fty.is_four_state() != lty.is_four_state())))
{
let formal = if fty.is_floating() {
crate::expr::LoweredValue::Real(crate::real::bits_to_real(cx, value))
} else {
crate::expr::LoweredValue::Packed(value)
};
let saved = cx.lvalue_ref.replace(Some(formal));
let converted = crate::expr::lower_expr(cx, rhs);
cx.lvalue_ref.set(saved);
crate::expr::store_to(cx, cb.lvalue, converted?)?;
return Ok(());
}
let value = match cb.lvalue.expr_type() {
Some(t) if t.is_integral() => crate::select::fit_width(&op, value, t.value_width()),
_ => value,
};
if cb.lvalue.kind() == ExpressionKind::ElementSelect
&& cb
.lvalue
.select_value()
.and_then(|b| crate::collections::coll_of_expr(&b))
.is_some_and(|c| crate::collections::handle_offsets(&c.elem).is_empty())
&& let Some(done) = crate::collections::write_element(
cx,
cb.lvalue,
crate::expr::LoweredValue::Packed(value),
)
{
done?;
return Ok(());
}
if crate::collections::partial_element_target(cb.lvalue).is_some() {
crate::expr::store_to(cx, cb.lvalue, crate::expr::LoweredValue::Packed(value))?;
return Ok(());
}
crate::select::lower_assign_to(cx, cb.lvalue, value)?;
Ok(())
}
fn part_bound_element<'d>(e: sv_lang::Expression<'d>) -> Option<sv_lang::Expression<'d>> {
let elem = crate::collections::partial_element_target(e)?;
let c = crate::collections::coll_of_expr(&elem.select_value()?)?;
(!c.assoc && crate::collections::handle_offsets(&c.elem).is_empty()).then_some(elem)
}
fn subroutine_locals<'d>(sym: &sv_lang::Symbol<'d>) -> Vec<sv_lang::Symbol<'d>> {
let ret_id = sym.subroutine_return_val_var().map(|r| r.uid());
let mut out: Vec<sv_lang::Symbol<'d>> = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut take = |m: sv_lang::Symbol<'d>, out: &mut Vec<sv_lang::Symbol<'d>>| {
if m.kind() == SymbolKind::Variable && Some(m.uid()) != ret_id && seen.insert(m.uid()) {
out.push(m);
}
};
for m in sym.members() {
take(m, &mut out);
}
if let Some(body) = sv_sim_sim::process::cfg::proc_body(sym) {
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement() {
for block in sv_sim_sim::process::cfg::block_scopes(&s) {
for m in block.members() {
take(m, &mut out);
}
}
}
let mut kids = sv_sim_sim::process::cfg::live_children(&n);
kids.reverse();
stack.extend(kids);
}
for v in sv_sim_sim::patterns::pattern_vars(body.as_sem_node()) {
if seen.insert(v.uid()) {
out.push(v);
}
}
}
out
}
fn owned_handle_locals<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
decls: &[sv_lang::Symbol<'d>],
this_var: Option<sv_lang::Symbol<'d>>,
) -> Vec<sv_lang::Symbol<'d>> {
let pattern: std::collections::HashSet<_> = sv_sim_sim::process::cfg::proc_body(&cx.fn_sym)
.map(|b| {
sv_sim_sim::patterns::pattern_vars(b.as_sem_node())
.iter()
.map(|v| v.uid())
.collect()
})
.unwrap_or_default();
decls
.iter()
.copied()
.filter(|l| {
this_var.is_none_or(|tv| tv.uid() != l.uid())
&& !pattern.contains(&l.uid())
&& l.variable_lifetime() != sv_lang::VariableLifetime::Static
&& !sv_sim_sim::netlist::is_static_procedural_var(l)
&& cx.locals.contains_key(&l.uid())
&& l.value_type().is_some_and(|t| {
crate::arrays::array_shape(&t).is_none()
&& !crate::collections::handle_offsets(&t).is_empty()
})
})
.collect()
}
fn owned_handle_formals<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
args: &[sv_lang::Symbol<'d>],
) -> Vec<sv_lang::Symbol<'d>> {
args.iter()
.copied()
.filter(|a| {
!is_by_reference(a)
&& cx.locals.contains_key(&a.uid())
&& a.value_type().is_some_and(|t| {
crate::arrays::array_shape(&t).is_none()
&& !crate::collections::handle_offsets(&t).is_empty()
})
})
.collect()
}
fn release_locals_before_returns<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
f: FunctionValue<'ctx>,
locals: &[sv_lang::Symbol<'d>],
) {
if locals.is_empty() {
return;
}
let rets: Vec<_> = f
.get_basic_block_iter()
.filter_map(|bb| bb.get_terminator())
.filter(|t| t.get_opcode() == inkwell::values::InstructionOpcode::Return)
.collect();
for t in rets {
cx.b.position_before(&t);
for local in locals {
release_local(cx, local);
}
}
}
fn release_local<'ctx, 'd>(cx: &LowerCtx<'ctx, 'd, '_>, local: &sv_lang::Symbol<'d>) {
let (ptr, width, signed) = cx.locals[&local.uid()];
let Some(t) = local.value_type() else { return };
let old =
cx.b.build_load(storage_type(cx.ctx, width, cx.repr), ptr, "local_old")
.unwrap()
.into_int_value();
let pv = PackedValue {
llvm: old,
width,
repr: cx.repr,
signed,
};
crate::collections::release_handles(cx, &t, LoweredValue::Packed(pv));
}
fn move_out_local<'ctx, 'd>(cx: &LowerCtx<'ctx, 'd, '_>, e: sv_lang::Expression<'d>) {
let Some(v) = e.referenced_symbol() else {
return;
};
let is_arg = cx.fn_sym.subroutine_arguments().any(|a| a.uid() == v.uid());
let is_result = cx
.fn_sym
.subroutine_return_val_var()
.is_some_and(|r| r.uid() == v.uid());
let holds = v
.value_type()
.is_some_and(|t| !crate::collections::handle_offsets(&t).is_empty());
if holds
&& !is_arg
&& !is_result
&& let Some(&(ptr, width, _)) = cx.locals.get(&v.uid())
{
cx.b.build_store(ptr, storage_type(cx.ctx, width, cx.repr).const_zero())
.unwrap();
}
}
fn init_local<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
local: &sv_lang::Symbol<'d>,
) -> Result<(), LoweringError> {
if crate::class_events::init_local(cx, local)? {
return Ok(());
}
if crate::events::is_pool_event_var(cx, local) {
return crate::events::init_event_var(cx, local);
}
let (ptr, width, signed) = cx.locals[&local.uid()];
if let Some(shape) = local
.value_type()
.as_ref()
.and_then(crate::arrays::array_shape)
{
return crate::arrays::init_array_var(cx, ptr, &shape, local.initializer());
}
if let Some(t) = local.value_type()
&& crate::huge::is_image_type(&t)
{
return match local.initializer() {
Some(init) => crate::huge::store_expr_to_image(cx, local, init),
None => crate::huge::init_image(cx, ptr, width, t.is_four_state()),
};
}
let value = match local.initializer() {
Some(init)
if local
.value_type()
.is_some_and(|t| sv_sim_sim::state::storage::is_collection(&t)) =>
{
crate::collections::owned_arg(cx, init)?
}
Some(init) => {
let v = crate::real::to_storage(
cx,
crate::collections::owned(cx, init)?,
width,
"a >128-bit local initializer",
)?;
if v.width != width {
return Err(LoweringError::unsupported_at(
&format!(
"local initializer is {} bits for a {width}-bit variable",
v.width
),
local,
));
}
v.llvm
}
None => {
let ty = local
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("local has no type", local))?;
let _ = signed;
crate::arrays::default_value(cx, &ty)
}
};
cx.b.build_store(ptr, value).unwrap();
Ok(())
}
pub fn lower_function<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
_design: &sv_lang::Design,
sym: &sv_lang::Symbol<'d>,
repr: ValueRepr,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let (fn_ty, _is_void_return) = subroutine_fn_type(ctx, sym, repr, false)?;
let f = module.add_function(sym.name(), fn_ty, None);
lower_function_body(ctx, module, sym, repr, f, None)
}
fn contains_disable(s: &sv_lang::Statement<'_>) -> bool {
let mut stack = vec![s.as_sem_node()];
while let Some(n) = stack.pop() {
if n.as_statement()
.is_some_and(|c| c.kind() == StatementKind::Disable)
{
return true;
}
stack.extend(sv_sim_sim::process::cfg::live_children(&n));
}
false
}
pub fn lower_stmt<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
s: sv_lang::Statement<'d>,
) -> Result<(), LoweringError> {
let s = sv_sim_sim::process::cfg::unwrap_invalid(s);
crate::vpi_dyn::emit_entry_probe(cx, &s);
match s.kind() {
StatementKind::List | StatementKind::Block => {
for var in sv_sim_sim::process::cfg::block_automatic_vars(&s, false) {
if var.value_type().is_some_and(|t| {
crate::arrays::array_shape(&t).is_none()
&& !crate::collections::handle_offsets(&t).is_empty()
}) && cx.locals.contains_key(&var.uid())
{
release_local(cx, &var);
}
init_local(cx, &var)?;
}
let exit = s
.block_symbol()
.map(|b| b.name().to_string())
.filter(|n| !n.is_empty() && contains_disable(&s))
.map(|name| {
let (_, f) = current_block_and_fn(cx)?;
let bb = cx.ctx.append_basic_block(f, "disable_exit");
cx.block_exits.push((name, bb));
Ok::<_, LoweringError>(bb)
})
.transpose()?;
let blk_event = s
.block_symbol()
.filter(crate::covergroup::is_block_target)
.zip(cx.mem_accessor.as_ref().map(|m| m.handle));
if let Some((b, h)) = &blk_event {
sync_subroutine_writes(cx)?;
crate::covergroup::emit_block_event_of(cx, *h, b, false)?;
}
for child in sv_sim_sim::process::cfg::live_statements(&s) {
if current_block_terminated(cx) {
break;
}
lower_stmt(cx, child)?;
}
if let Some(bb) = exit {
cx.block_exits.pop();
if !current_block_terminated(cx) {
cx.b.build_unconditional_branch(bb).unwrap();
}
cx.b.position_at_end(bb);
}
if let Some((b, h)) = &blk_event
&& !current_block_terminated(cx)
{
sync_subroutine_writes(cx)?;
crate::covergroup::emit_block_event_of(cx, *h, b, true)?;
}
Ok(())
}
StatementKind::Disable => {
let label = sv_sim_sim::process::cfg::disable_label(&s)
.map_err(|e| LoweringError::unsupported_at(&e.to_string(), &cx.fn_sym))?;
if crate::process_lower::lower_deferred_disable(cx, &label)? {
return Ok(());
}
if !cx.block_exits.iter().any(|(n, _)| *n == label)
&& sv_sim_sim::process::cfg::is_function_disable_label(&label)
&& cx
.fn_sym
.subroutine_return_type()
.is_none_or(|t| t.is_void())
{
let rt = cx
.module
.get_function(crate::dpi::SV_RT_DPI_FN_DISABLE_SET_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
crate::dpi::SV_RT_DPI_FN_DISABLE_SET_SYMBOL,
cx.ctx.void_type().fn_type(
&[cx.ctx.ptr_type(inkwell::AddressSpace::default()).into()],
false,
),
None,
)
});
let name =
cx.b.build_global_string_ptr(&label, "fn_disable_label")
.unwrap()
.as_pointer_value();
cx.b.build_call(rt, &[name.into()], "").unwrap();
copy_out_formals(cx);
cx.b.build_return(None).unwrap();
return Ok(());
}
let &(_, bb) = cx
.block_exits
.iter()
.rev()
.find(|(n, _)| *n == label)
.ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"`disable {label};` in a function whose target is not an \
enclosing named block of the function"
),
&cx.fn_sym,
)
})?;
cx.b.build_unconditional_branch(bb).unwrap();
Ok(())
}
StatementKind::VariableDeclaration => Ok(()),
StatementKind::Empty => Ok(()),
StatementKind::Return => {
let e = match s.expr() {
Some(e) => e,
None => {
copy_out_formals(cx);
cx.b.build_return(None).unwrap();
return Ok(());
}
};
if cx
.fn_sym
.subroutine_return_type()
.is_some_and(|t| crate::huge::is_image_type(&t))
&& let Some(rv) = cx.fn_sym.subroutine_return_val_var()
{
crate::huge::store_expr_to_image(cx, &rv, e)?;
copy_out_formals(cx);
cx.b.build_return(None).unwrap();
return Ok(());
}
if let Some(shape) = array_result(&cx.fn_sym)
&& let Some(rv) = cx.fn_sym.subroutine_return_val_var()
{
let (ptr, _, _) = cx.locals[&rv.uid()];
crate::arrays::assign_array(
cx,
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(ptr)),
&shape,
e,
)?;
copy_out_formals(cx);
cx.b.build_return(None).unwrap();
return Ok(());
}
if cx
.fn_sym
.subroutine_return_type()
.is_some_and(|t| sv_sim_sim::state::storage::is_collection(&t))
{
if cx.fn_sym.subroutine_default_lifetime()
== Some(sv_lang::VariableLifetime::Static)
&& let Some(rv) = cx.fn_sym.subroutine_return_val_var()
&& let Some(t) = cx.fn_sym.subroutine_return_type()
{
let (slot, _, _) = cx.locals[&rv.uid()];
let v = crate::collections::store_static_result(cx, &t, slot, e)?;
copy_out_formals(cx);
cx.b.build_return(Some(&v)).unwrap();
return Ok(());
}
let local = sv_sim_sim::symid::is_value_name(e.kind())
&& e.referenced_symbol().is_some_and(|v| {
v.kind() == SymbolKind::Variable
&& v.variable_lifetime() == sv_lang::VariableLifetime::Automatic
});
let v = if local {
let v = lower_expr(cx, e)?
.into_packed("a collection result", &cx.fn_sym)?
.llvm;
move_out_local(cx, e);
v
} else if let Some(c) = cx
.fn_sym
.subroutine_return_type()
.and_then(|t| crate::collections::coll_of(&t))
{
let h = crate::collections::owned_value(cx, e)?;
let h = crate::collections::clamp_bound(cx, &c, h);
crate::collections::handle_value(cx, h).llvm
} else {
crate::collections::owned_arg(cx, e)?
};
copy_out_formals(cx);
cx.b.build_return(Some(&v)).unwrap();
return Ok(());
}
let this_uid = cx.fn_sym.subroutine_this_var().map(|t| t.uid());
let automatic_local = sv_sim_sim::symid::is_value_name(e.kind())
&& e.referenced_symbol().is_some_and(|v| {
v.kind() == SymbolKind::Variable
&& v.variable_lifetime() == sv_lang::VariableLifetime::Automatic
&& Some(v.uid()) != this_uid
});
let value = if automatic_local {
let v = lower_expr(cx, e)?;
move_out_local(cx, e);
v
} else {
crate::collections::owned(cx, e)?
};
let v = match value {
LoweredValue::Packed(p) => inkwell::values::BasicValueEnum::from(p.llvm),
LoweredValue::Str(s) => {
inkwell::values::BasicValueEnum::from(crate::strings::as_packed(cx, s).llvm)
}
LoweredValue::Real(r) => {
inkwell::values::BasicValueEnum::from(fit_float_return(cx, r.llvm))
}
LoweredValue::Wide(_) | LoweredValue::Huge(_) => {
return Err(LoweringError::unsupported_at(
"return of a >WIDE_THRESHOLD-bit wide value (no wide \
function-return ABI exists yet)",
&cx.fn_sym,
));
}
};
copy_out_formals(cx);
cx.b.build_return(Some(&v)).unwrap();
Ok(())
}
StatementKind::ExpressionStatement => {
let e = s.expr().ok_or_else(|| {
LoweringError::unsupported_at("expression statement missing expr", &cx.fn_sym)
})?;
if e.kind() == ExpressionKind::Call && e.is_system_call() == Some(false) {
return lower_task_call(cx, e);
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
{
let which = sub.known_name_id();
use sv_lang::KnownSystemName as K;
if matches!(
which,
K::Info | K::Warning | K::Error | K::Fatal | K::Finish | K::Stop
) {
let sys_fns =
crate::process_lower::fetch_or_declare_sys_call_fns(cx.ctx, cx.module);
let handle = cx
.ctx
.ptr_type(inkwell::AddressSpace::default())
.const_null();
if matches!(which, K::Finish | K::Stop) {
crate::process_lower::lower_finish_call(cx, handle, &sys_fns, e, which)?;
} else {
crate::process_lower::lower_severity_call(cx, handle, &sys_fns, e, which)?;
}
if matches!(which, K::Finish | K::Stop | K::Fatal) {
let (_, f) = current_block_and_fn(cx)?;
crate::stmt_shared::leave_now(cx, f);
let rest = cx.ctx.append_basic_block(f, "after_finish");
cx.b.position_at_end(rest);
}
return Ok(());
}
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
&& sub.known_name_id() == sv_lang::KnownSystemName::PrintTimeScale
{
let display = crate::systask::fetch_or_declare_display_family_fn(
cx.ctx,
cx.module,
crate::systask::DisplayFamily::Display,
);
crate::process_lower::lower_printtimescale_call(cx, display, e)?;
return Ok(());
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
&& let Some(family) = crate::systask::classify_display_family(sub.known_name_id())
{
use crate::systask::DisplayFamily as F;
if matches!(family, F::Strobe | F::Monitor) {
let handle = cx.mem_accessor.as_ref().map(|m| m.handle).ok_or_else(|| {
LoweringError::unsupported_at(
"$strobe/$monitor in a subroutine without an instance handle",
&cx.fn_sym,
)
})?;
let scope = sv_sim_sim::netlist::enclosing_instance_body(&cx.fn_sym)
.or_else(|| cx.fn_sym.parent())
.ok_or_else(|| {
LoweringError::unsupported_at(
"subroutine has no enclosing module",
&cx.fn_sym,
)
})?;
let slots = crate::module::signal_slots_of(&scope)?;
let which = if family == F::Strobe {
crate::process_lower::PostponedDisplay::Strobe
} else {
crate::process_lower::PostponedDisplay::Monitor
};
return crate::process_lower::lower_postponed_display_call(
cx, handle, &slots, e, which,
);
}
crate::systask::lower_display_family_call(cx, e, family)?;
return Ok(());
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
&& let Some(which) = crate::systask::classify_dump_control_call(sub.known_name_id())
{
crate::systask::lower_dump_control_call(cx, e, which)?;
return Ok(());
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
&& let Some(which) = crate::systask::classify_file_io_void_call(sub.known_name_id())
{
crate::systask::lower_file_io_void_call(cx, e, which)?;
return Ok(());
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
&& let Some(family) = crate::systask::classify_fdisplay_family(sub.known_name_id())
{
crate::systask::lower_fdisplay_family_call(cx, e, family)?;
return Ok(());
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
&& sub.known_name_id() == sv_lang::KnownSystemName::SdfAnnotate
{
crate::systask::lower_sdf_annotate_call(cx, e)?;
return Ok(());
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
&& sub.known_name_id() == sv_lang::KnownSystemName::Cast
{
return crate::sysfunc::lower_cast_stmt(cx, e);
}
crate::huge::discard(lower_expr(cx, e)?);
Ok(())
}
StatementKind::Conditional => {
crate::stmt_shared::lower_if(cx, s, &mut |cx, s| lower_stmt(cx, s))
}
StatementKind::Case => match sv_sim_sim::process::cfg::constant_case_live_arm(&s) {
Some(Some(arm)) => lower_stmt(cx, arm),
Some(None) => Ok(()),
None => lower_case(cx, s, &mut |cx, s| lower_stmt(cx, s)),
},
StatementKind::PatternCase => {
crate::stmt_shared::lower_pattern_case(cx, s, &mut |cx, s| lower_stmt(cx, s))
}
StatementKind::RandCase => {
crate::stmt_shared::lower_randcase(cx, s, &mut |cx, s| lower_stmt(cx, s))
}
StatementKind::WhileLoop => lower_while_loop(cx, s, &mut |cx, s| lower_stmt(cx, s)),
StatementKind::DoWhileLoop => lower_do_while_loop(cx, s, &mut |cx, s| lower_stmt(cx, s)),
StatementKind::ForLoop => lower_for_loop(cx, s, &mut |cx, s| lower_stmt(cx, s)),
StatementKind::RepeatLoop => lower_repeat_loop(cx, s, &mut |cx, s| lower_stmt(cx, s)),
StatementKind::ForeachLoop => lower_foreach_loop(cx, s, &mut |cx, s| lower_stmt(cx, s)),
StatementKind::ForeverLoop => lower_forever_loop(cx, s, &mut |cx, s| lower_stmt(cx, s)),
StatementKind::Break => lower_loop_jump(cx, true),
StatementKind::Continue => lower_loop_jump(cx, false),
StatementKind::ImmediateAssertion => {
let sys_fns = crate::process_lower::fetch_or_declare_sys_call_fns(cx.ctx, cx.module);
let slots = match (&cx.mem_accessor, s.is_deferred_assertion() == Some(true)) {
(Some(_), true) => sv_sim_sim::netlist::enclosing_instance_body(&cx.fn_sym)
.or_else(|| cx.fn_sym.parent())
.map(|scope| crate::module::signal_slots_of(&scope))
.transpose()?,
_ => None,
};
let handle = cx.mem_accessor.as_ref().map(|m| m.handle);
let env = match (handle, slots.as_ref()) {
(Some(handle), Some(signals)) => {
Some(crate::deferred_call::DeferredEnv { handle, signals })
}
_ => None,
};
crate::process_lower::lower_immediate_assertion(
cx,
&sys_fns,
&s,
env.as_ref(),
&mut |cx, a| lower_stmt(cx, *a),
)
}
StatementKind::WaitFork => Err(LoweringError::unsupported_at(
"wait fork (fork/join unresolved, see ARCHITECTURE.md §6.4 open \
sub-problems)",
&cx.fn_sym,
)),
StatementKind::DisableFork => Err(LoweringError::unsupported_at(
"disable fork (fork/join unresolved, see ARCHITECTURE.md §6.4 \
open sub-problems)",
&cx.fn_sym,
)),
StatementKind::WaitOrder => Err(LoweringError::unsupported_at(
"wait_order (fork/join unresolved, see ARCHITECTURE.md §6.4 open \
sub-problems)",
&cx.fn_sym,
)),
StatementKind::EventTrigger => {
let target = s.expressions().into_iter().next().ok_or_else(|| {
LoweringError::unsupported_at("an event trigger with no target", &cx.fn_sym)
})?;
if s.is_nonblocking_trigger() != Some(true)
&& crate::class_events::is_class_event(target)
{
return crate::expr::lower_event_trigger(cx, target);
}
Err(LoweringError::unsupported_at(
"statement kind EventTrigger in a subroutine body",
&cx.fn_sym,
))
}
StatementKind::ProceduralAssign | StatementKind::ProceduralDeassign => {
lower_subroutine_force(cx, &s)
}
StatementKind::Timed => Err(LoweringError::unsupported_at(
"a task body containing a timing-control statement (delay/event \
control) is a suspend point this crate's automatic, \
non-suspending task/function-call ABI does not support -- \
CG-154 (open/unsettled)",
&cx.fn_sym,
)),
StatementKind::Wait => Err(LoweringError::unsupported_at(
"a task body containing a wait(expr) statement is a suspend \
point this crate's automatic, non-suspending task/function-call \
ABI does not support -- CG-154 (open/unsettled)",
&cx.fn_sym,
)),
other => Err(LoweringError::unsupported_at(
&format!("statement kind {other:?}"),
&cx.fn_sym,
)),
}
}
fn body_param_hash(scope: &sv_lang::Symbol) -> u64 {
match scope.instance_body_parent_instance() {
Some(inst) => mangle::param_binding_hash(&inst),
None => mangle::param_binding_hash(scope),
}
}
pub(crate) fn mangled_subroutine_name(sym: &sv_lang::Symbol) -> Result<String, LoweringError> {
let parent = sym.parent().ok_or_else(|| {
LoweringError::unsupported_at(
"subroutine has no resolvable enclosing scope to mangle a name against",
sym,
)
})?;
let param_hash = if parent.kind() == sv_lang::kinds::SymbolKind::ClassType {
use std::hash::{Hash, Hasher};
let key = parent
.as_type()
.map(sv_sim_sim::netlist::class_specialization_key)
.ok_or_else(|| {
LoweringError::unsupported_at(
"class scope has no type to key a mangled name on",
sym,
)
})?;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
key.hash(&mut hasher);
hasher.finish()
} else if sv_sim_sim::netlist::itf::is_extern_impl(sym) {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
sym.hierarchical_path().hash(&mut hasher);
hasher.finish()
} else if parent.kind() == sv_lang::kinds::SymbolKind::GenerateBlock {
use std::hash::{Hash, Hasher};
let mut scope = parent;
while let Some(up) = scope.parent() {
if !matches!(
up.kind(),
sv_lang::kinds::SymbolKind::GenerateBlock
| sv_lang::kinds::SymbolKind::GenerateBlockArray
) {
break;
}
scope = up;
}
let full = parent.hierarchical_path();
let outer = scope
.parent()
.map(|p| p.hierarchical_path())
.unwrap_or_default();
let rel = full.strip_prefix(outer.as_str()).unwrap_or(full.as_str());
let mut hasher = std::collections::hash_map::DefaultHasher::new();
rel.hash(&mut hasher);
scope
.parent()
.map(|p| body_param_hash(&p))
.hash(&mut hasher);
hasher.finish()
} else {
body_param_hash(&parent)
};
Ok(mangle::mangle_kernel(
parent.name(),
param_hash,
EntityTag::Kernel,
sym.name(),
))
}
pub(crate) fn resolve_callee_fn<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
callee: sv_lang::Symbol<'d>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
if let Some(f) = crate::classes::resolve_or_lower_static_method(cx, callee)? {
return Ok(f);
}
if let Some(f) = cx.module.get_function(callee.name()) {
return Ok(f);
}
let mangled = mangled_subroutine_name(&callee)?;
if let Some(f) = cx.module.get_function(&mangled) {
return Ok(f);
}
let (fn_ty, _) = subroutine_fn_type(cx.ctx, &callee, cx.repr, false)?;
Ok(cx
.module
.add_function(&mangled, fn_ty, Some(Linkage::External)))
}
pub(crate) fn lower_task_call<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
call: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
if crate::covergroup::lower_call(cx, call)?.is_some() {
return Ok(());
}
if let crate::classes::ProcessCall::Lowered(_) =
crate::classes::lower_std_process_call(cx, call)?
{
return Ok(());
}
let not_vif_call = sv_sim_sim::netlist::vif::call_targets(&call).is_none();
if not_vif_call && let Some(recv) = call.call_this_class() {
return crate::classes::lower_method_task_call(cx, Some(recv), call);
}
if not_vif_call
&& let Some(callee) = call.call_subroutine()
&& crate::classes::resolve_implicit_this_instance_method(callee)?.is_some()
{
return crate::classes::lower_method_task_call(cx, None, call);
}
if sv_sim_sim::netlist::itf::is_vif_extern_call(&call) {
return Err(LoweringError::unsupported_at(
&sv_sim_sim::netlist::itf::vif_extern_message(&call),
&cx.fn_sym,
));
}
if let Some(r) = crate::vif::call_arms(cx, call) {
let (arms, merge) = r?;
for (callee, bb) in arms {
cx.b.position_at_end(bb);
lower_task_call_of(cx, callee, call)?;
cx.b.build_unconditional_branch(merge).unwrap();
}
cx.b.position_at_end(merge);
return Ok(());
}
if let Some(targets) = sv_sim_sim::netlist::itf::call_extern_targets(&call) {
if targets.impls.is_empty() {
return Err(LoweringError::unsupported_at(
"a call to an extern forkjoin task no module implements, outside a process body",
&cx.fn_sym,
));
}
for imp in targets.impls {
lower_task_call_of(cx, imp, call)?;
}
return Ok(());
}
let callee = call.call_subroutine().ok_or_else(|| {
LoweringError::unsupported_at("task/function call missing its callee symbol", &cx.fn_sym)
})?;
lower_task_call_of(cx, callee, call)
}
fn lower_task_call_of<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
callee: sv_lang::Symbol<'d>,
call: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
if callee.subroutine_flags().contains(MethodFlags::DPI_IMPORT) {
crate::dpi::lower_dpi_import_call(cx, &callee, call)?;
crate::stmt_shared::leave_if_aborted(cx)?;
return Ok(());
}
if array_result(&callee).is_some() {
lower_array_call_of(cx, callee, call)?;
return Ok(());
}
let callee_fn = resolve_callee_fn(cx, callee)?;
let (mut llvm_args, copy_backs) = lower_call_args(cx, &callee, call)?;
let discarded = match callee.subroutine_return_type() {
Some(t) if crate::huge::is_image_type(&t) => {
let img = crate::huge::result_image(cx, t.value_width())?;
llvm_args.push(img.into());
Some(img)
}
_ => None,
};
if llvm_args.len() != callee_fn.count_params() as usize {
return Err(LoweringError::unsupported_at(
"task/function call argument count does not match the callee's \
declared parameter count",
&cx.fn_sym,
));
}
cx.b.build_call(callee_fn, &llvm_args, "task_call").unwrap();
if let Some(img) = discarded {
crate::huge::release(img);
}
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
commit_callee_writes(cx, &callee);
if crate::dpi::reaches_context_import(&callee) {
crate::dpi::reload_shadows(cx);
}
copy_back_outputs(cx, copy_backs)
}
fn copy_out_formals(cx: &LowerCtx<'_, '_, '_>) {
for &(signal, temp, size) in &cx.copy_out {
cx.b.build_memcpy(temp, 8, signal, 8, size).unwrap();
}
}