1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use core::ffi::{c_char, c_void, CStr};
use core::ptr::NonNull;
use core::slice;

use std::sync::{Arc, LazyLock, Mutex};

use anyhow::{bail, ensure, Context as _};
use tracing::{instrument, trace_span};
use tracing_subscriber::EnvFilter;
use wasmtime::component::{Resource, ResourceAny, Val};
use wasmtime_cabish::{deref_arg, lift_params, lower_results};
use wasmtime_wasi::WasiView;

mod ffi;

#[repr(C)]
#[derive(Debug)]
pub struct List<T> {
    pub ptr: *const T,
    pub len: usize,
}

static ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(wasmtime::Engine::default);

#[repr(C)]
#[derive(Debug)]
pub struct Config {
    pub wasm: List<u8>,
}

pub struct Instance {
    instance: Mutex<wadge::Instance>,
    subscriber: Arc<dyn tracing::Subscriber + Send + Sync + 'static>,
}

#[instrument(level = "trace")]
fn instantiate(config: Config) -> anyhow::Result<Instance> {
    let Config { wasm } = config;
    ensure!(!wasm.ptr.is_null(), "`wasm_ptr` must not be null");
    let wasm = unsafe { slice::from_raw_parts(wasm.ptr, wasm.len) };
    let instance = wadge::instantiate(wadge::Config {
        engine: ENGINE.clone(),
        wasm,
    })
    .context("failed to instantiate component")?;
    let subscriber = tracing_subscriber::fmt()
        .without_time()
        .with_env_filter(EnvFilter::from_env("WADGE_LOG"))
        .finish();
    Ok(Instance {
        instance: instance.into(),
        subscriber: Arc::new(subscriber),
    })
}

#[instrument(level = "debug", ret(level = "debug"))]
fn call(
    instance_ptr: *mut c_void,
    instance: *const c_char,
    name: *const c_char,
    args: *const *mut c_void,
) -> anyhow::Result<()> {
    let inst =
        NonNull::new(instance_ptr.cast::<Instance>()).context("`instance_ptr` must not be null")?;
    ensure!(!instance.is_null(), "`instance` must not be null");
    ensure!(!name.is_null(), "`name` must not be null");
    let instance = unsafe { CStr::from_ptr(instance) }
        .to_str()
        .context("`instance` is not valid UTF-8")?;
    let name = unsafe { CStr::from_ptr(name) }
        .to_str()
        .context("`name` is not valid UTF-8")?;
    let inst = unsafe { inst.as_ref() };
    let _log = tracing::subscriber::set_default(Arc::clone(&inst.subscriber));
    let Ok(mut inst) = inst.instance.lock() else {
        bail!("failed to lock instance mutex")
    };
    let _span = trace_span!("call", "instance" = instance, "name" = name).entered();
    if let Some(ty) = name.strip_prefix("[resource-drop]") {
        let (rep, _) = deref_arg::<u32>(args)?;
        let rep = unsafe { rep.read() };
        let store = inst.store();
        let res = WasiView::table(store.data_mut())
            .delete::<ResourceAny>(Resource::new_own(rep))
            .with_context(|| format!("failed to delete `{ty}` from table"))?;
        res.resource_drop(store)
            .with_context(|| format!("failed to drop `{ty}`"))?;
    } else {
        let mut func = inst
            .func(instance, name)
            .context("failed to lookup function")?;
        let tys = func.params();
        let (params, args) =
            lift_params(func.store(), &tys, args).context("failed to lift parameters")?;
        let results_ty = func.results();
        let mut results = vec![Val::Bool(false); results_ty.len()];
        func.call(&params, &mut results)?;
        lower_results(func.store(), results, &results_ty, args)
            .context("failed to lower results")?;
    }
    Ok(())
}