esvc_wasm/
lib.rs

1use anyhow::{self as anyhow, anyhow as anyhow_, Context};
2use esvc_traits::Engine;
3use rayon::prelude::*;
4
5#[derive(Clone)]
6pub struct WasmEngine {
7    wte: wasmtime::Engine,
8    cmds: Vec<wasmtime::Module>,
9}
10
11impl Engine for WasmEngine {
12    type Error = anyhow::Error;
13    type Arg = Vec<u8>;
14    type Dat = Vec<u8>;
15
16    fn run_event_bare(&self, cmd: u32, arg: &Vec<u8>, dat: &Vec<u8>) -> anyhow::Result<Vec<u8>> {
17        let cmd: usize = cmd
18            .try_into()
19            .map_err(|_| anyhow_!("command ID overflow cmd={}", cmd))?;
20        let cmd = self
21            .cmds
22            .get(cmd)
23            .ok_or_else(|| anyhow_!("command {} not found", cmd))?;
24
25        let datlen: i32 = dat
26            .len()
27            .try_into()
28            .map_err(|_| anyhow_!("argument buffer overflow dat.len={}", dat.len()))?;
29        let evarglen: i32 = arg
30            .len()
31            .try_into()
32            .map_err(|_| anyhow_!("argument buffer overflow ev.arg.len={}", arg.len()))?;
33
34        // WASM stuff
35
36        let mut store = wasmtime::Store::new(&self.wte, ());
37        let instance = wasmtime::Instance::new(&mut store, cmd, &[])?;
38
39        let memory = instance
40            .get_memory(&mut store, "memory")
41            .ok_or_else(|| anyhow_!("unable to get export `memory`"))?;
42
43        let retptr = instance
44            .get_typed_func::<i32, i32, _>(&mut store, "__wbindgen_add_to_stack_pointer")?
45            .call(&mut store, -16)?;
46        let malloc = instance.get_typed_func::<i32, i32, _>(&mut store, "__wbindgen_malloc")?;
47        //let free = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "__wbindgen_free")?;
48
49        // transform :: retptr:i32 -> evargptr:i32 -> evarglen:i32 -> datptr:i32 -> datlen:i32 -> ()
50        let transform =
51            instance.get_typed_func::<(i32, i32, i32, i32, i32), (), _>(&mut store, "transform")?;
52
53        let evargptr = malloc.call(&mut store, evarglen)?;
54        memory.write(&mut store, evargptr.try_into()?, arg)?;
55
56        let datptr = malloc.call(&mut store, datlen)?;
57        memory.write(&mut store, datptr.try_into()?, dat)?;
58
59        // the main transform call
60        let () = transform.call(&mut store, (retptr, evargptr, evarglen, datptr, datlen))?;
61
62        // retrieve results
63        let ret = {
64            // *retptr :: (retptr2:i32, retlen2:i32)
65            let mut retbuf = [0u8; 8];
66            memory.read(&mut store, retptr.try_into()?, &mut retbuf)?;
67            let (retp0, retp1) = retbuf.split_at(4);
68            let retptr2: usize =
69                i32::from_le_bytes(<[u8; 4]>::try_from(retp0).unwrap()).try_into()?;
70            let retlen2: usize =
71                i32::from_le_bytes(<[u8; 4]>::try_from(retp1).unwrap()).try_into()?;
72            memory
73                .data(&mut store)
74                .get(retptr2..retptr2 + retlen2)
75                .with_context(|| "return value length out of bounds".to_string())?
76                .to_vec()
77        };
78
79        Ok(ret)
80    }
81}
82
83impl WasmEngine {
84    pub fn new() -> anyhow::Result<Self> {
85        let wtc = wasmtime::Config::default();
86        Ok(Self {
87            wte: wasmtime::Engine::new(&wtc)?,
88            cmds: Vec::new(),
89        })
90    }
91
92    pub fn add_commands<II, Iter, Item>(&mut self, wasms: II) -> anyhow::Result<(u32, usize)>
93    where
94        II: IntoIterator<IntoIter = Iter>,
95        Iter: Iterator<Item = Item> + Send,
96        Item: AsRef<[u8]> + Send,
97    {
98        let orig_id = self.cmds.len();
99        let id: u32 = orig_id.try_into()?;
100        self.cmds.extend(
101            wasms
102                .into_iter()
103                .par_bridge()
104                .map(|cmd| wasmtime::Module::new(&self.wte, cmd))
105                .collect::<Result<Vec<_>, _>>()?,
106        );
107        Ok((id, self.cmds.len() - orig_id))
108    }
109}