use core::ffi::c_void;
use core::ptr::slice_from_raw_parts;
use core::str;
use std::path::Path;
use std::sync::Arc;
use anyhow::{bail, Context as _};
use libloading::{library_filename, Library, Symbol};
use wasmtime::component::types::{self, ComponentItem};
use wasmtime::component::{Lift, LinkerInstance, Lower, ResourceTable, Type};
use wasmtime_cabish::{lift_results, CabishView};
use crate::engine::Ctx;
impl CabishView for Ctx {
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
}
pub struct Plugin {
lib: Library,
}
fn dlsym<'a, T>(lib: &'a Library, symbol: &str) -> anyhow::Result<Symbol<'a, T>> {
unsafe { lib.get::<T>(symbol.as_bytes()) }
.with_context(|| format!("failed to lookup `{symbol}`"))
}
fn link_func_0_1<T: Lower + 'static>(
linker: &mut LinkerInstance<impl CabishView>,
lib: &Library,
symbol: &str,
name: &str,
) -> anyhow::Result<()> {
let f = dlsym::<unsafe extern "C" fn() -> *const T>(lib, symbol)?;
let f = *f;
linker.func_wrap(name, move |_, ()| {
let ptr = unsafe { f() };
Ok((unsafe { ptr.read() },))
})
}
fn link_func_1_0<T: Lift + 'static>(
linker: &mut LinkerInstance<impl CabishView>,
lib: &Library,
symbol: &str,
name: &str,
) -> anyhow::Result<()> {
let f = dlsym::<unsafe extern "C" fn(T)>(lib, symbol)?;
let f = *f;
linker.func_wrap(name, move |_, (v,)| {
unsafe { f(v) };
Ok(())
})
}
fn link_func(
linker: &mut LinkerInstance<impl CabishView>,
lib: &Library,
ty: &types::ComponentFunc,
instance_name: &str,
name: &str,
) -> anyhow::Result<()> {
let param_tys = ty.params().map(|(_, ty)| ty).collect::<Arc<[_]>>();
let result_tys = ty.results().collect::<Arc<[_]>>();
let symbol = format!("{instance_name}#{name}");
match (&*param_tys, &*result_tys) {
([], []) => {
let f = dlsym::<unsafe extern "C" fn()>(lib, &symbol)?;
let f = *f;
linker.func_wrap(name, move |_, ()| {
unsafe { f() };
Ok(())
})
}
([], [Type::Bool]) => {
let f = dlsym::<unsafe extern "C" fn() -> *const u8>(lib, &symbol)?;
let f = *f;
linker.func_wrap(name, move |_, ()| {
let ptr = unsafe { f() };
let data = unsafe { ptr.read() };
Ok((data != 0,))
})
}
([], [Type::S8]) => link_func_0_1::<i8>(linker, lib, &symbol, name),
([], [Type::U8]) => link_func_0_1::<u8>(linker, lib, &symbol, name),
([], [Type::S16]) => link_func_0_1::<i16>(linker, lib, &symbol, name),
([], [Type::U16]) => link_func_0_1::<u16>(linker, lib, &symbol, name),
([], [Type::S32]) => link_func_0_1::<i32>(linker, lib, &symbol, name),
([], [Type::U32]) => link_func_0_1::<u32>(linker, lib, &symbol, name),
([], [Type::S64]) => link_func_0_1::<i64>(linker, lib, &symbol, name),
([], [Type::U64]) => link_func_0_1::<u64>(linker, lib, &symbol, name),
([], [Type::Float32]) => link_func_0_1::<f32>(linker, lib, &symbol, name),
([], [Type::Float64]) => link_func_0_1::<f64>(linker, lib, &symbol, name),
([], [Type::Char]) => {
let f = dlsym::<unsafe extern "C" fn() -> *const u32>(lib, &symbol)?;
let f = *f;
linker.func_wrap(name, move |_, ()| {
let ptr = unsafe { f() };
let data = unsafe { ptr.read() };
let data = char::from_u32(data)
.with_context(|| format!("`{data}` is not a valid char"))?;
Ok((data,))
})
}
([], [Type::String]) => {
let f = dlsym::<unsafe extern "C" fn() -> *const (*const u8, usize)>(lib, &symbol)?;
let f = *f;
linker.func_wrap(name, move |_, ()| {
let ptr = unsafe { f() };
let (data, len) = unsafe { ptr.read() };
if len > 0 {
let data = slice_from_raw_parts(data, len);
Ok((unsafe { str::from_utf8_unchecked(&*data) },))
} else {
Ok(("",))
}
})
}
([], [Type::List(ty)]) if ty.ty() == Type::U8 => {
let f = dlsym::<unsafe extern "C" fn() -> *const (*const u8, usize)>(lib, &symbol)?;
let f = *f;
linker.func_wrap(name, move |_, ()| {
let ptr = unsafe { f() };
let (data, len) = unsafe { ptr.read() };
if len > 0 {
let data = slice_from_raw_parts(data, len);
Ok((unsafe { &*data },))
} else {
Ok((&[],))
}
})
}
([], [..]) => {
let f = dlsym::<unsafe extern "C" fn() -> *const c_void>(lib, &symbol)?;
let f = *f;
linker.func_new(name, move |store, _, results| {
let ptr = unsafe { f() };
lift_results(store, &result_tys, ptr, results)
})
}
([Type::Bool], []) => {
let f = dlsym::<unsafe extern "C" fn(u8)>(lib, &symbol)?;
let f = *f;
linker.func_wrap(name, move |_, (v,)| {
unsafe { f(if v { 1 } else { 0 }) };
Ok(())
})
}
([Type::S8], []) => link_func_1_0::<i8>(linker, lib, &symbol, name),
([Type::U8], []) => link_func_1_0::<u8>(linker, lib, &symbol, name),
([Type::S16], []) => link_func_1_0::<i16>(linker, lib, &symbol, name),
([Type::U16], []) => link_func_1_0::<u16>(linker, lib, &symbol, name),
([Type::S32], []) => link_func_1_0::<i32>(linker, lib, &symbol, name),
([Type::U32], []) => link_func_1_0::<u32>(linker, lib, &symbol, name),
([Type::S64], []) => link_func_1_0::<i64>(linker, lib, &symbol, name),
([Type::U64], []) => link_func_1_0::<u64>(linker, lib, &symbol, name),
([Type::Float32], []) => link_func_1_0::<f32>(linker, lib, &symbol, name),
([Type::Float64], []) => link_func_1_0::<f64>(linker, lib, &symbol, name),
([Type::Char], []) => link_func_1_0::<char>(linker, lib, &symbol, name),
([Type::String], []) => {
let f = dlsym::<unsafe extern "C" fn(*const u8, usize)>(lib, &symbol)?;
let f = *f;
linker.func_wrap(name, move |_, (s,): (Box<str>,)| {
unsafe { f(s.as_ptr(), s.len()) };
Ok(())
})
}
([Type::List(ty)], []) if ty.ty() == Type::U8 => {
let f = dlsym::<unsafe extern "C" fn(*const u8, usize)>(lib, &symbol)?;
let f = *f;
linker.func_wrap(name, move |_, (s,): (Box<[u8]>,)| {
unsafe { f(s.as_ptr(), s.len()) };
Ok(())
})
}
([..], []) => {
bail!("TODO: cranelift-jit")
}
([..], [..]) => {
bail!("TODO: cranelift-jit")
}
}
.with_context(|| format!("failed to define function `{name}`"))
}
impl Plugin {
pub fn load(src: impl AsRef<Path>) -> anyhow::Result<Self> {
let src = src.as_ref();
let lib =
if src.has_root() || src.extension().is_some() || src.parent() != Some(Path::new("")) {
unsafe { Library::new(src) }
} else {
unsafe { Library::new(library_filename(src)) }
}
.context("failed to load dynamic library")?;
Ok(Self { lib })
}
pub fn add_to_linker(
&self,
engine: &wasmtime::Engine,
linker: &mut LinkerInstance<impl CabishView>,
instance_name: &str,
ty: &types::ComponentInstance,
) -> anyhow::Result<()> {
let Plugin { lib } = self;
for (name, ty) in ty.exports(engine) {
match ty {
ComponentItem::ComponentFunc(ty) => {
link_func(linker, lib, &ty, instance_name, name)
.with_context(|| format!("failed to define function `{name}`"))?;
}
ComponentItem::Resource(_ty) => bail!("resources not supported yet"),
ComponentItem::CoreFunc(..)
| ComponentItem::Module(..)
| ComponentItem::Component(..)
| ComponentItem::ComponentInstance(..)
| ComponentItem::Type(_) => {}
}
}
Ok(())
}
}