use crate::types::TypeWidth;
use inkwell::AddressSpace;
use inkwell::attributes::AttributeLoc;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum, IntType};
use inkwell::values::{BasicMetadataValueEnum, BasicValueEnum, IntValue, PointerValue};
use sv_sim_sim::symid::SymbolUid;
use sv_lang::kinds::ExpressionKind;
use sv_lang::{ArgumentDirection, Design, DpiExport, Symbol, Type};
use crate::error::LoweringError;
use crate::expr::{entry_alloca, lower_expr};
use crate::ops::OpBuilder;
use crate::value::{PackedValue, ValueRepr, WIDE_THRESHOLD};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DpiParamClass {
ByValueScalar,
ByValueNarrow(u32),
ByPointerWide,
ByPointerAggregate,
AlwaysPointer,
}
pub fn classify_dpi_param(
ty: &Type,
direction: ArgumentDirection,
) -> Result<DpiParamClass, LoweringError> {
if !ty.is_valid_for_dpi_arg() {
return Err(LoweringError::unsupported(
&format!(
"type `{}` is not valid as a DPI import/export argument \
(Type::is_valid_for_dpi_arg is false)",
ty.to_sv_string()
),
"classify_dpi_param",
));
}
match direction {
ArgumentDirection::Out | ArgumentDirection::InOut => Ok(DpiParamClass::AlwaysPointer),
ArgumentDirection::Ref => Err(LoweringError::unsupported(
"`ref` is not a valid DPI import/export argument direction",
"classify_dpi_param",
)),
ArgumentDirection::In => Ok(classify_by_shape(ty)),
}
}
fn classify_by_shape(ty: &Type) -> DpiParamClass {
if ty.is_unpacked_array() {
return DpiParamClass::ByPointerAggregate;
}
if ty.is_integral() {
if ty.is_scalar() {
return DpiParamClass::ByValueScalar;
}
if ty.is_four_state() {
return DpiParamClass::ByPointerWide;
}
if ty.is_predefined_integer() {
let width = ty.bit_width();
debug_assert!(
width <= 64,
"predefined two-state integer wider than 64 bits"
);
return DpiParamClass::ByValueNarrow(width as u32);
}
return DpiParamClass::ByPointerWide;
}
DpiParamClass::ByPointerAggregate
}
pub fn dpi_formal_args<'s, 'd>(sub: &'s Symbol<'d>) -> impl Iterator<Item = Symbol<'d>> + 's {
sub.subroutine_arguments()
}
fn int_type(ctx: &Context, w: u32) -> IntType<'_> {
ctx.custom_width_int_type(std::num::NonZeroU32::new(w).expect("zero-width int type"))
.expect("custom_width_int_type")
}
fn svlogic_swap_xz<'ctx>(
builder: &inkwell::builder::Builder<'ctx>,
v: inkwell::values::IntValue<'ctx>,
) -> inkwell::values::IntValue<'ctx> {
let ty = v.get_type();
let one = ty.const_int(1, false);
let hi = builder
.build_right_shift(v, one, false, "svlogic_hi")
.unwrap();
let hi_bit = builder.build_and(hi, one, "svlogic_hi_bit").unwrap();
builder.build_xor(v, hi_bit, "svlogic_swap_xz").unwrap()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DpiCType {
Scalar,
Int { width: u32, signed: bool },
Real,
ShortReal,
Chandle,
Str,
BitVec(u32),
LogicVec(u32),
}
impl DpiCType {
pub fn of(ty: &Type) -> Result<Self, String> {
let ty = match ty.enum_base() {
Some(base) if ty.is_enum() => base,
_ => *ty,
};
if ty.is_floating() {
return Ok(if ty.bit_width() == 32 {
DpiCType::ShortReal
} else {
DpiCType::Real
});
}
if ty.is_chandle() {
return Ok(DpiCType::Chandle);
}
if ty.is_string() {
return Ok(DpiCType::Str);
}
if ty.is_unpacked_array() {
return Err(format!(
"unpacked-array DPI argument `{}` is not supported",
ty.to_sv_string()
));
}
if !ty.is_integral() {
return Err(format!(
"type `{}` has no DPI-C representation",
ty.to_sv_string()
));
}
let width = ty.value_width();
if ty.is_scalar() {
return Ok(DpiCType::Scalar);
}
if ty.is_predefined_integer() && !ty.is_four_state() {
return Ok(DpiCType::Int {
width,
signed: ty.is_signed(),
});
}
if width > WIDE_THRESHOLD {
return Err(format!(
"a {width}-bit DPI argument is wider than the {WIDE_THRESHOLD}-bit limit"
));
}
Ok(if ty.is_four_state() {
DpiCType::LogicVec(width)
} else {
DpiCType::BitVec(width)
})
}
fn llvm_type<'ctx>(self, ctx: &'ctx Context) -> BasicTypeEnum<'ctx> {
match self {
DpiCType::Scalar => ctx.i8_type().into(),
DpiCType::Int { width, .. } => int_type(ctx, width).into(),
DpiCType::Real => ctx.f64_type().into(),
DpiCType::ShortReal => ctx.f32_type().into(),
DpiCType::Chandle | DpiCType::Str | DpiCType::BitVec(_) | DpiCType::LogicVec(_) => {
ctx.ptr_type(AddressSpace::default()).into()
}
}
}
fn vec_words(self) -> u32 {
match self {
DpiCType::BitVec(w) => w.div_ceil(32),
DpiCType::LogicVec(w) => 2 * w.div_ceil(32),
_ => 0,
}
}
fn ext_attr(self) -> Option<&'static str> {
match self {
DpiCType::Scalar => Some("zeroext"),
DpiCType::Int { width, signed } if width < 32 => {
Some(if signed { "signext" } else { "zeroext" })
}
_ => None,
}
}
}
thread_local! {
static DPI_DESIGN: std::cell::RefCell<Option<DpiDesign>> =
const { std::cell::RefCell::new(None) };
}
struct DpiDesign {
trees: Vec<sv_lang::SyntaxTree>,
c_symbols: std::collections::BTreeSet<String>,
timed_export: bool,
}
pub struct DpiDesignScope(());
impl DpiDesignScope {
pub fn enter(design: &Design) -> Self {
DPI_DESIGN.with(|d| {
*d.borrow_mut() = Some(DpiDesign {
trees: design.syntax_trees(),
c_symbols: Default::default(),
timed_export: design
.dpi_exports()
.any(|e| sv_sim_sim::process::cfg::subroutine_can_suspend(&e.subroutine())),
});
});
DpiDesignScope(())
}
pub fn c_symbols(&self) -> Vec<String> {
DPI_DESIGN.with(|d| {
d.borrow()
.as_ref()
.map(|d| d.c_symbols.iter().cloned().collect())
.unwrap_or_default()
})
}
}
impl Drop for DpiDesignScope {
fn drop(&mut self) {
DPI_DESIGN.with(|d| *d.borrow_mut() = None);
}
}
pub fn dpi_import_c_name(sub: &Symbol) -> String {
use sv_lang::AstNode;
let from_syntax = DPI_DESIGN.with(|d| {
let d = d.borrow();
let d = d.as_ref()?;
d.trees.iter().find_map(|tree| {
let node = sub.syntax(tree)?;
let import = sv_lang::nodes::DPIImportSyntax::cast(node)?;
let tok = import.c_identifier()?;
(!tok.is_missing() && !tok.value_text().is_empty()).then(|| tok.value_text().to_owned())
})
});
from_syntax.unwrap_or_else(|| sub.name().to_string())
}
fn dpi_import_is_pure_task(sub: &Symbol) -> bool {
use sv_lang::AstNode;
DPI_DESIGN.with(|d| {
let d = d.borrow();
let Some(d) = d.as_ref() else { return false };
d.trees.iter().any(|tree| {
let Some(node) = sub.syntax(tree) else {
return false;
};
let Some(import) = sv_lang::nodes::DPIImportSyntax::cast(node) else {
return false;
};
let pure = import.property().is_some_and(|t| t.value_text() == "pure");
let task = import
.method()
.keyword()
.is_some_and(|t| t.value_text() == "task");
pure && task
})
})
}
fn record_c_symbol(name: &str) {
DPI_DESIGN.with(|d| {
if let Some(d) = d.borrow_mut().as_mut() {
d.c_symbols.insert(name.to_owned());
}
});
}
struct DpiCopyBack<'ctx, 'd> {
lvalue: sv_lang::Expression<'d>,
temp: PointerValue<'ctx>,
ctype: DpiCType,
formal_ty: Type<'d>,
}
pub(crate) fn lower_dpi_import_call<'ctx, 'd, 'm>(
cx: &crate::expr::LowerCtx<'ctx, 'd, 'm>,
callee: &Symbol<'d>,
call: sv_lang::Expression<'d>,
) -> Result<Option<crate::expr::LoweredValue<'ctx>>, LoweringError> {
let ctx = cx.ctx;
let err = |msg: String| {
LoweringError::unsupported_at(
&format!("DPI import `{}`: {msg}", callee.name()),
&cx.fn_sym,
)
};
if dpi_import_is_pure_task(callee) {
return Err(err(
"an imported task cannot be `pure` (LRM 35.5.1.3: only imported functions have the pure property)"
.into(),
));
}
let formals: Vec<Symbol<'d>> = dpi_formal_args(callee).collect();
let actuals = call.operands();
if formals.len() != actuals.len() {
return Err(err(format!(
"{} formal argument(s) but {} actual(s)",
formals.len(),
actuals.len()
)));
}
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let mut param_tys: Vec<BasicMetadataTypeEnum<'ctx>> = Vec::new();
let mut attrs: Vec<Option<&'static str>> = Vec::new();
let mut args: Vec<BasicMetadataValueEnum<'ctx>> = Vec::new();
let mut copy_backs = Vec::new();
let mut array_backs = Vec::new();
let mut struct_backs = Vec::new();
for (formal, actual) in formals.iter().zip(actuals) {
let ty = formal
.value_type()
.ok_or_else(|| err(format!("formal `{}` has no type", formal.name())))?;
if let Some(shape) =
array_shape(&ty).map_err(|m| err(format!("formal `{}`: {m}", formal.name())))?
{
let direction = formal.formal_argument_direction();
let lvalue = if actual.kind() == ExpressionKind::Assignment {
actual
.left()
.ok_or_else(|| err("output argument with no target".into()))?
} else {
actual
};
let (arg, back) = lower_array_arg(cx, shape, lvalue, direction)
.map_err(|e| err(format!("formal `{}`: {}", formal.name(), e.message)))?;
param_tys.push(ptr_ty.into());
attrs.push(None);
args.push(arg.into());
array_backs.extend(back);
continue;
}
if let Some(layout) =
c_struct_layout(&ty).map_err(|m| err(format!("formal `{}`: {m}", formal.name())))?
{
let direction = formal.formal_argument_direction();
let buf = alloca_c_struct(cx, &layout);
match direction {
ArgumentDirection::In => {
let value = lower_expr(cx, actual)?;
write_c_struct(cx, value, &layout, buf)?;
}
ArgumentDirection::Out | ArgumentDirection::InOut => {
let lvalue = if actual.kind() == ExpressionKind::Assignment {
actual
.left()
.ok_or_else(|| err("output argument with no target".into()))?
} else {
actual
};
if direction == ArgumentDirection::InOut {
let value = lower_expr(cx, lvalue)?;
write_c_struct(cx, value, &layout, buf)?;
}
struct_backs.push((lvalue, buf, layout));
}
ArgumentDirection::Ref => {
return Err(err(format!(
"`ref` formal `{}` is not legal in a DPI import",
formal.name()
)));
}
}
param_tys.push(ptr_ty.into());
attrs.push(None);
args.push(buf.into());
continue;
}
let ctype =
DpiCType::of(&ty).map_err(|m| err(format!("formal `{}`: {m}", formal.name())))?;
let direction = formal.formal_argument_direction();
match direction {
ArgumentDirection::In => {
let arg = if ctype == DpiCType::Str {
c_string_arg(cx, actual)?
} else {
let value = lower_expr(cx, actual)?;
to_c_input(cx, value, ctype, &ty)?
};
param_tys.push(ctype.llvm_type(ctx).into());
attrs.push(ctype.ext_attr());
args.push(arg.into());
}
ArgumentDirection::Out | ArgumentDirection::InOut => {
let lvalue = if actual.kind() == ExpressionKind::Assignment {
actual
.left()
.ok_or_else(|| err("output argument with no target".into()))?
} else {
actual
};
let temp = alloca_c_temp(cx, ctype);
if direction == ArgumentDirection::InOut {
let v = if ctype == DpiCType::Str {
c_string_arg(cx, lvalue)?
} else {
let value = lower_expr(cx, lvalue)?;
to_c_input(cx, value, ctype, &ty)?
};
match ctype {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => {
let bytes = ctx
.i64_type()
.const_int(4 * ctype.vec_words() as u64, false);
cx.b.build_memcpy(temp, 4, v.into_pointer_value(), 4, bytes)
.unwrap();
}
_ => {
cx.b.build_store(temp, v).unwrap();
}
}
} else if matches!(ctype, DpiCType::Str) {
let empty = cx.b.build_global_string_ptr("", "dpi_empty_str").unwrap();
cx.b.build_store(temp, empty.as_pointer_value()).unwrap();
}
param_tys.push(ptr_ty.into());
attrs.push(None);
args.push(temp.into());
copy_backs.push(DpiCopyBack {
lvalue,
temp,
ctype,
formal_ty: ty,
});
}
ArgumentDirection::Ref => {
return Err(err(format!(
"`ref` formal `{}` is not legal in a DPI import",
formal.name()
)));
}
}
}
let is_task = callee.subroutine_kind() == Some(sv_lang::SubroutineKind::Task);
let ret_sv = callee
.subroutine_return_type()
.filter(|t| !t.is_void() && !is_task);
let ret_ctype = match &ret_sv {
Some(t) => Some(dpi_return_ctype(t).map_err(&err)?),
None => None,
};
if matches!(ret_ctype, Some(DpiCType::BitVec(_) | DpiCType::LogicVec(_))) {
return Err(err(
"a packed-vector return type is not legal for a DPI import (LRM 35.5.5)".into(),
));
}
let fn_ty = match ret_ctype {
Some(c) => c.llvm_type(ctx).fn_type(¶m_tys, false),
None if is_task => ctx.i32_type().fn_type(¶m_tys, false),
None => ctx.void_type().fn_type(¶m_tys, false),
};
let c_name = dpi_import_c_name(callee);
let callee_fn = match cx.module.get_function(&c_name) {
Some(f) if f.get_type() == fn_ty => f,
Some(_) => {
return Err(err(format!(
"C function `{c_name}` is also imported with a different signature"
)));
}
None => {
let f = cx
.module
.add_function(&c_name, fn_ty, Some(Linkage::External));
for (i, a) in attrs.iter().enumerate() {
if let Some(name) = a {
f.add_attribute(AttributeLoc::Param(i as u32), enum_attr(ctx, name));
}
}
f
}
};
record_c_symbol(&c_name);
let context = callee
.subroutine_flags()
.contains(sv_lang::MethodFlags::DPI_CONTEXT);
if context {
set_dpi_scope(cx, callee);
}
dpi_sync(cx);
let task_scope =
is_task && DPI_DESIGN.with(|d| d.borrow().as_ref().is_some_and(|d| d.timed_export));
let scope_fn = task_scope.then(|| {
cx.module
.get_function(SV_RT_DPI_TASK_SCOPE_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DPI_TASK_SCOPE_SYMBOL,
ctx.void_type().fn_type(
&[
ctx.i32_type().into(),
ctx.ptr_type(AddressSpace::default()).into(),
],
false,
),
Some(Linkage::External),
)
})
});
if let Some(f) = scope_fn {
let name =
cx.b.build_global_string_ptr(callee.name(), "dpi_task_name")
.unwrap()
.as_pointer_value();
cx.b.build_call(
f,
&[ctx.i32_type().const_int(1, false).into(), name.into()],
"",
)
.unwrap();
}
let fn_scope = (!is_task && sv_sim_sim::process::cfg::has_function_disables()).then(|| {
cx.module
.get_function(SV_RT_DPI_FN_SCOPE_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DPI_FN_SCOPE_SYMBOL,
ctx.void_type().fn_type(&[ctx.i32_type().into()], false),
Some(Linkage::External),
)
})
});
if let Some(f) = fn_scope {
cx.b.build_call(f, &[ctx.i32_type().const_int(1, false).into()], "")
.unwrap();
}
let site = cx.b.build_call(callee_fn, &args, "dpi_call").unwrap();
if let Some(f) = fn_scope {
cx.b.build_call(f, &[ctx.i32_type().const_int(0, false).into()], "")
.unwrap();
}
if let Some(f) = scope_fn {
let null = ctx.ptr_type(AddressSpace::default()).const_null();
cx.b.build_call(
f,
&[ctx.i32_type().const_int(0, false).into(), null.into()],
"",
)
.unwrap();
}
dpi_sync(cx);
if context {
reload_shadows(cx);
}
for (i, a) in attrs.iter().enumerate() {
if let Some(name) = a {
site.add_attribute(AttributeLoc::Param(i as u32), enum_attr(ctx, name));
}
}
for back in array_backs {
copy_array_back(cx, back)?;
}
for (lvalue, buf, layout) in struct_backs {
let value = read_c_struct(cx, &layout, buf);
store_value(cx, lvalue, value)?;
}
for cb in copy_backs {
let raw = match cb.ctype {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => cb.temp.into(),
c => {
cx.b.build_load(c.llvm_type(ctx), cb.temp, "dpi_out")
.unwrap()
}
};
let value = from_c(cx, raw, cb.ctype, &cb.formal_ty)?;
store_value(cx, cb.lvalue, value)?;
}
match (ret_ctype, ret_sv) {
(Some(c), Some(t)) => {
let raw = site
.try_as_basic_value()
.basic()
.ok_or_else(|| err("the C call produced no value".into()))?;
Ok(Some(from_c(cx, raw, c, &t)?))
}
_ => Ok(None),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DpiArrayShape {
Fixed {
elem: DpiCType,
dims: usize,
st: bool,
},
Open {
elem: DpiCType,
dims: usize,
st: bool,
},
OpenPacked { four: bool },
}
pub fn array_shape(ty: &Type) -> Result<Option<DpiArrayShape>, String> {
let open = ty.canonical().as_symbol().kind() == sv_lang::kinds::SymbolKind::DPIOpenArrayType;
if open && ty.dpi_open_array_is_packed() {
return Ok(Some(DpiArrayShape::OpenPacked {
four: ty.is_four_state(),
}));
}
if !open && !ty.is_unpacked_array() {
return Ok(None);
}
let is_array = |t: &Type| {
t.is_unpacked_array()
|| t.canonical().as_symbol().kind() == sv_lang::kinds::SymbolKind::DPIOpenArrayType
};
let mut dims = 1;
let mut elem = ty
.element_type()
.ok_or_else(|| "an array formal with no element type".to_owned())?;
while is_array(&elem) {
dims += 1;
elem = elem
.element_type()
.ok_or_else(|| "an array formal with no element type".to_owned())?;
}
if dims > crate::dpi::OPEN_MAX_DIMS {
return Err(format!(
"an unpacked DPI array with {dims} dimensions is not supported (at most {})",
crate::dpi::OPEN_MAX_DIMS
));
}
let st = elem.is_unpacked_struct();
let elem = if st {
c_struct_layout(&elem)?;
DpiCType::Chandle
} else {
DpiCType::of(&elem)?
};
Ok(Some(if open {
DpiArrayShape::Open { elem, dims, st }
} else {
DpiArrayShape::Fixed { elem, dims, st }
}))
}
impl DpiCType {
fn c_size(self) -> u32 {
match self {
DpiCType::Scalar => 1,
DpiCType::Int { width, .. } => width / 8,
DpiCType::Real | DpiCType::Chandle | DpiCType::Str => 8,
DpiCType::ShortReal => 4,
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => 4 * self.vec_words(),
}
}
fn open_kind(self) -> u32 {
match self {
DpiCType::BitVec(_) => 1,
DpiCType::LogicVec(_) => 2,
DpiCType::Scalar => 3,
_ => 0,
}
}
}
enum ArrayBack<'ctx, 'd> {
Memory {
buf: PointerValue<'ctx>,
rows: MemRows<'ctx>,
elem: DpiCType,
elem_ty: Type<'d>,
layout: Option<CStructLayout>,
},
Packed {
buf: PointerValue<'ctx>,
ctype: DpiCType,
lvalue: sv_lang::Expression<'d>,
ty: Type<'d>,
},
Coll {
buf: PointerValue<'ctx>,
sp: PointerValue<'ctx>,
n: IntValue<'ctx>,
coll: crate::collections::Coll<'d>,
elem: DpiCType,
lvalue: sv_lang::Expression<'d>,
copy_out: bool,
},
}
#[derive(Clone, Copy)]
struct MemRows<'ctx> {
r: crate::arrays::ArrayRef<'ctx>,
width: u32,
len: u64,
}
pub const OPEN_MAX_DIMS: usize = 8;
fn open_array_type(ctx: &Context) -> inkwell::types::StructType<'_> {
let i32t = ctx.i32_type();
let mut fields: Vec<BasicTypeEnum<'_>> = vec![ctx.ptr_type(AddressSpace::default()).into()];
fields.extend(std::iter::repeat_n::<BasicTypeEnum<'_>>(
i32t.into(),
6 + 2 * OPEN_MAX_DIMS,
));
ctx.struct_type(&fields, false)
}
fn build_open_array<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
data: PointerValue<'ctx>,
head: [u32; 5],
dims: &[(i64, i64)],
) -> PointerValue<'ctx> {
let ty = open_array_type(cx.ctx);
let d = entry_alloca(cx, ty, "dpi_open_array");
let f0 = cx.b.build_struct_gep(ty, d, 0, "oa_data").unwrap();
cx.b.build_store(f0, data).unwrap();
let mut fields: Vec<u32> = head.to_vec();
fields.push(dims.len() as u32);
for k in 0..OPEN_MAX_DIMS {
let (l, r) = dims.get(k).copied().unwrap_or((0, 0));
fields.push(l as i32 as u32);
fields.push(r as i32 as u32);
}
for (i, v) in fields.iter().enumerate() {
let f =
cx.b.build_struct_gep(ty, d, i as u32 + 1, "oa_field")
.unwrap();
cx.b.build_store(f, cx.ctx.i32_type().const_int(u64::from(*v), false))
.unwrap();
}
d
}
fn lower_array_arg<'ctx, 'd, 'm>(
cx: &crate::expr::LowerCtx<'ctx, 'd, 'm>,
shape: DpiArrayShape,
lvalue: sv_lang::Expression<'d>,
direction: ArgumentDirection,
) -> Result<(PointerValue<'ctx>, Option<ArrayBack<'ctx, 'd>>), LoweringError> {
let fail = |m: &str| LoweringError::unsupported_at(m, &cx.fn_sym);
let ty = lvalue
.expr_type()
.ok_or_else(|| fail("array actual has no type"))?;
let copy_in = direction != ArgumentDirection::Out;
let copy_out = direction != ArgumentDirection::In;
if let DpiArrayShape::OpenPacked { four } = shape {
let width = ty.value_width();
let ctype = if four {
DpiCType::LogicVec(width)
} else {
DpiCType::BitVec(width)
};
let buf = entry_alloca(
cx,
cx.ctx.i32_type().array_type(ctype.vec_words()),
"dpi_open_packed",
);
if copy_in {
write_c_vec(cx, lower_expr(cx, lvalue)?, ctype, &ty, buf)?;
} else {
cx.b.build_store(
buf,
cx.ctx.i32_type().array_type(ctype.vec_words()).const_zero(),
)
.unwrap();
}
let r = ty.bit_vector_range();
let h = build_open_array(
cx,
buf,
[
ctype.c_size(),
ctype.open_kind(),
width,
r.left as u32,
r.right as u32,
],
&[],
);
let back = copy_out.then_some(ArrayBack::Packed {
buf,
ctype,
lvalue,
ty,
});
return Ok((h, back));
}
let (DpiArrayShape::Fixed { elem, dims, st } | DpiArrayShape::Open { elem, dims, st }) = shape
else {
unreachable!()
};
if let DpiArrayShape::Open {
elem,
dims: 1,
st: false,
} = shape
&& let Some(coll) = crate::collections::coll_of_expr(&lvalue)
&& !coll.assoc
{
return lower_coll_array_arg(cx, coll, elem, lvalue, direction);
}
let (r, actual) = crate::arrays::array_target(cx, lvalue)?.ok_or_else(|| {
fail("an unpacked-array DPI argument must be a stored array (a module or local array)")
})?;
if actual.dims.len() != dims {
return Err(fail(&format!(
"an unpacked-array DPI argument has {} dimensions, its formal {dims}",
actual.dims.len()
)));
}
if r.valid.get_zero_extended_constant() != Some(1) {
return Err(fail(
"an unpacked-array DPI argument selected with a non-constant index",
));
}
let elem_ty = actual.elem;
if !(elem_ty.is_integral()
|| elem_ty.is_floating()
|| elem_ty.is_string()
|| elem_ty.is_chandle()
|| st)
{
return Err(fail("an array of unsupported elements"));
}
let layout = if st {
c_struct_layout(&elem_ty).map_err(|m| fail(&m))?
} else {
None
};
let row_width = match r.backing {
crate::arrays::Backing::Memory(info) => info.elem_width,
crate::arrays::Backing::Local(_) => elem_ty.value_width(),
};
let len = actual.rows();
let rows = MemRows {
r,
width: row_width,
len,
};
let size = layout.as_ref().map_or_else(|| elem.c_size(), |l| l.size);
let buf = entry_alloca(
cx,
cx.ctx.i8_type().array_type((u64::from(size) * len) as u32),
"dpi_array",
);
buf.as_instruction()
.expect("an alloca")
.set_alignment(8)
.unwrap();
if copy_in {
let bytes = cx.ctx.i64_type().const_int(u64::from(size) * len, false);
cx.b.build_memset(buf, 8, cx.ctx.i8_type().const_zero(), bytes)
.unwrap();
for_each_row(cx, rows, |k, row| {
let raw = crate::arrays::load_raw(cx, rows.r.backing, row, rows.width)?;
let pv = PackedValue {
llvm: raw,
width: rows.width,
repr: cx.repr,
signed: elem_ty.is_signed(),
};
let dst = elem_ptr(cx, buf, k, size);
if let Some(l) = &layout {
write_c_struct(cx, crate::expr::LoweredValue::Packed(pv), l, dst)
} else {
write_c_elem(cx, pv, elem, &elem_ty, dst)
}
})?;
} else {
let bytes = cx.ctx.i64_type().const_int(u64::from(size) * len, false);
cx.b.build_memset(buf, 8, cx.ctx.i8_type().const_zero(), bytes)
.unwrap();
}
let arg = match shape {
DpiArrayShape::Open { .. } => {
let pr = elem_ty.bit_vector_range();
build_open_array(
cx,
buf,
[
size,
elem.open_kind(),
row_width,
pr.left as u32,
pr.right as u32,
],
&actual.dims,
)
}
_ => buf,
};
let back = copy_out.then_some(ArrayBack::Memory {
buf,
rows,
elem,
elem_ty,
layout,
});
Ok((arg, back))
}
fn lower_coll_array_arg<'ctx, 'd, 'm>(
cx: &crate::expr::LowerCtx<'ctx, 'd, 'm>,
coll: crate::collections::Coll<'d>,
elem: DpiCType,
lvalue: sv_lang::Expression<'d>,
direction: ArgumentDirection,
) -> Result<(PointerValue<'ctx>, Option<ArrayBack<'ctx, 'd>>), LoweringError> {
let fail = |m: &str| LoweringError::unsupported_at(m, &cx.fn_sym);
let elem_ty = coll.elem;
if coll.obj
|| coll.leaky
|| !(elem_ty.is_integral() || elem_ty.is_floating())
|| elem_ty.value_width() > 64 * 64
{
return Err(fail(
"a queue or dynamic array actual of an open-array formal must hold integral or real elements",
));
}
let i64t = cx.ctx.i64_type();
let copy_in = direction != ArgumentDirection::Out;
let copy_out = direction != ArgumentDirection::In;
let width = elem_ty.value_width();
let size = elem.c_size();
let h = crate::collections::handle_of(cx, lvalue)?;
let n = crate::collections::size_of_handle(cx, &coll, h);
let n =
cx.b.build_int_z_extend_or_bit_cast(n, i64t, "dpi_n")
.unwrap();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let save = inkwell::intrinsics::Intrinsic::find("llvm.stacksave")
.and_then(|i| i.get_declaration(cx.module, &[ptr_ty.into()]))
.expect("llvm.stacksave");
let sp =
cx.b.build_call(save, &[], "dpi_sp")
.unwrap()
.try_as_basic_value()
.basic()
.expect("stacksave returns a pointer")
.into_pointer_value();
let bytes =
cx.b.build_int_mul(n, i64t.const_int(u64::from(size), false), "dpi_bytes")
.unwrap();
let buf =
cx.b.build_array_alloca(cx.ctx.i8_type(), bytes, "dpi_coll_buf")
.unwrap();
buf.as_instruction()
.expect("an alloca")
.set_alignment(8)
.unwrap();
cx.b.build_memset(buf, 8, cx.ctx.i8_type().const_zero(), bytes)
.unwrap();
if copy_in {
counted_loop(cx, n, |k| {
let v = crate::collections::read_at_coll(cx, &coll, h, k);
let pv = crate::real::to_storage(cx, v, width, "an open-array element")?;
write_c_elem(cx, pv, elem, &elem_ty, elem_ptr(cx, buf, k, size))
})?;
}
let pr = elem_ty.bit_vector_range();
let d = build_open_array(
cx,
buf,
[
size,
elem.open_kind(),
width,
pr.left as u32,
pr.right as u32,
],
&[(0, -1)],
);
let last =
cx.b.build_int_sub(n, i64t.const_int(1, false), "dpi_last")
.unwrap();
let last =
cx.b.build_int_truncate(last, cx.ctx.i32_type(), "dpi_last32")
.unwrap();
let ty = open_array_type(cx.ctx);
let f = cx.b.build_struct_gep(ty, d, 8, "oa_right0").unwrap();
cx.b.build_store(f, last).unwrap();
Ok((
d,
Some(ArrayBack::Coll {
buf,
sp,
n,
coll,
elem,
lvalue,
copy_out,
}),
))
}
fn copy_array_back<'ctx, 'd, 'm>(
cx: &crate::expr::LowerCtx<'ctx, 'd, 'm>,
back: ArrayBack<'ctx, 'd>,
) -> Result<(), LoweringError> {
match back {
ArrayBack::Packed {
buf,
ctype,
lvalue,
ty,
} => {
let v = from_c(cx, buf.into(), ctype, &ty)?;
store_value(cx, lvalue, v)
}
ArrayBack::Coll {
buf,
sp,
n,
coll,
elem,
lvalue,
copy_out,
} => {
if copy_out {
let i64t = cx.ctx.i64_type();
let slot = entry_alloca(cx, i64t, "dpi_coll_h");
cx.b.build_store(slot, crate::collections::handle_of(cx, lvalue)?)
.unwrap();
let size = elem.c_size();
let queue = i64t.const_int(u64::from(coll.queue), false);
counted_loop(cx, n, |k| {
let src = elem_ptr(cx, buf, k, size);
let raw = match elem {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => src.into(),
c => {
cx.b.build_load(c.llvm_type(cx.ctx), src, "dpi_elem")
.unwrap()
}
};
let v = from_c(cx, raw, elem, &coll.elem)?;
let val = crate::collections::elem_buf(cx, &coll, v)?;
let h = cx.b.build_load(i64t, slot, "h").unwrap().into_int_value();
let new = crate::collections::call(
cx,
&coll,
sv_sim_runtime::collections_rt::CollOp::Set,
h,
crate::collections::Args {
index: Some(k),
index2: Some(queue),
val: Some(val),
..Default::default()
},
);
cx.b.build_store(slot, new).unwrap();
Ok(())
})?;
let h = cx.b.build_load(i64t, slot, "h").unwrap().into_int_value();
crate::collections::set_handle(cx, lvalue, h)?;
}
let restore = inkwell::intrinsics::Intrinsic::find("llvm.stackrestore")
.and_then(|i| {
i.get_declaration(
cx.module,
&[cx.ctx.ptr_type(AddressSpace::default()).into()],
)
})
.expect("llvm.stackrestore");
cx.b.build_call(restore, &[sp.into()], "").unwrap();
Ok(())
}
ArrayBack::Memory {
buf,
rows,
elem,
elem_ty,
layout,
} => {
let size = layout.as_ref().map_or_else(|| elem.c_size(), |l| l.size);
for_each_row(cx, rows, |k, row| {
let src = elem_ptr(cx, buf, k, size);
if let Some(l) = &layout {
let v = read_c_struct(cx, l, src);
let bits = crate::real::to_storage(cx, v, rows.width, "array element")?;
return crate::arrays::store_raw(
cx,
rows.r.backing,
row,
bits.llvm,
rows.width,
);
}
let raw = match elem {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => src.into(),
c => {
cx.b.build_load(c.llvm_type(cx.ctx), src, "dpi_elem")
.unwrap()
}
};
let v = from_c(cx, raw, elem, &elem_ty)?;
let bits = crate::real::to_storage(cx, v, rows.width, "array element")?;
crate::arrays::store_raw(cx, rows.r.backing, row, bits.llvm, rows.width)
})
}
}
}
fn write_c_elem<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
pv: PackedValue<'ctx>,
elem: DpiCType,
elem_ty: &Type<'d>,
dst: PointerValue<'ctx>,
) -> Result<(), LoweringError> {
let value = if elem_ty.is_string() {
crate::expr::LoweredValue::Str(crate::strings::from_storage(cx, pv))
} else {
stored_value(cx, pv.llvm, elem_ty)
};
match elem {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => write_c_vec(cx, value, elem, elem_ty, dst),
c => {
let v = to_c_input(cx, value, c, elem_ty)?;
cx.b.build_store(dst, v).unwrap();
Ok(())
}
}
}
fn elem_ptr<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
k: IntValue<'ctx>,
size: u32,
) -> PointerValue<'ctx> {
let off =
cx.b.build_int_mul(
k,
cx.ctx.i64_type().const_int(u64::from(size), false),
"elem_off",
)
.unwrap();
unsafe {
cx.b.build_in_bounds_gep(cx.ctx.i8_type(), buf, &[off], "elem_ptr")
}
.unwrap()
}
fn for_each_row<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
rows: MemRows<'ctx>,
mut body: impl FnMut(IntValue<'ctx>, IntValue<'ctx>) -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let len = cx.ctx.i64_type().const_int(rows.len, false);
counted_loop(cx, len, |kv| {
let row = cx.b.build_int_add(rows.r.base, kv, "row").unwrap();
body(kv, row)
})
}
fn counted_loop<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
len: IntValue<'ctx>,
mut body: impl FnMut(IntValue<'ctx>) -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let i64t = cx.ctx.i64_type();
let pre = cx.b.get_insert_block().expect("positioned");
let func = pre.get_parent().expect("inside a function");
let head = cx.ctx.append_basic_block(func, "dpi_rows");
let body_bb = cx.ctx.append_basic_block(func, "dpi_row");
let done = cx.ctx.append_basic_block(func, "dpi_rows_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let k = cx.b.build_phi(i64t, "k").unwrap();
k.add_incoming(&[(&i64t.const_zero(), pre)]);
let kv = k.as_basic_value().into_int_value();
let more =
cx.b.build_int_compare(inkwell::IntPredicate::ULT, kv, len, "more")
.unwrap();
cx.b.build_conditional_branch(more, body_bb, done).unwrap();
cx.b.position_at_end(body_bb);
body(kv)?;
let next =
cx.b.build_int_add(kv, i64t.const_int(1, false), "k_next")
.unwrap();
let latch = cx.b.get_insert_block().expect("positioned");
k.add_incoming(&[(&next, latch)]);
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(done);
Ok(())
}
fn enum_attr(ctx: &Context, name: &str) -> inkwell::attributes::Attribute {
ctx.create_enum_attribute(
inkwell::attributes::Attribute::get_named_enum_kind_id(name),
0,
)
}
fn alloca_c_temp<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
ctype: DpiCType,
) -> PointerValue<'ctx> {
match ctype {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => {
let ty = cx.ctx.i32_type().array_type(ctype.vec_words());
let p = entry_alloca(cx, ty, "dpi_vec_out");
cx.b.build_store(p, ty.const_zero()).unwrap();
p
}
c => {
let ty = c.llvm_type(cx.ctx);
let p = entry_alloca(cx, ty, "dpi_out_tmp");
cx.b.build_store(p, ty.const_zero()).unwrap();
p
}
}
}
fn to_c_input<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
value: crate::expr::LoweredValue<'ctx>,
ctype: DpiCType,
ty: &Type<'d>,
) -> Result<BasicValueEnum<'ctx>, LoweringError> {
use crate::expr::LoweredValue;
let ctx = cx.ctx;
let op = OpBuilder { ctx, b: &cx.b };
let packed = |v: LoweredValue<'ctx>| -> Result<PackedValue<'ctx>, LoweringError> {
let pv = v.into_packed("a >128-bit DPI argument", &cx.fn_sym)?;
Ok(crate::select::fit_width(&op, pv, ty.value_width()))
};
Ok(match ctype {
DpiCType::Real | DpiCType::ShortReal => {
let f = match value {
LoweredValue::Real(r) => crate::real::as_f64(cx, r.llvm),
other => {
let pv = other.into_packed("a >128-bit value passed as real", &cx.fn_sym)?;
let raw = op.lane_val(op.to_2state4(pv));
if pv.signed {
cx.b.build_signed_int_to_float(raw, ctx.f64_type(), "itof")
.unwrap()
} else {
cx.b.build_unsigned_int_to_float(raw, ctx.f64_type(), "utof")
.unwrap()
}
}
};
if ctype == DpiCType::ShortReal {
cx.b.build_float_trunc(f, ctx.f32_type(), "dpi_shortreal")
.unwrap()
.into()
} else {
f.into()
}
}
DpiCType::Scalar => {
let pv = packed(value)?;
let val =
cx.b.build_int_z_extend_or_bit_cast(op.lane_val(pv), ctx.i8_type(), "sv_val")
.unwrap();
let unk =
cx.b.build_int_z_extend_or_bit_cast(op.lane_unk(pv), ctx.i8_type(), "sv_unk")
.unwrap();
let two_unk =
cx.b.build_left_shift(unk, ctx.i8_type().const_int(1, false), "sv_unk2")
.unwrap();
let lane = cx.b.build_or(val, two_unk, "sv_lane").unwrap();
svlogic_swap_xz(&cx.b, lane).into()
}
DpiCType::Int { width, .. } => {
let pv = packed(value)?;
let known = op.lane_val(op.to_2state4(pv));
op.resize_unsigned(known, pv.width, width).into()
}
DpiCType::Chandle => {
let pv = packed(value)?;
let bits = op.resize_unsigned(op.lane_val(pv), pv.width, 64);
cx.b.build_int_to_ptr(bits, ctx.ptr_type(AddressSpace::default()), "dpi_chandle")
.unwrap()
.into()
}
DpiCType::Str => {
let s = crate::strings::to_string(cx, value)?;
crate::strings::c_str(cx, s).into()
}
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => {
let words = ctype.vec_words();
let buf = entry_alloca(cx, ctx.i32_type().array_type(words), "dpi_vec_in");
write_c_vec(cx, value, ctype, ty, buf)?;
buf.into()
}
})
}
fn write_c_vec<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
value: crate::expr::LoweredValue<'ctx>,
ctype: DpiCType,
ty: &Type<'d>,
buf: PointerValue<'ctx>,
) -> Result<(), LoweringError> {
use crate::expr::LoweredValue;
let ctx = cx.ctx;
let op = OpBuilder { ctx, b: &cx.b };
let packed = |v: LoweredValue<'ctx>| -> Result<PackedValue<'ctx>, LoweringError> {
let pv = v.into_packed("a >128-bit DPI argument", &cx.fn_sym)?;
Ok(crate::select::fit_width(&op, pv, ty.value_width()))
};
let (DpiCType::BitVec(width) | DpiCType::LogicVec(width)) = ctype else {
unreachable!("write_c_vec is for vector C types")
};
{
{
let four = matches!(ctype, DpiCType::LogicVec(_));
let store_word = |idx: u32, w: IntValue<'ctx>| {
let p = unsafe {
cx.b.build_in_bounds_gep(
ctx.i32_type(),
buf,
&[ctx.i32_type().const_int(idx as u64, false)],
"dpi_word",
)
}
.unwrap();
cx.b.build_store(p, w).unwrap();
};
match value {
LoweredValue::Wide(wref) => {
for j in 0..width.div_ceil(32) {
let (aval, bval) = op.wide_load_limb(wref, j / 2);
let sh = ctx.i64_type().const_int(32 * (j % 2) as u64, false);
let half = |x: IntValue<'ctx>| {
let s = cx.b.build_right_shift(x, sh, false, "limb_sh").unwrap();
cx.b.build_int_truncate(s, ctx.i32_type(), "limb_w")
.unwrap()
};
if four {
store_word(2 * j, half(aval));
store_word(2 * j + 1, half(bval));
} else {
let known = cx.b.build_and(aval, op.not(bval), "known").unwrap();
store_word(j, half(known));
}
}
}
other => {
let pv = packed(other)?;
let (val, unk) = (op.lane_val(pv), op.lane_unk(pv));
let (aval, bval) = if four {
(op.xor(val, unk), unk)
} else {
(op.and(val, op.not(unk)), op.i_n(width).const_zero())
};
for j in 0..width.div_ceil(32) {
let word = |x: IntValue<'ctx>| {
let s = if j == 0 {
x
} else {
cx.b.build_right_shift(
x,
op.i_n(width).const_int(32 * j as u64, false),
false,
"w_sh",
)
.unwrap()
};
op.resize_unsigned(s, width, 32)
};
if four {
store_word(2 * j, word(aval));
store_word(2 * j + 1, word(bval));
} else {
store_word(j, word(aval));
}
}
}
}
}
}
Ok(())
}
fn c_string_arg<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
actual: sv_lang::Expression<'d>,
) -> Result<BasicValueEnum<'ctx>, LoweringError> {
if let Some(text) = string_constant(actual) {
return Ok(cx
.b
.build_global_string_ptr(&text, "dpi_str")
.unwrap()
.as_pointer_value()
.into());
}
let s = crate::strings::to_string(cx, lower_expr(cx, actual)?)?;
Ok(crate::strings::c_str(cx, s).into())
}
fn string_constant(e: sv_lang::Expression) -> Option<String> {
if let Some(s) = e.string_literal_value() {
return Some(s);
}
if e.kind() == ExpressionKind::Conversion
&& let Some(inner) = e.conversion_operand()
&& let Some(s) = string_constant(inner)
{
return Some(s);
}
match e.constant_value()? {
sv_lang::ConstantValue::String(s) => Some(s),
_ => None,
}
}
fn from_c<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
raw: BasicValueEnum<'ctx>,
ctype: DpiCType,
ty: &Type<'d>,
) -> Result<crate::expr::LoweredValue<'ctx>, LoweringError> {
use crate::expr::LoweredValue;
let ctx = cx.ctx;
let op = OpBuilder { ctx, b: &cx.b };
let width = ty.value_width();
let signed = ty.is_signed();
let known = |val: IntValue<'ctx>, w: u32| {
LoweredValue::Packed(op.pack(val, op.i_n(w).const_zero(), w, cx.repr, signed))
};
Ok(match ctype {
DpiCType::Real | DpiCType::ShortReal => {
let f = crate::real::as_f64(cx, raw.into_float_value());
LoweredValue::Real(crate::value::RealValue { llvm: f })
}
DpiCType::Scalar => {
let b =
cx.b.build_and(
raw.into_int_value(),
ctx.i8_type().const_int(3, false),
"svlogic",
)
.unwrap();
let lane = svlogic_swap_xz(&cx.b, b);
let val = cx.b.build_int_truncate(lane, op.i_n(1), "sv_val").unwrap();
let hi =
cx.b.build_right_shift(lane, ctx.i8_type().const_int(1, false), false, "sv_hi")
.unwrap();
let unk = cx.b.build_int_truncate(hi, op.i_n(1), "sv_unk").unwrap();
let unk = if cx.repr == ValueRepr::TwoState || !ty.is_four_state() {
op.i_n(1).const_zero()
} else {
unk
};
let val = if cx.repr == ValueRepr::TwoState || !ty.is_four_state() {
let not_unk = op.not(cx.b.build_int_truncate(hi, op.i_n(1), "hi1").unwrap());
op.and(val, not_unk)
} else {
val
};
LoweredValue::Packed(op.pack(val, unk, 1, cx.repr, signed))
}
DpiCType::Int { width: w, .. } => {
let v = raw.into_int_value();
let v = if signed && width > w {
cx.b.build_int_s_extend(v, op.i_n(width), "dpi_sext")
.unwrap()
} else {
op.resize_unsigned(v, w, width)
};
known(v, width)
}
DpiCType::Chandle => {
let bits =
cx.b.build_ptr_to_int(raw.into_pointer_value(), ctx.i64_type(), "chandle_bits")
.unwrap();
known(op.resize_unsigned(bits, 64, width), width)
}
DpiCType::Str => {
crate::expr::LoweredValue::Str(crate::strings::intern_c(cx, raw.into_pointer_value()))
}
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => {
let buf = raw.into_pointer_value();
let four = matches!(ctype, DpiCType::LogicVec(_));
let load_word = |idx: u32| {
let p = unsafe {
cx.b.build_in_bounds_gep(
ctx.i32_type(),
buf,
&[ctx.i32_type().const_int(idx as u64, false)],
"dpi_word",
)
}
.unwrap();
let w =
cx.b.build_load(ctx.i32_type(), p, "dpi_word_ld")
.unwrap()
.into_int_value();
op.resize_unsigned(w, 32, width.max(32))
};
let acc_w = width.max(32);
let mut aval = op.i_n(acc_w).const_zero();
let mut bval = op.i_n(acc_w).const_zero();
for j in 0..width.div_ceil(32) {
let sh = op.i_n(acc_w).const_int(32 * j as u64, false);
let place = |w: IntValue<'ctx>| cx.b.build_left_shift(w, sh, "w_place").unwrap();
if four {
aval = op.or(aval, place(load_word(2 * j)));
bval = op.or(bval, place(load_word(2 * j + 1)));
} else {
aval = op.or(aval, place(load_word(j)));
}
}
let aval = op.resize_unsigned(aval, acc_w, width);
let bval = op.resize_unsigned(bval, acc_w, width);
if four && cx.repr == ValueRepr::FourState {
let val = op.xor(aval, bval);
LoweredValue::Packed(op.pack(val, bval, width, cx.repr, signed))
} else if four {
known(op.and(aval, op.not(bval)), width)
} else {
known(aval, width)
}
}
})
}
#[derive(Debug, Clone, Copy)]
struct CStructField {
lo: u32,
bits: u32,
off: u32,
size: u32,
is_string: bool,
}
#[derive(Debug, Clone)]
struct CStructLayout {
fields: Vec<CStructField>,
size: u32,
width: u32,
}
fn c_struct_fields(
ty: &Type,
base_lo: u32,
base_off: u32,
out: &mut Vec<CStructField>,
) -> Result<(u32, u32), String> {
use sv_sim_sim::state::storage;
let (mut cur, mut max_align) = (0u32, 1u32);
for f in ty.fields() {
let fty = f
.value_type()
.ok_or_else(|| format!("member `{}` has no type", f.name()))?;
let fty = match fty.enum_base() {
Some(b) if fty.is_enum() => b,
_ => fty,
};
let (lo, _) = storage::field_slice(ty, f.field_index())
.ok_or_else(|| format!("member `{}` has no stored form", f.name()))?;
let lo = base_lo + lo as u32;
let (size, align, bits) = if fty.is_unpacked_struct() {
let at = cur;
let mut inner = Vec::new();
let (s, a) = c_struct_fields(&fty, lo, 0, &mut inner)?;
let off = at.next_multiple_of(a);
out.extend(inner.into_iter().map(|mut x| {
x.off += base_off + off;
x
}));
cur = off + s;
max_align = max_align.max(a);
continue;
} else if fty.is_floating() {
let b = fty.bit_width() as u32;
(b / 8, b / 8, b)
} else if fty.is_chandle() || fty.is_string() {
(8, 8, 64)
} else if fty.is_integral()
&& !fty.is_four_state()
&& (fty.is_predefined_integer() || fty.is_scalar())
{
if fty.is_scalar() {
(1, 1, 1)
} else {
let b = fty.bit_width() as u32;
(b / 8, b / 8, b)
}
} else {
return Err(format!(
"member `{}` of type `{}` is not supported in an unpacked-struct DPI \
argument (only byte/shortint/int/longint, bit, real, shortreal, chandle, \
string and nested structs of those)",
f.name(),
fty.to_sv_string()
));
};
let off = cur.next_multiple_of(align);
out.push(CStructField {
lo,
bits,
off: base_off + off,
size,
is_string: fty.is_string(),
});
cur = off + size;
max_align = max_align.max(align);
}
Ok((cur.next_multiple_of(max_align), max_align))
}
fn dpi_return_ctype(t: &Type) -> Result<DpiCType, String> {
if t.is_unpacked_struct() || t.is_unpacked_array() {
return Err(format!(
"return type `{}` is not a legal DPI return type: results are restricted to small values (LRM 35.5.5)",
t.to_sv_string()
));
}
DpiCType::of(t).map_err(|m| format!("return type: {m}"))
}
const STRUCT_MAX_BITS: u32 = 2048;
fn c_struct_layout(ty: &Type) -> Result<Option<CStructLayout>, String> {
if !ty.is_unpacked_struct() {
return Ok(None);
}
let width = ty.value_width();
if width > STRUCT_MAX_BITS {
return Err(format!(
"an unpacked-struct DPI argument of {width} bits (limit {STRUCT_MAX_BITS})"
));
}
let mut fields = Vec::new();
let (size, _) = c_struct_fields(ty, 0, 0, &mut fields)?;
Ok(Some(CStructLayout {
fields,
size,
width,
}))
}
fn alloca_c_struct<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
layout: &CStructLayout,
) -> PointerValue<'ctx> {
let ty = cx.ctx.i64_type().array_type(layout.size.div_ceil(8).max(1));
let p = entry_alloca(cx, ty, "dpi_struct");
cx.b.build_store(p, ty.const_zero()).unwrap();
p
}
fn c_struct_field_ptr<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
off: u32,
) -> PointerValue<'ctx> {
unsafe {
cx.b.build_in_bounds_gep(
cx.ctx.i8_type(),
buf,
&[cx.ctx.i32_type().const_int(u64::from(off), false)],
"dpi_struct_member",
)
}
.unwrap()
}
fn write_c_struct<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
value: crate::expr::LoweredValue<'ctx>,
layout: &CStructLayout,
buf: PointerValue<'ctx>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let pv = value.into_packed("a >128-bit DPI struct argument", &cx.fn_sym)?;
let pv = crate::select::fit_width(&op, pv, layout.width);
let all = op.lane_val(op.to_2state4(pv));
for f in &layout.fields {
let sh = all.get_type().const_int(u64::from(f.lo), false);
let shifted = cx.b.build_right_shift(all, sh, false, "st_sh").unwrap();
let narrow = op.resize_unsigned(shifted, layout.width, f.bits);
let c = op.resize_unsigned(narrow, f.bits, f.size * 8);
let p = c_struct_field_ptr(cx, buf, f.off);
let st = if f.is_string {
let id = crate::strings::StrValue { llvm: c };
cx.b.build_store(p, crate::strings::c_str(cx, id)).unwrap()
} else {
cx.b.build_store(p, c).unwrap()
};
st.set_alignment(1).unwrap();
}
Ok(())
}
fn read_c_struct<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
layout: &CStructLayout,
buf: PointerValue<'ctx>,
) -> crate::expr::LoweredValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let w = layout.width;
let mut acc = op.i_n(w).const_zero();
for f in &layout.fields {
let p = c_struct_field_ptr(cx, buf, f.off);
let ld = if f.is_string {
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let ld = cx.b.build_load(ptr_ty, p, "st_ld_str").unwrap();
inkwell::values::BasicValue::as_instruction_value(&ld)
.unwrap()
.set_alignment(1)
.unwrap();
crate::strings::intern_c(cx, ld.into_pointer_value())
.llvm
.into()
} else {
let ld =
cx.b.build_load(int_type(cx.ctx, f.size * 8), p, "st_ld")
.unwrap();
inkwell::values::BasicValue::as_instruction_value(&ld)
.unwrap()
.set_alignment(1)
.unwrap();
ld
};
let narrow = op.resize_unsigned(ld.into_int_value(), f.size * 8, f.bits);
let wide = op.resize_unsigned(narrow, f.bits, w);
let sh = op.i_n(w).const_int(u64::from(f.lo), false);
let placed = cx.b.build_left_shift(wide, sh, "st_place").unwrap();
acc = op.or(acc, placed);
}
crate::expr::LoweredValue::Packed(op.pack(acc, op.i_n(w).const_zero(), w, cx.repr, false))
}
fn store_value<'ctx, 'd>(
cx: &crate::expr::LowerCtx<'ctx, 'd, '_>,
lvalue: sv_lang::Expression<'d>,
value: crate::expr::LoweredValue<'ctx>,
) -> Result<(), LoweringError> {
let width = lvalue
.expr_type()
.map(|t| t.value_width())
.filter(|w| *w > 0)
.ok_or_else(|| {
LoweringError::unsupported_at("DPI output target has no width", &cx.fn_sym)
})?;
let bits = crate::real::to_storage(cx, value, width, "a >128-bit DPI output")?;
crate::select::lower_assign_to(cx, lvalue, bits)
}
fn set_dpi_scope<'ctx>(cx: &crate::expr::LowerCtx<'ctx, '_, '_>, callee: &Symbol) {
let ctx = cx.ctx;
let ptr = ctx.ptr_type(AddressSpace::default());
let f = cx
.module
.get_function(SV_RT_DPI_SET_SCOPE_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DPI_SET_SCOPE_SYMBOL,
ctx.void_type().fn_type(&[ptr.into(), ptr.into()], false),
Some(Linkage::External),
)
});
let handle = cx
.mem_accessor
.map(|a| a.handle)
.unwrap_or_else(|| ptr.const_null());
let scope_name = callee
.hierarchical_path()
.rsplit_once('.')
.map(|(scope, _)| scope.to_owned())
.unwrap_or_default();
let name =
cx.b.build_global_string_ptr(&scope_name, "dpi_scope_name")
.unwrap();
cx.b.build_call(f, &[handle.into(), name.as_pointer_value().into()], "")
.unwrap();
}
pub(crate) fn reload_shadows(cx: &crate::expr::LowerCtx) {
let Some(wt) = &cx.write_through else {
return;
};
let mut shadows: Vec<_> = wt.signals.iter().collect();
shadows.sort_by_key(|(_, (slot, _, _))| *slot);
for (ptr, (slot, width, wide)) in shadows {
let raw = if *wide {
crate::accessor::build_get_wide(cx, wt.fns, wt.handle, *slot, *width)
} else {
crate::accessor::build_get_scalar(cx, wt.fns, wt.handle, *slot, *width)
};
cx.b.build_store(*ptr, raw).unwrap();
}
}
pub(crate) fn reaches_context_import(sub: &Symbol) -> bool {
let mut seen = std::collections::HashSet::new();
let mut stack = vec![*sub];
while let Some(s) = stack.pop() {
if !seen.insert(s.uid()) {
continue;
}
let flags = s.subroutine_flags();
if flags.contains(sv_lang::MethodFlags::DPI_IMPORT) {
if flags.contains(sv_lang::MethodFlags::DPI_CONTEXT) {
return true;
}
continue;
}
let Some(body) = sv_sim_sim::process::cfg::proc_body(&s) else {
continue;
};
let mut nodes = vec![body.as_sem_node()];
while let Some(n) = nodes.pop() {
if let Some(e) = n.as_expression()
&& let Some(callee) = e.call_subroutine()
{
stack.push(callee);
}
nodes.extend(sv_sim_sim::process::cfg::live_children(&n));
}
}
false
}
pub const SV_RT_DPI_SYNC_SYMBOL: &str = "sv_rt_dpi_sync";
fn dpi_sync(cx: &crate::expr::LowerCtx) {
let f = cx
.module
.get_function(SV_RT_DPI_SYNC_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DPI_SYNC_SYMBOL,
cx.ctx.void_type().fn_type(&[], false),
Some(Linkage::External),
)
});
cx.b.build_call(f, &[], "").unwrap();
}
pub const SV_RT_DPI_SET_SCOPE_SYMBOL: &str = "sv_rt_dpi_set_scope";
pub fn resolve_dpi_c_symbols(names: &[String]) -> Result<Vec<(String, usize)>, LoweringError> {
unsafe extern "C" {
fn dlsym(
handle: *mut core::ffi::c_void,
symbol: *const core::ffi::c_char,
) -> *mut core::ffi::c_void;
}
let mut out = Vec::new();
let mut missing = Vec::new();
for name in names {
let c = std::ffi::CString::new(name.as_str()).map_err(|_| {
LoweringError::unsupported(&format!("DPI symbol {name:?} has a NUL"), "<dpi>")
})?;
let addr = unsafe { dlsym(core::ptr::null_mut(), c.as_ptr()) };
if addr.is_null() {
missing.push(name.clone());
} else {
out.push((name.clone(), addr as usize));
}
}
if missing.is_empty() {
Ok(out)
} else {
Err(LoweringError::unsupported(
&format!(
"DPI import C function(s) not found: {} -- provide them with \
`--sv-lib <lib.so>` or a `.c` source file on the command line",
missing.join(", ")
),
"<dpi>",
))
}
}
pub fn dpi_export_thunk_name(c_name: &str) -> String {
format!("__svsim_export${c_name}")
}
pub const SV_RT_DPI_EXPORT_HANDLE_SYMBOL: &str = "sv_rt_dpi_export_handle";
pub const SV_RT_DPI_EXPORT_VARIANT_SYMBOL: &str = "sv_rt_dpi_export_variant";
pub fn lower_dpi_export_thunk<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &crate::accessor::AccessorFns<'ctx>,
export: &DpiExport<'d>,
signals: &std::collections::HashMap<
sv_sim_sim::symid::SymbolId,
crate::process_lower::SignalSlot,
>,
thunk_name: &str,
) -> Result<(), LoweringError> {
use crate::expr::{LowerCtx, LoweredValue};
use crate::process_lower::SignalSlotClass;
let c_name = export.c_identifier();
let thunk_name = thunk_name.to_owned();
let sub = export.subroutine();
let err =
|msg: String| LoweringError::unsupported_at(&format!("DPI export `{c_name}`: {msg}"), &sub);
let is_task = sub.subroutine_kind() == Some(sv_lang::SubroutineKind::Task);
let formals: Vec<Symbol<'d>> = dpi_formal_args(&sub).collect();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let mut ctypes = Vec::new();
let mut layouts: Vec<Option<CStructLayout>> = Vec::new();
let mut c_params: Vec<BasicMetadataTypeEnum<'ctx>> = Vec::new();
for f in &formals {
let ty = f
.value_type()
.ok_or_else(|| err(format!("formal `{}` has no type", f.name())))?;
let layout =
c_struct_layout(&ty).map_err(|m| err(format!("formal `{}`: {m}", f.name())))?;
let c = match layout {
Some(_) => DpiCType::Chandle,
None => DpiCType::of(&ty).map_err(|m| err(format!("formal `{}`: {m}", f.name())))?,
};
let by_ref = layout.is_some() || f.formal_argument_direction() != ArgumentDirection::In;
layouts.push(layout);
c_params.push(if by_ref {
ptr_ty.into()
} else {
c.llvm_type(ctx).into()
});
ctypes.push((c, ty, by_ref));
}
let ret_sv = sub
.subroutine_return_type()
.filter(|t| !t.is_void() && !is_task);
let ret_c = match &ret_sv {
Some(t) => {
let c = dpi_return_ctype(t).map_err(&err)?;
if matches!(c, DpiCType::BitVec(_) | DpiCType::LogicVec(_)) {
return Err(err(format!(
"return type `{}` is not a legal DPI return type",
t.to_sv_string()
)));
}
Some(c)
}
None => None,
};
let thunk_ty = match ret_c {
Some(c) => c.llvm_type(ctx).fn_type(&c_params, false),
None if is_task => ctx.i32_type().fn_type(&c_params, false),
None => ctx.void_type().fn_type(&c_params, false),
};
let thunk = module.add_function(&thunk_name, thunk_ty, None);
for (i, (c, _, by_ref)) in ctypes.iter().enumerate() {
if let (false, Some(a)) = (by_ref, c.ext_attr()) {
thunk.add_attribute(AttributeLoc::Param(i as u32), enum_attr(ctx, a));
}
}
let b = ctx.create_builder();
b.position_at_end(ctx.append_basic_block(thunk, "entry"));
let handle_fn = module
.get_function(SV_RT_DPI_EXPORT_HANDLE_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_DPI_EXPORT_HANDLE_SYMBOL,
ptr_ty.fn_type(&[ptr_ty.into()], false),
Some(Linkage::External),
)
});
let name_str = b
.build_global_string_ptr(&c_name, "dpi_export_name")
.unwrap();
let handle = b
.build_call(
handle_fn,
&[name_str.as_pointer_value().into()],
"export_handle",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_dpi_export_handle returns a handle")
.into_pointer_value();
let cx = LowerCtx {
ctx,
b,
repr: ValueRepr::FourState,
locals: std::collections::HashMap::new(),
fn_sym: sub,
module,
mem_signals: std::collections::HashMap::new(),
mem_accessor: Some(crate::expr::MemAccessorCtx { handle, fns }),
lvalue_ref: Default::default(),
deferred_start: Default::default(),
unbounded: Default::default(),
coll_temp: Default::default(),
iter: Default::default(),
loop_exits: Default::default(),
block_exits: Default::default(),
write_through: None,
cells: Default::default(),
copy_out: Default::default(),
ctor_this: Default::default(),
decl_scale: Default::default(),
inline_this: Default::default(),
end_flags: Default::default(),
specify_dest: Default::default(),
elem_refs: Default::default(),
};
let op = OpBuilder { ctx, b: &cx.b };
let mut sv_args: Vec<BasicMetadataValueEnum<'ctx>> = Vec::new();
let mut outs = Vec::new();
for (i, (c, ty, by_ref)) in ctypes.iter().enumerate() {
let param = thunk.get_nth_param(i as u32).unwrap();
let width = ty.value_width();
if let Some(layout) = &layouts[i] {
let ptr = param.into_pointer_value();
if formals[i].formal_argument_direction() == ArgumentDirection::In {
let v = read_c_struct(&cx, layout, ptr);
sv_args.push(
v.into_packed("a >128-bit DPI export struct", &sub)?
.llvm
.into(),
);
continue;
}
let temp = entry_alloca(&cx, op.packed_type(width, cx.repr), "export_arg");
if sub_formal_is_inout(&formals[i]) {
let v = read_c_struct(&cx, layout, ptr);
let bits = crate::real::to_storage(&cx, v, width, "a >128-bit DPI inout")?;
cx.b.build_store(temp, bits.llvm).unwrap();
} else {
cx.b.build_store(temp, op.packed_type(width, cx.repr).const_zero())
.unwrap();
}
sv_args.push(temp.into());
outs.push((i, temp, *c, *ty, ptr));
continue;
}
if !by_ref {
let v = from_c(&cx, param, *c, ty)?;
sv_args.push(match v {
LoweredValue::Real(r) if ty.bit_width() == 32 => {
cx.b.build_float_trunc(crate::real::as_f64(&cx, r.llvm), ctx.f32_type(), "sr")
.unwrap()
.into()
}
LoweredValue::Real(r) => crate::real::as_f64(&cx, r.llvm).into(),
LoweredValue::Str(sv) => crate::strings::as_packed(&cx, sv).llvm.into(),
other => other
.into_packed("a >128-bit DPI export argument", &sub)?
.llvm
.into(),
});
continue;
}
let temp = entry_alloca(&cx, op.packed_type(width, cx.repr), "export_arg");
if sub_formal_is_inout(&formals[i]) {
let raw = match c {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => param,
_ => {
cx.b.build_load(c.llvm_type(ctx), param.into_pointer_value(), "export_inout")
.unwrap()
}
};
let v = from_c(&cx, raw, *c, ty)?;
let bits = crate::real::to_storage(&cx, v, width, "a >128-bit DPI inout")?;
cx.b.build_store(temp, bits.llvm).unwrap();
} else {
cx.b.build_store(temp, op.packed_type(width, cx.repr).const_zero())
.unwrap();
}
sv_args.push(temp.into());
outs.push((i, temp, *c, *ty, param.into_pointer_value()));
}
let abi = crate::func::subroutine_abi(&sub);
let mut shadows = Vec::new();
if let Some(sig) = abi.signals.iter().find(|s| !signals.contains_key(&s.uid())) {
let at = sv_lang::SourceLoc::default();
let always = ctx.bool_type().const_int(1, false);
crate::stmt_shared::error_if(
&cx,
always,
at,
&format!(
"unsupported: DPI export `{c_name}` called from C: module signal `{}` has no STATE slot in the calling scope",
sig.name()
),
)?;
match (ret_c, is_task) {
(Some(c), _) => {
let z = c.llvm_type(ctx).const_zero();
cx.b.build_return(Some(&z)).unwrap();
}
(None, true) => {
cx.b.build_return(Some(&ctx.i32_type().const_zero()))
.unwrap();
}
_ => {
cx.b.build_return(None).unwrap();
}
}
if !thunk.verify(true) {
return Err(err("thunk failed LLVM verify".into()));
}
return Ok(());
}
for sig in &abi.signals {
let info = *signals
.get(&sig.uid())
.ok_or_else(|| err(format!("module signal `{}` has no STATE slot", sig.name())))?;
let raw = match info.class {
SignalSlotClass::Inline => {
crate::accessor::build_get_scalar(&cx, fns, handle, info.slot, info.width)
}
SignalSlotClass::Wide => {
crate::accessor::build_get_wide(&cx, fns, handle, info.slot, info.width)
}
SignalSlotClass::Memory { .. } => {
return Err(err("a memory in the signal ABI".into()));
}
};
let ptr = entry_alloca(&cx, raw.get_type(), "export_shadow");
cx.b.build_store(ptr, raw).unwrap();
sv_args.push(ptr.into());
shadows.push((sig.uid(), ptr, info));
}
if abi.needs_handle {
sv_args.push(handle.into());
}
let mangled = crate::func::mangled_subroutine_name(&sub)?;
let sv_fn = module.get_function(&mangled).ok_or_else(|| {
err(format!(
"the exported subroutine `{mangled}` was not lowered"
))
})?;
let call = cx.b.build_call(sv_fn, &sv_args, "export_call").unwrap();
for (id, ptr, info) in &shadows {
if !abi.written.contains(id) {
continue;
}
let cur =
cx.b.build_load(int_type(ctx, info.width * 2), *ptr, "export_flush")
.unwrap()
.into_int_value();
match info.class {
SignalSlotClass::Wide => {
crate::accessor::build_set_wide(&cx, fns, handle, info.slot, cur, info.width)
}
_ => crate::accessor::build_set_scalar(&cx, fns, handle, info.slot, cur, info.width),
}
}
for (i, temp, c, ty, c_ptr) in outs {
let width = ty.value_width();
let bits =
cx.b.build_load(op.packed_type(width, cx.repr), temp, "export_out")
.unwrap()
.into_int_value();
let value = stored_value(&cx, bits, &ty);
if let Some(layout) = &layouts[i] {
write_c_struct(&cx, value, layout, c_ptr)?;
continue;
}
let cv = to_c_input(&cx, value, c, &ty)?;
match c {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => {
let bytes = ctx.i64_type().const_int(4 * c.vec_words() as u64, false);
cx.b.build_memcpy(c_ptr, 4, cv.into_pointer_value(), 4, bytes)
.unwrap();
}
_ => {
cx.b.build_store(c_ptr, cv).unwrap();
}
}
}
match (ret_c, ret_sv) {
(Some(c), Some(t)) => {
let rv = call
.try_as_basic_value()
.basic()
.ok_or_else(|| err("the SV function returned no value".into()))?;
let value = match rv {
BasicValueEnum::IntValue(i) if t.is_string() => {
LoweredValue::Str(crate::strings::from_storage(
&cx,
PackedValue {
llvm: i,
width: t.value_width(),
repr: cx.repr,
signed: false,
},
))
}
BasicValueEnum::FloatValue(f) => LoweredValue::Real(crate::value::RealValue {
llvm: crate::real::as_f64(&cx, f),
}),
other => LoweredValue::Packed(PackedValue {
llvm: other.into_int_value(),
width: t.value_width(),
repr: cx.repr,
signed: t.is_signed(),
}),
};
let cv = to_c_input(&cx, value, c, &t)?;
cx.b.build_return(Some(&cv)).unwrap();
}
_ if is_task => {
cx.b.build_return(Some(&ctx.i32_type().const_zero()))
.unwrap();
}
_ => {
cx.b.build_return(None).unwrap();
}
}
if !thunk.verify(true) {
return Err(err("thunk failed LLVM verify".into()));
}
Ok(())
}
pub const SV_RT_DPI_EXPORT_TASK_CALL_SYMBOL: &str = "sv_rt_dpi_export_task_call";
pub const SV_RT_DPI_EXPORT_TASK_ERROR_SYMBOL: &str = "sv_rt_dpi_export_task_error";
pub const SV_RT_DPI_TASK_SCOPE_SYMBOL: &str = "sv_rt_dpi_task_scope";
pub const SV_RT_DPI_FN_SCOPE_SYMBOL: &str = "sv_rt_dpi_fn_scope";
pub const SV_RT_DPI_FN_DISABLE_SET_SYMBOL: &str = "sv_rt_dpi_fn_disable_set";
pub const SV_RT_DPI_FN_DISABLE_INDEX_SYMBOL: &str = "sv_rt_dpi_fn_disable_index";
pub fn lower_dpi_export_task_thunk<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
export: &DpiExport<'d>,
export_idx: u32,
kernel: &crate::process_lower::ExportTaskKernel,
thunk_name: &str,
) -> Result<(), LoweringError> {
use crate::expr::LowerCtx;
let c_name = export.c_identifier();
let sub = export.subroutine();
let err = |msg: String| {
LoweringError::unsupported_at(&format!("DPI export task `{c_name}`: {msg}"), &sub)
};
let formals: Vec<Symbol<'d>> = dpi_formal_args(&sub).collect();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let mut ctypes = Vec::new();
let mut c_params: Vec<BasicMetadataTypeEnum<'ctx>> = Vec::new();
for (f, slot) in formals.iter().zip(&kernel.formals) {
let ty = f
.value_type()
.ok_or_else(|| err(format!("formal `{}` has no type", f.name())))?;
let c = DpiCType::of(&ty).map_err(|m| err(format!("formal `{}`: {m}", f.name())))?;
if slot.is_none() {
return Err(err(format!(
"static formal `{}` is module state, not frame",
f.name()
)));
}
let by_ref = f.formal_argument_direction() != ArgumentDirection::In;
c_params.push(if by_ref {
ptr_ty.into()
} else {
c.llvm_type(ctx).into()
});
ctypes.push((c, ty, by_ref));
}
let thunk = module.add_function(thunk_name, ctx.i32_type().fn_type(&c_params, false), None);
for (i, (c, _, by_ref)) in ctypes.iter().enumerate() {
if let (false, Some(a)) = (by_ref, c.ext_attr()) {
thunk.add_attribute(AttributeLoc::Param(i as u32), enum_attr(ctx, a));
}
}
let b = ctx.create_builder();
b.position_at_end(ctx.append_basic_block(thunk, "entry"));
let cx = LowerCtx {
ctx,
b,
repr: ValueRepr::FourState,
locals: std::collections::HashMap::new(),
fn_sym: sub,
module,
mem_signals: std::collections::HashMap::new(),
mem_accessor: None,
lvalue_ref: Default::default(),
deferred_start: Default::default(),
unbounded: Default::default(),
coll_temp: Default::default(),
iter: Default::default(),
loop_exits: Default::default(),
block_exits: Default::default(),
write_through: None,
cells: Default::default(),
copy_out: Default::default(),
ctor_this: Default::default(),
decl_scale: Default::default(),
inline_this: Default::default(),
end_flags: Default::default(),
specify_dest: Default::default(),
elem_refs: Default::default(),
};
let image_ty = ctx.i64_type().array_type(kernel.frame_words.max(1));
let image = entry_alloca(&cx, image_ty, "export_image");
cx.b.build_store(image, image_ty.const_zero()).unwrap();
let slot_ptr = |off: u32| {
unsafe {
cx.b.build_gep(
ctx.i8_type(),
image,
&[ctx.i64_type().const_int(u64::from(off) * 8, false)],
"export_slot",
)
.unwrap()
}
};
for (i, (c, ty, by_ref)) in ctypes.iter().enumerate() {
let slot = kernel.formals[i].as_ref().expect("checked above");
let param = thunk.get_nth_param(i as u32).unwrap();
let width = ty.value_width();
let dir = formals[i].formal_argument_direction();
let v = if !by_ref {
Some(from_c(&cx, param, *c, ty)?)
} else if dir == ArgumentDirection::InOut {
let raw = match c {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => param,
_ => {
cx.b.build_load(c.llvm_type(ctx), param.into_pointer_value(), "export_inout")
.unwrap()
}
};
Some(from_c(&cx, raw, *c, ty)?)
} else {
None
};
if let Some(v) = v {
let bits = crate::real::to_storage(&cx, v, width, "a >128-bit DPI export argument")?;
cx.b.build_store(slot_ptr(slot.offset), bits.llvm).unwrap();
}
}
let call_fn = module
.get_function(SV_RT_DPI_EXPORT_TASK_CALL_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_DPI_EXPORT_TASK_CALL_SYMBOL,
ctx.i32_type().fn_type(
&[ctx.i32_type().into(), ptr_ty.into(), ptr_ty.into()],
false,
),
Some(Linkage::External),
)
});
let handle_fn = module
.get_function(SV_RT_DPI_EXPORT_HANDLE_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_DPI_EXPORT_HANDLE_SYMBOL,
ptr_ty.fn_type(&[ptr_ty.into()], false),
Some(Linkage::External),
)
});
let name_str =
cx.b.build_global_string_ptr(&c_name, "dpi_export_name")
.unwrap();
let handle =
cx.b.build_call(
handle_fn,
&[name_str.as_pointer_value().into()],
"export_handle",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_dpi_export_handle returns a pointer");
dpi_sync(&cx);
let status =
cx.b.build_call(
call_fn,
&[
ctx.i32_type()
.const_int(u64::from(export_idx), false)
.into(),
image.into(),
handle.into(),
],
"export_status",
)
.unwrap()
.try_as_basic_value()
.basic()
.expect("the export-task call returns a status")
.into_int_value();
dpi_sync(&cx);
let bad = ctx.append_basic_block(thunk, "not_in_task");
let after = ctx.append_basic_block(thunk, "after_call");
let not_in_task =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
status,
ctx.i32_type().const_all_ones(),
"not_in_task",
)
.unwrap();
cx.b.build_conditional_branch(not_in_task, bad, after)
.unwrap();
cx.b.position_at_end(bad);
let err_fn = module
.get_function(SV_RT_DPI_EXPORT_TASK_ERROR_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_DPI_EXPORT_TASK_ERROR_SYMBOL,
ctx.void_type().fn_type(&[ptr_ty.into()], false),
Some(Linkage::External),
)
});
cx.b.build_call(err_fn, &[name_str.as_pointer_value().into()], "")
.unwrap();
cx.b.build_unreachable().unwrap();
cx.b.position_at_end(after);
let ok =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
status,
ctx.i32_type().const_zero(),
"export_ok",
)
.unwrap();
let back = ctx.append_basic_block(thunk, "outputs");
let done = ctx.append_basic_block(thunk, "done");
cx.b.build_conditional_branch(ok, back, done).unwrap();
cx.b.position_at_end(back);
let op = OpBuilder { ctx, b: &cx.b };
for (i, (c, ty, by_ref)) in ctypes.iter().enumerate() {
if !by_ref {
continue;
}
let slot = kernel.formals[i].as_ref().expect("checked above");
let width = ty.value_width();
let bits =
cx.b.build_load(
op.packed_type(width, cx.repr),
slot_ptr(slot.offset),
"export_out",
)
.unwrap()
.into_int_value();
let value = stored_value(&cx, bits, ty);
let cv = to_c_input(&cx, value, *c, ty)?;
let c_ptr = thunk.get_nth_param(i as u32).unwrap().into_pointer_value();
match c {
DpiCType::BitVec(_) | DpiCType::LogicVec(_) => {
let bytes = ctx.i64_type().const_int(4 * c.vec_words() as u64, false);
cx.b.build_memcpy(c_ptr, 4, cv.into_pointer_value(), 4, bytes)
.unwrap();
}
_ => {
cx.b.build_store(c_ptr, cv).unwrap();
}
}
}
cx.b.build_unconditional_branch(done).unwrap();
cx.b.position_at_end(done);
cx.b.build_return(Some(&status)).unwrap();
if !thunk.verify(true) {
return Err(err("thunk failed LLVM verify".into()));
}
Ok(())
}
pub fn lower_dpi_export_dispatcher<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
c_name: &str,
variants: &[String],
) -> Result<(), LoweringError> {
let err = |m: &str| LoweringError::unsupported(&format!("DPI export `{c_name}`: {m}"), "<dpi>");
let fs: Vec<_> = variants
.iter()
.map(|v| {
module
.get_function(v)
.ok_or_else(|| err("missing variant thunk"))
})
.collect::<Result<_, _>>()?;
let fn_ty = fs[0].get_type();
if fs.iter().any(|f| f.get_type() != fn_ty) {
return Err(err(
"its instances elaborate to different C signatures (a DPI export needs one)",
));
}
let d = module.add_function(&dpi_export_thunk_name(c_name), fn_ty, None);
let b = ctx.create_builder();
let entry = ctx.append_basic_block(d, "entry");
b.position_at_end(entry);
if let [only] = fs.as_slice() {
let args: Vec<BasicMetadataValueEnum> = d.get_param_iter().map(Into::into).collect();
let r = b.build_call(*only, &args, "fwd").unwrap();
match r.try_as_basic_value().basic() {
Some(v) => b.build_return(Some(&v)).unwrap(),
None => b.build_return(None).unwrap(),
};
return Ok(());
}
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let variant_fn = module
.get_function(SV_RT_DPI_EXPORT_VARIANT_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_DPI_EXPORT_VARIANT_SYMBOL,
ctx.i32_type().fn_type(&[ptr_ty.into()], false),
Some(Linkage::External),
)
});
let name = b.build_global_string_ptr(c_name, "export_name").unwrap();
let k = b
.build_call(variant_fn, &[name.as_pointer_value().into()], "variant")
.unwrap()
.try_as_basic_value()
.basic()
.unwrap()
.into_int_value();
let args: Vec<BasicMetadataValueEnum> = d.get_param_iter().map(Into::into).collect();
let mut cases = Vec::new();
for (i, f) in fs.iter().enumerate() {
let bb = ctx.append_basic_block(d, "variant");
cases.push((ctx.i32_type().const_int(i as u64, false), bb));
b.position_at_end(bb);
let r = b.build_call(*f, &args, "fwd").unwrap();
match r.try_as_basic_value().basic() {
Some(v) => b.build_return(Some(&v)).unwrap(),
None => b.build_return(None).unwrap(),
};
}
let default = cases[0].1;
b.position_at_end(entry);
b.build_switch(k, default, &cases).unwrap();
if !d.verify(true) {
return Err(err("dispatcher failed LLVM verify"));
}
Ok(())
}
fn sub_formal_is_inout(f: &Symbol) -> bool {
f.formal_argument_direction() == ArgumentDirection::InOut
}
fn stored_value<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
bits: IntValue<'ctx>,
ty: &Type,
) -> crate::expr::LoweredValue<'ctx> {
let pv = PackedValue {
llvm: bits,
width: ty.value_width(),
repr: cx.repr,
signed: ty.is_signed(),
};
if ty.is_floating() {
crate::expr::LoweredValue::Real(crate::real::bits_to_real(cx, pv))
} else if ty.is_string() {
crate::expr::LoweredValue::Str(crate::strings::from_storage(cx, pv))
} else {
crate::expr::LoweredValue::Packed(pv)
}
}