use std::hash::{Hash, Hasher};
use std::sync::OnceLock;
use inkwell::AddressSpace;
use inkwell::IntPredicate;
use inkwell::basic_block::BasicBlock;
use inkwell::module::Linkage;
use inkwell::values::{FunctionValue, InstructionOpcode, IntValue, PointerValue};
use sv_lang::{BinaryOp, Bit, UnaryOp, kinds::ExpressionKind};
use vj_runtime::wide_rt::{self, WideOp};
use vj_sim::symid::SymbolUid;
use crate::error::LoweringError;
use crate::expr::{
LowerCtx, LoweredValue, lower_expr_h, lower_expr_regular, require_const_i64,
write_through_commit, write_through_store,
};
use crate::ops::OpBuilder;
use crate::select::{SelectGeom, checked_window_width, field_window, geom_of};
use crate::types::TypeWidth;
use crate::value::{PackedValue, ValueRepr, WIDE_THRESHOLD};
pub const THRESHOLD_ENV: &str = "VJ_HUGE_FROM_BITS";
pub const DEFAULT_THRESHOLD: u32 = 2048;
pub fn threshold() -> u32 {
static T: OnceLock<u32> = OnceLock::new();
*T.get_or_init(|| match std::env::var(THRESHOLD_ENV) {
Err(_) => DEFAULT_THRESHOLD,
Ok(s) => match s.trim().parse::<u32>() {
Ok(n) if (64..=WIDE_THRESHOLD).contains(&n) => n,
_ => panic!("{THRESHOLD_ENV}={s:?}: expected an integer in 64..={WIDE_THRESHOLD}"),
},
})
}
pub fn is_huge_width(width: u32) -> bool {
width > threshold()
}
pub fn is_huge_type(ty: &sv_lang::Type<'_>) -> bool {
ty.is_integral() && !ty.is_floating() && is_huge_width(ty.value_width())
}
pub fn pk_words(width: u32) -> u32 {
(2 * u64::from(width)).div_ceil(64) as u32
}
#[derive(Clone, Copy)]
pub struct HugeValue<'ctx> {
pub ptr: PointerValue<'ctx>,
pub width: u32,
pub signed: bool,
}
impl HugeValue<'_> {
pub fn info(self) -> u64 {
wide_rt::info(self.width, self.signed)
}
}
fn loud<T>(cx: &LowerCtx<'_, '_, '_>, what: &str) -> Result<T, LoweringError> {
Err(LoweringError::unsupported_at(what, &cx.fn_sym))
}
const FREE: &str = "hugetmp_free_";
const BUSY: &str = "hugetmp_busy_";
fn entry_block<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> BasicBlock<'ctx> {
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.and_then(|f| f.get_first_basic_block())
.expect("lowering happens inside a function with an entry block")
}
pub(crate) fn acquire<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, words: u32) -> PointerValue<'ctx> {
let free = format!("{FREE}{words}_");
let busy = format!("{BUSY}{words}_");
let mut cur = entry_block(cx).get_first_instruction();
while let Some(inst) = cur {
if inst.get_opcode() != InstructionOpcode::Alloca {
break;
}
if inst
.get_name()
.is_some_and(|n| n.to_bytes().starts_with(free.as_bytes()))
{
let p = PointerValue::try_from(inst).expect("an alloca is a pointer");
p.set_name(&busy);
return p;
}
cur = inst.get_next_instruction();
}
let ty = cx.ctx.i64_type().array_type(words);
crate::expr::entry_alloca(cx, ty, &busy)
}
fn temp<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, width: u32) -> PointerValue<'ctx> {
acquire(cx, pk_words(width))
}
pub(crate) fn release(ptr: PointerValue<'_>) {
let Some(inst) = ptr.as_instruction() else {
return;
};
if inst.get_opcode() != InstructionOpcode::Alloca {
return;
}
let Some(name) = inst.get_name().and_then(|n| n.to_str().ok()) else {
return;
};
let Some(rest) = name.strip_prefix(BUSY) else {
return;
};
let digits: String = rest.chars().take_while(char::is_ascii_digit).collect();
ptr.set_name(&format!("{FREE}{digits}_"));
}
#[cfg(test)]
fn pool_words(name: &str) -> Option<u32> {
let rest = name
.strip_prefix(BUSY)
.or_else(|| name.strip_prefix(FREE))?;
rest.chars()
.take_while(char::is_ascii_digit)
.collect::<String>()
.parse()
.ok()
}
pub const SV_RT_HUGE_SYMBOL: &str = "sv_rt_huge";
fn rt_huge_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
cx.module
.get_function(SV_RT_HUGE_SYMBOL)
.unwrap_or_else(|| {
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ty = ptr.fn_type(
&[
i32t.into(),
ptr.into(),
i32t.into(),
i32t.into(),
i64t.into(),
i64t.into(),
ptr.into(),
i64t.into(),
],
false,
);
cx.module
.add_function(SV_RT_HUGE_SYMBOL, ty, Some(Linkage::External))
})
}
#[allow(clippy::too_many_arguments)]
fn rt_huge<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: u32,
handle: PointerValue<'ctx>,
slot: u32,
width: u32,
a: IntValue<'ctx>,
b: IntValue<'ctx>,
p: PointerValue<'ctx>,
delay: IntValue<'ctx>,
) -> PointerValue<'ctx> {
let i32t = cx.ctx.i32_type();
cx.b.build_call(
rt_huge_fn(cx),
&[
i32t.const_int(u64::from(op), false).into(),
handle.into(),
i32t.const_int(u64::from(slot), false).into(),
i32t.const_int(u64::from(width), false).into(),
a.into(),
b.into(),
p.into(),
delay.into(),
],
"rt_huge_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_pointer_value()
}
pub(crate) fn rt_prime<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
slot: u32,
width: u32,
rebuild: bool,
) -> PointerValue<'ctx> {
let i64t = cx.ctx.i64_type();
rt_huge(
cx,
vj_sim::sched::HUGE_OP_PRIME,
handle,
slot,
width,
i64t.const_int(u64::from(rebuild), false),
i64t.const_zero(),
null_ptr(cx),
i64t.const_zero(),
)
}
pub(crate) fn rt_mark<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
slot: u32,
width: u32,
start: IntValue<'ctx>,
len: IntValue<'ctx>,
) {
rt_huge(
cx,
vj_sim::sched::HUGE_OP_MARK,
handle,
slot,
width,
start,
len,
null_ptr(cx),
cx.ctx.i64_type().const_zero(),
);
}
pub(crate) fn rt_commit<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
slot: u32,
width: u32,
) {
let i64t = cx.ctx.i64_type();
rt_huge(
cx,
vj_sim::sched::HUGE_OP_COMMIT,
handle,
slot,
width,
i64t.const_zero(),
i64t.const_zero(),
null_ptr(cx),
i64t.const_zero(),
);
}
#[allow(clippy::too_many_arguments)]
fn rt_nba_window<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
slot: u32,
width: u32,
start: IntValue<'ctx>,
len: u32,
image: PointerValue<'ctx>,
delay: IntValue<'ctx>,
) {
rt_huge(
cx,
vj_sim::sched::HUGE_OP_NBA,
handle,
slot,
width,
start,
cx.ctx.i64_type().const_int(u64::from(len), false),
image,
delay,
);
}
fn null_ptr<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> PointerValue<'ctx> {
cx.ctx.ptr_type(AddressSpace::default()).const_null()
}
pub(crate) fn shadow_alloca<'ctx>(
ctx: &'ctx inkwell::context::Context,
b: &inkwell::builder::Builder<'ctx>,
width: u32,
name: &str,
) -> PointerValue<'ctx> {
let ty = ctx.i64_type().array_type(pk_words(width));
crate::types::entry_alloca_in(b, ty, name)
}
fn wide_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
let name = wide_rt::SV_RT_WIDE_SYMBOL;
cx.module.get_function(name).unwrap_or_else(|| {
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ty = i64t.fn_type(
&[
i32t.into(),
ptr.into(),
ptr.into(),
ptr.into(),
i64t.into(),
i64t.into(),
i64t.into(),
i64t.into(),
],
false,
);
cx.module.add_function(name, ty, Some(Linkage::External))
})
}
fn null<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> PointerValue<'ctx> {
cx.ctx.ptr_type(AddressSpace::default()).const_null()
}
fn c64<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, v: u64) -> IntValue<'ctx> {
cx.ctx.i64_type().const_int(v, false)
}
fn emit<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: WideOp,
dst: Option<PointerValue<'ctx>>,
a: Option<PointerValue<'ctx>>,
b: Option<PointerValue<'ctx>>,
p: [IntValue<'ctx>; 4],
) -> IntValue<'ctx> {
let n = || null(cx);
cx.b.build_call(
wide_fn(cx),
&[
cx.ctx.i32_type().const_int(op as u64, false).into(),
dst.unwrap_or_else(n).into(),
a.unwrap_or_else(n).into(),
b.unwrap_or_else(n).into(),
p[0].into(),
p[1].into(),
p[2].into(),
p[3].into(),
],
"sv_rt_wide_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
}
fn consts<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, p: [u64; 4]) -> [IntValue<'ctx>; 4] {
p.map(|v| c64(cx, v))
}
fn call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: WideOp,
dst: Option<PointerValue<'ctx>>,
a: Option<PointerValue<'ctx>>,
b: Option<PointerValue<'ctx>>,
p: [u64; 4],
) -> IntValue<'ctx> {
emit(cx, op, dst, a, b, consts(cx, p))
}
pub(crate) fn call_wide<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: WideOp,
dst: PointerValue<'ctx>,
a: PointerValue<'ctx>,
b: PointerValue<'ctx>,
p: [u64; 4],
) -> IntValue<'ctx> {
call(cx, op, Some(dst), Some(a), Some(b), p)
}
fn require_four_state(cx: &LowerCtx<'_, '_, '_>) -> Result<(), LoweringError> {
if cx.repr != ValueRepr::FourState {
return loud(
cx,
"a vector wider than 65535 bits needs the four-state value representation",
);
}
Ok(())
}
fn spill<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
pv: PackedValue<'ctx>,
) -> Result<HugeValue<'ctx>, LoweringError> {
require_four_state(cx)?;
let words = pk_words(pv.width);
let ptr = acquire(cx, words);
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let wide =
cx.b.build_int_z_extend_or_bit_cast(pv.llvm, op.i_n(words * 64), "huge_spill")
.unwrap();
cx.b.build_store(ptr, wide)
.unwrap()
.set_alignment(8)
.expect("alignment 8 is valid");
Ok(HugeValue {
ptr,
width: pv.width,
signed: pv.signed,
})
}
fn load_narrow<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ptr: PointerValue<'ctx>,
width: u32,
signed: bool,
) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let words = pk_words(width);
let v =
cx.b.build_load(op.i_n(words * 64), ptr, "huge_load")
.unwrap();
inkwell::values::BasicValue::as_instruction_value(&v)
.expect("a load is an instruction")
.set_alignment(8)
.expect("alignment 8 is valid");
let v =
cx.b.build_int_truncate_or_bit_cast(v.into_int_value(), op.i_n(2 * width), "huge_narrow")
.unwrap();
PackedValue {
llvm: v,
width,
repr: ValueRepr::FourState,
signed,
}
}
fn finish<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
dst: PointerValue<'ctx>,
width: u32,
signed: bool,
) -> LoweredValue<'ctx> {
if is_huge_width(width) {
return LoweredValue::Huge(HugeValue {
ptr: dst,
width,
signed,
});
}
let v = load_narrow(cx, dst, width, signed);
release(dst);
LoweredValue::Packed(v)
}
fn to_huge<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
lv: LoweredValue<'ctx>,
what: &str,
) -> Result<HugeValue<'ctx>, LoweringError> {
match lv {
LoweredValue::Huge(h) => Ok(h),
LoweredValue::Packed(p) => spill(cx, p),
LoweredValue::Real(_) | LoweredValue::Wide(_) | LoweredValue::Str(_) => loud(cx, what),
}
}
fn lower_operand<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<HugeValue<'ctx>, LoweringError> {
let lv = lower_expr_h(cx, e)?;
to_huge(
cx,
lv,
"an operand of a vector wider than the huge threshold must be an integral value",
)
}
pub(crate) fn bridge<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: LoweredValue<'ctx>,
) -> LoweredValue<'ctx> {
match v {
LoweredValue::Huge(h) if h.width <= WIDE_THRESHOLD => {
let p = load_narrow(cx, h.ptr, h.width, h.signed);
release(h.ptr);
LoweredValue::Packed(p)
}
other => other,
}
}
fn bit_result<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, ret: IntValue<'ctx>) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let two = cx.b.build_int_truncate(ret, op.i_n(2), "huge_bit").unwrap();
PackedValue {
llvm: two,
width: 1,
repr: ValueRepr::FourState,
signed: false,
}
}
fn type_width(cx: &LowerCtx<'_, '_, '_>, e: sv_lang::Expression<'_>) -> Result<u32, LoweringError> {
e.expr_type()
.map(|t| t.value_width())
.ok_or_else(|| LoweringError::unsupported_at("expression without a type", &cx.fn_sym))
}
fn type_signed(e: sv_lang::Expression<'_>) -> bool {
e.expr_type().is_some_and(|t| t.is_signed())
}
fn bit_kind(b: &Bit) -> u64 {
match b {
Bit::Zero => wide_rt::FILL_ZERO,
Bit::One => wide_rt::FILL_ONE,
Bit::X => wide_rt::FILL_X,
Bit::Z => wide_rt::FILL_Z,
}
}
fn constant_bits(e: sv_lang::Expression<'_>) -> Option<Vec<Bit>> {
let kind = e.kind();
let lit = if kind == ExpressionKind::IntegerLiteral {
e.integer_literal_value()
} else if kind == ExpressionKind::StringLiteral {
e.string_literal_int_value()
} else if kind == ExpressionKind::UnbasedUnsizedIntegerLiteral {
e.unbased_unsized_literal_value()
} else if kind == ExpressionKind::Call && e.is_system_call() == Some(false) {
return None;
} else {
return vj_sim::state::constant_expr_bits(&e);
};
let cv = lit?;
let iv = cv.as_integer()?;
vj_sim::state::svint_bits(iv).map(std::borrow::Cow::into_owned)
}
fn const_global<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
words: &[u64],
width: u32,
) -> PointerValue<'ctx> {
let mut h1 = std::collections::hash_map::DefaultHasher::new();
let mut h2 = std::collections::hash_map::DefaultHasher::new();
h1.write_u8(1);
h2.write_u8(2);
words.hash(&mut h1);
words.hash(&mut h2);
let name = format!(
"huge_const_{width}_{:016x}{:016x}",
h1.finish(),
h2.finish()
);
if let Some(g) = cx.module.get_global(&name) {
return g.as_pointer_value();
}
let i64t = cx.ctx.i64_type();
let vals: Vec<IntValue<'ctx>> = words.iter().map(|w| i64t.const_int(*w, false)).collect();
let ty = i64t.array_type(words.len() as u32);
let g = cx.module.add_global(ty, None, &name);
g.set_initializer(&i64t.const_array(&vals));
g.set_constant(true);
g.set_linkage(Linkage::Private);
g.set_alignment(8);
g.as_pointer_value()
}
fn constant<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<HugeValue<'ctx>>, LoweringError> {
let Some(bits) = constant_bits(e) else {
return Ok(None);
};
let width = type_width(cx, e)?;
if bits.len() as u64 != u64::from(width) {
return loud(
cx,
&format!("a constant of {} bits for a {width}-bit vector", bits.len()),
);
}
let signed = type_signed(e);
let kind = bit_kind(&bits[0]);
if bits.iter().all(|b| bit_kind(b) == kind) {
let dst = temp(cx, width);
call(
cx,
WideOp::Fill,
Some(dst),
None,
None,
[u64::from(width), kind, 0, 0],
);
return Ok(Some(HugeValue {
ptr: dst,
width,
signed,
}));
}
let words = crate::value::packed_words(&bits, width, ValueRepr::FourState);
Ok(Some(HugeValue {
ptr: const_global(cx, &words, width),
width,
signed,
}))
}
pub(crate) fn lower_constant<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
if !e.expr_type().is_some_and(|t| is_huge_type(&t)) {
return Ok(None);
}
require_four_state(cx)?;
Ok(constant(cx, e)?.map(LoweredValue::Huge))
}
pub(crate) fn is_huge_expr(e: sv_lang::Expression<'_>) -> bool {
e.expr_type().is_some_and(|t| is_huge_type(&t))
}
pub(crate) fn candidate_is_huge(c: sv_lang::Expression<'_>) -> bool {
if c.kind() == ExpressionKind::ValueRange {
c.left().is_some_and(is_huge_expr) || c.right().is_some_and(is_huge_expr)
} else {
is_huge_expr(c)
}
}
fn inside_parts<'d>(
e: sv_lang::Expression<'d>,
) -> (
Option<sv_lang::Expression<'d>>,
Vec<sv_lang::Expression<'d>>,
) {
if e.kind() == ExpressionKind::Dist {
(
e.dist_left(),
e.dist_items().iter().map(|item| item.value()).collect(),
)
} else {
(e.inside_left(), e.inside_range_list())
}
}
fn chain_base<'d>(e: sv_lang::Expression<'d>) -> Option<sv_lang::Expression<'d>> {
let kind = e.kind();
let base = if kind == ExpressionKind::ElementSelect || kind == ExpressionKind::RangeSelect {
e.select_value()?
} else if kind == ExpressionKind::MemberAccess {
e.operand()?
} else {
return None;
};
let ty = base.expr_type()?;
(ty.is_integral() && !ty.is_floating()).then_some(base)
}
fn chain_root<'d>(mut e: sv_lang::Expression<'d>) -> sv_lang::Expression<'d> {
while let Some(base) = chain_base(e) {
e = base;
}
e
}
pub(crate) fn rooted_in_huge(e: sv_lang::Expression<'_>) -> bool {
is_huge_expr(chain_root(e))
}
pub(crate) fn huge_local_root(cx: &LowerCtx<'_, '_, '_>, e: sv_lang::Expression<'_>) -> bool {
let root = chain_root(e);
vj_sim::symid::is_value_name(root.kind())
&& root
.referenced_symbol()
.is_some_and(|s| cx.locals.get(&s.uid()).is_some_and(|l| is_huge_width(l.1)))
}
pub(crate) fn class_prop_root(e: sv_lang::Expression<'_>) -> bool {
let root = chain_root(e);
is_huge_expr(root) && crate::classes::is_class_field_lvalue(root)
}
fn read_class_prop<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let Some((h, slot, ty, dead)) = crate::classes::class_field_loc(cx, e)? else {
return loud(cx, "internal: a class property expression with no location");
};
let width = ty.value_width();
let dst = temp(cx, width);
let live = crate::classes::field_get_into(cx, h, slot, dst);
if let Some((name, at)) = dead {
crate::classes::report_dead_handle_access(cx, live, at, &name, "read")?;
}
Ok(LoweredValue::Huge(HugeValue {
ptr: dst,
width,
signed: ty.is_signed(),
}))
}
fn write_class_prop<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
v: HugeValue<'ctx>,
) -> Result<(), LoweringError> {
let (root, levels) = resolve(cx, lhs)?;
let Some((h, slot, ty, dead)) = crate::classes::class_field_loc(cx, root)? else {
return loud(cx, "internal: a class property lvalue with no location");
};
let width = ty.value_width();
let cur = temp(cx, width);
match levels.last() {
None => {
call(
cx,
WideOp::Resize,
Some(cur),
Some(v.ptr),
None,
[u64::from(width), v.info(), 0, 0],
);
}
Some(last) => {
if last.width != v.width {
return loud(
cx,
"internal: the value assigned to a select of a huge vector is not as wide \
as the select",
);
}
crate::classes::field_get_into(cx, h, slot, cur);
write_levels(cx, cur, width, &levels, v)?;
}
}
let live = crate::classes::field_set_from(cx, h, slot, cur);
release(cur);
if let Some((name, at)) = dead {
crate::classes::report_dead_handle_access(cx, live, at, &name, "write")?;
}
Ok(())
}
pub(crate) fn mem_elem_root(cx: &LowerCtx<'_, '_, '_>, e: sv_lang::Expression<'_>) -> bool {
let root = chain_root(e);
if root.kind() != ExpressionKind::ElementSelect
|| !root.expr_type().is_some_and(|t| is_image_type(&t))
{
return false;
}
let mut base = root.select_value();
while let Some(b) = base
&& b.kind() == ExpressionKind::ElementSelect
{
base = b.select_value();
}
base.filter(|b| vj_sim::symid::is_value_name(b.kind()))
.and_then(|b| b.referenced_symbol())
.is_some_and(|s| cx.mem_signals.contains_key(&s.uid()))
}
fn mem_row<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
root: sv_lang::Expression<'d>,
) -> Result<(crate::arrays::ArrayRef<'ctx>, crate::expr::MemorySignalInfo), LoweringError> {
let Some((r, shape)) = crate::arrays::resolve_array_ref(cx, root)? else {
return loud(cx, "internal: a memory element expression with no storage");
};
match r.backing {
crate::arrays::Backing::Memory(info) if shape.dims.is_empty() => Ok((r, info)),
_ => loud(
cx,
"an unpacked array whose elements are wider than 65535 bits, other than as a \
module-level memory indexed down to one element",
),
}
}
fn read_mem_elem<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let (r, info) = mem_row(cx, e)?;
let Some(acc) = cx.mem_accessor else {
return loud(cx, "memory read with no accessor context");
};
let ty = e.expr_type().expect("a typed element");
let width = info.elem_width;
let dst = temp(cx, width);
let fetch = |cx: &LowerCtx<'ctx, 'd, '_>| {
crate::accessor::build_get_mem_huge(cx, acc.fns, acc.handle, info.slot, r.base, width, dst);
};
if r.valid.get_zero_extended_constant() == Some(1) {
fetch(cx);
} else {
let func =
cx.b.get_insert_block()
.and_then(|b| b.get_parent())
.expect("inside a function");
let ok_bb = cx.ctx.append_basic_block(func, "hmem_ok");
let bad_bb = cx.ctx.append_basic_block(func, "hmem_bad");
let done_bb = cx.ctx.append_basic_block(func, "hmem_done");
cx.b.build_conditional_branch(r.valid, ok_bb, bad_bb)
.unwrap();
cx.b.position_at_end(ok_bb);
fetch(cx);
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(bad_bb);
init_image(cx, dst, width, ty.is_four_state())?;
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
}
Ok(LoweredValue::Huge(HugeValue {
ptr: dst,
width,
signed: ty.is_signed(),
}))
}
fn write_mem_elem<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
v: HugeValue<'ctx>,
) -> Result<(), LoweringError> {
let (root, levels) = resolve(cx, lhs)?;
let (r, info) = mem_row(cx, root)?;
let Some(acc) = cx.mem_accessor else {
return loud(cx, "memory write with no accessor context");
};
let width = info.elem_width;
if let Some(last) = levels.last()
&& last.width != v.width
{
return loud(
cx,
"internal: the value assigned to a select of a huge vector is not as wide as the \
select",
);
}
crate::arrays::guarded(cx, r.valid, || {
let cur = temp(cx, width);
if levels.is_empty() {
call(
cx,
WideOp::Resize,
Some(cur),
Some(v.ptr),
None,
[u64::from(width), v.info(), 0, 0],
);
} else {
crate::accessor::build_get_mem_huge(
cx, acc.fns, acc.handle, info.slot, r.base, width, cur,
);
write_levels(cx, cur, width, &levels, v)?;
}
crate::accessor::build_set_mem_huge(cx, acc.fns, acc.handle, info.slot, r.base, width, cur);
release(cur);
Ok(())
})
}
fn mem_run<'ctx, 'm>(
cx: &LowerCtx<'ctx, '_, 'm>,
r: &crate::arrays::ArrayRef<'ctx>,
) -> Result<
(
crate::expr::MemorySignalInfo,
crate::expr::MemAccessorCtx<'ctx, 'm>,
),
LoweringError,
> {
let crate::arrays::Backing::Memory(info) = r.backing else {
return loud(
cx,
"a local unpacked array whose elements are wider than 65535 bits (LRM 1800-2023 \
6.9.1); only module-level memories of such elements are supported",
);
};
let Some(acc) = cx.mem_accessor else {
return loud(cx, "memory access with no accessor context");
};
Ok((info, acc))
}
pub(crate) fn load_mem_row<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &crate::arrays::ArrayRef<'ctx>,
k: IntValue<'ctx>,
four_state: bool,
) -> Result<HugeValue<'ctx>, LoweringError> {
let (info, acc) = mem_run(cx, r)?;
let width = info.elem_width;
let dst = temp(cx, width);
let row = cx.b.build_int_add(r.base, k, "hmem_row").unwrap();
let fetch = |cx: &LowerCtx<'ctx, '_, '_>| {
crate::accessor::build_get_mem_huge(cx, acc.fns, acc.handle, info.slot, row, width, dst);
};
if r.valid.get_zero_extended_constant() == Some(1) {
fetch(cx);
} else {
let func =
cx.b.get_insert_block()
.and_then(|b| b.get_parent())
.expect("inside a function");
let ok_bb = cx.ctx.append_basic_block(func, "hmem_ok");
let bad_bb = cx.ctx.append_basic_block(func, "hmem_bad");
let done_bb = cx.ctx.append_basic_block(func, "hmem_done");
cx.b.build_conditional_branch(r.valid, ok_bb, bad_bb)
.unwrap();
cx.b.position_at_end(ok_bb);
fetch(cx);
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(bad_bb);
init_image(cx, dst, width, four_state)?;
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
}
Ok(HugeValue {
ptr: dst,
width,
signed: false,
})
}
pub(crate) fn store_mem_row<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &crate::arrays::ArrayRef<'ctx>,
k: IntValue<'ctx>,
v: HugeValue<'ctx>,
) -> Result<(), LoweringError> {
let (info, acc) = mem_run(cx, r)?;
let width = info.elem_width;
let row = cx.b.build_int_add(r.base, k, "hmem_row").unwrap();
crate::arrays::guarded(cx, r.valid, || {
let cur = if v.width == width {
v.ptr
} else {
let cur = temp(cx, width);
call(
cx,
WideOp::Resize,
Some(cur),
Some(v.ptr),
None,
[u64::from(width), v.info(), 0, 0],
);
cur
};
crate::accessor::build_set_mem_huge(cx, acc.fns, acc.handle, info.slot, row, width, cur);
if cur != v.ptr {
release(cur);
}
Ok(())
})
}
pub(crate) fn element_image<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<HugeValue<'ctx>, LoweringError> {
require_four_state(cx)?;
lower_operand(cx, e)
}
fn involves(e: sv_lang::Expression<'_>) -> bool {
let sub = |x: Option<sv_lang::Expression<'_>>| x.is_some_and(is_huge_expr);
let kind = e.kind();
if kind == ExpressionKind::UnaryOp {
is_huge_expr(e) || sub(e.operand())
} else if kind == ExpressionKind::BinaryOp {
!matches!(
e.binary_op(),
Some(
BinaryOp::LogicalAnd
| BinaryOp::LogicalOr
| BinaryOp::LogicalImplication
| BinaryOp::LogicalEquivalence
)
) && (is_huge_expr(e) || sub(e.left()) || sub(e.right()))
} else if kind == ExpressionKind::Conversion {
is_huge_expr(e) || sub(e.conversion_operand())
} else if matches!(kind, ExpressionKind::Inside | ExpressionKind::Dist) {
let (left, cands) = inside_parts(e);
sub(left) || cands.into_iter().any(candidate_is_huge)
} else if matches!(
kind,
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect | ExpressionKind::MemberAccess
) {
rooted_in_huge(e)
} else if kind == ExpressionKind::Assignment {
is_huge_expr(e) || sub(e.left()) || e.left().is_some_and(rooted_in_huge)
} else {
is_huge_expr(e)
}
}
pub(crate) fn try_lower<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
if !involves(e) {
return Ok(None);
}
require_four_state(cx)?;
lower(cx, e).map(Some)
}
fn lower<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if is_huge_expr(e)
&& let Some(c) = constant(cx, e)?
{
return Ok(LoweredValue::Huge(c));
}
if !is_huge_expr(e)
&& matches!(
e.kind(),
ExpressionKind::ElementSelect
| ExpressionKind::RangeSelect
| ExpressionKind::MemberAccess
)
&& let Some(cv) = e.constant_value()
&& let Some(iv) = cv.as_integer()
&& !iv.has_unknown()
{
return crate::expr::lower_constant(cx, iv, Some(e));
}
if !native(cx, e)? {
return fall_back(cx, e);
}
match e.kind() {
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => named(cx, e),
ExpressionKind::UnaryOp => unary(cx, e),
ExpressionKind::BinaryOp => binary(cx, e),
ExpressionKind::ConditionalOp => ternary(cx, e),
ExpressionKind::Concatenation => concatenation(cx, e),
ExpressionKind::Replication => replication(cx, e),
ExpressionKind::Conversion => conversion(cx, e),
ExpressionKind::ElementSelect
| ExpressionKind::RangeSelect
| ExpressionKind::MemberAccess => read_chain(cx, e),
ExpressionKind::Assignment => assignment(cx, e),
ExpressionKind::LValueReference => lvalue_reference(cx),
ExpressionKind::Inside | ExpressionKind::Dist => inside(cx, e),
ExpressionKind::Call => {
let name = e
.system_subroutine()
.map(|s| s.known_name_id())
.ok_or_else(|| {
LoweringError::unsupported_at("a call without a name", &cx.fn_sym)
})?;
crate::sysfunc::lower_bit_sysfunc(cx, name, e)
.unwrap_or_else(|| loud(cx, "internal: not a bit-level system function"))
}
other => Err(LoweringError::unsupported_at(
&format!("a {other:?} expression involving a huge vector"),
&cx.fn_sym,
)),
}
}
fn native<'d>(
cx: &LowerCtx<'_, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<bool, LoweringError> {
if !matches!(
e.kind(),
ExpressionKind::NamedValue
| ExpressionKind::HierarchicalValue
| ExpressionKind::Concatenation
| ExpressionKind::Replication
| ExpressionKind::ElementSelect
| ExpressionKind::RangeSelect
| ExpressionKind::MemberAccess
| ExpressionKind::LValueReference
| ExpressionKind::Inside
| ExpressionKind::Dist
| ExpressionKind::Call
| ExpressionKind::Assignment
| ExpressionKind::UnaryOp
| ExpressionKind::BinaryOp
| ExpressionKind::ConditionalOp
| ExpressionKind::Conversion
) {
return Ok(false);
}
Ok(match e.kind() {
ExpressionKind::NamedValue
| ExpressionKind::HierarchicalValue
| ExpressionKind::Concatenation
| ExpressionKind::Replication
| ExpressionKind::ElementSelect
| ExpressionKind::RangeSelect
| ExpressionKind::MemberAccess
| ExpressionKind::LValueReference
| ExpressionKind::Inside
| ExpressionKind::Dist => true,
ExpressionKind::Call => is_bit_sysfunc(e),
ExpressionKind::Assignment => e
.left()
.is_some_and(|l| huge_local_root(cx, l) || class_prop_root(l) || mem_elem_root(cx, l)),
ExpressionKind::UnaryOp => match e.unary_op() {
Some(
UnaryOp::Preincrement
| UnaryOp::Predecrement
| UnaryOp::Postincrement
| UnaryOp::Postdecrement,
) => e.operand().is_some_and(|o| {
huge_local_root(cx, o) || class_prop_root(o) || mem_elem_root(cx, o)
}),
_ => true,
},
ExpressionKind::BinaryOp => true,
ExpressionKind::ConditionalOp => {
let conds = e.conditions();
conds.len() == 1
&& conds[0].pattern().is_none()
&& e.true_value().is_some()
&& e.false_value().is_some()
}
ExpressionKind::Conversion => {
use crate::expr::ConversionAction;
let operand = e.conversion_operand();
let to = e.expr_type();
let string_involved = to.as_ref().is_some_and(|t| t.is_string())
|| operand
.and_then(|o| o.expr_type())
.is_some_and(|t| t.is_string());
match e
.conversion_kind()
.map(crate::expr::classify_conversion_kind)
{
Some(Ok(ConversionAction::Value)) => true,
Some(Ok(ConversionAction::Stream)) => {
!string_involved
&& to.is_some_and(|t| plain_stream_type(&t))
&& operand.is_some_and(stream_native)
}
Some(Ok(ConversionAction::BitstreamCast)) => {
!string_involved
&& to.is_some_and(|t| plain_stream_type(&t))
&& operand.is_some_and(stream_native)
}
Some(Err(_)) | None => string_involved,
}
}
other => unreachable!("{other:?} was filtered out above"),
})
}
fn fall_back<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let too_wide = |x: Option<sv_lang::Expression<'_>>| {
x.and_then(|x| x.expr_type())
.is_some_and(|t| t.is_integral() && t.value_width() > WIDE_THRESHOLD)
};
if e.kind() == ExpressionKind::Call && e.is_system_call() == Some(false) {
return fall_back_lowered(cx, e);
}
if too_wide(Some(e))
|| too_wide(e.operand())
|| too_wide(e.left())
|| too_wide(e.right())
|| too_wide(e.conversion_operand())
{
return loud(
cx,
&format!(
"a {:?} expression on a vector wider than {WIDE_THRESHOLD} bits",
e.kind()
),
);
}
fall_back_lowered(cx, e)
}
fn fall_back_lowered<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
match lower_expr_regular(cx, e)? {
LoweredValue::Packed(pv) if is_huge_expr(e) => Ok(LoweredValue::Huge(spill(cx, pv)?)),
other => Ok(other),
}
}
fn named<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let sym = e.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at("a name with no referenced symbol", &cx.fn_sym)
})?;
match cx.locals.get(&sym.uid()) {
Some(&(ptr, width, signed)) if is_huge_width(width) => {
Ok(LoweredValue::Huge(HugeValue { ptr, width, signed }))
}
Some(_) => loud(
cx,
"internal: a narrow local used where a vector wider than 65535 bits was expected",
),
None if crate::classes::is_class_field_lvalue(e) => read_class_prop(cx, e),
None => fall_back(cx, e),
}
}
fn unary<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let opd = e.operand().ok_or_else(|| {
LoweringError::unsupported_at("unary operator without operand", &cx.fn_sym)
})?;
let uop = e
.unary_op()
.ok_or_else(|| LoweringError::unsupported_at("unary operator without kind", &cx.fn_sym))?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let reduce = |wop: WideOp| -> Result<PackedValue<'ctx>, LoweringError> {
let v = lower_operand(cx, opd)?;
let ret = call(cx, wop, None, Some(v.ptr), None, [0, v.info(), 0, 0]);
release(v.ptr);
Ok(bit_result(cx, ret))
};
let arith = |wop: WideOp| -> Result<LoweredValue<'ctx>, LoweringError> {
let w = type_width(cx, e)?;
let v = lower_operand(cx, opd)?;
release(v.ptr);
let dst = temp(cx, w);
call(
cx,
wop,
Some(dst),
Some(v.ptr),
None,
[u64::from(w), v.info(), 0, 0],
);
Ok(finish(cx, dst, w, type_signed(e)))
};
match uop {
UnaryOp::Plus => lower_expr_h(cx, opd),
UnaryOp::Minus => arith(WideOp::Neg),
UnaryOp::BitwiseNot => arith(WideOp::Not),
UnaryOp::BitwiseAnd => Ok(LoweredValue::Packed(reduce(WideOp::RedAnd)?)),
UnaryOp::BitwiseOr => Ok(LoweredValue::Packed(reduce(WideOp::RedOr)?)),
UnaryOp::BitwiseXor => Ok(LoweredValue::Packed(reduce(WideOp::RedXor)?)),
UnaryOp::BitwiseNand => Ok(LoweredValue::Packed(op.not4(reduce(WideOp::RedAnd)?))),
UnaryOp::BitwiseNor => Ok(LoweredValue::Packed(op.not4(reduce(WideOp::RedOr)?))),
UnaryOp::BitwiseXnor => Ok(LoweredValue::Packed(op.not4(reduce(WideOp::RedXor)?))),
UnaryOp::LogicalNot => Ok(LoweredValue::Packed(op.lnot4(reduce(WideOp::RedOr)?))),
UnaryOp::Preincrement
| UnaryOp::Predecrement
| UnaryOp::Postincrement
| UnaryOp::Postdecrement => increment(cx, e, opd, uop),
}
}
fn increment<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
opd: sv_lang::Expression<'d>,
uop: UnaryOp,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let w = type_width(cx, e)?;
let signed = type_signed(e);
let cur = lower_operand(cx, opd)?;
let post = matches!(uop, UnaryOp::Postincrement | UnaryOp::Postdecrement);
let wop = if matches!(uop, UnaryOp::Preincrement | UnaryOp::Postincrement) {
WideOp::Add
} else {
WideOp::Sub
};
let old = post.then(|| {
let copy = temp(cx, w);
call(
cx,
WideOp::Resize,
Some(copy),
Some(cur.ptr),
None,
[u64::from(w), cur.info(), 0, 0],
);
copy
});
let one = spill(
cx,
PackedValue {
llvm: OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.i_n(2)
.const_int(1, false),
width: 1,
repr: ValueRepr::FourState,
signed: false,
},
)?;
let new = temp(cx, w);
call(
cx,
wop,
Some(new),
Some(cur.ptr),
Some(one.ptr),
[u64::from(w), cur.info(), one.info(), u64::from(cur.signed)],
);
release(cur.ptr);
release(one.ptr);
let stored = HugeValue {
ptr: new,
width: w,
signed,
};
if class_prop_root(opd) && !huge_local_root(cx, opd) {
write_class_prop(cx, opd, stored)?;
} else if mem_elem_root(cx, opd) {
write_mem_elem(cx, opd, stored)?;
} else {
let (shadow, _) = write_chain(cx, opd, stored)?;
write_through_commit(cx, shadow);
}
match old {
Some(copy) => {
release(new);
Ok(finish(cx, copy, w, signed))
}
None => Ok(finish(cx, new, w, signed)),
}
}
fn binary<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let (Some(l), Some(r), Some(bop)) = (e.left(), e.right(), e.binary_op()) else {
return loud(cx, "binary operator without operands");
};
let arith = |wop: WideOp| -> Result<LoweredValue<'ctx>, LoweringError> {
let w = type_width(cx, e)?;
let lv = lower_operand(cx, l)?;
let rv = lower_operand(cx, r)?;
release(lv.ptr);
release(rv.ptr);
let dst = temp(cx, w);
call(
cx,
wop,
Some(dst),
Some(lv.ptr),
Some(rv.ptr),
[u64::from(w), lv.info(), rv.info(), u64::from(lv.signed)],
);
Ok(finish(cx, dst, w, type_signed(e)))
};
let shift = |wop: WideOp| -> Result<LoweredValue<'ctx>, LoweringError> {
let w = type_width(cx, e)?;
let lv = lower_operand(cx, l)?;
let rv = lower_operand(cx, r)?;
release(lv.ptr);
release(rv.ptr);
let dst = temp(cx, w);
call(
cx,
wop,
Some(dst),
Some(lv.ptr),
Some(rv.ptr),
[
u64::from(w),
lv.info(),
rv.info(),
u64::from(wop == WideOp::Ashr),
],
);
Ok(finish(cx, dst, w, type_signed(e)))
};
let compare = |wop: WideOp| -> Result<LoweredValue<'ctx>, LoweringError> {
let lv = lower_operand(cx, l)?;
let rv = lower_operand(cx, r)?;
let ret = call(
cx,
wop,
None,
Some(lv.ptr),
Some(rv.ptr),
[
u64::from(lv.width.max(rv.width)),
lv.info(),
rv.info(),
u64::from(lv.signed),
],
);
release(lv.ptr);
release(rv.ptr);
Ok(LoweredValue::Packed(bit_result(cx, ret)))
};
match bop {
BinaryOp::Add => arith(WideOp::Add),
BinaryOp::Subtract => arith(WideOp::Sub),
BinaryOp::Multiply => arith(WideOp::Mul),
BinaryOp::Divide => arith(WideOp::Div),
BinaryOp::Mod => arith(WideOp::Mod),
BinaryOp::BinaryAnd => arith(WideOp::And),
BinaryOp::BinaryOr => arith(WideOp::Or),
BinaryOp::BinaryXor => arith(WideOp::Xor),
BinaryOp::BinaryXnor => arith(WideOp::Xnor),
BinaryOp::LogicalShiftLeft | BinaryOp::ArithmeticShiftLeft => shift(WideOp::Shl),
BinaryOp::LogicalShiftRight => shift(WideOp::Shr),
BinaryOp::ArithmeticShiftRight => shift(if type_signed(e) {
WideOp::Ashr
} else {
WideOp::Shr
}),
BinaryOp::Equality => compare(WideOp::Eq),
BinaryOp::Inequality => compare(WideOp::Ne),
BinaryOp::CaseEquality => compare(WideOp::CaseEq),
BinaryOp::CaseInequality => compare(WideOp::CaseNe),
BinaryOp::WildcardEquality => compare(WideOp::WildEq),
BinaryOp::WildcardInequality => compare(WideOp::WildNe),
BinaryOp::LessThan => compare(WideOp::Lt),
BinaryOp::LessThanEqual => compare(WideOp::Le),
BinaryOp::GreaterThan => compare(WideOp::Gt),
BinaryOp::GreaterThanEqual => compare(WideOp::Ge),
BinaryOp::Power => {
let w = type_width(cx, e)?;
let lv = lower_operand(cx, l)?;
let rv = lower_operand(cx, r)?;
release(lv.ptr);
release(rv.ptr);
let dst = temp(cx, w);
call(
cx,
WideOp::Pow,
Some(dst),
Some(lv.ptr),
Some(rv.ptr),
[u64::from(w), lv.info(), rv.info(), 0],
);
Ok(finish(cx, dst, w, type_signed(e)))
}
BinaryOp::LogicalAnd
| BinaryOp::LogicalOr
| BinaryOp::LogicalImplication
| BinaryOp::LogicalEquivalence => loud(
cx,
"internal: a logical operator reached the huge-vector lowering",
),
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum TruthMode {
Strict,
Ternary,
}
pub(crate) fn truth_packed<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
lv: LoweredValue<'ctx>,
mode: TruthMode,
what: &str,
) -> Result<PackedValue<'ctx>, LoweringError> {
match lv {
LoweredValue::Huge(h) => {
let wop = match mode {
TruthMode::Strict => WideOp::Truth,
TruthMode::Ternary => WideOp::RedOr,
};
let ret = call(cx, wop, None, Some(h.ptr), None, [0, h.info(), 0, 0]);
release(h.ptr);
Ok(bit_result(cx, ret))
}
LoweredValue::Real(rv) => Ok(crate::real::real_truth_packed(cx, rv)),
other => other.into_packed(what, &cx.fn_sym),
}
}
pub(crate) fn lower_selector<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<HugeValue<'ctx>, LoweringError> {
require_four_state(cx)?;
lower_operand(cx, e)
}
pub(crate) fn compare_images<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
wop: WideOp,
l: HugeValue<'ctx>,
r: HugeValue<'ctx>,
) -> PackedValue<'ctx> {
let signed = l.signed && r.signed;
let (l, r) = (HugeValue { signed, ..l }, HugeValue { signed, ..r });
let ret = call(
cx,
wop,
None,
Some(l.ptr),
Some(r.ptr),
[
u64::from(l.width.max(r.width)),
l.info(),
r.info(),
u64::from(signed),
],
);
bit_result(cx, ret)
}
fn tolerance_contribution<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
left: HugeValue<'ctx>,
cand: sv_lang::Expression<'d>,
relative: bool,
) -> Result<PackedValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let (Some(ae), Some(be)) = (cand.left(), cand.right()) else {
return loud(cx, "an inside tolerance range missing an operand");
};
let a = lower_operand(cx, ae)?;
let b = lower_operand(cx, be)?;
let wa = a.width;
let (t, t_info, t_tmp) = if relative {
let w = a.width + b.width + 1;
let prod = temp(cx, w);
call(
cx,
WideOp::Mul,
Some(prod),
Some(a.ptr),
Some(b.ptr),
[u64::from(w), a.info(), b.info(), 0],
);
let hundred = spill(
cx,
op.pack(
op.i_n(32).const_int(100, false),
op.i_n(32).const_zero(),
32,
cx.repr,
false,
),
)?;
let q = temp(cx, w);
call(
cx,
WideOp::Div,
Some(q),
Some(prod),
Some(hundred.ptr),
[u64::from(w), wide_rt::info(w, true), hundred.info(), 1],
);
release(prod);
release(hundred.ptr);
(q, wide_rt::info(w, false), Some(q))
} else {
(b.ptr, b.info(), None)
};
let lo = temp(cx, wa);
let hi = temp(cx, wa);
for (wop, dst) in [(WideOp::Sub, lo), (WideOp::Add, hi)] {
call(
cx,
wop,
Some(dst),
Some(a.ptr),
Some(t),
[u64::from(wa), a.info(), t_info, 0],
);
}
release(a.ptr);
release(b.ptr);
if let Some(q) = t_tmp {
release(q);
}
let lo = HugeValue {
ptr: lo,
width: wa,
signed: a.signed,
};
let hi = HugeValue {
ptr: hi,
width: wa,
signed: a.signed,
};
let fwd = op.and4(
compare_images(cx, WideOp::Ge, left, lo),
compare_images(cx, WideOp::Le, left, hi),
);
let rev = op.and4(
compare_images(cx, WideOp::Ge, left, hi),
compare_images(cx, WideOp::Le, left, lo),
);
release(lo.ptr);
release(hi.ptr);
Ok(op.or4(fwd, rev))
}
fn inside_contribution<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
left: HugeValue<'ctx>,
cand: sv_lang::Expression<'d>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if cand.kind() == ExpressionKind::ValueRange {
if let Some(relative) = crate::expr::tolerance_op(cand) {
return tolerance_contribution(cx, left, cand, relative);
}
let (Some(lo), Some(hi)) = (cand.left(), cand.right()) else {
return loud(cx, "an inside range missing a bound");
};
let ge = if crate::expr::is_open_bound(Some(lo)) {
compare_images(cx, WideOp::Ge, left, left)
} else {
let lo = lower_operand(cx, lo)?;
let ge = compare_images(cx, WideOp::Ge, left, lo);
release(lo.ptr);
ge
};
let le = if crate::expr::is_open_bound(Some(hi)) {
compare_images(cx, WideOp::Le, left, left)
} else {
let hi = lower_operand(cx, hi)?;
let le = compare_images(cx, WideOp::Le, left, hi);
release(hi.ptr);
le
};
return Ok(op.and4(ge, le));
}
let item = lower_operand(cx, cand)?;
let m = compare_images(cx, WideOp::WildEq, left, item);
release(item.ptr);
Ok(m)
}
fn inside<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let (left, cands) = inside_parts(e);
let Some(left) = left else {
return loud(cx, "inside missing its left operand");
};
if cands.is_empty() {
return loud(cx, "inside with an empty candidate list");
}
let left = lower_operand(cx, left)?;
let mut acc: Option<PackedValue<'ctx>> = None;
for cand in cands {
let c = inside_contribution(cx, left, cand)?;
acc = Some(match acc {
None => c,
Some(prev) => op.or4(prev, c),
});
}
release(left.ptr);
Ok(LoweredValue::Packed(
acc.expect("candidates non-empty, checked above"),
))
}
pub(crate) fn case_label_matches<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
kind: sv_lang::CaseStatementCondition,
sel: HugeValue<'ctx>,
label: sv_lang::Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
use sv_lang::CaseStatementCondition as K;
let hit = match kind {
K::Inside => inside_contribution(cx, sel, label)?,
K::Normal => {
let item = lower_operand(cx, label)?;
let r = compare_images(cx, WideOp::CaseEq, sel, item);
release(item.ptr);
r
}
K::WildcardXOrZ | K::WildcardJustZ => {
let item = lower_operand(cx, label)?;
let signed = sel.signed && item.signed;
let (l, r) = (HugeValue { signed, ..sel }, HugeValue { signed, ..item });
let ret = call(
cx,
WideOp::CaseMatch,
None,
Some(l.ptr),
Some(r.ptr),
[
u64::from(l.width.max(r.width)),
l.info(),
r.info(),
u64::from(kind == K::WildcardJustZ),
],
);
release(item.ptr);
bit_result(cx, ret)
}
};
Ok(crate::ops::is_definitely_true(cx.ctx, &cx.b, hit))
}
fn is_bit_sysfunc(e: sv_lang::Expression<'_>) -> bool {
e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& e.system_subroutine()
.is_some_and(|s| crate::sysfunc::is_bit_sysfunc_name(s.known_name_id()))
}
pub(crate) fn sysfunc<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
name: sv_lang::KnownSystemName,
e: sv_lang::Expression<'_>,
h: HugeValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use sv_lang::KnownSystemName as K;
if matches!(name, K::Signed | K::Unsigned) {
return Ok(LoweredValue::Huge(HugeValue {
signed: name == K::Signed,
..h
}));
}
let ret = e.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("system function call has no type", &cx.fn_sym)
})?;
let rw = ret.value_width();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let count = |mask: u64| {
call(
cx,
WideOp::Count,
None,
Some(h.ptr),
None,
[mask, h.info(), 0, 0],
)
};
let i64t = cx.ctx.i64_type();
let (value, unknown) = match name {
K::CountOnes => (count(2), None),
K::OneHot | K::OneHot0 => {
let pred = if name == K::OneHot {
IntPredicate::EQ
} else {
IntPredicate::ULE
};
let bit =
cx.b.build_int_compare(pred, count(2), i64t.const_int(1, false), "huge_onehot")
.unwrap();
(bit, None)
}
K::IsUnknown => {
let n = count(12);
let bit =
cx.b.build_int_compare(IntPredicate::NE, n, i64t.const_zero(), "huge_isunk")
.unwrap();
(bit, None)
}
K::CountBits => {
let mut mask = i64t.const_zero();
for c in e.operands().iter().skip(1) {
let bit = c
.constant_value()
.as_ref()
.and_then(|cv| cv.as_integer())
.and_then(|i| i.bit(0));
let lane = if let Some(bit) = bit {
i64t.const_int(
match bit {
Bit::Zero => 1,
Bit::One => 2,
Bit::X => 4,
Bit::Z => 8,
},
false,
)
} else {
let cv = lower_expr_h(cx, *c)?.into_packed(
"a $countbits control bit of a real or wide value",
&cx.fn_sym,
)?;
let i1 = cx.ctx.bool_type();
let cval =
cx.b.build_int_truncate(op.lane_val(cv), i1, "hcb_v")
.unwrap();
let cunk =
cx.b.build_int_truncate(op.lane_unk(cv), i1, "hcb_u")
.unwrap();
let pick = |t: u64, f: u64, name: &str| {
cx.b.build_select(cval, c64(cx, t), c64(cx, f), name)
.unwrap()
.into_int_value()
};
cx.b.build_select(cunk, pick(8, 4, "hcb_x"), pick(2, 1, "hcb_k"), "hcb_lane")
.unwrap()
.into_int_value()
};
mask = cx.b.build_or(mask, lane, "hcb_mask").unwrap();
}
let n = emit(
cx,
WideOp::Count,
None,
Some(h.ptr),
None,
[mask, c64(cx, h.info()), c64(cx, 0), c64(cx, 0)],
);
(n, None)
}
K::Clog2 => {
let r = call(
cx,
WideOp::Clog2,
None,
Some(h.ptr),
None,
[0, h.info(), 0, 0],
);
let any_unk =
cx.b.build_int_compare(IntPredicate::EQ, r, i64t.const_all_ones(), "huge_clog2_x")
.unwrap();
(r, Some(any_unk))
}
_ => unreachable!("filtered by is_bit_sysfunc"),
};
release(h.ptr);
let rt = op.i_n(rw);
let v = crate::sysfunc::fit(cx, value, rw);
let (v, unk) = match unknown {
Some(x) => (
cx.b.build_select(x, rt.const_zero(), v, "huge_clog2_v")
.unwrap()
.into_int_value(),
cx.b.build_select(x, rt.const_all_ones(), rt.const_zero(), "huge_clog2_u")
.unwrap()
.into_int_value(),
),
None => (v, rt.const_zero()),
};
Ok(LoweredValue::Packed(op.pack(
v,
unk,
rw,
cx.repr,
ret.is_signed(),
)))
}
fn ternary<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let conds = e.conditions();
if conds.len() != 1 || conds[0].pattern().is_some() {
return loud(
cx,
"a pattern-matching or multi-condition `?:` with a vector wider than 65535 bits",
);
}
let (Some(t), Some(f)) = (e.true_value(), e.false_value()) else {
return loud(cx, "ternary without both arms");
};
let clean = |arm: sv_lang::Expression<'d>| {
crate::expr::is_side_effect_free(&arm, &cx.fn_sym, &crate::coverage::NoopCoverageSink)
};
let w = type_width(cx, e)?;
if !(clean(t)? && clean(f)?) {
return ternary_branching(cx, e, conds[0].expr(), t, f, w);
}
let cond = truth_packed(
cx,
lower_expr_h(cx, conds[0].expr())?,
TruthMode::Ternary,
"ternary condition must be an integral (non-real) value",
)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let is_true = crate::ops::ternary_is_definitely_true(cx.ctx, &cx.b, cond);
let is_false = crate::ops::is_definitely_false(cx.ctx, &cx.b, cond);
let i64t = cx.ctx.i64_type();
let mode =
cx.b.build_select(
is_true,
i64t.const_int(1, false),
cx.b.build_select(is_false, i64t.const_zero(), i64t.const_int(2, false), "m")
.unwrap()
.into_int_value(),
"huge_tmode",
)
.unwrap()
.into_int_value();
let _ = op;
let tv = lower_operand(cx, t)?;
let fv = lower_operand(cx, f)?;
release(tv.ptr);
release(fv.ptr);
let dst = temp(cx, w);
emit(
cx,
WideOp::Ternary,
Some(dst),
Some(tv.ptr),
Some(fv.ptr),
[
c64(cx, u64::from(w)),
c64(cx, tv.info()),
c64(cx, fv.info()),
mode,
],
);
Ok(finish(cx, dst, w, type_signed(e)))
}
fn ternary_branching<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
cond_expr: sv_lang::Expression<'d>,
t: sv_lang::Expression<'d>,
f: sv_lang::Expression<'d>,
w: u32,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let cond = truth_packed(
cx,
lower_expr_h(cx, cond_expr)?,
TruthMode::Ternary,
"ternary condition must be an integral (non-real) value",
)?;
let is_true = crate::ops::ternary_is_definitely_true(cx.ctx, &cx.b, cond);
let is_false = crate::ops::is_definitely_false(cx.ctx, &cx.b, cond);
let dst = temp(cx, w);
let func =
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(func, "huge_tern_then");
let check_bb = cx.ctx.append_basic_block(func, "huge_tern_check");
let else_bb = cx.ctx.append_basic_block(func, "huge_tern_else");
let ambig_bb = cx.ctx.append_basic_block(func, "huge_tern_ambig");
let merge_bb = cx.ctx.append_basic_block(func, "huge_tern_merge");
cx.b.build_conditional_branch(is_true, then_bb, check_bb)
.unwrap();
cx.b.position_at_end(check_bb);
cx.b.build_conditional_branch(is_false, else_bb, ambig_bb)
.unwrap();
let chosen = |arm: sv_lang::Expression<'d>| -> Result<(), LoweringError> {
let v = lower_operand(cx, arm)?;
call(
cx,
WideOp::Resize,
Some(dst),
Some(v.ptr),
None,
[u64::from(w), v.info(), 0, 0],
);
release(v.ptr);
cx.b.build_unconditional_branch(merge_bb).unwrap();
Ok(())
};
cx.b.position_at_end(then_bb);
chosen(t)?;
cx.b.position_at_end(else_bb);
chosen(f)?;
cx.b.position_at_end(ambig_bb);
let tv = lower_operand(cx, t)?;
let fv = lower_operand(cx, f)?;
call(
cx,
WideOp::Ternary,
Some(dst),
Some(tv.ptr),
Some(fv.ptr),
[u64::from(w), tv.info(), fv.info(), 2],
);
release(tv.ptr);
release(fv.ptr);
cx.b.build_unconditional_branch(merge_bb).unwrap();
cx.b.position_at_end(merge_bb);
Ok(finish(cx, dst, w, type_signed(e)))
}
fn concatenation<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let parts = e.operands();
if parts.is_empty() {
return loud(cx, "empty concatenation");
}
let w = type_width(cx, e)?;
let dst = temp(cx, w);
call(
cx,
WideOp::Fill,
Some(dst),
None,
None,
[u64::from(w), wide_rt::FILL_ZERO, 0, 0],
);
let mut offset = u64::from(w);
for part in parts {
if crate::expr::is_zero_replication(&part) {
if let Some(inner) = part.replication_concat() {
release(lower_operand(cx, inner)?.ptr);
}
continue;
}
let v = lower_operand(cx, part)?;
let Some(next) = offset.checked_sub(u64::from(v.width)) else {
return loud(cx, "concatenation operands wider than the concatenation");
};
offset = next;
call(
cx,
WideOp::Insert,
Some(dst),
Some(v.ptr),
None,
[u64::from(v.width), u64::from(w), offset, 0],
);
release(v.ptr);
}
if offset != 0 {
return loud(
cx,
"internal: concatenation operand widths do not sum to the concatenation's width",
);
}
Ok(finish(cx, dst, w, type_signed(e)))
}
fn replication<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let count_expr = e
.replication_count()
.ok_or_else(|| LoweringError::unsupported_at("replication without a count", &cx.fn_sym))?;
let count = require_const_i64(
count_expr,
"replication count must be a compile-time constant",
&cx.fn_sym,
)?;
if count <= 0 {
return loud(cx, "replication with a non-positive count");
}
let unit = e
.replication_concat()
.ok_or_else(|| LoweringError::unsupported_at("replication without a body", &cx.fn_sym))?;
let w = type_width(cx, e)?;
let v = lower_operand(cx, unit)?;
if u64::from(v.width) * count as u64 != u64::from(w) {
return loud(
cx,
"internal: replication width does not match count times the body",
);
}
release(v.ptr);
let dst = temp(cx, w);
call(
cx,
WideOp::Repl,
Some(dst),
Some(v.ptr),
None,
[u64::from(w), v.info(), count as u64, 0],
);
Ok(finish(cx, dst, w, type_signed(e)))
}
fn plain_stream_type(t: &sv_lang::Type<'_>) -> bool {
t.is_integral()
&& !t.is_floating()
&& !t.is_unpacked_array()
&& !vj_sim::state::storage::is_unpacked_aggregate(t)
}
fn stream_native(inner: sv_lang::Expression<'_>) -> bool {
if inner.kind() == ExpressionKind::Streaming {
let streams = inner.streams();
!streams.is_empty()
&& streams
.iter()
.all(|s| s.with_expr().is_none() && stream_native(s.operand()))
} else {
inner.expr_type().is_some_and(|t| plain_stream_type(&t))
}
}
fn stream_image<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<HugeValue<'ctx>, LoweringError> {
if e.kind() != ExpressionKind::Streaming {
return lower_operand(cx, e);
}
let mut parts = Vec::new();
for s in e.streams() {
parts.push(stream_image(cx, s.operand())?);
}
let total: u32 = parts.iter().map(|p| p.width).sum();
let dst = temp(cx, total);
call(
cx,
WideOp::Fill,
Some(dst),
None,
None,
[u64::from(total), wide_rt::FILL_ZERO, 0, 0],
);
let mut offset = total;
for v in parts {
offset -= v.width;
call(
cx,
WideOp::Insert,
Some(dst),
Some(v.ptr),
None,
[u64::from(v.width), u64::from(total), u64::from(offset), 0],
);
release(v.ptr);
}
let image = HugeValue {
ptr: dst,
width: total,
signed: false,
};
match e.streaming_slice_size() {
Some(n) if n > 0 => Ok(reorder_slices(cx, image, n, false)),
_ => Ok(image),
}
}
fn reorder_slices<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: HugeValue<'ctx>,
n: u64,
inverse: bool,
) -> HugeValue<'ctx> {
let dst = temp(cx, v.width);
call(
cx,
WideOp::ReverseSlices,
Some(dst),
Some(v.ptr),
None,
[u64::from(v.width), v.info(), n, u64::from(inverse)],
);
release(v.ptr);
HugeValue {
ptr: dst,
width: v.width,
signed: v.signed,
}
}
fn store_stream<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
) -> Result<Option<()>, LoweringError> {
let streams = lhs.streams();
let operands: Vec<_> = streams.iter().map(|s| s.operand()).collect();
let plain = !operands.is_empty()
&& streams.iter().all(|s| s.with_expr().is_none())
&& operands.iter().all(|o| {
o.kind() != ExpressionKind::Streaming
&& o.expr_type().is_some_and(|t| plain_stream_type(&t))
});
let any_huge =
matches!(value, LoweredValue::Huge(_)) || operands.iter().any(|o| is_huge_expr(*o));
if !plain || !any_huge {
return Ok(None);
}
require_four_state(cx)?;
let src = to_huge(
cx,
value,
"the value unpacked into a stream target must be integral",
)?;
let widths = operands
.iter()
.map(|o| type_width(cx, *o))
.collect::<Result<Vec<_>, _>>()?;
let total: u32 = widths.iter().sum();
if src.width < total {
return loud(
cx,
&format!(
"a {}-bit source unpacked into a {total}-bit stream",
src.width
),
);
}
let lead = HugeValue {
ptr: extract_new(
cx,
src.ptr,
src.width,
total,
c64(cx, u64::from(src.width - total)),
None,
),
width: total,
signed: false,
};
release(src.ptr);
let lead = match lhs.streaming_slice_size() {
Some(n) if n > 0 => reorder_slices(cx, lead, n, true),
_ => lead,
};
let mut top = total;
for (operand, w) in operands.into_iter().zip(widths) {
top -= w;
let piece = extract_new(cx, lead.ptr, total, w, c64(cx, u64::from(top)), None);
let lv = finish(cx, piece, w, type_signed(operand));
crate::expr::store_to(cx, operand, lv)?;
discard(lv);
}
release(lead.ptr);
Ok(Some(()))
}
fn conversion<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operand = e
.conversion_operand()
.ok_or_else(|| LoweringError::unsupported_at("conversion without operand", &cx.fn_sym))?;
let (Some(to_ty), Some(from_ty)) = (e.expr_type(), operand.expr_type()) else {
return loud(cx, "conversion without types");
};
let to_w = to_ty.value_width();
let to_signed = to_ty.is_signed();
if to_ty.is_string() {
let s = crate::strings::to_string(cx, lower_expr_h(cx, operand)?)?;
return Ok(LoweredValue::Str(s));
}
if from_ty.is_string() {
let s = crate::strings::to_string(cx, lower_expr_h(cx, operand)?)?;
return Ok(from_string(cx, s, to_w, to_signed));
}
let kind = e
.conversion_kind()
.ok_or_else(|| LoweringError::unsupported_at("conversion without kind", &cx.fn_sym))?;
match crate::expr::classify_conversion_kind(kind)
.map_err(|msg| LoweringError::unsupported_at(&msg, &cx.fn_sym))?
{
crate::expr::ConversionAction::Value => {}
crate::expr::ConversionAction::Stream => {
let s = stream_image(cx, operand)?;
if s.width > to_w {
return loud(
cx,
&format!("a {}-bit stream into a {to_w}-bit target", s.width),
);
}
if s.width == to_w {
return Ok(LoweredValue::Huge(HugeValue {
ptr: s.ptr,
width: to_w,
signed: to_signed,
}));
}
let dst = temp(cx, to_w);
call(
cx,
WideOp::Fill,
Some(dst),
None,
None,
[u64::from(to_w), wide_rt::FILL_ZERO, 0, 0],
);
call(
cx,
WideOp::Insert,
Some(dst),
Some(s.ptr),
None,
[
u64::from(s.width),
u64::from(to_w),
u64::from(to_w - s.width),
0,
],
);
release(s.ptr);
return Ok(finish(cx, dst, to_w, to_signed));
}
crate::expr::ConversionAction::BitstreamCast => {
let v = stream_image(cx, operand)?;
if v.width != to_w {
return loud(
cx,
&format!("a bit-stream cast of {} bits to a {to_w}-bit type", v.width),
);
}
return Ok(LoweredValue::Huge(HugeValue {
ptr: v.ptr,
width: to_w,
signed: to_signed,
}));
}
}
if from_ty.is_floating() {
if !to_ty.is_integral() {
return loud(cx, "a conversion from a real to a non-integral type");
}
let rv = lower_expr_h(cx, operand)?.into_real(
"real-to-int conversion operand must itself lower to a real value",
&cx.fn_sym,
)?;
let f = crate::real::as_f64(cx, rv.llvm);
let bits =
cx.b.build_bit_cast(f, cx.ctx.i64_type(), "huge_real_bits")
.unwrap()
.into_int_value();
let dst = temp(cx, to_w);
emit(
cx,
WideOp::FromReal,
Some(dst),
None,
None,
[c64(cx, u64::from(to_w)), bits, c64(cx, 0), c64(cx, 0)],
);
return Ok(finish(cx, dst, to_w, to_signed));
}
if to_ty.is_floating() {
if !from_ty.is_integral() {
return loud(cx, "a conversion from a non-integral type to a real");
}
let v = lower_operand(cx, operand)?;
let mant = if to_ty.bit_width() == 32 { 24 } else { 53 };
let bits = call(
cx,
WideOp::ToReal,
None,
Some(v.ptr),
None,
[0, v.info(), mant, 0],
);
release(v.ptr);
let f =
cx.b.build_bit_cast(bits, cx.ctx.f64_type(), "huge_real")
.unwrap()
.into_float_value();
return Ok(LoweredValue::Real(crate::value::RealValue { llvm: f }));
}
let v = lower_operand(cx, operand)?;
let flatten = from_ty.is_four_state() && !to_ty.is_four_state();
if !flatten && to_w == v.width {
return Ok(LoweredValue::Huge(HugeValue {
ptr: v.ptr,
width: to_w,
signed: to_signed,
}));
}
let from_info = wide_rt::info(v.width, from_ty.is_signed());
release(v.ptr);
let dst = temp(cx, to_w);
call(
cx,
if flatten {
WideOp::Flatten
} else {
WideOp::Resize
},
Some(dst),
Some(v.ptr),
None,
[u64::from(to_w), from_info, 0, 0],
);
Ok(finish(cx, dst, to_w, to_signed))
}
pub(crate) fn from_string<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
s: crate::strings::StrValue<'ctx>,
width: u32,
signed: bool,
) -> LoweredValue<'ctx> {
let dst = temp(cx, width);
emit(
cx,
WideOp::FromStr,
Some(dst),
None,
None,
[c64(cx, u64::from(width)), s.llvm, c64(cx, 0), c64(cx, 0)],
);
finish(cx, dst, width, signed)
}
pub(crate) fn to_string<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: HugeValue<'ctx>,
) -> crate::strings::StrValue<'ctx> {
let id = call(
cx,
WideOp::ToStr,
None,
Some(h.ptr),
None,
[0, h.info(), 0, 0],
);
release(h.ptr);
crate::strings::StrValue { llvm: id }
}
fn lvalue_reference<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let Some(LoweredValue::Huge(h)) = cx.lvalue_ref.get() else {
return loud(
cx,
"internal: a compound-assignment reference of a huge type outside a huge assignment",
);
};
let dst = temp(cx, h.width);
call(
cx,
WideOp::Resize,
Some(dst),
Some(h.ptr),
None,
[u64::from(h.width), h.info(), 0, 0],
);
Ok(LoweredValue::Huge(HugeValue {
ptr: dst,
width: h.width,
signed: h.signed,
}))
}
#[derive(Clone, Copy)]
struct Level<'ctx> {
start: IntValue<'ctx>,
bad: Option<IntValue<'ctx>>,
width: u32,
parent_width: u32,
exact: bool,
}
#[derive(Clone, Copy)]
struct WriteWindow<'ctx> {
start: IntValue<'ctx>,
width: u64,
bad: Option<IntValue<'ctx>>,
}
fn const_i64<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, v: i64) -> IntValue<'ctx> {
cx.ctx.i64_type().const_int(v as u64, true)
}
fn index_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
what: &str,
) -> Result<PackedValue<'ctx>, LoweringError> {
let lv = lower_expr_h(cx, e)?;
let LoweredValue::Huge(h) = lv else {
return lv.into_packed(what, &cx.fn_sym);
};
let r = call(
cx,
WideOp::Index,
None,
Some(h.ptr),
None,
[0, h.info(), 0, 0],
);
release(h.ptr);
let i64t = cx.ctx.i64_type();
let unknown =
cx.b.build_int_compare(
IntPredicate::EQ,
r,
i64t.const_int(i64::MIN as u64, true),
"hidx_x",
)
.unwrap();
let val =
cx.b.build_select(unknown, i64t.const_zero(), r, "hidx_val")
.unwrap()
.into_int_value();
let unk =
cx.b.build_select(
unknown,
i64t.const_all_ones(),
i64t.const_zero(),
"hidx_unk",
)
.unwrap()
.into_int_value();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(op.pack(val, unk, 64, ValueRepr::FourState, true))
}
fn select_level<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
geom: SelectGeom,
parent_width: u32,
) -> Result<Level<'ctx>, LoweringError> {
use crate::expr::{RangeSelectionKindClass, classify_range_selection_kind};
match e.kind() {
ExpressionKind::ElementSelect => {
let idx_expr = e.selector().ok_or_else(|| {
LoweringError::unsupported_at("bit-select missing index", &cx.fn_sym)
})?;
let idx = index_value(cx, idx_expr, "a bit-select index must be an integral value")?;
dynamic_level(cx, idx, 0, 1, geom, parent_width)
}
ExpressionKind::RangeSelect => {
let kind_ord = e.range_selection_kind().ok_or_else(|| {
LoweringError::unsupported_at(
"part-select missing range_selection_kind",
&cx.fn_sym,
)
})?;
let kind = classify_range_selection_kind(kind_ord)
.map_err(|msg| LoweringError::unsupported_at(&msg, &cx.fn_sym))?;
let (Some(left), Some(right)) = (e.range_left(), e.range_right()) else {
return loud(cx, "part-select missing a bound");
};
match kind {
RangeSelectionKindClass::Simple => {
let l = require_const_i64(
left,
"simple part-select left bound must be a compile-time constant",
&cx.fn_sym,
)?;
let r = require_const_i64(
right,
"simple part-select right bound must be a compile-time constant",
&cx.fn_sym,
)?;
let (pl, pr) = (geom.pos(l), geom.pos(r));
let (lo_e, hi_e) = (pl.min(pr), pl.max(pr));
let count = hi_e - lo_e + 1;
let width = checked_window_width(cx, count, geom, parent_width)?;
let in_range = lo_e >= 0 && hi_e < geom.elems();
let fully_out = hi_e < 0 || lo_e >= geom.elems();
let ew = i64::from(geom.elem_width);
let start = if fully_out { 0 } else { lo_e * ew };
Ok(Level {
start: const_i64(cx, start),
bad: fully_out.then(|| cx.ctx.bool_type().const_int(1, false)),
width,
parent_width,
exact: in_range,
})
}
RangeSelectionKindClass::IndexedUp | RangeSelectionKindClass::IndexedDown => {
let count = require_const_i64(
right,
"indexed part-select width must be a compile-time constant",
&cx.fn_sym,
)?;
if count <= 0 {
return loud(cx, "indexed part-select with a non-positive width");
}
checked_window_width(cx, count, geom, parent_width)?;
let idx = index_value(
cx,
left,
"an indexed part-select base must be an integral value",
)?;
let up = kind == RangeSelectionKindClass::IndexedUp;
let low_delta = match (up, geom.descending()) {
(true, true) | (false, false) => 0,
(true, false) => count - 1,
(false, true) => -(count - 1),
};
dynamic_level(cx, idx, low_delta, count, geom, parent_width)
}
}
}
other => Err(LoweringError::unsupported_at(
&format!("internal: select_level on a {other:?} expression"),
&cx.fn_sym,
)),
}
}
fn dynamic_level<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
idx: PackedValue<'ctx>,
low_delta: i64,
count: i64,
geom: SelectGeom,
parent_width: u32,
) -> Result<Level<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let cw = idx.width.max(64) + 2;
let cty = op.i_n(cw);
let val = op.lane_val(idx);
let ext = if idx.width == cw {
val
} else if idx.signed {
cx.b.build_int_s_extend(val, cty, "hsel_idx_sext").unwrap()
} else {
cx.b.build_int_z_extend(val, cty, "hsel_idx_zext").unwrap()
};
let c = |v: i64| cty.const_int(v as u64, true);
let low =
cx.b.build_int_add(ext, c(low_delta), "hsel_low_index")
.unwrap();
let pos = if geom.descending() {
cx.b.build_int_sub(low, c(geom.right), "hsel_pos").unwrap()
} else {
cx.b.build_int_sub(c(geom.right), low, "hsel_pos").unwrap()
};
let cmp =
|p: IntPredicate, v: i64, name: &str| cx.b.build_int_compare(p, pos, c(v), name).unwrap();
let fully_out = if count > 1 {
let before = cmp(IntPredicate::SLT, 1 - count, "hsel_before");
let past = cmp(IntPredicate::SGE, geom.elems(), "hsel_past");
cx.b.build_or(before, past, "hsel_fully_out").unwrap()
} else {
let below = cmp(IntPredicate::SLT, 0, "hsel_below");
let above = cmp(IntPredicate::SGT, geom.elems() - 1, "hsel_above");
cx.b.build_or(below, above, "hsel_oob").unwrap()
};
let unk = op.lane_unk(idx);
let unknown =
cx.b.build_int_compare(
IntPredicate::NE,
unk,
unk.get_type().const_zero(),
"hsel_idx_unknown",
)
.unwrap();
let bad = cx.b.build_or(fully_out, unknown, "hsel_bad").unwrap();
let ew = c(i64::from(geom.elem_width));
let start = cx.b.build_int_mul(pos, ew, "hsel_start_bits").unwrap();
let start =
cx.b.build_select(bad, c(0), start, "hsel_start_safe")
.unwrap()
.into_int_value();
let start =
cx.b.build_int_cast_sign_flag(start, cx.ctx.i64_type(), true, "hsel_start64")
.unwrap();
Ok(Level {
start,
bad: Some(bad),
width: (count as u32) * geom.elem_width,
parent_width,
exact: count == 1,
})
}
fn member_level<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
base: sv_lang::Expression<'d>,
parent_width: u32,
) -> Result<Level<'ctx>, LoweringError> {
let member = e.member_symbol().ok_or_else(|| {
LoweringError::unsupported_at("member access without a member", &cx.fn_sym)
})?;
let Some((off, fw)) = field_window(base.expr_type(), &member)
.filter(|(off, fw)| *fw > 0 && fw + off <= u64::from(parent_width))
else {
return loud(
cx,
"a struct/union field of a vector wider than 65535 bits that is not a slice of its \
base's stored bits",
);
};
Ok(Level {
start: const_i64(cx, off as i64),
bad: None,
width: fw as u32,
parent_width,
exact: true,
})
}
fn resolve<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<(sv_lang::Expression<'d>, Vec<Level<'ctx>>), LoweringError> {
let Some(base) = chain_base(e) else {
return Ok((e, Vec::new()));
};
let (root, mut levels) = resolve(cx, base)?;
let parent_width = type_width(cx, base)?;
let level = if e.kind() == ExpressionKind::MemberAccess {
member_level(cx, e, base, parent_width)?
} else {
let geom = geom_of(base.expr_type(), parent_width);
select_level(cx, e, geom, parent_width)?
};
levels.push(level);
Ok((root, levels))
}
fn or_bad<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
a: Option<IntValue<'ctx>>,
b: Option<IntValue<'ctx>>,
) -> Option<IntValue<'ctx>> {
match (a, b) {
(Some(a), Some(b)) => Some(cx.b.build_or(a, b, "hbad").unwrap()),
(x, None) => x,
(None, y) => y,
}
}
fn bad_i64<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, bad: Option<IntValue<'ctx>>) -> IntValue<'ctx> {
match bad {
Some(b) => {
cx.b.build_int_z_extend(b, cx.ctx.i64_type(), "hbad64")
.unwrap()
}
None => cx.ctx.i64_type().const_zero(),
}
}
fn fold<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
levels: &[Level<'ctx>],
) -> (IntValue<'ctx>, Option<IntValue<'ctx>>) {
let mut start = const_i64(cx, 0);
let mut bad = None;
for l in levels {
start = cx.b.build_int_add(start, l.start, "hstart").unwrap();
bad = or_bad(cx, bad, l.bad);
}
(start, bad)
}
fn extract_new<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
src: PointerValue<'ctx>,
src_width: u32,
width: u32,
start: IntValue<'ctx>,
bad: Option<IntValue<'ctx>>,
) -> PointerValue<'ctx> {
let dst = temp(cx, width);
emit(
cx,
WideOp::Extract,
Some(dst),
Some(src),
None,
[
c64(cx, u64::from(width)),
c64(cx, wide_rt::info(src_width, false)),
start,
bad_i64(cx, bad),
],
);
dst
}
fn insert_into<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
dst: PointerValue<'ctx>,
dst_width: u32,
src: PointerValue<'ctx>,
src_width: u32,
start: IntValue<'ctx>,
bad: Option<IntValue<'ctx>>,
) {
emit(
cx,
WideOp::Insert,
Some(dst),
Some(src),
None,
[
c64(cx, u64::from(src_width)),
c64(cx, u64::from(dst_width)),
start,
bad_i64(cx, bad),
],
);
}
fn read_chain<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let (root, levels) = resolve(cx, e)?;
let Some(last) = levels.last() else {
if is_huge_expr(e) && crate::classes::is_class_field_lvalue(e) {
return read_class_prop(cx, e);
}
if mem_elem_root(cx, e) {
return read_mem_elem(cx, e);
}
return fall_back(cx, e);
};
let width = last.width;
if type_width(cx, e)? != width {
return loud(
cx,
"internal: a select's type width differs from its resolved window",
);
}
let mut cur = lower_operand(cx, root)?;
let mut start = const_i64(cx, 0);
let mut bad = None;
for (i, level) in levels.iter().enumerate() {
start = cx.b.build_int_add(start, level.start, "hstart").unwrap();
bad = or_bad(cx, bad, level.bad);
if i + 1 == levels.len() || !levels[i + 1].exact {
release(cur.ptr);
let dst = extract_new(cx, cur.ptr, cur.width, level.width, start, bad);
cur = HugeValue {
ptr: dst,
width: level.width,
signed: false,
};
start = const_i64(cx, 0);
bad = None;
}
}
let out = finish(cx, cur.ptr, width, type_signed(e));
Ok(match out {
LoweredValue::Packed(v) => {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
LoweredValue::Packed(crate::select::two_state_result(&op, e, v))
}
other => other,
})
}
fn root_shadow<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
root: sv_lang::Expression<'d>,
) -> Result<(PointerValue<'ctx>, u32), LoweringError> {
if !vj_sim::symid::is_value_name(root.kind()) {
return loud(
cx,
"an assignment to a vector wider than 65535 bits through something other than a \
named variable",
);
}
let sym = root
.referenced_symbol()
.ok_or_else(|| LoweringError::unsupported_at("lvalue without a symbol", &cx.fn_sym))?;
match cx.locals.get(&sym.uid()) {
Some(&(ptr, width, _)) if is_huge_width(width) => Ok((ptr, width)),
_ => Err(LoweringError::unsupported_at(
"an assignment to a vector wider than 65535 bits that is not a module signal of \
this process (a procedural, function or class-property variable of that width)",
&sym,
)),
}
}
fn write_levels<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
dst: PointerValue<'ctx>,
dst_width: u32,
levels: &[Level<'ctx>],
v: HugeValue<'ctx>,
) -> Result<WriteWindow<'ctx>, LoweringError> {
let Some(k) = levels.iter().position(|l| !l.exact) else {
let (start, bad) = fold(cx, levels);
insert_into(cx, dst, dst_width, v.ptr, v.width, start, bad);
return Ok(WriteWindow {
start,
width: u64::from(v.width),
bad,
});
};
let l = levels[k];
let (prefix_start, prefix_bad) = fold(cx, &levels[..k]);
let window = if k == 0 {
WriteWindow {
start: l.start,
width: u64::from(l.width),
bad: l.bad,
}
} else {
WriteWindow {
start: prefix_start,
width: u64::from(l.parent_width),
bad: prefix_bad,
}
};
let parent = if k == 0 {
dst
} else {
extract_new(cx, dst, dst_width, l.parent_width, prefix_start, prefix_bad)
};
if k + 1 == levels.len() {
insert_into(cx, parent, l.parent_width, v.ptr, v.width, l.start, l.bad);
} else {
let q = extract_new(cx, parent, l.parent_width, l.width, l.start, l.bad);
write_levels(cx, q, l.width, &levels[k + 1..], v)?;
insert_into(cx, parent, l.parent_width, q, l.width, l.start, l.bad);
release(q);
}
if k > 0 {
insert_into(
cx,
dst,
dst_width,
parent,
l.parent_width,
prefix_start,
prefix_bad,
);
release(parent);
}
Ok(window)
}
fn write_chain<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
v: HugeValue<'ctx>,
) -> Result<(PointerValue<'ctx>, u32), LoweringError> {
let (root, levels) = resolve(cx, lhs)?;
let (shadow, width) = root_shadow(cx, root)?;
match levels.last() {
None => {
call(
cx,
WideOp::Resize,
Some(shadow),
Some(v.ptr),
None,
[u64::from(width), v.info(), 0, 0],
);
mark_window(cx, shadow, None);
}
Some(last) => {
if last.width != v.width {
return loud(
cx,
"internal: the value assigned to a select of a huge vector is not as wide \
as the select",
);
}
let window = write_levels(cx, shadow, width, &levels, v)?;
mark_window(cx, shadow, Some(window));
}
}
Ok((shadow, width))
}
fn mark_window<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
shadow: PointerValue<'ctx>,
window: Option<WriteWindow<'ctx>>,
) {
let Some(wt) = cx.write_through.as_ref() else {
return;
};
let Some(&(slot, width, _)) = wt.signals.get(&shadow) else {
return;
};
let i64t = cx.ctx.i64_type();
let (start, len) = match window {
None => (i64t.const_zero(), i64t.const_int(u64::from(width), false)),
Some(w) => {
let len = i64t.const_int(w.width, false);
let len = match w.bad {
Some(bad) => {
cx.b.build_select(bad, i64t.const_zero(), len, "hmark_len")
.unwrap()
.into_int_value()
}
None => len,
};
(w.start, len)
}
};
rt_mark(cx, wt.handle, slot, width, start, len);
}
fn assignment<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if e.is_nonblocking() == Some(true) {
if let (Some(lhs), Some(rhs)) = (e.left(), e.right())
&& class_prop_root(lhs)
&& !huge_local_root(cx, lhs)
{
let delay = crate::process_lower::nba_intra_delay(cx, e)?
.unwrap_or_else(|| cx.ctx.i64_type().const_zero());
let rhs = crate::collections::assigned_value(cx, lhs, rhs)?;
lower_nba_class_prop(cx, lhs, rhs, delay)?;
let z = cx.ctx.bool_type().const_zero();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
return Ok(LoweredValue::Packed(op.pack(z, z, 1, cx.repr, false)));
}
return loud(
cx,
"a nonblocking assignment to a vector wider than 65535 bits outside a process \
statement",
);
}
let (Some(lhs), Some(rhs)) = (e.left(), e.right()) else {
return loud(cx, "assignment without both sides");
};
let rhs_value = if e.is_compound_assignment() == Some(true) {
let current = lower_expr_h(cx, lhs)?;
let saved = cx.lvalue_ref.replace(Some(current));
let r = lower_expr_h(cx, rhs);
cx.lvalue_ref.set(saved);
if let LoweredValue::Huge(h) = current {
release(h.ptr);
}
r?
} else {
lower_expr_h(cx, rhs)?
};
let v = to_huge(
cx,
rhs_value,
"the value assigned to a vector wider than 65535 bits must be integral",
)?;
if class_prop_root(lhs) && !huge_local_root(cx, lhs) {
write_class_prop(cx, lhs, v)?;
return Ok(finish(cx, v.ptr, v.width, v.signed));
}
if mem_elem_root(cx, lhs) {
write_mem_elem(cx, lhs, v)?;
return Ok(finish(cx, v.ptr, v.width, v.signed));
}
let (shadow, width) = write_chain(cx, lhs, v)?;
write_through_commit(cx, shadow);
if chain_base(lhs).is_none() {
release(v.ptr);
let signed = cx
.locals
.values()
.find(|(p, _, _)| *p == shadow)
.is_some_and(|(_, _, s)| *s);
return Ok(LoweredValue::Huge(HugeValue {
ptr: shadow,
width,
signed,
}));
}
Ok(finish(cx, v.ptr, v.width, v.signed))
}
pub(crate) fn try_store<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
) -> Result<Option<()>, LoweringError> {
if lhs.kind() == ExpressionKind::Streaming {
return store_stream(cx, lhs, value);
}
if class_prop_root(lhs) && !huge_local_root(cx, lhs) {
require_four_state(cx)?;
let v = to_huge(
cx,
value,
"the value stored to a vector wider than the huge threshold must be integral",
)?;
write_class_prop(cx, lhs, v)?;
release(v.ptr);
return Ok(Some(()));
}
if mem_elem_root(cx, lhs) {
require_four_state(cx)?;
let v = to_huge(
cx,
value,
"the value stored to a vector wider than the huge threshold must be integral",
)?;
write_mem_elem(cx, lhs, v)?;
release(v.ptr);
return Ok(Some(()));
}
if !rooted_in_huge(lhs) || !huge_local_root(cx, lhs) {
return Ok(None);
}
require_four_state(cx)?;
let v = to_huge(
cx,
value,
"the value stored to a vector wider than the huge threshold must be integral",
)?;
let (shadow, _) = write_chain(cx, lhs, v)?;
write_through_commit(cx, shadow);
release(v.ptr);
Ok(Some(()))
}
pub(crate) fn try_store_symbol<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
sym: &sv_lang::Symbol<'_>,
value: LoweredValue<'ctx>,
) -> Result<bool, LoweringError> {
let Some(&(shadow, width, _)) = cx.locals.get(&sym.uid()) else {
return Ok(false);
};
if !is_huge_width(width) {
return Ok(false);
}
require_four_state(cx)?;
let v = to_huge(
cx,
value,
"the value stored to a vector wider than 65535 bits must be integral",
)?;
call(
cx,
WideOp::Resize,
Some(shadow),
Some(v.ptr),
None,
[u64::from(width), v.info(), 0, 0],
);
write_through_store(cx, shadow);
release(v.ptr);
Ok(true)
}
pub(crate) fn discard(lv: LoweredValue<'_>) {
if let LoweredValue::Huge(h) = lv {
release(h.ptr);
}
}
pub(crate) fn push_planes<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: HugeValue<'ctx>,
aval: PointerValue<'ctx>,
bval: PointerValue<'ctx>,
) {
call_wide(
cx,
WideOp::ToPlanes,
aval,
h.ptr,
bval,
[u64::from(h.width), 0, 0, 0],
);
}
pub(crate) fn lower_nba<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
signals: &std::collections::HashMap<vj_sim::symid::SymbolId, crate::process_lower::SignalSlot>,
lhs: sv_lang::Expression<'d>,
rhs: LoweredValue<'ctx>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
require_four_state(cx)?;
let (root, levels) = resolve(cx, lhs)?;
if !vj_sim::symid::is_value_name(root.kind()) {
return loud(
cx,
"a nonblocking assignment to a vector wider than 65535 bits through something \
other than a named signal",
);
}
let sym = root
.referenced_symbol()
.ok_or_else(|| LoweringError::unsupported_at("lvalue without a symbol", &cx.fn_sym))?;
let info = signals.get(&sym.uid()).copied().ok_or_else(|| {
LoweringError::unsupported_at(
"a nonblocking assignment to a process-local variable (only a module-level signal \
has an NBA staging target)",
&sym,
)
})?;
let slot = cx
.specify_dest
.get(&sym.uid())
.map_or(info.slot, |r| r.slot);
let w = info.width;
if !is_huge_width(w) {
return loud(cx, "internal: a narrow signal reached the huge NBA staging");
}
let v = to_huge(
cx,
rhs,
"the value assigned to a vector wider than 65535 bits must be integral",
)?;
if let Some(last) = levels.last()
&& (levels.len() == 1 || levels.iter().all(|l| l.exact))
{
if last.width != v.width {
return loud(
cx,
"internal: the value assigned to a select of a huge vector is not as wide \
as the select",
);
}
let (start, bad) = fold(cx, &levels);
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let done_bb = cx.ctx.append_basic_block(func, "hnba_done");
if let Some(bad) = bad {
let stage_bb = cx.ctx.append_basic_block(func, "hnba_stage");
cx.b.build_conditional_branch(bad, done_bb, stage_bb)
.unwrap();
cx.b.position_at_end(stage_bb);
}
rt_nba_window(cx, handle, slot, w, start, last.width, v.ptr, delay);
release(v.ptr);
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
return Ok(());
}
let image = temp(cx, w);
let mask = temp(cx, w);
let zero = |p: PointerValue<'ctx>| {
call(
cx,
WideOp::Fill,
Some(p),
None,
None,
[u64::from(w), wide_rt::FILL_ZERO, 0, 0],
);
};
zero(image);
zero(mask);
let mut bad = None;
match levels.last() {
None => {
call(
cx,
WideOp::Resize,
Some(image),
Some(v.ptr),
None,
[u64::from(w), v.info(), 0, 0],
);
call(
cx,
WideOp::Fill,
Some(mask),
None,
None,
[u64::from(w), wide_rt::FILL_ONE, 0, 0],
);
}
Some(last) => {
if last.width != v.width {
return loud(
cx,
"internal: the value assigned to a select of a huge vector is not as wide \
as the select",
);
}
for l in &levels {
bad = or_bad(cx, bad, l.bad);
}
write_levels(cx, image, w, &levels, v)?;
let ones = temp(cx, last.width);
call(
cx,
WideOp::Fill,
Some(ones),
None,
None,
[u64::from(last.width), wide_rt::FILL_ONE, 0, 0],
);
write_levels(
cx,
mask,
w,
&levels,
HugeValue {
ptr: ones,
width: last.width,
signed: false,
},
)?;
release(ones);
}
}
release(v.ptr);
stage_masked_image(cx, handle, slot, None, w, image, mask, bad, delay);
Ok(())
}
pub(crate) fn lower_nba_mem_elem<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
lhs: sv_lang::Expression<'d>,
rhs: LoweredValue<'ctx>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
require_four_state(cx)?;
let (root, levels) = resolve(cx, lhs)?;
let (r, info) = mem_row(cx, root)?;
let w = info.elem_width;
let v = to_huge(
cx,
rhs,
"the value assigned to a vector wider than 65535 bits must be integral",
)?;
let image = temp(cx, w);
let mask = temp(cx, w);
let fill = |p: PointerValue<'ctx>, kind: u64| {
call(
cx,
WideOp::Fill,
Some(p),
None,
None,
[u64::from(w), kind, 0, 0],
);
};
fill(image, wide_rt::FILL_ZERO);
let mut bad = if r.valid.get_zero_extended_constant() == Some(1) {
None
} else {
Some(cx.b.build_not(r.valid, "hmem_oob").unwrap())
};
match levels.last() {
None => {
call(
cx,
WideOp::Resize,
Some(image),
Some(v.ptr),
None,
[u64::from(w), v.info(), 0, 0],
);
fill(mask, wide_rt::FILL_ONE);
}
Some(last) => {
if last.width != v.width {
return loud(
cx,
"internal: the value assigned to a select of a huge vector is not as wide \
as the select",
);
}
fill(mask, wide_rt::FILL_ZERO);
for l in &levels {
bad = or_bad(cx, bad, l.bad);
}
write_levels(cx, image, w, &levels, v)?;
let ones = temp(cx, last.width);
call(
cx,
WideOp::Fill,
Some(ones),
None,
None,
[u64::from(last.width), wide_rt::FILL_ONE, 0, 0],
);
write_levels(
cx,
mask,
w,
&levels,
HugeValue {
ptr: ones,
width: last.width,
signed: false,
},
)?;
release(ones);
}
}
release(v.ptr);
stage_masked_image(
cx,
handle,
info.slot,
Some(r.base),
w,
image,
mask,
bad,
delay,
);
Ok(())
}
pub(crate) fn lower_nba_class_prop<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
rhs: LoweredValue<'ctx>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
require_four_state(cx)?;
let (root, levels) = resolve(cx, lhs)?;
let Some((h, slot, ty, dead)) = crate::classes::class_field_loc(cx, root)? else {
return loud(cx, "internal: a class property lvalue with no location");
};
let w = ty.value_width();
let v = to_huge(
cx,
rhs,
"the value assigned to a vector wider than 65535 bits must be integral",
)?;
if let Some((name, at)) = dead {
let probe = crate::expr::entry_alloca(cx, cx.ctx.i64_type(), "hcnba_live");
let live = crate::classes::field_get_into(
cx,
h,
vj_sim::objects::FieldSlot {
offset: slot.offset,
words: 1,
},
probe,
);
crate::classes::report_dead_handle_access(cx, live, at, &name, "write")?;
}
let image = temp(cx, w);
let mask = temp(cx, w);
let fill = |p: PointerValue<'ctx>, width: u32, kind: u64| {
call(
cx,
WideOp::Fill,
Some(p),
None,
None,
[u64::from(width), kind, 0, 0],
);
};
fill(image, w, wide_rt::FILL_ZERO);
let mut bad: Option<IntValue<'ctx>> = None;
match levels.last() {
None => {
call(
cx,
WideOp::Resize,
Some(image),
Some(v.ptr),
None,
[u64::from(w), v.info(), 0, 0],
);
fill(mask, w, wide_rt::FILL_Z);
}
Some(last) => {
if last.width != v.width {
return loud(
cx,
"internal: the value assigned to a select of a huge vector is not as wide \
as the select",
);
}
fill(mask, w, wide_rt::FILL_ZERO);
for l in &levels {
bad = or_bad(cx, bad, l.bad);
}
write_levels(cx, image, w, &levels, v)?;
let ones = temp(cx, last.width);
fill(ones, last.width, wide_rt::FILL_Z);
write_levels(
cx,
mask,
w,
&levels,
HugeValue {
ptr: ones,
width: last.width,
signed: false,
},
)?;
release(ones);
}
}
release(v.ptr);
let i64t = cx.ctx.i64_type();
let words = slot.words as u32;
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let done_bb = cx.ctx.append_basic_block(func, "hcnba_done");
if let Some(bad) = bad {
let stage_bb = cx.ctx.append_basic_block(func, "hcnba_stage");
cx.b.build_conditional_branch(bad, done_bb, stage_bb)
.unwrap();
cx.b.position_at_end(stage_bb);
}
let buf = crate::expr::entry_alloca(cx, i64t.array_type(2 * words + 1), "hcnba_buf");
let bytes = i64t.const_int(u64::from(words) * 8, false);
cx.b.build_memcpy(buf, 8, image, 8, bytes).unwrap();
let (mask_ptr, delay_ptr) = unsafe {
(
cx.b.build_in_bounds_gep(
i64t,
buf,
&[i64t.const_int(u64::from(words), false)],
"hcnba_mask",
)
.unwrap(),
cx.b.build_in_bounds_gep(
i64t,
buf,
&[i64t.const_int(u64::from(2 * words), false)],
"hcnba_delay",
)
.unwrap(),
)
};
cx.b.build_memcpy(mask_ptr, 8, mask, 8, bytes).unwrap();
cx.b.build_store(delay_ptr, delay).unwrap();
crate::classes::obj_call(
cx,
vj_runtime::objects_rt::ObjOp::FieldNba,
h,
i64t.const_int(slot.offset as u64, false),
i64t.const_int(slot.words as u64, false),
buf,
);
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
release(image);
release(mask);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn stage_masked_image<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
slot: u32,
row: Option<IntValue<'ctx>>,
w: u32,
image: PointerValue<'ctx>,
mask: PointerValue<'ctx>,
bad: Option<IntValue<'ctx>>,
delay: IntValue<'ctx>,
) {
let nwords = w.div_ceil(64);
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let done_bb = cx.ctx.append_basic_block(func, "hnba_done");
if let Some(bad) = bad {
let stage_bb = cx.ctx.append_basic_block(func, "hnba_stage");
cx.b.build_conditional_branch(bad, done_bb, stage_bb)
.unwrap();
cx.b.position_at_end(stage_bb);
}
let val_arr = acquire(cx, nwords);
let unk_arr = acquire(cx, nwords);
let mask_arr = acquire(cx, nwords);
let scratch = acquire(cx, nwords);
call(
cx,
WideOp::ToValUnk,
Some(val_arr),
Some(image),
Some(unk_arr),
[u64::from(w), 0, 0, 0],
);
call(
cx,
WideOp::ToValUnk,
Some(mask_arr),
Some(mask),
Some(scratch),
[u64::from(w), 0, 0, 0],
);
let i32t = cx.ctx.i32_type();
cx.b.build_call(
crate::select::rt_nba_masked_fn(cx),
&[
handle.into(),
i32t.const_int(u64::from(slot), false).into(),
row.unwrap_or_else(|| cx.ctx.i64_type().const_zero()).into(),
i32t.const_int(u64::from(row.is_some()), false).into(),
val_arr.into(),
unk_arr.into(),
mask_arr.into(),
i32t.const_int(u64::from(nwords), false).into(),
i32t.const_int(u64::from(w), false).into(),
delay.into(),
],
"rt_nba_masked_call",
)
.unwrap();
for p in [val_arr, unk_arr, mask_arr, scratch, image, mask] {
release(p);
}
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
}
pub(crate) fn is_image_width(width: u32) -> bool {
width > WIDE_THRESHOLD
}
pub(crate) fn is_image_type(ty: &sv_lang::Type<'_>) -> bool {
ty.is_integral() && !ty.is_floating() && is_image_width(ty.value_width())
}
pub(crate) fn image_storage_type<'ctx>(
ctx: &'ctx inkwell::context::Context,
width: u32,
) -> inkwell::types::ArrayType<'ctx> {
ctx.i64_type().array_type(pk_words(width))
}
pub(crate) fn require_images(
repr: ValueRepr,
at: &sv_lang::Symbol<'_>,
) -> Result<(), LoweringError> {
if repr != ValueRepr::FourState {
return Err(LoweringError::unsupported_at(
"a vector wider than 65535 bits needs the four-state value representation",
at,
));
}
Ok(())
}
pub(crate) fn init_image<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ptr: PointerValue<'ctx>,
width: u32,
four_state: bool,
) -> Result<(), LoweringError> {
require_four_state(cx)?;
let kind = if four_state {
wide_rt::FILL_X
} else {
wide_rt::FILL_ZERO
};
call(
cx,
WideOp::Fill,
Some(ptr),
None,
None,
[u64::from(width), kind, 0, 0],
);
Ok(())
}
pub(crate) fn pass_by_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
actual: sv_lang::Expression<'d>,
width: u32,
) -> Result<PointerValue<'ctx>, LoweringError> {
require_four_state(cx)?;
let v = lower_operand(cx, actual)?;
let dst = temp(cx, width);
call(
cx,
WideOp::Resize,
Some(dst),
Some(v.ptr),
None,
[u64::from(width), v.info(), 0, 0],
);
release(v.ptr);
Ok(dst)
}
pub(crate) fn result_image<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
width: u32,
) -> Result<PointerValue<'ctx>, LoweringError> {
require_four_state(cx)?;
Ok(temp(cx, width))
}
pub(crate) fn store_expr_to_image<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
sym: &sv_lang::Symbol<'d>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let v = lower_expr_h(cx, e)?;
if try_store_symbol(cx, sym, v)? {
Ok(())
} else {
loud(cx, "internal: an image variable with no local slot")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pk_words_is_two_planes_rounded_up_to_words() {
assert_eq!(pk_words(1), 1);
assert_eq!(pk_words(32), 1);
assert_eq!(pk_words(33), 2);
assert_eq!(pk_words(70001), 2188);
assert_eq!(pk_words(82944), 2592);
}
#[test]
fn the_default_threshold_is_below_the_integer_ceiling() {
if std::env::var(THRESHOLD_ENV).is_err() {
assert_eq!(threshold(), DEFAULT_THRESHOLD);
assert!(!is_huge_width(DEFAULT_THRESHOLD));
assert!(is_huge_width(DEFAULT_THRESHOLD + 1));
}
assert!((64..=WIDE_THRESHOLD).contains(&DEFAULT_THRESHOLD));
}
#[test]
fn pool_names_survive_llvm_uniquing() {
assert_eq!(pool_words("hugetmp_busy_12_"), Some(12));
assert_eq!(pool_words("hugetmp_free_2188_3"), Some(2188));
assert_eq!(pool_words("other"), None);
}
}