use inkwell::AddressSpace;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::types::StructType;
use inkwell::values::{BasicValue as _, FunctionValue, IntValue, PointerValue};
use sv_lang::KnownSystemName;
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use sv_sim_runtime::file_io::{
SV_RT_FCLOSE_SYMBOL, SV_RT_FDISPLAY_SYMBOL, SV_RT_FFLUSH_SYMBOL, SV_RT_FMONITOR_SYMBOL,
SV_RT_FSTROBE_SYMBOL, SV_RT_FWRITE_SYMBOL,
};
use sv_sim_runtime::fst::{SV_RT_DUMPFILE_SYMBOL, SV_RT_DUMPFLUSH_SYMBOL};
use sv_sim_runtime::rtarg::{
SV_RT_DISPLAY_SYMBOL, SV_RT_MONITOR_SYMBOL, SV_RT_STROBE_SYMBOL, SV_RT_WRITE_SYMBOL,
};
use sv_sim_runtime::sdf_annotate::SV_RT_SDF_ANNOTATE_SYMBOL;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, lower_expr};
use crate::types::int_type;
use crate::value::PackedValue;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DisplayFamily {
Display,
Write,
Strobe,
Monitor,
}
pub fn classify_display_family(name: KnownSystemName) -> Option<DisplayFamily> {
use KnownSystemName::{
Display, DisplayB, DisplayH, DisplayO, Monitor, MonitorB, MonitorH, MonitorO, Strobe,
StrobeB, StrobeH, StrobeO, Write, WriteB, WriteH, WriteO,
};
match name {
Display | DisplayB | DisplayO | DisplayH => Some(DisplayFamily::Display),
Write | WriteB | WriteO | WriteH => Some(DisplayFamily::Write),
Strobe | StrobeB | StrobeO | StrobeH => Some(DisplayFamily::Strobe),
Monitor | MonitorB | MonitorO | MonitorH => Some(DisplayFamily::Monitor),
_ => None,
}
}
pub fn display_family_symbol(f: DisplayFamily) -> &'static str {
match f {
DisplayFamily::Display => SV_RT_DISPLAY_SYMBOL,
DisplayFamily::Write => SV_RT_WRITE_SYMBOL,
DisplayFamily::Strobe => SV_RT_STROBE_SYMBOL,
DisplayFamily::Monitor => SV_RT_MONITOR_SYMBOL,
}
}
pub fn rtarg_llvm_type<'ctx>(ctx: &'ctx Context) -> StructType<'ctx> {
let i32_ty = ctx.i32_type();
let i8_ty = ctx.i8_type();
let i128_ty = ctx.i128_type();
let f64_ty = ctx.f64_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
ctx.struct_type(
&[
i32_ty.into(), i32_ty.into(), i8_ty.into(), i128_ty.into(), f64_ty.into(), ptr_ty.into(), ptr_ty.into(), i32_ty.into(), ptr_ty.into(), i32_ty.into(), ],
false,
)
}
fn zext_to_i128<'ctx>(ctx: &'ctx Context, b: &Builder<'ctx>, v: IntValue<'ctx>) -> IntValue<'ctx> {
if v.get_type().get_bit_width() == 128 {
v
} else {
b.build_int_z_extend(v, ctx.i128_type(), "rtarg_narrow_zext")
.unwrap()
}
}
pub(crate) fn store_narrow_rtarg<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rtarg_ty: StructType<'ctx>,
elem_ptr: PointerValue<'ctx>,
pv: PackedValue<'ctx>,
) {
let ctx = cx.ctx;
let b = &cx.b;
let i32_ty = ctx.i32_type();
let i8_ty = ctx.i8_type();
let f64_ty = ctx.f64_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let field = |idx: u32, name: &str| b.build_struct_gep(rtarg_ty, elem_ptr, idx, name).unwrap();
b.build_store(field(0, "rtarg_kind"), i32_ty.const_int(0, false))
.unwrap();
b.build_store(
field(1, "rtarg_width"),
i32_ty.const_int(u64::from(pv.width), false),
)
.unwrap();
b.build_store(
field(2, "rtarg_signed"),
i8_ty.const_int(u64::from(pv.signed), false),
)
.unwrap();
let narrow = zext_to_i128(ctx, b, pv.llvm);
b.build_store(field(3, "rtarg_narrow"), narrow).unwrap();
b.build_store(field(4, "rtarg_real"), f64_ty.const_float(0.0))
.unwrap();
b.build_store(field(5, "rtarg_wide_aval"), ptr_ty.const_null())
.unwrap();
b.build_store(field(6, "rtarg_wide_bval"), ptr_ty.const_null())
.unwrap();
b.build_store(field(7, "rtarg_wide_words"), i32_ty.const_int(0, false))
.unwrap();
b.build_store(field(8, "rtarg_str_ptr"), ptr_ty.const_null())
.unwrap();
b.build_store(field(9, "rtarg_str_len"), i32_ty.const_int(0, false))
.unwrap();
}
pub(crate) fn store_wide_rtarg<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rtarg_ty: StructType<'ctx>,
elem_ptr: PointerValue<'ctx>,
pv: PackedValue<'ctx>,
) {
let ctx = cx.ctx;
let b = &cx.b;
let i32_ty = ctx.i32_type();
let i8_ty = ctx.i8_type();
let i64_ty = ctx.i64_type();
let f64_ty = ctx.f64_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let width = pv.width;
let n_words = width.div_ceil(64);
let val_ty = int_type(ctx, width);
let llvm_bits = pv.llvm.get_type().get_bit_width();
let (val_part, unk_part) = if llvm_bits == width {
(pv.llvm, val_ty.const_zero())
} else {
debug_assert_eq!(
llvm_bits,
width.checked_mul(2).expect("width*2 overflow"),
"a wide $display argument's packed LLVM width must be either `width` (TwoState) \
or `2*width` (FourState)"
);
let packed_ty = pv.llvm.get_type();
let width_const = packed_ty.const_int(u64::from(width), false);
let low_mask = b
.build_right_shift(
packed_ty.const_all_ones(),
width_const,
false,
"rtarg_wide_low_mask",
)
.unwrap();
let val_part_wide = b
.build_and(pv.llvm, low_mask, "rtarg_wide_val_part_wide")
.unwrap();
let shifted = b
.build_right_shift(pv.llvm, width_const, false, "rtarg_wide_shift")
.unwrap();
let unk_part_wide = b
.build_and(shifted, low_mask, "rtarg_wide_unk_part_wide")
.unwrap();
let val_part = b
.build_int_truncate(val_part_wide, val_ty, "rtarg_wide_val_part")
.unwrap();
let unk_part = b
.build_int_truncate(unk_part_wide, val_ty, "rtarg_wide_unk_part")
.unwrap();
(val_part, unk_part)
};
let arr_ty = i64_ty.array_type(n_words);
let aval_alloca = crate::types::entry_alloca_in(b, arr_ty, "rtarg_wide_aval_arr");
let bval_alloca = crate::types::entry_alloca_in(b, arr_ty, "rtarg_wide_bval_arr");
let idx0 = i32_ty.const_zero();
for i in 0..n_words {
let shift = val_ty.const_int(u64::from(i) * 64, false);
let val_word_wide = b
.build_right_shift(val_part, shift, false, "rtarg_wide_val_word_wide")
.unwrap();
let unk_word_wide = b
.build_right_shift(unk_part, shift, false, "rtarg_wide_unk_word_wide")
.unwrap();
let val_word = b
.build_int_truncate(val_word_wide, i64_ty, "rtarg_wide_val_word")
.unwrap();
let unk_word = b
.build_int_truncate(unk_word_wide, i64_ty, "rtarg_wide_unk_word")
.unwrap();
let idxn = i32_ty.const_int(u64::from(i), false);
let aval_word_ptr = unsafe {
b.build_gep(
arr_ty,
aval_alloca,
&[idx0, idxn],
"rtarg_wide_aval_word_ptr",
)
.unwrap()
};
let bval_word_ptr = unsafe {
b.build_gep(
arr_ty,
bval_alloca,
&[idx0, idxn],
"rtarg_wide_bval_word_ptr",
)
.unwrap()
};
let aval_word = b
.build_xor(val_word, unk_word, "rtarg_wide_aval_word")
.unwrap();
b.build_store(aval_word_ptr, aval_word).unwrap();
b.build_store(bval_word_ptr, unk_word).unwrap();
}
let field = |idx: u32, name: &str| b.build_struct_gep(rtarg_ty, elem_ptr, idx, name).unwrap();
b.build_store(field(0, "rtarg_kind"), i32_ty.const_int(1, false))
.unwrap();
b.build_store(
field(1, "rtarg_width"),
i32_ty.const_int(u64::from(width), false),
)
.unwrap();
b.build_store(
field(2, "rtarg_signed"),
i8_ty.const_int(u64::from(pv.signed), false),
)
.unwrap();
b.build_store(field(3, "rtarg_narrow"), ctx.i128_type().const_zero())
.unwrap();
b.build_store(field(4, "rtarg_real"), f64_ty.const_float(0.0))
.unwrap();
b.build_store(field(5, "rtarg_wide_aval"), aval_alloca)
.unwrap();
b.build_store(field(6, "rtarg_wide_bval"), bval_alloca)
.unwrap();
b.build_store(
field(7, "rtarg_wide_words"),
i32_ty.const_int(u64::from(n_words), false),
)
.unwrap();
b.build_store(field(8, "rtarg_str_ptr"), ptr_ty.const_null())
.unwrap();
b.build_store(field(9, "rtarg_str_len"), i32_ty.const_int(0, false))
.unwrap();
}
pub(crate) fn store_huge_rtarg<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rtarg_ty: StructType<'ctx>,
elem_ptr: PointerValue<'ctx>,
h: crate::huge::HugeValue<'ctx>,
) {
let ctx = cx.ctx;
let b = &cx.b;
let i32_ty = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let n_words = h.width.div_ceil(64);
let arr_ty = ctx.i64_type().array_type(n_words);
let aval = crate::types::entry_alloca_in(b, arr_ty, "rtarg_huge_aval_arr");
let bval = crate::types::entry_alloca_in(b, arr_ty, "rtarg_huge_bval_arr");
crate::huge::push_planes(cx, h, aval, bval);
crate::huge::release(h.ptr);
let field = |idx: u32, name: &str| b.build_struct_gep(rtarg_ty, elem_ptr, idx, name).unwrap();
b.build_store(field(0, "rtarg_kind"), i32_ty.const_int(1, false))
.unwrap();
b.build_store(
field(1, "rtarg_width"),
i32_ty.const_int(u64::from(h.width), false),
)
.unwrap();
b.build_store(
field(2, "rtarg_signed"),
ctx.i8_type().const_int(u64::from(h.signed), false),
)
.unwrap();
b.build_store(field(3, "rtarg_narrow"), ctx.i128_type().const_zero())
.unwrap();
b.build_store(field(4, "rtarg_real"), ctx.f64_type().const_float(0.0))
.unwrap();
b.build_store(field(5, "rtarg_wide_aval"), aval).unwrap();
b.build_store(field(6, "rtarg_wide_bval"), bval).unwrap();
b.build_store(
field(7, "rtarg_wide_words"),
i32_ty.const_int(u64::from(n_words), false),
)
.unwrap();
b.build_store(field(8, "rtarg_str_ptr"), ptr_ty.const_null())
.unwrap();
b.build_store(field(9, "rtarg_str_len"), i32_ty.const_int(0, false))
.unwrap();
}
pub(crate) fn store_real_rtarg<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rtarg_ty: StructType<'ctx>,
elem_ptr: PointerValue<'ctx>,
rv: crate::value::RealValue<'ctx>,
) {
let ctx = cx.ctx;
let b = &cx.b;
let i32_ty = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let field = |idx: u32, name: &str| b.build_struct_gep(rtarg_ty, elem_ptr, idx, name).unwrap();
b.build_store(field(0, "rtarg_kind"), i32_ty.const_int(3, false))
.unwrap();
b.build_store(field(1, "rtarg_width"), i32_ty.const_int(64, false))
.unwrap();
b.build_store(field(2, "rtarg_signed"), ctx.i8_type().const_int(1, false))
.unwrap();
b.build_store(field(3, "rtarg_narrow"), ctx.i128_type().const_zero())
.unwrap();
b.build_store(field(4, "rtarg_real"), crate::real::as_f64(cx, rv.llvm))
.unwrap();
b.build_store(field(5, "rtarg_wide_aval"), ptr_ty.const_null())
.unwrap();
b.build_store(field(6, "rtarg_wide_bval"), ptr_ty.const_null())
.unwrap();
b.build_store(field(7, "rtarg_wide_words"), i32_ty.const_int(0, false))
.unwrap();
b.build_store(field(8, "rtarg_str_ptr"), ptr_ty.const_null())
.unwrap();
b.build_store(field(9, "rtarg_str_len"), i32_ty.const_int(0, false))
.unwrap();
}
fn push_rtarg_for_value<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rtarg_ty: StructType<'ctx>,
elem_ptr: PointerValue<'ctx>,
val: LoweredValue<'ctx>,
) -> Result<(), LoweringError> {
match val {
LoweredValue::Packed(pv) => {
if pv.width > 64 {
store_wide_rtarg(cx, rtarg_ty, elem_ptr, pv);
} else {
store_narrow_rtarg(cx, rtarg_ty, elem_ptr, pv);
}
Ok(())
}
LoweredValue::Str(s) => {
crate::strings::store_str_rtarg(cx, rtarg_ty, elem_ptr, s);
Ok(())
}
LoweredValue::Real(rv) => {
store_real_rtarg(cx, rtarg_ty, elem_ptr, rv);
Ok(())
}
LoweredValue::Huge(h) => {
store_huge_rtarg(cx, rtarg_ty, elem_ptr, h);
Ok(())
}
LoweredValue::Wide(_) => Err(LoweringError::unsupported_at(
"a >WIDE_THRESHOLD-bit (wide, limb-array) $display-family argument \
is out of Task 30's scope (RtArgKind::Wide4State's separate \
wide_aval/wide_bval array marshaling is not implemented by this \
crate)",
&cx.fn_sym,
)),
}
}
pub(crate) fn lower_format_expr<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
mut fmt: sv_lang::Expression<'_>,
) -> Result<PointerValue<'ctx>, LoweringError> {
while fmt.kind() == ExpressionKind::Conversion {
fmt = fmt.conversion_operand().ok_or_else(|| {
LoweringError::unsupported_at("format-string conversion missing operand", &cx.fn_sym)
})?;
}
if fmt.kind() != ExpressionKind::StringLiteral {
return Ok(crate::strings::c_str(cx, dynamic_format_string(cx, fmt)?));
}
let text = fmt.string_literal_value().ok_or_else(|| {
LoweringError::unsupported_at(
"$display-family format-string literal has no resolvable text",
&cx.fn_sym,
)
})?;
Ok(build_c_string_global(
cx.ctx,
cx.module,
&text,
"display_fmt",
))
}
fn dynamic_format_string<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
fmt: sv_lang::Expression<'_>,
) -> Result<crate::strings::StrValue<'ctx>, LoweringError> {
let ty = if fmt.kind() == ExpressionKind::ArbitrarySymbol {
fmt.referenced_symbol().and_then(|s| s.value_type())
} else {
fmt.expr_type()
};
if ty.is_some_and(|t| t.is_string() || t.is_integral()) {
let v = crate::expr::lower_expr(cx, fmt)?;
let v = match v {
LoweredValue::Packed(pv)
if fmt.kind() == ExpressionKind::ArbitrarySymbol
&& ty.is_some_and(|t| t.is_string()) =>
{
LoweredValue::Str(crate::strings::from_storage(cx, pv))
}
v => v,
};
return crate::strings::to_string(cx, v);
}
Err(LoweringError::unsupported_at(
&format!(
"a format argument that is neither a string literal nor a string value (a {:?} expression)",
fmt.kind()
),
&cx.fn_sym,
))
}
pub(crate) fn display_format_and_values<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
args: &[sv_lang::Expression<'d>],
call: sv_lang::Expression<'d>,
) -> Result<(PointerValue<'ctx>, Vec<LoweredValue<'ctx>>), LoweringError> {
display_format_and_values_flagged(cx, args, call).map(|(f, v, _)| (f, v))
}
fn is_time_call(mut e: sv_lang::Expression<'_>) -> bool {
while e.kind() == ExpressionKind::Conversion {
match e.conversion_operand() {
Some(inner) => e = inner,
None => break,
}
}
e.kind() == ExpressionKind::Call
&& e.system_subroutine().is_some_and(|s| {
matches!(
s.known_name_id(),
KnownSystemName::Time | KnownSystemName::STime | KnownSystemName::RealTime
)
})
}
pub(crate) fn display_format_and_values_flagged<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
args: &[sv_lang::Expression<'d>],
call: sv_lang::Expression<'d>,
) -> Result<(PointerValue<'ctx>, Vec<LoweredValue<'ctx>>, Vec<bool>), LoweringError> {
let peel = |mut e: sv_lang::Expression<'d>| {
while e.kind() == ExpressionKind::Conversion {
match e.conversion_operand() {
Some(inner) => e = inner,
None => break,
}
}
e
};
let value = |e: sv_lang::Expression<'d>| -> Result<LoweredValue<'ctx>, LoweringError> {
let lit = peel(e);
if lit.kind() == ExpressionKind::StringLiteral {
let text = lit.string_literal_value().unwrap_or_default();
if !text.is_empty() && !text.as_bytes().contains(&0) {
return Ok(LoweredValue::Str(crate::strings::literal(
cx,
text.as_bytes(),
)));
}
}
lower_expr(cx, e)
};
let radix = call
.system_subroutine()
.map(|s| s.name())
.and_then(|n| n.chars().last())
.filter(|c| matches!(c, 'b' | 'h' | 'o'))
.unwrap_or('d');
if let Some(&first) = args.first()
&& peel(first).kind() != ExpressionKind::StringLiteral
&& first.expr_type().is_some_and(|t| t.is_string())
{
let mut fmt = None;
let mut trailer = String::new();
for (i, &a) in args[1..].iter().enumerate() {
if let Some((key, off)) = net_strength_key(peel(a)) {
trailer.push_str(&format!("{i}:{key:x}@{off};"));
} else if reads_net(a) && net_strength_segs(peel(a)).is_some() {
trailer.push_str(&format!("{i}:!;"));
} else if reads_net(a) && is_net_select(peel(a)) {
trailer.push_str(&format!("{i}:!;"));
}
}
let mut extras = String::new();
if args.len() > 1 {
extras.push(sv_sim_runtime::fmt::DYN_EXTRA_MARK);
for &a in &args[1..] {
let ty = a.expr_type();
extras.push(
if a.kind() == ExpressionKind::EmptyArgument
|| ty.is_some_and(|t| t.is_string())
{
's'
} else if ty.is_some_and(|t| t.is_floating()) {
'g'
} else {
radix
},
);
}
}
if !trailer.is_empty() {
trailer.insert(0, sv_sim_runtime::fmt::DYN_STRENGTH_MARK);
}
let tail = format!("{extras}{trailer}");
if !tail.is_empty() {
let s = dynamic_format_string(cx, peel(first))?;
let t = crate::strings::literal(cx, tail.as_bytes());
let cat = crate::strings::str_op(
cx,
sv_sim_sim::strings::StrOp::Concat,
s.llvm,
t.llvm,
cx.ctx.i64_type().const_zero(),
);
fmt = Some(crate::strings::c_str(
cx,
crate::strings::StrValue { llvm: cat },
));
}
let fmt = match fmt {
Some(f) => f,
None => lower_format_expr(cx, first)?,
};
let values = args[1..]
.iter()
.map(|&a| {
if a.kind() == ExpressionKind::EmptyArgument {
Ok(LoweredValue::Str(crate::strings::literal(cx, b" ")))
} else {
value(a)
}
})
.collect::<Result<Vec<_>, _>>()?;
let flags = args[1..].iter().map(|&a| is_time_call(a)).collect();
return Ok((fmt, values, flags));
}
let scope_sym = call.system_call_scope().unwrap_or(cx.fn_sym);
let scope = sv_sim_sim::process::cfg::shown_path(&scope_sym);
let (time_unit, _) = crate::timescale::unit_and_precision(&scope_sym);
let bad_format = |text: &str, err: sv_sim_runtime::fmt::FormatError| {
LoweringError::unsupported_at(&format!("format string {text:?}: {err:?}"), &cx.fn_sym)
};
let mut text = String::new();
let mut values = Vec::new();
let mut pending = 0usize;
let mut piece_start = 0usize;
let mut bound = 0usize;
let mut flags: Vec<bool> = Vec::new();
let mut prev_time = false;
for &a in args {
flags.resize(values.len(), prev_time);
prev_time = is_time_call(a);
if a.kind() == ExpressionKind::EmptyArgument {
text.push(' ');
continue;
}
let lit = peel(a);
if pending == 0 && lit.kind() == ExpressionKind::StringLiteral {
let piece = sv_sim_runtime::fmt::tag_time_unit(
&sv_sim_runtime::fmt::substitute_scope(
&lit.string_literal_value().unwrap_or_default(),
&scope,
&library_cell(&scope_sym, &scope),
),
time_unit,
);
pending = sv_sim_runtime::fmt::count_args(&piece).map_err(|e| bad_format(&piece, e))?;
piece_start = text.len();
bound = 0;
text.push_str(&piece);
continue;
}
if crate::collections::coll_of_expr(&a).is_some() {
let v = crate::collections::format_pattern(cx, a)?;
if pending > 0 {
pending -= 1;
let specs = sv_sim_runtime::fmt::arg_specs(&text[piece_start..])
.map_err(|e| bad_format(&text, e))?;
let spec = specs[bound];
if spec.specifier != 'p' {
return Err(LoweringError::unsupported_at(
&format!(
"a queue, dynamic or associative array printed with %{} (only %p \
prints one)",
spec.specifier
),
&cx.fn_sym,
));
}
text.replace_range(piece_start + spec.start..piece_start + spec.end, "%s");
} else {
text.push_str("%s");
}
bound += 1;
values.push(v);
continue;
}
if pending > 0
&& let Some(ty) = a.expr_type()
&& ty.is_class()
{
let specs = sv_sim_runtime::fmt::arg_specs(&text[piece_start..])
.map_err(|e| bad_format(&text, e))?;
let spec = specs[bound];
if spec.specifier == 'p' {
let pv = lower_expr(cx, a)?.into_packed("a class handle argument", &cx.fn_sym)?;
let h = crate::ops::OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.lane_val(pv);
let h =
cx.b.build_int_z_extend_or_bit_cast(h, cx.ctx.i64_type(), "p_obj")
.unwrap();
pending -= 1;
text.replace_range(piece_start + spec.start..piece_start + spec.end, "%s");
bound += 1;
values.push(crate::classes::format_object(cx, ty, h)?);
continue;
}
}
if pending > 0
&& let Some(ty) = a.expr_type()
&& ty.is_enum()
&& crate::types::TypeWidth::value_width(&ty) <= 64
{
let specs = sv_sim_runtime::fmt::arg_specs(&text[piece_start..])
.map_err(|e| bad_format(&text, e))?;
let spec = specs[bound];
if spec.specifier == 'p' {
let pv = lower_expr(cx, a)?.into_packed("an enum argument", &cx.fn_sym)?;
if let Some(s) = crate::sysfunc::enum_pattern_text(cx, &ty, pv)? {
pending -= 1;
text.replace_range(piece_start + spec.start..piece_start + spec.end, "%s");
bound += 1;
values.push(s);
continue;
}
}
}
let fixed_array = |t: &sv_lang::Type| {
t.is_unpacked_array() && !sv_sim_sim::state::storage::is_collection(t)
};
let pk_p = |ty: &sv_lang::Type| -> bool {
pending > 0
&& ty.is_struct()
&& !ty.is_unpacked_struct()
&& sv_sim_runtime::fmt::arg_specs(&text[piece_start..])
.ok()
.and_then(|s| s.get(bound).map(|s| s.specifier == 'p'))
.unwrap_or(false)
};
if let Some(ty) = a.expr_type()
&& (sv_sim_sim::state::storage::is_unpacked_aggregate(&ty)
|| fixed_array(&ty)
|| pk_p(&ty))
{
let mut pattern = String::new();
let mut leaves = Vec::new();
let stored = if fixed_array(&ty) {
match crate::arrays::array_target(cx, a)? {
Some(t) => Some(t),
None => crate::arrays::array_call_value(cx, a)?,
}
} else {
None
};
if let Some((r, shape)) = stored {
array_pattern(cx, &r, &shape, 0, 0, &mut pattern, &mut leaves)?;
} else {
let v =
lower_expr(cx, a)?.into_packed("an aggregate display argument", &cx.fn_sym)?;
aggregate_pattern(cx, &ty, v, &mut pattern, &mut leaves)?;
}
if pending > 0 {
pending -= 1;
let specs = sv_sim_runtime::fmt::arg_specs(&text[piece_start..])
.map_err(|e| bad_format(&text, e))?;
let spec = specs[bound];
if spec.specifier != 'p' {
return Err(LoweringError::unsupported_at(
&format!(
"an unpacked struct/union printed with %{} (only %p prints one)",
spec.specifier
),
&cx.fn_sym,
));
}
text.replace_range(piece_start + spec.start..piece_start + spec.end, &pattern);
} else {
text.push_str(&pattern);
}
bound += leaves.len();
values.extend(leaves);
continue;
}
bound += 1;
let mut hidden: Vec<sv_lang::Expression<'d>> = Vec::new();
if pending > 0 {
pending -= 1;
let specs = sv_sim_runtime::fmt::arg_specs(&text[piece_start..])
.map_err(|e| bad_format(&text, e))?;
if specs.get(bound - 1).is_some_and(|s| s.specifier == 'v') {
if let Some((key, off)) = net_strength_key(lit) {
let tagged = sv_sim_runtime::fmt::tag_strength_key(
&text[piece_start..],
bound - 1,
key,
off,
);
text.truncate(piece_start);
text.push_str(&tagged);
} else if let Some((segs, idx)) = net_strength_segs(lit) {
let tagged = sv_sim_runtime::fmt::tag_strength_segs(
&text[piece_start..],
bound - 1,
&segs,
);
text.truncate(piece_start);
text.push_str(&tagged);
hidden = idx;
} else if reads_net(a) {
return Err(LoweringError::unsupported_at(
"%v of an expression over nets other than a whole net, a bit/part \
select of a packed net, or a concatenation of those (an unpacked-array \
net element with a non-constant index keeps no strengths)",
&cx.fn_sym,
));
}
}
} else {
let ty = a.expr_type();
text.push_str(if ty.is_some_and(|t| t.is_string()) {
"%s"
} else if ty.is_some_and(|t| t.is_floating()) {
"%#g"
} else {
match radix {
'b' => "%b",
'h' => "%h",
'o' => "%o",
_ => "%d",
}
});
}
values.push(value(a)?);
for ix in hidden {
values.push(value(ix)?);
}
}
if pending > 0 {
return Err(LoweringError::unsupported_at(
&format!("format string {text:?} has {pending} more specifier(s) than arguments"),
&cx.fn_sym,
));
}
flags.resize(values.len(), prev_time);
Ok((
build_c_string_global(cx.ctx, cx.module, &text, "display_fmt"),
values,
flags,
))
}
pub(crate) fn net_strength_key(e: sv_lang::Expression<'_>) -> Option<(u64, u32)> {
let mut sels = Vec::new();
let mut base = e;
while matches!(
base.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
sels.push(base);
base = base.select_value()?;
}
if !sv_sim_sim::symid::is_value_name(base.kind()) {
return None;
}
let sym = base.referenced_symbol()?;
if sym.kind() != SymbolKind::Net {
return None;
}
let uid = sv_sim_sim::symid::SymbolUid::uid(&sym).key_string();
let net_ty = base.expr_type()?;
if net_ty.is_unpacked_array() {
let mut dims = Vec::new();
let mut ty = net_ty;
while ty.is_unpacked_array() {
let r = ty.fixed_unpacked_array_range();
dims.push((i64::from(r.left), i64::from(r.right)));
ty = ty.element_type()?;
}
let mut row = 0i64;
let mut elem = None;
for (k, &(l, r)) in dims.iter().enumerate() {
let sel = *sels.get(sels.len().checked_sub(k + 1)?)?;
if sel.kind() != ExpressionKind::ElementSelect {
return None;
}
let i = sel
.selector()?
.constant_value()
.as_ref()
.and_then(sv_lang::ConstantValue::as_integer)
.and_then(sv_lang::SVInt::as_i64)?;
let n = (l - r).abs() + 1;
let p = if l <= r { i - l } else { l - i };
if !(0..n).contains(&p) {
return None;
}
row = row * n + if l <= r { p } else { n - 1 - p };
elem = Some(sel);
}
let elem = elem?;
let packed_selects = sels.len() - dims.len();
let off = if packed_selects == 0 {
0
} else {
let width = crate::types::TypeWidth::value_width(&elem.expr_type()?);
crate::select::constant_window_skip(e, width, dims.len())
.ok()?
.0
};
return Some((
sv_sim_runtime::fmt::strength_key(&format!("{uid}[{row}]")),
off,
));
}
let off = if sels.is_empty() {
0
} else {
let width = crate::types::TypeWidth::value_width(&net_ty);
crate::select::constant_window(e, width).ok()?.0
};
Some((sv_sim_runtime::fmt::strength_key(&uid), off))
}
fn is_net_select(e: sv_lang::Expression<'_>) -> bool {
matches!(
e.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect | ExpressionKind::Concatenation
)
}
fn net_strength_segs<'d>(
e: sv_lang::Expression<'d>,
) -> Option<(
Vec<sv_sim_runtime::fmt::StrengthSeg>,
Vec<sv_lang::Expression<'d>>,
)> {
use sv_sim_runtime::fmt::{SegAt, StrengthSeg};
fn peel_conv(mut e: sv_lang::Expression<'_>) -> sv_lang::Expression<'_> {
while e.kind() == ExpressionKind::Conversion {
match e.conversion_operand() {
Some(inner) => e = inner,
None => break,
}
}
e
}
fn walk<'d>(
e: sv_lang::Expression<'d>,
segs: &mut Vec<StrengthSeg>,
idx: &mut Vec<sv_lang::Expression<'d>>,
) -> Option<()> {
let e = peel_conv(e);
let width = crate::types::TypeWidth::value_width(&e.expr_type()?) as usize;
if width == 0 {
return Some(());
}
if e.kind() == ExpressionKind::Concatenation {
for op in e.operands() {
walk(op, segs, idx)?;
}
return Some(());
}
if let Some((key, off)) = net_strength_key(e) {
segs.push(StrengthSeg {
at: SegAt::Net {
key,
off: off as usize,
},
width,
});
} else if !reads_net(e) {
segs.push(StrengthSeg {
at: SegAt::Strong,
width,
});
} else if let Some((key, desc, right, ix)) = dynamic_bit_select(e) {
segs.push(StrengthSeg {
at: SegAt::Dyn { key, desc, right },
width: 1,
});
idx.push(ix);
} else {
return None;
}
Some(())
}
let (mut segs, mut idx) = (Vec::new(), Vec::new());
walk(e, &mut segs, &mut idx)?;
(!segs.is_empty()).then_some((segs, idx))
}
fn dynamic_bit_select<'d>(
e: sv_lang::Expression<'d>,
) -> Option<(u64, bool, i64, sv_lang::Expression<'d>)> {
if e.kind() != ExpressionKind::ElementSelect {
return None;
}
let base = e.select_value()?;
if !sv_sim_sim::symid::is_value_name(base.kind()) {
return None;
}
let sym = base.referenced_symbol()?;
if sym.kind() != SymbolKind::Net {
return None;
}
let ty = base.expr_type().filter(|t| !t.is_unpacked_array())?;
let width = crate::types::TypeWidth::value_width(&ty);
let geom = crate::select::geom_of(Some(ty), width);
if geom.elem_width != 1 {
return None;
}
Some((
sv_sim_runtime::fmt::strength_key(&sv_sim_sim::symid::SymbolUid::uid(&sym).key_string()),
geom.descending(),
geom.right,
e.selector()?,
))
}
fn reads_net(e: sv_lang::Expression<'_>) -> bool {
let mut stack = vec![e.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(x) = n.as_expression()
&& sv_sim_sim::symid::is_value_name(x.kind())
&& x.referenced_symbol()
.is_some_and(|s| s.kind() == SymbolKind::Net)
{
return true;
}
stack.extend(n.children());
}
false
}
fn array_pattern<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
r: &crate::arrays::ArrayRef<'ctx>,
shape: &crate::arrays::ArrayShape<'_>,
k: usize,
base: u64,
text: &mut String,
leaves: &mut Vec<LoweredValue<'ctx>>,
) -> Result<(), LoweringError> {
let (left, right) = shape.dims[k];
let size = (left - right).unsigned_abs() + 1;
let stride: u64 = shape.dims[k + 1..]
.iter()
.map(|(l, r)| (l - r).unsigned_abs() + 1)
.product();
let low = left.min(right);
text.push_str("'{");
for n in 0..size {
if n > 0 {
text.push_str(", ");
}
let idx = if left <= right {
left + n as i64
} else {
left - n as i64
};
let row = base + (idx - low) as u64 * stride;
if k + 1 < shape.dims.len() {
array_pattern(cx, r, shape, k + 1, row, text, leaves)?;
continue;
}
let elem = &shape.elem;
let at =
cx.b.build_int_add(r.base, cx.ctx.i64_type().const_int(row, false), "p_row")
.unwrap();
let e = crate::arrays::ElemRef {
backing: r.backing,
row: at,
valid: r.valid,
width: crate::types::TypeWidth::value_width(elem),
signed: elem.is_signed(),
default: crate::arrays::default_value(cx, elem),
};
let v = crate::arrays::read_element(cx, &e)?;
if sv_sim_sim::state::storage::is_unpacked_aggregate(elem)
|| crate::collections::coll_of(elem).is_some()
{
aggregate_pattern(cx, elem, v, text, leaves)?;
continue;
}
if elem.is_class() {
text.push_str("%s");
let h = crate::ops::OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.lane_val(v);
leaves.push(crate::classes::format_object(cx, *elem, h)?);
continue;
}
if let Some(s) = crate::sysfunc::enum_pattern_text(cx, elem, v)? {
text.push_str("%s");
leaves.push(s);
continue;
}
text.push_str("%p");
leaves.push(if elem.is_string() {
LoweredValue::Str(crate::strings::from_storage(cx, v))
} else if elem.is_floating() {
LoweredValue::Real(crate::real::bits_to_real(cx, v))
} else {
LoweredValue::Packed(v)
});
}
text.push('}');
Ok(())
}
pub(crate) fn aggregate_pattern<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ty: &sv_lang::Type<'_>,
v: PackedValue<'ctx>,
text: &mut String,
leaves: &mut Vec<LoweredValue<'ctx>>,
) -> Result<(), LoweringError> {
use sv_sim_sim::state::storage;
let op = crate::ops::OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let shape_err =
|| LoweringError::unsupported_at("an aggregate display argument's shape", &cx.fn_sym);
let slice = |lo: u64, w: u64, signed: bool| PackedValue {
signed,
..op.part_select4(
v,
op.i_n(v.width).const_int(lo, false),
cx.ctx.bool_type().const_zero(),
w as u32,
)
};
if let Some(c) = crate::collections::coll_of(ty) {
let h = op.lane_val(v);
let h = if v.width > 64 {
cx.b.build_int_truncate(h, cx.ctx.i64_type(), "p_coll")
.unwrap()
} else {
cx.b.build_int_z_extend_or_bit_cast(h, cx.ctx.i64_type(), "p_coll")
.unwrap()
};
text.push_str("%s");
leaves.push(crate::collections::format_handle(cx, &c, h)?);
return Ok(());
}
if ty.is_unpacked_union() && ty.is_tagged_union() {
return crate::tagged::print_pattern(cx, ty, v, text, leaves);
}
if storage::is_unpacked_aggregate(ty) {
text.push_str("'{");
let fields: Vec<_> = ty.fields().collect();
let shown = if ty.is_unpacked_union() {
&fields[..fields.len().min(1)]
} else {
&fields[..]
};
for (i, f) in shown.iter().enumerate() {
if i > 0 {
text.push_str(", ");
}
let (lo, w) = storage::field_slice(ty, f.field_index()).ok_or_else(shape_err)?;
let fty = f.value_type().ok_or_else(shape_err)?;
text.push_str(f.name());
text.push(':');
aggregate_pattern(cx, &fty, slice(lo, w, fty.is_signed()), text, leaves)?;
}
text.push('}');
} else if ty.is_unpacked_array() {
let range = ty.fixed_unpacked_array_range();
let elem = ty.element_type().ok_or_else(shape_err)?;
let ew = storage::storage_width(&elem).ok_or_else(shape_err)?;
text.push_str("'{");
for k in 0..i64::from(range.width()) {
if k > 0 {
text.push_str(", ");
}
let idx = if range.left <= range.right {
i64::from(range.left) + k
} else {
i64::from(range.left) - k
};
let pos = (idx - i64::from(range.lower())) as u64;
aggregate_pattern(
cx,
&elem,
slice(pos * ew, ew, elem.is_signed()),
text,
leaves,
)?;
}
text.push('}');
} else if ty.is_class() {
let h = op.lane_val(v);
let h =
cx.b.build_int_z_extend_or_bit_cast(h, cx.ctx.i64_type(), "p_obj")
.unwrap();
text.push_str("%s");
leaves.push(crate::classes::format_object(cx, *ty, h)?);
} else if ty.is_struct() && !ty.is_unpacked_struct() {
text.push_str("'{");
for (i, f) in ty.fields().enumerate() {
if i > 0 {
text.push_str(", ");
}
let (lo, w) = storage::field_slice(ty, f.field_index()).ok_or_else(shape_err)?;
let fty = f.value_type().ok_or_else(shape_err)?;
text.push_str(f.name());
text.push(':');
aggregate_pattern(cx, &fty, slice(lo, w, fty.is_signed()), text, leaves)?;
}
text.push('}');
} else if let Some(s) = crate::sysfunc::enum_pattern_text(cx, ty, v)? {
text.push_str("%s");
leaves.push(s);
} else {
text.push_str("%p");
leaves.push(if ty.is_floating() {
LoweredValue::Real(crate::real::bits_to_real(cx, v))
} else if ty.is_string() {
LoweredValue::Str(crate::strings::from_storage(cx, v))
} else {
LoweredValue::Packed(v)
});
}
Ok(())
}
fn library_cell(scope_sym: &sv_lang::Symbol<'_>, scope: &str) -> String {
let cell = scope_sym
.declaring_definition()
.map(|d| d.name().to_string())
.unwrap_or_else(|| scope.split('.').next().unwrap_or(scope).to_string());
let lib = scope_sym
.declaring_definition()
.and_then(|d| d.source_library())
.or_else(|| scope_sym.source_library())
.map_or_else(|| "work".to_string(), |l| l.name());
format!("{lib}.{cell}")
}
pub(crate) fn lower_display_format<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
fmt: sv_lang::Expression<'_>,
call: sv_lang::Expression<'_>,
) -> Result<PointerValue<'ctx>, LoweringError> {
let mut lit = fmt;
while lit.kind() == ExpressionKind::Conversion {
match lit.conversion_operand() {
Some(inner) => lit = inner,
None => break,
}
}
let Some(text) = (lit.kind() == ExpressionKind::StringLiteral)
.then(|| lit.string_literal_value())
.flatten()
else {
return lower_format_expr(cx, fmt);
};
let scope_sym = call.system_call_scope().unwrap_or(cx.fn_sym);
let (time_unit, _) = crate::timescale::unit_and_precision(&scope_sym);
let shown = sv_sim_sim::process::cfg::shown_path(&scope_sym);
let text = sv_sim_runtime::fmt::tag_time_unit(
&sv_sim_runtime::fmt::substitute_scope(&text, &shown, &library_cell(&scope_sym, &shown)),
time_unit,
);
sv_sim_runtime::fmt::check_format(&text).map_err(|err| {
LoweringError::unsupported_at(&format!("format string {text:?}: {err:?}"), &cx.fn_sym)
})?;
Ok(build_c_string_global(
cx.ctx,
cx.module,
&text,
"display_fmt",
))
}
pub(crate) fn build_c_string_global<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
s: &str,
name: &str,
) -> PointerValue<'ctx> {
let mut bytes = s.as_bytes().to_vec();
bytes.push(0);
let i8_ty = ctx.i8_type();
let const_bytes: Vec<IntValue<'ctx>> = bytes
.iter()
.map(|byte| i8_ty.const_int(u64::from(*byte), false))
.collect();
let arr = i8_ty.const_array(&const_bytes);
let global = module.add_global(arr.get_type(), None, name);
global.set_initializer(&arr);
global.set_constant(true);
global.set_linkage(Linkage::Private);
global.as_pointer_value()
}
pub(crate) fn fetch_or_declare_display_family_fn<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
family: DisplayFamily,
) -> FunctionValue<'ctx> {
let symbol = display_family_symbol(family);
module.get_function(symbol).unwrap_or_else(|| {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i32_ty = ctx.i32_type();
let void_ty = ctx.void_type();
let fn_ty = void_ty.fn_type(&[ptr_ty.into(), ptr_ty.into(), i32_ty.into()], false);
module.add_function(symbol, fn_ty, Some(Linkage::External))
})
}
pub fn lower_display_family_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
call: sv_lang::Expression<'d>,
family: DisplayFamily,
) -> Result<(), LoweringError> {
if family == DisplayFamily::Monitor {
return Err(LoweringError::unsupported_at(
"bare $monitor cannot be lowered by this (func.rs leaf-function) \
pipeline yet: sv_rt_monitor's real, exported ABI now takes a \
leading MonitorId argument (B-runtime Task 12, RT-036/RT-047), \
but no codegen infrastructure exists anywhere in this crate to \
mint a stable, per-call-site MonitorId -- emitting the old \
three-argument (fmt, args, nargs) call this function builds for \
every other DisplayFamily member would silently corrupt every \
argument sv_rt_monitor reads, rather than a mismatched call. \
$display/$write/$strobe are unaffected (their runtime symbols \
kept their original three-argument shape)",
&cx.fn_sym,
));
}
let operands = call.operands();
let (fmt_ptr, arg_values) = display_format_and_values(cx, &operands, call)?;
let rt_fn = fetch_or_declare_display_family_fn(cx.ctx, cx.module, family);
let rtarg_ty = rtarg_llvm_type(cx.ctx);
let (args_ptr, nargs_const) = build_rtarg_array(cx, rtarg_ty, arg_values)?;
cx.b.build_call(
rt_fn,
&[fmt_ptr.into(), args_ptr.into(), nargs_const.into()],
"rt_display_call",
)
.unwrap();
Ok(())
}
pub(crate) fn build_rtarg_array<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rtarg_ty: StructType<'ctx>,
arg_values: Vec<LoweredValue<'ctx>>,
) -> Result<(PointerValue<'ctx>, IntValue<'ctx>), LoweringError> {
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let n = arg_values.len() as u32;
let args_ptr = if n == 0 {
ptr_ty.const_null()
} else {
let arr_ty = rtarg_ty.array_type(n);
let arr_ptr = crate::types::entry_alloca_in(&cx.b, arr_ty, "rtargs");
if let Some(instr) = arr_ptr.as_instruction_value() {
instr
.set_alignment(16)
.expect("alloca instruction accepts an explicit alignment");
}
let i32_ty = cx.ctx.i32_type();
for (i, val) in arg_values.into_iter().enumerate() {
let elem_ptr = unsafe {
cx.b.build_gep(
arr_ty,
arr_ptr,
&[
i32_ty.const_int(0, false),
i32_ty.const_int(i as u64, false),
],
"rtarg_elem",
)
.unwrap()
};
push_rtarg_for_value(cx, rtarg_ty, elem_ptr, val)?;
}
arr_ptr
};
let nargs_const = cx.ctx.i32_type().const_int(u64::from(n), false);
Ok((args_ptr, nargs_const))
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DumpControlCall {
DumpFile,
DumpFlush,
}
pub fn classify_dump_control_call(name: KnownSystemName) -> Option<DumpControlCall> {
use KnownSystemName::{DumpFile, DumpFlush};
match name {
DumpFile => Some(DumpControlCall::DumpFile),
DumpFlush => Some(DumpControlCall::DumpFlush),
_ => None,
}
}
fn fetch_or_declare_dump_control_fn<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
symbol: &str,
has_path_arg: bool,
) -> FunctionValue<'ctx> {
module.get_function(symbol).unwrap_or_else(|| {
let void_ty = ctx.void_type();
let fn_ty = if has_path_arg {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
void_ty.fn_type(&[ptr_ty.into(), ptr_ty.into()], false)
} else {
void_ty.fn_type(&[], false)
};
module.add_function(symbol, fn_ty, Some(Linkage::External))
})
}
pub fn lower_dump_control_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
call: sv_lang::Expression<'d>,
which: DumpControlCall,
) -> Result<(), LoweringError> {
match which {
DumpControlCall::DumpFile => {
let operands = call.operands();
let Some(path_expr) = operands.first() else {
return Ok(());
};
let path_ptr = lower_format_expr(cx, *path_expr)?;
let rt_fn =
fetch_or_declare_dump_control_fn(cx.ctx, cx.module, SV_RT_DUMPFILE_SYMBOL, true);
let src_text = sv_sim_sim::process::cfg::source_text_range(path_expr.source_range())
.unwrap_or_default();
let src_ptr =
cx.b.build_global_string_ptr(&src_text, "dumpfile_src")
.unwrap()
.as_pointer_value();
cx.b.build_call(
rt_fn,
&[path_ptr.into(), src_ptr.into()],
"rt_dumpfile_call",
)
.unwrap();
}
DumpControlCall::DumpFlush => {
let rt_fn =
fetch_or_declare_dump_control_fn(cx.ctx, cx.module, SV_RT_DUMPFLUSH_SYMBOL, false);
cx.b.build_call(rt_fn, &[], "rt_dumpflush_call").unwrap();
}
}
Ok(())
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FileIoVoidCall {
FClose,
FFlush,
}
pub fn classify_file_io_void_call(name: KnownSystemName) -> Option<FileIoVoidCall> {
use KnownSystemName::{FClose, FFlush};
match name {
FClose => Some(FileIoVoidCall::FClose),
FFlush => Some(FileIoVoidCall::FFlush),
_ => None,
}
}
fn fetch_or_declare_file_io_void_fn<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
symbol: &str,
) -> FunctionValue<'ctx> {
module.get_function(symbol).unwrap_or_else(|| {
let void_ty = ctx.void_type();
let i32_ty = ctx.i32_type();
let fn_ty = void_ty.fn_type(&[i32_ty.into()], false);
module.add_function(symbol, fn_ty, Some(Linkage::External))
})
}
pub fn lower_file_io_void_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
call: sv_lang::Expression<'d>,
which: FileIoVoidCall,
) -> Result<(), LoweringError> {
let operands = call.operands();
let fd_i32 = match operands.first() {
Some(fd_expr) => {
let pv = lower_expr(cx, *fd_expr)?.into_packed(
"$fclose/$fflush's fd argument must be an integral value",
&cx.fn_sym,
)?;
let op = crate::ops::OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
crate::expr::packed_to_plain_i32(&op, pv)
}
None if which == FileIoVoidCall::FFlush => cx.ctx.i32_type().const_zero(),
None => {
return Err(LoweringError::unsupported_at(
"$fclose requires an fd argument",
&cx.fn_sym,
));
}
};
let symbol = match which {
FileIoVoidCall::FClose => SV_RT_FCLOSE_SYMBOL,
FileIoVoidCall::FFlush => SV_RT_FFLUSH_SYMBOL,
};
let rt_fn = fetch_or_declare_file_io_void_fn(cx.ctx, cx.module, symbol);
cx.b.build_call(rt_fn, &[fd_i32.into()], "rt_file_io_void_call")
.unwrap();
Ok(())
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FDisplayFamily {
FWrite,
FDisplay,
FStrobe,
FMonitor,
}
pub fn classify_fdisplay_family(name: KnownSystemName) -> Option<FDisplayFamily> {
use KnownSystemName::{
FDisplay, FDisplayB, FDisplayH, FDisplayO, FMonitor, FMonitorB, FMonitorH, FMonitorO,
FStrobe, FStrobeB, FStrobeH, FStrobeO, FWrite, FWriteB, FWriteH, FWriteO,
};
match name {
FWrite | FWriteB | FWriteO | FWriteH => Some(FDisplayFamily::FWrite),
FDisplay | FDisplayB | FDisplayO | FDisplayH => Some(FDisplayFamily::FDisplay),
FStrobe | FStrobeB | FStrobeO | FStrobeH => Some(FDisplayFamily::FStrobe),
FMonitor | FMonitorB | FMonitorO | FMonitorH => Some(FDisplayFamily::FMonitor),
_ => None,
}
}
pub fn fdisplay_family_symbol(f: FDisplayFamily) -> &'static str {
match f {
FDisplayFamily::FWrite => SV_RT_FWRITE_SYMBOL,
FDisplayFamily::FDisplay => SV_RT_FDISPLAY_SYMBOL,
FDisplayFamily::FStrobe => SV_RT_FSTROBE_SYMBOL,
FDisplayFamily::FMonitor => SV_RT_FMONITOR_SYMBOL,
}
}
fn fetch_or_declare_fdisplay_family_fn<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
family: FDisplayFamily,
) -> FunctionValue<'ctx> {
let symbol = fdisplay_family_symbol(family);
module.get_function(symbol).unwrap_or_else(|| {
let i32_ty = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let void_ty = ctx.void_type();
let fn_ty = void_ty.fn_type(
&[i32_ty.into(), ptr_ty.into(), ptr_ty.into(), i32_ty.into()],
false,
);
module.add_function(symbol, fn_ty, Some(Linkage::External))
})
}
pub fn lower_fdisplay_family_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
call: sv_lang::Expression<'d>,
family: FDisplayFamily,
) -> Result<(), LoweringError> {
let operands = call.operands();
let (fd_expr, rest) = operands.split_first().ok_or_else(|| {
LoweringError::unsupported_at("$fwrite-family call has no fd argument", &cx.fn_sym)
})?;
let fd_pv = lower_expr(cx, *fd_expr)?.into_packed(
"$fwrite-family's fd argument must be an integral value",
&cx.fn_sym,
)?;
let op = crate::ops::OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let fd_i32 = crate::expr::packed_to_plain_i32(&op, fd_pv);
let (fmt_ptr, arg_values) = display_format_and_values(cx, rest, call)?;
let rt_fn = fetch_or_declare_fdisplay_family_fn(cx.ctx, cx.module, family);
let rtarg_ty = rtarg_llvm_type(cx.ctx);
let (args_ptr, nargs_const) = build_rtarg_array(cx, rtarg_ty, arg_values)?;
cx.b.build_call(
rt_fn,
&[
fd_i32.into(),
fmt_ptr.into(),
args_ptr.into(),
nargs_const.into(),
],
"rt_fdisplay_call",
)
.unwrap();
Ok(())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SdfCallSite {
pub file: Option<String>,
pub scope: String,
pub options: [Option<Option<String>>; 5],
}
thread_local! {
static SDF_SITES: std::cell::RefCell<Vec<SdfCallSite>> =
const { std::cell::RefCell::new(Vec::new()) };
}
fn record_sdf_site(
file: Option<Option<String>>,
scope: String,
options: [Option<Option<String>>; 5],
) {
let file = file.flatten();
SDF_SITES.with(|s| {
s.borrow_mut().push(SdfCallSite {
file,
scope,
options,
});
});
}
pub fn clear_sdf_sites() {
SDF_SITES.with(|s| s.borrow_mut().clear());
}
pub fn take_sdf_sites() -> Vec<SdfCallSite> {
SDF_SITES.with(|s| std::mem::take(&mut *s.borrow_mut()))
}
pub(crate) fn lower_sdf_annotate_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
call: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let operands = call.operands();
if operands.is_empty() || operands.len() > 7 {
return Err(LoweringError::unsupported_at(
&format!(
"$sdf_annotate takes 1 to 7 arguments (sdf_file[, module_instance[, config_file\
[, log_file[, mtm_spec[, scale_factors[, scale_type]]]]]]), got {}",
operands.len()
),
&cx.fn_sym,
));
}
let present = |k: usize| {
operands
.get(k)
.copied()
.filter(|a| a.kind() != ExpressionKind::EmptyArgument)
};
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let null = ptr_ty.const_null();
let string_arg = |k: usize, what: &str| -> Result<PointerValue<'ctx>, LoweringError> {
let Some(mut arg) = present(k) else {
return Ok(null);
};
while arg.kind() == ExpressionKind::Conversion {
arg = arg.conversion_operand().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("$sdf_annotate {what} argument: conversion missing operand"),
&cx.fn_sym,
)
})?;
}
if arg.kind() == ExpressionKind::StringLiteral {
let text = arg.string_literal_value().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("$sdf_annotate {what} argument: string literal has no text"),
&cx.fn_sym,
)
})?;
return Ok(build_c_string_global(
cx.ctx,
cx.module,
&text,
"sdf_annotate_arg",
));
}
let ty = arg.expr_type();
if !ty
.as_ref()
.is_some_and(|t| t.is_string() || t.is_integral())
{
return Err(LoweringError::unsupported_at(
&format!(
"$sdf_annotate {what} argument must be a string literal, a string, or an \
integral character string"
),
&cx.fn_sym,
));
}
let s = crate::strings::to_string(cx, lower_expr(cx, arg)?)?;
Ok(crate::strings::c_str(cx, s))
};
let file = string_arg(0, "sdf_file")?;
let literal = |k: usize| -> Option<Option<String>> {
let Some(mut arg) = present(k) else {
return Some(None);
};
while arg.kind() == ExpressionKind::Conversion {
arg = arg.conversion_operand()?;
}
(arg.kind() == ExpressionKind::StringLiteral)
.then(|| arg.string_literal_value())
.flatten()
.map(Some)
};
let scope = match present(1) {
Some(inst) => {
let sym = inst
.arbitrary_symbol()
.or_else(|| inst.referenced_symbol())
.filter(|s| s.kind() == SymbolKind::Instance)
.ok_or_else(|| {
LoweringError::unsupported_at(
"$sdf_annotate module_instance argument must name a module instance",
&cx.fn_sym,
)
})?;
sv_sim_sim::process::cfg::shown_path(&sym)
}
None => {
let mut scope = call.system_call_scope().unwrap_or(cx.fn_sym);
while scope.kind() != SymbolKind::InstanceBody {
match scope.parent() {
Some(p) => scope = p,
None => {
return Err(LoweringError::unsupported_at(
"$sdf_annotate outside any module instance has no default scope",
&cx.fn_sym,
));
}
}
}
sv_sim_sim::process::cfg::shown_path(&scope)
}
};
let scope_text = scope.clone();
let scope = build_c_string_global(cx.ctx, cx.module, &scope, "sdf_annotate_scope");
let config = string_arg(2, "config_file")?;
let log = string_arg(3, "log_file")?;
let mtm = string_arg(4, "mtm_spec")?;
let scale_factors = string_arg(5, "scale_factors")?;
let scale_type = string_arg(6, "scale_type")?;
record_sdf_site(
literal(0),
scope_text,
[literal(2), literal(3), literal(4), literal(5), literal(6)],
);
let rt_fn = cx
.module
.get_function(SV_RT_SDF_ANNOTATE_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_SDF_ANNOTATE_SYMBOL,
cx.ctx.void_type().fn_type(&[ptr_ty.into(); 7], false),
Some(Linkage::External),
)
});
cx.b.build_call(
rt_fn,
&[
file.into(),
scope.into(),
config.into(),
log.into(),
mtm.into(),
scale_factors.into(),
scale_type.into(),
],
"rt_sdf_annotate_call",
)
.unwrap();
crate::stmt_shared::leave_if_aborted(cx)?;
Ok(())
}