1use core::ffi::{c_char, c_void, CStr};
2use core::ptr::NonNull;
3use core::slice;
4
5use std::sync::{Arc, LazyLock, Mutex};
6
7use anyhow::{bail, ensure, Context as _};
8use tracing::{instrument, trace_span};
9use tracing_subscriber::EnvFilter;
10use wasmtime::component::{Resource, ResourceAny, Val};
11use wasmtime_cabish::{deref_arg, lift_params, lower_results};
12use wasmtime_wasi::WasiView;
13
14mod ffi;
15
16#[repr(C)]
17#[derive(Debug)]
18pub struct List<T> {
19 pub ptr: *const T,
20 pub len: usize,
21}
22
23static ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(wasmtime::Engine::default);
24
25#[repr(C)]
26#[derive(Debug)]
27pub struct Config {
28 pub wasm: List<u8>,
29}
30
31pub struct Instance {
32 instance: Mutex<wadge::Instance>,
33 subscriber: Arc<dyn tracing::Subscriber + Send + Sync + 'static>,
34}
35
36#[instrument(level = "trace")]
37fn instantiate(config: Config) -> anyhow::Result<Instance> {
38 let Config { wasm } = config;
39 ensure!(!wasm.ptr.is_null(), "`wasm_ptr` must not be null");
40 let wasm = unsafe { slice::from_raw_parts(wasm.ptr, wasm.len) };
41 let instance = wadge::instantiate(wadge::Config {
42 engine: ENGINE.clone(),
43 wasm,
44 })
45 .context("failed to instantiate component")?;
46 let subscriber = tracing_subscriber::fmt()
47 .without_time()
48 .with_env_filter(EnvFilter::from_env("WADGE_LOG"))
49 .finish();
50 Ok(Instance {
51 instance: instance.into(),
52 subscriber: Arc::new(subscriber),
53 })
54}
55
56#[instrument(level = "debug", ret(level = "debug"))]
57fn call(
58 instance_ptr: *mut c_void,
59 instance: *const c_char,
60 name: *const c_char,
61 args: *const *mut c_void,
62) -> anyhow::Result<()> {
63 let inst =
64 NonNull::new(instance_ptr.cast::<Instance>()).context("`instance_ptr` must not be null")?;
65 ensure!(!instance.is_null(), "`instance` must not be null");
66 ensure!(!name.is_null(), "`name` must not be null");
67 let instance = unsafe { CStr::from_ptr(instance) }
68 .to_str()
69 .context("`instance` is not valid UTF-8")?;
70 let name = unsafe { CStr::from_ptr(name) }
71 .to_str()
72 .context("`name` is not valid UTF-8")?;
73 let inst = unsafe { inst.as_ref() };
74 let _log = tracing::subscriber::set_default(Arc::clone(&inst.subscriber));
75 let Ok(mut inst) = inst.instance.lock() else {
76 bail!("failed to lock instance mutex")
77 };
78 let _span = trace_span!("call", "instance" = instance, "name" = name).entered();
79 if let Some(ty) = name.strip_prefix("[resource-drop]") {
80 let (rep, _) = deref_arg::<u32>(args)?;
81 let rep = unsafe { rep.read() };
82 let store = inst.store();
83 let res = WasiView::table(store.data_mut())
84 .delete::<ResourceAny>(Resource::new_own(rep))
85 .with_context(|| format!("failed to delete `{ty}` from table"))?;
86 res.resource_drop(store)
87 .with_context(|| format!("failed to drop `{ty}`"))?;
88 } else {
89 let mut func = inst
90 .func(instance, name)
91 .context("failed to lookup function")?;
92 let tys = func.params();
93 let (params, args) =
94 lift_params(func.store(), &tys, args).context("failed to lift parameters")?;
95 let results_ty = func.results();
96 let mut results = vec![Val::Bool(false); results_ty.len()];
97 func.call(¶ms, &mut results)?;
98 lower_results(func.store(), results, &results_ty, args)
99 .context("failed to lower results")?;
100 }
101 Ok(())
102}