use sv_lang::Symbol;
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use std::cell::RefCell;
use std::collections::HashMap;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::types::FunctionType;
use inkwell::values::{BasicMetadataValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::{AddressSpace, IntPredicate};
use sv_sim_runtime::objects_rt::{ObjOp, SV_RT_OBJ_SYMBOL};
use sv_sim_sim::netlist::ClassTemplateId;
use sv_sim_sim::objects::FieldSlot;
use sv_sim_sim::symid::{SymbolId, SymbolUid};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, entry_alloca, lower_expr};
use crate::func::CopyBack;
use crate::ops::OpBuilder;
use crate::types::TypeWidth;
use crate::value::{PackedValue, RealValue, ValueRepr};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StdIntrinsic {
Process,
Semaphore,
Mailbox,
WeakReference,
}
pub fn is_builtin_std_class(sym: &Symbol) -> Option<StdIntrinsic> {
if !matches!(
sym.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
) {
return None;
}
let intrinsic = match sym.name() {
"process" => StdIntrinsic::Process,
"semaphore" => StdIntrinsic::Semaphore,
"mailbox" => StdIntrinsic::Mailbox,
"weak_reference" => StdIntrinsic::WeakReference,
_ => return None,
};
let mut scope = sym.parent();
while let Some(s) = scope {
if s.kind() == SymbolKind::Package && s.name() == "std" {
return Some(intrinsic);
}
scope = s.parent();
}
None
}
fn elab_to_lowering(e: sv_sim_sim::netlist::ElabError, at: &sv_lang::Symbol) -> LoweringError {
LoweringError::unsupported_at(&format!("{e}"), at)
}
#[derive(Clone, Copy, Debug)]
pub struct ObjectField<'d> {
pub sym: sv_lang::Symbol<'d>,
pub slot: FieldSlot,
}
#[derive(Debug)]
pub struct ObjectLayout<'d> {
fields: Vec<ObjectField<'d>>,
aliases: Vec<ObjectField<'d>>,
total_words: usize,
}
fn field_words(width: u32, repr: ValueRepr) -> usize {
let bits = if repr == ValueRepr::FourState {
width * 2
} else {
width
};
(bits as usize).div_ceil(64).max(1)
}
fn property_words(f: &sv_lang::Symbol<'_>) -> Result<usize, LoweringError> {
let ty = f
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", f))?;
if sv_sim_sim::state::storage::is_collection(&ty) {
return Ok(field_words(64, ValueRepr::FourState));
}
if sv_sim_sim::state::storage::is_unpacked_aggregate(&ty) || ty.is_unpacked_array() {
let Some(width) = sv_sim_sim::state::storage::storage_width(&ty) else {
return Err(LoweringError::unsupported_at(
"a class property of an unpacked array, struct or union type with no \
fixed-width stored form is not supported (an unpacked union holding a \
class handle or collection, or an element of an unsupported type)",
f,
));
};
let width = u32::try_from(width).map_err(|_| {
LoweringError::unsupported_at("a class property wider than 2^32 bits", f)
})?;
return Ok(field_words(width, ValueRepr::FourState));
}
if !(ty.is_integral()
|| ty.is_floating()
|| ty.is_chandle()
|| ty.is_covergroup()
|| ty.is_virtual_interface()
|| ty.is_string()
|| ty.is_event()
|| ty.is_class())
{
return Err(LoweringError::unsupported_at(
"class property of an unsupported type",
f,
));
}
if ty.is_event() {
return Ok(field_words(64, ValueRepr::FourState));
}
Ok(field_words(ty.value_width(), ValueRepr::FourState))
}
fn extended_covergroup_slot<'d>(
f: &sv_lang::Symbol<'d>,
earlier: &[ObjectField<'d>],
) -> Option<FieldSlot> {
let ty = f.value_type()?.canonical();
if !ty.is_covergroup() || ty.covergroup_base_group().is_none() {
return None;
}
earlier
.iter()
.rev()
.find(|o| o.sym.name() == f.name() && o.sym.value_type().is_some_and(|t| t.is_covergroup()))
.map(|o| o.slot)
}
impl<'d> ObjectLayout<'d> {
pub fn for_template(class_sym: sv_lang::Symbol<'d>) -> Result<Self, LoweringError> {
let base_ty = class_sym
.as_type()
.ok_or_else(|| LoweringError::unsupported_at("class symbol has no type", &class_sym))?;
let mut levels: Vec<sv_lang::Symbol<'d>> = sv_sim_sim::netlist::base_chain(base_ty)
.into_iter()
.map(|t| t.as_symbol())
.collect();
levels.push(class_sym);
let mut fields = Vec::new();
let mut aliases = Vec::new();
let mut offset = 0usize;
for level_sym in levels {
let members = sv_sim_sim::netlist::classify_members(level_sym)
.map_err(|e| elab_to_lowering(e, &level_sym))?;
for f in members.fields {
let words = property_words(&f)?;
if let Some(base_slot) = extended_covergroup_slot(&f, &fields) {
aliases.push(ObjectField {
sym: f,
slot: base_slot,
});
continue;
}
fields.push(ObjectField {
sym: f,
slot: FieldSlot { offset, words },
});
offset += words;
}
}
Ok(Self {
fields,
aliases,
total_words: offset,
})
}
pub fn for_statics(class_sym: sv_lang::Symbol<'d>) -> Result<Self, LoweringError> {
let members = sv_sim_sim::netlist::classify_members(class_sym)
.map_err(|e| elab_to_lowering(e, &class_sym))?;
let mut fields = Vec::new();
let mut offset = 0usize;
for f in members.statics {
let words = property_words(&f)?;
fields.push(ObjectField {
sym: f,
slot: FieldSlot { offset, words },
});
offset += words;
}
Ok(Self {
fields,
aliases: Vec::new(),
total_words: offset,
})
}
pub fn total_words(&self) -> usize {
self.total_words
}
pub fn fields(&self) -> &[ObjectField<'d>] {
&self.fields
}
pub fn is_alias(&self, member: &sv_lang::Symbol<'_>) -> bool {
let uid = member.uid();
self.aliases.iter().any(|f| f.sym.uid() == uid)
}
pub fn slot_of(&self, member: &sv_lang::Symbol<'_>) -> Option<FieldSlot> {
let uid = member.uid();
self.fields
.iter()
.chain(&self.aliases)
.find(|f| f.sym.uid() == uid)
.map(|f| f.slot)
}
pub fn handle_field_slots(&self) -> Vec<FieldSlot> {
self.fields
.iter()
.filter(|f| f.sym.value_type().is_some_and(|t| t.is_class()))
.map(|f| f.slot)
.collect()
}
pub fn collection_field_slots(&self) -> Vec<FieldSlot> {
self.fields
.iter()
.filter(|f| {
f.sym
.value_type()
.is_some_and(|t| sv_sim_sim::state::storage::is_collection(&t))
})
.map(|f| f.slot)
.collect()
}
pub fn nested_handle_slots(&self) -> Vec<sv_sim_sim::objects::NestedHandle> {
use crate::collections::{HandleKind, handle_offsets};
let mut out = Vec::new();
for f in &self.fields {
let Some(ty) = f.sym.value_type() else {
continue;
};
if ty.is_class() || sv_sim_sim::state::storage::is_collection(&ty) {
continue;
}
let slots = handle_offsets(&ty);
assert!(
slots.iter().all(|s| s.guards.is_empty()),
"a class property holding a tagged union with a class or collection member"
);
out.extend(
slots
.into_iter()
.map(|s| sv_sim_sim::objects::NestedHandle {
slot: f.slot,
bit: s.off,
coll: s.kind == HandleKind::Coll,
}),
);
}
out
}
pub fn rand_props(&self) -> Vec<sv_sim_sim::objects::RandProp> {
use sv_sim_sim::objects::{RandKind, RandProp};
self.fields
.iter()
.filter_map(|f| {
let kind = match f.sym.class_property_rand_mode() {
sv_lang::RandMode::None => {
if !sv_sim_sim::constraints::build::property_is_random(f.sym) {
return None;
}
RandKind::Rand
}
sv_lang::RandMode::Rand => RandKind::Rand,
sv_lang::RandMode::RandC => RandKind::RandC,
};
Some(RandProp { slot: f.slot, kind })
})
.collect()
}
}
struct VirtualMember<'d> {
overrides: Option<sv_lang::Symbol<'d>>,
implementation: Option<sv_lang::Symbol<'d>>,
}
fn classify_virtual_member<'d>(
m: sv_lang::Symbol<'d>,
) -> Result<Option<VirtualMember<'d>>, LoweringError> {
use sv_lang::MethodFlags;
let proto = match m.kind() {
SymbolKind::MethodPrototype => Some(m),
SymbolKind::Subroutine => m.subroutine_prototype(),
other => {
return Err(LoweringError::unsupported_at(
&format!(
"internal: {other:?} is not one of the two SymbolKinds \
(Subroutine/MethodPrototype) classify_members's own contract guarantees \
for a class's `methods` list"
),
&m,
));
}
};
if let Some(proto) = proto {
if !proto.method_prototype_is_virtual() {
return Ok(None);
}
let overrides = proto.method_prototype_override();
let implementation = if proto.method_prototype_flags().contains(MethodFlags::PURE) {
None
} else if m.kind() == SymbolKind::Subroutine && m.subroutine_this_var().is_some() {
Some(m)
} else {
Some(proto.method_prototype_subroutine().ok_or_else(|| {
LoweringError::unsupported_at(
"internal: a non-pure virtual method prototype has no resolved \
out-of-block implementation -- slang's own frontend should never leave \
this unresolved once the design compiles at all",
&m,
)
})?)
};
return Ok(Some(VirtualMember {
overrides,
implementation,
}));
}
if m.subroutine_this_var().is_none() {
return Ok(None);
}
let overrides = m.subroutine_override();
let is_virtual = m.subroutine_flags().contains(MethodFlags::VIRTUAL) || overrides.is_some();
if !is_virtual {
return Ok(None);
}
Ok(Some(VirtualMember {
overrides,
implementation: Some(m),
}))
}
pub struct VtableLayout<'d> {
slots: Vec<Option<sv_lang::Symbol<'d>>>,
by_uid: HashMap<SymbolId, usize>,
declared_in: HashMap<SymbolId, String>,
}
impl<'d> VtableLayout<'d> {
pub fn for_template(class_sym: sv_lang::Symbol<'d>) -> Result<Self, LoweringError> {
let base_ty = class_sym
.as_type()
.ok_or_else(|| LoweringError::unsupported_at("class symbol has no type", &class_sym))?;
let mut levels: Vec<sv_lang::Symbol<'d>> = sv_sim_sim::netlist::base_chain(base_ty)
.into_iter()
.map(|t| t.as_symbol())
.collect();
levels.push(class_sym);
let mut slots: Vec<Option<sv_lang::Symbol<'d>>> = Vec::new();
let mut by_uid: HashMap<SymbolId, usize> = HashMap::new();
let mut declared_in: HashMap<SymbolId, String> = HashMap::new();
let mut name_to_slot: HashMap<&'d str, usize> = HashMap::new();
for &level_sym in &levels {
let members = sv_sim_sim::netlist::classify_members(level_sym)
.map_err(|e| elab_to_lowering(e, &level_sym))?;
let level_key = specialization_key_of_symbol(&level_sym)?;
for m in members.methods {
let Some(vm) = classify_virtual_member(m)? else {
continue;
};
let slot = match vm.overrides {
Some(base_m) => *by_uid.get(&base_m.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"internal: an overriding virtual method's own base method has no \
vtable slot yet -- the base class's own level must be walked \
before this one, and VtableLayout::for_template's own base-chain \
walk guarantees that ordering",
&m,
)
})?,
None => {
let s = slots.len();
slots.push(None);
s
}
};
by_uid.insert(m.uid(), slot);
declared_in.insert(m.uid(), level_key.clone());
if m.kind() == SymbolKind::MethodPrototype
&& let Some(real) = m.method_prototype_subroutine()
{
by_uid.insert(real.uid(), slot);
declared_in.insert(real.uid(), level_key.clone());
}
name_to_slot.insert(m.name(), slot);
if let Some(impl_sym) = vm.implementation {
slots[slot] = Some(impl_sym);
}
}
}
let mut ifaces: Vec<sv_lang::Type<'d>> = Vec::new();
for &level_sym in &levels {
let level_ty = level_sym.as_type().ok_or_else(|| {
LoweringError::unsupported_at("class symbol has no type", &level_sym)
})?;
for iface_ty in sv_sim_sim::netlist::flattened_interfaces(level_ty) {
if !ifaces
.iter()
.any(|seen| seen.as_symbol() == iface_ty.as_symbol())
{
ifaces.push(iface_ty);
}
}
}
for iface_ty in ifaces {
let iface_sym = iface_ty.as_symbol();
let iface_key = specialization_key_of_symbol(&iface_sym)?;
let members = sv_sim_sim::netlist::classify_members(iface_sym)
.map_err(|e| elab_to_lowering(e, &iface_sym))?;
for p in members.methods {
if p.kind() != SymbolKind::MethodPrototype {
return Err(LoweringError::unsupported_at(
&format!(
"internal: {:?} is not the MethodPrototype kind every member of an \
interface class's own `methods` (classify_members) must be (LRM \
8.26.1: an interface class contains only pure virtual methods)",
p.kind()
),
&p,
));
}
let Some(&slot) = name_to_slot.get(p.name()) else {
return Err(LoweringError::unsupported_at(
&format!(
"internal: interface class method `{}` has no matching virtual \
method of the same name anywhere in this class's own hierarchy \
(base chain + own level) -- `name_to_slot` above records a slot \
for EVERY virtual method name walked, pure or implemented (LRM \
8.26's own \"a virtual class shall define or inherit a pure \
virtual method prototype OR virtual method implementation\" -- \
verified live against Verilator 5.032 that the deferred-to-a-\
descendant shape is legal SV, not merely an implemented-immediately \
one), so a design whose `implements` clause slang itself accepted \
should never reach this",
p.name()
),
&p,
));
};
by_uid.insert(p.uid(), slot);
declared_in.insert(p.uid(), iface_key.clone());
if let Some(stub) = p.method_prototype_subroutine() {
by_uid.insert(stub.uid(), slot);
declared_in.insert(stub.uid(), iface_key.clone());
}
}
}
Ok(Self {
slots,
by_uid,
declared_in,
})
}
pub fn len(&self) -> usize {
self.slots.len()
}
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
pub fn slot_of(&self, member: &sv_lang::Symbol<'_>) -> Option<usize> {
self.by_uid.get(&member.uid()).copied()
}
pub fn impls(&self) -> &[Option<sv_lang::Symbol<'d>>] {
&self.slots
}
}
fn is_virtual_method(callee: &sv_lang::Symbol<'_>) -> Result<bool, LoweringError> {
Ok(classify_virtual_member(*callee)?.is_some())
}
#[derive(Default)]
struct ClassIdRegistry {
ids: HashMap<String, ClassTemplateId>,
next_light: u32,
}
const LIGHT_TEMPLATE_ID_BASE: u32 = 0x4000_0000;
thread_local! {
static CLASS_TEMPLATE_IDS: RefCell<ClassIdRegistry> = RefCell::new(ClassIdRegistry::default());
}
pub fn reset_class_registries() {
CLASS_TEMPLATE_IDS.with(|c| *c.borrow_mut() = ClassIdRegistry::default());
VIRTUAL_IMPLS.with(|c| c.borrow_mut().clear());
sv_sim_sim::process::cfg::reset_aborting_virtuals();
crate::classes_randomize::reset_failed_programs();
}
fn specialization_key_of_symbol(sym: &sv_lang::Symbol<'_>) -> Result<String, LoweringError> {
let ty = sym
.as_type()
.ok_or_else(|| LoweringError::unsupported_at("class symbol has no type", sym))?;
Ok(sv_sim_sim::netlist::class_specialization_key(ty))
}
fn note_class_template_id(
key: String,
id: ClassTemplateId,
at: &sv_lang::Symbol<'_>,
) -> Result<(), LoweringError> {
CLASS_TEMPLATE_IDS.with(|c| {
let mut c = c.borrow_mut();
match c.ids.get(&key) {
Some(&prev) if prev != id => Err(LoweringError::unsupported_at(
&format!(
"two distinct class specializations (ClassTemplateId {} and {}) have the \
identical specialization key `{key}` -- they cannot be told apart by \
name and parameter binding, so they would silently share one object \
layout and method set",
prev.0, id.0
),
at,
)),
_ => {
c.ids.insert(key, id);
Ok(())
}
}
})
}
pub fn class_template_id_of(sym: &sv_lang::Symbol<'_>) -> Result<ClassTemplateId, LoweringError> {
let key = specialization_key_of_symbol(sym)?;
CLASS_TEMPLATE_IDS
.with(|c| c.borrow().ids.get(&key).copied())
.ok_or_else(|| {
LoweringError::unsupported_at(
"internal: this class's ClassTemplateId was never registered before codegen \
reached a `new()` site for it -- `pipeline::build_pipeline`'s own whole-design \
class discovery/registration pass must run first",
sym,
)
})
}
pub(crate) fn static_singleton_template_id(
class_sym: &sv_lang::Symbol<'_>,
) -> Result<ClassTemplateId, LoweringError> {
let key = format!("static|{}", specialization_key_of_symbol(class_sym)?);
Ok(CLASS_TEMPLATE_IDS.with(|c| {
let mut c = c.borrow_mut();
if let Some(&id) = c.ids.get(&key) {
return id;
}
let id = ClassTemplateId(LIGHT_TEMPLATE_ID_BASE + c.next_light);
c.next_light += 1;
sv_sim_sim::objects::register_static_singleton_key(key.trim_start_matches("static|"), id);
c.ids.insert(key, id);
id
}))
}
pub fn register_template<'d>(
id: ClassTemplateId,
class_sym: sv_lang::Symbol<'d>,
) -> Result<ObjectLayout<'d>, LoweringError> {
register_template_in(id, class_sym, &[])
}
pub fn register_template_in<'d>(
id: ClassTemplateId,
class_sym: sv_lang::Symbol<'d>,
universe: &[(ClassTemplateId, sv_lang::Symbol<'d>)],
) -> Result<ObjectLayout<'d>, LoweringError> {
let layout = ObjectLayout::for_template(class_sym)?;
sv_sim_sim::objects::register_handle_fields(id, layout.handle_field_slots());
sv_sim_sim::objects::register_collection_fields(id, layout.collection_field_slots());
sv_sim_sim::objects::register_nested_handles(id, layout.nested_handle_slots());
sv_sim_sim::objects::register_rand_props(id, layout.rand_props());
note_class_template_id(specialization_key_of_symbol(&class_sym)?, id, &class_sym)?;
if is_builtin_std_class(&class_sym).is_none() {
crate::classes_randomize::register_class_program(id, class_sym, &layout, universe);
}
Ok(layout)
}
pub(crate) fn cast_template_id(ty: sv_lang::Type<'_>) -> ClassTemplateId {
let key = sv_sim_sim::netlist::class_specialization_key(ty);
CLASS_TEMPLATE_IDS.with(|c| {
let mut c = c.borrow_mut();
if let Some(&id) = c.ids.get(&key) {
return id;
}
let light_key = format!("cast|{key}");
if let Some(&id) = c.ids.get(&light_key) {
return id;
}
let id = ClassTemplateId(LIGHT_TEMPLATE_ID_BASE + c.next_light);
c.next_light += 1;
c.ids.insert(light_key, id);
id
})
}
pub fn register_ancestor_closures(classes: &[(ClassTemplateId, sv_lang::Symbol<'_>)]) {
for &(id, class_sym) in classes {
let Some(ty) = class_sym.as_type() else {
continue;
};
let mut closure = std::collections::HashSet::new();
let bases = sv_sim_sim::netlist::base_chain(ty);
for level in bases.iter().copied().chain(std::iter::once(ty)) {
closure.insert(cast_template_id(level));
for iface in sv_sim_sim::netlist::flattened_interfaces(level) {
closure.insert(cast_template_id(iface));
}
}
sv_sim_sim::objects::register_ancestors(id, closure);
}
}
pub(crate) fn is_a_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: IntValue<'ctx>,
dest: sv_lang::Type<'_>,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
let id = cast_template_id(dest);
let r = obj_call(
cx,
ObjOp::IsA,
h,
i64t.const_int(u64::from(id.0), false),
i64t.const_zero(),
null,
);
cx.b.build_int_compare(inkwell::IntPredicate::NE, r, i64t.const_zero(), "is_a")
.unwrap()
}
fn obj_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ptr = cx.ctx.ptr_type(AddressSpace::default());
cx.module.get_function(SV_RT_OBJ_SYMBOL).unwrap_or_else(|| {
cx.module.add_function(
SV_RT_OBJ_SYMBOL,
i64t.fn_type(&[i32t.into(), i64t.into(), ptr.into()], false),
Some(Linkage::External),
)
})
}
pub(crate) fn obj_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: ObjOp,
h: IntValue<'ctx>,
index: IntValue<'ctx>,
index2: IntValue<'ctx>,
val: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let st = cx
.ctx
.struct_type(&[i64t.into(), i64t.into(), ptr.into()], false);
let buf = entry_alloca(cx, st, "obj_args");
let fields: [inkwell::values::BasicValueEnum<'ctx>; 3] =
[index.into(), index2.into(), val.into()];
for (i, v) in fields.into_iter().enumerate() {
let p = cx.b.build_struct_gep(st, buf, i as u32, "obj_arg").unwrap();
cx.b.build_store(p, v).unwrap();
}
let f = obj_fn(cx);
cx.b.build_call(
f,
&[
i32t.const_int(op as u32 as u64, false).into(),
h.into(),
buf.into(),
],
"obj_call",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_obj returns a value")
.into_int_value()
}
fn alloc_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
id: ClassTemplateId,
words: usize,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
obj_call(
cx,
ObjOp::Alloc,
i64t.const_zero(),
i64t.const_int(u64::from(id.0), false),
i64t.const_int(words as u64, false),
null,
)
}
pub(crate) fn retain_handle<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, h: IntValue<'ctx>) {
let i64t = cx.ctx.i64_type();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
obj_call(
cx,
ObjOp::Retain,
h,
i64t.const_zero(),
i64t.const_zero(),
null,
);
}
pub(crate) fn release_handle<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, h: IntValue<'ctx>) {
let i64t = cx.ctx.i64_type();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
obj_call(
cx,
ObjOp::Release,
h,
i64t.const_zero(),
i64t.const_zero(),
null,
);
}
const WIDE_MARSHAL_WORDS: u32 = 3;
pub(crate) fn word_ptr<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
words: u32,
idx: u32,
) -> PointerValue<'ctx> {
let i64t = cx.ctx.i64_type();
let arr_ty = i64t.array_type(words);
let idx0 = cx.ctx.i32_type().const_zero();
let idxn = cx.ctx.i32_type().const_int(u64::from(idx), false);
unsafe {
cx.b.build_gep(arr_ty, buf, &[idx0, idxn], "word_ptr")
.unwrap()
}
}
pub(crate) fn store_packed_words<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
pv: PackedValue<'ctx>,
words: u32,
) {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let wide_ty = op.i_n(words * 64);
let wide =
cx.b.build_int_z_extend_or_bit_cast(pv.llvm, wide_ty, "fld_wide")
.unwrap();
if words >= WIDE_MARSHAL_WORDS {
let st = cx.b.build_store(buf, wide).unwrap();
st.set_alignment(8).unwrap();
return;
}
for w in 0..words {
let shifted = if w == 0 {
wide
} else {
cx.b.build_right_shift(
wide,
wide_ty.const_int(u64::from(w) * 64, false),
false,
"fld_shift",
)
.unwrap()
};
let word =
cx.b.build_int_truncate_or_bit_cast(shifted, i64t, "fld_word")
.unwrap();
cx.b.build_store(word_ptr(cx, buf, words, w), word).unwrap();
}
}
pub(crate) fn load_packed_words<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
width: u32,
repr: ValueRepr,
words: u32,
signed: bool,
) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let wide_ty = op.i_n(words * 64);
let mut acc = wide_ty.const_zero();
if words >= WIDE_MARSHAL_WORDS {
let ld = cx.b.build_load(wide_ty, buf, "fld_wide_ld").unwrap();
inkwell::values::BasicValue::as_instruction_value(&ld)
.expect("a load")
.set_alignment(8)
.unwrap();
acc = ld.into_int_value();
}
for w in 0..words {
if words >= WIDE_MARSHAL_WORDS {
break;
}
let word =
cx.b.build_load(i64t, word_ptr(cx, buf, words, w), "fld_word_ld")
.unwrap()
.into_int_value();
let ext =
cx.b.build_int_z_extend_or_bit_cast(word, wide_ty, "fld_word_ext")
.unwrap();
let shifted = if w == 0 {
ext
} else {
cx.b.build_left_shift(
ext,
wide_ty.const_int(u64::from(w) * 64, false),
"fld_word_shl",
)
.unwrap()
};
acc = cx.b.build_or(acc, shifted, "fld_word_or").unwrap();
}
let target_ty = op.packed_type(width, repr);
let llvm =
cx.b.build_int_truncate_or_bit_cast(acc, target_ty, "fld_val")
.unwrap();
PackedValue {
llvm,
width,
repr,
signed,
}
}
pub(crate) fn field_set_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: IntValue<'ctx>,
slot: FieldSlot,
pv: PackedValue<'ctx>,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let words = slot.words as u32;
let buf = entry_alloca(cx, i64t.array_type(words), "fld_set_buf");
store_packed_words(cx, buf, pv, words);
let live = obj_call(
cx,
ObjOp::FieldSet,
h,
i64t.const_int(slot.offset as u64, false),
i64t.const_int(slot.words as u64, false),
buf,
);
cx.b.build_int_compare(IntPredicate::NE, live, i64t.const_zero(), "fld_set_live")
.unwrap()
}
pub(crate) fn field_get_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: IntValue<'ctx>,
slot: FieldSlot,
width: u32,
signed: bool,
) -> (PackedValue<'ctx>, IntValue<'ctx>) {
let i64t = cx.ctx.i64_type();
let words = slot.words as u32;
let buf = entry_alloca(cx, i64t.array_type(words), "fld_get_buf");
let live = obj_call(
cx,
ObjOp::FieldGet,
h,
i64t.const_int(slot.offset as u64, false),
i64t.const_int(slot.words as u64, false),
buf,
);
let live =
cx.b.build_int_compare(IntPredicate::NE, live, i64t.const_zero(), "fld_get_live")
.unwrap();
(
load_packed_words(cx, buf, width, cx.repr, words, signed),
live,
)
}
pub(crate) fn report_dead_handle_access<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
live: IntValue<'ctx>,
at: sv_lang::SourceLoc,
member: &str,
what: &str,
) -> Result<(), LoweringError> {
let dead = cx.b.build_not(live, "dead_handle").unwrap();
crate::stmt_shared::error_if(
cx,
dead,
at,
&format!("null object handle: {what} of member {member}"),
)
}
fn handle_as_packed<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, h: IntValue<'ctx>) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
op.pack(h, cx.ctx.i64_type().const_zero(), 64, cx.repr, false)
}
pub(crate) fn receiver_handle<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
recv: sv_lang::Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let pv = lower_expr(cx, recv)?.into_packed(
"a class method/field receiver is not a plain handle value",
&cx.fn_sym,
)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(op.lane_val(pv))
}
pub fn is_instance_field(member: &sv_lang::Symbol<'_>) -> bool {
member.kind() == SymbolKind::ClassProperty
&& member.variable_lifetime() == sv_lang::VariableLifetime::Automatic
}
pub fn is_static_field(member: &sv_lang::Symbol<'_>) -> bool {
member.kind() == SymbolKind::ClassProperty
&& member.variable_lifetime() == sv_lang::VariableLifetime::Static
}
fn store_field<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
slot: FieldSlot,
ty: &sv_lang::Type<'d>,
value: LoweredValue<'ctx>,
assign: bool,
) -> Result<LoweredValue<'ctx>, LoweringError> {
store_field_live(cx, h, slot, ty, value, assign).map(|(v, _)| v)
}
fn store_field_live<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
slot: FieldSlot,
ty: &sv_lang::Type<'d>,
value: LoweredValue<'ctx>,
assign: bool,
) -> Result<(LoweredValue<'ctx>, IntValue<'ctx>), LoweringError> {
let width = ty.value_width();
let pv = crate::real::to_storage(cx, value, width, "class property assignment")?;
if pv.width != width {
return Err(LoweringError::unsupported_at(
&format!(
"class property assignment is {} bits for a {width}-bit property",
pv.width
),
&cx.fn_sym,
));
}
let holds_objects = crate::collections::handle_offsets(ty)
.iter()
.any(|s| s.kind == crate::collections::HandleKind::Obj);
let frees_colls = assign && sv_sim_sim::state::storage::is_collection(ty);
let old = (holds_objects || frees_colls).then(|| load_field(cx, h, slot, ty));
let live = field_set_call(cx, h, slot, pv);
if let Some(old) = old {
crate::collections::release_handles_of(cx, ty, old, !frees_colls);
}
Ok((value, live))
}
fn load_field<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
slot: FieldSlot,
ty: &sv_lang::Type<'d>,
) -> LoweredValue<'ctx> {
load_field_live(cx, h, slot, ty).0
}
fn load_field_live<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
slot: FieldSlot,
ty: &sv_lang::Type<'d>,
) -> (LoweredValue<'ctx>, IntValue<'ctx>) {
let (pv, live) = field_get_call(cx, h, slot, ty.value_width(), ty.is_signed());
let v = if ty.is_floating() {
LoweredValue::Real(crate::real::bits_to_real(cx, pv))
} else if ty.is_string() {
LoweredValue::Str(crate::strings::from_storage(cx, pv))
} else {
LoweredValue::Packed(pv)
};
(v, live)
}
pub fn read_field<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
member: &sv_lang::Symbol<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let class_sym = member.parent().ok_or_else(|| {
LoweringError::unsupported_at("class property has no enclosing class", member)
})?;
let layout = ObjectLayout::for_template(class_sym)?;
let slot = layout.slot_of(member).ok_or_else(|| {
LoweringError::unsupported_at(
"internal: class property missing from its own class's layout",
member,
)
})?;
let ty = member
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", member))?;
Ok(load_field(cx, h, slot, &ty))
}
pub fn read_field_checked<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
member: &sv_lang::Symbol<'d>,
at: sv_lang::SourceLoc,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let (slot, ty) = member_slot_and_type(member)?;
let (v, live) = load_field_live(cx, h, slot, &ty);
report_dead_handle_access(cx, live, at, member.name(), "read")?;
Ok(v)
}
pub fn write_field_checked<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
member: &sv_lang::Symbol<'d>,
value: LoweredValue<'ctx>,
at: sv_lang::SourceLoc,
assign: bool,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let (slot, ty) = member_slot_and_type(member)?;
let (v, live) = store_field_live(cx, h, slot, &ty, value, assign)?;
report_dead_handle_access(cx, live, at, member.name(), "write")?;
Ok(v)
}
pub(crate) fn member_slot_and_type<'d>(
member: &sv_lang::Symbol<'d>,
) -> Result<(FieldSlot, sv_lang::Type<'d>), LoweringError> {
let class_sym = member.parent().ok_or_else(|| {
LoweringError::unsupported_at("class property has no enclosing class", member)
})?;
let layout = ObjectLayout::for_template(class_sym)?;
let slot = layout.slot_of(member).ok_or_else(|| {
LoweringError::unsupported_at(
"internal: class property missing from its own class's layout",
member,
)
})?;
let ty = member
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", member))?;
Ok((slot, ty))
}
pub fn write_field<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
member: &sv_lang::Symbol<'d>,
value: LoweredValue<'ctx>,
assign: bool,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let class_sym = member.parent().ok_or_else(|| {
LoweringError::unsupported_at("class property has no enclosing class", member)
})?;
let layout = ObjectLayout::for_template(class_sym)?;
let slot = layout.slot_of(member).ok_or_else(|| {
LoweringError::unsupported_at(
"internal: class property missing from its own class's layout",
member,
)
})?;
let ty = member
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", member))?;
store_field(cx, h, slot, &ty, value, assign)
}
fn handle_ref_text<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ty: sv_lang::Type<'_>,
h: IntValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i32t = cx.ctx.i32_type();
let index = cx.b.build_int_truncate(h, i32t, "obj_index").unwrap();
let fmt = crate::process_lower::build_c_string_global(
cx.ctx,
cx.module,
&format!("{}@%0d", ty.as_symbol().name().replace('%', "%%")),
"obj_ref_fmt",
);
let named = crate::strings::sformat_with(
cx,
fmt,
vec![LoweredValue::Packed(op.pack(
index,
i32t.const_zero(),
32,
cx.repr,
false,
))],
)?;
let null = crate::strings::literal(cx, b"null");
let is_null =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
h,
h.get_type().const_zero(),
"obj_ref_null",
)
.unwrap();
let id =
cx.b.build_select(is_null, null.llvm, named.llvm, "obj_ref")
.unwrap()
.into_int_value();
Ok(LoweredValue::Str(crate::strings::StrValue { llvm: id }))
}
pub(crate) fn format_object<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ty: sv_lang::Type<'_>,
h: IntValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let layout = ObjectLayout::for_template(ty.as_symbol())?;
let mut text = String::from("'{");
let mut leaves = Vec::new();
for (i, f) in layout.fields().iter().enumerate() {
if i > 0 {
text.push_str(", ");
}
let fty = f
.sym
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", &f.sym))?;
text.push_str(&f.sym.name().replace('%', "%%"));
text.push(':');
if fty.is_class() {
let (fh, _) = field_get_call(cx, h, f.slot, 64, false);
let fh = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.lane_val(fh);
text.push_str("%s");
leaves.push(handle_ref_text(cx, fty, fh)?);
} else if sv_sim_sim::state::storage::is_unpacked_aggregate(&fty)
|| (fty.is_unpacked_array() && !sv_sim_sim::state::storage::is_collection(&fty))
{
let v = load_field(cx, h, f.slot, &fty)
.into_packed("an aggregate class property", &f.sym)?;
crate::systask::aggregate_pattern(cx, &fty, v, &mut text, &mut leaves)?;
} else {
let v = load_field(cx, h, f.slot, &fty);
let named = match v {
LoweredValue::Packed(p) => crate::sysfunc::enum_pattern_text(cx, &fty, p)?,
_ => None,
};
if let Some(s) = named {
text.push_str("%s");
leaves.push(s);
} else {
text.push_str("%p");
leaves.push(v);
}
}
}
text.push('}');
let fmt = crate::process_lower::build_c_string_global(cx.ctx, cx.module, &text, "obj_fmt");
let body = crate::strings::sformat_with(cx, fmt, leaves)?;
let null = crate::strings::literal(cx, b"null");
let is_null =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
h,
h.get_type().const_zero(),
"obj_null",
)
.unwrap();
let id =
cx.b.build_select(is_null, null.llvm, body.llvm, "obj_text")
.unwrap()
.into_int_value();
Ok(LoweredValue::Str(crate::strings::StrValue { llvm: id }))
}
pub(crate) fn static_singleton_handle<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
class_sym: sv_lang::Symbol<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let layout = ObjectLayout::for_statics(class_sym)?;
let id = static_singleton_template_id(&class_sym)?;
let i64t = cx.ctx.i64_type();
let name = format!("sv_class_static_{}", id.0);
let cell = cx.module.get_global(&name).unwrap_or_else(|| {
let g = cx.module.add_global(i64t, None, &name);
g.set_initializer(&i64t.const_zero());
g.set_linkage(Linkage::Private);
g
});
let cur_bb =
cx.b.get_insert_block()
.ok_or_else(|| LoweringError::unsupported_at("no insertion block", &cx.fn_sym))?;
let func = cur_bb
.get_parent()
.ok_or_else(|| LoweringError::unsupported_at("no enclosing function", &cx.fn_sym))?;
let init = cx.ctx.append_basic_block(func, "cls_static_init");
let done = cx.ctx.append_basic_block(func, "cls_static_done");
let cached =
cx.b.build_load(i64t, cell.as_pointer_value(), "cls_static_cached")
.unwrap()
.into_int_value();
let live =
cx.b.build_int_compare(
inkwell::IntPredicate::NE,
cached,
i64t.const_zero(),
"cls_static_live",
)
.unwrap();
cx.b.build_conditional_branch(live, done, init).unwrap();
cx.b.position_at_end(init);
let h = alloc_call(cx, id, layout.total_words());
cx.b.build_store(cell.as_pointer_value(), h).unwrap();
for f in layout.fields() {
let fty = f
.sym
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", &f.sym))?;
let default = || {
LoweredValue::Packed(PackedValue {
llvm: crate::arrays::default_value(cx, &fty),
width: fty.value_width(),
repr: cx.repr,
signed: fty.is_signed(),
})
};
if fty.is_event() {
let id = crate::class_events::new_event(cx);
let o = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v = o.pack(id, o.i_n(64).const_zero(), 64, cx.repr, false);
let _live = field_set_call(cx, h, f.slot, v);
continue;
}
let value = match f.sym.initializer() {
Some(init_expr) => {
let cv = init_expr.constant_value();
if let Some(iv) = cv.as_ref().and_then(|c| c.as_integer()) {
crate::expr::lower_constant(cx, iv, Some(init_expr))?
} else if cv.is_some() && init_expr.kind() == ExpressionKind::Call {
match cv.as_ref() {
Some(sv_lang::ConstantValue::Real(r)) => LoweredValue::Real(RealValue {
llvm: cx.ctx.f64_type().const_float(*r),
}),
Some(sv_lang::ConstantValue::String(text)) => {
LoweredValue::Str(crate::strings::literal(cx, text.as_bytes()))
}
_ => {
return Err(LoweringError::unsupported_at(
"a static class property initialized by a call whose constant \
value is neither an integer, a real nor a string",
&f.sym,
));
}
}
} else if cv.is_some() {
crate::collections::owned(cx, init_expr)?
} else {
default()
}
}
None => default(),
};
store_field(cx, h, f.slot, &fty, value, false)?;
}
let init_end = cx.b.get_insert_block().unwrap();
cx.b.build_unconditional_branch(done).unwrap();
cx.b.position_at_end(done);
let phi = cx.b.build_phi(i64t, "cls_static_h").unwrap();
phi.add_incoming(&[(&cached, cur_bb), (&h, init_end)]);
Ok(phi.as_basic_value().into_int_value())
}
pub(crate) fn run_dynamic_static_initializers<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
class_sym: sv_lang::Symbol<'d>,
) -> Result<(), LoweringError> {
let layout = ObjectLayout::for_statics(class_sym)?;
let h = static_singleton_handle(cx, class_sym)?;
for f in layout.fields() {
let Some(init_expr) = f.sym.initializer().filter(|e| e.constant_value().is_none()) else {
continue;
};
let fty = f
.sym
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", &f.sym))?;
if fty.is_event() {
let id = crate::class_events::id_of(cx, init_expr)?;
let o = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v = o.pack(id, o.i_n(64).const_zero(), 64, cx.repr, false);
let _live = field_set_call(cx, h, f.slot, v);
continue;
}
let value = crate::collections::owned(cx, init_expr)?;
store_field(cx, h, f.slot, &fty, value, false)?;
}
Ok(())
}
pub(crate) fn static_location<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
member: &sv_lang::Symbol<'d>,
) -> Result<(IntValue<'ctx>, FieldSlot, sv_lang::Type<'d>), LoweringError> {
let class_sym = member.parent().ok_or_else(|| {
LoweringError::unsupported_at("class property has no enclosing class", member)
})?;
let layout = ObjectLayout::for_statics(class_sym)?;
let slot = layout.slot_of(member).ok_or_else(|| {
LoweringError::unsupported_at(
"internal: static class property missing from its own class's static layout",
member,
)
})?;
let ty = member
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", member))?;
let h = static_singleton_handle(cx, class_sym)?;
Ok((h, slot, ty))
}
pub fn read_static<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
member: &sv_lang::Symbol<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let (h, slot, ty) = static_location(cx, member)?;
Ok(load_field(cx, h, slot, &ty))
}
pub fn write_static<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
member: &sv_lang::Symbol<'d>,
value: LoweredValue<'ctx>,
assign: bool,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let (h, slot, ty) = static_location(cx, member)?;
store_field(cx, h, slot, &ty, value, assign)
}
pub fn current_this_var<'d>(
cx: &LowerCtx<'_, 'd, '_>,
) -> Result<sv_lang::Symbol<'d>, LoweringError> {
cx.fn_sym
.subroutine_this_var()
.or(cx.inline_this)
.ok_or_else(|| {
LoweringError::unsupported_at(
"`this`/a bare field reference outside a (non-static) class method body",
&cx.fn_sym,
)
})
}
pub(crate) fn current_this_handle<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
) -> Result<IntValue<'ctx>, LoweringError> {
if let Some(h) = cx.ctor_this.get() {
return Ok(h);
}
let this_var = current_this_var(cx)?;
let (slot, _, _) = *cx.locals.get(&this_var.uid()).ok_or_else(|| {
LoweringError::unsupported_at("`this` is unavailable in this scope", &cx.fn_sym)
})?;
Ok(cx
.b
.build_load(cx.ctx.i64_type(), slot, "this_ld")
.unwrap()
.into_int_value())
}
pub fn try_read_bare_field<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
sym: &sv_lang::Symbol<'d>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
if is_static_field(sym) {
return Ok(Some(read_static(cx, sym)?));
}
if !is_instance_field(sym) {
return Ok(None);
}
let h = current_this_handle(cx)?;
Ok(Some(read_field(cx, h, sym)?))
}
pub fn is_class_field_lvalue(lhs: sv_lang::Expression<'_>) -> bool {
if lhs.kind() == ExpressionKind::MemberAccess {
return lhs
.member_symbol()
.is_some_and(|m| is_instance_field(&m) || is_static_field(&m));
}
if matches!(
lhs.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
return lhs
.referenced_symbol()
.is_some_and(|s| is_instance_field(&s) || is_static_field(&s));
}
false
}
pub(crate) fn class_field_root<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<(IntValue<'ctx>, FieldSlot, sv_lang::Type<'d>)>, LoweringError> {
let Some((h, slot, ty, dead)) = class_field_loc(cx, e)? else {
return Ok(None);
};
if let Some((name, at)) = dead {
let probe = FieldSlot {
offset: slot.offset,
words: 1,
};
let (_, live) = field_get_call(cx, h, probe, 1, false);
report_dead_handle_access(cx, live, at, &name, "write")?;
}
Ok(Some((h, slot, ty)))
}
#[allow(clippy::type_complexity)]
pub(crate) fn class_field_loc<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<
Option<(
IntValue<'ctx>,
FieldSlot,
sv_lang::Type<'d>,
Option<(String, sv_lang::SourceLoc)>,
)>,
LoweringError,
> {
if e.kind() == ExpressionKind::MemberAccess {
let Some(member) = e.member_symbol() else {
return Ok(None);
};
if is_static_field(&member) {
let (h, slot, ty) = static_location(cx, &member)?;
return Ok(Some((h, slot, ty, None)));
}
if !is_instance_field(&member) {
return Ok(None);
}
let base_expr = e.operand().ok_or_else(|| {
LoweringError::unsupported_at("member-access lvalue missing base", &cx.fn_sym)
})?;
let h = receiver_handle(cx, base_expr)?;
let (slot, ty) = member_slot_and_type(&member)?;
return Ok(Some((
h,
slot,
ty,
Some((member.name().to_string(), e.source_range().start)),
)));
}
if matches!(
e.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
let Some(sym) = e.referenced_symbol() else {
return Ok(None);
};
if is_static_field(&sym) {
let (h, slot, ty) = static_location(cx, &sym)?;
return Ok(Some((h, slot, ty, None)));
}
if !is_instance_field(&sym) {
return Ok(None);
}
let h = current_this_handle(cx)?;
let (slot, ty) = member_slot_and_type(&sym)?;
return Ok(Some((h, slot, ty, None)));
}
Ok(None)
}
pub(crate) fn field_get_into<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: IntValue<'ctx>,
slot: FieldSlot,
dst: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let live = obj_call(
cx,
ObjOp::FieldGet,
h,
i64t.const_int(slot.offset as u64, false),
i64t.const_int(slot.words as u64, false),
dst,
);
cx.b.build_int_compare(IntPredicate::NE, live, i64t.const_zero(), "fld_img_live")
.unwrap()
}
pub(crate) fn field_set_from<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: IntValue<'ctx>,
slot: FieldSlot,
src: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let live = obj_call(
cx,
ObjOp::FieldSet,
h,
i64t.const_int(slot.offset as u64, false),
i64t.const_int(slot.words as u64, false),
src,
);
cx.b.build_int_compare(
IntPredicate::NE,
live,
i64t.const_zero(),
"fld_img_set_live",
)
.unwrap()
}
pub fn try_write_field<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
try_write_field_as(cx, lhs, value, false)
}
pub fn try_assign_field<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
try_write_field_as(cx, lhs, value, true)
}
fn try_write_field_as<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
assign: bool,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
if lhs.kind() == ExpressionKind::MemberAccess {
let Some(member) = lhs.member_symbol() else {
return Ok(None);
};
if is_static_field(&member) {
return Ok(Some(write_static(cx, &member, value, assign)?));
}
if !is_instance_field(&member) {
return Ok(None);
}
let base_expr = lhs.operand().ok_or_else(|| {
LoweringError::unsupported_at("member-access lvalue missing base", &cx.fn_sym)
})?;
let h = receiver_handle(cx, base_expr)?;
let at = lhs.source_range().start;
return Ok(Some(write_field_checked(
cx, h, &member, value, at, assign,
)?));
}
if matches!(
lhs.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
let Some(sym) = lhs.referenced_symbol() else {
return Ok(None);
};
if is_static_field(&sym) {
return Ok(Some(write_static(cx, &sym, value, assign)?));
}
if !is_instance_field(&sym) {
return Ok(None);
}
let h = current_this_handle(cx)?;
return Ok(Some(write_field(cx, h, &sym, value, assign)?));
}
Ok(None)
}
fn reject_if_std_intrinsic(
cx: &LowerCtx<'_, '_, '_>,
callee: &sv_lang::Symbol<'_>,
) -> Result<(), LoweringError> {
let class_sym = callee.parent().ok_or_else(|| {
LoweringError::unsupported_at("method has no enclosing class", &cx.fn_sym)
})?;
if let Some(intrinsic) = is_builtin_std_class(&class_sym) {
return Err(LoweringError::unsupported_at(
&format!(
"calling the std-package intrinsic {intrinsic:?}'s method is not yet \
implemented (classes plan Task 18)"
),
&cx.fn_sym,
));
}
Ok(())
}
pub(crate) enum ProcessCall<'ctx> {
NotProcess,
Lowered(Option<LoweredValue<'ctx>>),
}
fn process_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let symbol = sv_sim_runtime::process_class::SV_RT_PROCESS_SYMBOL;
cx.module.get_function(symbol).unwrap_or_else(|| {
cx.module.add_function(
symbol,
i64t.fn_type(&[i32t.into(), i64t.into(), i64t.into()], false),
Some(Linkage::External),
)
})
}
fn process_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: sv_sim_runtime::process_class::ProcessOp,
h: IntValue<'ctx>,
arg: IntValue<'ctx>,
) -> IntValue<'ctx> {
let i32t = cx.ctx.i32_type();
cx.b.build_call(
process_fn(cx),
&[
i32t.const_int(op as u32 as u64, false).into(),
h.into(),
arg.into(),
],
"process_call",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_process returns a value")
.into_int_value()
}
pub(crate) fn lower_std_process_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
) -> Result<ProcessCall<'ctx>, LoweringError> {
use sv_sim_runtime::process_class::ProcessOp;
let Some(callee) = call.call_subroutine() else {
return Ok(ProcessCall::NotProcess);
};
let Some(class_sym) = callee.parent() else {
return Ok(ProcessCall::NotProcess);
};
match is_builtin_std_class(&class_sym) {
Some(StdIntrinsic::Process) => {}
Some(StdIntrinsic::WeakReference) => return lower_weakref_call(cx, call, callee),
Some(StdIntrinsic::Semaphore) => return lower_semaphore_call(cx, call, callee),
Some(StdIntrinsic::Mailbox) => return lower_mailbox_call(cx, call, callee),
_ => return lower_object_rng_call(cx, call, callee, class_sym),
}
let at = call.source_range().start;
let i64t = cx.ctx.i64_type();
let method = callee.name();
if method == "self" {
let h = process_call(
cx,
ProcessOp::SelfHandle,
i64t.const_zero(),
i64t.const_zero(),
);
return Ok(ProcessCall::Lowered(Some(LoweredValue::Packed(
handle_as_packed(cx, h),
))));
}
let op = match method {
"status" => ProcessOp::Status,
"kill" => ProcessOp::Kill,
"await" => ProcessOp::Await,
"suspend" => ProcessOp::Suspend,
"resume" => ProcessOp::Resume,
"srandom" => ProcessOp::SRandom,
"get_randstate" => ProcessOp::GetRandState,
"set_randstate" => ProcessOp::SetRandState,
other => {
return Err(LoweringError::unsupported_at(
&format!("calling process::{other} is not implemented"),
&cx.fn_sym,
));
}
};
let recv = call.call_this_class().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("process::{method} called without a process handle"),
&cx.fn_sym,
)
})?;
let h = receiver_handle(cx, recv)?;
let null =
cx.b.build_int_compare(IntPredicate::EQ, h, i64t.const_zero(), "process_null")
.unwrap();
crate::stmt_shared::error_if(
cx,
null,
at,
&format!("null process handle: call of process::{method}"),
)?;
let method_arg = |what: &str| {
call.operands().get(1).copied().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("process::{method} called without its {what} argument"),
&cx.fn_sym,
)
})
};
let arg = match op {
ProcessOp::SRandom => crate::strings::index_i64(cx, method_arg("seed")?)?,
ProcessOp::SetRandState => {
crate::strings::to_string(cx, lower_expr(cx, method_arg("state")?)?)?.llvm
}
ProcessOp::Status
| ProcessOp::Kill
| ProcessOp::Await
| ProcessOp::Suspend
| ProcessOp::Resume
| ProcessOp::GetRandState
| ProcessOp::SelfHandle => i64t.const_zero(),
};
let ret = process_call(cx, op, h, arg);
match op {
ProcessOp::Status => {
let ty = callee.subroutine_return_type().ok_or_else(|| {
LoweringError::unsupported_at("process::status has no type", &cx.fn_sym)
})?;
let width = ty.value_width();
let opb = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val =
cx.b.build_int_truncate_or_bit_cast(ret, opb.i_n(width), "state")
.unwrap();
let unk = val.get_type().const_zero();
Ok(ProcessCall::Lowered(Some(LoweredValue::Packed(opb.pack(
val,
unk,
width,
cx.repr,
ty.is_signed(),
)))))
}
ProcessOp::Kill => {
crate::stmt_shared::leave_if_aborted(cx)?;
Ok(ProcessCall::Lowered(None))
}
ProcessOp::Await => {
let on_self =
cx.b.build_int_compare(IntPredicate::EQ, ret, i64t.const_zero(), "await_on_self")
.unwrap();
crate::stmt_shared::error_if(
cx,
on_self,
at,
"process::await called on the calling process itself (LRM 1800-2023 9.7)",
)?;
Ok(ProcessCall::Lowered(None))
}
ProcessOp::Suspend | ProcessOp::Resume | ProcessOp::SRandom | ProcessOp::SetRandState => {
Ok(ProcessCall::Lowered(None))
}
ProcessOp::GetRandState => Ok(ProcessCall::Lowered(Some(LoweredValue::Str(
crate::strings::StrValue { llvm: ret },
)))),
ProcessOp::SelfHandle => Err(LoweringError::unsupported_at(
"internal: process::self reached the receiver path",
&cx.fn_sym,
)),
}
}
fn weakref_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let symbol = sv_sim_runtime::weakref::SV_RT_WEAKREF_SYMBOL;
cx.module.get_function(symbol).unwrap_or_else(|| {
cx.module.add_function(
symbol,
i64t.fn_type(&[i32t.into(), i64t.into(), i64t.into()], false),
Some(Linkage::External),
)
})
}
fn weakref_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: sv_sim_runtime::weakref::WeakRefOp,
h: IntValue<'ctx>,
arg: IntValue<'ctx>,
) -> IntValue<'ctx> {
let i32t = cx.ctx.i32_type();
cx.b.build_call(
weakref_fn(cx),
&[
i32t.const_int(op as u32 as u64, false).into(),
h.into(),
arg.into(),
],
"weakref_call",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_weakref returns a value")
.into_int_value()
}
pub(crate) fn is_weak_get_call(e: &sv_lang::Expression<'_>) -> bool {
if e.kind() != ExpressionKind::Call || e.is_system_call() == Some(true) {
return false;
}
let Some(callee) = e.call_subroutine() else {
return false;
};
callee.name() == "get"
&& callee
.parent()
.is_some_and(|c| is_builtin_std_class(&c) == Some(StdIntrinsic::WeakReference))
}
pub(crate) fn reads_object_state(expr: &sv_lang::Expression<'_>) -> bool {
let mut stack = vec![expr.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression() {
let kind = e.kind();
if kind == ExpressionKind::Call && e.is_system_call() != Some(true) {
return true;
}
if kind == ExpressionKind::MemberAccess
&& e.member_symbol()
.is_some_and(|m| is_instance_field(&m) || is_static_field(&m))
{
return true;
}
if matches!(
kind,
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) && e
.referenced_symbol()
.is_some_and(|s| is_instance_field(&s) || is_static_field(&s))
{
return true;
}
}
stack.extend(n.children());
}
false
}
fn lower_weakref_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
callee: sv_lang::Symbol<'d>,
) -> Result<ProcessCall<'ctx>, LoweringError> {
use sv_sim_runtime::weakref::WeakRefOp;
let at = call.source_range().start;
let i64t = cx.ctx.i64_type();
let method = callee.name();
match method {
"get_id" => {
let obj = call.operands().first().copied().ok_or_else(|| {
LoweringError::unsupported_at(
"weak_reference::get_id called without its object argument",
&cx.fn_sym,
)
})?;
let h = receiver_handle(cx, obj)?;
let ret = weakref_call(cx, WeakRefOp::GetId, i64t.const_zero(), h);
let ty = callee.subroutine_return_type().ok_or_else(|| {
LoweringError::unsupported_at("weak_reference::get_id has no type", &cx.fn_sym)
})?;
let width = ty.value_width();
let opb = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val =
cx.b.build_int_truncate_or_bit_cast(ret, opb.i_n(width), "weak_id")
.unwrap();
let unk = val.get_type().const_zero();
Ok(ProcessCall::Lowered(Some(LoweredValue::Packed(opb.pack(
val,
unk,
width,
cx.repr,
ty.is_signed(),
)))))
}
"get" | "clear" => {
let op = if method == "get" {
WeakRefOp::Get
} else {
WeakRefOp::Clear
};
let recv = call.call_this_class().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("weak_reference::{method} called without a weak-reference handle"),
&cx.fn_sym,
)
})?;
let h = receiver_handle(cx, recv)?;
let null =
cx.b.build_int_compare(IntPredicate::EQ, h, i64t.const_zero(), "weakref_null")
.unwrap();
crate::stmt_shared::error_if(
cx,
null,
at,
&format!("null weak_reference handle: call of weak_reference::{method}"),
)?;
let ret = weakref_call(cx, op, h, i64t.const_zero());
if op == WeakRefOp::Get {
Ok(ProcessCall::Lowered(Some(LoweredValue::Packed(
handle_as_packed(cx, ret),
))))
} else {
Ok(ProcessCall::Lowered(None))
}
}
other => Err(LoweringError::unsupported_at(
&format!("calling weak_reference::{other} is not implemented"),
&cx.fn_sym,
)),
}
}
fn lower_new_weakref<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
class_sym: &sv_lang::Symbol<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use sv_sim_runtime::weakref::WeakRefOp;
let referent_expr = e
.new_class_constructor_call()
.and_then(|c| c.operands().first().copied())
.or_else(|| e.operands().first().copied())
.ok_or_else(|| {
LoweringError::unsupported_at(
"`new()` of weak_reference without its referent argument",
&cx.fn_sym,
)
})?;
let referent = receiver_handle(cx, referent_expr)?;
let id = class_template_id_of(class_sym)?;
let h = alloc_call(cx, id, 1);
weakref_call(cx, WeakRefOp::New, h, referent);
Ok(LoweredValue::Packed(handle_as_packed(cx, h)))
}
fn semaphore_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let symbol = sv_sim_runtime::semaphore_rt::SV_RT_SEMAPHORE_SYMBOL;
cx.module.get_function(symbol).unwrap_or_else(|| {
cx.module.add_function(
symbol,
i64t.fn_type(&[i32t.into(), i64t.into(), i64t.into()], false),
Some(Linkage::External),
)
})
}
pub(crate) fn semaphore_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: sv_sim_runtime::semaphore_rt::SemaphoreOp,
h: IntValue<'ctx>,
arg: IntValue<'ctx>,
) -> IntValue<'ctx> {
let i32t = cx.ctx.i32_type();
cx.b.build_call(
semaphore_fn(cx),
&[
i32t.const_int(op as u32 as u64, false).into(),
h.into(),
arg.into(),
],
"semaphore_call",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_semaphore returns a value")
.into_int_value()
}
fn semaphore_count_arg<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
default: u64,
) -> Result<IntValue<'ctx>, LoweringError> {
match call.operands().get(1).copied() {
Some(e) => crate::strings::index_i64(cx, e),
None => Ok(cx.ctx.i64_type().const_int(default, false)),
}
}
fn lower_semaphore_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
callee: sv_lang::Symbol<'d>,
) -> Result<ProcessCall<'ctx>, LoweringError> {
use sv_sim_runtime::semaphore_rt::SemaphoreOp;
let at = call.source_range().start;
let i64t = cx.ctx.i64_type();
let method = callee.name();
let op = match method {
"put" => SemaphoreOp::Put,
"get" => SemaphoreOp::Get,
"try_get" => SemaphoreOp::TryGet,
other => {
return Err(LoweringError::unsupported_at(
&format!("calling semaphore::{other} is not implemented"),
&cx.fn_sym,
));
}
};
let recv = call.call_this_class().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("semaphore::{method} called without a semaphore handle"),
&cx.fn_sym,
)
})?;
let h = receiver_handle(cx, recv)?;
let null =
cx.b.build_int_compare(IntPredicate::EQ, h, i64t.const_zero(), "semaphore_null")
.unwrap();
crate::stmt_shared::error_if(
cx,
null,
at,
&format!("null semaphore handle: call of semaphore::{method}"),
)?;
let arg = semaphore_count_arg(cx, call, 1)?;
let negative =
cx.b.build_int_compare(IntPredicate::SLT, arg, i64t.const_zero(), "semaphore_neg")
.unwrap();
crate::stmt_shared::error_if(
cx,
negative,
at,
&format!("semaphore::{method}: negative keyCount"),
)?;
let ret = semaphore_call(cx, op, h, arg);
if op != SemaphoreOp::TryGet {
return Ok(ProcessCall::Lowered(None));
}
let ty = callee.subroutine_return_type().ok_or_else(|| {
LoweringError::unsupported_at("semaphore::try_get has no type", &cx.fn_sym)
})?;
let width = ty.value_width();
let opb = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val =
cx.b.build_int_truncate_or_bit_cast(ret, opb.i_n(width), "try_get")
.unwrap();
let unk = val.get_type().const_zero();
Ok(ProcessCall::Lowered(Some(LoweredValue::Packed(opb.pack(
val,
unk,
width,
cx.repr,
ty.is_signed(),
)))))
}
fn lower_new_semaphore<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
class_sym: &sv_lang::Symbol<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use sv_sim_runtime::semaphore_rt::{SEMAPHORE_FIELD_WORDS, SemaphoreOp};
let count = match e
.new_class_constructor_call()
.and_then(|c| c.operands().first().copied())
.or_else(|| e.operands().first().copied())
{
Some(arg) => crate::strings::index_i64(cx, arg)?,
None => cx.ctx.i64_type().const_zero(),
};
let id = class_template_id_of(class_sym)?;
let h = alloc_call(cx, id, SEMAPHORE_FIELD_WORDS);
semaphore_call(cx, SemaphoreOp::New, h, count);
Ok(LoweredValue::Packed(handle_as_packed(cx, h)))
}
fn mailbox_type_fingerprint(ty: &sv_lang::Type<'_>) -> u64 {
let t = ty.canonical();
let key = if t.is_string() {
"string".to_string()
} else if t.is_class() {
format!("class:{}", t.to_sv_string())
} else if t.is_floating() {
format!("float:{}", t.bit_width())
} else if t.is_integral() && !t.is_enum() && !t.is_struct() {
format!(
"int:{}:{}:{}",
t.bit_width(),
t.is_signed(),
t.is_four_state()
)
} else {
format!("other:{}", t.to_sv_string())
};
key.bytes().fold(0xcbf2_9ce4_8422_2325_u64, |h, b| {
(h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3)
})
}
fn mailbox_message_shape<'d>(
cx: &LowerCtx<'_, 'd, '_>,
ty: &sv_lang::Type<'d>,
) -> Result<crate::collections::Coll<'d>, LoweringError> {
if ty.is_untyped_type() {
return Err(LoweringError::unsupported_at(
"a mailbox message of no type",
&cx.fn_sym,
));
}
let obj = ty.is_class();
let coll = sv_sim_sim::state::storage::is_collection(ty);
if !obj && !coll && !crate::collections::handle_offsets(ty).is_empty() {
return Err(LoweringError::unsupported_at(
&format!(
"a mailbox message of type `{}`, which holds a queue, dynamic or associative \
array or a class handle inside a struct, union or fixed-size array",
ty.to_sv_string()
),
&cx.fn_sym,
));
}
let width = if coll { 64 } else { ty.value_width().max(1) };
Ok(crate::collections::Coll {
elem: *ty,
width,
words: (2 * width).div_ceil(64).max(1),
assoc: false,
queue: false,
bound: 0,
key_kind: 0,
key_width: 0,
obj,
leaky: false,
})
}
fn strip_untyped_conversion<'d>(e: sv_lang::Expression<'d>) -> sv_lang::Expression<'d> {
let mut e = e;
while e.kind() == ExpressionKind::Conversion
&& e.expr_type().is_some_and(|t| t.is_untyped_type())
&& let Some(inner) = e.operand()
{
e = inner;
}
e
}
pub(crate) fn has_side_effects(e: sv_lang::Expression<'_>) -> bool {
use sv_lang::UnaryOp;
let kind = e.kind();
if kind == ExpressionKind::Call || kind == ExpressionKind::Assignment {
return true;
}
if kind == ExpressionKind::UnaryOp
&& matches!(
e.unary_op(),
Some(
UnaryOp::Preincrement
| UnaryOp::Predecrement
| UnaryOp::Postincrement
| UnaryOp::Postdecrement
)
)
{
return true;
}
e.operands().into_iter().any(has_side_effects)
}
fn mailbox_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let symbol = sv_sim_runtime::mailbox_rt::SV_RT_MAILBOX_SYMBOL;
cx.module.get_function(symbol).unwrap_or_else(|| {
cx.module.add_function(
symbol,
i64t.fn_type(&[i32t.into(), i64t.into(), ptr.into()], false),
Some(Linkage::External),
)
})
}
fn mailbox_owner_kind(c: &crate::collections::Coll<'_>) -> u64 {
use sv_sim_runtime::mailbox_rt::{OWNS_COLLECTION, OWNS_OBJECT};
if c.obj {
u64::from(OWNS_OBJECT)
} else if sv_sim_sim::state::storage::is_collection(&c.elem) {
u64::from(OWNS_COLLECTION)
} else {
0
}
}
fn mailbox_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: sv_sim_runtime::mailbox_rt::MailboxOp,
h: IntValue<'ctx>,
msg: Option<(&crate::collections::Coll<'_>, u64, PointerValue<'ctx>)>,
bound: IntValue<'ctx>,
) -> IntValue<'ctx> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ptr_t = cx.ctx.ptr_type(AddressSpace::default());
let ty = cx.ctx.struct_type(
&[
i32t.into(),
i32t.into(),
i64t.into(),
ptr_t.into(),
i64t.into(),
],
false,
);
let args = entry_alloca(cx, ty, "mailbox_args");
let (words, obj, fingerprint, buf) = match msg {
Some((c, fp, buf)) => (u64::from(c.words), mailbox_owner_kind(c), fp, buf),
None => (0, 0, 0, ptr_t.const_null()),
};
let fields: [inkwell::values::BasicValueEnum<'ctx>; 5] = [
i32t.const_int(words, false).into(),
i32t.const_int(obj, false).into(),
i64t.const_int(fingerprint, false).into(),
buf.into(),
bound.into(),
];
for (i, v) in fields.into_iter().enumerate() {
let p =
cx.b.build_struct_gep(ty, args, i as u32, "mailbox_arg")
.unwrap();
cx.b.build_store(p, v).unwrap();
}
cx.b.build_call(
mailbox_fn(cx),
&[
i32t.const_int(op as u32 as u64, false).into(),
h.into(),
args.into(),
],
"mailbox_call",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_mailbox returns a value")
.into_int_value()
}
fn if_then<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
cond: IntValue<'ctx>,
name: &str,
body: impl FnOnce() -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let (_, f) = crate::stmt_shared::current_block_and_fn(cx)?;
let then_bb = cx.ctx.append_basic_block(f, &format!("{name}_then"));
let done_bb = cx.ctx.append_basic_block(f, &format!("{name}_done"));
cx.b.build_conditional_branch(cond, then_bb, done_bb)
.unwrap();
cx.b.position_at_end(then_bb);
body()?;
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
Ok(())
}
fn mailbox_first_phase<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let second = mailbox_call(
cx,
sv_sim_runtime::mailbox_rt::MailboxOp::Phase,
i64t.const_zero(),
None,
i64t.const_zero(),
);
cx.b.build_int_compare(IntPredicate::EQ, second, i64t.const_zero(), "mailbox_first")
.unwrap()
}
fn mailbox_hoist_destination<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
arg: &sv_lang::Expression<'d>,
) -> Result<crate::vif::Hoisted, LoweringError> {
use sv_sim_runtime::mailbox_rt::MailboxOp;
let guard = crate::vif::hoist_begin();
if !has_side_effects(*arg) {
return Ok(guard);
}
let (parts, terminal) = crate::vif::side_effect_selectors(arg);
if has_side_effects(terminal) {
return Err(LoweringError::unsupported_at(
"mailbox get/peek into a destination whose base expression runs code",
&cx.fn_sym,
));
}
let i64t = cx.ctx.i64_type();
let mut slot = 0u64;
for p in parts.into_iter().rev() {
let mut meta = None;
let first = mailbox_first_phase(cx);
let first_slot = slot;
if_then(cx, first, "mailbox_dest", || {
let pv = lower_expr(cx, p)?.into_packed("a mailbox destination part", &cx.fn_sym)?;
let bits = pv.llvm.get_type().get_bit_width();
if bits > 256 {
return Err(LoweringError::unsupported_at(
"mailbox get/peek destination part wider than 256 bits",
&cx.fn_sym,
));
}
for c in 0..bits.div_ceil(64) {
let shifted = if c == 0 {
pv.llvm
} else {
cx.b.build_right_shift(
pv.llvm,
pv.llvm.get_type().const_int(u64::from(64 * c), false),
false,
"mailbox_dest_shr",
)
.unwrap()
};
let chunk = if bits <= 64 {
cx.b.build_int_z_extend_or_bit_cast(shifted, i64t, "w")
.unwrap()
} else {
cx.b.build_int_truncate_or_bit_cast(shifted, i64t, "w")
.unwrap()
};
mailbox_call(
cx,
MailboxOp::StashSet,
i64t.const_int(first_slot + u64::from(c), false),
None,
chunk,
);
}
meta = Some((pv.llvm.get_type(), pv.width, pv.repr, pv.signed));
Ok(())
})?;
let (ty, width, repr, signed) = meta.expect("the closure ran at lowering time");
let bits = ty.get_bit_width();
let mut acc = ty.const_zero();
for c in 0..bits.div_ceil(64) {
let w = mailbox_call(
cx,
MailboxOp::StashGet,
i64t.const_int(slot + u64::from(c), false),
None,
i64t.const_zero(),
);
let w = if bits <= 64 {
cx.b.build_int_truncate_or_bit_cast(w, ty, "w").unwrap()
} else {
cx.b.build_int_z_extend_or_bit_cast(w, ty, "w").unwrap()
};
let w = if c == 0 {
w
} else {
cx.b.build_left_shift(w, ty.const_int(u64::from(64 * c), false), "w")
.unwrap()
};
acc = cx.b.build_or(acc, w, "w").unwrap();
}
slot += u64::from(bits.div_ceil(64));
crate::vif::hoist_push(
&p,
crate::value::PackedValue {
llvm: acc,
width,
repr,
signed,
},
);
}
Ok(guard)
}
fn lower_mailbox_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
callee: sv_lang::Symbol<'d>,
) -> Result<ProcessCall<'ctx>, LoweringError> {
use sv_sim_runtime::mailbox_rt::MailboxOp;
let at = call.source_range().start;
let i64t = cx.ctx.i64_type();
let method = callee.name();
let op = match method {
"num" => MailboxOp::Num,
"put" => MailboxOp::Put,
"try_put" => MailboxOp::TryPut,
"get" => MailboxOp::Get,
"try_get" => MailboxOp::TryGet,
"peek" => MailboxOp::Peek,
"try_peek" => MailboxOp::TryPeek,
other => {
return Err(LoweringError::unsupported_at(
&format!("calling mailbox::{other} is not implemented"),
&cx.fn_sym,
));
}
};
let recv = call.call_this_class().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("mailbox::{method} called without a mailbox handle"),
&cx.fn_sym,
)
})?;
let blocking_take = matches!(op, MailboxOp::Get | MailboxOp::Peek);
let h = if blocking_take && has_side_effects(recv) {
let slot = entry_alloca(cx, i64t, "mailbox_recv");
cx.b.build_store(slot, i64t.const_zero()).unwrap();
let first = mailbox_first_phase(cx);
if_then(cx, first, "mailbox_recv", || {
let h = receiver_handle(cx, recv)?;
cx.b.build_store(slot, h).unwrap();
Ok(())
})?;
cx.b.build_load(i64t, slot, "mailbox_recv_h")
.unwrap()
.into_int_value()
} else {
receiver_handle(cx, recv)?
};
let null_msg = format!("null mailbox handle: call of mailbox::{method}");
if !blocking_take {
let null =
cx.b.build_int_compare(IntPredicate::EQ, h, i64t.const_zero(), "mailbox_null")
.unwrap();
crate::stmt_shared::error_if(cx, null, at, &null_msg)?;
}
if op == MailboxOp::Num {
let ret = mailbox_call(cx, op, h, None, i64t.const_zero());
return Ok(ProcessCall::Lowered(Some(crate::collections::int(cx, ret))));
}
let arg = call.operands().get(1).copied().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("mailbox::{method} called without its message argument"),
&cx.fn_sym,
)
})?;
let formal_ty = callee
.subroutine_arguments()
.next()
.and_then(|f| f.value_type())
.ok_or_else(|| {
LoweringError::unsupported_at(
&format!("mailbox::{method} has no message formal"),
&cx.fn_sym,
)
})?;
let untyped = formal_ty.is_untyped_type();
let arg = if untyped {
strip_untyped_conversion(arg)
} else {
arg
};
let arg = match op {
MailboxOp::Get | MailboxOp::TryGet | MailboxOp::Peek | MailboxOp::TryPeek
if arg.kind() == ExpressionKind::Assignment =>
{
arg.left().unwrap_or(arg)
}
_ => arg,
};
let msg_ty = if untyped {
arg.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("a mailbox message with no type", &cx.fn_sym)
})?
} else {
formal_ty
};
let c = mailbox_message_shape(cx, &msg_ty)?;
let fp = mailbox_type_fingerprint(&msg_ty);
let zero = i64t.const_zero();
match op {
MailboxOp::Put | MailboxOp::TryPut => {
let owns = mailbox_owner_kind(&c) != 0;
let v = if owns {
crate::collections::owned(cx, arg)?
} else {
crate::collections::elem_value(cx, &c, arg)?
};
let handle = match v {
LoweredValue::Packed(pv) if owns => Some(crate::collections::op(cx).lane_val(pv)),
_ => None,
};
let buf = crate::collections::elem_buf(cx, &c, v)?;
let ret = mailbox_call(cx, op, h, Some((&c, fp, buf)), zero);
if op == MailboxOp::Put {
return Ok(ProcessCall::Lowered(None));
}
if let Some(handle) = handle {
let full =
cx.b.build_int_compare(IntPredicate::EQ, ret, zero, "mailbox_full")
.unwrap();
if_then(cx, full, "mailbox_refused", || {
if c.obj {
release_handle(cx, handle);
} else {
crate::collections::raw_call(
cx,
[1, 0, 0, 0, 0],
sv_sim_runtime::collections_rt::CollOp::Release,
handle,
crate::collections::Args::default(),
);
}
Ok(())
})?;
}
Ok(ProcessCall::Lowered(Some(crate::collections::int(cx, ret))))
}
MailboxOp::Get | MailboxOp::TryGet | MailboxOp::Peek | MailboxOp::TryPeek => {
let _once = if blocking_take {
mailbox_hoist_destination(cx, &arg)?
} else {
crate::vif::hoist_begin()
};
let out = entry_alloca(
cx,
OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.i_n(64 * c.words),
"mailbox_out",
);
let ret = mailbox_call(cx, op, h, Some((&c, fp, out)), zero);
if blocking_take {
let no_mailbox =
cx.b.build_int_compare(
IntPredicate::EQ,
ret,
i64t.const_int(4, false),
"mailbox_none",
)
.unwrap();
crate::stmt_shared::error_if(cx, no_mailbox, at, &null_msg)?;
let bad =
cx.b.build_int_compare(
IntPredicate::EQ,
ret,
i64t.const_int(3, false),
"mailbox_mismatch",
)
.unwrap();
crate::stmt_shared::error_if(
cx,
bad,
at,
&format!(
"mailbox::{method}: the message is not of the type of the variable \
(`{}`)",
msg_ty.to_sv_string()
),
)?;
}
let got =
cx.b.build_int_compare(
IntPredicate::EQ,
ret,
i64t.const_int(1, false),
"mailbox_got",
)
.unwrap();
if_then(cx, got, "mailbox_deliver", || {
let v = match crate::collections::load_elem(cx, &c, out) {
LoweredValue::Packed(pv) if c.elem.is_string() => {
LoweredValue::Str(crate::strings::StrValue {
llvm: crate::collections::op(cx).lane_val(pv),
})
}
v => v,
};
let owns = mailbox_owner_kind(&c) != 0;
if owns && sv_sim_sim::symid::is_value_name(arg.kind()) {
crate::collections::release_old(cx, arg)?;
crate::expr::store_to(cx, arg, v)?;
} else if owns {
crate::collections::store_releasing(cx, arg, v)?;
} else {
crate::expr::store_to(cx, arg, v)?;
}
Ok(())
})?;
if blocking_take {
Ok(ProcessCall::Lowered(None))
} else {
Ok(ProcessCall::Lowered(Some(crate::collections::int(cx, ret))))
}
}
MailboxOp::New
| MailboxOp::Num
| MailboxOp::Phase
| MailboxOp::StashSet
| MailboxOp::StashGet => unreachable!("handled above"),
}
}
fn lower_new_mailbox<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
class_sym: &sv_lang::Symbol<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use sv_sim_runtime::mailbox_rt::{MAILBOX_FIELD_WORDS, MailboxOp};
let bound = match e
.new_class_constructor_call()
.and_then(|c| c.operands().first().copied())
.or_else(|| e.operands().first().copied())
{
Some(arg) => crate::strings::index_i64(cx, arg)?,
None => cx.ctx.i64_type().const_zero(),
};
let id = class_template_id_of(class_sym)?;
let h = alloc_call(cx, id, MAILBOX_FIELD_WORDS);
mailbox_call(cx, MailboxOp::New, h, None, bound);
Ok(LoweredValue::Packed(handle_as_packed(cx, h)))
}
fn lower_object_rng_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
callee: sv_lang::Symbol<'d>,
class_sym: sv_lang::Symbol<'d>,
) -> Result<ProcessCall<'ctx>, LoweringError> {
if matches!(callee.name(), "pre_randomize" | "post_randomize")
&& matches!(
class_sym.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
)
&& callee
.subroutine_flags()
.contains(sv_lang::MethodFlags::BUILT_IN)
{
crate::classes_randomize::lower_builtin_hook_call(cx, call, callee, class_sym)?;
return Ok(ProcessCall::Lowered(None));
}
if matches!(callee.name(), "rand_mode" | "constraint_mode")
&& matches!(
class_sym.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
)
&& callee
.subroutine_flags()
.contains(sv_lang::MethodFlags::BUILT_IN)
{
return crate::rand_mode::lower_object_mode_call(cx, call, callee);
}
let op = match callee.name() {
"srandom" => ObjOp::SRandom,
"get_randstate" => ObjOp::GetRandState,
"set_randstate" => ObjOp::SetRandState,
_ => return Ok(ProcessCall::NotProcess),
};
if !matches!(
class_sym.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
) || !callee
.subroutine_flags()
.contains(sv_lang::MethodFlags::BUILT_IN)
{
return Ok(ProcessCall::NotProcess);
}
let method = callee.name();
let at = call.source_range().start;
let i64t = cx.ctx.i64_type();
let recv = call.call_this_class();
let h = match recv {
Some(recv) => receiver_handle(cx, recv)?,
None => current_this_handle(cx)?,
};
let null =
cx.b.build_int_compare(IntPredicate::EQ, h, i64t.const_zero(), "obj_rng_null")
.unwrap();
crate::stmt_shared::error_if(
cx,
null,
at,
&format!("null object handle: call of {method}"),
)?;
let mut actuals = call.operands();
if recv.is_some() {
actuals.remove(0);
}
let method_arg = |what: &str| {
actuals.first().copied().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{method} called without its {what} argument"),
&cx.fn_sym,
)
})
};
let arg = match op {
ObjOp::SRandom => crate::strings::index_i64(cx, method_arg("seed")?)?,
ObjOp::SetRandState => {
crate::strings::to_string(cx, lower_expr(cx, method_arg("state")?)?)?.llvm
}
_ => i64t.const_zero(),
};
let null_ptr = cx.ctx.ptr_type(AddressSpace::default()).const_null();
let ret = obj_call(cx, op, h, arg, i64t.const_zero(), null_ptr);
match op {
ObjOp::GetRandState => Ok(ProcessCall::Lowered(Some(LoweredValue::Str(
crate::strings::StrValue { llvm: ret },
)))),
_ => Ok(ProcessCall::Lowered(None)),
}
}
pub(crate) fn resolve_implicit_this_instance_method<'d>(
callee: sv_lang::Symbol<'d>,
) -> Result<Option<sv_lang::Symbol<'d>>, LoweringError> {
let callee = if callee.kind() == SymbolKind::MethodPrototype {
let Some(real) = callee.method_prototype_subroutine() else {
return Ok(None);
};
real
} else {
callee
};
let Some(class_sym) = callee.parent() else {
return Ok(None);
};
if !matches!(
class_sym.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
) {
return Ok(None);
}
if is_builtin_std_class(&class_sym).is_some() {
return Ok(None);
}
if callee.subroutine_this_var().is_some() {
return Ok(Some(callee));
}
if is_virtual_method(&callee)? {
return Ok(Some(callee));
}
Ok(None)
}
fn build_method_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
callee: sv_lang::Symbol<'d>,
h: IntValue<'ctx>,
call: sv_lang::Expression<'d>,
) -> Result<
(
FunctionValue<'ctx>,
Vec<BasicMetadataValueEnum<'ctx>>,
Vec<CopyBack<'ctx, 'd>>,
),
LoweringError,
> {
let mangled = crate::func::mangled_subroutine_name(&callee)?;
let fv = cx.module.get_function(&mangled).ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"class method/constructor `{mangled}` was not lowered into this module -- \
cross-module class method calls are not yet supported (classes plan Task 5 \
scope)"
),
&cx.fn_sym,
)
})?;
build_method_call_with(cx, fv, callee, h, call)
}
fn build_method_call_with<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
fv: FunctionValue<'ctx>,
args_callee: sv_lang::Symbol<'d>,
h: IntValue<'ctx>,
call: sv_lang::Expression<'d>,
) -> Result<
(
FunctionValue<'ctx>,
Vec<BasicMetadataValueEnum<'ctx>>,
Vec<CopyBack<'ctx, 'd>>,
),
LoweringError,
> {
let (mut llvm_args, copy_backs) = crate::func::lower_call_args(cx, &args_callee, call)?;
let candidate_hidden = HIDDEN_SHADOWS.with(|m| {
m.borrow()
.get(fv.get_name().to_string_lossy().as_ref())
.cloned()
});
if candidate_hidden.is_some() {
let base = crate::func::subroutine_abi(&args_callee);
if !base.needs_handle {
let keep = llvm_args.len().saturating_sub(base.signals.len());
llvm_args.truncate(keep);
}
}
llvm_args.insert(0, h.into());
let wants_handle = candidate_hidden
.as_ref()
.is_some_and(|hidden| llvm_args.len() + hidden.len() + 1 == fv.count_params() as usize)
&& cx.mem_accessor.is_some();
if llvm_args.len() < fv.count_params() as usize
&& let Some(hidden) = candidate_hidden
&& llvm_args.len() + hidden.len() + usize::from(wants_handle) == fv.count_params() as usize
&& hidden
.iter()
.all(|(id, w)| w.is_some() || cx.locals.contains_key(id))
{
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
for (id, w) in hidden {
if let Some(&(ptr, _, _)) = cx.locals.get(&id) {
llvm_args.push(ptr.into());
} else if let Some(w) = w {
let ty = op.packed_type(w, cx.repr);
let scratch = entry_alloca(cx, ty, "static_formal_scratch");
cx.b.build_store(scratch, ty.const_zero()).unwrap();
llvm_args.push(scratch.into());
}
}
if wants_handle && let Some(accessor) = cx.mem_accessor {
llvm_args.push(accessor.handle.into());
}
}
if llvm_args.len() != fv.count_params() as usize {
return Err(LoweringError::unsupported_at(
&format!(
"class method/constructor call argument count ({}) does not match the callee's \
declared parameter count ({}, with the implicit receiver) (callee `{}`, \
implementation `{}` of type {})",
llvm_args.len(),
fv.count_params(),
args_callee.name(),
fv.get_name().to_string_lossy(),
fv.get_type().print_to_string().to_string_lossy()
),
&cx.fn_sym,
));
}
Ok((fv, llvm_args, copy_backs))
}
type VirtualImplKey = (SymbolId, String);
type VirtualImplMap = HashMap<VirtualImplKey, Vec<(ClassTemplateId, String)>>;
thread_local! {
static VIRTUAL_IMPLS: RefCell<VirtualImplMap> = RefCell::new(HashMap::new());
}
pub fn register_virtual_impls<'d>(
classes: &[(ClassTemplateId, sv_lang::Symbol<'d>)],
) -> Result<(), LoweringError> {
let mut map: VirtualImplMap = HashMap::new();
let mut impls: Vec<(Vec<SymbolId>, sv_lang::Symbol<'d>)> = Vec::new();
for &(id, class_sym) in classes {
let layout = VtableLayout::for_template(class_sym)?;
for (slot, impl_sym) in layout.slots.iter().enumerate() {
if let Some(impl_sym) = impl_sym {
let impl_sym = if impl_sym.kind() == SymbolKind::MethodPrototype {
impl_sym.method_prototype_subroutine().unwrap_or(*impl_sym)
} else {
*impl_sym
};
let uids = layout
.by_uid
.iter()
.filter(|&(_, &s)| s == slot)
.map(|(uid, _)| uid.clone())
.collect();
impls.push((uids, impl_sym));
}
}
for (uid, &slot) in &layout.by_uid {
let Some(impl_sym) = layout.slots[slot] else {
continue;
};
let declared_in = layout.declared_in.get(uid).ok_or_else(|| {
LoweringError::unsupported_at(
"internal: a vtable-slot member has no recorded declaring class",
&class_sym,
)
})?;
let mangled = crate::func::mangled_subroutine_name(&impl_sym)?;
map.entry((uid.clone(), declared_in.clone()))
.or_default()
.push((id, mangled));
}
}
VIRTUAL_IMPLS.with(|c| *c.borrow_mut() = map);
sv_sim_sim::process::cfg::reset_aborting_virtuals();
let hooks = crate::classes_randomize::register_hook_impls(classes)?;
loop {
let mut changed = false;
if !sv_sim_sim::process::cfg::randomize_can_abort()
&& hooks
.iter()
.any(|h| sv_sim_sim::process::cfg::subroutine_can_abort(h))
{
changed |= sv_sim_sim::process::cfg::set_randomize_can_abort();
}
for (uids, impl_sym) in &impls {
if sv_sim_sim::process::cfg::subroutine_can_abort(impl_sym) {
for uid in uids {
changed |= sv_sim_sim::process::cfg::mark_aborting_virtual(uid.clone());
}
}
}
if !changed {
break;
}
}
crate::classes_randomize::settle_hook_aborts(&hooks);
Ok(())
}
fn virtual_candidates(
callee: &sv_lang::Symbol<'_>,
) -> Result<Vec<(ClassTemplateId, String)>, LoweringError> {
let class_sym = callee.parent().ok_or_else(|| {
LoweringError::unsupported_at("virtual method has no enclosing class", callee)
})?;
let key = (callee.uid(), specialization_key_of_symbol(&class_sym)?);
Ok(VIRTUAL_IMPLS.with(|c| c.borrow().get(&key).cloned().unwrap_or_default()))
}
pub(crate) fn dynamic_template_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: IntValue<'ctx>,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
obj_call(
cx,
ObjOp::DynamicTemplate,
h,
i64t.const_zero(),
i64t.const_zero(),
null,
)
}
#[allow(clippy::type_complexity)]
fn virtual_call_arms<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
callee: sv_lang::Symbol<'d>,
h: IntValue<'ctx>,
) -> Result<
Option<(
Vec<(FunctionValue<'ctx>, inkwell::basic_block::BasicBlock<'ctx>)>,
inkwell::basic_block::BasicBlock<'ctx>,
)>,
LoweringError,
> {
if !is_virtual_method(&callee)? {
return Ok(None);
}
let candidates = virtual_candidates(&callee)?;
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let merge = cx.ctx.append_basic_block(f, "virtual_call_done");
let dyn_id = dynamic_template_call(cx, h);
let mut arms: Vec<(FunctionValue<'ctx>, inkwell::basic_block::BasicBlock<'ctx>)> = Vec::new();
let mut impl_arm: HashMap<String, usize> = HashMap::new();
let mut cases = Vec::new();
for (id, mangled) in candidates {
let k = match impl_arm.get(&mangled) {
Some(&k) => k,
None => {
let fv = cx.module.get_function(&mangled).ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"internal: virtual method implementation `{mangled}` was not \
lowered into this module -- pipeline::build_pipeline's own class \
sub-pass must declare every class template \
register_virtual_impls walks before any call site is lowered"
),
&cx.fn_sym,
)
})?;
let arm = cx.ctx.append_basic_block(f, "virtual_call");
arms.push((fv, arm));
impl_arm.insert(mangled, arms.len() - 1);
arms.len() - 1
}
};
cases.push((
cx.ctx.i64_type().const_int(u64::from(id.0), false),
arms[k].1,
));
}
cx.b.build_switch(dyn_id, merge, &cases).unwrap();
Ok(Some((arms, merge)))
}
thread_local! {
static DESIGN_SYNTAX_TREES: RefCell<Vec<sv_lang::SyntaxTree>> =
const { RefCell::new(Vec::new()) };
}
pub(crate) fn with_design_trees<R>(f: impl FnOnce(&[sv_lang::SyntaxTree]) -> R) -> R {
DESIGN_SYNTAX_TREES.with(|t| f(&t.borrow()))
}
pub struct DesignSyntaxScope(
Option<(
Vec<sv_lang::SyntaxTree>,
Option<sv_sim_sim::sva::sampled::InferredDisableResolver>,
)>,
);
impl DesignSyntaxScope {
pub fn enter(design: &sv_lang::Design) -> Self {
let outer_trees = DESIGN_SYNTAX_TREES
.with(|t| std::mem::replace(&mut *t.borrow_mut(), design.syntax_trees()));
let outer_resolver = sv_sim_sim::sva::sampled::set_inferred_disable_resolver(Some(
crate::sva::emit::resolve_checker_inferred_disable,
));
DesignSyntaxScope(Some((outer_trees, outer_resolver)))
}
}
impl Drop for DesignSyntaxScope {
fn drop(&mut self) {
let (trees, resolver) = self.0.take().unwrap_or_default();
sv_sim_sim::sva::sampled::set_inferred_disable_resolver(resolver);
DESIGN_SYNTAX_TREES.with(|t| *t.borrow_mut() = trees);
}
}
pub fn call_is_explicit_super(call: sv_lang::Expression<'_>) -> bool {
use sv_lang::AstNode;
use sv_lang::kinds::SyntaxKind;
use sv_lang::nodes::{InvocationExpressionSyntax, ScopedNameSyntax};
DESIGN_SYNTAX_TREES.with(|trees| {
for tree in trees.borrow().iter() {
let Some(node) = call.syntax(tree) else {
continue;
};
let callee = match InvocationExpressionSyntax::cast(node) {
Some(inv) => inv.left().syntax(),
None => node,
};
if callee.kind() == SyntaxKind::SuperHandle {
return true;
}
return ScopedNameSyntax::cast(callee)
.is_some_and(|scoped| scoped.left().syntax().kind() == SyntaxKind::SuperHandle);
}
false
})
}
fn callee_class_never_instantiated(
cx: &LowerCtx<'_, '_, '_>,
callee: &sv_lang::Symbol<'_>,
) -> bool {
let Ok(mangled) = crate::func::mangled_subroutine_name(callee) else {
return false;
};
cx.module.get_function(&mangled).is_none()
&& callee
.parent()
.is_some_and(|p| class_template_id_of(&p).is_err())
}
pub fn lower_method_task_call<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
recv: Option<sv_lang::Expression<'d>>,
call: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let callee = call.call_subroutine().ok_or_else(|| {
LoweringError::unsupported_at("method call missing its callee symbol", &cx.fn_sym)
})?;
reject_if_std_intrinsic(cx, &callee)?;
if recv.is_some()
&& let Some((fv, args, copy_backs)) = static_method_call_via_handle(cx, callee, call)?
{
cx.b.build_call(fv, &args, "static_method_task_call")
.unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
crate::func::copy_back_outputs(cx, copy_backs)?;
return Ok(());
}
let h = match recv {
Some(recv) => receiver_handle(cx, recv)?,
None => current_this_handle(cx)?,
};
let is_super = recv.is_none() && call_is_explicit_super(call);
let null_done = begin_null_receiver_guard(cx, recv, h, call, &callee)?;
hold_receiver(cx, recv, h);
if !is_super && let Some((arms, merge)) = virtual_call_arms(cx, callee, h)? {
for (fv, bb) in arms {
cx.b.position_at_end(bb);
let (fv, llvm_args, copy_backs) = build_method_call_with(cx, fv, callee, h, call)?;
cx.b.build_call(fv, &llvm_args, "virtual_method_task_call")
.unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
crate::func::copy_back_outputs(cx, copy_backs)?;
cx.b.build_unconditional_branch(merge).unwrap();
}
cx.b.position_at_end(merge);
unhold_receiver(cx, recv, h);
end_null_receiver_guard(cx, null_done);
return Ok(());
}
if callee_class_never_instantiated(cx, &callee) {
unhold_receiver(cx, recv, h);
end_null_receiver_guard(cx, null_done);
return Ok(());
}
let (fv, llvm_args, copy_backs) = build_method_call(cx, callee, h, call)?;
cx.b.build_call(fv, &llvm_args, "method_task_call").unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
crate::func::copy_back_outputs(cx, copy_backs)?;
unhold_receiver(cx, recv, h);
end_null_receiver_guard(cx, null_done);
Ok(())
}
fn begin_null_receiver_guard<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
recv: Option<sv_lang::Expression<'d>>,
h: IntValue<'ctx>,
call: sv_lang::Expression<'d>,
callee: &sv_lang::Symbol<'d>,
) -> Result<Option<inkwell::basic_block::BasicBlock<'ctx>>, LoweringError> {
if recv.is_none() {
return Ok(None);
}
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let null =
cx.b.build_int_compare(
IntPredicate::EQ,
h,
cx.ctx.i64_type().const_zero(),
"method_recv_null",
)
.unwrap();
crate::stmt_shared::error_if(
cx,
null,
call.source_range().start,
&format!("null object handle: call of {}", callee.name()),
)?;
let live = cx.ctx.append_basic_block(f, "method_recv_live");
let done = cx.ctx.append_basic_block(f, "method_recv_done");
cx.b.build_conditional_branch(null, done, live).unwrap();
cx.b.position_at_end(live);
Ok(Some(done))
}
fn end_null_receiver_guard<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
done: Option<inkwell::basic_block::BasicBlock<'ctx>>,
) {
if let Some(done) = done {
cx.b.build_unconditional_branch(done).unwrap();
cx.b.position_at_end(done);
}
}
fn hold_receiver<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
recv: Option<sv_lang::Expression<'d>>,
h: IntValue<'ctx>,
) {
if recv.is_some() {
retain_handle(cx, h);
}
}
fn unhold_receiver<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
recv: Option<sv_lang::Expression<'d>>,
h: IntValue<'ctx>,
) {
if recv.is_some() {
release_handle(cx, h);
}
}
type StaticViaHandle<'ctx, 'd> = (
FunctionValue<'ctx>,
Vec<BasicMetadataValueEnum<'ctx>>,
Vec<CopyBack<'ctx, 'd>>,
);
fn static_method_call_via_handle<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
callee: sv_lang::Symbol<'d>,
call: sv_lang::Expression<'d>,
) -> Result<Option<StaticViaHandle<'ctx, 'd>>, LoweringError> {
let Some(fv) = resolve_or_lower_static_method(cx, callee)? else {
return Ok(None);
};
let (args, copy_backs) = crate::func::lower_call_args(cx, &callee, call)?;
Ok(Some((fv, args, copy_backs)))
}
pub fn lower_method_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
recv: Option<sv_lang::Expression<'d>>,
call: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let callee = call.call_subroutine().ok_or_else(|| {
LoweringError::unsupported_at("method call missing its callee symbol", &cx.fn_sym)
})?;
reject_if_std_intrinsic(cx, &callee)?;
let ret_sv = callee.subroutine_return_type();
if ret_sv.as_ref().is_none_or(sv_lang::Type::is_void) {
return Err(LoweringError::unsupported_at(
"a void method call cannot be used as an expression value",
&cx.fn_sym,
));
}
let ret_sv = ret_sv.unwrap();
if recv.is_some()
&& let Some((fv, args, copy_backs)) = static_method_call_via_handle(cx, callee, call)?
{
let call_site = cx.b.build_call(fv, &args, "static_method_call").unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
crate::func::copy_back_outputs(cx, copy_backs)?;
let ret_v = call_site.try_as_basic_value().basic().ok_or_else(|| {
LoweringError::unsupported_at(
"method call produced no return value despite a non-void SV return type",
&cx.fn_sym,
)
})?;
if ret_sv.is_floating() {
let f = crate::real::as_f64(cx, ret_v.into_float_value());
return Ok(LoweredValue::Real(RealValue { llvm: f }));
}
return Ok(LoweredValue::Packed(PackedValue {
llvm: ret_v.into_int_value(),
width: ret_sv.value_width(),
repr: cx.repr,
signed: ret_sv.is_signed(),
}));
}
let h = match recv {
Some(recv) => receiver_handle(cx, recv)?,
None => current_this_handle(cx)?,
};
if recv.is_none() {
return lower_method_call_live(cx, recv, call, callee, h, &ret_sv);
}
let is_real = ret_sv.is_floating();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let int_ty = op.packed_type(ret_sv.value_width(), cx.repr);
let slot = if is_real {
let slot = entry_alloca(cx, cx.ctx.f64_type(), "null_call_ret");
cx.b.build_store(slot, cx.ctx.f64_type().const_zero())
.unwrap();
slot
} else {
let slot = entry_alloca(cx, int_ty, "null_call_ret");
cx.b.build_store(slot, int_ty.const_zero()).unwrap();
slot
};
let done = begin_null_receiver_guard(cx, recv, h, call, &callee)?;
let live = lower_method_call_live(cx, recv, call, callee, h, &ret_sv)?;
match live {
LoweredValue::Real(r) => {
cx.b.build_store(slot, r.llvm).unwrap();
}
LoweredValue::Packed(p) => {
cx.b.build_store(slot, p.llvm).unwrap();
}
LoweredValue::Wide(_) | LoweredValue::Str(_) | LoweredValue::Huge(_) => {
return Err(LoweringError::unsupported_at(
"internal: a class method call lowers to a packed or real value only",
&cx.fn_sym,
));
}
}
end_null_receiver_guard(cx, done);
if is_real {
let f =
cx.b.build_load(cx.ctx.f64_type(), slot, "null_call_ret_ld")
.unwrap()
.into_float_value();
return Ok(LoweredValue::Real(RealValue { llvm: f }));
}
let v =
cx.b.build_load(int_ty, slot, "null_call_ret_ld")
.unwrap()
.into_int_value();
Ok(LoweredValue::Packed(PackedValue {
llvm: v,
width: ret_sv.value_width(),
repr: cx.repr,
signed: ret_sv.is_signed(),
}))
}
fn lower_method_call_live<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
recv: Option<sv_lang::Expression<'d>>,
call: sv_lang::Expression<'d>,
callee: sv_lang::Symbol<'d>,
h: IntValue<'ctx>,
ret_sv: &sv_lang::Type<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let is_super = recv.is_none() && call_is_explicit_super(call);
hold_receiver(cx, recv, h);
if !is_super && is_virtual_method(&callee)? {
let v = lower_virtual_method_call_value(cx, callee, h, call, ret_sv)?;
unhold_receiver(cx, recv, h);
return Ok(v);
}
if callee_class_never_instantiated(cx, &callee) {
unhold_receiver(cx, recv, h);
let zero = if ret_sv.is_floating() {
LoweredValue::Real(RealValue {
llvm: cx.ctx.f64_type().const_zero(),
})
} else {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
LoweredValue::Packed(PackedValue {
llvm: op.packed_type(ret_sv.value_width(), cx.repr).const_zero(),
width: ret_sv.value_width(),
repr: cx.repr,
signed: ret_sv.is_signed(),
})
};
return Ok(zero);
}
let (fv, llvm_args, copy_backs) = build_method_call(cx, callee, h, call)?;
let call_site = cx.b.build_call(fv, &llvm_args, "method_call").unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
crate::func::copy_back_outputs(cx, copy_backs)?;
unhold_receiver(cx, recv, h);
let ret_v = call_site.try_as_basic_value().basic().ok_or_else(|| {
LoweringError::unsupported_at(
"method call produced no return value despite a non-void SV return type",
&cx.fn_sym,
)
})?;
if ret_sv.is_floating() {
let f = crate::real::as_f64(cx, ret_v.into_float_value());
return Ok(LoweredValue::Real(RealValue { llvm: f }));
}
Ok(LoweredValue::Packed(PackedValue {
llvm: ret_v.into_int_value(),
width: ret_sv.value_width(),
repr: cx.repr,
signed: ret_sv.is_signed(),
}))
}
fn lower_virtual_method_call_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
callee: sv_lang::Symbol<'d>,
h: IntValue<'ctx>,
call: sv_lang::Expression<'d>,
ret_sv: &sv_lang::Type<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let is_real = ret_sv.is_floating();
let slot = if is_real {
let slot = entry_alloca(cx, cx.ctx.f64_type(), "virtual_ret");
cx.b.build_store(slot, cx.ctx.f64_type().const_zero())
.unwrap();
slot
} else {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ty = op.packed_type(ret_sv.value_width(), cx.repr);
let slot = entry_alloca(cx, ty, "virtual_ret");
cx.b.build_store(slot, crate::arrays::default_value(cx, ret_sv))
.unwrap();
slot
};
let (arms, merge) = virtual_call_arms(cx, callee, h)?
.expect("caller already confirmed `callee` is virtual before calling this function");
for (fv, bb) in arms {
cx.b.position_at_end(bb);
let (fv, llvm_args, copy_backs) = build_method_call_with(cx, fv, callee, h, call)?;
let call_site =
cx.b.build_call(fv, &llvm_args, "virtual_method_call")
.unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
crate::func::copy_back_outputs(cx, copy_backs)?;
let ret_v = call_site.try_as_basic_value().basic().ok_or_else(|| {
LoweringError::unsupported_at(
"virtual method call produced no return value despite a non-void SV return \
type",
&cx.fn_sym,
)
})?;
cx.b.build_store(slot, ret_v).unwrap();
cx.b.build_unconditional_branch(merge).unwrap();
}
cx.b.position_at_end(merge);
if is_real {
let f =
cx.b.build_load(cx.ctx.f64_type(), slot, "virtual_ret_ld")
.unwrap()
.into_float_value();
return Ok(LoweredValue::Real(RealValue { llvm: f }));
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ty = op.packed_type(ret_sv.value_width(), cx.repr);
let v =
cx.b.build_load(ty, slot, "virtual_ret_ld")
.unwrap()
.into_int_value();
Ok(LoweredValue::Packed(PackedValue {
llvm: v,
width: ret_sv.value_width(),
repr: cx.repr,
signed: ret_sv.is_signed(),
}))
}
struct CtorThisGuard<'ctx, 'a> {
cell: &'a std::cell::Cell<Option<IntValue<'ctx>>>,
prev: Option<IntValue<'ctx>>,
}
impl<'ctx, 'a> CtorThisGuard<'ctx, 'a> {
fn new(cell: &'a std::cell::Cell<Option<IntValue<'ctx>>>, h: IntValue<'ctx>) -> Self {
let prev = cell.replace(Some(h));
Self { cell, prev }
}
}
impl<'ctx, 'a> Drop for CtorThisGuard<'ctx, 'a> {
fn drop(&mut self) {
self.cell.set(self.prev);
}
}
pub fn method_fn_type<'ctx, 'd>(
ctx: &'ctx Context,
method: &sv_lang::Symbol<'d>,
repr: ValueRepr,
) -> Result<(FunctionType<'ctx>, bool), LoweringError> {
let receiver = method.subroutine_this_var().is_some();
if let Ok(name) = crate::func::mangled_subroutine_name(method) {
let hidden: Vec<(SymbolId, Option<u32>)> = crate::func::subroutine_abi(method)
.signals
.iter()
.map(|sig| {
let scratch_width = (sig.kind() == SymbolKind::FormalArgument
&& sig.formal_argument_direction() == sv_lang::ArgumentDirection::In)
.then(|| sig.value_type())
.flatten()
.filter(|t| t.is_integral() && !crate::huge::is_huge_width(t.value_width()))
.map(|t| t.value_width());
(sig.uid(), scratch_width)
})
.collect();
HIDDEN_SHADOWS.with(|m| m.borrow_mut().insert(name, hidden));
}
crate::func::subroutine_fn_type(ctx, method, repr, receiver)
}
pub fn register_method_names<'d>(methods: &[(Symbol<'d>, Symbol<'d>)]) -> MethodRegistryGuard {
let syms: Vec<Symbol<'d>> = methods.iter().map(|(_, m)| *m).collect();
crate::func::build_group_abis(&syms);
MethodRegistryGuard
}
pub struct MethodRegistryGuard;
impl Drop for MethodRegistryGuard {
fn drop(&mut self) {
crate::func::clear_group_abis();
}
}
thread_local! {
static HIDDEN_SHADOWS: RefCell<HashMap<String, HiddenShadowList>> = RefCell::new(HashMap::new());
}
type HiddenShadowList = Vec<(SymbolId, Option<u32>)>;
pub fn lower_method_body<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
class_sym: sv_lang::Symbol<'d>,
method: &sv_lang::Symbol<'d>,
repr: ValueRepr,
f: FunctionValue<'ctx>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let _ = class_sym;
let this_var = method.subroutine_this_var();
crate::func::lower_function_body(ctx, module, method, repr, f, this_var)
}
pub(crate) fn resolve_or_lower_static_method<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
callee: sv_lang::Symbol<'d>,
) -> Result<Option<FunctionValue<'ctx>>, LoweringError> {
let callee = if callee.kind() == SymbolKind::MethodPrototype {
match callee.method_prototype_subroutine() {
Some(real) => real,
None => return Ok(None),
}
} else {
callee
};
let Some(class_sym) = callee.parent() else {
return Ok(None);
};
if !matches!(
class_sym.kind(),
SymbolKind::ClassType | SymbolKind::GenericClassDef
) {
return Ok(None);
}
if is_builtin_std_class(&class_sym).is_some() {
return Ok(None);
}
if callee.subroutine_this_var().is_some() {
return Ok(None);
}
if is_virtual_method(&callee)? {
return Ok(None);
}
let mangled = crate::func::mangled_subroutine_name(&callee)?;
if let Some(f) = cx.module.get_function(&mangled) {
return Ok(Some(f));
}
let (fn_ty, _is_void) = method_fn_type(cx.ctx, &callee, cx.repr)?;
let f = cx.module.add_function(&mangled, fn_ty, None);
lower_method_body(cx.ctx, cx.module, class_sym, &callee, cx.repr, f)?;
Ok(Some(f))
}
fn store_default_fields<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
class_ty: sv_lang::Type<'d>,
) -> Result<(), LoweringError> {
let mut cur = Some(class_ty);
while let Some(ty) = cur {
let class_sym = ty.as_symbol();
let layout = ObjectLayout::for_template(class_sym)?;
let members = sv_sim_sim::netlist::classify_members(class_sym)
.map_err(|e| elab_to_lowering(e, &class_sym))?;
for f_sym in members.fields {
if layout.is_alias(&f_sym) || f_sym.initializer().is_some() {
continue;
}
let Some(slot) = layout.slot_of(&f_sym) else {
continue;
};
let fty = f_sym.value_type().ok_or_else(|| {
LoweringError::unsupported_at("class property has no type", &f_sym)
})?;
if fty.is_event() {
continue;
}
let value = LoweredValue::Packed(PackedValue {
llvm: crate::arrays::default_value(cx, &fty),
width: fty.value_width(),
repr: cx.repr,
signed: fty.is_signed(),
});
store_field(cx, h, slot, &fty, value, false)?;
}
cur = ty.class_base();
}
Ok(())
}
fn run_own_field_inits<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
class_sym: sv_lang::Symbol<'d>,
) -> Result<(), LoweringError> {
let layout = ObjectLayout::for_template(class_sym)?;
let members = sv_sim_sim::netlist::classify_members(class_sym)
.map_err(|e| elab_to_lowering(e, &class_sym))?;
for f_sym in members.fields {
if layout.is_alias(&f_sym) {
continue;
}
let slot = layout.slot_of(&f_sym).ok_or_else(|| {
LoweringError::unsupported_at(
"internal: own field missing from its own class's layout",
&f_sym,
)
})?;
let fty = f_sym
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("class property has no type", &f_sym))?;
if fty.is_event() {
let id = match f_sym.initializer() {
Some(init) => {
let _guard = CtorThisGuard::new(&cx.ctor_this, h);
crate::class_events::id_of(cx, init)?
}
None => crate::class_events::new_event(cx),
};
let o = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v = o.pack(id, o.i_n(64).const_zero(), 64, cx.repr, false);
let _live = field_set_call(cx, h, slot, v);
continue;
}
if f_sym.initializer().is_none() {
continue;
}
let value = match f_sym.initializer() {
Some(init) => {
let _guard = CtorThisGuard::new(&cx.ctor_this, h);
crate::collections::owned(cx, init)?
}
None => LoweredValue::Packed(PackedValue {
llvm: crate::arrays::default_value(cx, &fty),
width: fty.value_width(),
repr: cx.repr,
signed: fty.is_signed(),
}),
};
store_field(cx, h, slot, &fty, value, false)?;
}
Ok(())
}
fn embeds_super_new(ty: sv_lang::Type<'_>) -> bool {
matches!(
ty.class_base_constructor_call(),
Some(e) if e.kind() == ExpressionKind::NewClass
) || implicit_default_super(ty)
}
fn implicit_default_super(ty: sv_lang::Type<'_>) -> bool {
ty.class_base().is_some()
&& ty.class_constructor().is_some_and(|c| {
c.subroutine_flags()
.contains(sv_lang::MethodFlags::DEFAULTED_SUPER_ARG)
})
&& ty.class_base_constructor_call().is_none()
}
pub(crate) fn emit_implicit_default_super<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
) -> Result<(), LoweringError> {
let Some(cur_sym) = cx.fn_sym.parent() else {
return Ok(());
};
if cx.fn_sym.kind() != SymbolKind::Subroutine
|| !cx
.fn_sym
.subroutine_flags()
.contains(sv_lang::MethodFlags::CONSTRUCTOR)
{
return Ok(());
}
let Some(cur_ty) = cur_sym.as_type() else {
return Ok(());
};
if !implicit_default_super(cur_ty) {
return Ok(());
}
let h = current_this_handle(cx)?;
let base_ty = cur_ty
.class_base()
.expect("checked by implicit_default_super");
if !embeds_super_new(base_ty) {
chain_to_base(cx, h, base_ty)?;
run_own_field_inits(cx, h, base_ty.as_symbol())?;
}
if let Some(base_ctor) = base_ty.class_constructor() {
call_ctor_level(cx, h, base_ctor, None)?;
}
run_own_field_inits(cx, h, cur_sym)
}
fn is_synth_default_ctor(ctor: &sv_lang::Symbol<'_>) -> bool {
ctor.subroutine_this_var().is_none()
&& ctor
.subroutine_flags()
.contains(sv_lang::MethodFlags::DEFAULTED_SUPER_ARG)
}
fn call_ctor_level<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
ctor: sv_lang::Symbol<'d>,
call: Option<sv_lang::Expression<'d>>,
) -> Result<(), LoweringError> {
if is_synth_default_ctor(&ctor) {
let cls = ctor.parent().ok_or_else(|| {
LoweringError::unsupported_at("a generated constructor with no class", &ctor)
})?;
let ty = cls
.as_type()
.ok_or_else(|| LoweringError::unsupported_at("a constructor outside a class", &ctor))?;
if let Some(base_ty) = ty.class_base() {
if !embeds_super_new(base_ty) {
chain_to_base(cx, h, base_ty)?;
run_own_field_inits(cx, h, base_ty.as_symbol())?;
}
if let Some(base_ctor) = base_ty.class_constructor() {
call_ctor_level(cx, h, base_ctor, call)?;
}
}
return run_own_field_inits(cx, h, cls);
}
match call {
Some(call) => {
let (fv, llvm_args, copy_backs) = build_method_call(cx, ctor, h, call)?;
cx.b.build_call(fv, &llvm_args, "ctor_call").unwrap();
leave_if_ctor_aborted(cx, &ctor)?;
crate::func::copy_back_outputs(cx, copy_backs)
}
None => forward_to_real_ctor(cx, h, ctor),
}
}
fn forward_to_real_ctor<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
base_ctor: sv_lang::Symbol<'d>,
) -> Result<(), LoweringError> {
let derived: Vec<sv_lang::Symbol<'d>> = cx.fn_sym.subroutine_arguments().collect();
let base: Vec<sv_lang::Symbol<'d>> = base_ctor.subroutine_arguments().collect();
let start = (0..=derived.len().saturating_sub(base.len()))
.find(|&s| {
derived.len() >= base.len()
&& base
.iter()
.enumerate()
.all(|(i, b)| derived[s + i].name() == b.name())
})
.ok_or_else(|| {
LoweringError::unsupported_at(
"internal: the inherited `default` constructor arguments are not a run of \
the derived constructor's formals",
&cx.fn_sym,
)
})?;
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.ok_or_else(|| LoweringError::unsupported_at("no current function", &cx.fn_sym))?;
let mut llvm_args: Vec<BasicMetadataValueEnum<'ctx>> = vec![h.into()];
for (i, formal) in base.iter().enumerate() {
let t = formal
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("formal has no type", formal))?;
let param = f.get_nth_param(1 + (start + i) as u32).ok_or_else(|| {
LoweringError::unsupported_at("internal: missing constructor parameter", &cx.fn_sym)
})?;
if let Some(shape) = crate::arrays::array_shape(&t) {
if crate::func::is_by_reference(formal) {
llvm_args.push(param.into());
continue;
}
let ty = crate::arrays::local_array_type(cx.ctx, &shape, cx.repr);
let copy = crate::expr::entry_alloca(cx, ty, "default_fwd_array");
let size = inkwell::types::BasicType::size_of(&ty).expect("a sized array type");
cx.b.build_memcpy(copy, 8, param.into_pointer_value(), 8, size)
.unwrap();
llvm_args.push(copy.into());
continue;
}
if crate::func::is_by_reference(formal) || t.is_floating() {
llvm_args.push(param.into());
continue;
}
let pv = PackedValue {
llvm: param.into_int_value(),
width: t.value_width(),
repr: cx.repr,
signed: t.is_signed(),
};
llvm_args.push(crate::collections::retain_stored(cx, &t, pv).llvm.into());
}
let mangled = crate::func::mangled_subroutine_name(&base_ctor)?;
let fv = cx.module.get_function(&mangled).ok_or_else(|| {
LoweringError::unsupported_at(
&format!("class constructor `{mangled}` was not lowered into this module"),
&cx.fn_sym,
)
})?;
crate::func::append_hidden_args(cx, &base_ctor, &mut llvm_args)?;
if llvm_args.len() != fv.count_params() as usize {
return Err(LoweringError::unsupported_at(
"internal: a constructor `default` forward passes the wrong number of arguments",
&cx.fn_sym,
));
}
cx.b.build_call(fv, &llvm_args, "default_fwd_ctor_call")
.unwrap();
leave_if_ctor_aborted(cx, &base_ctor)
}
fn lower_default_ctor_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
ctor: sv_lang::Symbol<'d>,
) -> Result<(), LoweringError> {
let formals: Vec<sv_lang::Symbol<'d>> = ctor.subroutine_arguments().collect();
let mut llvm_args: Vec<BasicMetadataValueEnum<'ctx>> = vec![h.into()];
for formal in &formals {
let default = formal.formal_argument_default_value().ok_or_else(|| {
LoweringError::unsupported_at(
"an implicit base-constructor call (no explicit `super.new(...)`/`extends \
Base(args)`) needs every base-constructor formal to have a default value -- \
slang's own frontend already guarantees this whenever it leaves \
`Type::class_base_constructor_call()` unset with a base class present, so a \
missing default here is an internal inconsistency, not a reachable SV-source \
failure",
&cx.fn_sym,
)
})?;
let width = formal.value_type().map(|t| t.value_width()).unwrap_or(0);
let pv = crate::real::to_storage(
cx,
lower_expr(cx, default)?,
width,
"implicit base-constructor default argument",
)?;
llvm_args.push(pv.llvm.into());
}
let mangled = crate::func::mangled_subroutine_name(&ctor)?;
let fv = cx.module.get_function(&mangled).ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"class method/constructor `{mangled}` was not lowered into this module -- \
cross-module class method calls are not yet supported (classes plan Task 5 \
scope)"
),
&cx.fn_sym,
)
})?;
crate::func::append_hidden_args(cx, &ctor, &mut llvm_args)?;
if llvm_args.len() != fv.count_params() as usize {
return Err(LoweringError::unsupported_at(
&format!(
"implicit base-constructor call argument count {} does not match the callee's \
declared parameter count {} (plus the implicit receiver) {mangled}",
llvm_args.len(),
fv.count_params()
),
&cx.fn_sym,
));
}
cx.b.build_call(fv, &llvm_args, "base_ctor_default_call")
.unwrap();
leave_if_ctor_aborted(cx, &ctor)
}
fn leave_if_ctor_aborted(
cx: &LowerCtx<'_, '_, '_>,
ctor: &sv_lang::Symbol<'_>,
) -> Result<(), LoweringError> {
if sv_sim_sim::process::cfg::subroutine_can_abort(ctor) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
Ok(())
}
fn chain_to_base<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
h: IntValue<'ctx>,
ty: sv_lang::Type<'d>,
) -> Result<(), LoweringError> {
let Some(base_ty) = ty.class_base() else {
return Ok(());
};
let base_sym = base_ty.as_symbol();
let base_embedded_super = embeds_super_new(base_ty);
if !base_embedded_super {
chain_to_base(cx, h, base_ty)?;
run_own_field_inits(cx, h, base_sym)?;
}
match ty.class_base_constructor_call() {
Some(base_expr) => {
if base_expr.kind() != ExpressionKind::Call {
return Err(LoweringError::unsupported_at(
"internal: a base-constructor-call expression reached `chain_to_base` with \
an unexpected kind -- only an `extends Base(args)`-synthesized `Call` is \
possible once `lower_new_class`'s own `embedded_super` check has already \
filtered out the explicit `super.new(...)`-statement (`NewClass`) case",
&cx.fn_sym,
));
}
let ctor = base_expr.call_subroutine().ok_or_else(|| {
LoweringError::unsupported_at(
"base-constructor-call expression missing its callee symbol",
&cx.fn_sym,
)
})?;
call_ctor_level(cx, h, ctor, Some(base_expr))
}
None => {
let Some(ctor) = base_ty.class_constructor() else {
return Ok(());
};
lower_default_ctor_call(cx, h, ctor)
}
}
}
pub fn lower_new_class<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let is_super = e.new_class_is_super_class() == Some(true);
let mut cur_class_sym: Option<sv_lang::Symbol<'d>> = None;
let (class_sym, ty) = if is_super {
let sym = cx.fn_sym.parent().ok_or_else(|| {
LoweringError::unsupported_at(
"`super.new(...)` reached outside any class method/constructor body",
&cx.fn_sym,
)
})?;
cur_class_sym = Some(sym);
let cur_ty = sym.as_type().ok_or_else(|| {
LoweringError::unsupported_at(
"`super.new(...)`'s enclosing scope is not a class",
&cx.fn_sym,
)
})?;
let base_ty = cur_ty.class_base().ok_or_else(|| {
LoweringError::unsupported_at(
"`super.new(...)` used in a class with no base class",
&cx.fn_sym,
)
})?;
(base_ty.as_symbol(), base_ty)
} else {
let ty = e
.expr_type()
.ok_or_else(|| {
LoweringError::unsupported_at("`new` expression has no class type", &cx.fn_sym)
})?
.canonical();
if !ty.is_class() {
return Err(LoweringError::unsupported_at(
"a `new` expression whose type is not a class (`new[]`/`new <covergroup>` are \
a separate ExpressionKind, not this arm's concern)",
&cx.fn_sym,
));
}
(ty.as_symbol(), ty)
};
if let Some(intrinsic) = is_builtin_std_class(&class_sym) {
if intrinsic == StdIntrinsic::WeakReference {
return lower_new_weakref(cx, e, &class_sym);
}
if intrinsic == StdIntrinsic::Semaphore {
return lower_new_semaphore(cx, e, &class_sym);
}
if intrinsic == StdIntrinsic::Mailbox {
return lower_new_mailbox(cx, e, &class_sym);
}
return Err(LoweringError::unsupported_at(
&format!("`new()` on the std-package intrinsic {intrinsic:?} is not supported"),
&cx.fn_sym,
));
}
let h = if is_super {
current_this_handle(cx)?
} else {
let id = class_template_id_of(&class_sym)?;
let layout = ObjectLayout::for_template(class_sym)?;
let h = alloc_call(cx, id, layout.total_words());
store_default_fields(cx, h, ty)?;
crate::classes_randomize::ensure_static_owners(cx, class_sym, id)?;
h
};
let embedded_super = embeds_super_new(ty);
if !embedded_super {
chain_to_base(cx, h, ty)?;
run_own_field_inits(cx, h, class_sym)?;
}
if let Some(ctor_call) = e.new_class_constructor_call() {
let ctor = ctor_call.call_subroutine().ok_or_else(|| {
LoweringError::unsupported_at(
"`new(...)` constructor call missing its callee symbol",
&cx.fn_sym,
)
})?;
call_ctor_level(cx, h, ctor, Some(ctor_call))?;
} else if is_super
&& cx
.fn_sym
.subroutine_flags()
.contains(sv_lang::MethodFlags::DEFAULTED_SUPER_ARG)
&& let Some(base_ctor) = ty.class_constructor()
{
call_ctor_level(cx, h, base_ctor, None)?;
}
if let Some(cur_class_sym) = cur_class_sym {
run_own_field_inits(cx, h, cur_class_sym)?;
}
Ok(LoweredValue::Packed(handle_as_packed(cx, h)))
}
pub fn lower_copy_class<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let src = e.copy_class_source().ok_or_else(|| {
LoweringError::unsupported_at("a `new` copy expression with no source", &cx.fn_sym)
})?;
let h = receiver_handle(cx, src)?;
let i64t = cx.ctx.i64_type();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
let new = obj_call(
cx,
ObjOp::Copy,
h,
i64t.const_zero(),
i64t.const_zero(),
null,
);
Ok(LoweredValue::Packed(handle_as_packed(cx, new)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::compile_source;
#[test]
fn pure_virtual_stub_dot_call_is_still_recognized_as_virtual() {
let design = compile_source(
"virtual class shape;\n\
pure virtual function int area();\n\
endclass\n\
class square extends shape;\n\
function int area(); return 1; endfunction\n\
endclass\n\
module top;\n\
function void show(shape s);\n\
$display(\"%0d\", s.area());\n\
endfunction\n\
endmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let top = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let show = top.members().find(|m| m.name() == "show").unwrap();
let body = show.body().unwrap();
fn find_call<'d>(node: sv_lang::SemNode<'d>) -> Option<sv_lang::Symbol<'d>> {
if let Some(e) = node.as_expression()
&& e.kind() == ExpressionKind::Call
&& let Some(callee) = e.call_subroutine()
{
return Some(callee);
}
node.children().into_iter().find_map(find_call)
}
let callee = find_call(body.as_sem_node()).expect("s.area() must be findable");
assert_eq!(callee.kind(), SymbolKind::Subroutine);
assert!(
callee.subroutine_this_var().is_none(),
"this test's own premise: the pure-virtual stub's this_var must be None"
);
assert!(
is_virtual_method(&callee).unwrap(),
"a pure-virtual stub's dot-call callee must still be recognized as virtual"
);
}
#[test]
fn bare_call_to_a_still_pure_virtual_method_is_recognized_as_an_instance_method() {
let design = compile_source(
"virtual class shape;\n\
pure virtual function int area();\n\
function int double_area();\n\
return 2*area();\n\
endfunction\n\
endclass\n\
class square extends shape;\n\
function int area(); return 1; endfunction\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let shape = unit.find("shape").unwrap();
let double_area = shape.members().find(|m| m.name() == "double_area").unwrap();
fn find_call<'d>(node: sv_lang::SemNode<'d>) -> Option<sv_lang::Expression<'d>> {
if let Some(e) = node.as_expression()
&& e.kind() == ExpressionKind::Call
{
return Some(e);
}
node.children().into_iter().find_map(find_call)
}
let call = find_call(double_area.body().unwrap().as_sem_node())
.expect("2*area() must contain a findable area() call");
let callee = call
.call_subroutine()
.expect("bare area() call must resolve to a callee symbol");
assert!(
resolve_implicit_this_instance_method(callee)
.unwrap_or_else(|e| panic!("resolve_implicit_this_instance_method failed: {e}"))
.is_some(),
"a bare call to a still-pure-virtual method, from a sibling method of its own \
declaring class, must be recognized as an instance-method call"
);
}
#[test]
fn new_on_an_abstract_class_is_rejected_by_the_frontend() {
let design = compile_source(
"virtual class shape;\n\
pure virtual function int area();\n\
endclass\n\
module top;\n\
shape s;\n\
initial s = new;\n\
endmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
assert!(
design.diagnostics().has_errors(),
"`new` on an abstract class must be a compile-time diagnostic, not a clean compile"
);
}
#[test]
fn explicit_super_call_is_detected_only_for_an_actual_super_prefix() {
let design = compile_source(
"class base;\n\
virtual function int f(); return 1; endfunction\n\
endclass\n\
class derived extends base;\n\
function int g();\n\
return super.f();\n\
endfunction\n\
function int h();\n\
return f();\n\
endfunction\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let _scope = DesignSyntaxScope::enter(&design);
let unit = design.compilation_units().next().unwrap();
let derived = unit.find("derived").unwrap();
let g = derived.members().find(|m| m.name() == "g").unwrap();
let h = derived.members().find(|m| m.name() == "h").unwrap();
let g_call = g.body().unwrap().expressions().into_iter().next().unwrap();
let h_call = h.body().unwrap().expressions().into_iter().next().unwrap();
assert_eq!(
g_call.call_subroutine().unwrap().uid(),
h_call.call_subroutine().unwrap().uid(),
"this test's own premise: both calls must resolve to the identical base::f symbol"
);
assert!(
call_is_explicit_super(g_call),
"super.f() must be detected as an explicit super call"
);
assert!(
!call_is_explicit_super(h_call),
"a bare f() must NOT be detected as an explicit super call, even though it \
resolves to the identical callee symbol"
);
}
#[test]
fn std_process_semaphore_mailbox_weak_reference_are_recognized() {
let design = compile_source("module m;\nendmodule\n")
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let std_pkg = design.std_package();
assert_eq!(std_pkg.name(), "std");
for (name, expect) in [
("process", StdIntrinsic::Process),
("semaphore", StdIntrinsic::Semaphore),
("mailbox", StdIntrinsic::Mailbox),
("weak_reference", StdIntrinsic::WeakReference),
] {
let sym = std_pkg
.members()
.find(|m| m.name() == name)
.unwrap_or_else(|| panic!("std package must declare {name}"));
assert_eq!(
is_builtin_std_class(&sym),
Some(expect),
"mismatch for {name}"
);
}
}
#[test]
fn a_user_class_of_the_same_name_outside_std_is_not_recognized() {
let design = compile_source(
"package p;\n\
class mailbox;\n\
endclass\n\
endpackage\n\
module m;\n\
import p::mailbox;\n\
mailbox mb;\n\
endmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
assert!(
!design.diagnostics().has_errors(),
"the shadowing-free user `mailbox` fixture must compile cleanly: {}",
design.diagnostics()
);
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let sym = body.find("mb").unwrap().value_type().unwrap().as_symbol();
assert_eq!(sym.name(), "mailbox");
assert_eq!(
is_builtin_std_class(&sym),
None,
"a user class named mailbox, outside the std package, must not be mistaken for \
the built-in generic"
);
}
#[test]
fn a_non_class_symbol_is_never_recognized() {
let design = compile_source("module m;\nint x;\nendmodule\n")
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let x = body.find("x").unwrap();
assert_eq!(x.kind(), SymbolKind::Variable);
assert_eq!(is_builtin_std_class(&x), None);
}
#[test]
fn object_layout_orders_fields_by_declaration() {
let design = compile_source(
"class foo;\n\
int a;\n\
real b;\n\
bit c;\n\
function void m();\n\
endfunction\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let class_sym = unit
.find("foo")
.unwrap_or_else(|| panic!("top-level class `foo` must be findable"));
let layout = ObjectLayout::for_template(class_sym)
.unwrap_or_else(|e| panic!("ObjectLayout::for_template failed: {e}"));
let names: Vec<&str> = layout.fields().iter().map(|f| f.sym.name()).collect();
assert_eq!(
names,
["a", "b", "c"],
"fields must stay in declaration order, never re-sorted"
);
let offsets: Vec<usize> = layout.fields().iter().map(|f| f.slot.offset).collect();
assert_eq!(offsets[0], 0);
for w in 1..offsets.len() {
assert!(
offsets[w] >= offsets[w - 1] + layout.fields()[w - 1].slot.words,
"field {w}'s offset must start no earlier than the previous field's own end"
);
}
assert_eq!(
layout.total_words(),
offsets[2] + layout.fields()[2].slot.words
);
assert!(
layout.handle_field_slots().is_empty(),
"none of a/b/c is a class handle"
);
}
#[test]
fn object_layout_finds_a_class_typed_handle_field() {
let design = compile_source(
"class node;\n\
int v;\n\
node next;\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let class_sym = unit
.find("node")
.unwrap_or_else(|| panic!("top-level class `node` must be findable"));
let layout = ObjectLayout::for_template(class_sym)
.unwrap_or_else(|e| panic!("ObjectLayout::for_template failed: {e}"));
let handle_slots = layout.handle_field_slots();
assert_eq!(
handle_slots.len(),
1,
"exactly one field (`next`) is a class handle"
);
let next_slot = layout
.slot_of(
&unit
.find("node")
.unwrap()
.members()
.find(|m| m.name() == "next")
.unwrap(),
)
.unwrap();
assert_eq!(handle_slots[0], next_slot);
}
fn var_class_sym<'d>(design: &'d sv_lang::Design, var: &str) -> sv_lang::Symbol<'d> {
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let v = body
.members()
.find(|m| m.name() == var)
.unwrap_or_else(|| panic!("variable `{var}` must be a member of the top module"));
v.value_type()
.unwrap_or_else(|| panic!("`{var}` must have a type"))
.as_symbol()
}
#[test]
fn two_specializations_of_one_generic_class_have_distinct_identity() {
let design = compile_source(
"class Box #(type T = int);\n\
T v;\n\
static int n;\n\
virtual function T get(); return v; endfunction\n\
endclass\n\
module top;\n\
Box#(int) a;\n\
Box#(string) b;\n\
Box#(int) c;\n\
endmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let a = var_class_sym(&design, "a");
let b = var_class_sym(&design, "b");
let c = var_class_sym(&design, "c");
let key = |s: &sv_lang::Symbol<'_>| {
sv_sim_sim::netlist::class_specialization_key(s.as_type().unwrap())
};
assert_ne!(
key(&a),
key(&b),
"Box#(int) and Box#(string) are two classes"
);
assert_eq!(
key(&a),
key(&c),
"two uses of the SAME binding are one specialization"
);
reset_class_registries();
register_template(ClassTemplateId(7_100), a).expect("register Box#(int)");
register_template(ClassTemplateId(7_101), b).expect("register Box#(string)");
assert_eq!(class_template_id_of(&a).unwrap(), ClassTemplateId(7_100));
assert_eq!(class_template_id_of(&b).unwrap(), ClassTemplateId(7_101));
assert_eq!(
class_template_id_of(&c).unwrap(),
ClassTemplateId(7_100),
"the second `Box#(int)` use resolves to the first one's id"
);
let a_layout = ObjectLayout::for_template(a).unwrap();
let b_layout = ObjectLayout::for_template(b).unwrap();
assert_eq!(a_layout.fields().len(), 1, "`n` is static, not a field");
assert!(a_layout.fields()[0].sym.value_type().unwrap().is_integral());
assert!(b_layout.fields()[0].sym.value_type().unwrap().is_string());
let a_statics = ObjectLayout::for_statics(a).unwrap();
assert_eq!(
a_statics
.fields()
.iter()
.map(|f| f.sym.name())
.collect::<Vec<_>>(),
["n"]
);
let a_get = a.members().find(|m| m.name() == "get").unwrap();
let b_get = b.members().find(|m| m.name() == "get").unwrap();
assert_ne!(
crate::func::mangled_subroutine_name(&a_get).unwrap(),
crate::func::mangled_subroutine_name(&b_get).unwrap(),
"Box#(int)::get and Box#(string)::get must not share a symbol name"
);
assert_eq!(
VtableLayout::for_template(a).unwrap().len(),
1,
"one virtual method, one slot, per specialization"
);
assert_eq!(VtableLayout::for_template(b).unwrap().len(), 1);
}
#[test]
fn nested_classes_have_their_own_identity_even_when_names_collide() {
let design = compile_source(
"class Outer1;\n\
int o;\n\
class Inner; int x; endclass\n\
endclass\n\
class Outer2;\n\
class Inner; int x; int y; endclass\n\
endclass\n\
class Box #(type T = int); T v; endclass\n\
module top;\n\
Outer1::Inner i1;\n\
Outer2::Inner i2;\n\
Box#(Outer1::Inner) b1;\n\
Box#(Outer2::Inner) b2;\n\
endmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let i1 = var_class_sym(&design, "i1");
let i2 = var_class_sym(&design, "i2");
let b1 = var_class_sym(&design, "b1");
let b2 = var_class_sym(&design, "b2");
let unit = design.compilation_units().next().unwrap();
let outer1 = unit.find("Outer1").unwrap();
let key = |s: &sv_lang::Symbol<'_>| {
sv_sim_sim::netlist::class_specialization_key(s.as_type().unwrap())
};
assert_ne!(key(&i1), key(&outer1), "Inner is not its enclosing class");
assert_ne!(key(&i1), key(&i2), "Outer1::Inner is not Outer2::Inner");
assert_ne!(
key(&b1),
key(&b2),
"Box#(Outer1::Inner) is not Box#(Outer2::Inner)"
);
reset_class_registries();
register_template(ClassTemplateId(7_110), outer1).unwrap();
let l1 = register_template(ClassTemplateId(7_111), i1).unwrap();
let l2 = register_template(ClassTemplateId(7_112), i2).unwrap();
assert_eq!(class_template_id_of(&i1).unwrap(), ClassTemplateId(7_111));
assert_eq!(class_template_id_of(&i2).unwrap(), ClassTemplateId(7_112));
assert_eq!(l1.total_words(), 1, "Outer1::Inner has one field");
assert_eq!(l2.total_words(), 2, "Outer2::Inner has two fields");
register_template(ClassTemplateId(7_113), b1).unwrap();
register_template(ClassTemplateId(7_114), b2)
.expect("the two Box specializations register under distinct keys");
}
#[test]
fn same_named_classes_in_two_packages_have_distinct_mangled_methods() {
let design = compile_source(
"package p1;\n\
class Foo; int x; function new(); x = 1; endfunction\n\
function int get(); return x; endfunction endclass\n\
endpackage\n\
package p2;\n\
class Foo; int x; function new(); x = 2; endfunction\n\
function int get(); return x; endfunction endclass\n\
endpackage\n\
module top; p1::Foo a; p2::Foo b; endmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let a = var_class_sym(&design, "a");
let b = var_class_sym(&design, "b");
assert_ne!(
sv_sim_sim::netlist::class_specialization_key(a.as_type().unwrap()),
sv_sim_sim::netlist::class_specialization_key(b.as_type().unwrap()),
"p1::Foo and p2::Foo are two classes"
);
for member in ["new", "get"] {
let a_m = a.members().find(|m| m.name() == member).unwrap();
let b_m = b.members().find(|m| m.name() == member).unwrap();
assert_ne!(
crate::func::mangled_subroutine_name(&a_m).unwrap(),
crate::func::mangled_subroutine_name(&b_m).unwrap(),
"p1::Foo::{member} and p2::Foo::{member} must not share a symbol name"
);
}
}
#[test]
fn registering_one_specialization_under_two_ids_is_a_loud_error() {
let design = compile_source(
"class Box #(type T = int); T v; endclass\n\
module top; Box#(int) a; endmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let a = var_class_sym(&design, "a");
reset_class_registries();
register_template(ClassTemplateId(7_120), a).unwrap();
let err = register_template(ClassTemplateId(7_121), a)
.expect_err("the same key under a second id must be rejected");
assert!(
err.message.contains("identical specialization key"),
"got: {}",
err.message
);
}
#[test]
fn object_layout_includes_inherited_fields_base_first() {
let design = compile_source(
"class base;\n\
int x;\n\
endclass\n\
class derived extends base;\n\
int y;\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let base_sym = unit
.find("base")
.unwrap_or_else(|| panic!("top-level class `base` must be findable"));
let derived_sym = unit
.find("derived")
.unwrap_or_else(|| panic!("top-level class `derived` must be findable"));
let derived_layout = ObjectLayout::for_template(derived_sym)
.unwrap_or_else(|e| panic!("ObjectLayout::for_template(derived) failed: {e}"));
let names: Vec<&str> = derived_layout
.fields()
.iter()
.map(|f| f.sym.name())
.collect();
assert_eq!(
names,
["x", "y"],
"base's own field(s) must come first, then derived's own, never re-sorted"
);
assert_eq!(derived_layout.total_words(), 2);
let base_layout = ObjectLayout::for_template(base_sym)
.unwrap_or_else(|e| panic!("ObjectLayout::for_template(base) failed: {e}"));
let x_in_base = base_layout
.slot_of(&base_sym.members().find(|m| m.name() == "x").unwrap())
.unwrap();
let x_in_derived = derived_layout
.slot_of(&base_sym.members().find(|m| m.name() == "x").unwrap())
.unwrap();
assert_eq!(x_in_base, x_in_derived);
}
#[test]
fn rand_props_lists_rand_and_randc_in_layout_order_base_first() {
use sv_sim_sim::objects::{RandKind, RandProp, rand_props};
let design = compile_source(
"class A;\n\
rand int a;\n\
randc bit [3:0] b;\n\
int c;\n\
endclass\n\
class B extends A;\n\
rand int d;\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let a_sym = unit.find("A").expect("class A must be findable");
let b_sym = unit.find("B").expect("class B must be findable");
fn member<'d>(class: &sv_lang::Symbol<'d>, name: &str) -> sv_lang::Symbol<'d> {
class
.members()
.find(|m| m.name() == name)
.unwrap_or_else(|| panic!("member `{name}` must exist"))
}
let a_layout = ObjectLayout::for_template(a_sym).unwrap();
let b_layout = ObjectLayout::for_template(b_sym).unwrap();
let slot_a = a_layout.slot_of(&member(&a_sym, "a")).unwrap();
let slot_b = a_layout.slot_of(&member(&a_sym, "b")).unwrap();
let slot_c = a_layout.slot_of(&member(&a_sym, "c")).unwrap();
let slot_d = b_layout.slot_of(&member(&b_sym, "d")).unwrap();
assert_ne!(slot_c, slot_a, "premise: `c` has its own slot");
assert_ne!(slot_c, slot_b, "premise: `c` has its own slot");
let expect_a = vec![
RandProp {
slot: slot_a,
kind: RandKind::Rand,
},
RandProp {
slot: slot_b,
kind: RandKind::RandC,
},
];
let expect_b = vec![
expect_a[0],
expect_a[1],
RandProp {
slot: slot_d,
kind: RandKind::Rand,
},
];
assert_eq!(a_layout.rand_props(), expect_a);
assert_eq!(b_layout.rand_props(), expect_b);
assert_eq!(b_layout.slot_of(&member(&a_sym, "a")).unwrap(), slot_a);
assert_eq!(b_layout.slot_of(&member(&a_sym, "b")).unwrap(), slot_b);
register_template(ClassTemplateId(7_130), a_sym).unwrap();
register_template(ClassTemplateId(7_131), b_sym).unwrap();
assert_eq!(rand_props(ClassTemplateId(7_130)), expect_a);
assert_eq!(rand_props(ClassTemplateId(7_131)), expect_b);
}
#[test]
fn extern_method_prototype_resolves_to_a_this_bearing_subroutine() {
let design = compile_source(
"class pair;\n\
int a;\n\
int b;\n\
extern function int sum();\n\
endclass\n\
function int pair::sum();\n\
return a + b;\n\
endfunction\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let class_sym = unit
.find("pair")
.unwrap_or_else(|| panic!("top-level class `pair` must be findable"));
let proto = class_sym
.members()
.find(|m| m.name() == "sum")
.unwrap_or_else(|| panic!("`pair` must declare a `sum` member"));
assert_eq!(
proto.kind(),
SymbolKind::MethodPrototype,
"an UNRESOLVED `.members()` walk (unlike `.find()`) must give the raw prototype"
);
assert!(
proto.method_prototype_first_extern_impl().is_none(),
"the plan's own literal text named this accessor, but it is for an INTERFACE's \
modport-implemented `extern` method (`module m(Iface i); function void i.f(); \
endfunction`), a different mechanism -- it must be `None` here"
);
let real = proto.method_prototype_subroutine().unwrap_or_else(|| {
panic!("`method_prototype_subroutine` must resolve the out-of-block `pair::sum`")
});
assert_eq!(real.kind(), SymbolKind::Subroutine);
assert_eq!(real.name(), "sum");
assert!(
real.subroutine_this_var().is_some(),
"the resolved out-of-block implementation must have a bound `this`, exactly like \
an ordinary in-block instance method"
);
}
#[test]
fn class_base_constructor_call_shapes() {
let design = compile_source(
"class root; endclass\n\
class explicit_super extends root;\n\
function new();\n\
super.new();\n\
endfunction\n\
endclass\n\
class no_explicit_ctor extends root;\n\
endclass\n\
module top;\n\
root r;\n\
explicit_super e;\n\
no_explicit_ctor n;\n\
initial begin\n\
r = new;\n\
e = new;\n\
n = new;\n\
end\n\
endmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let e_ty = body.find("e").unwrap().value_type().unwrap();
let call = e_ty
.class_base_constructor_call()
.expect("explicit_super has a base-constructor call");
assert_eq!(call.kind(), ExpressionKind::NewClass);
assert_eq!(call.new_class_is_super_class(), Some(true));
let n_ty = body.find("n").unwrap().value_type().unwrap();
assert!(n_ty.class_base_constructor_call().is_none());
assert!(n_ty.class_base().is_some());
assert!(n_ty.class_base().unwrap().class_constructor().is_none());
let r_ty = body.find("r").unwrap().value_type().unwrap();
assert!(r_ty.class_base_constructor_call().is_none());
assert!(r_ty.class_base().is_none());
}
#[test]
fn object_layout_stores_each_collection_property_as_one_handle() {
let src = "class k;\nint a;\nint q[$];\nint d[];\nint m[string];\nendclass\n\
module top;\nendmodule\n";
let design = compile_source(src).expect("compile");
let unit = design.compilation_units().next().unwrap();
let class_sym = unit.find("k").expect("class k");
let layout = ObjectLayout::for_template(class_sym).expect("collections are storable");
assert_eq!(layout.fields.len(), 4);
let slots = layout.collection_field_slots();
assert_eq!(slots.len(), 3, "q, d and m are collection fields");
assert!(slots.iter().all(|s| s.words == 2));
}
#[test]
fn object_layout_stores_unpacked_aggregate_properties_flat() {
for (decl, words) in [
("int q[3];", 3),
("struct { int a; byte b; } q;", 2),
("static int q[3];", 3),
] {
let src = format!("class bad;\n{decl}\nendclass\nmodule top;\nendmodule\n");
let design = compile_source(&src)
.unwrap_or_else(|e| panic!("compile_source failed for `{decl}`: {e}"));
let unit = design.compilation_units().next().unwrap();
let class_sym = unit
.find("bad")
.unwrap_or_else(|| panic!("top-level class `bad` must be findable"));
let layout = if decl.starts_with("static") {
ObjectLayout::for_statics(class_sym)
} else {
ObjectLayout::for_template(class_sym)
}
.unwrap_or_else(|e| panic!("`{decl}` must be laid out: {}", e.message));
assert_eq!(layout.fields().len(), 1, "{decl}");
assert_eq!(layout.fields()[0].slot.words, words, "{decl}");
}
}
#[test]
fn object_layout_rejects_an_unpacked_union_holding_a_handle() {
let src = "class h; endclass\nclass bad;\nunion { h x; int i; } q;\nendclass\n\
module top;\nendmodule\n";
let design = compile_source(src).unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let class_sym = unit.find("bad").unwrap();
let err = ObjectLayout::for_template(class_sym)
.expect_err("a union holding a handle must be rejected, not silently dropped");
assert!(
err.message.contains("no fixed-width stored form"),
"error should name the unsupported shape: {}",
err.message
);
}
#[test]
fn interface_implementation_has_no_override_link() {
let design = compile_source(
"interface class ifoo;\n\
pure virtual function int get_val();\n\
endclass\n\
class a implements ifoo;\n\
virtual function int get_val(); return 10; endfunction\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let ifoo = unit.find("ifoo").unwrap();
let iface_proto = ifoo.members().find(|m| m.name() == "get_val").unwrap();
assert_eq!(iface_proto.kind(), SymbolKind::MethodPrototype);
assert!(
iface_proto.method_prototype_override().is_none(),
"an interface class's own top-level method prototype has no base to override"
);
let a = unit.find("a").unwrap();
let a_get_val = a.members().find(|m| m.name() == "get_val").unwrap();
assert_eq!(a_get_val.kind(), SymbolKind::Subroutine);
assert!(
a_get_val.subroutine_override().is_none(),
"a class's own implementation of an interface method must have no override link \
to the interface's MethodPrototype -- confirmed live against \
ClassSymbols.cpp::ClassType::handleImplements, which never calls setOverride for \
this relationship (see VtableLayout::for_template's own Task 8 doc comment)"
);
assert!(
a_get_val.subroutine_prototype().is_none(),
"nor does it resolve back to the interface's own MethodPrototype via \
subroutine_prototype() -- that accessor is for an in-class `extern`/pure \
declaration's own prototype, a different relationship entirely"
);
}
#[test]
fn vtable_layout_aliases_interface_method_to_each_implementers_own_slot() {
let design = compile_source(
"interface class ifoo;\n\
pure virtual function int get_val();\n\
endclass\n\
class a implements ifoo;\n\
virtual function int get_val(); return 10; endfunction\n\
endclass\n\
class b implements ifoo;\n\
virtual function int get_val(); return 20; endfunction\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let ifoo = unit.find("ifoo").unwrap();
let iface_proto = ifoo.members().find(|m| m.name() == "get_val").unwrap();
let iface_stub = iface_proto
.method_prototype_subroutine()
.expect("a pure interface method still gets a synthesized callable stub");
let a = unit.find("a").unwrap();
let a_get_val = a.members().find(|m| m.name() == "get_val").unwrap();
let a_layout =
VtableLayout::for_template(a).unwrap_or_else(|e| panic!("for_template(a): {e}"));
let a_slot = a_layout
.slot_of(&a_get_val)
.expect("a::get_val must have its own slot");
assert_eq!(
a_layout.slot_of(&iface_proto),
Some(a_slot),
"ifoo::get_val's own MethodPrototype must alias to a::get_val's slot"
);
assert_eq!(
a_layout.slot_of(&iface_stub),
Some(a_slot),
"ifoo::get_val's synthesized stub (what a dot-call through an ifoo-typed handle \
actually resolves call_subroutine() to) must alias to the SAME slot"
);
assert_eq!(
a_layout.impls()[a_slot].map(|s| s.uid()),
Some(a_get_val.uid())
);
let b = unit.find("b").unwrap();
let b_get_val = b.members().find(|m| m.name() == "get_val").unwrap();
let b_layout =
VtableLayout::for_template(b).unwrap_or_else(|e| panic!("for_template(b): {e}"));
let b_slot = b_layout
.slot_of(&b_get_val)
.expect("b::get_val must have its own slot");
assert_eq!(b_layout.slot_of(&iface_proto), Some(b_slot));
assert_eq!(
b_layout.impls()[b_slot].map(|s| s.uid()),
Some(b_get_val.uid())
);
}
#[test]
fn vtable_layout_maps_each_interface_method_by_name_not_position() {
let design = compile_source(
"interface class ifoo;\n\
pure virtual function int m1();\n\
pure virtual function int m2();\n\
endclass\n\
interface class ibar extends ifoo;\n\
pure virtual function int m3();\n\
endclass\n\
class pad;\n\
virtual function int p1(); return 100; endfunction\n\
virtual function int p2(); return 200; endfunction\n\
endclass\n\
class a extends pad implements ibar;\n\
virtual function int m1(); return 1; endfunction\n\
virtual function int m2(); return 2; endfunction\n\
virtual function int m3(); return 3; endfunction\n\
endclass\n\
class b implements ibar;\n\
virtual function int m3(); return 13; endfunction\n\
virtual function int m2(); return 12; endfunction\n\
virtual function int m1(); return 11; endfunction\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let proto = |iface: &str, name: &str| {
unit.find(iface)
.unwrap()
.members()
.find(|m| m.name() == name)
.unwrap()
};
let check = |class: &str, expected_slots: [usize; 3]| {
let cls = unit.find(class).unwrap();
let layout = VtableLayout::for_template(cls)
.unwrap_or_else(|e| panic!("for_template({class}): {e}"));
let names = ["m1", "m2", "m3"];
let mut seen = Vec::new();
for (i, name) in names.iter().enumerate() {
let own = cls.members().find(|m| m.name() == *name).unwrap();
let iface = if *name == "m3" { "ibar" } else { "ifoo" };
let iface_proto = proto(iface, name);
let own_slot = layout
.slot_of(&own)
.unwrap_or_else(|| panic!("{class}::{name} must have its own slot"));
assert_eq!(own_slot, expected_slots[i], "{class}::{name}'s own slot");
assert_eq!(
layout.slot_of(&iface_proto),
Some(own_slot),
"{iface}::{name} must alias {class}::{name}'s own slot, not another \
method's"
);
let iface_stub = iface_proto.method_prototype_subroutine().unwrap();
assert_eq!(layout.slot_of(&iface_stub), Some(own_slot));
assert_eq!(
layout.impls()[own_slot].map(|s| s.uid()),
Some(own.uid()),
"{class}'s slot {own_slot} must hold {class}::{name}'s implementation"
);
seen.push(own_slot);
}
seen.sort_unstable();
seen.dedup();
assert_eq!(
seen.len(),
3,
"{class}: three interface methods, three slots"
);
};
check("a", [2, 3, 4]);
check("b", [2, 1, 0]);
}
#[test]
fn vtable_layout_finds_an_interface_implemented_only_via_a_base_class() {
let design = compile_source(
"interface class ifoo;\n\
pure virtual function int get_val();\n\
endclass\n\
virtual class base implements ifoo;\n\
pure virtual function int get_val();\n\
endclass\n\
class derived extends base;\n\
virtual function int get_val(); return 5; endfunction\n\
endclass\n\
module top;\nendmodule\n",
)
.unwrap_or_else(|e| panic!("compile_source failed: {e}"));
let unit = design.compilation_units().next().unwrap();
let derived = unit.find("derived").unwrap();
let derived_ty = derived.as_type().unwrap();
assert!(
sv_sim_sim::netlist::flattened_interfaces(derived_ty).is_empty(),
"this test's own premise: `derived`'s own flattened_interfaces must be empty -- \
it implements ifoo only transitively, through `base`"
);
let ifoo = unit.find("ifoo").unwrap();
let iface_proto = ifoo.members().find(|m| m.name() == "get_val").unwrap();
let derived_get_val = derived.members().find(|m| m.name() == "get_val").unwrap();
let layout = VtableLayout::for_template(derived)
.unwrap_or_else(|e| panic!("for_template(derived): {e}"));
let slot = layout
.slot_of(&derived_get_val)
.expect("derived::get_val must have its own slot");
assert_eq!(
layout.slot_of(&iface_proto),
Some(slot),
"ifoo::get_val must still alias to derived::get_val's slot even though the \
`implements` clause was written on `base`, not `derived`"
);
assert_eq!(
layout.impls()[slot].map(|s| s.uid()),
Some(derived_get_val.uid())
);
}
}