use inkwell::IntPredicate;
use inkwell::values::IntValue;
use sv_lang::Expression;
use vj_sim::netlist::vif;
use vj_sim::symid::SymbolUid;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, lower_expr};
use crate::ops::OpBuilder;
use crate::select::{Lvalue, LvalueRoot, RootMode};
use crate::types::TypeWidth;
use crate::value::PackedValue;
pub const KEY_SHIFT: u32 = 20;
fn id_of<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
base: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let pv = lower_expr(cx, base)?.into_packed("a virtual interface", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v = op.lane_val(pv);
let id = if pv.width == 64 {
v
} else {
cx.b.build_int_z_extend_or_bit_cast(v, cx.ctx.i64_type(), "vif_id")
.unwrap()
};
let is_null =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_zero(),
"vif_null",
)
.unwrap();
let r = base.source_range();
let name = vj_sim::process::cfg::source_text_range(r)
.unwrap_or_else(|| "a virtual interface".to_string());
crate::stmt_shared::fatal_if(
cx,
is_null,
r.start,
&format!("null virtual interface {name} used"),
)?;
Ok(id)
}
pub(crate) fn instance_value<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
inst: &sv_lang::Symbol<'_>,
) -> Option<LoweredValue<'ctx>> {
let id = vif::instance_id(inst)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Some(LoweredValue::Packed(op.pack(
op.i_n(64).const_int(id, false),
op.i_n(64).const_zero(),
64,
cx.repr,
false,
)))
}
fn event_cell<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
sym: &sv_lang::Symbol<'_>,
what: &str,
) -> Result<inkwell::values::PointerValue<'ctx>, LoweringError> {
let (ptr, width, _) = *cx.locals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{what}: a virtual interface event this process cannot reach"),
sym,
)
})?;
if width != vj_sim::state::EVENT_CELL_WIDTH {
return Err(LoweringError::unsupported_at(
&format!("{what}: a virtual interface member that is not a named event"),
sym,
));
}
Ok(ptr)
}
pub(crate) fn event_trigger<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: Expression<'d>,
) -> Result<(), LoweringError> {
let targets = vif::member_targets(&target).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface event trigger", &cx.fn_sym)
})?;
let base = vif::base(&target).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface event trigger", &cx.fn_sym)
})?;
let id = id_of(cx, base)?;
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, "vif_ev_done");
let w = vj_sim::state::EVENT_CELL_WIDTH;
let packed_ty = crate::types::int_type(cx.ctx, w * 2);
for (k, t) in targets.into_iter().enumerate() {
let Some(sym) = t else { continue };
let ptr = event_cell(cx, &sym, "-> (event trigger)")?;
let arm = cx.ctx.append_basic_block(f, "vif_ev");
let next = cx.ctx.append_basic_block(f, "vif_ev_next");
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_ev_hit",
)
.unwrap();
cx.b.build_conditional_branch(hit, arm, next).unwrap();
cx.b.position_at_end(arm);
let cur =
cx.b.build_load(packed_ty, ptr, "event_cur")
.unwrap()
.into_int_value();
let packed = crate::expr::event_triggered_value(cx, cur);
cx.b.build_store(ptr, packed).unwrap();
crate::expr::write_through_store(cx, ptr);
cx.b.build_unconditional_branch(merge).unwrap();
cx.b.position_at_end(next);
}
cx.b.build_unconditional_branch(merge).unwrap();
cx.b.position_at_end(merge);
Ok(())
}
pub(crate) fn event_triggered<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let targets = vif::member_targets(&target).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface event .triggered", &cx.fn_sym)
})?;
let base = vif::base(&target).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface event .triggered", &cx.fn_sym)
})?;
let id = id_of(cx, base)?;
let w = vj_sim::state::EVENT_CELL_WIDTH;
let packed_ty = crate::types::int_type(cx.ctx, w * 2);
let mut acc = cx.ctx.bool_type().const_zero();
for (k, t) in targets.into_iter().enumerate() {
let Some(sym) = t else { continue };
let ptr = event_cell(cx, &sym, ".triggered")?;
let cur =
cx.b.build_load(packed_ty, ptr, "event_cur")
.unwrap()
.into_int_value();
let stamped = crate::expr::event_stamped_now(cx, cur);
let sel =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_ev_sel",
)
.unwrap();
let both = cx.b.build_and(stamped, sel, "vif_ev_both").unwrap();
acc = cx.b.build_or(acc, both, "vif_ev_acc").unwrap();
}
Ok(acc)
}
fn event_elem_targets<'d>(
target: &Expression<'d>,
) -> Option<(Expression<'d>, Vec<Option<sv_lang::Symbol<'d>>>)> {
if target.kind() != sv_lang::kinds::ExpressionKind::ElementSelect
|| !target.expr_type().is_some_and(|t| t.is_event())
{
return None;
}
let root = nested_root(target)?;
Some((vif::base(&root)?, vif::member_targets(&root)?))
}
fn each_event_elem<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: Expression<'d>,
f: &mut dyn FnMut(usize, crate::arrays::EventElem<'ctx>) -> Result<(), LoweringError>,
) -> Option<Result<(), LoweringError>> {
let (base, targets) = event_elem_targets(&target)?;
Some((|| {
let _once = hoist_selectors(cx, &target, targets.iter().flatten().count())?;
let id = id_of(cx, base)?;
for (k, t) in targets.iter().enumerate() {
if t.is_none() {
continue;
}
let prev = PICK.with(|p| p.replace(Some(k)));
let el = crate::arrays::resolve_event_element(cx, target);
PICK.with(|p| p.set(prev));
let Some(mut el) = el? else {
return Err(LoweringError::unsupported_at(
"an element of a virtual interface event array this process cannot reach",
&cx.fn_sym,
));
};
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_ev_hit",
)
.unwrap();
el.valid = cx.b.build_and(el.valid, hit, "vif_ev_sel").unwrap();
f(k, el)?;
}
Ok(())
})())
}
pub(crate) fn event_elem_trigger<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: Expression<'d>,
) -> Option<Result<(), LoweringError>> {
each_event_elem(cx, target, &mut |_, el| {
crate::arrays::when_valid(cx, el.valid, || {
let cur = crate::arrays::load_event_elem(cx, &el)?;
let triggered = crate::expr::event_triggered_value(cx, cur);
crate::arrays::store_event_elem(cx, &el, triggered)
})
})
}
pub(crate) fn event_elem_triggered<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: Expression<'d>,
) -> Option<Result<IntValue<'ctx>, LoweringError>> {
let acc = std::cell::Cell::new(cx.ctx.bool_type().const_zero());
let r = each_event_elem(cx, target, &mut |_, el| {
let cur = crate::arrays::load_event_elem(cx, &el)?;
let stamped = crate::expr::event_stamped_now(cx, cur);
let both = cx.b.build_and(stamped, el.valid, "vif_ev_both").unwrap();
acc.set(cx.b.build_or(acc.get(), both, "vif_ev_acc").unwrap());
Ok(())
})?;
Some(r.map(|()| acc.get()))
}
pub(crate) fn read<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
let targets = vif::member_targets(&e)?;
Some((|| {
let ty = e.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member with no type", &cx.fn_sym)
})?;
let width = ty.value_width();
let base = vif::base(&e).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})?;
let id = id_of(cx, base)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if ty.is_unpacked_array() && !vj_sim::state::storage::is_collection(&ty) {
let mut acc: Option<PackedValue<'ctx>> = None;
for (k, target) in targets.iter().enumerate() {
if target.is_none() {
continue;
}
let prev = PICK.with(|p| p.replace(Some(k)));
let v = crate::arrays::pack_array_value(cx, e);
PICK.with(|p| p.set(prev));
let v = v?;
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_hit",
)
.unwrap();
let base = acc.unwrap_or(v);
acc = Some(op.select(hit, v, base));
}
return acc.map(LoweredValue::Packed).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface array member", &cx.fn_sym)
});
}
let mut acc = PackedValue {
llvm: crate::arrays::default_value(cx, &ty),
width,
repr: cx.repr,
signed: ty.is_signed(),
};
for (k, target) in targets.iter().enumerate() {
let Some(sym) = target else {
continue;
};
let (ptr, w, signed) = *cx.locals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"a virtual interface member this process cannot reach",
sym,
)
})?;
let llvm =
cx.b.build_load(op.packed_type(w, cx.repr), ptr, "vif_m")
.unwrap()
.into_int_value();
let v = crate::select::fit_width(
&op,
PackedValue {
llvm,
width: w,
repr: cx.repr,
signed,
},
width,
);
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_hit",
)
.unwrap();
acc = op.select(hit, v, acc);
}
Ok(if ty.is_floating() {
LoweredValue::Real(crate::real::bits_to_real(cx, acc))
} else if ty.is_string() {
LoweredValue::Str(crate::strings::from_storage(cx, acc))
} else {
LoweredValue::Packed(PackedValue {
signed: ty.is_signed(),
..acc
})
})
})())
}
fn array_member_targets<'d>(e: &Expression<'d>) -> Option<Vec<Option<sv_lang::Symbol<'d>>>> {
let mut cur = *e;
if cur.kind() != sv_lang::kinds::ExpressionKind::ElementSelect {
return None;
}
while cur.kind() == sv_lang::kinds::ExpressionKind::ElementSelect {
cur = cur.select_value()?;
if !cur.expr_type()?.is_unpacked_array() {
return None;
}
}
let ty = cur.expr_type()?;
if !ty.is_unpacked_array() || vj_sim::state::storage::is_collection(&ty) {
return None;
}
vif::member_targets(&cur)
}
pub(crate) fn read_element<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
let targets = array_member_targets(&e)?;
Some((|| {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let _once = hoist_selectors(cx, &e, targets.iter().flatten().count())?;
let id = id_of(cx, root_base(cx, &e)?)?;
let mut acc: Option<PackedValue<'ctx>> = None;
for (k, target) in targets.iter().enumerate() {
let Some(sym) = target else {
continue;
};
let Some(r) = crate::arrays::resolve_element_in(cx, e, Some(sym))? else {
return Err(LoweringError::unsupported_at(
"a virtual interface array member that is not a fully indexed element",
&cx.fn_sym,
));
};
let v = crate::arrays::read_element(cx, &r)?;
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_hit",
)
.unwrap();
let base = acc.unwrap_or(PackedValue {
llvm: r.default,
..v
});
acc = Some(op.select(hit, v, base));
}
let acc = acc.ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface array member", &cx.fn_sym)
})?;
Ok(LoweredValue::Packed(acc))
})())
}
fn root_base<'d>(
cx: &LowerCtx<'_, 'd, '_>,
e: &Expression<'d>,
) -> Result<Expression<'d>, LoweringError> {
let mut cur = *e;
while cur.kind() == sv_lang::kinds::ExpressionKind::ElementSelect {
cur = cur.select_value().ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface element select", &cx.fn_sym)
})?;
}
vif::base(&cur).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})
}
thread_local! {
static PICK: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
}
pub(crate) fn picked_symbol<'d>(e: &Expression<'d>) -> Option<sv_lang::Symbol<'d>> {
let k = PICK.with(std::cell::Cell::get)?;
vif::member_targets(e)?.into_iter().nth(k)?
}
pub(crate) fn picked_lvalue<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
mode: RootMode<'_>,
) -> Option<Result<Lvalue<'ctx>, LoweringError>> {
if !vif::is_member_access(&e) {
return None;
}
let Some(sym) = picked_symbol(&e) else {
return Some(Err(LoweringError::unsupported_at(
"a virtual interface member as this kind of assignment target",
&cx.fn_sym,
)));
};
Some(candidate_lvalue(cx, &sym, mode))
}
fn candidate_lvalue<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
sym: &sv_lang::Symbol<'d>,
mode: RootMode<'_>,
) -> Result<Lvalue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let driven = (sym.kind() == sv_lang::kinds::SymbolKind::ClockVar)
.then(|| sym.initializer().and_then(|e| e.referenced_symbol()))
.flatten();
let sym = driven.as_ref().unwrap_or(sym);
let unreachable = || {
LoweringError::unsupported_at("a virtual interface member this process cannot reach", sym)
};
let (root, width, signed) = match mode {
RootMode::Shadow => {
let (ptr, w, s) = *cx.locals.get(&sym.uid()).ok_or_else(unreachable)?;
(LvalueRoot::Shadow { ptr }, w, s)
}
RootMode::Signal(signals) => {
let info = signals.get(&sym.uid()).copied().ok_or_else(unreachable)?;
(
LvalueRoot::Signal { slot: info.slot },
info.width,
info.signed,
)
}
};
Ok(Lvalue {
root,
root_width: width,
root_signed: signed,
lo: op.i_n(width).const_zero(),
width,
bad: cx.ctx.bool_type().const_zero(),
edge: None,
pad: None,
})
}
fn nested_root<'d>(lhs: &Expression<'d>) -> Option<Expression<'d>> {
use sv_lang::kinds::ExpressionKind as K;
if vif::is_member_access(lhs) {
return None;
}
let mut cur = *lhs;
loop {
let kind = cur.kind();
if matches!(kind, K::ElementSelect | K::RangeSelect) {
cur = cur.select_value()?;
} else if kind == K::MemberAccess && vif::is_member_access(&cur) {
return Some(cur);
} else if kind == K::MemberAccess {
cur = cur.operand()?;
} else {
return None;
}
}
}
type HoistedIndex = (
(u64, u64),
llvm_sys::prelude::LLVMValueRef,
u32,
crate::value::ValueRepr,
bool,
);
thread_local! {
static HOISTED: std::cell::RefCell<Vec<HoistedIndex>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub(crate) struct Hoisted(usize);
impl Drop for Hoisted {
fn drop(&mut self) {
HOISTED.with(|h| h.borrow_mut().truncate(self.0));
}
}
fn span_key(e: &Expression<'_>) -> (u64, u64) {
let r = e.source_range();
(r.start.offset, r.end.offset)
}
pub(crate) fn hoisted<'ctx>(e: &Expression<'_>) -> Option<LoweredValue<'ctx>> {
HOISTED.with(|h| {
let h = h.borrow();
if h.is_empty() {
return None;
}
let key = span_key(e);
h.iter()
.rev()
.find(|x| x.0 == key)
.map(|&(_, v, width, repr, signed)| {
let llvm = unsafe { IntValue::new(v) };
LoweredValue::Packed(PackedValue {
llvm,
width,
repr,
signed,
})
})
})
}
fn hoist_selectors<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: &Expression<'d>,
candidates: usize,
) -> Result<Hoisted, LoweringError> {
let guard = hoist_begin();
if candidates <= 1 {
return Ok(guard);
}
for p in side_effect_selectors(lhs).0.into_iter().rev() {
let pv = lower_expr(cx, p)?.into_packed("a select index", &cx.fn_sym)?;
hoist_push(&p, pv);
}
Ok(guard)
}
pub(crate) fn hoist_begin() -> Hoisted {
Hoisted(HOISTED.with(|h| h.borrow().len()))
}
pub(crate) fn hoist_push(p: &Expression<'_>, pv: PackedValue<'_>) {
use inkwell::values::AsValueRef;
let raw = pv.llvm.as_value_ref();
HOISTED.with(|h| {
h.borrow_mut()
.push((span_key(p), raw, pv.width, pv.repr, pv.signed));
});
}
pub(crate) fn side_effect_selectors<'d>(
lhs: &Expression<'d>,
) -> (Vec<Expression<'d>>, Expression<'d>) {
use sv_lang::kinds::ExpressionKind as K;
let mut parts = Vec::new();
let mut cur = *lhs;
loop {
let kind = cur.kind();
if matches!(kind, K::ElementSelect | K::RangeSelect) {
for p in [cur.selector(), cur.range_left(), cur.range_right()]
.into_iter()
.flatten()
{
if crate::classes::has_side_effects(p) {
parts.push(p);
}
}
match cur.select_value() {
Some(n) => cur = n,
None => break,
}
} else if kind == K::MemberAccess {
match cur.operand() {
Some(n) => cur = n,
None => break,
}
} else {
break;
}
}
(parts, cur)
}
pub(crate) fn nested_lvalues<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
mode: RootMode<'_>,
) -> Option<Result<Vec<Lvalue<'ctx>>, LoweringError>> {
let root = nested_root(&lhs)?;
let targets = vif::member_targets(&root)?;
Some((|| {
let _once = hoist_selectors(cx, &lhs, targets.iter().flatten().count())?;
let id = id_of(
cx,
vif::base(&root).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})?,
)?;
let mut out = Vec::new();
for (k, target) in targets.iter().enumerate() {
if target.is_none() {
continue;
}
let prev = PICK.with(|p| p.replace(Some(k)));
let lv = crate::select::resolve_lvalue(cx, lhs, mode);
PICK.with(|p| p.set(prev));
let mut lv = lv?;
let miss =
cx.b.build_int_compare(
IntPredicate::NE,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_miss",
)
.unwrap();
lv.bad = cx.b.build_or(lv.bad, miss, "vif_nested_skip").unwrap();
out.push(lv);
}
Ok(out)
})())
}
pub(crate) fn lvalues<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
mode: RootMode<'_>,
) -> Option<Result<Vec<Lvalue<'ctx>>, LoweringError>> {
let targets = vif::member_targets(&lhs)?;
Some((|| {
let base = vif::base(&lhs).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})?;
let id = id_of(cx, base)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let mut out = Vec::new();
for (k, target) in targets.iter().enumerate() {
let Some(sym) = target else {
continue;
};
let driven = (sym.kind() == sv_lang::kinds::SymbolKind::ClockVar)
.then(|| sym.initializer().and_then(|e| e.referenced_symbol()))
.flatten();
let sym = driven.as_ref().unwrap_or(sym);
let unreachable = || {
LoweringError::unsupported_at(
"a virtual interface member this process cannot reach",
sym,
)
};
let (root, width, signed) = match mode {
RootMode::Shadow => {
let (ptr, w, s) = *cx.locals.get(&sym.uid()).ok_or_else(unreachable)?;
(LvalueRoot::Shadow { ptr }, w, s)
}
RootMode::Signal(signals) => {
let info = signals.get(&sym.uid()).copied().ok_or_else(unreachable)?;
(
LvalueRoot::Signal { slot: info.slot },
info.width,
info.signed,
)
}
};
let bad =
cx.b.build_int_compare(
IntPredicate::NE,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_miss",
)
.unwrap();
out.push(Lvalue {
root,
root_width: width,
root_signed: signed,
lo: op.i_n(width).const_zero(),
width,
bad,
edge: None,
pad: None,
});
}
Ok(out)
})())
}
pub(crate) fn edge_clock_slot<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
signals: &std::collections::HashMap<vj_sim::symid::SymbolId, crate::process_lower::SignalSlot>,
lhs: Expression<'d>,
block_counter: bool,
) -> Option<Result<IntValue<'ctx>, LoweringError>> {
let targets = vif::member_targets(&lhs)?;
Some((|| {
let base = vif::base(&lhs).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})?;
let id = id_of(cx, base)?;
let i32t = cx.ctx.i32_type();
let mut acc = i32t.const_zero();
for (k, target) in targets.iter().enumerate() {
let Some(sym) = target else {
continue;
};
let slot_sym = if block_counter {
sym.parent().ok_or_else(|| {
LoweringError::unsupported_at("a clock variable with no clocking block", sym)
})?
} else {
let ev = sym
.parent()
.and_then(|cb| cb.clocking_block_event())
.and_then(|n| n.as_timing_control())
.ok_or_else(|| {
LoweringError::unsupported_at("a clocking block with no event", sym)
})?;
let clock = vj_sim::sva::sampled::clock_event_of(ev)
.map_err(|e| LoweringError::unsupported_at(&e.0, sym))?;
clock.signal.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at(
"an edge output skew whose clocking event is not a plain signal",
sym,
)
})?
};
let csym = slot_sym;
let slot = signals
.get(&csym.uid())
.ok_or_else(|| {
LoweringError::unsupported_at(
"an edge output skew through a virtual interface whose clock is not a \
module-level signal",
&csym,
)
})?
.slot;
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_clk_hit",
)
.unwrap();
acc =
cx.b.build_select(hit, i32t.const_int(u64::from(slot), false), acc, "vif_clk")
.unwrap()
.into_int_value();
}
Ok(acc)
})())
}
pub(crate) fn event_base<'d>(e: &Expression<'d>) -> Option<Expression<'d>> {
vif::is_member_access(e).then(|| vif::base(e)).flatten()
}
pub(crate) fn event_elem_root<'d>(e: &Expression<'d>) -> Option<Expression<'d>> {
event_elem_targets(e)?;
nested_root(e)
}
#[allow(clippy::type_complexity)]
pub(crate) fn event_elem_candidates<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: Expression<'d>,
) -> Option<Result<Vec<(crate::arrays::EventElem<'ctx>, u64)>, LoweringError>> {
let mut out = Vec::new();
let r = each_event_elem(cx, target, &mut |k, el| {
out.push((el, k as u64 + 1));
Ok(())
})?;
Some(r.map(|()| out))
}
pub(crate) fn suspend_key_multi<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
vars: &[(Expression<'d>, usize)],
resume: u32,
) -> Result<IntValue<'ctx>, LoweringError> {
let i64t = cx.ctx.i64_type();
let mut ok = cx.ctx.bool_type().const_int(1, false);
let mut sum = i64t.const_zero();
let mut stride = 1u64;
for (base, n) in vars {
let id = id_of(cx, *base)?;
let off =
cx.b.build_int_sub(id, i64t.const_int(1, false), "vif_id_off")
.unwrap();
let in_range =
cx.b.build_int_compare(
IntPredicate::ULT,
off,
i64t.const_int(*n as u64, false),
"vif_in_range",
)
.unwrap();
ok = cx.b.build_and(ok, in_range, "vif_all_in_range").unwrap();
let scaled =
cx.b.build_int_mul(off, i64t.const_int(stride, false), "vif_scaled")
.unwrap();
sum = cx.b.build_int_add(sum, scaled, "vif_sum").unwrap();
stride *= *n as u64;
}
let code =
cx.b.build_int_add(sum, i64t.const_int(1, false), "vif_code")
.unwrap();
let code =
cx.b.build_select(ok, code, i64t.const_zero(), "vif_key_id")
.unwrap()
.into_int_value();
let code =
cx.b.build_int_truncate(code, cx.ctx.i32_type(), "vif_key_id32")
.unwrap();
let shifted =
cx.b.build_left_shift(
code,
cx.ctx.i32_type().const_int(u64::from(KEY_SHIFT), false),
"vif_key_hi",
)
.unwrap();
Ok(cx
.b
.build_int_add(
shifted,
cx.ctx.i32_type().const_int(u64::from(resume), false),
"vif_key",
)
.unwrap())
}
pub(crate) fn suspend_key<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
base: Expression<'d>,
resume: u32,
candidates: usize,
) -> Result<IntValue<'ctx>, LoweringError> {
let id = id_of(cx, base)?;
let i64t = cx.ctx.i64_type();
let in_range =
cx.b.build_int_compare(
IntPredicate::ULE,
id,
i64t.const_int(candidates as u64, false),
"vif_in_range",
)
.unwrap();
let id =
cx.b.build_select(in_range, id, i64t.const_zero(), "vif_key_id")
.unwrap()
.into_int_value();
let id =
cx.b.build_int_truncate(id, cx.ctx.i32_type(), "vif_key_id32")
.unwrap();
let shifted =
cx.b.build_left_shift(
id,
cx.ctx.i32_type().const_int(u64::from(KEY_SHIFT), false),
"vif_key_hi",
)
.unwrap();
Ok(cx
.b
.build_int_add(
shifted,
cx.ctx.i32_type().const_int(u64::from(resume), false),
"vif_key",
)
.unwrap())
}
#[allow(clippy::type_complexity)]
pub(crate) fn call_arms<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: Expression<'d>,
) -> Option<
Result<
(
Vec<(sv_lang::Symbol<'d>, inkwell::basic_block::BasicBlock<'ctx>)>,
inkwell::basic_block::BasicBlock<'ctx>,
),
LoweringError,
>,
> {
let (base, targets) = vif::call_targets(&call)?;
Some((|| {
let id = id_of(cx, base)?;
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, "vif_call_done");
let mut arms = Vec::new();
for (k, target) in targets.into_iter().enumerate() {
let Some(sym) = target else {
continue;
};
let arm = cx.ctx.append_basic_block(f, "vif_call");
let next = cx.ctx.append_basic_block(f, "vif_call_next");
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_call_hit",
)
.unwrap();
cx.b.build_conditional_branch(hit, arm, next).unwrap();
cx.b.position_at_end(next);
arms.push((sym, arm));
}
cx.b.build_unconditional_branch(merge).unwrap();
Ok((arms, merge))
})())
}
pub(crate) fn call_arms_apply(call: &Expression<'_>) -> bool {
vif::call_targets(call).is_some()
}
pub(crate) fn assign_array<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
rhs: Expression<'d>,
) -> Option<Result<(), LoweringError>> {
let ty = lhs.expr_type()?;
if !ty.is_unpacked_array() || vj_sim::state::storage::is_collection(&ty) {
return None;
}
let root = if vif::is_member_access(&lhs) {
lhs
} else {
nested_root(&lhs)?
};
let targets = vif::member_targets(&root)?;
Some((|| {
let staged = if targets.iter().flatten().count() > 1
&& crate::classes::has_side_effects(rhs)
{
let shape = crate::arrays::array_shape(&ty).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface array assignment", &cx.fn_sym)
})?;
if !crate::collections::handle_offsets(&shape.elem).is_empty() {
return Err(LoweringError::unsupported_at(
"a virtual interface array assignment of reference-holding elements whose \
value has a side effect (it would run once per candidate instance)",
&cx.fn_sym,
));
}
if let Some(call) = crate::arrays::array_call_value(cx, rhs)? {
Some(call)
} else {
let tmp = crate::expr::entry_alloca(
cx,
crate::arrays::local_array_type(cx.ctx, &shape, cx.repr),
"vif_arr_once",
);
let tmp_ref =
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(tmp));
crate::arrays::assign_array_into_fresh(cx, tmp_ref, &shape, rhs)?;
Some((tmp_ref, shape))
}
} else {
None
};
let _once = hoist_selectors(cx, &lhs, targets.iter().flatten().count())?;
let id = id_of(
cx,
vif::base(&root).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})?,
)?;
for (k, target) in targets.iter().enumerate() {
if target.is_none() {
continue;
}
let prev = PICK.with(|p| p.replace(Some(k)));
let dst = crate::arrays::array_target(cx, lhs);
PICK.with(|p| p.set(prev));
let Some((mut dst, shape)) = dst? else {
return Err(LoweringError::unsupported_at(
"a virtual interface array member this process cannot reach",
&cx.fn_sym,
));
};
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
cx.ctx.i64_type().const_int(k as u64 + 1, false),
"vif_hit",
)
.unwrap();
dst.valid = cx.b.build_and(dst.valid, hit, "vif_arr_sel").unwrap();
match &staged {
Some((src, src_shape)) => {
crate::arrays::copy_array(cx, dst, &shape, *src, src_shape)?;
}
None => crate::arrays::assign_array(cx, dst, &shape, rhs)?,
}
}
Ok(())
})())
}
pub(crate) fn array_values<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Option<Result<Vec<PackedValue<'ctx>>, LoweringError>> {
use sv_lang::kinds::ExpressionKind as K;
if !matches!(e.kind(), K::ElementSelect | K::RangeSelect) {
return None;
}
let root = nested_root(&e)?;
let targets = vif::member_targets(&root)?;
Some((|| {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let _once = hoist_selectors(cx, &e, targets.iter().flatten().count())?;
let id = id_of(
cx,
vif::base(&root).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})?,
)?;
let i64t = cx.ctx.i64_type();
let mut acc: Option<Vec<PackedValue<'ctx>>> = None;
for (k, target) in targets.iter().enumerate() {
if target.is_none() {
continue;
}
let prev = PICK.with(|p| p.replace(Some(k)));
let src = crate::arrays::array_target(cx, e);
PICK.with(|p| p.set(prev));
let Some((r, shape)) = src? else {
return Err(LoweringError::unsupported_at(
"a virtual interface array member this process cannot reach",
&cx.fn_sym,
));
};
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
id,
i64t.const_int(k as u64 + 1, false),
"vif_hit",
)
.unwrap();
let mut rows = Vec::new();
for row in 0..shape.rows() {
rows.push(crate::arrays::load_row(
cx,
&r,
i64t.const_int(row, false),
&shape.elem,
)?);
}
acc = Some(match acc {
None => rows,
Some(prev) => rows
.into_iter()
.zip(prev)
.map(|(v, p)| op.select(hit, v, p))
.collect(),
});
}
acc.ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface array member", &cx.fn_sym)
})
})())
}