use inkwell::IntPredicate;
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use sv_sim_runtime::collections_rt::CollOp;
use sv_sim_runtime::objects_rt::ObjOp;
use sv_sim_sim::constraints::build::{Builder, Declared};
use sv_sim_sim::constraints::registry::{self, Home, Outcome, ProgState, ProgVar, Program};
use sv_sim_sim::objects::FieldSlot;
use sv_sim_sim::symid::SymbolUid;
use crate::classes::{load_packed_words, obj_call, store_packed_words, word_ptr};
use crate::classes_randomize::{find_local_occurrence, restriction_list};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, entry_alloca};
use crate::ops::OpBuilder;
pub(crate) fn is_scope_call(
call: sv_lang::Expression<'_>,
has_receiver: bool,
in_method: bool,
) -> bool {
!has_receiver && (!in_method || is_std_qualified(call))
}
fn is_std_qualified(call: sv_lang::Expression<'_>) -> bool {
use sv_lang::AstNode;
use sv_lang::kinds::SyntaxKind;
use sv_lang::nodes::{
ArrayOrRandomizeMethodExpressionSyntax, IdentifierNameSyntax, InvocationExpressionSyntax,
ScopedNameSyntax,
};
crate::classes::with_design_trees(|trees| {
for tree in trees {
let Some(node) = call.syntax(tree) else {
continue;
};
let invocation = match ArrayOrRandomizeMethodExpressionSyntax::cast(node) {
Some(with) => with.method().syntax(),
None => node,
};
let callee = match InvocationExpressionSyntax::cast(invocation) {
Some(inv) => inv.left().syntax(),
None => invocation,
};
if callee.kind() != SyntaxKind::ScopedName {
return false;
}
return ScopedNameSyntax::cast(callee)
.and_then(|s| IdentifierNameSyntax::cast(s.left().syntax()))
.and_then(|n| n.identifier())
.is_some_and(|t| t.text() == "std");
}
false
})
}
struct ScopeVar<'d> {
expr: sv_lang::Expression<'d>,
sym: sv_lang::Symbol<'d>,
real: bool,
aggregate: bool,
coll: bool,
}
impl ScopeVar<'_> {
fn is_array(&self) -> bool {
self.aggregate
&& self.sym.kind() != SymbolKind::ClassProperty
&& self
.sym
.value_type()
.is_some_and(|t| t.canonical().is_unpacked_array())
}
}
fn parse_vars<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
args: &[sv_lang::Expression<'d>],
) -> Result<Vec<ScopeVar<'d>>, LoweringError> {
let fail =
|why: &str| LoweringError::unsupported_at(&format!("std::randomize(): {why}"), &cx.fn_sym);
let mut vars: Vec<ScopeVar<'d>> = Vec::new();
for &arg in args {
let a = match arg.left() {
Some(lhs) if arg.kind() == ExpressionKind::Assignment => lhs,
_ => arg,
};
if a.kind() == ExpressionKind::NullLiteral {
return Err(fail(
"`null` is illegal in a scope randomize (LRM 18.7, footnote 43); call it with \
no argument to check its constraints",
));
}
let sym = if a.kind() == ExpressionKind::NamedValue {
a.referenced_symbol()
} else {
None
};
let Some(sym) = sym else {
return Err(fail(
"an argument must be the plain name of a variable (LRM 18.12); an element, a \
member, a hierarchical name or another expression is not supported",
));
};
match sym.kind() {
SymbolKind::Variable | SymbolKind::FormalArgument => {}
SymbolKind::ClassProperty => {}
other => {
return Err(fail(&format!(
"`{}` is a {other:?}, not a variable (LRM 18.12)",
sym.name()
)));
}
}
let real = sym.value_type().is_some_and(|t| t.is_floating());
let integral = sym
.value_type()
.is_some_and(|t| t.is_integral() && !t.is_floating());
let aggregate = sym
.value_type()
.is_some_and(sv_sim_sim::constraints::build::is_aggregate_type);
let coll_ty = sym
.value_type()
.and_then(|t| crate::collections::coll_of(&t));
let coll = coll_ty.is_some_and(|c| !c.assoc);
if let Some(c) = coll_ty {
if c.assoc {
return Err(fail(&format!(
"`{}` is an associative array: its keys are never randomized (LRM 18.4) and \
a scope randomize of one is not supported",
sym.name()
)));
}
if c.bound != 0 {
return Err(fail(&format!(
"`{}` is a bounded queue: a scope randomize does not honor its bound \
(LRM 7.10.5), use an unbounded queue or a dynamic array",
sym.name()
)));
}
if c.obj {
return Err(fail(&format!(
"`{}` holds class handles, which cannot be randomized (LRM 18.4)",
sym.name()
)));
}
}
if !integral && !real && !aggregate && !coll {
return Err(fail(&format!(
"`{}` is not an integral, real, unpacked struct, fixed-size array, queue or \
dynamic array variable; a string or class handle cannot be randomized by a \
scope randomize in this build",
sym.name()
)));
}
if !vars.iter().any(|v| v.sym.uid() == sym.uid()) {
vars.push(ScopeVar {
expr: a,
sym,
real,
aggregate,
coll,
});
}
}
Ok(vars)
}
fn words_of(width: u32) -> usize {
(2 * width as usize).div_ceil(64).max(1)
}
struct ScopeSite<'d> {
spec_id: u32,
captured: Vec<(sv_lang::Expression<'d>, usize, u32)>,
capture_words: usize,
out_words: usize,
slots: Vec<FieldSlot>,
real_width: Vec<Option<u32>>,
arrays: Vec<SiteArray<'d>>,
}
struct SiteArray<'d> {
word: usize,
expr: sv_lang::Expression<'d>,
rand: bool,
}
fn build_site<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
inline: Option<sv_lang::SemNode<'d>>,
vars: &[ScopeVar<'d>],
) -> Result<ScopeSite<'d>, LoweringError> {
let fail =
|why: &str| LoweringError::unsupported_at(&format!("std::randomize(): {why}"), &cx.fn_sym);
let listed: Vec<sv_sim_sim::symid::SymbolId> = vars.iter().map(|v| v.sym.uid()).collect();
let is_local = |x: sv_lang::Expression<'_>| -> bool {
x.referenced_symbol()
.is_some_and(|s| !listed.contains(&s.uid()))
};
let mut decl: Vec<Vec<Declared>> = Vec::new();
let mut array_id: Vec<Option<u32>> = Vec::new();
let built = crate::classes::with_design_trees(|trees| {
let mut b = Builder::new(trees);
for v in vars {
if v.coll {
let id = b
.declare_array_forced(v.sym)
.map_err(|e| format!("`{}`: {e}", v.sym.name()))?;
decl.push(Vec::new());
array_id.push(Some(id));
continue;
}
array_id.push(None);
let ds = if v.aggregate {
b.declare_aggregate_forced(v.sym)
} else if v.real {
b.declare_real_forced(v.sym)
.map(|i| vec![Declared::Real(i)])
} else {
b.declare_var_forced(v.sym).map(|i| vec![Declared::Int(i)])
}
.map_err(|e| format!("`{}`: {e}", v.sym.name()))?;
decl.push(ds);
}
if let Some(node) = inline {
let block = node
.as_constraint()
.ok_or_else(|| "internal: the constraints are not a constraint node".to_string())?;
b.set_local_classifier(is_local);
b.add_constraint(block)
.map_err(|e| format!("constraint: {e}"))?;
}
b.finish().map_err(|e| e.to_string())
})
.map_err(|why| fail(&why))?;
let set = built;
let mut offset = 0usize;
let mut slots = Vec::with_capacity(vars.len());
let mut real_width = Vec::with_capacity(vars.len());
let mut owner_int: Vec<usize> = vec![usize::MAX; set.vars.len()];
let mut owner_real: Vec<usize> = vec![usize::MAX; set.real_vars.len()];
for (k, (v, ds)) in vars.iter().zip(&decl).enumerate() {
if v.coll {
slots.push(FieldSlot { offset, words: 1 });
offset += 1;
real_width.push(None);
continue;
}
let (width, rw) = if v.aggregate {
let w = v
.sym
.value_type()
.and_then(|t| sv_sim_sim::state::storage::storage_width(&t))
.ok_or_else(|| fail(&format!("`{}` has no stored width", v.sym.name())))?;
(w as u32, None)
} else {
match ds[0] {
Declared::Int(i) => (set.vars[i as usize].ty.width, None),
Declared::Real(i) => {
let w = set.real_vars[i as usize].kind.width();
(w, Some(w))
}
}
};
for d in ds {
let (owner, i) = match *d {
Declared::Int(i) => (&mut owner_int, i),
Declared::Real(i) => (&mut owner_real, i),
};
*owner
.get_mut(i as usize)
.ok_or_else(|| fail("internal: random variable ids out of step"))? = k;
}
let slot = FieldSlot {
offset,
words: words_of(width),
};
offset += slot.words;
slots.push(slot);
real_width.push(rw);
}
if owner_int.contains(&usize::MAX) || owner_real.contains(&usize::MAX) {
return Err(fail("internal: random variable ids out of step"));
}
let prog_vars: Vec<ProgVar> = set
.vars
.iter()
.enumerate()
.map(|(i, v)| ProgVar {
ty: v.ty,
slot: slots[owner_int[i]],
home: Home::Instance,
prop_index: None,
domain_conjunct: set.domain_conjunct[i],
randc: v.randc,
window: v.window,
})
.collect();
let real_vars: Vec<registry::ProgRealVar> = set
.real_vars
.iter()
.enumerate()
.map(|(i, v)| registry::ProgRealVar {
kind: v.kind,
slot: slots[owner_real[i]],
home: Home::Instance,
prop_index: None,
window: v.window,
})
.collect();
let mut prog_arrays = Vec::with_capacity(set.arrays.len());
let mut site_arrays = Vec::with_capacity(set.arrays.len());
for (ai, a) in set.arrays.iter().enumerate() {
let arg = array_id.iter().position(|id| *id == Some(ai as u32));
let (word, expr) = match arg {
Some(k) => (slots[k].offset, vars[k].expr),
None => {
let node = inline
.ok_or_else(|| fail("internal: a collection read without constraints"))?;
let ex =
find_local_occurrence(node, &a.symbol, &[], &is_local).ok_or_else(|| {
fail(&format!(
"internal: could not find the read of `{}` to capture",
a.name
))
})?;
offset += 1;
(offset - 1, ex)
}
};
prog_arrays.push(registry::ProgArray {
elem_ty: a.elem_ty,
elem_words: (2 * a.elem_ty.width as usize).div_ceil(64).max(1),
slot: FieldSlot {
offset: word,
words: 1,
},
home: Home::Instance,
rand: a.rand,
prop_index: None,
elem_domain: a.elem_domain.clone(),
assoc: a.assoc,
});
site_arrays.push(SiteArray {
word,
expr,
rand: a.rand,
});
}
let mut states = Vec::with_capacity(set.states.len());
for (k, s) in set.states.iter().enumerate() {
if !set.captures.iter().any(|c| c.state as usize == k) {
return Err(fail(&format!(
"internal: `{}` is read as object state in a scope randomize",
s.name
)));
}
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,
});
}
let frag = Program {
vars: prog_vars,
states,
conjuncts: set.conjuncts,
before: set.before,
dists: set.dists,
funcs: set.funcs,
softs: set.softs,
real_vars,
real: set.real,
arrays: prog_arrays,
..Program::default()
};
let mut captured = Vec::new();
let mut caps = Vec::new();
for c in &set.captures {
let node = inline.ok_or_else(|| fail("internal: a capture without 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 `{}` to capture",
c.name
))
})?;
let cap = registry::Capture {
state: c.state,
width: c.ty.width,
};
captured.push((ex, cap.words(), c.ty.width));
caps.push(cap);
}
let capture_words = captured.iter().map(|(_, n, _)| n).sum();
let spec_id = registry::register_spec(registry::Spec {
frag,
captures: caps,
only: None,
});
Ok(ScopeSite {
spec_id,
captured,
capture_words,
out_words: offset,
slots,
real_width,
arrays: site_arrays,
})
}
pub(crate) fn lower_scope_randomize<'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();
if restriction_list(e).is_some_and(|l| !l.is_empty()) {
return Err(LoweringError::unsupported_at(
"std::randomize(): a parenthesized identifier list after `with` is illegal in a \
scope randomize (LRM 18.7, footnote 43)",
&cx.fn_sym,
));
}
let vars = parse_vars(cx, &e.operands())?;
let site = build_site(cx, inline, &vars)?;
let i64t = cx.ctx.i64_type();
let capture_words = site.capture_words as u32;
let total = capture_words + site.out_words as u32;
let buf = if total == 0 {
cx.ctx
.ptr_type(inkwell::AddressSpace::default())
.const_null()
} else {
entry_alloca(cx, i64t.array_type(total), "scope_rand")
};
let mut next = 0u32;
for (ex, n, w) in &site.captured {
let pv = crate::real::to_storage(
cx,
crate::expr::lower_expr(cx, *ex)?,
*w,
"a constraint reads a variable that is neither integral nor real",
)?;
let part = word_ptr(cx, buf, total, next);
store_packed_words(cx, part, pv, *n as u32);
next += *n as u32;
}
if vars.iter().any(|v| v.aggregate) && total > 0 {
for k in 0..total {
cx.b.build_store(word_ptr(cx, buf, total, k), i64t.const_zero())
.unwrap();
}
}
for (v, slot) in vars.iter().zip(&site.slots) {
if v.aggregate {
let cur = if v.is_array() {
crate::arrays::pack_array_value(cx, v.expr)?
} else {
crate::expr::lower_expr(cx, v.expr)?.into_packed(
"a scope randomize aggregate is not a flat value",
&cx.fn_sym,
)?
};
let part = word_ptr(cx, buf, total, capture_words + slot.offset as u32);
store_packed_words(cx, part, cur, slot.words as u32);
}
}
for arr in &site.arrays {
let c = crate::collections::coll_of_expr(&arr.expr).ok_or_else(|| {
LoweringError::unsupported_at(
"std::randomize(): internal: a collection argument without a collection type",
&cx.fn_sym,
)
})?;
let h = crate::collections::handle_of(cx, arr.expr)?;
let copy = crate::collections::call(
cx,
&c,
CollOp::Clone,
h,
crate::collections::Args::default(),
);
cx.b.build_store(
word_ptr(cx, buf, total, capture_words + arr.word as u32),
copy,
)
.unwrap();
}
let code = obj_call(
cx,
ObjOp::ScopeRandomize,
i64t.const_zero(),
i64t.const_int(site.out_words as u64, false),
i64t.const_int(u64::from(site.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),
"scope_rand_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),
"scope_rand_ok",
)
.unwrap();
if !site.arrays.is_empty() {
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let drop_bb = cx.ctx.append_basic_block(f, "scope_rand_drop");
let keep_bb = cx.ctx.append_basic_block(f, "scope_rand_keep");
cx.b.build_conditional_branch(solved, keep_bb, drop_bb)
.unwrap();
for (bb, only_state) in [(drop_bb, false), (keep_bb, true)] {
cx.b.position_at_end(bb);
for arr in site.arrays.iter().filter(|a| a.rand != only_state) {
let c = crate::collections::coll_of_expr(&arr.expr).expect("checked above");
let w =
cx.b.build_load(
i64t,
word_ptr(cx, buf, total, capture_words + arr.word as u32),
"scope_rand_copy",
)
.unwrap()
.into_int_value();
crate::collections::call(
cx,
&c,
CollOp::Release,
w,
crate::collections::Args::default(),
);
}
if bb == drop_bb {
cx.b.build_unconditional_branch(keep_bb).unwrap();
}
}
}
if !vars.is_empty() {
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let write = cx.ctx.append_basic_block(f, "scope_rand_write");
let done = cx.ctx.append_basic_block(f, "scope_rand_done");
cx.b.build_conditional_branch(solved, write, done).unwrap();
cx.b.position_at_end(write);
for arr in site.arrays.iter().filter(|a| a.rand) {
let w =
cx.b.build_load(
i64t,
word_ptr(cx, buf, total, capture_words + arr.word as u32),
"scope_rand_new",
)
.unwrap()
.into_int_value();
let new = LoweredValue::Packed(crate::collections::handle_value(cx, w));
if crate::classes::is_class_field_lvalue(arr.expr) {
crate::classes::try_assign_field(cx, arr.expr, new)?;
} else {
crate::collections::store_releasing(cx, arr.expr, new)?;
}
}
for ((v, slot), rw) in vars.iter().zip(&site.slots).zip(&site.real_width) {
if v.coll {
continue;
}
if let Some(width) = *rw {
let part = word_ptr(cx, buf, total, capture_words + slot.offset as u32);
let pv = load_packed_words(cx, part, width, cx.repr, slot.words as u32, false);
crate::expr::store_to(
cx,
v.expr,
LoweredValue::Real(crate::real::bits_to_real(cx, pv)),
)?;
continue;
}
if v.is_array() {
let width = v
.sym
.value_type()
.and_then(|t| sv_sim_sim::state::storage::storage_width(&t))
.unwrap_or(0) as u32;
let part = word_ptr(cx, buf, total, capture_words + slot.offset as u32);
let pv = load_packed_words(cx, part, width, cx.repr, slot.words as u32, false);
crate::arrays::store_array_from_packed(cx, v.expr, pv)?;
continue;
}
let cur = crate::expr::lower_expr(cx, v.expr)?.into_packed(
"a scope randomize variable is not an integral value",
&cx.fn_sym,
)?;
let part = word_ptr(cx, buf, total, capture_words + slot.offset as u32);
let pv =
load_packed_words(cx, part, cur.width, cur.repr, slot.words as u32, cur.signed);
crate::expr::store_to(cx, v.expr, LoweredValue::Packed(pv))?;
}
cx.b.build_unconditional_branch(done).unwrap();
cx.b.position_at_end(done);
}
let i32t = cx.ctx.i32_type();
let raw =
cx.b.build_int_z_extend(solved, i32t, "scope_rand_ret")
.unwrap();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
raw,
i32t.const_zero(),
32,
cx.repr,
true,
)))
}