use inkwell::AddressSpace;
use inkwell::IntPredicate;
use inkwell::module::Linkage;
use inkwell::types::StructType;
use inkwell::values::{IntValue, PointerValue};
use sv_lang::kinds::ExpressionKind;
use sv_lang::{Expression, KnownSystemName, Type};
use sv_sim_runtime::collections_rt::{CollOp, SV_RT_COLL_SYMBOL};
use sv_sim_sim::symid::SymbolUid;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, entry_alloca, lower_expr, store_to};
use crate::ops::OpBuilder;
use crate::types::TypeWidth;
use crate::value::{PackedValue, ValueRepr};
#[derive(Clone, Copy)]
pub(crate) struct Coll<'d> {
pub elem: Type<'d>,
pub width: u32,
pub words: u32,
pub assoc: bool,
pub queue: bool,
pub bound: u32,
pub key_kind: u32,
pub key_width: u32,
pub obj: bool,
pub leaky: bool,
}
impl Coll<'_> {
pub(crate) fn key_words(&self) -> u32 {
(2 * self.key_width).div_ceil(64).max(1)
}
}
pub(crate) fn coll_of<'d>(ty: &Type<'d>) -> Option<Coll<'d>> {
if !sv_sim_sim::state::storage::is_collection(ty) {
return None;
}
let elem = ty.element_type()?;
let obj = elem.is_class();
assert!(
handle_offsets(&elem).iter().all(|s| s.guards.is_empty()),
"a queue, dynamic or associative array whose elements are tagged unions with a class or collection member"
);
let leaky = !obj
&& handle_offsets(&elem)
.iter()
.any(|s| matches!(s.kind, HandleKind::Obj));
let width = if obj { 64 } else { elem.value_width().max(1) };
let assoc = ty.is_associative_array();
let (key_kind, key_width) = if assoc {
match ty.associative_index_type() {
Some(k) if k.is_string() => (2, 64),
Some(k) if k.is_class() => (3, 64),
Some(k) => (u32::from(k.is_signed()), k.value_width().max(1)),
None => (0, 64),
}
} else {
(0, 0)
};
Some(Coll {
elem,
width,
words: (2 * width).div_ceil(64).max(1),
assoc,
queue: ty.is_queue(),
bound: if ty.is_queue() {
ty.queue_max_bound()
} else {
0
},
key_kind,
key_width,
obj,
leaky,
})
}
pub(crate) fn coll_of_expr<'d>(e: &Expression<'d>) -> Option<Coll<'d>> {
coll_of(&e.expr_type()?)
}
#[derive(Default)]
pub(crate) struct Args<'ctx> {
pub index: Option<IntValue<'ctx>>,
pub index2: Option<IntValue<'ctx>>,
pub h2: Option<IntValue<'ctx>>,
pub val: Option<PointerValue<'ctx>>,
pub dflt: Option<PointerValue<'ctx>>,
pub key: Option<PointerValue<'ctx>>,
}
fn args_type<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> StructType<'ctx> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ptr = cx.ctx.ptr_type(AddressSpace::default());
cx.ctx.struct_type(
&[
i32t.into(),
i32t.into(),
i32t.into(),
i32t.into(),
i32t.into(),
i64t.into(),
i64t.into(),
i64t.into(),
ptr.into(),
ptr.into(),
ptr.into(),
],
false,
)
}
pub(crate) fn call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
op: CollOp,
h: IntValue<'ctx>,
a: Args<'ctx>,
) -> IntValue<'ctx> {
raw_call(
cx,
[
c.words,
u32::from(c.assoc),
c.key_kind,
c.key_width,
u32::from(c.obj),
],
op,
h,
a,
)
}
pub(crate) fn raw_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
shape: [u32; 5],
op: CollOp,
h: IntValue<'ctx>,
a: Args<'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 = args_type(cx);
let f = cx
.module
.get_function(SV_RT_COLL_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_COLL_SYMBOL,
i64t.fn_type(&[i32t.into(), i64t.into(), ptr.into()], false),
Some(Linkage::External),
)
});
let buf = entry_alloca(cx, st, "coll_args");
let fields: [inkwell::values::BasicValueEnum<'ctx>; 11] = [
i32t.const_int(u64::from(shape[0]), false).into(),
i32t.const_int(u64::from(shape[1]), false).into(),
i32t.const_int(u64::from(shape[2]), false).into(),
i32t.const_int(u64::from(shape[3]), false).into(),
i32t.const_int(u64::from(shape[4]), false).into(),
a.index.unwrap_or(i64t.const_zero()).into(),
a.index2.unwrap_or(i64t.const_zero()).into(),
a.h2.unwrap_or(i64t.const_zero()).into(),
a.val.unwrap_or(ptr.const_null()).into(),
a.dflt.unwrap_or(ptr.const_null()).into(),
a.key.unwrap_or(ptr.const_null()).into(),
];
for (i, v) in fields.into_iter().enumerate() {
let p =
cx.b.build_struct_gep(st, buf, i as u32, "coll_arg")
.unwrap();
cx.b.build_store(p, v).unwrap();
}
cx.b.build_call(
f,
&[
i32t.const_int(op as u64, false).into(),
h.into(),
buf.into(),
],
"coll_call",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_coll returns a value")
.into_int_value()
}
pub(crate) fn op<'a, 'ctx>(cx: &'a LowerCtx<'ctx, '_, '_>) -> OpBuilder<'a, 'ctx> {
OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
}
pub(crate) fn four_state<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
pv: PackedValue<'ctx>,
) -> IntValue<'ctx> {
if pv.repr == ValueRepr::FourState {
return pv.llvm;
}
let o = op(cx);
o.pack(
o.lane_val(pv),
o.lane_unk(pv),
pv.width,
ValueRepr::FourState,
pv.signed,
)
.llvm
}
pub(crate) fn from_four_state<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
raw: IntValue<'ctx>,
width: u32,
signed: bool,
) -> PackedValue<'ctx> {
let pv = PackedValue {
llvm: raw,
width,
repr: ValueRepr::FourState,
signed,
};
if cx.repr == ValueRepr::FourState {
return pv;
}
let o = op(cx);
o.pack(o.lane_val(pv), o.lane_unk(pv), width, cx.repr, signed)
}
pub(crate) fn words_buf<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
words: u32,
raw: IntValue<'ctx>,
name: &str,
) -> PointerValue<'ctx> {
let o = op(cx);
let ty = o.i_n(64 * words);
let buf = entry_alloca(cx, ty, name);
let bits = raw.get_type().get_bit_width();
let v = if bits < 64 * words {
cx.b.build_int_z_extend(raw, ty, "coll_zext").unwrap()
} else if bits > 64 * words {
cx.b.build_int_truncate(raw, ty, "coll_trunc").unwrap()
} else {
raw
};
cx.b.build_store(buf, v).unwrap();
buf
}
pub(crate) fn load_words<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
words: u32,
width: u32,
) -> IntValue<'ctx> {
let o = op(cx);
let all =
cx.b.build_load(o.i_n(64 * words), buf, "coll_ld")
.unwrap()
.into_int_value();
if 64 * words == 2 * width {
all
} else {
cx.b.build_int_truncate(all, o.i_n(2 * width), "coll_elem")
.unwrap()
}
}
pub(crate) fn elem_buf<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
v: LoweredValue<'ctx>,
) -> Result<PointerValue<'ctx>, LoweringError> {
let pv = crate::real::to_storage(cx, v, c.width, "a >128-bit collection element")?;
let pv = crate::select::fit_width(&op(cx), pv, c.width);
Ok(words_buf(cx, c.words, four_state(cx, pv), "coll_val"))
}
pub(crate) fn elem_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
c: &Coll,
e: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if c.obj || c.leaky || sv_sim_sim::state::storage::is_collection(&c.elem) {
owned(cx, e)
} else if c.elem.is_unpacked_array()
&& !sv_sim_sim::state::storage::is_collection(&c.elem)
&& e.expr_type().is_some_and(|t| t.is_unpacked_array())
{
Ok(LoweredValue::Packed(crate::arrays::pack_array_value(
cx, e,
)?))
} else {
lower_expr(cx, e)
}
}
pub(crate) fn retain_elem<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, c: &Coll, v: LoweredValue<'ctx>) {
if c.obj
&& let LoweredValue::Packed(pv) = v
{
crate::classes::retain_handle(cx, op(cx).lane_val(pv));
}
}
pub(crate) fn default_buf<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, c: &Coll) -> PointerValue<'ctx> {
let d = crate::arrays::default_value(cx, &c.elem);
let pv = PackedValue {
llvm: d,
width: c.width,
repr: cx.repr,
signed: c.elem.is_signed(),
};
words_buf(cx, c.words, four_state(cx, pv), "coll_dflt")
}
pub(crate) fn load_elem<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
buf: PointerValue<'ctx>,
) -> LoweredValue<'ctx> {
let raw = load_words(cx, buf, c.words, c.width);
let pv = from_four_state(cx, raw, c.width, c.elem.is_signed());
if c.elem.is_floating() {
LoweredValue::Real(crate::real::bits_to_real(cx, pv))
} else {
LoweredValue::Packed(pv)
}
}
pub(crate) fn key_buf<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
c: &Coll,
key: Expression<'d>,
) -> Result<PointerValue<'ctx>, LoweringError> {
Ok(key_buf_checked(cx, c, key)?.0)
}
pub(crate) fn key_buf_checked<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
c: &Coll,
key: Expression<'d>,
) -> Result<(PointerValue<'ctx>, IntValue<'ctx>), LoweringError> {
let v = lower_expr(cx, key)?;
let pv = crate::real::to_storage(cx, v, c.key_width, "an associative array key")?;
let pv = crate::select::fit_width(&op(cx), pv, c.key_width);
let unk = op(cx).lane_unk(pv);
let invalid =
cx.b.build_int_compare(
IntPredicate::NE,
unk,
unk.get_type().const_zero(),
"key_invalid",
)
.unwrap();
Ok((
words_buf(cx, c.key_words(), four_state(cx, pv), "coll_key"),
invalid,
))
}
pub(crate) fn load_key<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
buf: PointerValue<'ctx>,
) -> PackedValue<'ctx> {
let raw = load_words(cx, buf, c.key_words(), c.key_width);
from_four_state(cx, raw, c.key_width, c.key_kind == 1)
}
pub(crate) fn handle_of<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let pv = lower_expr(cx, e)?.into_packed("a collection handle", &cx.fn_sym)?;
let o = op(cx);
let v = o.lane_val(pv);
Ok(if pv.width == 64 {
v
} else {
cx.b.build_int_z_extend_or_bit_cast(v, cx.ctx.i64_type(), "coll_h")
.unwrap()
})
}
pub(crate) fn handle_value<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: IntValue<'ctx>,
) -> PackedValue<'ctx> {
let o = op(cx);
o.pack(h, cx.ctx.i64_type().const_zero(), 64, cx.repr, false)
}
pub(crate) fn set_handle<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: Expression<'d>,
h: IntValue<'ctx>,
) -> Result<(), LoweringError> {
store_to(cx, target, LoweredValue::Packed(handle_value(cx, h)))?;
Ok(())
}
pub(crate) fn clamp_bound<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
h: IntValue<'ctx>,
) -> IntValue<'ctx> {
if c.bound == 0 {
return h;
}
let keep = cx.ctx.i64_type().const_int(u64::from(c.bound) + 1, false);
call(
cx,
c,
CollOp::Truncate,
h,
Args {
index: Some(keep),
..Args::default()
},
)
}
pub(crate) fn void<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> LoweredValue<'ctx> {
let z = cx.ctx.bool_type().const_zero();
LoweredValue::Packed(op(cx).pack(z, z, 1, cx.repr, false))
}
pub(crate) fn int<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, v: IntValue<'ctx>) -> LoweredValue<'ctx> {
let o = op(cx);
let i32t = cx.ctx.i32_type();
let v =
cx.b.build_int_truncate_or_bit_cast(v, i32t, "coll_int")
.unwrap();
LoweredValue::Packed(o.pack(v, i32t.const_zero(), 32, cx.repr, true))
}
fn index_of<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
c: &Coll,
h: IntValue<'ctx>,
e: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
index_of_known(cx, c, h, e).map(|(idx, _)| idx)
}
fn index_of_known<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
c: &Coll,
h: IntValue<'ctx>,
e: Expression<'d>,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>), LoweringError> {
let i64t = cx.ctx.i64_type();
let size = call(cx, c, CollOp::Size, h, Args::default());
let last =
cx.b.build_int_sub(size, i64t.const_int(1, false), "coll_last")
.unwrap();
let saved = cx.unbounded.replace(Some(last));
let v = lower_expr(cx, e);
cx.unbounded.set(saved);
let pv = v?.into_packed("a collection index", &cx.fn_sym)?;
let o = op(cx);
let val = o.lane_val(pv);
let val = if pv.width >= 64 {
cx.b.build_int_truncate_or_bit_cast(val, i64t, "coll_idx")
.unwrap()
} else if pv.signed {
cx.b.build_int_s_extend(val, i64t, "coll_idx").unwrap()
} else {
cx.b.build_int_z_extend(val, i64t, "coll_idx").unwrap()
};
let unk = o.lane_unk(pv);
let known =
cx.b.build_int_compare(
IntPredicate::EQ,
unk,
unk.get_type().const_zero(),
"coll_known",
)
.unwrap();
let idx =
cx.b.build_select(known, val, i64t.const_all_ones(), "coll_index")
.unwrap()
.into_int_value();
Ok((idx, known))
}
pub(crate) fn lower_unbounded<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> Option<LoweredValue<'ctx>> {
cx.unbounded.get().map(|v| int(cx, v))
}
pub(crate) fn read_element<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
read_element_with(cx, e, false)
}
pub(crate) fn read_element_warning<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
read_element_with(cx, e, true)
}
fn read_element_with<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
warn: bool,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
let base = e.select_value()?;
let c = coll_of_expr(&base)?;
Some((|| {
let sel = e.selector().ok_or_else(|| {
LoweringError::unsupported_at("a collection select with no index", &cx.fn_sym)
})?;
let h = handle_of(cx, base)?;
let val = entry_alloca(cx, op(cx).i_n(64 * c.words), "coll_out");
let dflt = default_buf(cx, &c);
if c.assoc {
let key = key_buf(cx, &c, sel)?;
let found = call(
cx,
&c,
CollOp::Lookup,
h,
Args {
val: Some(val),
dflt: Some(dflt),
key: Some(key),
..Args::default()
},
);
if warn
&& !(sv_sim_sim::state::storage::is_collection(&c.elem)
|| c.elem.is_unpacked_array())
{
let missing =
cx.b.build_int_compare(
IntPredicate::EQ,
found,
found.get_type().const_zero(),
"assoc_missing",
)
.unwrap();
crate::stmt_shared::warning_if(
cx,
missing,
e.source_range().start,
"reading a nonexistent entry of an associative array or one with an invalid (x or z) index; the array type's default value is returned",
)?;
}
} else {
let i = index_of(cx, &c, h, sel)?;
call(
cx,
&c,
CollOp::Get,
h,
Args {
index: Some(i),
val: Some(val),
dflt: Some(dflt),
..Args::default()
},
);
}
Ok(load_elem(cx, &c, val))
})())
}
pub(crate) fn read_at<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
arr: Expression<'d>,
h: IntValue<'ctx>,
pos: IntValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let c = coll_of_expr(&arr)
.ok_or_else(|| LoweringError::unsupported_at("a non-collection", &cx.fn_sym))?;
Ok(read_at_coll(cx, &c, h, pos))
}
pub(crate) fn read_at_coll<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
h: IntValue<'ctx>,
pos: IntValue<'ctx>,
) -> LoweredValue<'ctx> {
let val = entry_alloca(cx, op(cx).i_n(64 * c.words), "coll_at");
let dflt = default_buf(cx, c);
call(
cx,
c,
CollOp::Get,
h,
Args {
index: Some(pos),
val: Some(val),
dflt: Some(dflt),
..Args::default()
},
);
load_elem(cx, c, val)
}
pub(crate) fn read_pos<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
h: IntValue<'ctx>,
pos: IntValue<'ctx>,
) -> LoweredValue<'ctx> {
if !c.assoc {
return read_at_coll(cx, c, h, pos);
}
let val = entry_alloca(cx, op(cx).i_n(64 * c.words), "coll_at");
let key = entry_alloca(cx, op(cx).i_n(64 * c.key_words()), "coll_at_key");
call(
cx,
c,
CollOp::EntryAt,
h,
Args {
index: Some(pos),
val: Some(val),
key: Some(key),
..Args::default()
},
);
load_elem(cx, c, val)
}
pub(crate) fn size_of_handle<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
h: IntValue<'ctx>,
) -> IntValue<'ctx> {
call(cx, c, CollOp::Size, h, Args::default())
}
fn loop_index_i64<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
var: &sv_lang::Symbol<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let o = op(cx);
let &(ptr, width, signed) = cx
.locals
.get(&var.uid())
.ok_or_else(|| LoweringError::unsupported_at("foreach index has no frame slot", var))?;
let v =
cx.b.build_load(o.packed_type(width, cx.repr), ptr, "nest_idx")
.unwrap()
.into_int_value();
let v = o.lane_val(PackedValue {
llvm: v,
width,
repr: cx.repr,
signed,
});
let i64t = cx.ctx.i64_type();
Ok(if signed {
cx.b.build_int_s_extend_or_bit_cast(v, i64t, "nest_idx64")
} else {
cx.b.build_int_z_extend_or_bit_cast(v, i64t, "nest_idx64")
}
.unwrap())
}
pub(crate) fn elem_at_coll<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
h: IntValue<'ctx>,
pos: IntValue<'ctx>,
key: Option<PackedValue<'ctx>>,
) -> LoweredValue<'ctx> {
match key {
Some(k) if c.assoc => {
let val = entry_alloca(cx, op(cx).i_n(64 * c.words), "coll_at");
let dflt = default_buf(cx, c);
let kv = crate::select::fit_width(&op(cx), k, c.key_width);
let kb = words_buf(cx, c.key_words(), four_state(cx, kv), "coll_key");
call(
cx,
c,
CollOp::Lookup,
h,
Args {
val: Some(val),
dflt: Some(dflt),
key: Some(kb),
..Args::default()
},
);
load_elem(cx, c, val)
}
_ => read_at_coll(cx, c, h, pos),
}
}
pub(crate) fn child_coll_at<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
c: &Coll<'d>,
h: IntValue<'ctx>,
pos: IntValue<'ctx>,
key: Option<PackedValue<'ctx>>,
via: &[(sv_lang::Symbol<'d>, i32, i32)],
) -> Result<(Coll<'d>, IntValue<'ctx>), LoweringError> {
let mut inner = c.elem;
for _ in via {
inner = inner.element_type().ok_or_else(|| {
LoweringError::unsupported_at(
"foreach fixed dimension of a non-array element",
&cx.fn_sym,
)
})?;
}
let child = coll_of(&inner).ok_or_else(|| {
LoweringError::unsupported_at(
"foreach over a dimension that is not a queue, dynamic or associative array element \
of the previous one",
&cx.fn_sym,
)
})?;
let elem = elem_at_coll(cx, c, h, pos, key);
let pv = elem.into_packed("a nested collection handle", &cx.fn_sym)?;
let mut v = op(cx).lane_val(pv);
let i64t = cx.ctx.i64_type();
if !via.is_empty() {
let mut acc = i64t.const_zero();
for (var, l, r) in via {
let count = (i64::from(*l) - i64::from(*r)).unsigned_abs() + 1;
let idx = loop_index_i64(cx, var)?;
let lo = i64::from(*l.min(r));
let row =
cx.b.build_int_sub(idx, i64t.const_int(lo as u64, true), "via_row")
.unwrap();
let scaled =
cx.b.build_int_mul(acc, i64t.const_int(count, false), "via_scaled")
.unwrap();
acc = cx.b.build_int_add(scaled, row, "via_acc").unwrap();
}
let bits =
cx.b.build_int_mul(acc, i64t.const_int(64, false), "via_bits")
.unwrap();
let sh =
cx.b.build_int_z_extend_or_bit_cast(bits, v.get_type(), "via_sh")
.unwrap();
v = cx.b.build_right_shift(v, sh, false, "via_shr").unwrap();
v =
cx.b.build_int_truncate_or_bit_cast(v, i64t, "via_h")
.unwrap();
}
let ch =
cx.b.build_int_z_extend_or_bit_cast(v, i64t, "coll_child_h")
.unwrap();
Ok((child, ch))
}
struct FixedSlot<'ctx, 'd> {
row: Expression<'d>,
shift: IntValue<'ctx>,
ok: IntValue<'ctx>,
}
fn fixed_slot<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Option<Result<FixedSlot<'ctx, 'd>, LoweringError>> {
coll_of_expr(&e)?;
let mut sels = Vec::new();
let mut cur = e;
while cur.kind() == ExpressionKind::ElementSelect {
let base = cur.select_value()?;
let t = base.expr_type()?;
if !t.is_unpacked_array() || sv_sim_sim::state::storage::is_collection(&t) {
break;
}
sels.push(cur);
cur = base;
}
if sels.is_empty() || cur.kind() != ExpressionKind::ElementSelect {
return None;
}
coll_of_expr(&cur.select_value()?)?;
sels.reverse();
Some((|| {
let o = op(cx);
let i64t = cx.ctx.i64_type();
let mut ty = cur.expr_type().expect("typed select");
let mut acc = i64t.const_zero();
let mut ok = cx.ctx.bool_type().const_all_ones();
for s in sels {
let r = ty.fixed_unpacked_array_range();
let (l, rr) = (i64::from(r.left), i64::from(r.right));
let count = (l - rr).unsigned_abs() + 1;
let sel = s.selector().ok_or_else(|| {
LoweringError::unsupported_at("a select with no index", &cx.fn_sym)
})?;
let ipv = lower_expr(cx, sel)?.into_packed("an array index", &cx.fn_sym)?;
let iv = o.lane_val(ipv);
let idx = if ipv.signed {
cx.b.build_int_s_extend_or_bit_cast(iv, i64t, "fic_idx")
} else {
cx.b.build_int_z_extend_or_bit_cast(iv, i64t, "fic_idx")
}
.unwrap();
let norm =
cx.b.build_int_sub(idx, i64t.const_int(l.min(rr) as u64, true), "fic_norm")
.unwrap();
let inr =
cx.b.build_int_compare(
IntPredicate::ULT,
norm,
i64t.const_int(count, false),
"fic_in",
)
.unwrap();
ok = cx.b.build_and(ok, inr, "fic_ok").unwrap();
let scaled =
cx.b.build_int_mul(acc, i64t.const_int(count, false), "fic_scaled")
.unwrap();
acc = cx.b.build_int_add(scaled, norm, "fic_acc").unwrap();
ty = ty.element_type().ok_or_else(|| {
LoweringError::unsupported_at("a select of a non-array", &cx.fn_sym)
})?;
}
let acc =
cx.b.build_select(ok, acc, i64t.const_zero(), "fic_pin")
.unwrap()
.into_int_value();
let shift =
cx.b.build_int_mul(acc, i64t.const_int(64, false), "fic_bits")
.unwrap();
Ok(FixedSlot {
row: cur,
shift,
ok,
})
})())
}
pub(crate) fn read_fixed_in_coll<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
let slot = fixed_slot(cx, e)?;
Some((|| {
let slot = slot?;
let row = read_element(cx, slot.row).ok_or_else(|| {
LoweringError::unsupported_at("a nested collection row", &cx.fn_sym)
})??;
let pv = row.into_packed("a nested collection row", &cx.fn_sym)?;
let i64t = cx.ctx.i64_type();
let v = op(cx).lane_val(pv);
let sh =
cx.b.build_int_z_extend_or_bit_cast(slot.shift, v.get_type(), "fic_sh")
.unwrap();
let v = cx.b.build_right_shift(v, sh, false, "fic_shr").unwrap();
let h =
cx.b.build_int_truncate_or_bit_cast(v, i64t, "fic_h")
.unwrap();
let h =
cx.b.build_select(slot.ok, h, i64t.const_zero(), "fic_sel")
.unwrap()
.into_int_value();
Ok(LoweredValue::Packed(handle_value(cx, h)))
})())
}
pub(crate) fn write_fixed_in_coll<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
v: LoweredValue<'ctx>,
) -> Option<Result<(), LoweringError>> {
let slot = fixed_slot(cx, lhs)?;
Some((|| {
let slot = slot?;
let o = op(cx);
let row = read_element(cx, slot.row).ok_or_else(|| {
LoweringError::unsupported_at("a nested collection row", &cx.fn_sym)
})??;
let pv = row.into_packed("a nested collection row", &cx.fn_sym)?;
let h = v.into_packed("a collection handle", &cx.fn_sym)?;
let rv = o.lane_val(pv);
let ty = rv.get_type();
let sh =
cx.b.build_int_z_extend_or_bit_cast(slot.shift, ty, "fic_wsh")
.unwrap();
let hv =
cx.b.build_int_z_extend_or_bit_cast(o.lane_val(h), ty, "fic_wh")
.unwrap();
let low_word =
cx.b.build_int_z_extend_or_bit_cast(cx.ctx.i64_type().const_all_ones(), ty, "fic_m")
.unwrap();
let hole = cx.b.build_left_shift(low_word, sh, "fic_slot").unwrap();
let keep =
cx.b.build_and(rv, cx.b.build_not(hole, "fic_nh").unwrap(), "fic_keep")
.unwrap();
let put = cx.b.build_left_shift(hv, sh, "fic_put").unwrap();
let new = cx.b.build_or(keep, put, "fic_new").unwrap();
let new =
cx.b.build_select(slot.ok, new, rv, "fic_w")
.unwrap()
.into_int_value();
let packed = o.pack(new, ty.const_zero(), pv.width, cx.repr, false);
guarded_by(cx, slot.ok, || {
write_element(cx, slot.row, LoweredValue::Packed(packed)).unwrap_or_else(|| {
Err(LoweringError::unsupported_at(
"a nested collection row write",
&cx.fn_sym,
))
})
})
})())
}
pub(crate) fn guarded_by<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ok: IntValue<'ctx>,
body: impl FnOnce() -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let then_bb = cx.ctx.append_basic_block(func, "guard_then");
let done_bb = cx.ctx.append_basic_block(func, "guard_done");
cx.b.build_conditional_branch(ok, then_bb, done_bb).unwrap();
cx.b.position_at_end(then_bb);
let r = body();
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
r
}
pub(crate) fn chain_in_range<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
elem: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let o = op(cx);
let i64t = cx.ctx.i64_type();
let mut ok = cx.ctx.bool_type().const_all_ones();
let mut cur = lhs;
while cur.source_range() != elem.source_range() || cur.kind() != elem.kind() {
let kind = cur.kind();
if kind == ExpressionKind::MemberAccess {
cur = cur.operand().ok_or_else(|| {
LoweringError::unsupported_at("a member access with no base", &cx.fn_sym)
})?;
continue;
}
let base = cur
.select_value()
.ok_or_else(|| LoweringError::unsupported_at("a select with no base", &cx.fn_sym))?;
if kind == ExpressionKind::ElementSelect
&& let Some(ty) = base.expr_type()
{
let r = if ty.is_unpacked_array() && !ty.is_dynamically_sized_array() {
Some(ty.fixed_unpacked_array_range())
} else if ty.is_packed_array() {
Some(ty.packed_array_range())
} else if ty.is_integral() && !ty.is_struct() && !ty.is_union() {
Some(ty.bit_vector_range())
} else {
None
};
if let Some(r) = r {
let sel = cur.selector().ok_or_else(|| {
LoweringError::unsupported_at("a select with no index", &cx.fn_sym)
})?;
let ipv = lower_expr(cx, sel)?.into_packed("an array index", &cx.fn_sym)?;
let iv = o.lane_val(ipv);
let idx = if ipv.signed {
cx.b.build_int_s_extend_or_bit_cast(iv, i64t, "pir_idx")
} else {
cx.b.build_int_z_extend_or_bit_cast(iv, i64t, "pir_idx")
}
.unwrap();
let lower = i64t.const_int(i64::from(r.lower()) as u64, true);
let norm = cx.b.build_int_sub(idx, lower, "pir_n").unwrap();
let inr =
cx.b.build_int_compare(
IntPredicate::ULT,
norm,
i64t.const_int(u64::from(r.width()), false),
"pir_in",
)
.unwrap();
ok = cx.b.build_and(ok, inr, "pir_ok").unwrap();
}
}
cur = base;
}
Ok(ok)
}
pub(crate) fn write_element<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
v: LoweredValue<'ctx>,
) -> Option<Result<(), LoweringError>> {
if lhs.kind() != ExpressionKind::ElementSelect {
return None;
}
let base = lhs.select_value()?;
let c = coll_of_expr(&base)?;
Some((|| {
let sel = lhs.selector().ok_or_else(|| {
LoweringError::unsupported_at("a collection select with no index", &cx.fn_sym)
})?;
let h = handle_of(cx, base)?;
let val = elem_buf(cx, &c, v)?;
let new = if c.assoc {
let (key, invalid) = key_buf_checked(cx, &c, sel)?;
crate::stmt_shared::warning_if(
cx,
invalid,
lhs.source_range().start,
"writing an associative array through an invalid (x or z) index; the write is ignored",
)?;
call(
cx,
&c,
CollOp::Store,
h,
Args {
val: Some(val),
key: Some(key),
..Args::default()
},
)
} else {
let i = index_of(cx, &c, h, sel)?;
let queue = cx.ctx.i64_type().const_int(u64::from(c.queue), false);
call(
cx,
&c,
CollOp::Set,
h,
Args {
index: Some(i),
index2: Some(queue),
val: Some(val),
..Args::default()
},
)
};
set_handle(cx, base, new)
})())
}
#[derive(Clone, Copy)]
pub struct ElemRefSlot<'ctx> {
pub id: PointerValue<'ctx>,
pub id_width: u32,
pub width: u32,
pub signed: bool,
pub part: Option<PartSlots<'ctx>>,
}
#[derive(Clone, Copy)]
pub struct PartSlots<'ctx> {
pub lo: PointerValue<'ctx>,
pub elem_width: PointerValue<'ctx>,
}
pub(crate) fn open_ref<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let base = lhs.select_value();
let c = base.and_then(|b| coll_of_expr(&b));
let (Some(base), Some(c), Some(sel)) = (base, c, lhs.selector()) else {
return Err(LoweringError::unsupported_at(
"a `ref` to something other than a queue or dynamic-array element (internal \
invariant)",
&cx.fn_sym,
));
};
let h = handle_of(cx, base)?;
let owned = call(cx, &c, CollOp::RefOwn, h, Args::default());
set_handle(cx, base, owned)?;
if c.assoc {
let (key, invalid) = key_buf_checked(cx, &c, sel)?;
let invalid =
cx.b.build_int_z_extend(invalid, cx.ctx.i64_type(), "ref_key_invalid")
.unwrap();
return Ok(call(
cx,
&c,
CollOp::RefOpen,
owned,
Args {
index2: Some(invalid),
key: Some(key),
..Args::default()
},
));
}
let i = index_of(cx, &c, owned, sel)?;
Ok(call(
cx,
&c,
CollOp::RefOpen,
owned,
Args {
index: Some(i),
..Args::default()
},
))
}
pub(crate) fn open_ref_part<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
elem: Expression<'d>,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>), LoweringError> {
let c = elem
.select_value()
.and_then(|b| coll_of_expr(&b))
.ok_or_else(|| {
LoweringError::unsupported_at(
"a `ref` to a part of something other than a queue or dynamic-array element \
(internal invariant)",
&cx.fn_sym,
)
})?;
if c.assoc {
return Err(LoweringError::unsupported_at(
"a `ref` to a member or select of an associative-array element (pass the whole \
element, or copy the member through a local)",
&cx.fn_sym,
));
}
if !handle_offsets(&c.elem).is_empty() {
return Err(LoweringError::unsupported_at(
"a `ref` to a member of a queue or dynamic-array element that holds a handle",
&cx.fn_sym,
));
}
let scratch = entry_alloca(cx, op(cx).packed_type(c.width, cx.repr), "ref_part_scratch");
let saved = cx.coll_temp.replace(Some(scratch));
let lv = crate::select::resolve_lvalue(cx, lhs, crate::select::RootMode::Shadow);
cx.coll_temp.set(saved);
let lv = lv?;
if lv.edge.is_some() || lv.pad.is_some() {
return Err(LoweringError::unsupported_at(
"a `ref` to a select that straddles the edge of a queue or dynamic-array element",
&cx.fn_sym,
));
}
let i64t = cx.ctx.i64_type();
let id = open_ref(cx, elem)?;
let id =
cx.b.build_select(lv.bad, i64t.const_zero(), id, "ref_part_id")
.unwrap()
.into_int_value();
let lo = if lv.lo.get_type().get_bit_width() > 64 {
cx.b.build_int_truncate(lv.lo, i64t, "ref_part_lo")
} else {
cx.b.build_int_z_extend_or_bit_cast(lv.lo, i64t, "ref_part_lo")
}
.unwrap();
Ok((id, lo, i64t.const_int(u64::from(c.width), false)))
}
fn window_actual(e: Expression<'_>) -> bool {
if crate::classes::is_class_field_lvalue(e) {
return true;
}
let k = e.kind();
if matches!(
k,
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
return true;
}
if k == ExpressionKind::MemberAccess {
return e.operand().is_some_and(window_actual);
}
k == ExpressionKind::ElementSelect
|| k == ExpressionKind::RangeSelect
&& e.select_value()
.is_some_and(|b| b.expr_type().is_some_and(|t| t.is_integral()) && window_actual(b))
}
pub(crate) fn open_ref_window<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
width: u32,
) -> Option<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>)> {
use crate::select::{LvalueRoot, RootMode};
if !window_actual(lhs) {
return None;
}
let lv = crate::select::resolve_lvalue(cx, lhs, RootMode::Shadow).ok()?;
if lv.edge.is_some()
|| lv.pad.is_some()
|| lv.width != width
|| crate::huge::is_image_width(lv.root_width)
{
return None;
}
let i64t = cx.ctx.i64_type();
let two_state = u32::from(cx.repr == ValueRepr::TwoState);
let shape = [1, 0, 0, 0, two_state];
let id = match lv.root {
LvalueRoot::Shadow { ptr } => {
let addr = cx.b.build_ptr_to_int(ptr, i64t, "win_addr").unwrap();
raw_call(
cx,
shape,
CollOp::WinOpenMem,
i64t.const_zero(),
Args {
index: Some(addr),
..Args::default()
},
)
}
LvalueRoot::Field { h, slot } => {
let packed = (slot.offset as u64 & 0xffff_ffff) | ((slot.words as u64) << 32);
raw_call(
cx,
shape,
CollOp::WinOpenField,
i64t.const_zero(),
Args {
index: Some(h),
index2: Some(i64t.const_int(packed, false)),
..Args::default()
},
)
}
_ => return None,
};
let id =
cx.b.build_select(lv.bad, i64t.const_zero(), id, "ref_win_id")
.unwrap()
.into_int_value();
let lo = if lv.lo.get_type().get_bit_width() > 64 {
cx.b.build_int_truncate(lv.lo, i64t, "ref_win_lo")
} else {
cx.b.build_int_z_extend_or_bit_cast(lv.lo, i64t, "ref_win_lo")
}
.unwrap();
Some((id, lo, i64t.const_int(u64::from(lv.root_width), false)))
}
pub(crate) fn held_ref_part<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
e: Expression<'_>,
) -> (IntValue<'ctx>, IntValue<'ctx>) {
let zero = cx.ctx.i64_type().const_zero();
let slots = (|| {
let sym = e.referenced_symbol()?;
let (ptr, _, _) = *cx.locals.get(&sym.uid())?;
cx.elem_refs.get(&ptr)?.part
})();
match slots {
Some(p) => (load_slot64(cx, p.lo), load_slot64(cx, p.elem_width)),
None => (zero, zero),
}
}
pub(crate) fn held_ref_id<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
e: Expression<'_>,
) -> Option<IntValue<'ctx>> {
if cx.elem_refs.is_empty() || !sv_sim_sim::symid::is_value_name(e.kind()) {
return None;
}
let sym = e.referenced_symbol()?;
let (ptr, _, _) = *cx.locals.get(&sym.uid())?;
let r = cx.elem_refs.get(&ptr).copied()?;
Some(load_ref_id(cx, &r))
}
pub(crate) fn load_ref_slot<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
slot: PointerValue<'ctx>,
) -> IntValue<'ctx> {
load_ref_id(
cx,
&ElemRefSlot {
id: slot,
id_width: 64,
width: 1,
signed: false,
part: None,
},
)
}
fn load_ref_id<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, r: &ElemRefSlot<'ctx>) -> IntValue<'ctx> {
load_slot_of(cx, r.id, r.id_width)
}
fn load_slot64<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, slot: PointerValue<'ctx>) -> IntValue<'ctx> {
load_slot_of(cx, slot, 64)
}
fn load_slot_of<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
slot: PointerValue<'ctx>,
width: u32,
) -> IntValue<'ctx> {
let o = op(cx);
let pv = PackedValue {
llvm: cx
.b
.build_load(o.packed_type(width, cx.repr), slot, "ref_id")
.unwrap()
.into_int_value(),
width,
repr: cx.repr,
signed: false,
};
cx.b.build_int_z_extend_or_bit_cast(o.lane_val(pv), cx.ctx.i64_type(), "ref_id64")
.unwrap()
}
fn part_args<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &ElemRefSlot<'ctx>,
whole: CollOp,
part: CollOp,
) -> (CollOp, IntValue<'ctx>, Args<'ctx>) {
let i64t = cx.ctx.i64_type();
let Some(p) = r.part else {
return (whole, i64t.const_zero(), Args::default());
};
let a = Args {
index2: Some(load_slot64(cx, p.lo)),
h2: Some(load_slot64(cx, p.elem_width)),
..Args::default()
};
(part, i64t.const_int(u64::from(r.width), false), a)
}
fn ref_shape(words: u32) -> [u32; 5] {
[words, 0, 0, 0, 0]
}
pub(crate) fn elem_ref_refresh<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, ptr: PointerValue<'ctx>) {
let Some(r) = cx.elem_refs.get(&ptr).copied() else {
return;
};
refresh_slot(cx, &r, ptr);
}
pub(crate) fn refresh_slot<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &ElemRefSlot<'ctx>,
ptr: PointerValue<'ctx>,
) {
let words = (2 * r.width).div_ceil(64).max(1);
let cur = PackedValue {
llvm: cx
.b
.build_load(op(cx).packed_type(r.width, cx.repr), ptr, "ref_cur")
.unwrap()
.into_int_value(),
width: r.width,
repr: cx.repr,
signed: r.signed,
};
let buf = words_buf(cx, words, four_state(cx, cur), "ref_buf");
let (op_, h, a) = part_args(cx, r, CollOp::RefGet, CollOp::RefGetPart);
raw_call(
cx,
ref_shape(words),
op_,
h,
Args {
index: Some(load_ref_id(cx, r)),
val: Some(buf),
..a
},
);
let raw = load_words(cx, buf, words, r.width);
let pv = from_four_state(cx, raw, r.width, r.signed);
cx.b.build_store(ptr, pv.llvm).unwrap();
}
pub(crate) fn elem_ref_flush<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, ptr: PointerValue<'ctx>) {
let Some(r) = cx.elem_refs.get(&ptr).copied() else {
return;
};
let words = (2 * r.width).div_ceil(64).max(1);
let cur = PackedValue {
llvm: cx
.b
.build_load(op(cx).packed_type(r.width, cx.repr), ptr, "ref_cur")
.unwrap()
.into_int_value(),
width: r.width,
repr: cx.repr,
signed: r.signed,
};
let buf = words_buf(cx, words, four_state(cx, cur), "ref_buf");
let (op_, h, a) = part_args(cx, &r, CollOp::RefPut, CollOp::RefPutPart);
raw_call(
cx,
ref_shape(words),
op_,
h,
Args {
index: Some(load_ref_id(cx, &r)),
val: Some(buf),
..a
},
);
}
pub(crate) fn close_ref<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, tptr: PointerValue<'ctx>, twidth: u32) {
let r = ElemRefSlot {
id: tptr,
id_width: twidth,
width: 1,
signed: false,
part: None,
};
raw_call(
cx,
ref_shape(1),
CollOp::RefClose,
cx.ctx.i64_type().const_zero(),
Args {
index: Some(load_ref_id(cx, &r)),
..Args::default()
},
);
cx.b.build_store(tptr, op(cx).packed_type(twidth, cx.repr).const_zero())
.unwrap();
}
pub(crate) fn nba_element<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
signals: &std::collections::HashMap<
sv_sim_sim::symid::SymbolId,
crate::process_lower::SignalSlot,
>,
lhs: Expression<'d>,
rhs: Expression<'d>,
delay: IntValue<'ctx>,
) -> Option<Result<(), LoweringError>> {
use crate::select::{LvalueRoot, RootMode};
if lhs.kind() != ExpressionKind::ElementSelect {
return None;
}
let base = lhs.select_value()?;
let c = coll_of_expr(&base)?;
Some((|| {
let sel = lhs.selector().ok_or_else(|| {
LoweringError::unsupported_at("a collection select with no index", &cx.fn_sym)
})?;
let lv = crate::select::resolve_lvalue(cx, base, RootMode::Signal(signals))?;
let (slot, row, has_row) = match lv.root {
LvalueRoot::Signal { slot } => (slot, cx.ctx.i64_type().const_zero(), 0u64),
LvalueRoot::MemRow { info, row } => (info.slot, row, 1),
_ => {
return Err(LoweringError::unsupported_at(
"a nonblocking write into a collection that is not a module variable",
&cx.fn_sym,
));
}
};
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let (i, key) = if c.assoc {
(cx.ctx.i64_type().const_zero(), key_buf(cx, &c, sel)?)
} else {
let h = handle_of(cx, base)?;
(index_of(cx, &c, h, sel)?, ptr.const_null())
};
let v = if coll_of(&c.elem).is_some() {
assigned_value(cx, lhs, rhs)?
} else {
elem_value(cx, &c, rhs)?
};
let val = elem_buf(cx, &c, v)?;
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let name = crate::process_lower::SV_RT_NBA_COLL_SYMBOL;
let f = cx.module.get_function(name).unwrap_or_else(|| {
let fn_ty = cx.ctx.void_type().fn_type(
&[
ptr.into(),
i32t.into(),
i64t.into(),
i32t.into(),
i64t.into(),
i32t.into(),
ptr.into(),
i32t.into(),
i64t.into(),
ptr.into(),
i32t.into(),
i32t.into(),
],
false,
);
cx.module.add_function(name, fn_ty, Some(Linkage::External))
});
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let stage_bb = cx.ctx.append_basic_block(func, "nba_coll_stage");
let done_bb = cx.ctx.append_basic_block(func, "nba_coll_done");
cx.b.build_conditional_branch(lv.bad, done_bb, stage_bb)
.unwrap();
cx.b.position_at_end(stage_bb);
let flags = u64::from(c.queue) | (u64::from(c.obj) << 1);
cx.b.build_call(
f,
&[
handle.into(),
i32t.const_int(u64::from(slot), false).into(),
row.into(),
i32t.const_int(has_row, false).into(),
i.into(),
i32t.const_int(flags, false).into(),
val.into(),
i32t.const_int(u64::from(c.words), false).into(),
delay.into(),
key.into(),
i32t.const_int(u64::from(c.key_kind), false).into(),
i32t.const_int(u64::from(c.key_width), false).into(),
],
"rt_nba_coll_call",
)
.unwrap();
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
Ok(())
})())
}
fn is_fresh(e: &Expression<'_>) -> bool {
matches!(
e.kind(),
ExpressionKind::Concatenation
| ExpressionKind::SimpleAssignmentPattern
| ExpressionKind::StructuredAssignmentPattern
| ExpressionKind::ReplicatedAssignmentPattern
| ExpressionKind::NewArray
| ExpressionKind::RangeSelect
| ExpressionKind::Call
| ExpressionKind::EmptyArgument
)
}
pub(crate) fn owned_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let mut e = e;
while e.kind() == ExpressionKind::Conversion
&& let Some(inner) = e.operand()
&& coll_of_expr(&inner).is_some()
{
e = inner;
}
if e.kind() == ExpressionKind::Conversion
&& (e.conversion_kind() == Some(2)
|| (e.conversion_kind() == Some(4)
&& e.conversion_operand().is_some_and(|i| {
coll_of_expr(&i).is_none()
&& !i.expr_type().is_some_and(|t| t.is_unpacked_array())
})))
&& let Some(c) = coll_of_expr(&e)
{
return crate::stream::stream_to_collection(cx, e, &c);
}
if e.kind() == ExpressionKind::Conversion
&& let Some(inner) = e.operand()
&& inner.expr_type().is_some_and(|t| t.is_unpacked_array())
&& let Some(c) = coll_of_expr(&e)
{
return crate::array_methods::gather_fixed(cx, &c, inner);
}
let h = handle_of(cx, e)?;
if is_fresh(&e) {
return Ok(h);
}
let c = coll_of_expr(&e).ok_or_else(|| {
LoweringError::unsupported_at("a non-collection collection value", &cx.fn_sym)
})?;
Ok(call(cx, &c, CollOp::Clone, h, Args::default()))
}
pub(crate) fn owned_arg<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let h = owned_value(cx, e)?;
Ok(handle_value(cx, h).llvm)
}
fn stored_handle<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, v: PackedValue<'ctx>) -> IntValue<'ctx> {
let lane = op(cx).lane_val(v);
if v.width == 64 {
lane
} else {
cx.b.build_int_z_extend_or_bit_cast(lane, cx.ctx.i64_type(), "coll_h")
.unwrap()
}
}
pub(crate) fn clone_stored<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ty: &Type<'_>,
v: PackedValue<'ctx>,
) -> Result<IntValue<'ctx>, LoweringError> {
let c = coll_of(ty).ok_or_else(|| {
LoweringError::unsupported_at("a non-collection collection value", &cx.fn_sym)
})?;
let h = call(cx, &c, CollOp::Clone, stored_handle(cx, v), Args::default());
Ok(handle_value(cx, h).llvm)
}
pub(crate) fn store_static_result<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
ty: &Type<'d>,
slot: PointerValue<'ctx>,
e: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let c = coll_of(ty).ok_or_else(|| {
LoweringError::unsupported_at("a non-collection collection value", &cx.fn_sym)
})?;
let new = owned_value(cx, e)?;
let new = clamp_bound(cx, &c, new);
let stored = handle_value(cx, new);
let old =
cx.b.build_load(stored.llvm.get_type(), slot, "static_result_old")
.unwrap()
.into_int_value();
let old_pv = PackedValue {
llvm: old,
..stored
};
call(
cx,
&c,
CollOp::Release,
stored_handle(cx, old_pv),
Args::default(),
);
cx.b.build_store(slot, stored.llvm).unwrap();
clone_stored(cx, ty, stored)
}
pub(crate) fn assigned_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
rhs: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if coll_of_expr(&lhs).is_some() {
let h = owned_value(cx, rhs)?;
return Ok(LoweredValue::Packed(handle_value(cx, h)));
}
owned(cx, rhs)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum HandleKind {
Coll,
Obj,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct TagGuard {
pub lsb: u32,
pub width: u32,
pub val: u64,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct HandleSlot {
pub off: u32,
pub kind: HandleKind,
pub guards: Vec<TagGuard>,
}
impl HandleSlot {
fn at(off: u32, kind: HandleKind) -> Self {
Self {
off,
kind,
guards: Vec::new(),
}
}
fn shifted(mut self, by: u32) -> Self {
self.off += by;
for g in &mut self.guards {
g.lsb += by;
}
self
}
}
pub(crate) fn handle_offsets(ty: &Type<'_>) -> Vec<HandleSlot> {
use sv_sim_sim::state::storage;
if storage::is_collection(ty) {
return vec![HandleSlot::at(0, HandleKind::Coll)];
}
if ty.is_class() {
return vec![HandleSlot::at(0, HandleKind::Obj)];
}
let mut out = Vec::new();
if ty.is_unpacked_struct() {
for f in ty.fields() {
let (Some(fty), Some((lo, _))) =
(f.value_type(), storage::field_slice(ty, f.field_index()))
else {
continue;
};
out.extend(
handle_offsets(&fty)
.into_iter()
.map(|s| s.shifted(lo as u32)),
);
}
} else if ty.is_unpacked_union() && ty.is_tagged_union() {
let tag = storage::tagged_tag_slice(ty);
for f in ty.fields() {
let Some(fty) = f.value_type() else { continue };
for mut s in handle_offsets(&fty) {
if let Some((lsb, width)) = tag.filter(|&(_, w)| w > 0) {
s.guards.push(TagGuard {
lsb: lsb as u32,
width: width as u32,
val: u64::from(f.field_index()),
});
}
out.push(s);
}
}
} else if ty.is_unpacked_array()
&& let Some(elem) = ty.element_type()
&& let Some(ew) = storage::storage_width(&elem)
{
let inner = handle_offsets(&elem);
if !inner.is_empty() {
for i in 0..ty.fixed_unpacked_array_range().width() {
out.extend(inner.iter().cloned().map(|s| s.shifted(i * ew as u32)));
}
}
}
out
}
fn map_handles<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
pv: PackedValue<'ctx>,
offsets: &[HandleSlot],
f: &mut dyn FnMut(IntValue<'ctx>, HandleKind) -> IntValue<'ctx>,
) -> PackedValue<'ctx> {
let o = op(cx);
let ty = o.i_n(pv.width);
let i64t = cx.ctx.i64_type();
let mut val = o.lane_val(pv);
for slot in offsets {
let (off, kind) = (slot.off, slot.kind);
let sh = ty.const_int(u64::from(off), false);
let h = cx.b.build_right_shift(val, sh, false, "coll_fh").unwrap();
let h = if pv.width > 64 {
cx.b.build_int_truncate(h, i64t, "coll_fh64").unwrap()
} else {
cx.b.build_int_z_extend_or_bit_cast(h, i64t, "coll_fh64")
.unwrap()
};
let live = slot
.guards
.iter()
.fold(None, |acc: Option<IntValue<'ctx>>, g| {
let w = ty;
let tag =
cx.b.build_and(
cx.b.build_right_shift(
val,
w.const_int(u64::from(g.lsb), false),
false,
"coll_gt",
)
.unwrap(),
w.const_int((1u64 << g.width.min(63)) - 1, false),
"coll_gtm",
)
.unwrap();
let hit =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
tag,
w.const_int(g.val, false),
"coll_gtq",
)
.unwrap();
Some(match acc {
Some(a) => cx.b.build_and(a, hit, "coll_gta").unwrap(),
None => hit,
})
});
let h_in = match live {
Some(l) => {
cx.b.build_select(l, h, i64t.const_zero(), "coll_fgh")
.unwrap()
.into_int_value()
}
None => h,
};
let new = f(h_in, kind);
let new = match live {
Some(l) => {
cx.b.build_select(l, new, h, "coll_fgn")
.unwrap()
.into_int_value()
}
None => new,
};
let new = if pv.width > 64 {
cx.b.build_int_z_extend(new, ty, "coll_fnew").unwrap()
} else {
cx.b.build_int_truncate_or_bit_cast(new, ty, "coll_fnew")
.unwrap()
};
let mask =
cx.b.build_left_shift(
cx.b.build_int_z_extend_or_bit_cast(i64t.const_all_ones(), ty, "coll_fm")
.unwrap(),
sh,
"coll_fmask",
)
.unwrap();
let kept =
cx.b.build_and(val, cx.b.build_not(mask, "coll_fkeep").unwrap(), "coll_fk")
.unwrap();
let placed = cx.b.build_left_shift(new, sh, "coll_fput").unwrap();
val = cx.b.build_or(kept, placed, "coll_fset").unwrap();
}
o.pack(val, o.lane_unk(pv), pv.width, pv.repr, pv.signed)
}
pub(crate) fn fresh(e: &Expression<'_>) -> bool {
let mut e = *e;
while e.kind() == ExpressionKind::Conversion
&& let Some(inner) = e.operand()
{
e = inner;
}
let pops = e.is_system_call() == Some(true)
&& e.system_subroutine().is_some_and(|s| {
matches!(
s.known_name_id(),
KnownSystemName::PopBack | KnownSystemName::PopFront
)
})
&& !e
.operands()
.first()
.and_then(coll_of_expr)
.is_some_and(|c| c.leaky);
matches!(
e.kind(),
ExpressionKind::SimpleAssignmentPattern
| ExpressionKind::StructuredAssignmentPattern
| ExpressionKind::ReplicatedAssignmentPattern
| ExpressionKind::TaggedUnion
| ExpressionKind::NewClass
| ExpressionKind::CopyClass
| ExpressionKind::NullLiteral
) || (e.kind() == ExpressionKind::Call
&& (e.is_system_call() != Some(true) || pops)
&& !crate::classes::is_weak_get_call(&e))
}
pub(crate) fn owned<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if coll_of_expr(&e).is_some() {
let h = owned_value(cx, e)?;
return Ok(LoweredValue::Packed(handle_value(cx, h)));
}
if e.expr_type()
.is_some_and(|t| crate::arrays::array_shape(&t).is_some())
{
return Ok(LoweredValue::Packed(crate::arrays::pack_array_value(
cx, e,
)?));
}
let v = lower_expr(cx, e)?;
let offsets = e
.expr_type()
.map(|t| handle_offsets(&t))
.unwrap_or_default();
if offsets.is_empty() || fresh(&e) {
return Ok(v);
}
let LoweredValue::Packed(pv) = v else {
return Ok(v);
};
Ok(LoweredValue::Packed(retain_offsets(cx, pv, &offsets)))
}
fn retain_offsets<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
pv: PackedValue<'ctx>,
offsets: &[HandleSlot],
) -> PackedValue<'ctx> {
map_handles(cx, pv, offsets, &mut |h, kind| match kind {
HandleKind::Coll => raw_call(cx, [1, 0, 0, 0, 0], CollOp::Clone, h, Args::default()),
HandleKind::Obj => {
crate::classes::retain_handle(cx, h);
h
}
})
}
pub(crate) fn retain_stored<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ty: &Type<'_>,
pv: PackedValue<'ctx>,
) -> PackedValue<'ctx> {
let offsets = handle_offsets(ty);
if offsets.is_empty() {
return pv;
}
retain_offsets(cx, pv, &offsets)
}
fn store_releases_itself(lhs: Expression<'_>) -> bool {
crate::classes::is_class_field_lvalue(lhs)
|| (lhs.kind() == ExpressionKind::ElementSelect
&& lhs
.select_value()
.is_some_and(|b| coll_of_expr(&b).is_some()))
}
pub(crate) fn release_old<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
) -> Result<(), LoweringError> {
if !sv_sim_sim::symid::is_value_name(lhs.kind()) {
return Err(LoweringError::unsupported_at(
"internal: release_old on a target that is not a plain name",
&cx.fn_sym,
));
}
if store_releases_itself(lhs) {
return Ok(());
}
if let Some(t) = lhs.expr_type()
&& !handle_offsets(&t).is_empty()
{
let old = lower_expr(cx, lhs)?;
release_handles(cx, &t, old);
}
Ok(())
}
pub(crate) fn store_releasing<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
value: LoweredValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let held = lhs
.expr_type()
.filter(|t| !handle_offsets(t).is_empty() && !store_releases_itself(lhs));
let Some(t) = held else {
return store_to(cx, lhs, value);
};
let mut release = |old: PackedValue<'ctx>| release_handles(cx, &t, LoweredValue::Packed(old));
crate::expr::store_to_releasing(cx, lhs, value, Some(&mut release))
}
pub(crate) fn release_element<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
t: &Type<'_>,
r: &crate::arrays::ElemRef<'ctx>,
) -> Result<(), LoweringError> {
if handle_offsets(t).is_empty() {
return Ok(());
}
let old = crate::arrays::read_element(cx, r)?;
release_handles(cx, t, LoweredValue::Packed(old));
Ok(())
}
pub(crate) fn release_handles<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ty: &Type<'_>,
v: LoweredValue<'ctx>,
) {
release_handles_of(cx, ty, v, false);
}
pub(crate) fn release_handles_of<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ty: &Type<'_>,
v: LoweredValue<'ctx>,
objs_only: bool,
) {
let offsets = handle_offsets(ty);
let LoweredValue::Packed(pv) = v else {
return;
};
if offsets.is_empty() {
return;
}
map_handles(cx, pv, &offsets, &mut |h, kind| {
match kind {
HandleKind::Coll if objs_only => {}
HandleKind::Coll => {
raw_call(cx, [1, 0, 0, 0, 0], CollOp::Release, h, Args::default());
}
HandleKind::Obj => crate::classes::release_handle(cx, h),
}
h
});
}
pub(crate) fn assign_whole<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
rhs: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let c = coll_of_expr(&lhs)
.ok_or_else(|| LoweringError::unsupported_at("a non-collection target", &cx.fn_sym))?;
let new = owned_value(cx, rhs)?;
let new = clamp_bound(cx, &c, new);
if sv_sim_sim::symid::is_value_name(lhs.kind()) {
let old = handle_of(cx, lhs)?;
call(cx, &c, CollOp::Release, old, Args::default());
set_handle(cx, lhs, new)?;
} else {
store_releasing(cx, lhs, LoweredValue::Packed(handle_value(cx, new)))?;
}
Ok(LoweredValue::Packed(handle_value(cx, new)))
}
pub(crate) fn lower_constructor<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
let c = coll_of_expr(&e)?;
let kind = e.kind();
let r = || -> Result<IntValue<'ctx>, LoweringError> {
let i64t = cx.ctx.i64_type();
let zero = i64t.const_zero();
match kind {
ExpressionKind::Concatenation => {
let mut h = zero;
for x in e.operands() {
let fixed_array = coll_of_expr(&x).is_none()
&& !(c.elem.is_unpacked_array())
&& x.expr_type().is_some_and(|t| t.is_unpacked_array());
if fixed_array {
if c.leaky {
return Err(LoweringError::unsupported_at(
"an array operand in a concatenation of structs holding class handles",
&cx.fn_sym,
));
}
for v in crate::arrays::array_operand_elements(cx, x, &c.elem)? {
retain_elem(cx, &c, LoweredValue::Packed(v));
let val = elem_buf(cx, &c, LoweredValue::Packed(v))?;
h = call(
cx,
&c,
CollOp::Push,
h,
Args {
val: Some(val),
..Args::default()
},
);
}
} else if let Some(oc) = coll_of_expr(&x).filter(|o| o.assoc) {
let other = handle_of(cx, x)?;
let acc = entry_alloca(cx, i64t, "cat_assoc_acc");
cx.b.build_store(acc, h).unwrap();
let n = size_of_handle(cx, &oc, other);
let n =
cx.b.build_int_z_extend_or_bit_cast(n, i64t, "cat_n")
.unwrap();
crate::array_methods::each(cx, n, false, |pos, _| {
let v = read_pos(cx, &oc, other, pos);
retain_elem(cx, &c, v);
let val = elem_buf(cx, &c, v)?;
let cur =
cx.b.build_load(i64t, acc, "cat_h")
.unwrap()
.into_int_value();
let nh = call(
cx,
&c,
CollOp::Push,
cur,
Args {
val: Some(val),
..Args::default()
},
);
cx.b.build_store(acc, nh).unwrap();
Ok(())
})?;
h =
cx.b.build_load(i64t, acc, "cat_h")
.unwrap()
.into_int_value();
} else if coll_of_expr(&x).is_some()
&& !(sv_sim_sim::state::storage::is_collection(&c.elem)
&& x.expr_type()
.is_some_and(|t| c.elem.is_assignment_compatible(&t)))
{
let other = handle_of(cx, x)?;
h = call(
cx,
&c,
CollOp::Append,
h,
Args {
h2: Some(other),
..Args::default()
},
);
} else {
let v = elem_value(cx, &c, x)?;
let val = elem_buf(cx, &c, v)?;
h = call(
cx,
&c,
CollOp::Push,
h,
Args {
val: Some(val),
..Args::default()
},
);
}
}
Ok(h)
}
ExpressionKind::StructuredAssignmentPattern => {
let mut h = zero;
if let Some(d) = e.structured_pattern_default_setter() {
let dv = if c.elem.is_string()
&& d.kind() == ExpressionKind::StringLiteral
&& let Some(s) = crate::strings::lower_string_expr(cx, d)?
{
LoweredValue::Str(s)
} else {
elem_value(cx, &c, d)?
};
let val = elem_buf(cx, &c, dv)?;
h = call(
cx,
&c,
CollOp::SetDefault,
h,
Args {
val: Some(val),
..Args::default()
},
);
}
for setter in e.structured_pattern_index_setters() {
let key = key_buf(cx, &c, setter.index())?;
let val = elem_buf(cx, &c, elem_value(cx, &c, setter.expr())?)?;
h = call(
cx,
&c,
CollOp::Store,
h,
Args {
val: Some(val),
key: Some(key),
..Args::default()
},
);
}
Ok(h)
}
ExpressionKind::SimpleAssignmentPattern
| ExpressionKind::ReplicatedAssignmentPattern => {
if c.assoc {
return Err(LoweringError::unsupported_at(
"an associative array assignment pattern",
&cx.fn_sym,
));
}
let elems = e.pattern_elements();
let reps = if kind == ExpressionKind::ReplicatedAssignmentPattern {
e.replicated_pattern_count()
.and_then(|n| n.constant_value())
.and_then(|v| v.as_i64())
.ok_or_else(|| {
LoweringError::unsupported_at(
"a non-constant replication count",
&cx.fn_sym,
)
})?
} else {
1
};
let mut h = zero;
for _ in 0..reps.max(0) {
for x in &elems {
let v = elem_value(cx, &c, *x)?;
let val = elem_buf(cx, &c, v)?;
h = call(
cx,
&c,
CollOp::Push,
h,
Args {
val: Some(val),
..Args::default()
},
);
}
}
Ok(h)
}
ExpressionKind::NewArray => {
let n = e.new_array_size().ok_or_else(|| {
LoweringError::unsupported_at("new[] with no size", &cx.fn_sym)
})?;
let n = lower_expr(cx, n)?.into_packed("a new[] size", &cx.fn_sym)?;
let o = op(cx);
let n = o.lane_val(n);
let n =
cx.b.build_int_z_extend_or_bit_cast(n, i64t, "new_n")
.unwrap();
let src = match e.new_array_init() {
Some(init) => handle_of(cx, init)?,
None => zero,
};
let dflt = default_buf(cx, &c);
Ok(call(
cx,
&c,
CollOp::Resized,
zero,
Args {
index: Some(n),
h2: Some(src),
dflt: Some(dflt),
..Args::default()
},
))
}
ExpressionKind::RangeSelect => {
let base = e.select_value().ok_or_else(|| {
LoweringError::unsupported_at("a slice with no base", &cx.fn_sym)
})?;
let h = handle_of(cx, base)?;
let (a, b) = slice_bounds(cx, &c, h, e)?;
Ok(call(
cx,
&c,
CollOp::Slice,
h,
Args {
index: Some(a),
index2: Some(b),
..Args::default()
},
))
}
_ => Err(LoweringError::unsupported_at(
"not a collection constructor",
&cx.fn_sym,
)),
}
};
if !matches!(
kind,
ExpressionKind::Concatenation
| ExpressionKind::SimpleAssignmentPattern
| ExpressionKind::ReplicatedAssignmentPattern
| ExpressionKind::NewArray
) && !(kind == ExpressionKind::StructuredAssignmentPattern && c.assoc)
&& !(kind == ExpressionKind::RangeSelect
&& e.select_value().is_some_and(|b| coll_of_expr(&b).is_some()))
{
return None;
}
Some(r().map(|h| LoweredValue::Packed(handle_value(cx, h))))
}
pub(crate) fn slice_bounds<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
c: &Coll,
h: IntValue<'ctx>,
e: Expression<'d>,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>), LoweringError> {
let left = e
.range_left()
.ok_or_else(|| LoweringError::unsupported_at("a slice with no left bound", &cx.fn_sym))?;
let right = e
.range_right()
.ok_or_else(|| LoweringError::unsupported_at("a slice with no right bound", &cx.fn_sym))?;
let (a, ka) = index_of_known(cx, c, h, left)?;
let (r, kr) = index_of_known(cx, c, h, right)?;
let one = cx.ctx.i64_type().const_int(1, false);
let (a, b) = match e.range_selection_kind() {
Some(1) => {
let b = cx.b.build_int_add(a, r, "slice_hi").unwrap();
(a, cx.b.build_int_sub(b, one, "slice_hi1").unwrap())
}
Some(2) => {
let lo = cx.b.build_int_sub(a, r, "slice_lo").unwrap();
(cx.b.build_int_add(lo, one, "slice_lo1").unwrap(), a)
}
_ => (a, r),
};
let known = cx.b.build_and(ka, kr, "slice_known").unwrap();
let a =
cx.b.build_select(known, a, cx.ctx.i64_type().const_zero(), "slice_a")
.unwrap()
.into_int_value();
let b =
cx.b.build_select(known, b, cx.ctx.i64_type().const_all_ones(), "slice_b")
.unwrap()
.into_int_value();
Ok((a, b))
}
pub(crate) fn lower_equality<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
negate: bool,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
let l = e.left()?;
let r = e.right()?;
let c = coll_of_expr(&l)?;
Some((|| {
let a = handle_of(cx, l)?;
let b = handle_of(cx, r)?;
let eq = call(
cx,
&c,
CollOp::Equal,
a,
Args {
h2: Some(b),
..Args::default()
},
);
let eq =
cx.b.build_int_truncate(eq, cx.ctx.bool_type(), "coll_eq")
.unwrap();
let eq = if negate {
cx.b.build_not(eq, "coll_ne").unwrap()
} else {
eq
};
let z = cx.ctx.bool_type().const_zero();
Ok(LoweredValue::Packed(op(cx).pack(eq, z, 1, cx.repr, false)))
})())
}
pub(crate) fn lower_method<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
use KnownSystemName as K;
if matches!(name, K::Dimensions | K::UnpackedDimensions) {
return None;
}
if name == K::FRead {
return None;
}
let args = e.operands();
let this = *args.first()?;
let c = coll_of_expr(&this)?;
let rest: Vec<Expression<'d>> = args[1..].to_vec();
Some((|| {
let h = handle_of(cx, this)?;
let i64t = cx.ctx.i64_type();
let size = || call(cx, &c, CollOp::Size, h, Args::default());
let arg = |k: usize| {
rest.get(k).copied().ok_or_else(|| {
LoweringError::unsupported_at(
"a collection method is missing an argument",
&cx.fn_sym,
)
})
};
match name {
K::Size | K::ArraySize | K::Num => Ok(int(cx, size())),
K::Left if c.assoc => Ok(int(cx, i64t.const_zero())),
K::Increment if c.assoc => Ok(int(cx, i64t.const_all_ones())),
K::Right if c.assoc => {
let w = c.key_width.min(64);
let bits = if c.key_kind == 1 { w - 1 } else { w };
let max = if bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
Ok(int(cx, i64t.const_int(max, false)))
}
K::Low | K::High if c.assoc => {
let key = entry_alloca(cx, op(cx).i_n(64 * c.key_words()), "coll_lohi");
let which = if name == K::Low {
CollOp::First
} else {
CollOp::Last
};
let found = call(
cx,
&c,
which,
h,
Args {
key: Some(key),
..Args::default()
},
);
let o = op(cx);
let i32t = cx.ctx.i32_type();
let kv = o.lane_val(load_key(cx, &c, key));
let kv = if c.key_width < 32 {
if c.key_kind == 1 {
cx.b.build_int_s_extend(kv, i32t, "coll_k32").unwrap()
} else {
cx.b.build_int_z_extend(kv, i32t, "coll_k32").unwrap()
}
} else {
cx.b.build_int_truncate_or_bit_cast(kv, i32t, "coll_k32")
.unwrap()
};
let hit =
cx.b.build_int_compare(IntPredicate::NE, found, i64t.const_zero(), "coll_lh")
.unwrap();
let val =
cx.b.build_select(hit, kv, i32t.const_zero(), "coll_lv")
.unwrap()
.into_int_value();
let unk =
cx.b.build_select(hit, i32t.const_zero(), i32t.const_all_ones(), "coll_lu")
.unwrap()
.into_int_value();
Ok(LoweredValue::Packed(o.pack(val, unk, 32, cx.repr, true)))
}
K::Left if c.assoc => Ok(int(cx, i64t.const_zero())),
K::Increment if c.assoc => Ok(int(cx, i64t.const_all_ones())),
K::Right if c.assoc => {
let w = c.key_width.min(63);
let max = if c.key_kind == 1 {
(1u64 << (w - 1)) - 1
} else {
(1u64 << w) - 1
};
Ok(int(cx, i64t.const_int(max, false)))
}
K::Low | K::High if c.assoc => {
let o = op(cx);
let zero_key = o.i_n(2 * c.key_width).const_zero();
let key = words_buf(cx, c.key_words(), zero_key, "coll_lowhigh_key");
let which = if name == K::Low {
CollOp::First
} else {
CollOp::Last
};
let found = call(
cx,
&c,
which,
h,
Args {
key: Some(key),
..Args::default()
},
);
let hit =
cx.b.build_int_compare(IntPredicate::NE, found, i64t.const_zero(), "coll_hit")
.unwrap();
let k = crate::select::fit_width(&o, load_key(cx, &c, key), 32);
let i32t = cx.ctx.i32_type();
let val =
cx.b.build_select(hit, o.lane_val(k), i32t.const_zero(), "coll_lh_v")
.unwrap()
.into_int_value();
let unk =
cx.b.build_select(hit, o.lane_unk(k), i32t.const_all_ones(), "coll_lh_u")
.unwrap()
.into_int_value();
Ok(LoweredValue::Packed(o.pack(val, unk, 32, cx.repr, true)))
}
K::Low | K::Left => Ok(int(cx, i64t.const_zero())),
K::High | K::Right => {
let last =
cx.b.build_int_sub(size(), i64t.const_int(1, false), "coll_high")
.unwrap();
Ok(int(cx, last))
}
K::Increment => Ok(int(cx, i64t.const_all_ones())),
K::PushBack | K::PushFront => {
let val = elem_buf(cx, &c, elem_value(cx, &c, arg(0)?)?)?;
let front = i64t.const_int(u64::from(name == K::PushFront), false);
let new = call(
cx,
&c,
CollOp::Push,
h,
Args {
index: Some(front),
val: Some(val),
..Args::default()
},
);
set_handle(cx, this, clamp_bound(cx, &c, new))?;
Ok(void(cx))
}
K::PopBack | K::PopFront => {
let val = entry_alloca(cx, op(cx).i_n(64 * c.words), "coll_pop");
let dflt = default_buf(cx, &c);
let front = i64t.const_int(u64::from(name == K::PopFront), false);
let new = call(
cx,
&c,
CollOp::Pop,
h,
Args {
index: Some(front),
val: Some(val),
dflt: Some(dflt),
..Args::default()
},
);
set_handle(cx, this, new)?;
Ok(load_elem(cx, &c, val))
}
K::Insert => {
let i = index_of(cx, &c, h, arg(0)?)?;
let val = elem_buf(cx, &c, elem_value(cx, &c, arg(1)?)?)?;
let new = call(
cx,
&c,
CollOp::Insert,
h,
Args {
index: Some(i),
val: Some(val),
..Args::default()
},
);
set_handle(cx, this, clamp_bound(cx, &c, new))?;
Ok(void(cx))
}
K::Delete => {
let new = match rest.first() {
None => call(cx, &c, CollOp::Clear, h, Args::default()),
Some(&k) if c.assoc => {
let key = key_buf(cx, &c, k)?;
call(
cx,
&c,
CollOp::DeleteKey,
h,
Args {
key: Some(key),
..Args::default()
},
)
}
Some(&k) => {
let i = index_of(cx, &c, h, k)?;
call(
cx,
&c,
CollOp::DeleteAt,
h,
Args {
index: Some(i),
..Args::default()
},
)
}
};
set_handle(cx, this, new)?;
Ok(void(cx))
}
K::Exists => {
let key = key_buf(cx, &c, arg(0)?)?;
Ok(int(
cx,
call(
cx,
&c,
CollOp::Exists,
h,
Args {
key: Some(key),
..Args::default()
},
),
))
}
K::First | K::Last | K::Next | K::Prev if c.assoc => {
let target = arg(0)?;
let key = key_buf(cx, &c, target)?;
let which = match name {
K::First => CollOp::First,
K::Last => CollOp::Last,
K::Next => CollOp::Next,
_ => CollOp::Prev,
};
let found = call(
cx,
&c,
which,
h,
Args {
key: Some(key),
..Args::default()
},
);
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let then_bb = cx.ctx.append_basic_block(f, "coll_found");
let done_bb = cx.ctx.append_basic_block(f, "coll_found_done");
let hit =
cx.b.build_int_compare(IntPredicate::NE, found, i64t.const_zero(), "coll_hit")
.unwrap();
cx.b.build_conditional_branch(hit, then_bb, done_bb)
.unwrap();
cx.b.position_at_end(then_bb);
let k = load_key(cx, &c, key);
if c.key_kind == 3 {
let old = handle_of(cx, target)?;
crate::classes::retain_handle(cx, op(cx).lane_val(k));
store_to(cx, target, LoweredValue::Packed(k))?;
crate::classes::release_handle(cx, old);
} else {
store_to(cx, target, LoweredValue::Packed(k))?;
}
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
Ok(int(cx, found))
}
_ => Err(LoweringError::unsupported_at(
&format!("the array method {name:?} on a queue, dynamic or associative array"),
&cx.fn_sym,
)),
}
})())
}
pub(crate) fn size_and_handle<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
arr: Expression<'d>,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>), LoweringError> {
let c = coll_of_expr(&arr).ok_or_else(|| {
LoweringError::unsupported_at("foreach over a non-collection", &cx.fn_sym)
})?;
let h = handle_of(cx, arr)?;
Ok((call(cx, &c, CollOp::Size, h, Args::default()), h))
}
pub(crate) fn nested_handle<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
arr: Expression<'d>,
path: &[sv_sim_sim::process::cfg::NestStep<'d>],
) -> Result<(Coll<'d>, IntValue<'ctx>), LoweringError> {
let o = op(cx);
let i64t = cx.ctx.i64_type();
let index_of_var = |var: &sv_lang::Symbol<'d>| -> Result<IntValue<'ctx>, LoweringError> {
let &(ptr, width, signed) = cx
.locals
.get(&var.uid())
.ok_or_else(|| LoweringError::unsupported_at("foreach index has no frame slot", var))?;
let v =
cx.b.build_load(o.packed_type(width, cx.repr), ptr, "nest_idx")
.unwrap()
.into_int_value();
let v = o.lane_val(PackedValue {
llvm: v,
width,
repr: cx.repr,
signed,
});
Ok(if signed {
cx.b.build_int_s_extend_or_bit_cast(v, i64t, "nest_idx64")
} else {
cx.b.build_int_z_extend_or_bit_cast(v, i64t, "nest_idx64")
}
.unwrap())
};
let (mut c, mut h, rest) = if let Some(c) = coll_of_expr(&arr) {
(c, handle_of(cx, arr)?, path)
} else {
let resolved = crate::arrays::resolve_array_ref(cx, arr)?;
let elem = match &resolved {
Some((_, shape)) => shape.elem,
None => {
let mut t = arr.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("foreach over an untyped array", &cx.fn_sym)
})?;
while t.is_unpacked_array() && !sv_sim_sim::state::storage::is_collection(&t) {
t = t.element_type().ok_or_else(|| {
LoweringError::unsupported_at("foreach over a non-array", &cx.fn_sym)
})?;
}
t
}
};
let child = coll_of(&elem).ok_or_else(|| {
LoweringError::unsupported_at("foreach over a non-collection", &cx.fn_sym)
})?;
let nfixed = path.iter().take_while(|s| s.fixed.is_some()).count();
let mut acc = i64t.const_zero();
for s in &path[..nfixed] {
let (l, rr) = s.fixed.unwrap();
let count = (i64::from(l) - i64::from(rr)).unsigned_abs() + 1;
let idx = index_of_var(&s.var)?;
let row_off =
cx.b.build_int_sub(idx, i64t.const_int(l.min(rr) as u64, true), "nest_row")
.unwrap();
let scaled =
cx.b.build_int_mul(acc, i64t.const_int(count, false), "nest_scaled")
.unwrap();
acc = cx.b.build_int_add(scaled, row_off, "nest_acc").unwrap();
}
let raw = if let Some((r, _)) = resolved {
let row = cx.b.build_int_add(r.base, acc, "nest_rowp").unwrap();
crate::arrays::load_raw(cx, r.backing, row, 64)?
} else {
let whole =
lower_expr(cx, arr)?.into_packed("a fixed array of collections", &cx.fn_sym)?;
let wv = o.lane_val(whole);
let sh =
cx.b.build_int_mul(acc, i64t.const_int(64, false), "nest_bits")
.unwrap();
let sh =
cx.b.build_int_z_extend_or_bit_cast(sh, wv.get_type(), "nest_sh")
.unwrap();
let v = cx.b.build_right_shift(wv, sh, false, "nest_shr").unwrap();
cx.b.build_int_truncate_or_bit_cast(v, i64t, "nest_raw")
.unwrap()
};
let h = o.lane_val(PackedValue {
llvm: raw,
width: 64,
repr: cx.repr,
signed: false,
});
(child, h, &path[nfixed..])
};
let mut k = 0;
while k < rest.len() {
let s = &rest[k];
if s.fixed.is_some() {
return Err(LoweringError::unsupported_at(
"foreach whose body waits over a fixed dimension that does not follow a queue, \
dynamic array or associative array",
&cx.fn_sym,
));
}
let via: Vec<_> = rest[k + 1..]
.iter()
.map_while(|v| v.fixed.map(|(l, r)| (v.var, l, r)))
.collect();
let (pos, key) = if c.assoc {
(i64t.const_zero(), Some(var_value(cx, &s.var)?))
} else {
(index_of_var(&s.var)?, None)
};
(c, h) = child_coll_at(cx, &c, h, pos, key, &via)?;
k += 1 + via.len();
}
Ok((c, h))
}
pub(crate) fn nested_size<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
arr: Expression<'d>,
path: &[sv_sim_sim::process::cfg::NestStep<'d>],
) -> Result<IntValue<'ctx>, LoweringError> {
let (c, h) = nested_handle(cx, arr, path)?;
Ok(size_of_handle(cx, &c, h))
}
fn var_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
var: &sv_lang::Symbol<'d>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let &(ptr, width, signed) = cx
.locals
.get(&var.uid())
.ok_or_else(|| LoweringError::unsupported_at("foreach index has no frame slot", var))?;
let v =
cx.b.build_load(op(cx).packed_type(width, cx.repr), ptr, "nest_key")
.unwrap()
.into_int_value();
Ok(PackedValue {
llvm: v,
width,
repr: cx.repr,
signed,
})
}
pub(crate) fn key_at_coll<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
h: IntValue<'ctx>,
pos: IntValue<'ctx>,
) -> PackedValue<'ctx> {
let key = entry_alloca(cx, op(cx).i_n(64 * c.key_words()), "coll_key_at");
call(
cx,
c,
CollOp::KeyAt,
h,
Args {
index: Some(pos),
key: Some(key),
..Args::default()
},
);
load_key(cx, c, key)
}
pub(crate) fn assoc_step<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
arr: Expression<'d>,
path: &[sv_sim_sim::process::cfg::NestStep<'d>],
cur: PackedValue<'ctx>,
next: bool,
) -> Result<PackedValue<'ctx>, LoweringError> {
let (c, h) = if path.is_empty() {
let c = coll_of_expr(&arr).ok_or_else(|| {
LoweringError::unsupported_at("foreach over a non-collection", &cx.fn_sym)
})?;
(c, handle_of(cx, arr)?)
} else {
nested_handle(cx, arr, path)?
};
let pv = crate::select::fit_width(&op(cx), cur, c.key_width);
let key = words_buf(cx, c.key_words(), four_state(cx, pv), "coll_key");
call(
cx,
&c,
if next { CollOp::Next } else { CollOp::First },
h,
Args {
key: Some(key),
..Args::default()
},
);
Ok(load_key(cx, &c, key))
}
pub(crate) fn format_pattern<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let c = coll_of_expr(&e)
.ok_or_else(|| LoweringError::unsupported_at("%p of a non-collection", &cx.fn_sym))?;
let h = handle_of(cx, e)?;
format_handle(cx, &c, h)
}
pub(crate) fn format_handle<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
c: &Coll,
h: IntValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let i64t = cx.ctx.i64_type();
if !(c.elem.is_integral() || c.elem.is_string() || c.elem.is_floating())
|| enum_desc_ok(&c.elem)
{
let mut desc = String::new();
type_descriptor(cx, &c.elem, &mut desc)?;
let desc = crate::process_lower::build_c_string_global(cx.ctx, cx.module, &desc, "pfmt_ty");
let id = call(
cx,
c,
CollOp::Format,
h,
Args {
index: Some(i64t.const_int(u64::from(c.width), false)),
val: Some(desc),
..Args::default()
},
);
return Ok(LoweredValue::Str(crate::strings::StrValue { llvm: id }));
}
let mut flags = u64::from(c.elem.is_signed());
if c.elem.is_string() {
flags |= 2;
} else if c.elem.is_floating() {
flags |= if c.elem.bit_width() == 32 { 8 } else { 4 };
}
let id = call(
cx,
c,
CollOp::Format,
h,
Args {
index: Some(i64t.const_int(u64::from(c.width), false)),
index2: Some(i64t.const_int(flags, false)),
..Args::default()
},
);
Ok(LoweredValue::Str(crate::strings::StrValue { llvm: id }))
}
pub(crate) fn enum_desc_ok(ty: &Type<'_>) -> bool {
ty.is_enum() && ty.value_width() <= 32
}
fn type_descriptor(
cx: &LowerCtx<'_, '_, '_>,
ty: &Type<'_>,
out: &mut String,
) -> Result<(), LoweringError> {
use std::fmt::Write;
use sv_sim_sim::state::storage;
let bad = |what: &str| {
LoweringError::unsupported_at(&format!("%p of a collection element: {what}"), &cx.fn_sym)
};
if let Some(c) = coll_of(ty) {
let _ = write!(out, "q{};{};", u32::from(c.assoc), c.width);
return type_descriptor(cx, &c.elem, out);
}
if ty.is_class() {
let _ = write!(out, "c{};", ty.as_symbol().name());
} else if ty.is_string() {
out.push('s');
} else if ty.is_floating() {
out.push(if ty.bit_width() == 32 { 'f' } else { 'r' });
} else if enum_desc_ok(ty) {
let w = ty.value_width();
let mask = (1u64 << w) - 1;
let members: Vec<_> = ty.enum_members().collect();
let _ = write!(out, "e{w};{};{};", u32::from(ty.is_signed()), members.len());
for m in &members {
let cv = m.constant_value();
let v = cv
.as_ref()
.and_then(|c| c.as_integer())
.and_then(|i| i.as_i64())
.ok_or_else(|| bad("an enum member with no constant value"))?;
let _ = write!(out, "{};{};", m.name(), (v as u64) & mask);
}
} else if ty.is_integral() {
let _ = write!(out, "i{};{};", ty.bit_width(), u32::from(ty.is_signed()));
} else if ty.is_unpacked_union() && ty.is_tagged_union() {
let (tag_lo, tag_w) =
storage::tagged_tag_slice(ty).ok_or_else(|| bad("a tagged union's tag"))?;
let fields: Vec<_> = ty.fields().collect();
let _ = write!(out, "u{tag_lo};{tag_w};{};", fields.len());
for f in &fields {
let fty = f.value_type().ok_or_else(|| bad("an untyped member"))?;
if fty.is_void() {
let _ = write!(out, "{};0;", f.name());
continue;
}
let (_, w) =
crate::select::field_window(Some(*ty), f).ok_or_else(|| bad("a member's shape"))?;
let _ = write!(out, "{};{w};", f.name());
type_descriptor(cx, &fty, out)?;
}
} else if storage::is_unpacked_aggregate(ty) {
let fields: Vec<_> = ty.fields().collect();
let shown = if ty.is_unpacked_union() {
&fields[..fields.len().min(1)]
} else {
&fields[..]
};
let _ = write!(out, "t{};", shown.len());
for f in shown {
let (lo, w) =
storage::field_slice(ty, f.field_index()).ok_or_else(|| bad("a field's shape"))?;
let fty = f.value_type().ok_or_else(|| bad("an untyped field"))?;
let _ = write!(out, "{lo};{w};{};", f.name());
type_descriptor(cx, &fty, out)?;
}
} else if ty.is_unpacked_array() {
let range = ty.fixed_unpacked_array_range();
let elem = ty
.element_type()
.ok_or_else(|| bad("an array without elements"))?;
let ew = storage::storage_width(&elem).ok_or_else(|| bad("an element's shape"))?;
let _ = write!(
out,
"a{};{ew};{};",
range.width(),
u32::from(range.left > range.right)
);
type_descriptor(cx, &elem, out)?;
} else {
return Err(bad("an unsupported element type"));
}
Ok(())
}
pub(crate) fn partial_element_target<'d>(lhs: Expression<'d>) -> Option<Expression<'d>> {
let mut cur = lhs;
let mut first = true;
loop {
let kind = cur.kind();
cur = if kind == ExpressionKind::ElementSelect || kind == ExpressionKind::RangeSelect {
let base = cur.select_value()?;
if kind == ExpressionKind::ElementSelect && coll_of_expr(&base).is_some() {
return (!first).then_some(cur);
}
base
} else if kind == ExpressionKind::MemberAccess {
cur.operand()?
} else {
return None;
};
first = false;
}
}