use std::cell::Cell;
use std::collections::HashMap;
use std::ffi::{CStr, CString, c_char, c_int, c_void};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Mutex;
pub const SVDPI_H: &str = include_str!("../include/svdpi.h");
const RTLD_NOW: c_int = 2;
const RTLD_GLOBAL: c_int = 0x100;
unsafe extern "C" {
fn dlopen(filename: *const c_char, flags: c_int) -> *mut c_void;
fn dlerror() -> *mut c_char;
}
pub fn load_libraries(paths: &[PathBuf]) -> Result<(), String> {
for path in paths {
let c = CString::new(path.as_os_str().as_encoded_bytes())
.map_err(|_| format!("library path {} contains a NUL", path.display()))?;
let handle = unsafe { dlopen(c.as_ptr(), RTLD_NOW | RTLD_GLOBAL) };
if handle.is_null() {
let why = unsafe {
let e = dlerror();
if e.is_null() {
"unknown error".to_owned()
} else {
CStr::from_ptr(e).to_string_lossy().into_owned()
}
};
return Err(format!("cannot load DPI library {}: {why}", path.display()));
}
}
Ok(())
}
pub fn sv_lib_path(arg: &str) -> PathBuf {
let p = PathBuf::from(arg);
if p.exists() || p.extension().is_some_and(|e| e == "so") {
p
} else {
PathBuf::from(format!("{arg}.so"))
}
}
pub fn is_c_source(path: &Path) -> bool {
path.extension()
.and_then(|e| e.to_str())
.is_some_and(|e| matches!(e, "c" | "cc" | "cpp" | "cxx" | "C"))
}
pub fn compile_sources(sources: &[PathBuf]) -> Result<PathBuf, String> {
let dir = std::env::temp_dir().join(format!(
"vj-dpi-{}-{}",
std::process::id(),
COMPILE_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
));
std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
compile_to(sources, &dir, "libsvdpi_user.so")
}
fn compile_to(sources: &[PathBuf], dir: &Path, name: &str) -> Result<PathBuf, String> {
std::fs::write(dir.join("svdpi.h"), SVDPI_H).map_err(|e| e.to_string())?;
crate::vpi::write_headers(dir)?;
let cxx = sources
.iter()
.any(|s| s.extension().is_some_and(|e| e != "c"));
let compiler = if cxx {
std::env::var("CXX").unwrap_or_else(|_| "c++".to_owned())
} else {
std::env::var("CC").unwrap_or_else(|_| "cc".to_owned())
};
let out = dir.join(name);
let status = Command::new(&compiler)
.args(["-shared", "-fPIC", "-O1", "-g"])
.arg("-I")
.arg(dir)
.args(sources)
.arg("-o")
.arg(&out)
.status()
.map_err(|e| format!("cannot run the C compiler `{compiler}`: {e}"))?;
if !status.success() {
return Err(format!(
"compiling DPI source(s) {} with `{compiler}` failed",
sources
.iter()
.map(|s| s.display().to_string())
.collect::<Vec<_>>()
.join(", ")
));
}
Ok(out)
}
static COMPILE_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
thread_local! {
static PENDING_LIBRARIES: std::cell::RefCell<Vec<PathBuf>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub fn set_pending_libraries(paths: Vec<PathBuf>) {
PENDING_LIBRARIES.with(|p| *p.borrow_mut() = paths);
}
pub fn take_pending_libraries() -> Vec<PathBuf> {
PENDING_LIBRARIES.with(|p| std::mem::take(&mut *p.borrow_mut()))
}
pub fn load_export_trampolines(c_names: &[String]) -> Result<(), String> {
let mut c_names = c_names.to_vec();
c_names.sort();
c_names.dedup();
if c_names.is_empty() {
return Ok(());
}
let mut src = String::from(
"/* vj DPI export trampolines */\n\
#if defined(__x86_64__)\n\
#define VJ_JUMP(p) \"jmp *\" #p \"(%rip)\"\n\
#elif defined(__aarch64__)\n\
#define VJ_JUMP(p) \"adrp x16, \" #p \"\\n ldr x16, [x16, :lo12:\" #p \"]\\n br x16\"\n\
#else\n\
#error \"DPI exports: unsupported architecture\"\n\
#endif\n",
);
for n in &c_names {
src.push_str(&format!(
"void* __vj_p_{n} __attribute__((visibility(\"hidden\")));\n\
void __vj_set_{n}(void* t) {{ __vj_p_{n} = t; }}\n\
__attribute__((naked)) void {n}(void) {{ __asm__(VJ_JUMP(__vj_p_{n})); }}\n"
));
}
let dir = std::env::temp_dir().join(format!(
"vj-dpi-exports-{}-{}",
std::process::id(),
COMPILE_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
));
std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
let c = dir.join("vj_exports.c");
std::fs::write(&c, src).map_err(|e| e.to_string())?;
let lib = compile_to(&[c], &dir, "libvj_exports.so")?;
load_libraries(&[lib])
}
unsafe extern "C" {
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
}
pub fn set_export_targets(thunks: &[(String, usize)]) -> Result<(), String> {
for (name, addr) in thunks {
let setter = CString::new(format!("__vj_set_{name}")).map_err(|e| e.to_string())?;
let f = unsafe { dlsym(std::ptr::null_mut(), setter.as_ptr()) };
if f.is_null() {
return Err(format!("DPI export trampoline for `{name}` is not loaded"));
}
let set: extern "C" fn(*mut c_void) = unsafe { std::mem::transmute(f) };
set(*addr as *mut c_void);
}
Ok(())
}
#[repr(C)]
pub struct AotDpiHost {
pub set_scope: unsafe extern "C" fn(*mut c_void, *const c_char),
pub export_handle: unsafe extern "C" fn(*const c_char) -> *mut c_void,
pub export_variant: unsafe extern "C" fn(*const c_char) -> u32,
pub register_scope: unsafe extern "C" fn(*const c_char, usize),
pub register_export_variant: unsafe extern "C" fn(*const c_char, *const c_char, u32),
pub set_export_target: unsafe extern "C" fn(*const c_char, usize) -> i32,
pub disabled_state: *const std::sync::atomic::AtomicU8,
}
unsafe impl Sync for AotDpiHost {}
static AOT_DISABLED_STATE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
pub static AOT_DPI_HOST: AotDpiHost = AotDpiHost {
set_scope: sv_rt_dpi_set_scope,
export_handle: sv_rt_dpi_export_handle,
export_variant: sv_rt_dpi_export_variant,
register_scope: aot_register_scope,
register_export_variant: aot_register_export_variant,
set_export_target: aot_set_export_target,
disabled_state: &AOT_DISABLED_STATE,
};
unsafe extern "C" fn aot_register_scope(name: *const c_char, handle: usize) {
register_scope(&unsafe { CStr::from_ptr(name) }.to_string_lossy(), handle);
}
unsafe extern "C" fn aot_register_export_variant(
c_name: *const c_char,
scope: *const c_char,
variant: u32,
) {
let (c_name, scope) = unsafe { (CStr::from_ptr(c_name), CStr::from_ptr(scope)) };
register_export_variant(&c_name.to_string_lossy(), &scope.to_string_lossy(), variant);
}
unsafe extern "C" fn aot_set_export_target(c_name: *const c_char, addr: usize) -> i32 {
let name = unsafe { CStr::from_ptr(c_name) }
.to_string_lossy()
.into_owned();
i32::from(set_export_targets(&[(name, addr)]).is_err())
}
pub fn register_export_variant(c_name: &str, scope: &str, variant: u32) {
scope_named(scope)
.exports
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(c_name.to_owned(), variant);
}
pub unsafe extern "C" fn sv_rt_dpi_export_variant(c_name: *const c_char) -> u32 {
let name = unsafe { CStr::from_ptr(c_name) }.to_string_lossy();
let scope = CURRENT_SCOPE.with(|c| c.get());
let found = (!scope.is_null())
.then(|| unsafe { &*scope })
.and_then(|s| {
s.exports
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(name.as_ref())
.copied()
});
match found {
Some(k) => k,
None => {
let scope_name = if scope.is_null() {
"(none)".to_owned()
} else {
unsafe { &*scope }.name.to_string_lossy().into_owned()
};
eprintln!("error: DPI export `{name}` is not exported by scope {scope_name}");
std::process::abort();
}
}
}
pub unsafe extern "C" fn sv_rt_dpi_export_handle(c_name: *const c_char) -> *mut c_void {
let h = current_scope_handle();
if h.is_null() {
let name = unsafe { CStr::from_ptr(c_name) }.to_string_lossy();
eprintln!(
"error: DPI export `{name}` called with no instance scope (call it from a \
`context` import, or set one with svSetScope)"
);
std::process::abort();
}
h
}
thread_local! {
static OUTPUT: Cell<Option<*mut (dyn std::io::Write + 'static)>> = const { Cell::new(None) };
}
unsafe extern "C" {
fn fflush(stream: *mut c_void) -> c_int;
}
pub struct OutputGuard<'a> {
out: *mut (dyn std::io::Write + 'a),
}
impl<'a> OutputGuard<'a> {
pub fn set(out: &'a mut dyn std::io::Write) -> Self {
let out: *mut (dyn std::io::Write + 'a) = out;
let erased: *mut (dyn std::io::Write + 'static) = unsafe { std::mem::transmute(out) };
OUTPUT.with(|o| o.set(Some(erased)));
OutputGuard { out }
}
pub fn write(&self, bytes: &[u8]) -> std::io::Result<()> {
let out = unsafe { &mut *self.out };
out.write_all(bytes)?;
out.flush()
}
}
impl Drop for OutputGuard<'_> {
fn drop(&mut self) {
OUTPUT.with(|o| o.set(None));
}
}
pub extern "C" fn sv_rt_dpi_sync() {
if let Some(out) = OUTPUT.with(|o| o.get()) {
let pending = vj_runtime::display_sink::take_output();
if !pending.is_empty() {
let out = unsafe { &mut *out };
let _ = out.write_all(&pending);
let _ = out.flush();
}
}
unsafe {
fflush(std::ptr::null_mut());
}
}
struct Scope {
name: CString,
handle: Mutex<usize>,
user_data: Mutex<HashMap<usize, usize>>,
exports: Mutex<HashMap<String, u32>>,
}
static SCOPES: Mutex<Option<HashMap<String, &'static Scope>>> = Mutex::new(None);
thread_local! {
static CURRENT_SCOPE: Cell<*const Scope> = const { Cell::new(std::ptr::null()) };
}
fn scope_named(name: &str) -> &'static Scope {
let mut scopes = SCOPES.lock().unwrap_or_else(|e| e.into_inner());
let map = scopes.get_or_insert_with(HashMap::new);
map.entry(name.to_owned()).or_insert_with(|| {
Box::leak(Box::new(Scope {
name: CString::new(name).unwrap_or_default(),
handle: Mutex::new(0),
user_data: Mutex::new(HashMap::new()),
exports: Mutex::new(HashMap::new()),
}))
})
}
pub fn register_scope(name: &str, handle: usize) {
let s = scope_named(name);
*s.handle.lock().unwrap_or_else(|e| e.into_inner()) = handle;
}
pub fn reset_for_new_run() {
CURRENT_SCOPE.with(|c| c.set(std::ptr::null()));
}
pub unsafe extern "C" fn sv_rt_dpi_set_scope(handle: *mut c_void, name: *const c_char) {
let name = unsafe { CStr::from_ptr(name) }.to_string_lossy();
let s = scope_named(&name);
if !handle.is_null() {
*s.handle.lock().unwrap_or_else(|e| e.into_inner()) = handle as usize;
}
CURRENT_SCOPE.with(|c| c.set(s));
}
pub fn current_scope_handle() -> *mut c_void {
CURRENT_SCOPE.with(|c| {
let s = c.get();
if s.is_null() {
std::ptr::null_mut()
} else {
let s = unsafe { &*s };
*s.handle.lock().unwrap_or_else(|e| e.into_inner()) as *mut c_void
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn svDpiVersion() -> *const c_char {
c"1800-2005".as_ptr()
}
#[unsafe(no_mangle)]
pub extern "C" fn svGetScope() -> *mut c_void {
CURRENT_SCOPE.with(|c| c.get() as *mut c_void)
}
#[unsafe(no_mangle)]
pub extern "C" fn svSetScope(scope: *mut c_void) -> *mut c_void {
CURRENT_SCOPE.with(|c| c.replace(scope as *const Scope) as *mut c_void)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svGetNameFromScope(scope: *mut c_void) -> *const c_char {
if scope.is_null() {
return std::ptr::null();
}
unsafe { &*(scope as *const Scope) }.name.as_ptr()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svGetScopeFromName(name: *const c_char) -> *mut c_void {
if name.is_null() {
return std::ptr::null_mut();
}
let name = unsafe { CStr::from_ptr(name) }.to_string_lossy();
let scopes = SCOPES.lock().unwrap_or_else(|e| e.into_inner());
scopes
.as_ref()
.and_then(|m| m.get(name.as_ref()))
.map_or(std::ptr::null_mut(), |s| *s as *const Scope as *mut c_void)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svPutUserData(
scope: *mut c_void,
key: *mut c_void,
data: *mut c_void,
) -> c_int {
if scope.is_null() {
return -1;
}
let s = unsafe { &*(scope as *const Scope) };
s.user_data
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(key as usize, data as usize);
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svGetUserData(scope: *mut c_void, key: *mut c_void) -> *mut c_void {
if scope.is_null() {
return std::ptr::null_mut();
}
let s = unsafe { &*(scope as *const Scope) };
s.user_data
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&(key as usize))
.map_or(std::ptr::null_mut(), |d| *d as *mut c_void)
}
#[unsafe(no_mangle)]
pub extern "C" fn svGetCallerInfo(_file: *mut *const c_char, _line: *mut c_int) -> c_int {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svIsDisabledState() -> c_int {
c_int::from(
vj_sim::dpi_task::active_call_disabled()
|| AOT_DISABLED_STATE.load(std::sync::atomic::Ordering::Relaxed) != 0,
)
}
#[unsafe(no_mangle)]
pub extern "C" fn svAckDisabledState() {
vj_sim::dpi_task::ack_disabled_state();
let _ = AOT_DISABLED_STATE.compare_exchange(
vj_sim::dpi_task::MIRROR_DISABLED,
vj_sim::dpi_task::MIRROR_ACKED,
std::sync::atomic::Ordering::Relaxed,
std::sync::atomic::Ordering::Relaxed,
);
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct SvLogicVecVal {
pub aval: u32,
pub bval: u32,
}
fn get_bit(words: *const u32, i: usize) -> u32 {
(unsafe { *words.add(i / 32) } >> (i % 32)) & 1
}
fn put_bit(words: *mut u32, i: usize, b: u32) {
unsafe {
let w = words.add(i / 32);
*w = (*w & !(1 << (i % 32))) | ((b & 1) << (i % 32));
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svGetBitselBit(s: *const u32, i: c_int) -> u8 {
get_bit(s, i as usize) as u8
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svGetBitselLogic(s: *const SvLogicVecVal, i: c_int) -> u8 {
let i = i as usize;
let w = unsafe { *s.add(i / 32) };
let a = (w.aval >> (i % 32)) & 1;
let b = (w.bval >> (i % 32)) & 1;
(a | (b << 1)) as u8
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svPutBitselBit(d: *mut u32, i: c_int, s: u8) {
put_bit(d, i as usize, u32::from(s & 1));
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svPutBitselLogic(d: *mut SvLogicVecVal, i: c_int, s: u8) {
let i = i as usize;
let w = unsafe { &mut *d.add(i / 32) };
let m = 1u32 << (i % 32);
w.aval = (w.aval & !m) | (u32::from(s & 1) << (i % 32));
w.bval = (w.bval & !m) | (u32::from((s >> 1) & 1) << (i % 32));
}
fn copy_bits(d: *mut u32, d_lo: usize, s: *const u32, s_lo: usize, w: usize, stride: usize) {
for k in 0..w {
let (si, di) = (s_lo + k, d_lo + k);
let bit = unsafe { (*s.add((si / 32) * stride) >> (si % 32)) & 1 };
unsafe {
let p = d.add((di / 32) * stride);
*p = (*p & !(1 << (di % 32))) | (bit << (di % 32));
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svGetPartselBit(d: *mut u32, s: *const u32, i: c_int, w: c_int) {
copy_bits(d, 0, s, i as usize, w as usize, 1);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svGetPartselLogic(
d: *mut SvLogicVecVal,
s: *const SvLogicVecVal,
i: c_int,
w: c_int,
) {
let (d, s) = (d as *mut u32, s as *const u32);
copy_bits(d, 0, s, i as usize, w as usize, 2);
copy_bits(
unsafe { d.add(1) },
0,
unsafe { s.add(1) },
i as usize,
w as usize,
2,
);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svPutPartselBit(d: *mut u32, s: u32, i: c_int, w: c_int) {
copy_bits(d, i as usize, &s, 0, w as usize, 1);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn svPutPartselLogic(
d: *mut SvLogicVecVal,
s: SvLogicVecVal,
i: c_int,
w: c_int,
) {
let d = d as *mut u32;
copy_bits(d, i as usize, &s.aval, 0, w as usize, 2);
copy_bits(unsafe { d.add(1) }, i as usize, &s.bval, 0, w as usize, 2);
}
pub const OPEN_MAX_DIMS: usize = 8;
#[repr(C)]
pub struct OpenArray {
data: *mut u8,
elem_size: u32,
kind: u32,
elem_bits: u32,
pleft: i32,
pright: i32,
udims: i32,
bounds: [[i32; 2]; OPEN_MAX_DIMS],
}
fn oa<'a>(h: *const c_void) -> Option<&'a OpenArray> {
(!h.is_null()).then(|| unsafe { &*(h as *const OpenArray) })
}
fn dim_bounds(h: *const c_void, d: c_int) -> Option<(i32, i32)> {
let a = oa(h)?;
match d {
0 => Some((a.pleft, a.pright)),
1.. if d <= a.udims => {
let [l, r] = a.bounds[d as usize - 1];
Some((l, r))
}
_ => None,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn svLeft(h: *const c_void, d: c_int) -> c_int {
dim_bounds(h, d).map_or(0, |(l, _)| l)
}
#[unsafe(no_mangle)]
pub extern "C" fn svRight(h: *const c_void, d: c_int) -> c_int {
dim_bounds(h, d).map_or(0, |(_, r)| r)
}
#[unsafe(no_mangle)]
pub extern "C" fn svLow(h: *const c_void, d: c_int) -> c_int {
dim_bounds(h, d).map_or(0, |(l, r)| l.min(r))
}
#[unsafe(no_mangle)]
pub extern "C" fn svHigh(h: *const c_void, d: c_int) -> c_int {
dim_bounds(h, d).map_or(0, |(l, r)| l.max(r))
}
#[unsafe(no_mangle)]
pub extern "C" fn svIncrement(h: *const c_void, d: c_int) -> c_int {
dim_bounds(h, d).map_or(0, |(l, r)| if l >= r { 1 } else { -1 })
}
#[unsafe(no_mangle)]
pub extern "C" fn svSize(h: *const c_void, d: c_int) -> c_int {
dim_bounds(h, d).map_or(0, |(l, r)| (l - r).abs() + 1)
}
#[unsafe(no_mangle)]
pub extern "C" fn svDimensions(h: *const c_void) -> c_int {
oa(h).map_or(0, |a| if a.udims > 0 { a.udims } else { 1 })
}
#[unsafe(no_mangle)]
pub extern "C" fn svGetArrayPtr(h: *const c_void) -> *mut c_void {
oa(h).map_or(std::ptr::null_mut(), |a| a.data as *mut c_void)
}
#[unsafe(no_mangle)]
pub extern "C" fn svSizeOfArray(h: *const c_void) -> c_int {
oa(h).map_or(0, |a| {
let elems: i32 = a.bounds[..a.udims as usize]
.iter()
.map(|[l, r]| (l - r).abs() + 1)
.product();
elems * a.elem_size as c_int
})
}
fn elem_ptr_n(h: *const c_void, idx: &[c_int]) -> *mut c_void {
let Some(a) = oa(h).filter(|a| a.udims >= 1 && a.udims as usize == idx.len()) else {
return std::ptr::null_mut();
};
let mut off = 0i64;
for (&i, [l, r]) in idx.iter().zip(&a.bounds) {
let (low, size) = (l.min(r), (l - r).abs() + 1);
let k = i64::from(i) - i64::from(*low);
if k < 0 || k >= i64::from(size) {
return std::ptr::null_mut();
}
off = off * i64::from(size) + k;
}
unsafe { a.data.add(off as usize * a.elem_size as usize) as *mut c_void }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn vj_dpi_elem_ptr(
h: *const c_void,
idx: *const c_int,
n: c_int,
) -> *mut c_void {
elem_ptr_n(h, unsafe {
std::slice::from_raw_parts(idx, n.max(0) as usize)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn svGetArrElemPtr1(h: *const c_void, i: c_int) -> *mut c_void {
elem_ptr_n(h, &[i])
}
#[unsafe(no_mangle)]
pub extern "C" fn svGetArrElemPtr2(h: *const c_void, i: c_int, j: c_int) -> *mut c_void {
elem_ptr_n(h, &[i, j])
}
#[unsafe(no_mangle)]
pub extern "C" fn svGetArrElemPtr3(h: *const c_void, i: c_int, j: c_int, k: c_int) -> *mut c_void {
elem_ptr_n(h, &[i, j, k])
}
fn elem_words(a: &OpenArray, p: *const u8) -> Vec<(u32, u32)> {
let words = a.elem_bits.div_ceil(32).max(1) as usize;
unsafe {
match a.kind {
1 => (0..words)
.map(|w| ((p as *const u32).add(w).read_unaligned(), 0))
.collect(),
2 => (0..words)
.map(|w| {
let q = (p as *const u32).add(2 * w);
(q.read_unaligned(), q.add(1).read_unaligned())
})
.collect(),
3 => {
let b = u32::from(*p);
vec![(b & 1, (b >> 1) & 1)]
}
_ => {
let mut bytes = [0u8; 8];
std::ptr::copy_nonoverlapping(p, bytes.as_mut_ptr(), (a.elem_size as usize).min(8));
let v = u64::from_le_bytes(bytes);
(0..words).map(|w| ((v >> (32 * w)) as u32, 0)).collect()
}
}
}
}
fn put_elem_words(a: &OpenArray, p: *mut u8, words: &[(u32, u32)]) {
unsafe {
match a.kind {
1 => {
for (w, (aval, _)) in words.iter().enumerate() {
(p as *mut u32).add(w).write_unaligned(*aval);
}
}
2 => {
for (w, (aval, bval)) in words.iter().enumerate() {
let q = (p as *mut u32).add(2 * w);
q.write_unaligned(*aval);
q.add(1).write_unaligned(*bval);
}
}
3 => *p = ((words[0].0 & 1) | ((words[0].1 & 1) << 1)) as u8,
_ => {
let v = words.iter().enumerate().fold(0u64, |acc, (w, (aval, _))| {
acc | (u64::from(*aval) << (32 * w))
});
let bytes = v.to_le_bytes();
std::ptr::copy_nonoverlapping(bytes.as_ptr(), p, (a.elem_size as usize).min(8));
}
}
}
}
fn elem(h: *const c_void, idx: &[c_int]) -> Option<(&'static OpenArray, *mut u8)> {
let p = elem_ptr_n(h, idx);
(!p.is_null()).then(|| (oa(h).expect("non-null"), p as *mut u8))
}
fn n_words(a: &OpenArray) -> usize {
a.elem_bits.div_ceil(32).max(1) as usize
}
fn get_bit_vec(d: *mut u32, h: *const c_void, idx: &[c_int]) {
if let Some((a, p)) = elem(h, idx) {
for (w, (aval, bval)) in elem_words(a, p).into_iter().enumerate() {
unsafe { *d.add(w) = aval & !bval };
}
}
}
fn get_logic_vec(d: *mut SvLogicVecVal, h: *const c_void, idx: &[c_int]) {
if let Some((a, p)) = elem(h, idx) {
for (w, (aval, bval)) in elem_words(a, p).into_iter().enumerate() {
unsafe { *d.add(w) = SvLogicVecVal { aval, bval } };
}
}
}
fn put_bit_vec(h: *const c_void, s: *const u32, idx: &[c_int]) {
if let Some((a, p)) = elem(h, idx) {
let words: Vec<(u32, u32)> = (0..n_words(a)).map(|w| (unsafe { *s.add(w) }, 0)).collect();
put_elem_words(a, p, &words);
}
}
fn put_logic_vec(h: *const c_void, s: *const SvLogicVecVal, idx: &[c_int]) {
if let Some((a, p)) = elem(h, idx) {
let words: Vec<(u32, u32)> = (0..n_words(a))
.map(|w| {
let v = unsafe { *s.add(w) };
(v.aval, v.bval)
})
.collect();
put_elem_words(a, p, &words);
}
}
fn oa_get_bit(h: *const c_void, idx: &[c_int]) -> u8 {
elem(h, idx).map_or(0, |(a, p)| {
let (aval, bval) = elem_words(a, p)[0];
(aval & !bval & 1) as u8
})
}
fn oa_get_logic(h: *const c_void, idx: &[c_int]) -> u8 {
elem(h, idx).map_or(0, |(a, p)| {
let (aval, bval) = elem_words(a, p)[0];
((aval & 1) | ((bval & 1) << 1)) as u8
})
}
fn oa_put_bit(h: *const c_void, v: u8, idx: &[c_int]) {
if let Some((a, p)) = elem(h, idx) {
let mut words = vec![(0, 0); n_words(a)];
words[0] = (u32::from(v & 1), 0);
put_elem_words(a, p, &words);
}
}
fn oa_put_logic(h: *const c_void, v: u8, idx: &[c_int]) {
if let Some((a, p)) = elem(h, idx) {
let mut words = vec![(0, 0); n_words(a)];
words[0] = (u32::from(v & 1), u32::from((v >> 1) & 1));
put_elem_words(a, p, &words);
}
}
macro_rules! elem_access {
($([$($i:ident),+] => $get_bv:ident $get_lv:ident $put_bv:ident $put_lv:ident
$get_b:ident $get_l:ident $put_b:ident $put_l:ident;)*) => {
$(
/// # Safety
/// `d` holds the element's words.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $get_bv(d: *mut u32, h: *const c_void, $($i: c_int),+) {
get_bit_vec(d, h, &[$($i),+]);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $get_lv(d: *mut SvLogicVecVal, h: *const c_void, $($i: c_int),+) {
get_logic_vec(d, h, &[$($i),+]);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $put_bv(h: *const c_void, s: *const u32, $($i: c_int),+) {
put_bit_vec(h, s, &[$($i),+]);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $put_lv(h: *const c_void, s: *const SvLogicVecVal, $($i: c_int),+) {
put_logic_vec(h, s, &[$($i),+]);
}
#[unsafe(no_mangle)]
pub extern "C" fn $get_b(h: *const c_void, $($i: c_int),+) -> u8 {
oa_get_bit(h, &[$($i),+])
}
#[unsafe(no_mangle)]
pub extern "C" fn $get_l(h: *const c_void, $($i: c_int),+) -> u8 {
oa_get_logic(h, &[$($i),+])
}
#[unsafe(no_mangle)]
pub extern "C" fn $put_b(h: *const c_void, v: u8, $($i: c_int),+) {
oa_put_bit(h, v, &[$($i),+]);
}
#[unsafe(no_mangle)]
pub extern "C" fn $put_l(h: *const c_void, v: u8, $($i: c_int),+) {
oa_put_logic(h, v, &[$($i),+]);
}
)*
};
}
elem_access! {
[i1] => svGetBitArrElem1VecVal svGetLogicArrElem1VecVal svPutBitArrElem1VecVal
svPutLogicArrElem1VecVal svGetBitArrElem1 svGetLogicArrElem1 svPutBitArrElem1
svPutLogicArrElem1;
[i1, i2] => svGetBitArrElem2VecVal svGetLogicArrElem2VecVal svPutBitArrElem2VecVal
svPutLogicArrElem2VecVal svGetBitArrElem2 svGetLogicArrElem2 svPutBitArrElem2
svPutLogicArrElem2;
[i1, i2, i3] => svGetBitArrElem3VecVal svGetLogicArrElem3VecVal svPutBitArrElem3VecVal
svPutLogicArrElem3VecVal svGetBitArrElem3 svGetLogicArrElem3 svPutBitArrElem3
svPutLogicArrElem3;
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn vj_dpi_elem_op(
op: c_int,
h: *const c_void,
buf: *mut c_void,
value: c_int,
idx: *const c_int,
n: c_int,
) -> c_int {
let idx = unsafe { std::slice::from_raw_parts(idx, n.max(0) as usize) };
match op {
0 => get_bit_vec(buf as *mut u32, h, idx),
1 => get_logic_vec(buf as *mut SvLogicVecVal, h, idx),
2 => put_bit_vec(h, buf as *const u32, idx),
3 => put_logic_vec(h, buf as *const SvLogicVecVal, idx),
4 => return c_int::from(oa_get_bit(h, idx)),
5 => return c_int::from(oa_get_logic(h, idx)),
6 => oa_put_bit(h, value as u8, idx),
7 => oa_put_logic(h, value as u8, idx),
_ => {}
}
0
}
const SVDPI_VARIADIC_C: &str = r#"#include <stdarg.h>
#include "svdpi.h"
extern void* vj_dpi_elem_ptr(const void*, const int*, int);
extern int vj_dpi_elem_op(int, const void*, void*, int, const int*, int);
#define GATHER(h, first) int idx[8]; int n = svDimensions(h), k; va_list ap; \
if (n > 8) n = 8; idx[0] = first; va_start(ap, first); \
for (k = 1; k < n; k++) idx[k] = va_arg(ap, int); va_end(ap)
void* svGetArrElemPtr(const svOpenArrayHandle h, int i, ...) { GATHER(h, i); return vj_dpi_elem_ptr(h, idx, n); }
void svGetBitArrElemVecVal(svBitVecVal* d, const svOpenArrayHandle s, int i, ...) { GATHER(s, i); vj_dpi_elem_op(0, s, d, 0, idx, n); }
void svGetLogicArrElemVecVal(svLogicVecVal* d, const svOpenArrayHandle s, int i, ...) { GATHER(s, i); vj_dpi_elem_op(1, s, d, 0, idx, n); }
void svPutBitArrElemVecVal(const svOpenArrayHandle d, const svBitVecVal* s, int i, ...) { GATHER(d, i); vj_dpi_elem_op(2, d, (void*)s, 0, idx, n); }
void svPutLogicArrElemVecVal(const svOpenArrayHandle d, const svLogicVecVal* s, int i, ...) { GATHER(d, i); vj_dpi_elem_op(3, d, (void*)s, 0, idx, n); }
svBit svGetBitArrElem(const svOpenArrayHandle s, int i, ...) { GATHER(s, i); return (svBit)vj_dpi_elem_op(4, s, 0, 0, idx, n); }
svLogic svGetLogicArrElem(const svOpenArrayHandle s, int i, ...) { GATHER(s, i); return (svLogic)vj_dpi_elem_op(5, s, 0, 0, idx, n); }
void svPutBitArrElem(const svOpenArrayHandle d, svBit v, int i, ...) { GATHER(d, i); vj_dpi_elem_op(6, d, 0, v, idx, n); }
void svPutLogicArrElem(const svOpenArrayHandle d, svLogic v, int i, ...) { GATHER(d, i); vj_dpi_elem_op(7, d, 0, v, idx, n); }
"#;
pub fn load_svdpi_support() -> Result<(), String> {
static LOADED: std::sync::OnceLock<Result<(), String>> = std::sync::OnceLock::new();
LOADED
.get_or_init(|| {
let dir = std::env::temp_dir().join(format!(
"vj-svdpi-support-{}-{}",
std::process::id(),
COMPILE_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
));
std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
let c = dir.join("svdpi_variadic.c");
std::fs::write(&c, SVDPI_VARIADIC_C).map_err(|e| e.to_string())?;
let lib = compile_to(&[c], &dir, "libsvdpi_support.so")?;
load_libraries(&[lib])
})
.clone()
}