From f61ef446d3b6f0b6360a94595026f24bb8b8d1c8 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 16:50:09 +0200 Subject: [PATCH] Documentation pass --- crates/plugin_runtime/src/plugin.rs | 128 +++++++++------------------- plugins/json_language/src/lib.rs | 2 +- 2 files changed, 40 insertions(+), 90 deletions(-) diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index a37cd9e60c5a4572fab7ad7ab43ea4a69ead90e1..27633d9eb9d9c02b7d3fb70887c40c145f7b2dec 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -13,6 +13,7 @@ use wasmtime::{ }; use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; +/// Represents a resource currently managed by the plugin, like a file descriptor. pub struct PluginResource(u32); #[repr(C)] @@ -34,6 +35,7 @@ impl WasiBuffer { } } +/// Represents a typed WebAssembly function. pub struct WasiFn { function: TypedFunc, _function_type: PhantomData R>, @@ -50,6 +52,10 @@ impl Clone for WasiFn { } } +/// This struct is used to build a new [`Plugin`], using the builder pattern. +/// Create a new default plugin with `PluginBuilder::new_with_default_ctx`, +/// and add host-side exported functions using `host_function` and `host_function_async`. +/// Finalize the plugin by calling [`init`]. pub struct PluginBuilder { wasi_ctx: WasiCtx, engine: Engine, @@ -57,6 +63,8 @@ pub struct PluginBuilder { } impl PluginBuilder { + /// Create a new [`PluginBuilder`] with the given WASI context. + /// Using the default context is a safe bet, see [`new_with_default_context`]. pub fn new(wasi_ctx: WasiCtx) -> Result { let mut config = Config::default(); config.async_support(true); @@ -71,89 +79,17 @@ impl PluginBuilder { }) } + /// Create a new `PluginBuilder` that inherits the + /// host processes' access to `stdout` and `stderr`. pub fn new_with_default_ctx() -> Result { let wasi_ctx = WasiCtxBuilder::new() - .inherit_stdin() + .inherit_stdout() .inherit_stderr() .build(); Self::new(wasi_ctx) } - // pub fn host_function_async( - // mut self, - // name: &str, - // function: impl Fn(A) -> Pin + Send + Sync>> + Sync + Send + 'static, - // ) -> Result - // where - // A: DeserializeOwned + Send, - // R: Serialize + Send, - // { - // self.linker.func_wrap1_async( - // "env", - // &format!("__{}", name), - // move |caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| { - // // let function = &function; - // Box::new(async move { - // // grab a handle to the memory - // let mut plugin_memory = match caller.get_export("memory") { - // Some(Extern::Memory(mem)) => mem, - // _ => return Err(Trap::new("Could not grab slice of plugin memory"))?, - // }; - - // let buffer = WasiBuffer::from_u64(packed_buffer); - - // // get the args passed from Guest - // let args = Wasi::buffer_to_type(&mut plugin_memory, &mut caller, &buffer)?; - - // // Call the Host-side function - // let result: R = function(args).await; - - // // Serialize the result back to guest - // let result = Wasi::serialize_to_bytes(result).map_err(|_| { - // Trap::new("Could not serialize value returned from function") - // })?; - - // // Ok((buffer, plugin_memory, result)) - // Wasi::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer).await?; - - // let buffer = Wasi::bytes_to_buffer( - // caller.data().alloc_buffer(), - // &mut plugin_memory, - // &mut caller, - // result, - // ) - // .await?; - - // Ok(buffer.into_u64()) - // }) - // }, - // )?; - // Ok(self) - // } - - // pub fn host_function_async(mut self, name: &str, function: F) -> Result - // where - // F: Fn(u64) -> Pin + Send + Sync + 'static>> - // + Send - // + Sync - // + 'static, - // { - // self.linker.func_wrap1_async( - // "env", - // &format!("__{}", name), - // move |_: Caller<'_, WasiCtxAlloc>, _: u64| { - // // let function = &function; - // Box::new(async { - // // let function = function; - // // Call the Host-side function - // let result: u64 = function(7).await; - // Ok(result) - // }) - // }, - // )?; - // Ok(self) - // } - + /// Add an `async` host function. See [`host_function`] for details. pub fn host_function_async( mut self, name: &str, @@ -218,6 +154,22 @@ impl PluginBuilder { Ok(self) } + /// Add a new host function to the given `PluginBuilder`. + /// A host function is a function defined host-side, in Rust, + /// that is accessible guest-side, in WebAssembly. + /// You can specify host-side functions to import using + /// the `#[input]` macro attribute: + /// ```ignore + /// #[input] + /// fn total(counts: Vec) -> f64; + /// ``` + /// When loading a plugin, you need to provide all host functions the plugin imports: + /// ```ignore + /// let plugin = PluginBuilder::new_with_default_context() + /// .host_function("total", |counts| counts.iter().fold(0.0, |tot, n| tot + n)) + /// // and so on... + /// ``` + /// And that's a wrap! pub fn host_function( mut self, name: &str, @@ -276,6 +228,8 @@ impl PluginBuilder { Ok(self) } + /// Initializes a [`Plugin`] from a given compiled Wasm module. + /// Both binary (`.wasm`) and text (`.wat`) module formats are supported. pub async fn init>(self, module: T) -> Result { Plugin::init(module.as_ref().to_vec(), self).await } @@ -310,6 +264,8 @@ impl WasiCtxAlloc { } } +/// Represents a WebAssembly plugin, with access to the WebAssembly System Inferface. +/// Build a new plugin using [`PluginBuilder`]. pub struct Plugin { engine: Engine, module: Module, @@ -318,6 +274,8 @@ pub struct Plugin { } impl Plugin { + /// Dumps the *entirety* of Wasm linear memory to `stdout`. + /// Don't call this unless you're debugging a memory issue! pub fn dump_memory(data: &[u8]) { for (i, byte) in data.iter().enumerate() { if i % 32 == 0 { @@ -400,6 +358,8 @@ impl Plugin { } /// Returns `true` if the resource existed and was removed. + /// Currently the only resource we support is adding scoped paths (e.g. folders and files) + /// to plugins using [`attach_path`]. pub fn remove_resource(&mut self, resource: PluginResource) -> Result<(), Error> { self.store .data_mut() @@ -410,16 +370,6 @@ impl Plugin { Ok(()) } - // pub fn with_resource( - // &mut self, - // resource: WasiResource, - // callback: fn(&mut Self) -> Result, - // ) -> Result { - // let result = callback(self); - // self.remove_resource(resource)?; - // return result; - // } - // So this call function is kinda a dance, I figured it'd be a good idea to document it. // the high level is we take a serde type, serialize it to a byte array, // (we're doing this using bincode for now) @@ -463,12 +413,14 @@ impl Plugin { // This isn't a problem because Wasm stops executing after the function returns, // so the heap is still valid for our inspection when we want to pull things out. + /// Serializes a given type to bytes. fn serialize_to_bytes(item: A) -> Result, Error> { // serialize the argument using bincode let bytes = bincode::serialize(&item)?; Ok(bytes) } + /// Deserializes a given type from bytes. fn deserialize_to_type(bytes: &[u8]) -> Result { // serialize the argument using bincode let bytes = bincode::deserialize(bytes)?; @@ -522,6 +474,7 @@ impl Plugin { Ok(result) } + // TODO: don't allocate a new `Vec`! /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer. fn buffer_to_bytes<'a>( plugin_memory: &'a Memory, @@ -570,9 +523,6 @@ impl Plugin { handle: &WasiFn, arg: A, ) -> Result { - // dbg!(&handle.name); - // dbg!(serde_json::to_string(&arg)).unwrap(); - let mut plugin_memory = self .instance .get_memory(&mut self.store, "memory") diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 7299a4748d6a21aaac47b8d1785f01b1c4a45633..5d20bd369abea2ba8347aa3882ff130b395d9d3e 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -5,7 +5,7 @@ use std::fs; use std::path::PathBuf; #[import] -fn command(string: &str) -> Option; +fn command(string: &str) -> Option>; // #[no_mangle] // // TODO: switch len from usize to u32?