diff --git a/Cargo.toml b/Cargo.toml index eb1466b202..1e742e2c1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,10 @@ rayon = "1.1" wasm-webidl-bindings = "0.4" [workspace] -members = ["misc/wasmtime-py"] +members = [ + "misc/wasmtime-rust", + "misc/wasmtime-py", +] [features] lightbeam = ["wasmtime-environ/lightbeam", "wasmtime-jit/lightbeam"] diff --git a/misc/wasmtime-rust/Cargo.toml b/misc/wasmtime-rust/Cargo.toml new file mode 100644 index 0000000000..0374fe8a8f --- /dev/null +++ b/misc/wasmtime-rust/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wasmtime-rust" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +test = false +doctest = false + +[dependencies] +cranelift-codegen = "0.38.0" +cranelift-native = "0.38.0" +failure = "0.1.5" +wasmtime-interface-types = { path = "../../wasmtime-interface-types" } +wasmtime-jit = { path = "../../wasmtime-jit" } +wasmtime-rust-macro = { path = "./macro" } diff --git a/misc/wasmtime-rust/README.md b/misc/wasmtime-rust/README.md new file mode 100644 index 0000000000..c524b9482f --- /dev/null +++ b/misc/wasmtime-rust/README.md @@ -0,0 +1,37 @@ +# `wasmtime-rust` - Using WebAssembly from Rust + +This crate is intended to be an example of how to load WebAssembly files from a +native Rust application. You can always use `wasmtime` and its family of crates +directly, but the purpose of this crate is to provide an ergonomic macro: + +```rust +#[wasmtime_rust::wasmtime] +trait WasmMarkdown { + fn render(&mut self, input: &str) -> String; +} + +fn main() -> Result<(), failure::Error> { + let mut markdown = WasmMarkdown::load_file("markdown.wasm")?; + println!("{}", markdown.render("# Hello, Rust!")); + + Ok(()) +} +``` + +The `wasmtime` macro defined in the `wasmtime-rust` crate is placed on a `trait` +which includes the set of functionality which a wasm module should export. In +this case we're expecting one `render` function which takes and returns a +string. + +The macro expands to a `struct` with all of the methods on the trait (they must +all be `&mut self`) and one function called `load_file` to actually instantiate +the module. + +Note that this macro is still in early stages of development, so error messages +aren't great yet and all functionality isn't supported yet. + +## Missing features + +Currently if the wasm module imports any symbols outside of the WASI namespace +the module will not load. It's intended that support for this will be added soon +though! diff --git a/misc/wasmtime-rust/examples/markdown.rs b/misc/wasmtime-rust/examples/markdown.rs new file mode 100644 index 0000000000..7147bf9835 --- /dev/null +++ b/misc/wasmtime-rust/examples/markdown.rs @@ -0,0 +1,13 @@ +use wasmtime_rust::wasmtime; + +#[wasmtime] +trait WasmMarkdown { + fn render(&mut self, input: &str) -> String; +} + +fn main() -> Result<(), failure::Error> { + let mut markdown = WasmMarkdown::load_file("markdown.wasm")?; + println!("{}", markdown.render("# Hello, Rust!")); + + Ok(()) +} diff --git a/misc/wasmtime-rust/macro/Cargo.toml b/misc/wasmtime-rust/macro/Cargo.toml new file mode 100644 index 0000000000..f6e8b3a41e --- /dev/null +++ b/misc/wasmtime-rust/macro/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "wasmtime-rust-macro" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +proc-macro = true +test = false +doctest = false + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0", features = ['full'] } diff --git a/misc/wasmtime-rust/macro/README.md b/misc/wasmtime-rust/macro/README.md new file mode 100644 index 0000000000..8199860771 --- /dev/null +++ b/misc/wasmtime-rust/macro/README.md @@ -0,0 +1,5 @@ +# `wasmtime-rust-macro` + +This is the actual definition of the `#[wasmtime]` macro, but it's intended that +this crate isn't used directly but rather the `wasmtime-rust` crate is used +instead. diff --git a/misc/wasmtime-rust/macro/src/lib.rs b/misc/wasmtime-rust/macro/src/lib.rs new file mode 100644 index 0000000000..0cdce6d919 --- /dev/null +++ b/misc/wasmtime-rust/macro/src/lib.rs @@ -0,0 +1,159 @@ +extern crate proc_macro; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::spanned::Spanned; + +#[proc_macro_attribute] +pub fn wasmtime( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let item = syn::parse_macro_input!(item as syn::ItemTrait); + expand(item).unwrap_or_else(|e| e.to_compile_error()).into() +} + +fn expand(item: syn::ItemTrait) -> syn::Result { + let definition = generate_struct(&item)?; + let load = generate_load(&item)?; + let methods = generate_methods(&item)?; + let name = &item.ident; + + Ok(quote! { + #definition + impl #name { + #load + #methods + } + }) +} + +fn generate_struct(item: &syn::ItemTrait) -> syn::Result { + let vis = &item.vis; + let name = &item.ident; + let root = root(); + Ok(quote! { + #vis struct #name { + cx: #root::wasmtime_jit::Context, + handle: #root::wasmtime_jit::InstanceHandle, + data: #root::wasmtime_interface_types::ModuleData, + } + }) +} + +fn generate_load(item: &syn::ItemTrait) -> syn::Result { + let vis = &item.vis; + let name = &item.ident; + let root = root(); + Ok(quote! { + #vis fn load_file(path: impl AsRef) -> Result<#name, #root::failure::Error> { + let bytes = std::fs::read(path)?; + + let isa = { + let isa_builder = #root::cranelift_native::builder() + .map_err(|s| #root::failure::format_err!("{}", s))?; + let flag_builder = #root::cranelift_codegen::settings::builder(); + isa_builder.finish(#root::cranelift_codegen::settings::Flags::new(flag_builder)) + }; + + let mut cx = #root::wasmtime_jit::Context::with_isa(isa); + let data = #root::wasmtime_interface_types::ModuleData::new(&bytes)?; + let handle = cx.instantiate_module(None, &bytes)?; + + Ok(#name { cx, handle, data }) + } + }) +} + +fn generate_methods(item: &syn::ItemTrait) -> syn::Result { + macro_rules! bail { + ($e:expr, $($fmt:tt)*) => ( + return Err(syn::Error::new($e.span(), format!($($fmt)*))); + ) + } + let mut result = TokenStream::new(); + let root = root(); + + for item in item.items.iter() { + let method = match item { + syn::TraitItem::Method(f) => f, + other => bail!(other, "only methods are allowed"), + }; + if let Some(e) = &method.default { + bail!(e, "cannot specify an implementation of methods"); + } + if let Some(t) = &method.sig.constness { + bail!(t, "cannot be `const`"); + } + if let Some(t) = &method.sig.asyncness { + bail!(t, "cannot be `async`"); + } + + let mut args = Vec::new(); + for arg in method.sig.inputs.iter() { + let arg = match arg { + syn::FnArg::Receiver(_) => continue, + syn::FnArg::Typed(arg) => arg, + }; + let ident = match &*arg.pat { + syn::Pat::Ident(i) => i, + other => bail!(other, "must use bare idents for arguments"), + }; + if let Some(t) = &ident.by_ref { + bail!(t, "arguments cannot bind by reference"); + } + if let Some(t) = &ident.mutability { + bail!(t, "arguments cannot be mutable"); + } + if let Some((_, t)) = &ident.subpat { + bail!(t, "arguments cannot have sub-bindings"); + } + let ident = &ident.ident; + args.push(quote! { + #root::wasmtime_interface_types::Value::from(#ident) + }); + } + + let convert_ret = match &method.sig.output { + syn::ReturnType::Default => { + quote! { + <() as #root::FromVecValue>::from(results) + } + } + syn::ReturnType::Type(_, ty) => match &**ty { + syn::Type::Tuple(..) => { + quote! { <#ty as #root::FromVecValue>::from(results) } + } + _ => { + quote! { <(#ty,) as #root::FromVecValue>::from(results).map(|t| t.0) } + } + }, + }; + + let sig = &method.sig; + let attrs = &method.attrs; + let name = &method.sig.ident; + + result.extend(quote! { + #(#attrs)* + #sig { + let args = [ + #(#args),* + ]; + let results = self.data.invoke( + &mut self.cx, + &mut self.handle, + stringify!(#name), + &args, + ).expect("wasm execution failed"); + #convert_ret.expect("failed to convert return type") + } + }); + } + + Ok(result) +} + +fn root() -> TokenStream { + quote! { wasmtime_rust::__rt } +} diff --git a/misc/wasmtime-rust/src/lib.rs b/misc/wasmtime-rust/src/lib.rs new file mode 100644 index 0000000000..da9ca53ddd --- /dev/null +++ b/misc/wasmtime-rust/src/lib.rs @@ -0,0 +1,47 @@ +pub use wasmtime_rust_macro::wasmtime; + +// modules used by the macro +#[doc(hidden)] +pub mod __rt { + pub use cranelift_codegen; + pub use cranelift_native; + pub use failure; + pub use wasmtime_interface_types; + pub use wasmtime_jit; + + use std::convert::{TryFrom, TryInto}; + use wasmtime_interface_types::Value; + + pub trait FromVecValue: Sized { + fn from(list: Vec) -> Result; + } + + macro_rules! tuple { + ($(($($a:ident),*),)*) => ($( + impl<$($a: TryFrom),*> FromVecValue for ($($a,)*) + where $(failure::Error: From<$a::Error>,)* + { + #[allow(non_snake_case)] + fn from(list: Vec) -> Result { + let mut iter = list.into_iter(); + $( + let $a = iter.next() + .ok_or_else(|| failure::format_err!("not enough values"))? + .try_into()?; + )* + if iter.next().is_some() { + failure::format_err!("too many return values"); + } + Ok(($($a,)*)) + } + } + )*) + } + + tuple! { + (), + (A), + (A, B), + (A, B, C), + } +} diff --git a/wasmtime-interface-types/src/value.rs b/wasmtime-interface-types/src/value.rs index 3fd9f7c084..48fd610c61 100644 --- a/wasmtime-interface-types/src/value.rs +++ b/wasmtime-interface-types/src/value.rs @@ -1,3 +1,4 @@ +use std::convert::TryFrom; use std::fmt; /// The set of all possible WebAssembly Interface Types @@ -20,6 +21,17 @@ macro_rules! from { Value::$b(val) } } + + impl TryFrom for $a { + type Error = failure::Error; + + fn try_from(val: Value) -> Result<$a, Self::Error> { + match val { + Value::$b(v) => Ok(v), + v => failure::bail!("cannot convert {:?} to {}", v, stringify!($a)), + } + } + } )*) }