use std::fmt;
use std::path::PathBuf;
use std::sync::Arc;
use celox_design::ExternFunction;
use thiserror::Error;
#[derive(Clone, Default)]
pub struct DpiSymbols {
functions: Vec<(String, usize)>,
libraries: Vec<PathBuf>,
resolved: Option<(Vec<ExternFunction>, ExternFunctionTable)>,
}
impl fmt::Debug for DpiSymbols {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DpiSymbols")
.field(
"functions",
&self
.functions
.iter()
.map(|(name, _)| name)
.collect::<Vec<_>>(),
)
.field("libraries", &self.libraries)
.finish()
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DpiError {
#[error("DPI-C import `{name}` is not provided by any registered function or library")]
UnresolvedFunction { name: String },
#[error("cannot load DPI-C library `{}`: {reason}", path.display())]
LibraryLoad { path: PathBuf, reason: String },
#[error("DPI-C libraries cannot be loaded on this target")]
LibrariesUnsupported,
}
impl DpiSymbols {
pub(crate) fn add_function(&mut self, name: String, address: usize) {
self.resolved = None;
match self.functions.iter_mut().find(|(known, _)| *known == name) {
Some((_, known)) => *known = address,
None => self.functions.push((name, address)),
}
}
pub(crate) fn add_library(&mut self, path: PathBuf) {
self.resolved = None;
self.libraries.push(path);
}
pub(crate) fn resolve(
&self,
functions: &[ExternFunction],
) -> Result<ExternFunctionTable, DpiError> {
if let Some((resolved, table)) = &self.resolved
&& resolved == functions
{
return Ok(table.clone());
}
let addresses = functions
.iter()
.map(|function| self.address(&function.name))
.collect::<Result<Vec<_>, _>>()?;
Ok(ExternFunctionTable {
addresses: addresses.into(),
})
}
pub(crate) fn resolve_ahead(&mut self, functions: &[ExternFunction]) -> Result<(), DpiError> {
let table = self.resolve(functions)?;
self.resolved = Some((functions.to_vec(), table));
Ok(())
}
fn address(&self, name: &str) -> Result<usize, DpiError> {
if let Some(&(_, address)) = self.functions.iter().find(|(known, _)| known == name) {
return Ok(address);
}
for path in &self.libraries {
if let Some(address) = library_symbol(path, name)? {
return Ok(address);
}
}
Err(DpiError::UnresolvedFunction {
name: name.to_string(),
})
}
}
#[cfg(feature = "host-runtime")]
fn library_symbol(path: &std::path::Path, name: &str) -> Result<Option<usize>, DpiError> {
use crate::HashMap;
use std::sync::{LazyLock, Mutex};
static LIBRARIES: LazyLock<Mutex<HashMap<PathBuf, &'static libloading::Library>>> =
LazyLock::new(|| Mutex::new(HashMap::default()));
let library = {
let mut libraries = LIBRARIES.lock().unwrap_or_else(|error| error.into_inner());
match libraries.get(path) {
Some(library) => *library,
None => {
let library = unsafe { libloading::Library::new(path) }.map_err(|error| {
DpiError::LibraryLoad {
path: path.to_path_buf(),
reason: error.to_string(),
}
})?;
let library: &'static libloading::Library = Box::leak(Box::new(library));
libraries.insert(path.to_path_buf(), library);
library
}
}
};
let symbol = unsafe { library.get::<unsafe extern "C" fn()>(name.as_bytes()) };
Ok(symbol.ok().map(|symbol| *symbol as usize))
}
#[cfg(not(feature = "host-runtime"))]
fn library_symbol(_path: &std::path::Path, _name: &str) -> Result<Option<usize>, DpiError> {
Err(DpiError::LibrariesUnsupported)
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ExternFunctionTable {
addresses: Arc<[usize]>,
}
impl ExternFunctionTable {
pub(crate) fn as_ptr(&self) -> *const usize {
self.addresses.as_ptr()
}
pub(crate) unsafe fn call(&self, func: u32, args: &[u64]) -> u64 {
unsafe { call_c(self.addresses[func as usize], args) }
}
}
unsafe fn call_c(address: usize, args: &[u64]) -> u64 {
macro_rules! call {
($($arg:ident),*) => {{
let function: extern "C" fn($($arg: u64),*) -> u64 =
unsafe { std::mem::transmute(address) };
let [$($arg),*] = args else { unreachable!() };
function($(*$arg),*)
}};
}
match args.len() {
0 => call!(),
1 => call!(a0),
2 => call!(a0, a1),
3 => call!(a0, a1, a2),
4 => call!(a0, a1, a2, a3),
5 => call!(a0, a1, a2, a3, a4),
6 => call!(a0, a1, a2, a3, a4, a5),
7 => call!(a0, a1, a2, a3, a4, a5, a6),
8 => call!(a0, a1, a2, a3, a4, a5, a6, a7),
9 => call!(a0, a1, a2, a3, a4, a5, a6, a7, a8),
10 => call!(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9),
11 => call!(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10),
12 => call!(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11),
13 => call!(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12),
14 => call!(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13),
15 => call!(
a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14
),
16 => call!(
a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15
),
count => panic!("the SIR verifier bounds extern call arguments, not {count}"),
}
}