use inkwell::IntPredicate;
use inkwell::values::IntValue;
use sv_lang::KnownSystemName;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, lower_expr, lower_expr_h};
use crate::ops::OpBuilder;
use crate::types::TypeWidth;
use crate::value::{PackedValue, RealValue};
pub(crate) fn lower_bit_sysfunc<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: sv_lang::Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
if !is_bit_sysfunc_name(name) {
return None;
}
Some(lower(cx, name, e))
}
pub(crate) fn is_bit_sysfunc_name(name: KnownSystemName) -> bool {
use KnownSystemName as K;
matches!(
name,
K::Signed
| K::Unsigned
| K::Clog2
| K::CountBits
| K::CountOnes
| K::OneHot
| K::OneHot0
| K::IsUnknown
)
}
fn has_wildcard_compare(e: sv_lang::Expression<'_>) -> bool {
use sv_lang::BinaryOp;
(e.kind() == sv_lang::kinds::ExpressionKind::BinaryOp
&& matches!(
e.binary_op(),
Some(BinaryOp::WildcardEquality | BinaryOp::WildcardInequality)
))
|| e.operands().into_iter().any(has_wildcard_compare)
}
fn lower<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use KnownSystemName as K;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if !has_wildcard_compare(e)
&& let Some(iv) = e.constant_value().as_ref().and_then(|c| c.as_integer())
{
return crate::expr::lower_constant(cx, iv, Some(e));
}
let args = e.operands();
let arg0 = *args.first().ok_or_else(|| {
LoweringError::unsupported_at("system function with no argument", &cx.fn_sym)
})?;
let arg = lower_expr_h(cx, arg0)?;
if let LoweredValue::Huge(h) = arg {
return crate::huge::sysfunc(cx, name, e, h);
}
let x = arg.into_packed(
"a bit-level system function of a real or >128-bit value",
&cx.fn_sym,
)?;
let ret = e.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("system function call has no type", &cx.fn_sym)
})?;
let ret_width = ret.value_width();
let val = op.lane_val(x);
let unk = op.lane_unk(x);
let ty = op.i_n(x.width);
let known = |v: IntValue<'ctx>| {
let v = fit(cx, v, ret_width);
op.pack(
v,
op.i_n(ret_width).const_zero(),
ret_width,
cx.repr,
ret.is_signed(),
)
};
let popcount = |bits: IntValue<'ctx>| -> IntValue<'ctx> {
let f = inkwell::intrinsics::Intrinsic::find("llvm.ctpop")
.and_then(|i| i.get_declaration(cx.module, &[ty.into()]))
.expect("llvm.ctpop");
cx.b.build_call(f, &[bits.into()], "popcount")
.unwrap()
.try_as_basic_value()
.basic()
.expect("ctpop returns a value")
.into_int_value()
};
let ones = cx.b.build_and(val, op.not(unk), "ones").unwrap();
let v = match name {
K::Signed | K::Unsigned => PackedValue {
signed: name == K::Signed,
..x
},
K::CountOnes => known(popcount(ones)),
K::OneHot | K::OneHot0 => {
let n = popcount(ones);
let pred = if name == K::OneHot {
IntPredicate::EQ
} else {
IntPredicate::ULE
};
let bit =
cx.b.build_int_compare(pred, n, ty.const_int(1, false), "onehot")
.unwrap();
known(bit)
}
K::IsUnknown => {
let bit =
cx.b.build_int_compare(IntPredicate::NE, unk, ty.const_zero(), "isunknown")
.unwrap();
known(bit)
}
K::CountBits => {
let mut mask = ty.const_zero();
for c in args.iter().skip(1) {
let const_bit = c
.constant_value()
.as_ref()
.and_then(|cv| cv.as_integer())
.and_then(|i| i.bit(0));
let zeros = cx.b.build_and(op.not(val), op.not(unk), "zeros").unwrap();
let xs = cx.b.build_and(op.not(val), unk, "xs").unwrap();
let zs = cx.b.build_and(val, unk, "zs").unwrap();
let lanes = if let Some(bit) = const_bit {
if bit == sv_lang::Bit::One {
ones
} else if bit == sv_lang::Bit::Zero {
zeros
} else if bit == sv_lang::Bit::X {
xs
} else {
zs
}
} else {
let cv = lower_expr_h(cx, *c)?.into_packed(
"a $countbits control bit of a real or >128-bit value",
&cx.fn_sym,
)?;
let i1 = cx.ctx.bool_type();
let cval =
cx.b.build_int_truncate(op.lane_val(cv), i1, "cb_v")
.unwrap();
let cunk =
cx.b.build_int_truncate(op.lane_unk(cv), i1, "cb_u")
.unwrap();
let known_pick =
cx.b.build_select(cval, ones, zeros, "cb_k")
.unwrap()
.into_int_value();
let unk_pick =
cx.b.build_select(cval, zs, xs, "cb_x")
.unwrap()
.into_int_value();
cx.b.build_select(cunk, unk_pick, known_pick, "cb_lanes")
.unwrap()
.into_int_value()
};
mask = cx.b.build_or(mask, lanes, "countbits_mask").unwrap();
}
known(popcount(mask))
}
K::Clog2 => {
let f = inkwell::intrinsics::Intrinsic::find("llvm.ctlz")
.and_then(|i| i.get_declaration(cx.module, &[ty.into()]))
.expect("llvm.ctlz");
let one = ty.const_int(1, false);
let xm1 = cx.b.build_int_sub(val, one, "clog2_xm1").unwrap();
let clz =
cx.b.build_call(
f,
&[xm1.into(), cx.ctx.bool_type().const_zero().into()],
"clz",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("ctlz returns a value")
.into_int_value();
let bits =
cx.b.build_int_sub(ty.const_int(u64::from(x.width), false), clz, "clog2_bits")
.unwrap();
let small =
cx.b.build_int_compare(IntPredicate::ULE, val, one, "clog2_small")
.unwrap();
let r =
cx.b.build_select(small, ty.const_zero(), bits, "clog2")
.unwrap()
.into_int_value();
let any_unk =
cx.b.build_int_compare(IntPredicate::NE, unk, ty.const_zero(), "clog2_unk")
.unwrap();
let rw = op.i_n(ret_width);
let unk_lane =
cx.b.build_select(any_unk, rw.const_all_ones(), rw.const_zero(), "clog2_x")
.unwrap()
.into_int_value();
let r = fit(cx, r, ret_width);
let r =
cx.b.build_select(any_unk, rw.const_zero(), r, "clog2_v")
.unwrap()
.into_int_value();
op.pack(r, unk_lane, ret_width, cx.repr, ret.is_signed())
}
_ => unreachable!("filtered by lower_bit_sysfunc"),
};
Ok(LoweredValue::Packed(v))
}
pub(crate) fn lower_cast<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
lower_cast_with(cx, e, false)
}
pub(crate) fn lower_cast_stmt<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
lower_cast_with(cx, e, true)?;
Ok(())
}
fn lower_cast_with<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
task_form: bool,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let ops = e.operands();
let (Some(&dest), Some(&src)) = (ops.first(), ops.get(1)) else {
return Err(LoweringError::unsupported_at(
"$cast needs two arguments",
&cx.fn_sym,
));
};
let dest = if dest.kind() == sv_lang::kinds::ExpressionKind::Assignment {
dest.left().unwrap_or(dest)
} else {
dest
};
let dty = dest
.expr_type()
.ok_or_else(|| LoweringError::unsupported_at("$cast target has no type", &cx.fn_sym))?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let width = dty.value_width();
let sty_floating = src.expr_type().is_some_and(|t| t.is_floating());
let srcv = lower_expr(cx, src)?;
let srcv = match srcv {
LoweredValue::Real(r) if !dty.is_floating() && dty.is_integral() && width <= 64 => {
let fty = r.llvm.get_type();
let diff = cx.b.build_float_sub(r.llvm, r.llvm, "cast_vmv").unwrap();
let bad =
cx.b.build_float_compare(inkwell::FloatPredicate::UNO, diff, diff, "cast_bad")
.unwrap();
let safe =
cx.b.build_select(bad, fty.const_zero(), r.llvm, "cast_safe")
.unwrap()
.into_float_value();
let rounded = op.round_real_to_int(RealValue { llvm: safe }, width, dty.is_signed());
let wty = op.i_n(width);
let unk = if dty.is_four_state() {
cx.b.build_select(bad, wty.const_all_ones(), wty.const_zero(), "cast_unk")
.unwrap()
.into_int_value()
} else {
wty.const_zero()
};
LoweredValue::Packed(op.pack(rounded, unk, width, cx.repr, dty.is_signed()))
}
LoweredValue::Packed(pv) if dty.is_floating() && !sty_floating => {
match crate::real::packed_to_real(cx, pv) {
Some(r) => LoweredValue::Real(r),
None => LoweredValue::Packed(pv),
}
}
other => other,
};
let v = crate::real::to_storage(cx, srcv, width, "a >128-bit $cast")?;
let v = crate::select::fit_width(&op, v, width);
let v = PackedValue {
signed: dty.is_signed(),
..v
};
let bool_ty = cx.ctx.bool_type();
let is_class = dty.is_class();
let ok = if is_class {
let related = match src.expr_type() {
Some(sty) if sty.is_null() => true,
Some(sty) if sty.is_class() => {
dty.is_assignment_compatible(&sty)
|| dty.is_derived_from(&sty)
|| sty.class_is_interface()
|| dty.class_is_interface()
}
Some(_) | None => {
return Err(LoweringError::unsupported_at(
"$cast to a class from a non-class source",
&cx.fn_sym,
));
}
};
if !related {
bool_ty.const_zero()
} else {
let h = op.lane_val(v);
let is_null =
cx.b.build_int_compare(IntPredicate::EQ, h, h.get_type().const_zero(), "cast_null")
.unwrap();
let is_a = crate::classes::is_a_call(cx, h, dty);
cx.b.build_or(is_null, is_a, "cast_legal").unwrap()
}
} else if dty.is_enum() {
let mut any = bool_ty.const_zero();
for m in dty.enum_members() {
let cv = m.constant_value();
let iv = cv.as_ref().and_then(|c| c.as_integer()).ok_or_else(|| {
LoweringError::unsupported_at("an enum member with no constant value", &cx.fn_sym)
})?;
let mv = crate::expr::lower_constant(cx, iv, None)?
.into_packed("a wide enum member", &cx.fn_sym)?;
let mv = crate::select::fit_width(&op, mv, width);
let eq = crate::ops::is_definitely_true(cx.ctx, &cx.b, op.caseeq4(v, mv));
any = cx.b.build_or(any, eq, "cast_member").unwrap();
}
any
} else {
bool_ty.const_all_ones()
};
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let then_bb = cx.ctx.append_basic_block(f, "cast_ok");
let fail_bb = cx.ctx.append_basic_block(f, "cast_fail");
let done_bb = cx.ctx.append_basic_block(f, "cast_done");
cx.b.build_conditional_branch(ok, then_bb, fail_bb).unwrap();
cx.b.position_at_end(then_bb);
let stored = if dty.is_floating() {
LoweredValue::Real(crate::real::bits_to_real(cx, v))
} else {
LoweredValue::Packed(v)
};
let fresh_src = is_class && crate::collections::fresh(&src);
if is_class {
if !fresh_src {
crate::classes::retain_handle(cx, op.lane_val(v));
}
if sv_sim_sim::symid::is_value_name(dest.kind()) {
crate::collections::release_old(cx, dest)?;
crate::expr::store_to(cx, dest, stored)?;
} else {
crate::collections::store_releasing(cx, dest, stored)?;
}
} else {
crate::expr::store_to(cx, dest, stored)?;
}
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(fail_bb);
if fresh_src {
crate::classes::release_handle(cx, op.lane_val(v));
}
if task_form {
let what = if is_class {
format!(
"the source object is not an instance of class {}",
dty.as_symbol().name()
)
} else {
"the source value is not a legal value of the destination type".to_string()
};
crate::stmt_shared::fatal_if(
cx,
bool_ty.const_all_ones(),
e.source_range().start,
&format!("$cast failed: {what}"),
)?;
}
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
let i32t = cx.ctx.i32_type();
let r = cx.b.build_int_z_extend(ok, i32t, "cast_result").unwrap();
Ok(LoweredValue::Packed(op.pack(
r,
i32t.const_zero(),
32,
cx.repr,
true,
)))
}
pub(crate) fn lower_enum_method<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: sv_lang::Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
use KnownSystemName as K;
if !matches!(
name,
K::First | K::Last | K::Next | K::Prev | K::Num | K::Name
) {
return None;
}
let args = e.operands();
let this = *args.first()?;
let ty = this.expr_type()?;
if !ty.is_enum() {
return None;
}
Some(lower_enum(cx, name, e, this, ty, &args[1..]))
}
pub(crate) fn enum_pattern_text<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ty: &sv_lang::Type<'_>,
cur: PackedValue<'ctx>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
if !ty.is_enum() || ty.value_width() > 64 {
return Ok(None);
}
let fmt = crate::process_lower::build_c_string_global(cx.ctx, cx.module, "%p", "enum_p_fmt");
let mut text = crate::strings::sformat_with(cx, fmt, vec![LoweredValue::Packed(cur)])?.llvm;
for m in ty.enum_members() {
let cv = m.constant_value();
let iv = cv.as_ref().and_then(|c| c.as_integer()).ok_or_else(|| {
LoweringError::unsupported_at("an enum member with no constant value", &cx.fn_sym)
})?;
let v =
crate::expr::lower_constant(cx, iv, None)?.into_packed("a wide enum", &cx.fn_sym)?;
let hit =
cx.b.build_int_compare(IntPredicate::EQ, cur.llvm, v.llvm, "enum_p_hit")
.unwrap();
let lit = crate::strings::literal(cx, m.name().as_bytes()).llvm;
text =
cx.b.build_select(hit, lit, text, "enum_p_name")
.unwrap()
.into_int_value();
}
Ok(Some(LoweredValue::Str(crate::strings::StrValue {
llvm: text,
})))
}
fn lower_enum<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: sv_lang::Expression<'d>,
this: sv_lang::Expression<'d>,
ty: sv_lang::Type<'d>,
rest: &[sv_lang::Expression<'d>],
) -> Result<LoweredValue<'ctx>, LoweringError> {
use KnownSystemName as K;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if name != K::Name
&& let Some(iv) = e.constant_value().as_ref().and_then(|c| c.as_integer())
{
return crate::expr::lower_constant(cx, iv, Some(e));
}
let members: Vec<(String, PackedValue<'ctx>)> = ty
.enum_members()
.map(|m| {
let cv = m.constant_value();
let iv = cv.as_ref().and_then(|c| c.as_integer()).ok_or_else(|| {
LoweringError::unsupported_at("an enum member with no constant value", &cx.fn_sym)
})?;
let v = crate::expr::lower_constant(cx, iv, None)?;
Ok((
m.name().to_string(),
v.into_packed("a wide enum", &cx.fn_sym)?,
))
})
.collect::<Result<_, _>>()?;
let width = ty.value_width();
let signed = ty.is_signed();
let cur = lower_expr(cx, this)?.into_packed("an enum value", &cx.fn_sym)?;
let i64t = cx.ctx.i64_type();
let mut idx = i64t.const_all_ones();
for (i, (_, v)) in members.iter().enumerate().rev() {
let hit =
cx.b.build_int_compare(IntPredicate::EQ, cur.llvm, v.llvm, "enum_hit")
.unwrap();
idx =
cx.b.build_select(hit, i64t.const_int(i as u64, false), idx, "enum_idx")
.unwrap()
.into_int_value();
}
let is_member =
cx.b.build_int_compare(IntPredicate::SGE, idx, i64t.const_zero(), "enum_member")
.unwrap();
match name {
K::Name => {
let mut id = crate::strings::literal(cx, b"").llvm;
for (i, (n, _)) in members.iter().enumerate() {
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
idx,
i64t.const_int(i as u64, false),
"enum_name_hit",
)
.unwrap();
let lit = crate::strings::literal(cx, n.as_bytes()).llvm;
id =
cx.b.build_select(hit, lit, id, "enum_name")
.unwrap()
.into_int_value();
}
Ok(LoweredValue::Str(crate::strings::StrValue { llvm: id }))
}
K::Next | K::Prev => {
let n = members.len() as u64;
let step = match rest.first() {
Some(s) => {
let sv = lower_expr(cx, *s)?.into_packed("an enum step", &cx.fn_sym)?;
crate::expr::mem_row_index_i64(&op, cx, sv)
}
None => i64t.const_int(1, false),
};
let step =
cx.b.build_int_signed_rem(step, i64t.const_int(n, false), "enum_step")
.unwrap();
let moved = if name == K::Next {
cx.b.build_int_add(idx, step, "enum_fwd").unwrap()
} else {
cx.b.build_int_sub(idx, step, "enum_back").unwrap()
};
let nn = i64t.const_int(n, false);
let r = cx.b.build_int_signed_rem(moved, nn, "enum_r").unwrap();
let r = cx.b.build_int_add(r, nn, "enum_r_pos").unwrap();
let r = cx.b.build_int_signed_rem(r, nn, "enum_r_mod").unwrap();
let mut out = crate::arrays::default_value(cx, &ty);
for (i, (_, v)) in members.iter().enumerate() {
let hit =
cx.b.build_int_compare(
IntPredicate::EQ,
r,
i64t.const_int(i as u64, false),
"enum_pick",
)
.unwrap();
let pick =
cx.b.build_select(hit, v.llvm, out, "enum_val")
.unwrap()
.into_int_value();
out =
cx.b.build_select(is_member, pick, out, "enum_next")
.unwrap()
.into_int_value();
}
Ok(LoweredValue::Packed(PackedValue {
llvm: out,
width,
repr: cx.repr,
signed,
}))
}
_ => Err(LoweringError::unsupported_at(
"an enum first()/last()/num() that did not fold to a constant",
&cx.fn_sym,
)),
}
}
pub(crate) fn fit<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: IntValue<'ctx>,
width: u32,
) -> IntValue<'ctx> {
let ty = crate::types::int_type(cx.ctx, width);
let have = v.get_type().get_bit_width();
if have < width {
cx.b.build_int_z_extend(v, ty, "sysf_zext").unwrap()
} else if have > width {
cx.b.build_int_truncate(v, ty, "sysf_trunc").unwrap()
} else {
v
}
}