use inkwell::IntPredicate;
use inkwell::values::{IntValue, PointerValue};
use sv_lang::Type;
use sv_lang::kinds::ExpressionKind;
use sv_sim_sim::symid::SymbolUid;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, MemorySignalInfo, lower_expr, mem_row_index_i64};
use crate::ops::OpBuilder;
use crate::types::{TypeWidth, int_type};
use crate::value::{PackedValue, ValueRepr};
pub(crate) struct ArrayShape<'d> {
pub dims: Vec<(i64, i64)>,
pub elem: Type<'d>,
}
impl ArrayShape<'_> {
pub(crate) fn size(&self, k: usize) -> u64 {
(self.dims[k].0 - self.dims[k].1).unsigned_abs() + 1
}
pub fn rows(&self) -> u64 {
(0..self.dims.len()).map(|k| self.size(k)).product()
}
pub(crate) fn stride(&self, k: usize) -> u64 {
(k + 1..self.dims.len()).map(|j| self.size(j)).product()
}
}
pub(crate) fn array_shape<'d>(ty: &Type<'d>) -> Option<ArrayShape<'d>> {
if !ty.is_unpacked_array() || ty.is_dynamically_sized_array() {
return None;
}
let mut dims = Vec::new();
let mut cur = *ty;
while cur.is_unpacked_array() && !cur.is_dynamically_sized_array() {
let r = cur.fixed_unpacked_array_range();
dims.push((i64::from(r.left), i64::from(r.right)));
cur = cur.element_type()?;
}
Some(ArrayShape { dims, elem: cur })
}
pub(crate) fn row_type<'ctx>(
ctx: &'ctx inkwell::context::Context,
elem: &Type,
repr: ValueRepr,
) -> inkwell::types::IntType<'ctx> {
let w = elem.value_width();
int_type(
ctx,
if repr == ValueRepr::FourState {
2 * w
} else {
w
},
)
}
pub(crate) fn local_array_type<'ctx>(
ctx: &'ctx inkwell::context::Context,
shape: &ArrayShape,
repr: ValueRepr,
) -> inkwell::types::ArrayType<'ctx> {
row_type(ctx, &shape.elem, repr).array_type(shape.rows() as u32)
}
#[derive(Clone, Copy)]
pub(crate) enum Backing<'ctx> {
Local(PointerValue<'ctx>),
Memory(MemorySignalInfo),
}
#[derive(Clone, Copy)]
pub(crate) struct ArrayRef<'ctx> {
pub backing: Backing<'ctx>,
pub base: IntValue<'ctx>,
pub valid: IntValue<'ctx>,
}
impl<'ctx> ArrayRef<'ctx> {
pub(crate) fn whole(cx: &LowerCtx<'ctx, '_, '_>, backing: Backing<'ctx>) -> Self {
ArrayRef {
backing,
base: cx.ctx.i64_type().const_zero(),
valid: cx.ctx.bool_type().const_all_ones(),
}
}
fn always_valid(&self) -> bool {
self.valid.get_zero_extended_constant() == Some(1)
}
}
pub(crate) fn resolve_array_ref<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<(ArrayRef<'ctx>, ArrayShape<'d>)>, LoweringError> {
resolve_array_ref_in(cx, e, None)
}
pub(crate) fn resolve_array_ref_in<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
root: Option<&sv_lang::Symbol<'d>>,
) -> Result<Option<(ArrayRef<'ctx>, ArrayShape<'d>)>, LoweringError> {
let mut selects = Vec::new();
let mut cur = e;
while matches!(
cur.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
let Some(base) = cur.select_value() else {
return Ok(None);
};
if !base.expr_type().is_some_and(|t| {
t.is_unpacked_array() && !sv_sim_sim::state::storage::is_collection(&t)
}) {
break;
}
selects.push(cur);
cur = base;
}
let picked = if root.is_none() {
crate::vif::picked_symbol(&cur)
} else {
None
};
let root = root.or(picked.as_ref());
if root.is_none() && !sv_sim_sim::symid::is_value_name(cur.kind()) {
return Ok(None);
}
let Some(sym) = root.cloned().or_else(|| cur.referenced_symbol()) else {
return Ok(None);
};
let backing = if let Some(info) = crate::events::static_array_backing(cx, &sym) {
Backing::Memory(info)
} else if let Some(info) = cx.mem_signals.get(&sym.uid()).copied() {
Backing::Memory(info)
} else if let Some(&(ptr, _, _)) = cx.locals.get(&sym.uid()) {
Backing::Local(ptr)
} else if root.is_none()
&& let Some(ptr) = constant_array(cx, &sym, cur)?
{
Backing::Local(ptr)
} else {
return Ok(None);
};
let Some(full) = cur.expr_type().as_ref().and_then(array_shape) else {
return Ok(None);
};
selects.reverse();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let konst = |v: i64| i64t.const_int(v as u64, true);
let mut base = i64t.const_zero();
let mut valid = cx.ctx.bool_type().const_all_ones();
let mut dims = full.dims.clone();
let mut k = 0;
let index_of = |x: sv_lang::Expression<'d>| -> Result<_, LoweringError> {
let idx = lower_expr(cx, x)?.into_packed("an array index", &cx.fn_sym)?;
let unk = op.lane_unk(idx);
let known =
cx.b.build_int_compare(
IntPredicate::EQ,
unk,
unk.get_type().const_zero(),
"arr_known",
)
.unwrap();
Ok((mem_row_index_i64(&op, cx, idx), known))
};
for sel in selects {
if k >= dims.len() {
return Ok(None);
}
let low = dims[k].0.min(dims[k].1);
let size = (dims[k].0 - dims[k].1).unsigned_abs() + 1;
let stride = full.stride(k);
let (lowest, count, ok) = if sel.kind() == ExpressionKind::ElementSelect {
let Some(x) = sel.selector() else {
return Ok(None);
};
let (i, known) = index_of(x)?;
(i, 1u64, known)
} else {
let range_const = |x: Option<sv_lang::Expression<'d>>| {
x.and_then(|x| x.constant_value()).and_then(|c| c.as_i64())
};
match sel.range_selection_kind() {
Some(0) => {
let (Some(l), Some(r)) = (
range_const(sel.range_left()),
range_const(sel.range_right()),
) else {
return Err(LoweringError::unsupported_at(
"an unpacked-array slice with non-constant bounds",
&cx.fn_sym,
));
};
(
konst(l.min(r)),
(l - r).unsigned_abs() + 1,
cx.ctx.bool_type().const_all_ones(),
)
}
Some(kind @ (1 | 2)) => {
let Some(w) = range_const(sel.range_right()) else {
return Err(LoweringError::unsupported_at(
"an indexed unpacked-array slice with a non-constant width",
&cx.fn_sym,
));
};
let Some(b) = sel.range_left() else {
return Ok(None);
};
let (i, known) = index_of(b)?;
let lowest = if kind == 1 {
i
} else {
cx.b.build_int_sub(i, konst(w - 1), "arr_slice_low")
.unwrap()
};
(lowest, w.max(1) as u64, known)
}
_ => return Ok(None),
}
};
let norm = cx.b.build_int_sub(lowest, konst(low), "arr_norm").unwrap();
let in_range = if count > size {
cx.ctx.bool_type().const_zero()
} else {
cx.b.build_int_compare(
IntPredicate::ULE,
norm,
i64t.const_int(size - count, false),
"arr_in_range",
)
.unwrap()
};
let good = cx.b.build_and(in_range, ok, "arr_ok").unwrap();
valid = cx.b.build_and(valid, good, "arr_valid").unwrap();
let scaled =
cx.b.build_int_mul(norm, i64t.const_int(stride, false), "arr_scaled")
.unwrap();
base = cx.b.build_int_add(base, scaled, "arr_base").unwrap();
if sel.kind() == ExpressionKind::ElementSelect {
k += 1;
} else {
let Some(r) = sel.expr_type().map(|t| t.fixed_unpacked_array_range()) else {
return Ok(None);
};
dims[k] = (i64::from(r.left), i64::from(r.right));
}
}
let base =
cx.b.build_select(valid, base, i64t.const_zero(), "arr_safe_base")
.unwrap()
.into_int_value();
let shape = ArrayShape {
dims: dims[k..].to_vec(),
elem: full.elem,
};
Ok(Some((
ArrayRef {
backing,
base,
valid,
},
shape,
)))
}
fn constant_array<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
sym: &sv_lang::Symbol<'d>,
name: sv_lang::Expression<'d>,
) -> Result<Option<PointerValue<'ctx>>, LoweringError> {
let Some(sv_lang::ConstantValue::Array(top)) = name.constant_value() else {
return Ok(None);
};
let Some(shape) = name.expr_type().as_ref().and_then(array_shape) else {
return Ok(None);
};
if shape.elem.is_string() && !shape.elem.is_integral() {
return Ok(None);
}
if !shape.elem.is_integral() && shape.elem.is_dynamically_sized_array() {
return Err(LoweringError::unsupported_at(
"a constant unpacked array whose elements are not integral",
sym,
));
}
let gname = format!("__sv_sim_const_array_{}", sym.uid());
if let Some(g) = cx.module.get_global(&gname) {
return Ok(Some(g.as_pointer_value()));
}
let width = shape.elem.value_width();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let row_ty = op.packed_type(width, cx.repr);
let mut rows = vec![row_ty.const_zero(); shape.rows() as usize];
fn fill<'ctx>(
cv: &sv_lang::ConstantValue,
k: usize,
row: u64,
shape: &ArrayShape<'_>,
width: u32,
cx: &LowerCtx<'ctx, '_, '_>,
rows: &mut [IntValue<'ctx>],
) -> Result<(), String> {
if k == shape.dims.len() {
if !shape.elem.is_integral() {
let bits = crate::expr::const_stored_bits(&shape.elem, cv)
.filter(|b| b.len() as u32 == width)
.ok_or("an element without a stored-bits form")?;
rows[row as usize] = crate::value::const_bits(cx.ctx, &bits, false, cx.repr).llvm;
return Ok(());
}
let v = cv.as_integer().ok_or("a non-integer element")?;
let mut bits = sv_sim_sim::state::svint_bits(v)
.ok_or("unrecoverable element bits")?
.into_owned();
let pad = if v.is_signed() {
bits.last().copied().unwrap_or(sv_lang::Bit::Zero)
} else {
sv_lang::Bit::Zero
};
bits.resize(width as usize, pad);
rows[row as usize] =
crate::value::const_bits(cx.ctx, &bits, v.is_signed(), cx.repr).llvm;
return Ok(());
}
let sv_lang::ConstantValue::Array(elems) = cv else {
return Err("a constant array with too few dimensions".to_string());
};
let (left, right) = shape.dims[k];
let low = left.min(right);
for (p, e) in elems.iter().enumerate() {
let index = if left <= right {
left + p as i64
} else {
left - p as i64
};
let r = row + (index - low) as u64 * shape.stride(k);
fill(e, k + 1, r, shape, width, cx, rows)?;
}
Ok(())
}
fill(
&sv_lang::ConstantValue::Array(top),
0,
0,
&shape,
width,
cx,
&mut rows,
)
.map_err(|why| {
LoweringError::unsupported_at(&format!("constant unpacked array: {why}"), sym)
})?;
let g = cx
.module
.add_global(row_ty.array_type(rows.len() as u32), None, &gname);
g.set_initializer(&row_ty.const_array(&rows));
g.set_constant(true);
g.set_linkage(inkwell::module::Linkage::Private);
Ok(Some(g.as_pointer_value()))
}
pub(crate) fn read_constant_unpacked_element<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<crate::expr::LoweredValue<'ctx>>, LoweringError> {
let mut selects = Vec::new();
let mut cur = e;
while cur.kind() == ExpressionKind::ElementSelect {
let Some(base) = cur.select_value() else {
return Ok(None);
};
selects.push(cur);
cur = base;
}
if selects.is_empty() || cur.kind() != ExpressionKind::NamedValue {
return Ok(None);
}
let Some(sv_lang::ConstantValue::Array(top)) = cur.constant_value() else {
return Ok(None);
};
let Some(shape) = cur.expr_type().as_ref().and_then(array_shape) else {
return Ok(None);
};
let is_real = shape.elem.is_floating();
if !(shape.elem.is_string() || is_real) || selects.len() != shape.dims.len() {
return Ok(None);
}
if shape.rows() > 4096 {
return Err(LoweringError::unsupported_at(
"a constant string/real array of more than 4096 elements indexed at run time",
&cx.fn_sym,
));
}
selects.reverse();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let mut flat = i64t.const_zero();
let mut valid = cx.ctx.bool_type().const_all_ones();
for (k, sel) in selects.iter().enumerate() {
let Some(x) = sel.selector() else {
return Ok(None);
};
let idx = lower_expr(cx, x)?.into_packed("an array index", &cx.fn_sym)?;
let unk = op.lane_unk(idx);
let known =
cx.b.build_int_compare(IntPredicate::EQ, unk, unk.get_type().const_zero(), "k")
.unwrap();
let i = mem_row_index_i64(&op, cx, idx);
let low = shape.dims[k].0.min(shape.dims[k].1);
let size = shape.size(k) as i64;
let off =
cx.b.build_int_sub(i, i64t.const_int(low as u64, true), "off")
.unwrap();
let in_range =
cx.b.build_int_compare(
IntPredicate::ULT,
off,
i64t.const_int(size as u64, true),
"inr",
)
.unwrap();
valid = cx.b.build_and(valid, known, "v").unwrap();
valid = cx.b.build_and(valid, in_range, "v").unwrap();
let term =
cx.b.build_int_mul(off, i64t.const_int(shape.stride(k), false), "term")
.unwrap();
flat = cx.b.build_int_add(flat, term, "flat").unwrap();
}
fn collect(
cv: &sv_lang::ConstantValue,
k: usize,
row: u64,
shape: &ArrayShape<'_>,
out: &mut [Option<sv_lang::ConstantValue>],
) -> Option<()> {
if k == shape.dims.len() {
if !matches!(
cv,
sv_lang::ConstantValue::String(_) | sv_lang::ConstantValue::Real(_)
) {
return None;
}
out[row as usize] = Some(cv.clone());
return Some(());
}
let sv_lang::ConstantValue::Array(elems) = cv else {
return None;
};
let (left, right) = shape.dims[k];
let low = left.min(right);
for (p, el) in elems.iter().enumerate() {
let index = if left <= right {
left + p as i64
} else {
left - p as i64
};
collect(
el,
k + 1,
row + (index - low) as u64 * shape.stride(k),
shape,
out,
)?;
}
Some(())
}
let mut texts = vec![None; shape.rows() as usize];
collect(
&sv_lang::ConstantValue::Array(top),
0,
0,
&shape,
&mut texts,
)
.ok_or_else(|| {
LoweringError::unsupported_at(
"a constant string/real array with an element of another kind",
&cx.fn_sym,
)
})?;
let f64t = cx.ctx.f64_type();
let mut acc: inkwell::values::BasicValueEnum<'ctx> = if is_real {
f64t.const_zero().into()
} else {
i64t.const_zero().into()
};
let default = acc;
for (r, cv) in texts.iter().enumerate() {
let Some(cv) = cv else { continue };
let lit: inkwell::values::BasicValueEnum<'ctx> = match cv {
sv_lang::ConstantValue::Real(f) => f64t.const_float(*f).into(),
sv_lang::ConstantValue::String(text) => {
crate::strings::literal(cx, text.as_bytes()).llvm.into()
}
_ => unreachable!("checked in collect"),
};
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
flat,
i64t.const_int(r as u64, false),
"hit",
)
.unwrap();
acc = cx.b.build_select(hit, lit, acc, "cconst").unwrap();
}
let acc =
cx.b.build_select(valid, acc, default, "cconst_valid")
.unwrap();
Ok(Some(if is_real {
crate::expr::LoweredValue::Real(crate::value::RealValue {
llvm: acc.into_float_value(),
})
} else {
crate::expr::LoweredValue::Str(crate::strings::StrValue {
llvm: acc.into_int_value(),
})
}))
}
pub(crate) struct ElemRef<'ctx> {
pub backing: Backing<'ctx>,
pub row: IntValue<'ctx>,
pub valid: IntValue<'ctx>,
pub width: u32,
pub signed: bool,
pub default: IntValue<'ctx>,
}
pub(crate) fn resolve_element<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<ElemRef<'ctx>>, LoweringError> {
resolve_element_in(cx, e, None)
}
pub(crate) fn resolve_element_in<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
root: Option<&sv_lang::Symbol<'d>>,
) -> Result<Option<ElemRef<'ctx>>, LoweringError> {
if e.kind() != ExpressionKind::ElementSelect
|| e.expr_type().is_some_and(|t| {
t.is_unpacked_array() && !sv_sim_sim::state::storage::is_collection(&t)
})
{
return Ok(None);
}
if e.expr_type().is_some_and(|t| t.is_event()) {
return Err(LoweringError::unsupported_at(
"a named event array element used as a value",
&cx.fn_sym,
));
}
let Some((r, shape)) = resolve_array_ref_in(cx, e, root)? else {
return Ok(None);
};
if !shape.dims.is_empty() {
return Ok(None);
}
Ok(Some(ElemRef {
backing: r.backing,
row: r.base,
valid: r.valid,
width: shape.elem.value_width(),
signed: shape.elem.is_signed(),
default: default_value(cx, &shape.elem),
}))
}
pub(crate) fn array_target<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<(ArrayRef<'ctx>, ArrayShape<'d>)>, LoweringError> {
let e = peel_array_conversions(e);
if !e.expr_type().is_some_and(|t| t.is_unpacked_array()) {
return Ok(None);
}
Ok(resolve_array_ref(cx, e)?.filter(|(_, shape)| !shape.dims.is_empty()))
}
pub(crate) fn spill_member_array<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<(ArrayRef<'ctx>, ArrayShape<'d>)>, LoweringError> {
let e = peel_array_conversions(e);
let is_member = e.kind() == ExpressionKind::MemberAccess
|| (e.kind() == ExpressionKind::NamedValue
&& e.referenced_symbol()
.is_some_and(|s| s.kind() == sv_lang::kinds::SymbolKind::ClassProperty));
if !is_member {
return Ok(None);
}
let Some(shape) = e.expr_type().as_ref().and_then(array_shape) else {
return Ok(None);
};
if shape.dims.is_empty()
|| shape.rows() > MAX_UNROLLED_ROWS
|| !crate::collections::handle_offsets(&shape.elem).is_empty()
{
return Ok(None);
}
let flat = crate::expr::lower_expr(cx, e)?.into_packed("a member array value", &cx.fn_sym)?;
let ptr = rows_from_flat(cx, flat, &shape, "arr_member")?;
Ok(Some((ArrayRef::whole(cx, Backing::Local(ptr)), shape)))
}
pub(crate) fn rows_from_flat<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
flat: PackedValue<'ctx>,
shape: &ArrayShape<'_>,
name: &str,
) -> Result<PointerValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ptr = crate::expr::entry_alloca(cx, local_array_type(cx.ctx, shape, cx.repr), name);
let tmp = ArrayRef::whole(cx, Backing::Local(ptr));
let w = shape.elem.value_width();
let i64t = cx.ctx.i64_type();
for r in 0..shape.rows() {
let row = PackedValue {
signed: shape.elem.is_signed(),
..op.part_select4(
flat,
op.i_n(flat.width).const_int(r * u64::from(w), false),
cx.ctx.bool_type().const_zero(),
w,
)
};
store_row(cx, &tmp, i64t.const_int(r, false), row)?;
}
Ok(ptr)
}
pub(crate) fn array_call_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<(ArrayRef<'ctx>, ArrayShape<'d>)>, LoweringError> {
let e = peel_array_conversions(e);
if crate::func::user_callee(&e).is_none() {
return Ok(None);
}
let Some(shape) = e.expr_type().as_ref().and_then(array_shape) else {
return Ok(None);
};
let ptr = crate::func::lower_array_call(cx, e)?;
Ok(Some((ArrayRef::whole(cx, Backing::Local(ptr)), shape)))
}
pub(crate) fn local_row_ptr<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
base: PointerValue<'ctx>,
row: IntValue<'ctx>,
width: u32,
) -> PointerValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
unsafe {
cx.b.build_in_bounds_gep(op.packed_type(width, cx.repr), base, &[row], "arr_elem")
}
.unwrap()
}
pub(crate) fn default_value<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, value_ty: &Type) -> IntValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let width = value_ty.value_width();
if sv_sim_sim::state::storage::is_unpacked_aggregate(value_ty)
&& let Some(bits) = sv_sim_sim::state::storage::default_bits(value_ty)
{
return crate::value::const_bits(cx.ctx, &bits, false, cx.repr).llvm;
}
let ty = op.packed_type(width, cx.repr);
if value_ty.is_four_state() && cx.repr == ValueRepr::FourState {
let ones = ty.const_all_ones();
cx.b.build_left_shift(ones, ty.const_int(u64::from(width), false), "arr_default_x")
.unwrap()
} else {
ty.const_zero()
}
}
fn huge_rows_refused(cx: &LowerCtx<'_, '_, '_>, width: u32) -> Result<(), LoweringError> {
if width > crate::value::WIDE_THRESHOLD {
return Err(LoweringError::unsupported_at(
"a whole-array operation on an unpacked array whose elements are wider than the \
wide-value threshold (LRM 1800-2023 6.9.1); only reading or writing one element \
(`m[i]`, `m[i][b]`) is supported",
&cx.fn_sym,
));
}
Ok(())
}
pub(crate) fn load_raw<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
backing: Backing<'ctx>,
row: IntValue<'ctx>,
width: u32,
) -> Result<IntValue<'ctx>, LoweringError> {
Ok(match backing {
Backing::Memory(info) => {
huge_rows_refused(cx, width)?;
let acc = cx.mem_accessor.ok_or_else(|| {
LoweringError::unsupported_at("memory read with no accessor context", &cx.fn_sym)
})?;
crate::accessor::build_get_mem_word(cx, acc.fns, acc.handle, info.slot, row, width)
}
Backing::Local(base) => {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let p = local_row_ptr(cx, base, row, width);
cx.b.build_load(op.packed_type(width, cx.repr), p, "arr_ld")
.unwrap()
.into_int_value()
}
})
}
pub(crate) fn store_raw<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
backing: Backing<'ctx>,
row: IntValue<'ctx>,
value: IntValue<'ctx>,
width: u32,
) -> Result<(), LoweringError> {
match backing {
Backing::Memory(info) => {
huge_rows_refused(cx, width)?;
let acc = cx.mem_accessor.ok_or_else(|| {
LoweringError::unsupported_at("memory write with no accessor context", &cx.fn_sym)
})?;
crate::accessor::build_set_mem_word(
cx, acc.fns, acc.handle, info.slot, row, value, width,
);
}
Backing::Local(base) => {
let p = local_row_ptr(cx, base, row, width);
cx.b.build_store(p, value).unwrap();
}
}
Ok(())
}
pub(crate) fn load_row<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &ArrayRef<'ctx>,
k: IntValue<'ctx>,
elem: &Type,
) -> Result<PackedValue<'ctx>, LoweringError> {
let width = elem.value_width();
let row = cx.b.build_int_add(r.base, k, "arr_row").unwrap();
let fetched = load_raw(cx, r.backing, row, width)?;
let llvm = if r.always_valid() {
fetched
} else {
let dflt = default_value(cx, elem);
cx.b.build_select(r.valid, fetched, dflt, "arr_read")
.unwrap()
.into_int_value()
};
Ok(PackedValue {
llvm,
width,
repr: cx.repr,
signed: elem.is_signed(),
})
}
pub(crate) fn stored_elements_in_stream_order<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<(Vec<PackedValue<'ctx>>, Type<'d>)>, LoweringError> {
let Some((r, shape)) = array_target(cx, peel_array_conversions(e))? else {
return Ok(None);
};
let i64t = cx.ctx.i64_type();
let mut out = Vec::with_capacity(shape.rows() as usize);
for flat in 0..shape.rows() {
let mut rest = flat;
let mut row = 0u64;
for k in (0..shape.dims.len()).rev() {
let size = shape.size(k);
let i = rest % size;
rest /= size;
let (left, right) = shape.dims[k];
let pos = if left <= right { i } else { size - 1 - i };
row += pos * shape.stride(k);
}
out.push(load_row(cx, &r, i64t.const_int(row, false), &shape.elem)?);
}
Ok(Some((out, shape.elem)))
}
pub(crate) fn guarded<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
valid: IntValue<'ctx>,
f: impl FnOnce() -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
if valid.get_zero_extended_constant() == Some(1) {
return f();
}
let func =
cx.b.get_insert_block()
.and_then(|b| b.get_parent())
.expect("inside a function");
let then_bb = cx.ctx.append_basic_block(func, "arr_valid");
let done_bb = cx.ctx.append_basic_block(func, "arr_valid_done");
cx.b.build_conditional_branch(valid, then_bb, done_bb)
.unwrap();
cx.b.position_at_end(then_bb);
f()?;
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
Ok(())
}
pub(crate) fn store_row<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &ArrayRef<'ctx>,
k: IntValue<'ctx>,
value: PackedValue<'ctx>,
) -> Result<(), LoweringError> {
guarded(cx, r.valid, || {
let row = cx.b.build_int_add(r.base, k, "arr_row").unwrap();
store_raw(cx, r.backing, row, value.llvm, value.width)
})
}
pub(crate) fn read_element<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &ElemRef<'ctx>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let fetched = load_raw(cx, r.backing, r.row, r.width)?;
let dflt = r.default;
let v =
cx.b.build_select(r.valid, fetched, dflt, "arr_read")
.unwrap()
.into_int_value();
Ok(PackedValue {
llvm: v,
width: r.width,
repr: cx.repr,
signed: r.signed,
})
}
pub(crate) fn write_element<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &ElemRef<'ctx>,
value: PackedValue<'ctx>,
) -> Result<(), LoweringError> {
guarded(cx, r.valid, || {
store_raw(cx, r.backing, r.row, value.llvm, r.width)
})
}
#[derive(Clone, Copy)]
pub(crate) struct EventElem<'ctx> {
pub info: MemorySignalInfo,
pub row: IntValue<'ctx>,
pub valid: IntValue<'ctx>,
}
pub(crate) fn resolve_event_element<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<EventElem<'ctx>>, LoweringError> {
if let Some(h) = crate::events::resolve_handle(cx, e)? {
return Ok(Some(h));
}
if e.kind() != ExpressionKind::ElementSelect || !e.expr_type().is_some_and(|t| t.is_event()) {
return Ok(None);
}
let Some((r, shape)) = resolve_array_ref(cx, e)? else {
return Ok(None);
};
if !shape.dims.is_empty() {
return Ok(None);
}
match r.backing {
Backing::Memory(info) => Ok(Some(EventElem {
info,
row: r.base,
valid: r.valid,
})),
Backing::Local(_) => Err(LoweringError::unsupported_at(
"an element of an event array that is not a module-level signal",
&cx.fn_sym,
)),
}
}
pub(crate) fn load_event_elem<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
el: &EventElem<'ctx>,
) -> Result<IntValue<'ctx>, LoweringError> {
load_raw(
cx,
Backing::Memory(el.info),
el.row,
sv_sim_sim::state::EVENT_CELL_WIDTH,
)
}
pub(crate) fn store_event_elem<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
el: &EventElem<'ctx>,
value: IntValue<'ctx>,
) -> Result<(), LoweringError> {
store_raw(
cx,
Backing::Memory(el.info),
el.row,
value,
sv_sim_sim::state::EVENT_CELL_WIDTH,
)
}
pub(crate) fn when_valid<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
valid: IntValue<'ctx>,
f: impl FnOnce() -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
guarded(cx, valid, f)
}
pub(crate) fn row_loop<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rows: u64,
acc: IntValue<'ctx>,
mut body: impl FnMut(IntValue<'ctx>, IntValue<'ctx>) -> Result<IntValue<'ctx>, LoweringError>,
) -> Result<IntValue<'ctx>, LoweringError> {
let i64t = cx.ctx.i64_type();
let pre = cx.b.get_insert_block().expect("positioned");
let func = pre.get_parent().expect("inside a function");
let head = cx.ctx.append_basic_block(func, "arr_loop");
let body_bb = cx.ctx.append_basic_block(func, "arr_loop_row");
let done = cx.ctx.append_basic_block(func, "arr_loop_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let k = cx.b.build_phi(i64t, "k").unwrap();
k.add_incoming(&[(&i64t.const_zero(), pre)]);
let a = cx.b.build_phi(acc.get_type(), "acc").unwrap();
a.add_incoming(&[(&acc, pre)]);
let kv = k.as_basic_value().into_int_value();
let av = a.as_basic_value().into_int_value();
let more =
cx.b.build_int_compare(IntPredicate::ULT, kv, i64t.const_int(rows, false), "more")
.unwrap();
cx.b.build_conditional_branch(more, body_bb, done).unwrap();
cx.b.position_at_end(body_bb);
let next_acc = body(kv, av)?;
let next =
cx.b.build_int_add(kv, i64t.const_int(1, false), "k_next")
.unwrap();
let latch = cx.b.get_insert_block().expect("positioned");
k.add_incoming(&[(&next, latch)]);
a.add_incoming(&[(&next_acc, latch)]);
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(done);
Ok(av)
}
pub(crate) fn init_local_array<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
base: PointerValue<'ctx>,
shape: &ArrayShape,
) {
let width = shape.elem.value_width();
let dflt = default_value(cx, &shape.elem);
let unit = cx.ctx.bool_type().const_zero();
row_loop(cx, shape.rows(), unit, |k, acc| {
let p = local_row_ptr(cx, base, k, width);
cx.b.build_store(p, dflt).unwrap();
Ok(acc)
})
.expect("a local store cannot fail");
}
pub(crate) fn init_array_var<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
ptr: PointerValue<'ctx>,
shape: &ArrayShape<'d>,
init: Option<sv_lang::Expression<'d>>,
) -> Result<(), LoweringError> {
match init {
Some(init) => {
assign_array_into_fresh(cx, ArrayRef::whole(cx, Backing::Local(ptr)), shape, init)
}
None => {
init_local_array(cx, ptr, shape);
Ok(())
}
}
}
enum ArraySource<'ctx, 'd> {
Stored(ArrayRef<'ctx>),
Rows(Vec<Row<'d>>),
Values(Vec<PackedValue<'ctx>>),
}
#[derive(Clone, Copy)]
enum Row<'d> {
Expr(sv_lang::Expression<'d>),
Byte(u8),
}
fn row_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
r: Row<'d>,
elem: &Type,
own: bool,
) -> Result<PackedValue<'ctx>, LoweringError> {
match r {
Row::Expr(x) => element_value(cx, x, elem, own),
Row::Byte(c) => {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let bits: Vec<sv_lang::Bit> = (0..8)
.map(|i| {
if c >> i & 1 == 1 {
sv_lang::Bit::One
} else {
sv_lang::Bit::Zero
}
})
.collect();
let v = crate::value::const_bits(cx.ctx, &bits, false, cx.repr);
Ok(crate::select::fit_width(&op, v, elem.value_width()))
}
}
}
fn string_literal_bytes(e: sv_lang::Expression<'_>) -> Option<Vec<u8>> {
let mut x = e;
while x.kind() == ExpressionKind::Conversion {
x = x.conversion_operand()?;
}
if x.kind() != ExpressionKind::StringLiteral {
return None;
}
x.string_literal_value().map(String::into_bytes)
}
fn peel_array_conversions(mut e: sv_lang::Expression<'_>) -> sv_lang::Expression<'_> {
while e.kind() == ExpressionKind::Conversion
&& let Some(inner) = e.conversion_operand()
&& inner.expr_type().is_some_and(|t| t.is_unpacked_array())
{
e = inner;
}
e
}
fn array_operand<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<(ArrayShape<'d>, ArraySource<'ctx, 'd>), LoweringError> {
let e = peel_array_conversions(e);
let shape = e
.expr_type()
.as_ref()
.and_then(array_shape)
.ok_or_else(|| {
LoweringError::unsupported_at(
"an unpacked-array value whose type is not a fixed-size array",
&cx.fn_sym,
)
})?;
if let Some((r, _)) = array_target(cx, e)? {
return Ok((shape, ArraySource::Stored(r)));
}
if let Some(values) = crate::vif::array_values(cx, e) {
return Ok((shape, ArraySource::Values(values?)));
}
if let Some((r, _)) = spill_member_array(cx, e)? {
return Ok((shape, ArraySource::Stored(r)));
}
if e.kind() == ExpressionKind::Conversion && matches!(e.conversion_kind(), Some(2 | 4)) {
let stored = if e.conversion_kind() == Some(2) {
crate::stream::lower_stream_conversion(cx, e)?
} else {
crate::stream::lower_bitstream_cast(cx, e)?
}
.into_packed("a streamed array value", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ew = shape.elem.value_width();
let values = (0..shape.rows() as u32)
.map(|r| PackedValue {
signed: shape.elem.is_signed(),
..op.part_select4(
stored,
op.i_n(stored.width).const_int(u64::from(r * ew), false),
cx.ctx.bool_type().const_zero(),
ew,
)
})
.collect();
return Ok((shape, ArraySource::Values(values)));
}
if e.kind() == ExpressionKind::ElementSelect
&& e.select_value()
.is_some_and(|b| crate::collections::coll_of_expr(&b).is_some())
{
let stored =
crate::expr::lower_expr(cx, e)?.into_packed("a collection element", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ew = shape.elem.value_width();
let values = (0..shape.rows() as u32)
.map(|r| PackedValue {
signed: shape.elem.is_signed(),
..op.part_select4(
stored,
op.i_n(stored.width).const_int(u64::from(r * ew), false),
cx.ctx.bool_type().const_zero(),
ew,
)
})
.collect();
return Ok((shape, ArraySource::Values(values)));
}
if e.kind() == ExpressionKind::RangeSelect
&& let Some(base) = e.select_value()
&& let Some(c) = crate::collections::coll_of_expr(&base)
&& !c.assoc
&& shape.dims.len() == 1
&& !shape.elem.is_floating()
&& crate::collections::handle_offsets(&shape.elem).is_empty()
{
let h = crate::collections::handle_of(cx, base)?;
let (lo, _) = crate::collections::slice_bounds(cx, &c, h, e)?;
let i64t = cx.ctx.i64_type();
let width = shape.elem.value_width();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let mut values = Vec::new();
for k in 0..shape.rows() {
let pos =
cx.b.build_int_add(lo, i64t.const_int(k, false), "slice_pos")
.unwrap();
let v = crate::collections::read_at_coll(cx, &c, h, pos)
.into_packed("a dynamic array slice element", &cx.fn_sym)?;
values.push(crate::select::fit_width(&op, v, width));
}
if shape.dims[0].0 > shape.dims[0].1 {
values.reverse();
}
return Ok((shape, ArraySource::Values(values)));
}
if let Some(values) = crate::array_methods::map_fixed_values(cx, e) {
return Ok((shape, ArraySource::Values(values?)));
}
if crate::func::user_callee(&e).is_some() {
let ptr = crate::func::lower_array_call(cx, e)?;
return Ok((
shape,
ArraySource::Stored(ArrayRef::whole(cx, Backing::Local(ptr))),
));
}
if e.kind() == ExpressionKind::ConditionalOp {
let values = conditional_array_values(cx, e, &shape)?;
return Ok((shape, ArraySource::Values(values)));
}
if e.kind() == ExpressionKind::Concatenation
&& e.operands()
.iter()
.any(|o| o.expr_type().is_some_and(|t| t.is_unpacked_array()))
{
let values = array_concat_values(cx, e, &shape)?;
return Ok((shape, ArraySource::Values(values)));
}
if e.kind() == ExpressionKind::NamedValue
&& shape.elem.is_string()
&& let Some(sv_lang::ConstantValue::Array(top)) = e.constant_value()
{
let values = constant_string_rows(cx, &sv_lang::ConstantValue::Array(top), &shape)?;
return Ok((shape, ArraySource::Values(values)));
}
let mut rows = Vec::new();
pattern_rows(cx, e, &shape, 0, &mut rows)?;
Ok((shape, ArraySource::Rows(rows)))
}
fn constant_string_rows<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
cv: &sv_lang::ConstantValue,
shape: &ArrayShape<'_>,
) -> Result<Vec<PackedValue<'ctx>>, LoweringError> {
fn walk<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
cv: &sv_lang::ConstantValue,
k: usize,
shape: &ArrayShape<'_>,
out: &mut Vec<PackedValue<'ctx>>,
) -> Result<(), LoweringError> {
let bad = || {
LoweringError::unsupported_at(
"a constant string array with an element of another kind",
&cx.fn_sym,
)
};
if k == shape.dims.len() {
let sv_lang::ConstantValue::String(text) = cv else {
return Err(bad());
};
let lit = crate::strings::literal(cx, text.as_bytes());
let v = crate::real::to_storage(
cx,
crate::expr::LoweredValue::Str(lit),
shape.elem.value_width(),
"a string array element",
)?;
out.push(v);
return Ok(());
}
let sv_lang::ConstantValue::Array(elems) = cv else {
return Err(bad());
};
let mut parts: Vec<Vec<PackedValue<'ctx>>> = Vec::new();
for el in elems.iter() {
let mut one = Vec::new();
walk(cx, el, k + 1, shape, &mut one)?;
parts.push(one);
}
if shape.dims[k].0 > shape.dims[k].1 {
parts.reverse();
}
out.extend(parts.into_iter().flatten());
Ok(())
}
let mut out = Vec::new();
walk(cx, cv, 0, shape, &mut out)?;
Ok(out)
}
fn conditional_array_values<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
shape: &ArrayShape<'d>,
) -> Result<Vec<PackedValue<'ctx>>, LoweringError> {
let refuse = |what: &str| {
LoweringError::unsupported_at(&format!("an unpacked-array `?:` whose {what}"), &cx.fn_sym)
};
let conds = e.conditions();
if conds.len() != 1 || conds[0].pattern().is_some() {
return Err(refuse("condition is a `matches` pattern"));
}
if !crate::collections::handle_offsets(&shape.elem).is_empty() {
return Err(refuse("elements are class handles or collections"));
}
let arm = |x: Option<sv_lang::Expression<'d>>| {
x.ok_or_else(|| refuse("arm is missing"))
.and_then(|x| {
let inner = peel_array_conversions(x);
if crate::func::user_callee(&inner).is_some() {
return Err(refuse("arm is an array-valued call"));
}
if inner.kind() == ExpressionKind::ConditionalOp {
return Ok((conditional_array_values(cx, inner, shape)?, true));
}
Ok((Vec::new(), false))
})
.map(|(v, done)| (v, done, x))
};
let (tv, t_done, t) = arm(e.true_value())?;
let (fv, f_done, f) = arm(e.false_value())?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let width = shape.elem.value_width();
let fit = |v: PackedValue<'ctx>| crate::select::fit_width(&op, v, width);
let rows_of = |done: bool,
vals: Vec<PackedValue<'ctx>>,
x: Option<sv_lang::Expression<'d>>|
-> Result<Vec<PackedValue<'ctx>>, LoweringError> {
if done {
return Ok(vals);
}
let (s, src) = array_operand(cx, x.expect("checked by arm"))?;
check_compatible(cx, shape, &s)?;
let all: Vec<PackedValue<'ctx>> = match src {
ArraySource::Rows(exprs) => exprs
.iter()
.map(|&r| row_value(cx, r, &shape.elem, false))
.collect::<Result<_, _>>()?,
ArraySource::Values(vs) => vs.into_iter().map(fit).collect(),
ArraySource::Stored(sr) => (0..s.rows())
.map(|r| load_row(cx, &sr, i64t.const_int(r, false), &s.elem).map(fit))
.collect::<Result<_, _>>()?,
};
Ok((0..shape.rows())
.map(|r| all[corresponding_row(shape, &s, r) as usize])
.collect())
};
let then_rows = rows_of(t_done, tv, t)?;
let else_rows = rows_of(f_done, fv, f)?;
let cond_pv = crate::huge::truth_packed(
cx,
crate::expr::lower_expr_h(cx, conds[0].expr())?,
crate::huge::TruthMode::Ternary,
"ternary condition must be an integral (non-real) value",
)?;
let cond_true = crate::ops::ternary_is_definitely_true(cx.ctx, &cx.b, cond_pv);
let cond_false = crate::ops::is_definitely_false(cx.ctx, &cx.b, cond_pv);
Ok(then_rows
.into_iter()
.zip(else_rows)
.map(|(a, b)| {
let merged = op.ternary_merge4(a, b);
let inner = op.select(cond_false, b, merged);
op.select(cond_true, a, inner)
})
.collect())
}
fn holds_collection(ty: &Type<'_>) -> bool {
crate::collections::handle_offsets(ty)
.iter()
.any(|s| s.kind == crate::collections::HandleKind::Coll)
}
fn is_single_collection_element(elem: &Type<'_>, ty: Option<&Type<'_>>) -> bool {
holds_collection(elem) && ty.is_some_and(|t| elem.is_assignment_compatible(t))
}
pub(crate) fn array_operand_elements<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
o: sv_lang::Expression<'d>,
elem: &sv_lang::Type<'d>,
) -> Result<Vec<PackedValue<'ctx>>, LoweringError> {
array_operand_elements_with(cx, o, elem, false)
}
fn array_operand_elements_with<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
o: sv_lang::Expression<'d>,
elem: &sv_lang::Type<'d>,
own: bool,
) -> Result<Vec<PackedValue<'ctx>>, LoweringError> {
let (os, mut vs) = array_operand_rows(cx, o, elem, own)?;
if os.dims.len() != 1 {
return Err(LoweringError::unsupported_at(
"an unpacked array concatenation with a multi-dimensional operand",
&cx.fn_sym,
));
}
if os.dims[0].0 > os.dims[0].1 {
vs.reverse();
}
Ok(vs)
}
fn array_operand_rows<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
o: sv_lang::Expression<'d>,
elem: &sv_lang::Type<'d>,
own: bool,
) -> Result<(ArrayShape<'d>, Vec<PackedValue<'ctx>>), LoweringError> {
let (os, src) = array_operand(cx, o)?;
let i64t = cx.ctx.i64_type();
let retains = own
&& crate::func::user_callee(&peel_array_conversions(o)).is_none()
&& !crate::collections::handle_offsets(&os.elem).is_empty();
let vs = match src {
ArraySource::Rows(rows) => rows
.iter()
.map(|&x| row_value(cx, x, elem, own))
.collect::<Result<Vec<_>, _>>()?,
ArraySource::Values(vs) => vs,
ArraySource::Stored(sr) => (0..os.rows())
.map(|k| {
let v = load_row(cx, &sr, i64t.const_int(k, false), &os.elem)?;
Ok(if retains {
crate::collections::retain_stored(cx, &os.elem, v)
} else {
v
})
})
.collect::<Result<Vec<_>, LoweringError>>()?,
};
Ok((os, vs))
}
fn array_concat_values<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
shape: &ArrayShape<'d>,
) -> Result<Vec<PackedValue<'ctx>>, LoweringError> {
let refuse = |what: &str| {
Err(LoweringError::unsupported_at(
&format!("an unpacked array concatenation with {what}"),
&cx.fn_sym,
))
};
let own = !crate::collections::handle_offsets(&shape.elem).is_empty();
if shape.dims.len() != 1 {
return array_concat_values_nd(cx, e, shape, own);
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let width = shape.elem.value_width();
let mut values = Vec::new();
for o in e.operands() {
let ty = o.expr_type();
if is_single_collection_element(&shape.elem, ty.as_ref()) {
values.push(element_value(cx, o, &shape.elem, true)?);
continue;
}
if ty
.as_ref()
.is_some_and(sv_sim_sim::state::storage::is_collection)
{
return refuse("a queue, dynamic or associative array operand");
}
if ty.as_ref().is_some_and(|t| t.is_unpacked_array()) {
let vs = array_operand_elements_with(cx, o, &shape.elem, own)?;
values.extend(
vs.into_iter()
.map(|v| crate::select::fit_width(&op, v, width)),
);
} else {
values.push(element_value(cx, o, &shape.elem, own)?);
}
}
if values.len() as u64 != shape.rows() {
return refuse("an element count that differs from the target array's size");
}
if shape.dims[0].0 > shape.dims[0].1 {
values.reverse();
}
Ok(values)
}
fn array_concat_values_nd<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
shape: &ArrayShape<'d>,
own: bool,
) -> Result<Vec<PackedValue<'ctx>>, LoweringError> {
let refuse = |what: &str| {
Err(LoweringError::unsupported_at(
&format!("an unpacked array concatenation with {what}"),
&cx.fn_sym,
))
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let width = shape.elem.value_width();
let inner = shape.stride(0) as usize;
let mut chunks: Vec<Vec<PackedValue<'ctx>>> = Vec::new();
for o in e.operands() {
let ty = o.expr_type();
if ty
.as_ref()
.is_some_and(sv_sim_sim::state::storage::is_collection)
{
return refuse("a queue, dynamic or associative array operand");
}
if !ty.as_ref().is_some_and(|t| t.is_unpacked_array()) {
return refuse("a non-array operand in a multi-dimensional result");
}
let (os, vs) = array_operand_rows(cx, o, &shape.elem, own)?;
let vs: Vec<_> = vs
.into_iter()
.map(|v| crate::select::fit_width(&op, v, width))
.collect();
if os.dims.len() + 1 == shape.dims.len() && vs.len() == inner {
chunks.push(vs);
} else if os.dims.len() == shape.dims.len() && vs.len() % inner == 0 {
let mut cs: Vec<Vec<PackedValue<'ctx>>> = vs.chunks(inner).map(<[_]>::to_vec).collect();
if os.dims[0].0 > os.dims[0].1 {
cs.reverse();
}
chunks.extend(cs);
} else {
return refuse("an operand whose rank or element shape differs from the result's");
}
}
if chunks.len() as u64 != shape.size(0) {
return refuse("an element count that differs from the target array's size");
}
if shape.dims[0].0 > shape.dims[0].1 {
chunks.reverse();
}
Ok(chunks.into_iter().flatten().collect())
}
pub(crate) fn pack_array_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let (shape, src) = array_operand(cx, e)?;
if shape.rows() > MAX_UNROLLED_ROWS {
return Err(LoweringError::unsupported_at(
"a very large fixed-size array stored as a collection element",
&cx.fn_sym,
));
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let width = shape.elem.value_width();
let share = crate::func::user_callee(&peel_array_conversions(e)).is_none();
let retains = share && !crate::collections::handle_offsets(&shape.elem).is_empty();
let mut values = match src {
ArraySource::Rows(exprs) => exprs
.iter()
.map(|&x| row_value(cx, x, &shape.elem, true))
.collect::<Result<Vec<_>, _>>()?,
ArraySource::Values(vs) => vs
.into_iter()
.map(|v| crate::select::fit_width(&op, v, width))
.collect(),
ArraySource::Stored(sr) => (0..shape.rows())
.map(|r| {
let v = load_row(cx, &sr, cx.ctx.i64_type().const_int(r, false), &shape.elem)?;
Ok(if retains {
crate::collections::retain_stored(cx, &shape.elem, v)
} else {
v
})
})
.collect::<Result<Vec<_>, LoweringError>>()?,
};
values.reverse();
Ok(op.concat4(&values))
}
pub(crate) fn store_array_from_packed<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: sv_lang::Expression<'d>,
v: PackedValue<'ctx>,
) -> Result<(), LoweringError> {
let Some((dst, shape)) = array_target(cx, target)? else {
return Err(LoweringError::unsupported_at(
"an array that is not stored as rows",
&cx.fn_sym,
));
};
if !crate::collections::handle_offsets(&shape.elem).is_empty()
|| shape.rows() > MAX_UNROLLED_ROWS
{
return Err(LoweringError::unsupported_at(
"storing a flat value into an array of handles or a very large array",
&cx.fn_sym,
));
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let w = shape.elem.value_width();
let i64t = cx.ctx.i64_type();
for r in 0..shape.rows() {
let row = PackedValue {
signed: false,
..op.part_select4(
v,
op.i_n(v.width).const_int(r * u64::from(w), false),
cx.ctx.bool_type().const_zero(),
w,
)
};
store_row(cx, &dst, i64t.const_int(r, false), row)?;
}
Ok(())
}
pub(crate) fn write_array_rows_from_packed<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: sv_lang::Expression<'d>,
v: PackedValue<'ctx>,
write: &mut dyn FnMut(
crate::select::Lvalue<'ctx>,
PackedValue<'ctx>,
) -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let Some((dst, shape)) = array_target(cx, target)? else {
return Err(LoweringError::unsupported_at(
"an array that is not stored as rows",
&cx.fn_sym,
));
};
let Backing::Memory(info) = dst.backing else {
return Err(LoweringError::unsupported_at(
"a nonblocking assignment to a local array",
&cx.fn_sym,
));
};
if !crate::collections::handle_offsets(&shape.elem).is_empty()
|| shape.rows() > MAX_UNROLLED_ROWS
{
return Err(LoweringError::unsupported_at(
"storing a flat value into an array of handles or a very large array",
&cx.fn_sym,
));
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let w = shape.elem.value_width();
let i64t = cx.ctx.i64_type();
for r in 0..shape.rows() {
let row = PackedValue {
signed: false,
..op.part_select4(
v,
op.i_n(v.width).const_int(r * u64::from(w), false),
cx.ctx.bool_type().const_zero(),
w,
)
};
guarded(cx, dst.valid, || {
let idx =
cx.b.build_int_add(dst.base, i64t.const_int(r, false), "arr_row")
.unwrap();
write(
crate::select::mem_row_lvalue(cx, info, idx, row.width, row.signed),
row,
)
})?;
}
Ok(())
}
fn pattern_rows<'d>(
cx: &LowerCtx<'_, 'd, '_>,
e: sv_lang::Expression<'d>,
shape: &ArrayShape<'d>,
k: usize,
out: &mut Vec<Row<'d>>,
) -> Result<(), LoweringError> {
let e = peel_array_conversions(e);
if k + 1 == shape.dims.len()
&& shape.elem.value_width() == 8
&& let Some(bytes) = string_literal_bytes(e)
{
let n = shape.size(k) as usize;
let mut chars: Vec<Row<'d>> = (0..n)
.map(|i| Row::Byte(bytes.get(i).copied().unwrap_or(0)))
.collect();
if shape.dims[k].0 > shape.dims[k].1 {
chars.reverse();
}
out.extend(chars);
return Ok(());
}
let mut elems = match e.kind() {
ExpressionKind::SimpleAssignmentPattern | ExpressionKind::StructuredAssignmentPattern => {
e.pattern_elements()
}
ExpressionKind::Concatenation
if e.operands().iter().all(|o| {
!o.expr_type().is_some_and(|t| {
t.is_unpacked_array() || sv_sim_sim::state::storage::is_collection(&t)
})
}) =>
{
e.operands()
}
ExpressionKind::ReplicatedAssignmentPattern => {
let n = e
.replicated_pattern_count()
.and_then(|c| c.constant_value())
.and_then(|c| c.as_i64())
.ok_or_else(|| {
LoweringError::unsupported_at(
"a replicated assignment pattern with a non-constant count",
&cx.fn_sym,
)
})?;
let one = e.pattern_elements();
(0..n).flat_map(|_| one.iter().copied()).collect()
}
_ => {
return Err(LoweringError::unsupported_at(
&format!(
"an unpacked-array value of expression kind {:?} (only a stored \
array, an array-valued function call or an assignment pattern)",
e.kind()
),
&cx.fn_sym,
));
}
};
if elems.len() as u64 != shape.size(k) {
return Err(LoweringError::unsupported_at(
"an assignment pattern whose element count does not match its array dimension",
&cx.fn_sym,
));
}
if shape.dims[k].0 > shape.dims[k].1 {
elems.reverse();
}
for el in elems {
if k + 1 == shape.dims.len() {
out.push(Row::Expr(el));
} else {
pattern_rows(cx, el, shape, k + 1, out)?;
}
}
Ok(())
}
fn corresponding_row(to: &ArrayShape, from: &ArrayShape, row: u64) -> u64 {
let mut rest = row;
let mut out = 0;
let mut stride = 1;
for k in (0..to.dims.len()).rev() {
let size = to.size(k);
let off = rest % size;
rest /= size;
let flip = (to.dims[k].0 > to.dims[k].1) != (from.dims[k].0 > from.dims[k].1);
out += if flip { size - 1 - off } else { off } * stride;
stride *= size;
}
out
}
fn same_row_order(a: &ArrayShape, b: &ArrayShape) -> bool {
(0..a.dims.len()).all(|k| (a.dims[k].0 > a.dims[k].1) == (b.dims[k].0 > b.dims[k].1))
}
pub(crate) const MAX_UNROLLED_ROWS: u64 = 1 << 16;
fn check_compatible(
cx: &LowerCtx<'_, '_, '_>,
a: &ArrayShape,
b: &ArrayShape,
) -> Result<(), LoweringError> {
let same = a.dims.len() == b.dims.len() && (0..a.dims.len()).all(|k| a.size(k) == b.size(k));
if !same {
return Err(LoweringError::unsupported_at(
"unpacked arrays of different shapes in one assignment or comparison",
&cx.fn_sym,
));
}
if a.rows() > MAX_UNROLLED_ROWS && !same_row_order(a, b) {
return Err(LoweringError::unsupported_at(
"a copy or comparison of a very large array between opposite index directions",
&cx.fn_sym,
));
}
Ok(())
}
fn element_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
elem: &Type,
own: bool,
) -> Result<PackedValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let width = elem.value_width();
let lowered = if own {
crate::collections::owned(cx, e)?
} else {
lower_expr(cx, e)?
};
let v = crate::real::to_storage(cx, lowered, width, "an array element wider than 128 bits")?;
Ok(crate::select::fit_width(&op, v, width))
}
fn may_overlap(a: &ArrayRef<'_>, b: &ArrayRef<'_>) -> bool {
match (a.backing, b.backing) {
(Backing::Local(x), Backing::Local(y)) => x == y,
(Backing::Memory(x), Backing::Memory(y)) => x.slot == y.slot,
_ => false,
}
}
fn snapshot<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
src: &ArrayRef<'ctx>,
shape: &ArrayShape,
retain: bool,
) -> Result<ArrayRef<'ctx>, LoweringError> {
let ptr = crate::expr::entry_alloca(cx, local_array_type(cx.ctx, shape, cx.repr), "arr_tmp");
let tmp = ArrayRef::whole(cx, Backing::Local(ptr));
let unit = cx.ctx.bool_type().const_zero();
row_loop(cx, shape.rows(), unit, |k, acc| {
let mut v = load_row(cx, src, k, &shape.elem)?;
if retain {
v = crate::collections::retain_stored(cx, &shape.elem, v);
}
store_row(cx, &tmp, k, v)?;
Ok(acc)
})?;
Ok(tmp)
}
fn emit_rows<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
dst: Option<&ArrayRef<'ctx>>,
dst_shape: &ArrayShape<'d>,
src_shape: &ArrayShape<'d>,
src: ArraySource<'ctx, 'd>,
share: bool,
mut write: impl FnMut(IntValue<'ctx>, PackedValue<'ctx>) -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
check_compatible(cx, dst_shape, src_shape)?;
let retains = share && !crate::collections::handle_offsets(&src_shape.elem).is_empty();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let width = dst_shape.elem.value_width();
let fit = |v: PackedValue<'ctx>| crate::select::fit_width(&op, v, width);
let values = match src {
ArraySource::Rows(exprs) => exprs
.iter()
.map(|&x| row_value(cx, x, &dst_shape.elem, true))
.collect::<Result<Vec<_>, _>>()?,
ArraySource::Values(vs) => vs.into_iter().map(fit).collect(),
ArraySource::Stored(sr) => {
let (sr, retain_read) = match dst {
Some(d) if may_overlap(d, &sr) => (snapshot(cx, &sr, src_shape, retains)?, false),
_ => (sr, retains),
};
let read = |k: IntValue<'ctx>| -> Result<PackedValue<'ctx>, LoweringError> {
let v = load_row(cx, &sr, k, &src_shape.elem)?;
Ok(fit(if retain_read {
crate::collections::retain_stored(cx, &src_shape.elem, v)
} else {
v
}))
};
if same_row_order(dst_shape, src_shape) {
let unit = cx.ctx.bool_type().const_zero();
row_loop(cx, dst_shape.rows(), unit, |k, acc| {
write(k, read(k)?)?;
Ok(acc)
})?;
return Ok(());
}
(0..src_shape.rows())
.map(|r| read(i64t.const_int(r, false)))
.collect::<Result<Vec<_>, LoweringError>>()?
}
};
for r in 0..dst_shape.rows() {
let v = values[corresponding_row(dst_shape, src_shape, r) as usize];
write(i64t.const_int(r, false), v)?;
}
Ok(())
}
pub(crate) fn assign_array<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
dst: ArrayRef<'ctx>,
dst_shape: &ArrayShape<'d>,
rhs: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
assign_array_with(cx, dst, dst_shape, rhs, true)
}
pub(crate) fn assign_array_into_fresh<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
dst: ArrayRef<'ctx>,
dst_shape: &ArrayShape<'d>,
rhs: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
assign_array_with(cx, dst, dst_shape, rhs, false)
}
fn release_row<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &ArrayRef<'ctx>,
k: IntValue<'ctx>,
elem: &Type,
) -> Result<(), LoweringError> {
let old = load_row(cx, r, k, elem)?;
crate::collections::release_handles(cx, elem, crate::expr::LoweredValue::Packed(old));
Ok(())
}
fn assign_array_with<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
dst: ArrayRef<'ctx>,
dst_shape: &ArrayShape<'d>,
rhs: sv_lang::Expression<'d>,
holds_old: bool,
) -> Result<(), LoweringError> {
let has_handles = !crate::collections::handle_offsets(&dst_shape.elem).is_empty();
let releases = holds_old && has_handles;
let inner = peel_array_conversions(rhs);
if crate::collections::coll_of_expr(&inner).is_some() {
if dst_shape.dims.len() != 1 {
return Err(LoweringError::unsupported_at(
"a queue or dynamic array assigned to a multi-dimensional array",
&cx.fn_sym,
));
}
let rows = dst_shape.rows();
let descending = dst_shape.dims[0].0 > dst_shape.dims[0].1;
let h = crate::collections::handle_of(cx, inner)?;
let i64t = cx.ctx.i64_type();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let unit = cx.ctx.bool_type().const_zero();
let coll = crate::collections::coll_of_expr(&inner).expect("checked above");
let size = crate::collections::size_of_handle(cx, &coll, h);
let size =
cx.b.build_int_z_extend_or_bit_cast(size, i64t, "src_size")
.unwrap();
let bad =
cx.b.build_int_compare(
IntPredicate::NE,
size,
i64t.const_int(rows, false),
"size_bad",
)
.unwrap();
let func =
cx.b.get_insert_block()
.and_then(|b| b.get_parent())
.expect("in a function");
let err_bb = cx.ctx.append_basic_block(func, "arr_size_err");
let copy_bb = cx.ctx.append_basic_block(func, "arr_size_ok");
let done_bb = cx.ctx.append_basic_block(func, "arr_size_done");
cx.b.build_conditional_branch(bad, err_bb, copy_bb).unwrap();
cx.b.position_at_end(err_bb);
crate::stmt_shared::error_if(
cx,
cx.ctx.bool_type().const_int(1, false),
rhs.source_range().start,
&format!(
"a queue or dynamic array assigned to a fixed-size array of {rows} elements has a different size"
),
)?;
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(copy_bb);
row_loop(cx, rows, unit, |k, acc| {
let pos = if descending {
cx.b.build_int_sub(i64t.const_int(rows - 1, false), k, "arr_from_pos")
.unwrap()
} else {
k
};
let v = crate::collections::read_at(cx, inner, h, pos)?;
let width = dst_shape.elem.value_width();
let v = crate::real::to_storage(cx, v, width, "a >128-bit array element")?;
let v = crate::collections::retain_stored(
cx,
&dst_shape.elem,
crate::select::fit_width(&op, v, width),
);
if releases {
release_row(cx, &dst, k, &dst_shape.elem)?;
}
store_row(cx, &dst, k, v)?;
Ok(acc)
})?;
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
return Ok(());
}
if inner.kind() == ExpressionKind::Concatenation
&& inner.operands().iter().any(|o| {
let ty = o.expr_type();
ty.as_ref()
.is_some_and(sv_sim_sim::state::storage::is_collection)
&& !is_single_collection_element(&dst_shape.elem, ty.as_ref())
})
{
return assign_concat_with_collections(cx, &dst, dst_shape, inner, releases);
}
let (src_shape, src) = array_operand(cx, rhs)?;
let share = crate::func::user_callee(&inner).is_none();
emit_rows(cx, Some(&dst), dst_shape, &src_shape, src, share, |k, v| {
if releases {
release_row(cx, &dst, k, &dst_shape.elem)?;
}
store_row(cx, &dst, k, v)
})
}
fn assign_concat_with_collections<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
dst: &ArrayRef<'ctx>,
dst_shape: &ArrayShape<'d>,
e: sv_lang::Expression<'d>,
releases: bool,
) -> Result<(), LoweringError> {
let refuse = |what: &str| {
Err(LoweringError::unsupported_at(
&format!("an unpacked array concatenation with {what}"),
&cx.fn_sym,
))
};
let owns = !crate::collections::handle_offsets(&dst_shape.elem).is_empty();
let inner = dst_shape.stride(0);
let count = dst_shape.size(0);
let i64t = cx.ctx.i64_type();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let width = dst_shape.elem.value_width();
enum Part<'ctx, 'd> {
Fixed(Vec<Vec<PackedValue<'ctx>>>),
Coll(crate::collections::Coll<'d>, IntValue<'ctx>, IntValue<'ctx>),
}
let mut parts = Vec::new();
let mut total = i64t.const_zero();
for o in e.operands() {
let ty = o.expr_type();
if is_single_collection_element(&dst_shape.elem, ty.as_ref()) {
if inner != 1 {
return refuse("a collection element operand and a multi-dimensional target");
}
let v = element_value(cx, o, &dst_shape.elem, owns)?;
total =
cx.b.build_int_add(total, i64t.const_int(1, false), "cat_total")
.unwrap();
parts.push(Part::Fixed(vec![vec![v]]));
} else if ty
.as_ref()
.is_some_and(sv_sim_sim::state::storage::is_collection)
{
let Some(c) = crate::collections::coll_of_expr(&o) else {
return refuse("a collection operand of an unsupported kind");
};
if inner > 1 && (c.width as u64) != inner * u64::from(width) {
return refuse("an operand whose element shape differs from the target's");
}
let h = crate::collections::handle_of(cx, o)?;
let n = crate::collections::size_of_handle(cx, &c, h);
let n =
cx.b.build_int_z_extend_or_bit_cast(n, i64t, "cat_n")
.unwrap();
total = cx.b.build_int_add(total, n, "cat_total").unwrap();
parts.push(Part::Coll(c, h, n));
} else if ty.as_ref().is_some_and(|t| t.is_unpacked_array()) {
let chunks: Vec<Vec<PackedValue<'ctx>>> = if inner == 1 {
array_operand_elements_with(cx, o, &dst_shape.elem, owns)?
.into_iter()
.map(|v| vec![crate::select::fit_width(&op, v, width)])
.collect()
} else {
let (os, vs) = array_operand_rows(cx, o, &dst_shape.elem, owns)?;
let vs: Vec<_> = vs
.into_iter()
.map(|v| crate::select::fit_width(&op, v, width))
.collect();
let step = inner as usize;
if os.dims.len() + 1 == dst_shape.dims.len() && vs.len() == step {
vec![vs]
} else if os.dims.len() == dst_shape.dims.len() && vs.len() % step == 0 {
let mut cs: Vec<Vec<PackedValue<'ctx>>> =
vs.chunks(step).map(<[_]>::to_vec).collect();
if os.dims[0].0 > os.dims[0].1 {
cs.reverse();
}
cs
} else {
return refuse(
"an operand whose rank or element shape differs from the target's",
);
}
};
total =
cx.b.build_int_add(
total,
i64t.const_int(chunks.len() as u64, false),
"cat_total",
)
.unwrap();
parts.push(Part::Fixed(chunks));
} else if inner == 1 {
let v = element_value(cx, o, &dst_shape.elem, owns)?;
total =
cx.b.build_int_add(total, i64t.const_int(1, false), "cat_total")
.unwrap();
parts.push(Part::Fixed(vec![vec![v]]));
} else {
return refuse("a non-array operand and a multi-dimensional target");
}
}
let bad =
cx.b.build_int_compare(
IntPredicate::NE,
total,
i64t.const_int(count, false),
"cat_bad",
)
.unwrap();
let func =
cx.b.get_insert_block()
.and_then(|b| b.get_parent())
.expect("in a function");
let err_bb = cx.ctx.append_basic_block(func, "cat_size_err");
let ok_bb = cx.ctx.append_basic_block(func, "cat_size_ok");
let done_bb = cx.ctx.append_basic_block(func, "cat_size_done");
cx.b.build_conditional_branch(bad, err_bb, ok_bb).unwrap();
cx.b.position_at_end(err_bb);
crate::stmt_shared::error_if(
cx,
cx.ctx.bool_type().const_int(1, false),
e.source_range().start,
&format!(
"an unpacked array concatenation assigned to a fixed-size array of {count} elements has a different element count"
),
)?;
if owns {
for part in &parts {
if let Part::Fixed(chunks) = part {
for v in chunks.iter().flatten() {
crate::collections::release_handles(
cx,
&dst_shape.elem,
crate::expr::LoweredValue::Packed(*v),
);
}
}
}
}
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(ok_bb);
let descending = dst_shape.dims[0].0 > dst_shape.dims[0].1;
let row_of = |p: IntValue<'ctx>, j: u64| {
let outer = if descending {
cx.b.build_int_sub(i64t.const_int(count - 1, false), p, "cat_row")
.unwrap()
} else {
p
};
if inner == 1 {
outer
} else {
let base =
cx.b.build_int_mul(outer, i64t.const_int(inner, false), "cat_row0")
.unwrap();
cx.b.build_int_add(base, i64t.const_int(j, false), "cat_rowj")
.unwrap()
}
};
let put = |p: IntValue<'ctx>, j: u64, v: PackedValue<'ctx>| -> Result<(), LoweringError> {
let row = row_of(p, j);
if releases {
release_row(cx, dst, row, &dst_shape.elem)?;
}
store_row(cx, dst, row, v)
};
let mut off = i64t.const_zero();
for part in &parts {
match part {
Part::Fixed(chunks) => {
for (i, chunk) in chunks.iter().enumerate() {
let p =
cx.b.build_int_add(off, i64t.const_int(i as u64, false), "cat_at")
.unwrap();
for (j, v) in chunk.iter().enumerate() {
put(p, j as u64, *v)?;
}
}
off =
cx.b.build_int_add(off, i64t.const_int(chunks.len() as u64, false), "cat_off")
.unwrap();
}
Part::Coll(c, h, n) => {
let base = off;
row_loop(cx, count, i64t.const_zero(), |j, acc| {
let live =
cx.b.build_int_compare(IntPredicate::ULT, j, *n, "cat_live")
.unwrap();
let func =
cx.b.get_insert_block()
.and_then(|b| b.get_parent())
.expect("in a function");
let st = cx.ctx.append_basic_block(func, "cat_st");
let nx = cx.ctx.append_basic_block(func, "cat_nx");
cx.b.build_conditional_branch(live, st, nx).unwrap();
cx.b.position_at_end(st);
let v = crate::collections::read_pos(cx, c, *h, j);
let v = crate::real::to_storage(
cx,
v,
c.width,
"a queue or dynamic array element wider than 128 bits",
)?;
let p = cx.b.build_int_add(base, j, "cat_at").unwrap();
for r in 0..inner {
let part = if inner == 1 {
crate::select::fit_width(&op, v, width)
} else {
crate::stream::bits(cx, v, (r as u32) * width, width)
};
let part = crate::collections::retain_stored(cx, &dst_shape.elem, part);
put(p, r, part)?;
}
cx.b.build_unconditional_branch(nx).unwrap();
cx.b.position_at_end(nx);
Ok(acc)
})?;
off = cx.b.build_int_add(off, *n, "cat_off").unwrap();
}
}
}
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
Ok(())
}
pub(crate) fn copy_array<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
dst: ArrayRef<'ctx>,
dst_shape: &ArrayShape<'d>,
src: ArrayRef<'ctx>,
src_shape: &ArrayShape<'d>,
) -> Result<(), LoweringError> {
let releases = !crate::collections::handle_offsets(&dst_shape.elem).is_empty();
emit_rows(
cx,
Some(&dst),
dst_shape,
src_shape,
ArraySource::Stored(src),
true,
|k, v| {
if releases {
release_row(cx, &dst, k, &dst_shape.elem)?;
}
store_row(cx, &dst, k, v)
},
)
}
pub(crate) fn nba_array<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
dst: ArrayRef<'ctx>,
dst_shape: &ArrayShape<'d>,
rhs: sv_lang::Expression<'d>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
let Backing::Memory(info) = dst.backing else {
return Err(LoweringError::unsupported_at(
"a nonblocking assignment to a local array",
&cx.fn_sym,
));
};
let (src_shape, src) = array_operand(cx, rhs)?;
let share = crate::func::user_callee(&peel_array_conversions(rhs)).is_none();
nba_array_rows(
cx, handle, dst, info, dst_shape, &src_shape, src, share, delay,
)
}
pub(crate) fn nba_stage_ref<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
row: &ArrayRef<'ctx>,
value: PackedValue<'ctx>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
let Backing::Memory(info) = row.backing else {
return Err(LoweringError::unsupported_at(
"a nonblocking assignment to a local array",
&cx.fn_sym,
));
};
guarded(cx, row.valid, || {
crate::select::stage_nba_row(cx, handle, info, row.base, value, delay)
})
}
pub(crate) fn nba_array_from_local<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
dst: ArrayRef<'ctx>,
dst_shape: &ArrayShape<'d>,
src: PointerValue<'ctx>,
src_shape: &ArrayShape<'d>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
let Backing::Memory(info) = dst.backing else {
return Err(LoweringError::unsupported_at(
"a nonblocking assignment to a local array",
&cx.fn_sym,
));
};
let src = ArraySource::Stored(ArrayRef::whole(cx, Backing::Local(src)));
nba_array_rows(
cx, handle, dst, info, dst_shape, src_shape, src, true, delay,
)
}
#[allow(clippy::too_many_arguments)]
fn nba_array_rows<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
dst: ArrayRef<'ctx>,
info: MemorySignalInfo,
dst_shape: &ArrayShape<'d>,
src_shape: &ArrayShape<'d>,
src: ArraySource<'ctx, 'd>,
share: bool,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
emit_rows(cx, None, dst_shape, src_shape, src, share, |k, v| {
guarded(cx, dst.valid, || {
let row = cx.b.build_int_add(dst.base, k, "arr_row").unwrap();
crate::select::stage_nba_row(cx, handle, info, row, v, delay)
})
})
}
pub(crate) fn compare_arrays<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
bop: sv_lang::BinaryOp,
) -> Result<PackedValue<'ctx>, LoweringError> {
use sv_lang::BinaryOp as B;
let missing = || LoweringError::unsupported_at("array comparison operand", &cx.fn_sym);
let (ls, lsrc) = array_operand(cx, e.left().ok_or_else(missing)?)?;
let (rs, rsrc) = array_operand(cx, e.right().ok_or_else(missing)?)?;
check_compatible(cx, &ls, &rs)?;
let case = match bop {
B::Equality | B::Inequality => false,
B::CaseEquality | B::CaseInequality => true,
_ => {
return Err(LoweringError::unsupported_at(
&format!("operator {bop:?} on unpacked arrays"),
&cx.fn_sym,
));
}
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let floating = ls.elem.is_floating();
let bit = |llvm: IntValue<'ctx>| PackedValue {
llvm,
width: 1,
repr: cx.repr,
signed: false,
};
let cmp = |a: PackedValue<'ctx>, b: PackedValue<'ctx>| -> PackedValue<'ctx> {
if floating {
let (fa, fb) = (
crate::real::bits_to_real(cx, a).llvm,
crate::real::bits_to_real(cx, b).llvm,
);
let eq =
cx.b.build_float_compare(inkwell::FloatPredicate::OEQ, fa, fb, "arr_feq")
.unwrap();
op.pack(eq, cx.ctx.bool_type().const_zero(), 1, cx.repr, false)
} else if case {
op.caseeq4(a, b)
} else {
op.eq4(a, b)
}
};
let one = op
.pack(
cx.ctx.bool_type().const_all_ones(),
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)
.llvm;
let combine = |acc: IntValue<'ctx>, c: PackedValue<'ctx>| op.and4(bit(acc), c).llvm;
let result = match (lsrc, rsrc) {
(ArraySource::Stored(lr), ArraySource::Stored(rr)) if same_row_order(&ls, &rs) => {
row_loop(cx, ls.rows(), one, |k, acc| {
let a = load_row(cx, &lr, k, &ls.elem)?;
let b = load_row(cx, &rr, k, &rs.elem)?;
Ok(combine(acc, cmp(a, b)))
})?
}
(lsrc, rsrc) => {
let row_values = |src: ArraySource<'ctx, 'd>,
shape: &ArrayShape<'d>|
-> Result<Vec<PackedValue<'ctx>>, LoweringError> {
match src {
ArraySource::Rows(exprs) => exprs
.iter()
.map(|&x| row_value(cx, x, &shape.elem, false))
.collect(),
ArraySource::Values(vs) => Ok(vs),
ArraySource::Stored(r) => (0..shape.rows())
.map(|k| load_row(cx, &r, i64t.const_int(k, false), &shape.elem))
.collect(),
}
};
let lv = row_values(lsrc, &ls)?;
let rv = row_values(rsrc, &rs)?;
let mut acc = one;
for (r, a) in lv.into_iter().enumerate() {
let b = rv[corresponding_row(&ls, &rs, r as u64) as usize];
acc = combine(acc, cmp(a, b));
}
acc
}
};
let result = bit(result);
Ok(match bop {
B::Inequality | B::CaseInequality => op.not4(result),
_ => result,
})
}
pub(crate) fn assign_coll_slice<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
rhs: sv_lang::Expression<'d>,
) -> Option<Result<(), LoweringError>> {
if lhs.kind() != ExpressionKind::RangeSelect {
return None;
}
let base = lhs.select_value()?;
let c = crate::collections::coll_of_expr(&base)?;
Some((|| {
let unsupported = |what: &str| LoweringError::unsupported_at(what, &cx.fn_sym);
if c.assoc {
return Err(unsupported(
"a slice of an associative array as an assignment target",
));
}
if c.elem.is_floating()
|| c.obj
|| c.leaky
|| !crate::collections::handle_offsets(&c.elem).is_empty()
{
return Err(unsupported(
"a queue or dynamic-array slice assignment target with real or handle elements",
));
}
let from_coll = crate::collections::coll_of_expr(&rhs);
let fixed = from_coll.is_none() && rhs.expr_type().is_some_and(|t| t.is_unpacked_array());
if from_coll.is_none() && !fixed {
return Err(unsupported(
"a slice assignment whose right-hand side is not an array value",
));
}
let rc = from_coll.unwrap_or(c);
let i64t = cx.ctx.i64_type();
let one = i64t.const_int(1, false);
let h = crate::collections::handle_of(cx, base)?;
let (lo, hi) = crate::collections::slice_bounds(cx, &c, h, lhs)?;
let new = if fixed {
let mut h = i64t.const_zero();
for v in array_operand_elements(cx, rhs, &c.elem)? {
let val =
crate::collections::elem_buf(cx, &c, crate::expr::LoweredValue::Packed(v))?;
h = crate::collections::call(
cx,
&c,
sv_sim_runtime::collections_rt::CollOp::Push,
h,
crate::collections::Args {
val: Some(val),
..Default::default()
},
);
}
h
} else {
crate::collections::owned_value(cx, rhs)?
};
let n = crate::collections::size_of_handle(cx, &rc, new);
let n =
cx.b.build_int_z_extend_or_bit_cast(n, i64t, "slice_n")
.unwrap();
let size = crate::collections::size_of_handle(cx, &c, h);
let size =
cx.b.build_int_z_extend_or_bit_cast(size, i64t, "slice_dst_size")
.unwrap();
let span =
cx.b.build_int_add(
cx.b.build_int_sub(hi, lo, "slice_span").unwrap(),
one,
"slice_cnt",
)
.unwrap();
let empty =
cx.b.build_int_compare(IntPredicate::SLT, span, i64t.const_zero(), "slice_empty")
.unwrap();
let count =
cx.b.build_select(empty, i64t.const_zero(), span, "slice_count")
.unwrap()
.into_int_value();
let mismatch =
cx.b.build_int_compare(IntPredicate::NE, count, n, "slice_mismatch")
.unwrap();
let neg =
cx.b.build_int_compare(IntPredicate::SLT, lo, i64t.const_zero(), "slice_neg")
.unwrap();
let past =
cx.b.build_int_compare(IntPredicate::SGE, hi, size, "slice_past")
.unwrap();
let outside = cx.b.build_or(neg, past, "slice_outside").unwrap();
let some =
cx.b.build_int_compare(IntPredicate::UGT, n, i64t.const_zero(), "slice_some")
.unwrap();
let outside = cx.b.build_and(outside, some, "slice_outside_n").unwrap();
let bad = cx.b.build_or(mismatch, outside, "slice_dst_bad").unwrap();
let func =
cx.b.get_insert_block()
.and_then(|b| b.get_parent())
.expect("in a function");
let err_bb = cx.ctx.append_basic_block(func, "slice_err");
let ok_bb = cx.ctx.append_basic_block(func, "slice_ok");
let done_bb = cx.ctx.append_basic_block(func, "slice_done");
cx.b.build_conditional_branch(bad, err_bb, ok_bb).unwrap();
cx.b.position_at_end(err_bb);
crate::stmt_shared::error_if(
cx,
cx.ctx.bool_type().const_int(1, false),
lhs.source_range().start,
"a queue or dynamic-array slice assigned to must lie inside the array and have the \
size of the value assigned; nothing is assigned",
)?;
crate::collections::call(
cx,
&rc,
sv_sim_runtime::collections_rt::CollOp::Release,
new,
crate::collections::Args::default(),
);
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(ok_bb);
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let pre = cx.b.get_insert_block().expect("positioned");
let head = cx.ctx.append_basic_block(func, "slice_loop");
let body_bb = cx.ctx.append_basic_block(func, "slice_loop_row");
let end_bb = cx.ctx.append_basic_block(func, "slice_loop_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let k = cx.b.build_phi(i64t, "k").unwrap();
k.add_incoming(&[(&i64t.const_zero(), pre)]);
let hp = cx.b.build_phi(i64t, "h").unwrap();
hp.add_incoming(&[(&h, pre)]);
let kv = k.as_basic_value().into_int_value();
let hv = hp.as_basic_value().into_int_value();
let more =
cx.b.build_int_compare(IntPredicate::ULT, kv, n, "more")
.unwrap();
cx.b.build_conditional_branch(more, body_bb, end_bb)
.unwrap();
cx.b.position_at_end(body_bb);
let v = crate::collections::read_at_coll(cx, &rc, new, kv)
.into_packed("a slice element", &cx.fn_sym)?;
let v = crate::select::fit_width(&op, v, c.width);
let val = crate::collections::elem_buf(cx, &c, crate::expr::LoweredValue::Packed(v))?;
let pos = cx.b.build_int_add(lo, kv, "slice_pos").unwrap();
let nh = crate::collections::call(
cx,
&c,
sv_sim_runtime::collections_rt::CollOp::Set,
hv,
crate::collections::Args {
index: Some(pos),
index2: Some(i64t.const_int(u64::from(c.queue), false)),
val: Some(val),
..Default::default()
},
);
let next = cx.b.build_int_add(kv, one, "k_next").unwrap();
let latch = cx.b.get_insert_block().expect("positioned");
k.add_incoming(&[(&next, latch)]);
hp.add_incoming(&[(&nh, latch)]);
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(end_bb);
crate::collections::set_handle(cx, base, hv)?;
crate::collections::call(
cx,
&rc,
sv_sim_runtime::collections_rt::CollOp::Release,
new,
crate::collections::Args::default(),
);
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
Ok(())
})())
}