use inkwell::IntPredicate;
use inkwell::values::IntValue;
use sv_lang::Type;
use crate::arrays::{ArrayRef, array_target};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, lower_expr, mem_row_index_i64};
use crate::ops::OpBuilder;
use crate::stream::{bits, restream, unstream};
use crate::types::TypeWidth;
use crate::value::PackedValue;
fn leaf_offsets(shape: &crate::arrays::ArrayShape<'_>) -> Vec<u64> {
let inner = shape.stride(0);
(0..inner)
.map(|flat| {
let mut rest = flat;
let mut row = 0u64;
for k in (1..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);
}
row
})
.collect()
}
fn advance<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, r: &ArrayRef<'ctx>, off: u64) -> ArrayRef<'ctx> {
if off == 0 {
return *r;
}
ArrayRef {
base: cx
.b
.build_int_add(r.base, cx.ctx.i64_type().const_int(off, false), "with_leaf")
.unwrap(),
..*r
}
}
struct WithElems<'ctx, 'd> {
rows: Vec<ArrayRef<'ctx>>,
leaves: Vec<u64>,
stream_ascending: bool,
elem: Type<'d>,
outside: IntValue<'ctx>,
}
fn konst<'d>(x: Option<sv_lang::Expression<'d>>) -> Option<i64> {
x.and_then(|x| x.constant_value()).and_then(|c| c.as_i64())
}
pub(crate) fn element_count<'d>(
cx: &LowerCtx<'_, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
) -> Result<Option<u64>, LoweringError> {
let Some(_) = operand
.expr_type()
.as_ref()
.and_then(crate::arrays::array_shape)
else {
return Ok(None);
};
let unsup = |what: &str| LoweringError::unsupported_at(what, &cx.fn_sym);
let (Some(l), Some(r)) = (w.range_left(), w.range_right()) else {
return Ok(Some(1));
};
Ok(Some(match w.range_selection_kind() {
Some(0) => match (konst(Some(l)), konst(Some(r))) {
(Some(l), Some(r)) => (l - r).unsigned_abs() + 1,
_ => {
return Err(unsup(
"a stream `with [a:b]` on a fixed-size array with non-constant bounds",
));
}
},
Some(1 | 2) => match konst(Some(r)) {
Some(n) if n >= 1 => n as u64,
_ => {
return Err(unsup(
"a stream `with [a +: n]` on a fixed-size array with a non-constant width",
));
}
},
_ => return Err(unsup("a stream `with` range of an unknown kind")),
}))
}
fn elements<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
) -> Result<WithElems<'ctx, 'd>, LoweringError> {
let count = element_count(cx, operand, w)?.ok_or_else(|| {
LoweringError::unsupported_at(
"a stream `with` range on an operand that is not a one-dimensional fixed-size array",
&cx.fn_sym,
)
})?;
let (base, shape) = array_target(cx, operand)?.ok_or_else(|| {
LoweringError::unsupported_at(
"a stream `with` range on an array that is not stored as rows",
&cx.fn_sym,
)
})?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let c64 = |v: i64| i64t.const_int(v as u64, true);
let (left, right) = shape.dims[0];
let low = left.min(right);
let size = shape.size(0);
let index_of = |x: sv_lang::Expression<'d>| -> Result<_, LoweringError> {
let idx = lower_expr(cx, x)?.into_packed("a stream `with` 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(),
"with_known",
)
.unwrap();
Ok((mem_row_index_i64(&op, cx, idx), known))
};
let all = cx.ctx.bool_type().const_all_ones();
let kind = w.range_selection_kind();
let (lowest, known) = match (w.range_left(), w.range_right()) {
(Some(l), Some(r)) => match kind {
Some(0) => (
c64(konst(Some(l)).unwrap_or(0).min(konst(Some(r)).unwrap_or(0))),
all,
),
Some(1) => index_of(l)?,
_ => {
let (i, k) = index_of(l)?;
(
cx.b.build_int_sub(i, c64(count as i64 - 1), "with_low")
.unwrap(),
k,
)
}
},
_ => index_of(w.selector().ok_or_else(|| {
LoweringError::unsupported_at(
"a stream `with` that is not an index or range",
&cx.fn_sym,
)
})?)?,
};
let stream_ascending = match (kind, w.range_left(), w.range_right()) {
(Some(0), Some(l), Some(r)) => konst(Some(l)) <= konst(Some(r)),
_ => left <= right,
};
let norm = cx.b.build_int_sub(lowest, c64(low), "with_norm").unwrap();
let mut rows = Vec::with_capacity(count as usize);
let mut outside = cx.ctx.bool_type().const_zero();
for p in 0..count {
let row =
cx.b.build_int_add(norm, i64t.const_int(p, false), "with_row")
.unwrap();
let in_range =
cx.b.build_int_compare(
IntPredicate::ULT,
row,
i64t.const_int(size, false),
"with_in_range",
)
.unwrap();
let good = cx.b.build_and(in_range, known, "with_ok").unwrap();
let bad = cx.b.build_not(good, "with_bad").unwrap();
outside = cx.b.build_or(outside, bad, "with_outside").unwrap();
let valid = cx.b.build_and(base.valid, good, "with_valid").unwrap();
let row =
cx.b.build_int_mul(row, i64t.const_int(shape.stride(0), false), "with_srow")
.unwrap();
let at = cx.b.build_int_add(base.base, row, "with_at").unwrap();
let at =
cx.b.build_select(valid, at, i64t.const_zero(), "with_safe")
.unwrap()
.into_int_value();
rows.push(ArrayRef {
backing: base.backing,
base: at,
valid,
});
}
Ok(WithElems {
rows,
leaves: leaf_offsets(&shape),
stream_ascending,
elem: shape.elem,
outside,
})
}
fn report_outside<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
outside: IntValue<'ctx>,
operand: sv_lang::Expression<'_>,
) -> Result<(), LoweringError> {
crate::stmt_shared::error_if(
cx,
outside,
operand.source_range().start,
"stream `with` range is outside the bounds of the array",
)
}
pub(crate) fn pack<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let el = elements(cx, operand, w)?;
report_outside(cx, el.outside, operand)?;
let n = el.rows.len();
let mut parts = Vec::with_capacity(n * el.leaves.len());
for i in 0..n {
let p = if el.stream_ascending { i } else { n - 1 - i };
for &off in &el.leaves {
let v = crate::arrays::load_row(
cx,
&el.rows[p],
cx.ctx.i64_type().const_int(off, false),
&el.elem,
)?;
parts.push(restream(cx, v, &el.elem)?);
}
}
Ok(OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.concat4(&parts))
}
pub(crate) fn width<'d>(
cx: &LowerCtx<'_, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
) -> Result<Option<u32>, LoweringError> {
let Some(n) = element_count(cx, operand, w)? else {
return Ok(None);
};
let elem = operand
.expr_type()
.and_then(|t| crate::arrays::array_shape(&t))
.map(|s| s.elem);
let inner = operand
.expr_type()
.and_then(|t| crate::arrays::array_shape(&t))
.map_or(1, |s| s.stride(0) as u32);
Ok(elem.map(|e| n as u32 * inner * e.value_width()))
}
pub(crate) fn unpack<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
v: PackedValue<'ctx>,
) -> Result<(), LoweringError> {
unpack_with(cx, operand, w, v, &mut |row, value| {
crate::arrays::store_row(cx, row, cx.ctx.i64_type().const_zero(), value)
})
}
pub(crate) type RowSink<'ctx, 'a> =
dyn FnMut(&ArrayRef<'ctx>, PackedValue<'ctx>) -> Result<(), LoweringError> + 'a;
pub(crate) fn unpack_with<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
v: PackedValue<'ctx>,
put: &mut RowSink<'ctx, '_>,
) -> Result<(), LoweringError> {
let el = elements(cx, operand, w)?;
report_outside(cx, el.outside, operand)?;
let n = el.rows.len() as u32;
let inner = el.leaves.len() as u32;
let total = n * inner;
let ew = el.elem.value_width();
for q in 0..total {
let piece = bits(cx, v, (total - 1 - q) * ew, ew);
let stored = unstream(cx, piece, &el.elem)?;
let (i, j) = (q / inner, q % inner);
let pos = if el.stream_ascending { i } else { n - 1 - i };
let row = PackedValue {
signed: false,
..stored
};
put(
&advance(cx, &el.rows[pos as usize], el.leaves[j as usize]),
row,
)?;
}
Ok(())
}
pub(crate) fn push_collection_range<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let unsup = |what: &str| LoweringError::unsupported_at(what, &cx.fn_sym);
let c = crate::collections::coll_of_expr(&operand)
.filter(|c| !c.assoc && c.elem.is_integral())
.ok_or_else(|| {
unsup("a stream `with [...]` operand that is not an integral queue or dynamic array")
})?;
let i64t = cx.ctx.i64_type();
let int = |e: sv_lang::Expression<'d>| -> Result<IntValue<'ctx>, LoweringError> {
let pv = lower_expr(cx, e)?.into_packed("a stream `with` bound", &cx.fn_sym)?;
Ok(mem_row_index_i64(
&OpBuilder {
ctx: cx.ctx,
b: &cx.b,
},
cx,
pv,
))
};
let one = i64t.const_int(1, false);
let (lo, hi) = match (w.range_left(), w.range_right()) {
(Some(l), Some(r)) => match w.range_selection_kind() {
Some(0) => {
let (a, b) = (int(l)?, int(r)?);
let a_lt_b =
cx.b.build_int_compare(IntPredicate::SLT, a, b, "with_lt")
.unwrap();
let lo =
cx.b.build_select(a_lt_b, a, b, "with_lo")
.unwrap()
.into_int_value();
let hi =
cx.b.build_select(a_lt_b, b, a, "with_hi")
.unwrap()
.into_int_value();
(lo, hi)
}
Some(1) => {
let (a, n) = (int(l)?, int(r)?);
let sum = cx.b.build_int_add(a, n, "with_end").unwrap();
(a, cx.b.build_int_sub(sum, one, "with_hi").unwrap())
}
Some(2) => {
let (a, n) = (int(l)?, int(r)?);
let diff = cx.b.build_int_sub(a, n, "with_start").unwrap();
(cx.b.build_int_add(diff, one, "with_lo").unwrap(), a)
}
_ => return Err(unsup("a stream `with` range of an unknown kind")),
},
_ => {
let i = int(w
.selector()
.ok_or_else(|| unsup("a stream `with` that is not an index or range"))?)?;
(i, i)
}
};
let h = crate::collections::handle_of(cx, operand)?;
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let slot = crate::expr::entry_alloca(cx, i64t, "with_i");
cx.b.build_store(slot, lo).unwrap();
let head = cx.ctx.append_basic_block(f, "with_head");
let body = cx.ctx.append_basic_block(f, "with_body");
let done = cx.ctx.append_basic_block(f, "with_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let i = cx.b.build_load(i64t, slot, "i").unwrap().into_int_value();
let more =
cx.b.build_int_compare(IntPredicate::SLE, i, hi, "with_more")
.unwrap();
cx.b.build_conditional_branch(more, body, done).unwrap();
cx.b.position_at_end(body);
let crate::expr::LoweredValue::Packed(el) = crate::collections::read_at_coll(cx, &c, h, i)
else {
return Err(unsup("a streamed collection element that is not packed"));
};
crate::stream::push_bits(cx, el);
let next = cx.b.build_int_add(i, one, "with_next").unwrap();
cx.b.build_store(slot, next).unwrap();
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(done);
Ok(())
}
pub(crate) fn runtime_count<'d>(
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
) -> bool {
let fixed = operand
.expr_type()
.as_ref()
.and_then(crate::arrays::array_shape)
.is_some_and(|s| !s.dims.is_empty());
if !fixed {
return false;
}
let (Some(l), Some(r)) = (w.range_left(), w.range_right()) else {
return false;
};
match w.range_selection_kind() {
Some(0) => konst(Some(l)).is_none() || konst(Some(r)).is_none(),
Some(1 | 2) => !matches!(konst(Some(r)), Some(n) if n >= 1),
_ => false,
}
}
struct RuntimeRange<'ctx> {
lo: IntValue<'ctx>,
count: IntValue<'ctx>,
ascending: IntValue<'ctx>,
known: IntValue<'ctx>,
}
fn runtime_range<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
w: sv_lang::Expression<'d>,
array_ascending: bool,
) -> Result<RuntimeRange<'ctx>, LoweringError> {
use crate::expr::{RangeSelectionKindClass as K, classify_range_selection_kind};
let unsup = |what: &str| LoweringError::unsupported_at(what, &cx.fn_sym);
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let boolt = cx.ctx.bool_type();
let (Some(l), Some(r), Some(kind)) =
(w.range_left(), w.range_right(), w.range_selection_kind())
else {
return Err(unsup("a stream `with` that is not a range"));
};
let int = |e: sv_lang::Expression<'d>| -> Result<_, LoweringError> {
let pv = lower_expr(cx, e)?.into_packed("a stream `with` bound", &cx.fn_sym)?;
let unk = op.lane_unk(pv);
let known =
cx.b.build_int_compare(
IntPredicate::EQ,
unk,
unk.get_type().const_zero(),
"with_known",
)
.unwrap();
Ok((mem_row_index_i64(&op, cx, pv), known))
};
let one = i64t.const_int(1, false);
let zero = i64t.const_zero();
let ((lv, lk), (rv, rk)) = (int(l)?, int(r)?);
let known = cx.b.build_and(lk, rk, "with_known").unwrap();
let array_dir = if array_ascending {
boolt.const_all_ones()
} else {
boolt.const_zero()
};
let (lo, count, ascending) = match classify_range_selection_kind(kind).map_err(|m| unsup(&m))? {
K::IndexedUp => (lv, rv, array_dir),
K::IndexedDown => {
let lo = cx.b.build_int_sub(lv, rv, "with_lo").unwrap();
(
cx.b.build_int_add(lo, one, "with_lo1").unwrap(),
rv,
array_dir,
)
}
K::Simple => {
let gt =
cx.b.build_int_compare(IntPredicate::SGT, lv, rv, "with_gt")
.unwrap();
let lo =
cx.b.build_select(gt, rv, lv, "with_min")
.unwrap()
.into_int_value();
let hi =
cx.b.build_select(gt, lv, rv, "with_max")
.unwrap()
.into_int_value();
let d = cx.b.build_int_sub(hi, lo, "with_d").unwrap();
let asc = cx.b.build_not(gt, "with_asc").unwrap();
(lo, cx.b.build_int_add(d, one, "with_cnt").unwrap(), asc)
}
};
let neg =
cx.b.build_int_compare(IntPredicate::SLT, count, zero, "with_neg")
.unwrap();
let count =
cx.b.build_select(neg, zero, count, "with_c0")
.unwrap()
.into_int_value();
let count =
cx.b.build_select(known, count, zero, "with_ck")
.unwrap()
.into_int_value();
Ok(RuntimeRange {
lo,
count,
ascending,
known,
})
}
pub(crate) fn runtime_bits<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let shape = operand
.expr_type()
.as_ref()
.and_then(crate::arrays::array_shape)
.ok_or_else(|| {
LoweringError::unsupported_at(
"a stream `with` range on an operand that is not a fixed-size array",
&cx.fn_sym,
)
})?;
let rr = runtime_range(cx, w, shape.dims[0].0 <= shape.dims[0].1)?;
let ew = cx
.ctx
.i64_type()
.const_int(u64::from(shape.elem.value_width()), false);
let ew =
cx.b.build_int_mul(
ew,
ew.get_type().const_int(shape.stride(0), false),
"with_ew",
)
.unwrap();
Ok(cx.b.build_int_mul(rr.count, ew, "with_bits").unwrap())
}
pub(crate) fn for_each_selected<'ctx, 'd, F>(
cx: &LowerCtx<'ctx, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
mut body: F,
) -> Result<(), LoweringError>
where
F: FnMut(&ArrayRef<'ctx>, IntValue<'ctx>, &Type<'d>) -> Result<(), LoweringError>,
{
let (base, shape) = array_target(cx, operand)?.ok_or_else(|| {
LoweringError::unsupported_at(
"a stream `with` range on an array that is not stored as rows",
&cx.fn_sym,
)
})?;
let (left, right) = shape.dims[0];
let low = left.min(right);
let size = shape.size(0);
let leaves = leaf_offsets(&shape);
let inner = leaves.len() as u64;
let rr = runtime_range(cx, w, left <= right)?;
let i64t = cx.ctx.i64_type();
let boolt = cx.ctx.bool_type();
let c64 = |v: i64| i64t.const_int(v as u64, true);
let one = c64(1);
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let p_slot = crate::expr::entry_alloca(cx, i64t, "with_p");
let bad_slot = crate::expr::entry_alloca(cx, boolt, "with_bad");
cx.b.build_store(p_slot, i64t.const_zero()).unwrap();
let not_known = cx.b.build_not(rr.known, "with_unk").unwrap();
cx.b.build_store(bad_slot, not_known).unwrap();
let head = cx.ctx.append_basic_block(f, "with_head");
let loop_body = cx.ctx.append_basic_block(f, "with_body");
let done = cx.ctx.append_basic_block(f, "with_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let p = cx.b.build_load(i64t, p_slot, "p").unwrap().into_int_value();
let more =
cx.b.build_int_compare(IntPredicate::SLT, p, rr.count, "with_more")
.unwrap();
cx.b.build_conditional_branch(more, loop_body, done)
.unwrap();
cx.b.position_at_end(loop_body);
let fwd = cx.b.build_int_add(rr.lo, p, "with_fwd").unwrap();
let last = cx.b.build_int_add(rr.lo, rr.count, "with_end").unwrap();
let rev = cx.b.build_int_sub(last, p, "with_rev").unwrap();
let rev = cx.b.build_int_sub(rev, one, "with_rev1").unwrap();
let idx =
cx.b.build_select(rr.ascending, fwd, rev, "with_idx")
.unwrap()
.into_int_value();
let row = cx.b.build_int_sub(idx, c64(low), "with_row").unwrap();
let in_range =
cx.b.build_int_compare(
IntPredicate::ULT,
row,
i64t.const_int(size, false),
"with_in",
)
.unwrap();
let out = cx.b.build_not(in_range, "with_out").unwrap();
let was_bad =
cx.b.build_load(boolt, bad_slot, "bad")
.unwrap()
.into_int_value();
let now_bad = cx.b.build_or(was_bad, out, "with_bad").unwrap();
cx.b.build_store(bad_slot, now_bad).unwrap();
let valid = cx.b.build_and(base.valid, in_range, "with_valid").unwrap();
let srow =
cx.b.build_int_mul(row, c64(inner as i64), "with_srow")
.unwrap();
let at = cx.b.build_int_add(base.base, srow, "with_at").unwrap();
let at =
cx.b.build_select(valid, at, i64t.const_zero(), "with_safe")
.unwrap()
.into_int_value();
let r = ArrayRef {
backing: base.backing,
base: at,
valid,
};
for (j, &off) in leaves.iter().enumerate() {
let leaf_p = cx.b.build_int_mul(p, c64(inner as i64), "with_lp").unwrap();
let leaf_p =
cx.b.build_int_add(leaf_p, c64(j as i64), "with_lpj")
.unwrap();
body(&advance(cx, &r, off), leaf_p, &shape.elem)?;
}
let next = cx.b.build_int_add(p, one, "with_next").unwrap();
cx.b.build_store(p_slot, next).unwrap();
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(done);
let bad =
cx.b.build_load(boolt, bad_slot, "bad")
.unwrap()
.into_int_value();
report_outside(cx, bad, operand)
}
pub(crate) fn push_fixed_range<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
operand: sv_lang::Expression<'d>,
w: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let zero = cx.ctx.i64_type().const_zero();
for_each_selected(cx, operand, w, |row, _p, elem| {
let v = crate::arrays::load_row(cx, row, zero, elem)?;
let s = restream(cx, v, elem)?;
crate::stream::push_bits(cx, s);
Ok(())
})
}