use std::cell::RefCell;
use std::collections::HashSet;
use inkwell::IntPredicate;
use sv_lang::kinds::SymbolKind;
use sv_sim_runtime::objects_rt::ObjOp;
use sv_sim_sim::constraints::build::{
Builder, CaptureInfo, Declared, is_aggregate_type, property_is_random,
};
use sv_sim_sim::constraints::registry::{
self, Home, Outcome, ProgArray, ProgHandle, ProgRealVar, ProgState, ProgVar, Program,
};
use sv_sim_sim::constraints::walk;
use sv_sim_sim::netlist::{ClassTemplateId, classify_members};
use sv_sim_sim::objects::FieldSlot;
use sv_sim_sim::symid::SymbolUid;
use crate::classes::{
ObjectLayout, cast_template_id, current_this_handle, current_this_var, obj_call,
receiver_handle, static_singleton_handle, static_singleton_template_id, word_ptr,
};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, entry_alloca};
use crate::ops::OpBuilder;
use crate::randomize_scope;
thread_local! {
static FAILED_PROGRAMS: RefCell<Vec<(ClassTemplateId, String)>> =
const { RefCell::new(Vec::new()) };
static STATIC_OWNERS: RefCell<Vec<(ClassTemplateId, u32)>> =
const { RefCell::new(Vec::new()) };
static HOOKS: RefCell<Vec<(ClassTemplateId, Hook, String)>> =
const { RefCell::new(Vec::new()) };
static ABORTING_HOOKS: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
}
pub fn reset_failed_programs() {
FAILED_PROGRAMS.with(|f| f.borrow_mut().clear());
STATIC_OWNERS.with(|f| f.borrow_mut().clear());
HOOKS.with(|f| f.borrow_mut().clear());
ABORTING_HOOKS.with(|f| f.borrow_mut().clear());
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Hook {
Pre,
Post,
}
impl Hook {
fn method(self) -> &'static str {
match self {
Hook::Pre => "pre_randomize",
Hook::Post => "post_randomize",
}
}
fn from_method(name: &str) -> Option<Self> {
match name {
"pre_randomize" => Some(Hook::Pre),
"post_randomize" => Some(Hook::Post),
_ => None,
}
}
fn phase(self) -> walk::Phase {
match self {
Hook::Pre => walk::Phase::Pre,
Hook::Post => walk::Phase::Post,
}
}
}
pub(crate) fn nearest_user_hook<'d>(
class_sym: sv_lang::Symbol<'d>,
hook: Hook,
) -> Option<sv_lang::Symbol<'d>> {
let mut levels: Vec<sv_lang::Symbol<'d>> = class_sym
.as_type()
.map(|ty| {
sv_sim_sim::netlist::base_chain(ty)
.into_iter()
.map(|t| t.as_symbol())
.collect()
})
.unwrap_or_default();
levels.push(class_sym);
for level in levels.into_iter().rev() {
for m in level.members() {
if m.name() != hook.method() {
continue;
}
let m = if m.kind() == SymbolKind::MethodPrototype {
m.method_prototype_subroutine().unwrap_or(m)
} else {
m
};
if m.kind() == SymbolKind::Subroutine
&& !m
.subroutine_flags()
.contains(sv_lang::MethodFlags::BUILT_IN)
{
return Some(m);
}
}
}
None
}
pub(crate) fn register_hook_impls<'d>(
classes: &[(ClassTemplateId, sv_lang::Symbol<'d>)],
) -> Result<Vec<sv_lang::Symbol<'d>>, LoweringError> {
let mut entries = Vec::new();
let mut syms: Vec<sv_lang::Symbol<'d>> = Vec::new();
for &(id, class_sym) in classes {
for hook in [Hook::Pre, Hook::Post] {
let Some(sym) = nearest_user_hook(class_sym, hook) else {
continue;
};
let mangled = crate::func::mangled_subroutine_name(&sym)?;
if !syms.iter().any(|s| s.uid() == sym.uid()) {
syms.push(sym);
}
entries.push((id, hook, mangled));
}
}
HOOKS.with(|h| *h.borrow_mut() = entries);
ABORTING_HOOKS.with(|h| h.borrow_mut().clear());
Ok(syms)
}
pub(crate) fn settle_hook_aborts(hooks: &[sv_lang::Symbol<'_>]) {
for sym in hooks {
if sv_sim_sim::process::cfg::subroutine_can_abort(sym)
&& let Ok(mangled) = crate::func::mangled_subroutine_name(sym)
{
ABORTING_HOOKS.with(|h| h.borrow_mut().insert(mangled));
}
}
}
fn hook_impls(hook: Hook) -> Vec<(ClassTemplateId, String)> {
HOOKS.with(|h| {
h.borrow()
.iter()
.filter(|(_, k, _)| *k == hook)
.map(|(id, _, name)| (*id, name.clone()))
.collect()
})
}
fn emit_hook_walk<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: inkwell::values::IntValue<'ctx>,
hook: Hook,
count: inkwell::values::IntValue<'ctx>,
buf: inkwell::values::PointerValue<'ctx>,
) -> Result<(), LoweringError> {
let impls = hook_impls(hook);
if impls.is_empty() {
return Ok(());
}
let i64t = cx.ctx.i64_type();
let null = cx
.ctx
.ptr_type(inkwell::AddressSpace::default())
.const_null();
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let token = obj_call(
cx,
ObjOp::RandWalkBegin,
h,
count,
i64t.const_int(hook.phase().code(), false),
buf,
);
let next_bb = cx.ctx.append_basic_block(f, "hook_walk_next");
let dispatch_bb = cx.ctx.append_basic_block(f, "hook_walk_dispatch");
let done_bb = cx.ctx.append_basic_block(f, "hook_walk_done");
cx.b.build_unconditional_branch(next_bb).unwrap();
cx.b.position_at_end(next_bb);
let obj = obj_call(
cx,
ObjOp::RandWalkNext,
token,
i64t.const_zero(),
i64t.const_zero(),
null,
);
let at_end =
cx.b.build_int_compare(IntPredicate::EQ, obj, i64t.const_zero(), "hook_walk_end")
.unwrap();
cx.b.build_conditional_branch(at_end, done_bb, dispatch_bb)
.unwrap();
cx.b.position_at_end(dispatch_bb);
let dyn_id = crate::classes::dynamic_template_call(cx, obj);
for (id, mangled) in impls {
let fv = cx.module.get_function(&mangled).ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"internal: `{}` implementation `{mangled}` was not declared into this module",
hook.method()
),
&cx.fn_sym,
)
})?;
if fv.count_params() != 1 {
return Err(LoweringError::unsupported_at(
&format!(
"internal: `{}` implementation `{mangled}` takes {} parameters, expected \
only the receiver",
hook.method(),
fv.count_params()
),
&cx.fn_sym,
));
}
let arm = cx.ctx.append_basic_block(f, "hook_call");
let skip = cx.ctx.append_basic_block(f, "hook_call_next");
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
dyn_id,
i64t.const_int(u64::from(id.0), false),
"hook_hit",
)
.unwrap();
cx.b.build_conditional_branch(hit, arm, skip).unwrap();
cx.b.position_at_end(arm);
cx.b.build_call(fv, &[obj.into()], "hook_call").unwrap();
if ABORTING_HOOKS.with(|a| a.borrow().contains(&mangled)) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
cx.b.build_unconditional_branch(next_bb).unwrap();
cx.b.position_at_end(skip);
}
cx.b.build_unconditional_branch(next_bb).unwrap();
cx.b.position_at_end(done_bb);
obj_call(
cx,
ObjOp::RandWalkEnd,
token,
i64t.const_zero(),
i64t.const_zero(),
null,
);
Ok(())
}
pub(crate) fn lower_builtin_hook_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
callee: sv_lang::Symbol<'d>,
class_sym: sv_lang::Symbol<'d>,
) -> Result<(), LoweringError> {
let Some(hook) = Hook::from_method(callee.name()) else {
return Err(LoweringError::unsupported_at(
&format!("internal: `{}` is not a randomize hook", callee.name()),
&cx.fn_sym,
));
};
let at = call.source_range().start;
let i64t = cx.ctx.i64_type();
let h = match call.call_this_class() {
Some(recv) => receiver_handle(cx, recv)?,
None => current_this_handle(cx)?,
};
let Some(user) = nearest_user_hook(class_sym, hook) else {
return Ok(());
};
let null =
cx.b.build_int_compare(IntPredicate::EQ, h, i64t.const_zero(), "hook_null_recv")
.unwrap();
crate::stmt_shared::error_if(
cx,
null,
at,
&format!("null object handle: call of {}", hook.method()),
)?;
let mangled = crate::func::mangled_subroutine_name(&user)?;
let fv = cx.module.get_function(&mangled).ok_or_else(|| {
LoweringError::unsupported_at(
&format!("internal: `{mangled}` was not declared into this module"),
&cx.fn_sym,
)
})?;
if fv.count_params() != 1 {
return Err(LoweringError::unsupported_at(
&format!("internal: `{mangled}` takes more than its receiver"),
&cx.fn_sym,
));
}
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let run = cx.ctx.append_basic_block(f, "builtin_hook_run");
let done = cx.ctx.append_basic_block(f, "builtin_hook_done");
cx.b.build_conditional_branch(null, done, run).unwrap();
cx.b.position_at_end(run);
cx.b.build_call(fv, &[h.into()], "builtin_hook_call")
.unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&user) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
cx.b.build_unconditional_branch(done).unwrap();
cx.b.position_at_end(done);
Ok(())
}
fn static_home(level: sv_lang::Symbol<'_>) -> Result<Home, LoweringError> {
Ok(Home::Static(static_singleton_template_id(&level)?.0))
}
fn static_state_location(
levels: &[sv_lang::Symbol<'_>],
uid: &sv_sim_sim::symid::SymbolId,
) -> Result<Option<(FieldSlot, Home)>, LoweringError> {
for &level in levels {
let members = classify_members(level)
.map_err(|e| LoweringError::unsupported_at(&e.to_string(), &level))?;
if !members.statics.iter().any(|s| s.uid() == *uid) {
continue;
}
let statics = ObjectLayout::for_statics(level)?;
let slot = statics
.fields()
.iter()
.find(|f| f.sym.uid() == *uid)
.map(|f| f.slot);
if let Some(slot) = slot {
return Ok(Some((slot, static_home(level)?)));
}
}
Ok(None)
}
pub(crate) fn class_levels<'d>(ty: sv_lang::Type<'d>) -> Vec<sv_lang::Symbol<'d>> {
let mut levels: Vec<sv_lang::Symbol<'d>> = sv_sim_sim::netlist::base_chain(ty)
.into_iter()
.map(|t| t.as_symbol())
.collect();
levels.push(ty.as_symbol());
levels
}
pub(crate) struct ClassBlock<'d> {
pub(crate) name: String,
pub(crate) sym: sv_lang::Symbol<'d>,
pub(crate) level: u32,
pub(crate) home_level: usize,
pub(crate) is_static: bool,
pub(crate) static_flipped: bool,
}
pub(crate) fn class_constraint_blocks<'d>(levels: &[sv_lang::Symbol<'d>]) -> Vec<ClassBlock<'d>> {
let mut blocks: Vec<ClassBlock<'d>> = Vec::new();
for (level_ix, &level) in levels.iter().enumerate() {
for m in level.members() {
if m.kind() != SymbolKind::ConstraintBlock {
continue;
}
let flags = m.constraint_block_flags();
if flags.contains(sv_lang::ConstraintBlockFlags::PURE) {
continue; }
let name = m.name().to_string();
match blocks.iter_mut().find(|b| b.name == name) {
Some(slot) => {
slot.static_flipped |=
flags.contains(sv_lang::ConstraintBlockFlags::STATIC) != slot.is_static;
slot.sym = m;
slot.level = level_ix as u32;
}
None => blocks.push(ClassBlock {
name,
sym: m,
level: level_ix as u32,
home_level: level_ix,
is_static: flags.contains(sv_lang::ConstraintBlockFlags::STATIC),
static_flipped: false,
}),
}
}
}
blocks
}
pub(crate) fn flipped_static_reason(name: &str) -> String {
format!(
"constraint `{name}` is overridden with the opposite `static` qualifier; whether its \
`constraint_mode()` is per class or per object would depend on the dynamic class, \
which is not supported (declare the override `static` like the base, or neither)"
)
}
fn build_program<'d>(
class_sym: sv_lang::Symbol<'d>,
layout: &ObjectLayout<'_>,
universe: &[(ClassTemplateId, sv_lang::Symbol<'d>)],
) -> Result<Program, String> {
build_program_impl(class_sym, layout, None, universe).map(|(program, _)| program)
}
struct InlineBlock<'a, 'd> {
constraints: sv_lang::Constraint<'d>,
is_local: &'a dyn for<'s> Fn(sv_lang::Expression<'s>) -> bool,
}
struct FragmentSpec<'a, 'd> {
inline: Option<&'a InlineBlock<'a, 'd>>,
forced: &'a [sv_lang::Symbol<'d>],
}
fn build_fragment(
class_sym: sv_lang::Symbol<'_>,
layout: &ObjectLayout<'_>,
spec: &FragmentSpec<'_, '_>,
) -> Result<(Program, Vec<CaptureInfo>), String> {
build_program_impl(class_sym, layout, Some(spec), &[])
}
fn build_program_impl<'d>(
class_sym: sv_lang::Symbol<'_>,
layout: &ObjectLayout<'_>,
frag: Option<&FragmentSpec<'_, '_>>,
universe: &[(ClassTemplateId, sv_lang::Symbol<'d>)],
) -> Result<(Program, Vec<CaptureInfo>), String> {
let class_name = class_sym.name().to_string();
let ctx = |e: &dyn std::fmt::Display| format!("class `{class_name}`: {e}");
let ty = class_sym
.as_type()
.ok_or_else(|| ctx(&"class symbol has no type"))?;
let mut levels: Vec<sv_lang::Symbol<'_>> = sv_sim_sim::netlist::base_chain(ty)
.into_iter()
.map(|t| t.as_symbol())
.collect();
levels.push(class_sym);
let blocks = class_constraint_blocks(&levels);
if frag.is_none()
&& let Some(blk) = blocks.iter().find(|b| b.static_flipped)
{
return Err(ctx(&flipped_static_reason(&blk.name)));
}
crate::classes::with_design_trees(|trees| {
let mut b = Builder::new(trees);
b.set_slot_resolver(|member| {
let class_sym = member
.parent()
.ok_or_else(|| format!("`{}` has no enclosing class", member.name()))?;
let layout = ObjectLayout::for_template(class_sym).map_err(|e| e.to_string())?;
layout.slot_of(&member).ok_or_else(|| {
format!(
"`{}` is not an instance property of its class (a static member cannot be \
read through a handle in a constraint yet)",
member.name()
)
})
});
if let Ok(vt) = crate::classes::VtableLayout::for_template(class_sym) {
b.set_virtual_resolver(move |callee| {
let slot = vt.slot_of(&callee)?;
let imp = (*vt.impls().get(slot)?)?;
Some(
if imp.kind() == sv_lang::kinds::SymbolKind::MethodPrototype {
imp.method_prototype_subroutine().unwrap_or(imp)
} else {
imp
},
)
});
}
if !universe.is_empty() {
b.set_virtual_targets(|callee, static_class| {
let static_uid = static_class.uid();
let mut out = Vec::new();
for &(id, csym) in universe {
let derives = csym.uid() == static_uid
|| csym.as_type().is_some_and(|t| {
sv_sim_sim::netlist::base_chain(t)
.iter()
.any(|b| b.as_symbol().uid() == static_uid)
});
if !derives {
continue;
}
let Ok(vt) = crate::classes::VtableLayout::for_template(csym) else {
continue;
};
let Some(slot) = vt.slot_of(&callee) else {
continue;
};
let Some(Some(imp)) = vt.impls().get(slot).copied() else {
continue;
};
let imp = if imp.kind() == sv_lang::kinds::SymbolKind::MethodPrototype {
imp.method_prototype_subroutine().unwrap_or(imp)
} else {
imp
};
out.push((id.0, imp));
}
out
});
}
let mut locs = Locations::default();
let mut prop_index = 0usize;
for f in layout.fields() {
if !property_is_random(f.sym) {
continue;
}
declare_located(
&mut b,
f.sym,
(Some(prop_index), f.slot, Home::Instance),
&mut locs,
)
.map_err(|e| ctx(&e))?;
prop_index += 1;
}
for &level in &levels {
let members = classify_members(level).map_err(|e| ctx(&e))?;
let declares_rand = members.statics.iter().any(|s| property_is_random(*s));
if !declares_rand {
continue;
}
let home = static_home(level).map_err(|e| ctx(&e))?;
let statics = ObjectLayout::for_statics(level).map_err(|e| ctx(&e))?;
for f in statics.fields() {
if !property_is_random(f.sym) {
continue;
}
declare_located(&mut b, f.sym, (None, f.slot, home), &mut locs)
.map_err(|e| ctx(&e))?;
}
}
if let Some(frag) = frag {
for &sym in frag.forced {
if property_is_random(sym) {
continue;
}
let (slot, home) = match layout.fields().iter().find(|f| f.sym.uid() == sym.uid()) {
Some(f) => (f.slot, Home::Instance),
None => static_state_location(&levels, &sym.uid())
.map_err(|e| ctx(&e))?
.ok_or_else(|| {
ctx(&format!(
"randomize({}): not a property of the class",
sym.name()
))
})?,
};
let vt = sym.value_type();
if vt.is_some_and(is_aggregate_type) {
for d in b
.declare_aggregate_forced(sym)
.map_err(|e| ctx(&format!("randomize({}): {e}", sym.name())))?
{
let (id, table) = match d {
Declared::Int(id) => (id, &mut locs.vars),
Declared::Real(id) => (id, &mut locs.reals),
};
if id as usize == table.len() {
table.push((None, slot, home));
}
}
continue;
}
if vt.is_some_and(|t| {
let c = t.canonical();
c.is_dynamically_sized_array() || c.is_queue() || c.is_associative_array()
}) {
let id = b
.declare_array_forced(sym)
.map_err(|e| ctx(&format!("randomize({}): {e}", sym.name())))?;
if id as usize == locs.arrays.len() {
locs.arrays.push((None, slot, home));
}
continue;
}
let is_real = vt.is_some_and(|t| t.is_floating());
let id = if is_real {
b.declare_real_forced(sym)
} else {
b.declare_var_forced(sym)
}
.map_err(|e| ctx(&format!("randomize({}): {e}", sym.name())))?;
let table = if is_real {
&mut locs.reals
} else {
&mut locs.vars
};
if id as usize == table.len() {
table.push((None, slot, home));
}
}
if let Some(inline) = frag.inline {
let classify = inline.is_local;
b.set_local_classifier(classify);
b.set_priority_scope(0, u32::MAX);
b.add_constraint(inline.constraints)
.map_err(|e| ctx(&format!("inline constraint: {e}")))?;
}
} else {
for blk in &blocks {
b.set_priority_scope(0, blk.level);
b.add_block(blk.sym)
.map_err(|e| ctx(&format!("constraint block `{}`: {e}", blk.sym.name())))?;
}
}
let set = b.finish().map_err(|e| ctx(&e))?;
let mut prog_blocks = Vec::new();
if frag.is_none() {
if set.blocks.len() != blocks.len() {
return Err(ctx(&"internal: constraint block records out of step"));
}
for (i, (blk, info)) in blocks.iter().zip(&set.blocks).enumerate() {
let home = if blk.is_static {
static_home(levels[blk.home_level]).map_err(|e| ctx(&e))?
} else {
Home::Instance
};
prog_blocks.push(registry::ProgBlock {
home,
ordinal: i as u32,
conjuncts: info.conjuncts.clone(),
items: info.items.clone(),
});
}
}
let Locations {
vars: var_loc,
reals: real_loc,
arrays: array_loc,
handles: rand_handles,
} = locs;
let vars = set
.vars
.iter()
.enumerate()
.map(|(i, v)| ProgVar {
ty: v.ty,
slot: var_loc[i].1,
home: var_loc[i].2,
prop_index: var_loc[i].0,
domain_conjunct: set.domain_conjunct[i],
randc: v.randc,
window: v.window,
})
.collect();
let real_vars = set
.real_vars
.iter()
.enumerate()
.map(|(i, v)| ProgRealVar {
kind: v.kind,
slot: real_loc[i].1,
home: real_loc[i].2,
prop_index: real_loc[i].0,
window: v.window,
})
.collect();
let locate =
|uid: &sv_sim_sim::symid::SymbolId, name: &str| -> Result<(FieldSlot, Home), String> {
match layout.fields().iter().find(|f| f.sym.uid() == *uid) {
Some(f) => Ok((f.slot, Home::Instance)),
None => static_state_location(&levels, uid)
.map_err(|e| ctx(&e))?
.ok_or_else(|| {
ctx(&format!(
"a constraint reads `{name}`, which is neither a property of the \
class nor a static property of the class or one of its bases \
(state outside the class is not supported yet)"
))
}),
}
};
let mut arrays = Vec::new();
let mut rand_arrays = array_loc.iter();
for a in &set.arrays {
let (prop_index, slot, home) = if a.rand {
*rand_arrays
.next()
.ok_or_else(|| ctx(&"internal: rand array ids out of step"))?
} else {
let (slot, home) = locate(&a.symbol, &a.name)?;
(None, slot, home)
};
arrays.push(ProgArray {
elem_ty: a.elem_ty,
elem_words: (2 * a.elem_ty.width as usize).div_ceil(64).max(1),
slot,
home,
rand: a.rand,
prop_index,
elem_domain: a.elem_domain.clone(),
assoc: a.assoc,
});
}
let mut states = Vec::new();
for (k, s) in set.states.iter().enumerate() {
if set.captures.iter().any(|c| c.state as usize == k) {
states.push(ProgState {
root_bit: 0,
ty: s.ty,
slot: FieldSlot {
offset: 0,
words: 1,
},
home: Home::Instance,
via: Vec::new(),
window: None,
class_of: false,
});
continue;
}
let (slot, home) = locate(&s.symbol, &s.name)?;
states.push(ProgState {
root_bit: s.root_bit,
ty: s.ty,
slot,
home,
via: s.via.iter().map(|h| h.slot).collect(),
window: s.window,
class_of: s.class_of,
});
}
Ok((
Program {
vars,
states,
conjuncts: set.conjuncts,
before: set.before,
dists: set.dists,
real_vars,
real: set.real,
arrays,
funcs: set.funcs,
softs: set.softs,
rand_handles,
blocks: prog_blocks,
n_disables: set.disables,
disable_keys: if frag.is_some() {
set.disable_keys
} else {
Vec::new()
},
},
set.captures,
))
})
}
#[derive(Default)]
struct Locations {
vars: Vec<(Option<usize>, FieldSlot, Home)>,
reals: Vec<(Option<usize>, FieldSlot, Home)>,
arrays: Vec<(Option<usize>, FieldSlot, Home)>,
handles: Vec<ProgHandle>,
}
fn declare_located(
b: &mut Builder<'_>,
sym: sv_lang::Symbol<'_>,
loc: (Option<usize>, FieldSlot, Home),
locs: &mut Locations,
) -> Result<(), String> {
let vt = sym.value_type();
if vt.is_some_and(|t| t.canonical().is_class()) {
locs.handles.push(ProgHandle {
slot: loc.1,
home: loc.2,
prop_index: loc.0,
bit: 0,
});
return Ok(());
}
if vt.is_some_and(|t| t.canonical().is_unpacked_array() && is_class_array(t)) {
let t = vt.unwrap_or_else(|| unreachable!());
for s in crate::collections::handle_offsets(&t.canonical()) {
if s.kind == crate::collections::HandleKind::Obj && s.guards.is_empty() {
locs.handles.push(ProgHandle {
slot: loc.1,
home: loc.2,
prop_index: loc.0,
bit: s.off,
});
}
}
return Ok(());
}
if vt.is_some_and(is_aggregate_type) {
for d in b.declare_rand_aggregate(sym).map_err(|e| e.to_string())? {
let (id, table) = match d {
Declared::Int(id) => (id, &mut locs.vars),
Declared::Real(id) => (id, &mut locs.reals),
};
if id as usize != table.len() {
return Err("internal: rand variable ids out of step".to_string());
}
table.push(loc);
}
return Ok(());
}
let is_real = vt.is_some_and(|t| t.is_floating());
let is_array = vt.is_some_and(|t| {
let c = t.canonical();
c.is_dynamically_sized_array() || c.is_queue() || c.is_associative_array()
});
let (id, table) = if is_real {
(b.declare_rand_real(sym), &mut locs.reals)
} else if is_array {
(b.declare_rand_array(sym), &mut locs.arrays)
} else {
(b.declare_rand(sym), &mut locs.vars)
};
let id = id.map_err(|e| e.to_string())?;
if id as usize != table.len() {
return Err("internal: rand variable ids out of step".to_string());
}
table.push(loc);
Ok(())
}
fn is_class_array(t: sv_lang::Type<'_>) -> bool {
let mut c = t.canonical();
while c.is_unpacked_array() {
match c.element_type() {
Some(e) => c = e.canonical(),
None => return false,
}
}
c.is_class() && !t.canonical().is_class()
}
fn static_owners_of(p: &Program) -> Vec<u32> {
let homes = p
.vars
.iter()
.map(|v| v.home)
.chain(p.real_vars.iter().map(|v| v.home))
.chain(p.arrays.iter().map(|a| a.home))
.chain(p.states.iter().map(|s| s.home))
.chain(p.rand_handles.iter().map(|h| h.home))
.chain(p.blocks.iter().map(|b| b.home));
let mut owners = Vec::new();
for h in homes {
if let Home::Static(owner) = h
&& !owners.contains(&owner)
{
owners.push(owner);
}
}
owners
}
fn rand_member_classes(ty: sv_lang::Type<'_>) -> Vec<ClassTemplateId> {
let mut out: Vec<ClassTemplateId> = Vec::new();
let mut todo = vec![ty];
let mut seen = vec![cast_template_id(ty)];
while let Some(t) = todo.pop() {
let Ok(layout) = ObjectLayout::for_template(t.as_symbol()) else {
continue;
};
for f in layout.fields() {
if !property_is_random(f.sym) {
continue;
}
let Some(m) = f
.sym
.value_type()
.map(|v| v.canonical())
.filter(|v| v.is_class())
else {
continue;
};
let id = cast_template_id(m);
if !seen.contains(&id) {
seen.push(id);
out.push(id);
todo.push(m);
}
}
}
out
}
pub(crate) fn ensure_static_owners<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
class_sym: sv_lang::Symbol<'d>,
id: ClassTemplateId,
) -> Result<(), LoweringError> {
let owners: Vec<u32> = STATIC_OWNERS.with(|o| {
o.borrow()
.iter()
.filter(|&&(t, _)| t == id)
.map(|&(_, owner)| owner)
.collect()
});
if owners.is_empty() {
return Ok(());
}
let Some(ty) = class_sym.as_type() else {
return Ok(());
};
let mut chain = sv_sim_sim::netlist::base_chain(ty);
chain.push(ty);
for level in chain {
let sym = level.as_symbol();
if owners.contains(&crate::classes::static_singleton_template_id(&sym)?.0) {
crate::classes::static_singleton_handle(cx, sym)?;
}
}
Ok(())
}
pub fn register_class_program<'d>(
id: ClassTemplateId,
class_sym: sv_lang::Symbol<'d>,
layout: &ObjectLayout<'_>,
universe: &[(ClassTemplateId, sv_lang::Symbol<'d>)],
) {
let program = build_program(class_sym, layout, universe);
match &program {
Err(reason) => FAILED_PROGRAMS.with(|f| f.borrow_mut().push((id, reason.clone()))),
Ok(p) => STATIC_OWNERS.with(|o| {
let mut o = o.borrow_mut();
for owner in static_owners_of(p) {
if !o.contains(&(id, owner)) {
o.push((id, owner));
}
}
}),
}
registry::register_program(id, program);
}
pub(crate) fn restriction_list(call: sv_lang::Expression<'_>) -> Option<Vec<String>> {
use sv_lang::AstNode;
use sv_lang::nodes::{ArrayOrRandomizeMethodExpressionSyntax, IdentifierNameSyntax};
crate::classes::with_design_trees(|trees| {
for tree in trees {
let Some(node) = call.syntax(tree) else {
continue;
};
let method = ArrayOrRandomizeMethodExpressionSyntax::cast(node)?;
let args = method.args()?;
let names: Vec<String> = args
.expressions()
.iter()
.filter_map(|x| {
IdentifierNameSyntax::cast(x.syntax())?
.identifier()
.map(|t| t.text().to_string())
})
.collect();
return Some(names);
}
None
})
}
fn is_local_qualified(x: sv_lang::Expression<'_>) -> bool {
use sv_lang::AstNode;
use sv_lang::kinds::SyntaxKind;
use sv_lang::nodes::ScopedNameSyntax;
crate::classes::with_design_trees(|trees| {
for tree in trees {
let Some(node) = x.syntax(tree) else {
continue;
};
return ScopedNameSyntax::cast(node)
.is_some_and(|s| s.left().syntax().kind() == SyntaxKind::LocalScope);
}
false
})
}
pub(crate) fn find_local_occurrence<'d>(
node: sv_lang::SemNode<'d>,
uid: &sv_sim_sim::symid::SymbolId,
hops: &[sv_sim_sim::symid::SymbolId],
is_local: &dyn for<'s> Fn(sv_lang::Expression<'s>) -> bool,
) -> Option<sv_lang::Expression<'d>> {
use sv_lang::kinds::ExpressionKind;
if let Some(x) = node.as_expression() {
if hops.is_empty() {
if matches!(
x.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) && x.referenced_symbol().is_some_and(|s| s.uid() == *uid)
&& is_local(x)
{
return Some(x);
}
} else if x.kind() == ExpressionKind::MemberAccess
&& let Some(path) = sv_sim_sim::constraints::build::handle_path_uids(x)
&& path.first() == Some(uid)
&& path[1..] == *hops
{
return Some(x);
}
}
node.children()
.into_iter()
.find_map(|c| find_local_occurrence(c, uid, hops, is_local))
}
enum ArgList<'d> {
Plain,
Null,
Vars(Vec<sv_lang::Symbol<'d>>),
}
fn parse_arg_list<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
args: &[sv_lang::Expression<'d>],
chain: &[sv_sim_sim::symid::SymbolId],
) -> Result<ArgList<'d>, LoweringError> {
use sv_lang::kinds::ExpressionKind;
let fail =
|why: &str| LoweringError::unsupported_at(&format!("randomize(): {why}"), &cx.fn_sym);
if args.is_empty() {
return Ok(ArgList::Plain);
}
if args.iter().any(|a| a.kind() == ExpressionKind::NullLiteral) {
if args.len() > 1 {
return Err(fail(
"`null` cannot be combined with other arguments (LRM 18.11.1)",
));
}
return Ok(ArgList::Null);
}
let mut vars: Vec<sv_lang::Symbol<'d>> = Vec::new();
for a in args {
let sym = if matches!(
a.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
a.referenced_symbol()
} else {
None
};
let Some(sym) = sym else {
return Err(fail(
"an argument must be the name of a property of the object's class (LRM \
18.11); an element, a member or another expression is not supported",
));
};
if sym.kind() != SymbolKind::ClassProperty
|| !sym.parent().is_some_and(|p| chain.contains(&p.uid()))
{
return Err(fail(&format!(
"`{}` is not a property of the object's class (LRM 18.11)",
sym.name()
)));
}
if !property_is_random(sym) {
let unsupported = sym.value_type().is_some_and(|t| t.canonical().is_class());
if unsupported {
return Err(fail(&format!(
"`{}` is a class handle: an argument list cannot make a handle \
random (LRM 18.4: the object it designates is randomized only when the \
handle is declared `rand`)",
sym.name()
)));
}
}
if !vars.iter().any(|v| v.uid() == sym.uid()) {
vars.push(sym);
}
}
Ok(ArgList::Vars(vars))
}
fn target_of(
layout: &ObjectLayout<'_>,
levels: &[sv_lang::Symbol<'_>],
sym: sv_lang::Symbol<'_>,
) -> Result<registry::Target, String> {
let uid = sym.uid();
let (slot, home) = match layout.fields().iter().find(|f| f.sym.uid() == uid) {
Some(f) => (f.slot, Home::Instance),
None => static_state_location(levels, &uid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("`{}` has no storage in the class", sym.name()))?,
};
Ok(registry::Target {
home,
slot,
window: None,
})
}
struct SpecSite<'d> {
spec_id: u32,
frag: Program,
target_homes: Vec<Home>,
captured: Vec<(sv_lang::Expression<'d>, usize, u32)>,
capture_words: usize,
}
fn build_spec_site<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: sv_lang::Expression<'d>,
inline: Option<sv_lang::SemNode<'d>>,
arglist: &ArgList<'d>,
ty: sv_lang::Type<'d>,
) -> Result<SpecSite<'d>, LoweringError> {
let what = match (inline.is_some(), matches!(arglist, ArgList::Plain)) {
(true, true) => "randomize() with { ... }",
(true, false) => "randomize(...) with { ... }",
(false, _) => "randomize(...)",
};
let fail = |why: &str| LoweringError::unsupported_at(&format!("{what}: {why}"), &cx.fn_sym);
let class_sym = ty.as_symbol();
let layout = ObjectLayout::for_template(class_sym)?;
let levels = class_levels(ty);
let chain: Vec<sv_sim_sim::symid::SymbolId> = levels.iter().map(|l| l.uid()).collect();
let restriction = restriction_list(call);
let is_local = |x: sv_lang::Expression<'_>| -> bool {
let Some(sym) = x.referenced_symbol() else {
return false;
};
let in_object = sym.kind() == SymbolKind::ClassProperty
&& sym.parent().is_some_and(|p| chain.contains(&p.uid()));
randomize_scope::resolve(
sym.name(),
is_local_qualified(x),
restriction.as_deref(),
in_object,
) == randomize_scope::Scope::Call
};
let constraints =
match inline {
Some(node) => Some(node.as_constraint().ok_or_else(|| {
fail("internal: the inline constraints are not a constraint node")
})?),
None => None,
};
let block = constraints.map(|constraints| InlineBlock {
constraints,
is_local: &is_local,
});
let forced: &[sv_lang::Symbol<'d>] = match arglist {
ArgList::Vars(v) => v,
ArgList::Plain | ArgList::Null => &[],
};
let frag_spec = FragmentSpec {
inline: block.as_ref(),
forced,
};
let (frag, captures) =
build_fragment(class_sym, &layout, &frag_spec).map_err(|why| fail(&why))?;
let mut captured = Vec::new();
let mut specs = Vec::new();
for c in &captures {
let node = inline.ok_or_else(|| fail("internal: a capture without inline constraints"))?;
let ex = find_local_occurrence(node, &c.symbol, &c.hops, &is_local).ok_or_else(|| {
fail(&format!(
"internal: could not find the read of local `{}` to capture",
c.name
))
})?;
let cap = registry::Capture {
state: c.state,
width: c.ty.width,
};
captured.push((ex, cap.words(), c.ty.width));
specs.push(cap);
}
let capture_words = captured.iter().map(|(_, n, _)| n).sum();
let only = match arglist {
ArgList::Plain => None,
ArgList::Null => Some(Vec::new()),
ArgList::Vars(v) => Some(
v.iter()
.map(|&s| target_of(&layout, &levels, s))
.collect::<Result<Vec<_>, _>>()
.map_err(|why| fail(&why))?,
),
};
let target_homes = only.iter().flatten().map(|t| t.home).collect::<Vec<Home>>();
let spec_id = registry::register_spec(registry::Spec {
frag: frag.clone(),
captures: specs,
only,
});
Ok(SpecSite {
spec_id,
frag,
target_homes,
captured,
capture_words,
})
}
fn receiver_and_args<'d>(
e: sv_lang::Expression<'d>,
) -> (
Option<sv_lang::Expression<'d>>,
Vec<sv_lang::Expression<'d>>,
) {
let mut ops = e.operands();
if let Some(r) = e.call_this_class() {
return (Some(r), ops);
}
let first_is_class = ops
.first()
.and_then(|o| o.expr_type())
.is_some_and(|t| t.canonical().is_class());
if first_is_class {
let r = ops.remove(0);
return (Some(r), ops);
}
(None, ops)
}
pub(crate) fn lower_randomize_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let at = e.source_range().start;
let inline = e.randomize_inline_constraints();
let (recv, args) = receiver_and_args(e);
let in_method = current_this_var(cx).is_ok();
if crate::scope_randomize::is_scope_call(e, recv.is_some(), in_method) {
return crate::scope_randomize::lower_scope_randomize(cx, e);
}
sv_sim_sim::constraints::registry::note_object_site();
let static_ty = match recv {
Some(r) => r.expr_type().map(|t| t.canonical()),
None => current_this_var(cx)?.value_type().map(|t| t.canonical()),
};
let inline_site = match static_ty {
Some(ty) => {
let arglist = parse_arg_list(
cx,
&args,
&class_levels(ty)
.iter()
.map(|l| l.uid())
.collect::<Vec<sv_sim_sim::symid::SymbolId>>(),
)?;
if inline.is_none() && matches!(arglist, ArgList::Plain) {
None
} else {
Some(build_spec_site(cx, e, inline, &arglist, ty)?)
}
}
None if inline.is_some() || !args.is_empty() => {
return Err(LoweringError::unsupported_at(
"randomize(...): the receiver's class is not known",
&cx.fn_sym,
));
}
None => None,
};
let mut pairs = Vec::new();
if let Some(ty) = static_ty {
let dest = cast_template_id(ty);
let failed = FAILED_PROGRAMS.with(|f| {
f.borrow()
.iter()
.find(|(id, _)| sv_sim_sim::objects::template_is_a(*id, dest))
.map(|(_, why)| why.clone())
});
if let Some(why) = failed {
if why.contains("constraints solve integral variables only") {
if let Some(r) = recv {
receiver_handle(cx, r)?;
}
let always = cx.ctx.bool_type().const_int(1, false);
crate::stmt_shared::error_if(
cx,
always,
at,
&format!("unsupported: randomize(): {why}"),
)?;
let i32t = cx.ctx.i32_type();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
return Ok(LoweredValue::Packed(op.pack(
i32t.const_zero(),
i32t.const_zero(),
32,
cx.repr,
true,
)));
}
return Err(LoweringError::unsupported_at(
&format!("randomize(): {why}"),
&cx.fn_sym,
));
}
let mut needed: Vec<u32> = STATIC_OWNERS.with(|o| {
o.borrow()
.iter()
.filter(|(t, _)| sv_sim_sim::objects::template_is_a(*t, dest))
.map(|&(_, owner)| owner)
.collect()
});
for member in rand_member_classes(ty) {
STATIC_OWNERS.with(|o| {
for &(t, owner) in o.borrow().iter() {
if sv_sim_sim::objects::template_is_a(t, member) && !needed.contains(&owner) {
needed.push(owner);
}
}
});
}
if let Some(site) = &inline_site {
needed.extend(static_owners_of(&site.frag));
for home in &site.target_homes {
if let Home::Static(owner) = *home {
needed.push(owner);
}
}
}
if !needed.is_empty() {
let mut chain = sv_sim_sim::netlist::base_chain(ty);
chain.push(ty);
for level in chain {
let sym = level.as_symbol();
let id = static_singleton_template_id(&sym)?;
if needed.contains(&id.0) && !pairs.iter().any(|&(i, _)| i == id.0) {
pairs.push((id.0, static_singleton_handle(cx, sym)?));
}
}
let i64t = cx.ctx.i64_type();
for &owner in &needed {
if pairs.iter().any(|&(i, _)| i == owner) {
continue;
}
let name = format!("sv_class_static_{owner}");
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(inkwell::module::Linkage::Private);
g
});
let live =
cx.b.build_load(i64t, cell.as_pointer_value(), "rand_static_cell")
.unwrap()
.into_int_value();
pairs.push((owner, live));
}
}
}
let h = match recv {
Some(r) => receiver_handle(cx, r)?,
None => current_this_handle(cx)?,
};
let i64t = cx.ctx.i64_type();
let pair_words = 2 * pairs.len() as u32;
let capture_words = inline_site.as_ref().map_or(0, |s| s.capture_words) as u32;
let (count, buf) = if pair_words + capture_words == 0 {
(
i64t.const_zero(),
cx.ctx
.ptr_type(inkwell::AddressSpace::default())
.const_null(),
)
} else {
let words = pair_words + capture_words;
let buf = entry_alloca(cx, i64t.array_type(words), "rand_statics");
for (k, &(id, handle)) in pairs.iter().enumerate() {
let at = word_ptr(cx, buf, words, 2 * k as u32);
cx.b.build_store(at, i64t.const_int(u64::from(id), false))
.unwrap();
let at = word_ptr(cx, buf, words, 2 * k as u32 + 1);
cx.b.build_store(at, handle).unwrap();
}
(i64t.const_int(pairs.len() as u64, false), buf)
};
let mut spec_id = 0;
if let Some(site) = &inline_site {
spec_id = site.spec_id;
let words = pair_words + capture_words;
let mut next = pair_words;
for (ex, n, w) in &site.captured {
let pv = crate::real::to_storage(
cx,
crate::expr::lower_expr(cx, *ex)?,
*w,
"an inline constraint reads a local that is neither integral nor real",
)?;
let part = word_ptr(cx, buf, words, next);
crate::classes::store_packed_words(cx, part, pv, *n as u32);
next += *n as u32;
}
}
emit_hook_walk(cx, h, Hook::Pre, count, buf)?;
let code = obj_call(
cx,
ObjOp::Randomize,
h,
count,
i64t.const_int(u64::from(spec_id), false),
buf,
);
for outcome in (0..).map_while(Outcome::from_code) {
let Some(msg) = outcome.error_message() else {
continue;
};
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
code,
i64t.const_int(outcome.code(), false),
"randomize_err",
)
.unwrap();
crate::stmt_shared::error_if(cx, hit, at, msg)?;
}
let solved =
cx.b.build_int_compare(
IntPredicate::EQ,
code,
i64t.const_int(Outcome::Solved.code(), false),
"randomize_ok",
)
.unwrap();
if !hook_impls(Hook::Post).is_empty() {
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let post = cx.ctx.append_basic_block(f, "post_randomize");
let after = cx.ctx.append_basic_block(f, "post_randomize_done");
cx.b.build_conditional_branch(solved, post, after).unwrap();
cx.b.position_at_end(post);
emit_hook_walk(cx, h, Hook::Post, count, buf)?;
cx.b.build_unconditional_branch(after).unwrap();
cx.b.position_at_end(after);
}
let i32t = cx.ctx.i32_type();
let raw =
cx.b.build_int_z_extend(solved, i32t, "randomize_ret")
.unwrap();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
raw,
i32t.const_zero(),
32,
cx.repr,
true,
)))
}