use std::cell::RefCell;
use std::collections::HashMap;
use inkwell::AddressSpace;
use inkwell::module::Linkage;
use sv_lang::Expression;
use sv_lang::kinds::ExpressionKind;
pub use sv_sim_exec::vpi_data::{ARG_WORDS, ARITY_SUFFIX, ArgDesc, SiteDesc, SysTfDecl};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, entry_alloca, lower_expr};
use crate::ops::OpBuilder;
use crate::types::TypeWidth;
use crate::value::{RealValue, ValueRepr};
pub const SV_RT_VPI_SYSTF_SYMBOL: &str = "sv_rt_vpi_systf";
thread_local! {
static REGISTERED: RefCell<HashMap<String, SysTfDecl>> = RefCell::new(HashMap::new());
static SITES: RefCell<Vec<SiteDesc>> = const { RefCell::new(Vec::new()) };
}
pub fn set_registered(decls: Vec<SysTfDecl>) {
REGISTERED.with(|r| *r.borrow_mut() = decls.into_iter().map(|d| (d.name.clone(), d)).collect());
SITES.with(|s| s.borrow_mut().clear());
}
pub fn take_sites() -> Vec<SiteDesc> {
SITES.with(|s| std::mem::take(&mut *s.borrow_mut()))
}
pub fn is_registered(name: &str) -> bool {
registered(name).is_some()
}
fn registered(name: &str) -> Option<SysTfDecl> {
let base = name.split(ARITY_SUFFIX).next().unwrap_or(name);
REGISTERED.with(|r| r.borrow().get(base).cloned())
}
fn operand(mut e: Expression<'_>) -> Expression<'_> {
while e.kind() == ExpressionKind::Conversion {
match e.conversion_operand() {
Some(inner) => e = inner,
None => break,
}
}
e
}
fn describe(e: &Expression<'_>) -> ArgDesc {
use sv_lang::kinds::SymbolKind;
let kind = e.kind();
if kind == ExpressionKind::EmptyArgument {
return ArgDesc::Empty;
}
if kind == ExpressionKind::StringLiteral {
let text = e
.constant_value()
.and_then(|c| c.as_integer().map(literal_text))
.unwrap_or_default();
return ArgDesc::Str(text);
}
if [
ExpressionKind::NamedValue,
ExpressionKind::HierarchicalValue,
]
.contains(&kind)
&& let Some(s) = e.referenced_symbol()
&& [SymbolKind::Variable, SymbolKind::Net].contains(&s.kind())
{
return ArgDesc::Signal(s.hierarchical_path());
}
ArgDesc::Value
}
fn literal_text(i: &sv_lang::SVInt) -> String {
let w = i.bit_width() as usize;
let mut bytes = Vec::new();
for byte in (0..w.div_ceil(8)).rev() {
let mut v = 0u8;
for k in 0..8 {
if i.bit(byte * 8 + k).is_some_and(|b| b.logic_t_value() == 1) {
v |= 1 << k;
}
}
if v != 0 {
bytes.push(v);
}
}
String::from_utf8_lossy(&bytes).into_owned()
}
fn lower_simtime<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> Result<LoweredValue<'ctx>, LoweringError> {
let rt_fn = crate::expr::fetch_or_declare_sv_rt_time(cx.ctx, cx.module);
let ticks =
cx.b.build_call(rt_fn, &[], "rt_simtime_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let known = cx.ctx.i64_type().const_zero();
Ok(LoweredValue::Packed(
op.pack(ticks, known, 64, cx.repr, false),
))
}
fn lower_type_query<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
e: Expression<'_>,
name: &str,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let arg = e
.children()
.into_iter()
.find_map(|n| n.as_expression())
.map(operand);
if name == crate::frontend::SIZEOF_NAME
&& arg.is_some_and(|a| {
matches!(
a.kind(),
ExpressionKind::BinaryOp | ExpressionKind::UnaryOp | ExpressionKind::ConditionalOp
) && a.expr_type().is_some_and(|t| t.value_width() <= 32)
})
{
return Err(LoweringError::unsupported(
"$sizeof of an operator expression no wider than 32 bits",
name,
));
}
let ty = arg
.and_then(|a| a.expr_type())
.filter(|t| t.is_integral())
.ok_or_else(|| {
LoweringError::unsupported(&format!("{name} of a non-integral argument"), name)
})?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let (value, width, signed) = if name == crate::frontend::IS_SIGNED_NAME {
(u64::from(ty.is_signed()), 1, false)
} else {
(u64::from(ty.value_width()), 32, true)
};
let t = if width == 1 {
cx.ctx.bool_type()
} else {
cx.ctx.i32_type()
};
Ok(LoweredValue::Packed(op.pack(
t.const_int(value, false),
t.const_zero(),
width,
cx.repr,
signed,
)))
}
pub fn lower_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
name: &str,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
let Some(decl) = registered(name) else {
if name == crate::frontend::SIMTIME_NAME {
return lower_simtime(cx).map(Some);
}
if name == crate::frontend::IS_SIGNED_NAME || name == crate::frontend::SIZEOF_NAME {
return lower_type_query(cx, e, name).map(Some);
}
return Ok(None);
};
let i64t = cx.ctx.i64_type();
let i32t = cx.ctx.i32_type();
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let args: Vec<Expression<'d>> = e
.children()
.into_iter()
.filter_map(|n| n.as_expression())
.map(operand)
.collect();
let n = args.len().max(1);
let buf = entry_alloca(cx, i64t.array_type((n * ARG_WORDS) as u32), "systf_args");
let mut descs = Vec::with_capacity(args.len());
let store = |at: usize, v: inkwell::values::IntValue<'ctx>| {
let p = unsafe {
cx.b.build_gep(i64t, buf, &[i64t.const_int(at as u64, false)], "systf_w")
}
.unwrap();
cx.b.build_store(p, v).unwrap();
};
for (i, a) in args.iter().enumerate() {
let d = describe(a);
if d == ArgDesc::Value {
let (bits, unk, tag, width) = match lower_expr(cx, *a)? {
LoweredValue::Real(r) => {
let f = crate::real::as_f64(cx, r.llvm);
let bits =
cx.b.build_bit_cast(f, i64t, "systf_real")
.unwrap()
.into_int_value();
(bits, i64t.const_zero(), 2u64, 64u64)
}
LoweredValue::Str(s) => (s.llvm, i64t.const_zero(), 3, 64),
other => {
let pv = other.into_packed("a system task argument", &cx.fn_sym)?;
let (v, u) = (op.lane_val(pv), op.lane_unk(pv));
let fit = |x: inkwell::values::IntValue<'ctx>| match pv.width.cmp(&64) {
std::cmp::Ordering::Less => {
if pv.signed {
cx.b.build_int_s_extend(x, i64t, "systf_sx").unwrap()
} else {
cx.b.build_int_z_extend(x, i64t, "systf_zx").unwrap()
}
}
std::cmp::Ordering::Equal => x,
std::cmp::Ordering::Greater => {
cx.b.build_int_truncate(x, i64t, "systf_tr").unwrap()
}
};
let unk = match pv.width.cmp(&64) {
std::cmp::Ordering::Less => {
cx.b.build_int_z_extend(u, i64t, "systf_ux").unwrap()
}
std::cmp::Ordering::Equal => u,
std::cmp::Ordering::Greater => {
cx.b.build_int_truncate(u, i64t, "systf_ut").unwrap()
}
};
(
fit(v),
unk,
u64::from(pv.signed),
u64::from(pv.width.min(64)),
)
}
};
store(i * ARG_WORDS, bits);
store(i * ARG_WORDS + 1, unk);
store(i * ARG_WORDS + 2, i64t.const_int(tag, false));
store(i * ARG_WORDS + 3, i64t.const_int(width, false));
}
descs.push(d);
}
let range = {
let r = e.source_range();
(r.start.buffer, r.start.offset, r.end.offset)
};
let site = SITES.with(|s| {
let mut s = s.borrow_mut();
s.push(SiteDesc {
name: decl.name.clone(),
args: descs,
is_func: decl.is_func,
range,
text: sv_sim_sim::process::cfg::source_text_range(e.source_range()),
});
s.len() - 1
});
let n_out = sv_sim_exec::vpi_data::result_words(decl.sized);
let out = entry_alloca(cx, i64t.array_type(2 * n_out as u32 + 1), "systf_out");
let f = cx
.module
.get_function(SV_RT_VPI_SYSTF_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_VPI_SYSTF_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[i32t.into(), ptr.into(), i32t.into(), ptr.into()], false),
Some(Linkage::External),
)
});
cx.b.build_call(
f,
&[
i32t.const_int(site as u64, false).into(),
buf.into(),
i32t.const_int(args.len() as u64, false).into(),
out.into(),
],
"systf",
)
.unwrap();
crate::dpi::reload_shadows(cx);
let word = |k: u64| {
let p = unsafe {
cx.b.build_gep(i64t, out, &[i64t.const_int(k, false)], "systf_o")
}
.unwrap();
cx.b.build_load(i64t, p, "systf_ret")
.unwrap()
.into_int_value()
};
if decl.real {
let f =
cx.b.build_bit_cast(word(0), cx.ctx.f64_type(), "systf_rf")
.unwrap()
.into_float_value();
return Ok(Some(LoweredValue::Real(RealValue { llvm: f })));
}
let (width, signed) = decl.sized.unwrap_or_else(|| {
e.expr_type()
.filter(|t| t.is_integral() && t.value_width() <= 64)
.map_or((32, true), |t| (t.value_width(), t.is_signed()))
});
let plane = |k: usize, name| {
let wide = op.i_n(64 * n_out as u32);
let p = unsafe {
cx.b.build_gep(
i64t,
out,
&[i64t.const_int((k * n_out) as u64, false)],
"systf_p",
)
}
.unwrap();
let x = cx.b.build_load(wide, p, name).unwrap().into_int_value();
if width == 64 * n_out as u32 {
x
} else {
cx.b.build_int_truncate(x, op.i_n(width), name).unwrap()
}
};
let (v, u) = (plane(0, "systf_v"), plane(1, "systf_u"));
Ok(Some(LoweredValue::Packed(op.pack(
v,
u,
width,
if cx.repr == ValueRepr::FourState {
ValueRepr::FourState
} else {
ValueRepr::TwoState
},
signed,
))))
}