diff --git a/Cargo.lock b/Cargo.lock index 7b8c50b2f2..f3820ae53b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2567,6 +2567,7 @@ dependencies = [ "wasi-common", "wasmtime", "wasmtime-runtime", + "wasmtime-wiggle", "wig", "wiggle", ] @@ -2580,6 +2581,27 @@ dependencies = [ "wast 17.0.0", ] +[[package]] +name = "wasmtime-wiggle" +version = "0.18.0" +dependencies = [ + "wasmtime", + "wasmtime-wiggle-macro", + "wiggle", + "witx", +] + +[[package]] +name = "wasmtime-wiggle-macro" +version = "0.18.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wiggle-generate", + "witx", +] + [[package]] name = "wast" version = "11.0.0" diff --git a/Cargo.toml b/Cargo.toml index 147c2d6c5b..b9255545e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ members = [ "crates/misc/run-examples", "crates/misc/rust", "crates/wiggle", + "crates/wiggle/wasmtime", "examples/fib-debug/wasm", "examples/wasi/wasm", "fuzz", diff --git a/crates/wasi-common/wig/src/lib.rs b/crates/wasi-common/wig/src/lib.rs index 1a6688621b..d0fd3f41e0 100644 --- a/crates/wasi-common/wig/src/lib.rs +++ b/crates/wasi-common/wig/src/lib.rs @@ -38,11 +38,6 @@ pub fn define_wasi_struct(args: TokenStream) -> TokenStream { wasi::define_struct(args.into()).into() } -#[proc_macro] -pub fn define_wasi_struct_for_wiggle(args: TokenStream) -> TokenStream { - wasi::define_struct_for_wiggle(args.into()).into() -} - #[proc_macro] pub fn define_hostcalls(args: TokenStream) -> TokenStream { hostcalls::define(args.into()).into() diff --git a/crates/wasi-common/wig/src/wasi.rs b/crates/wasi-common/wig/src/wasi.rs index 479cb2d867..9e31879659 100644 --- a/crates/wasi-common/wig/src/wasi.rs +++ b/crates/wasi-common/wig/src/wasi.rs @@ -273,282 +273,3 @@ pub fn define_struct(args: TokenStream) -> TokenStream { } } } - -pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream { - let path = utils::witx_path_from_args(args); - let doc = match witx::load(&[&path]) { - Ok(doc) => doc, - Err(e) => { - panic!("error opening file {}: {}", path.display(), e); - } - }; - - let mut fields = Vec::new(); - let mut get_exports = Vec::new(); - let mut ctor_externs = Vec::new(); - let mut ctor_fields = Vec::new(); - let mut linker_add = Vec::new(); - - for module in doc.modules() { - let module_name = module.name.as_str(); - let module_id = Ident::new(module_name, Span::call_site()); - for func in module.funcs() { - let name = func.name.as_str(); - let name_ident = Ident::new(name, Span::call_site()); - fields.push(quote! { pub #name_ident: wasmtime::Func }); - get_exports.push(quote! { #name => Some(&self.#name_ident) }); - ctor_fields.push(name_ident.clone()); - linker_add.push(quote! { - linker.define(#module_name, #name, self.#name_ident.clone())?; - }); - // `proc_exit` is special; it's essentially an unwinding primitive, - // so we implement it in the runtime rather than use the implementation - // in wasi-common. - if name == "proc_exit" { - ctor_externs.push(quote! { - let #name_ident = wasmtime::Func::wrap(store, crate::wasi_proc_exit); - }); - continue; - } - - let mut shim_arg_decls = Vec::new(); - let mut params = Vec::new(); - let mut formats = Vec::new(); - let mut format_args = Vec::new(); - let mut hostcall_args = Vec::new(); - - for param in func.params.iter() { - let name = utils::param_name(param); - - // Registers a new parameter to the shim we're making with the - // given `name`, the `abi_ty` wasm type and `hex` defines - // whether it's debug-printed in a hex format or not. - // - // This will register a whole bunch of things: - // - // * The cranelift type for the parameter - // * Syntax to specify the actual function parameter - // * How to log the parameter value in a call to `trace!` - // * How to actually pass this argument to the host - // implementation, converting as necessary. - let mut add_param = |name: &Ident, abi_ty: Abi, hex: bool| { - match abi_ty { - Abi::I32 => { - params.push(quote! { types::I32 }); - shim_arg_decls.push(quote! { #name: i32 }); - } - Abi::I64 => { - params.push(quote! { types::I64 }); - shim_arg_decls.push(quote! { #name: i64 }); - } - Abi::F32 => { - params.push(quote! { types::F32 }); - shim_arg_decls.push(quote! { #name: f32 }); - } - Abi::F64 => { - params.push(quote! { types::F64 }); - shim_arg_decls.push(quote! { #name: f64 }); - } - } - formats.push(format!("{}={}", name, if hex { "{:#x}" } else { "{}" },)); - format_args.push(name.clone()); - hostcall_args.push(quote! { #name as _ }); - }; - - match &*param.tref.type_() { - witx::Type::Int(e) => match e.repr { - witx::IntRepr::U64 => add_param(&name, Abi::I64, false), - _ => add_param(&name, Abi::I32, false), - }, - - witx::Type::Enum(e) => match e.repr { - witx::IntRepr::U64 => add_param(&name, Abi::I64, false), - _ => add_param(&name, Abi::I32, false), - }, - - witx::Type::Flags(f) => match f.repr { - witx::IntRepr::U64 => add_param(&name, Abi::I64, true), - _ => add_param(&name, Abi::I32, true), - }, - - witx::Type::Builtin(witx::BuiltinType::Char8) - | witx::Type::Builtin(witx::BuiltinType::S8) - | witx::Type::Builtin(witx::BuiltinType::U8) - | witx::Type::Builtin(witx::BuiltinType::S16) - | witx::Type::Builtin(witx::BuiltinType::U16) - | witx::Type::Builtin(witx::BuiltinType::S32) - | witx::Type::Builtin(witx::BuiltinType::U32) - | witx::Type::Builtin(witx::BuiltinType::USize) => { - add_param(&name, Abi::I32, false); - } - - witx::Type::Builtin(witx::BuiltinType::S64) - | witx::Type::Builtin(witx::BuiltinType::U64) => { - add_param(&name, Abi::I64, false); - } - - witx::Type::Builtin(witx::BuiltinType::F32) => { - add_param(&name, Abi::F32, false); - } - - witx::Type::Builtin(witx::BuiltinType::F64) => { - add_param(&name, Abi::F64, false); - } - - // strings/arrays have an extra ABI parameter for the length - // of the array passed. - witx::Type::Builtin(witx::BuiltinType::String) | witx::Type::Array(_) => { - add_param(&name, Abi::I32, true); - let len = format_ident!("{}_len", name); - add_param(&len, Abi::I32, false); - } - - witx::Type::ConstPointer(_) - | witx::Type::Handle(_) - | witx::Type::Pointer(_) => { - add_param(&name, Abi::I32, true); - } - - witx::Type::Struct(_) | witx::Type::Union(_) => { - panic!("unsupported argument type") - } - } - } - - let mut results = func.results.iter(); - let mut ret_ty = quote! { () }; - let mut cvt_ret = quote! {}; - let mut returns = Vec::new(); - let mut handle_early_error = quote! { panic!("error: {:?}", e) }; - - // The first result is returned bare right now... - if let Some(ret) = results.next() { - handle_early_error = quote! { return e.into() }; - match &*ret.tref.type_() { - // Eventually we'll want to add support for more returned - // types, but for now let's just conform to what `*.witx` - // definitions currently use. - witx::Type::Enum(e) => match e.repr { - witx::IntRepr::U16 => { - returns.push(quote! { types::I32 }); - ret_ty = quote! { i32 }; - cvt_ret = quote! { .into() } - } - other => panic!("unsupported ret enum repr {:?}", other), - }, - other => panic!("unsupported first return {:?}", other), - } - } - - // ... and all remaining results are returned via out-poiners - for result in results { - let name = format_ident!("{}", result.name.as_str()); - params.push(quote! { types::I32 }); - shim_arg_decls.push(quote! { #name: i32 }); - formats.push(format!("{}={{:#x}}", name)); - format_args.push(name.clone()); - hostcall_args.push(quote! { #name }); - } - - let format_str = format!("{}({})", name, formats.join(", ")); - ctor_externs.push(quote! { - let my_cx = cx.clone(); - let #name_ident = wasmtime::Func::wrap( - store, - move |caller: wasmtime::Caller<'_> #(,#shim_arg_decls)*| -> #ret_ty { - log::trace!( - #format_str, - #(#format_args),* - ); - unsafe { - let mem = match caller.get_export("memory") { - Some(wasmtime::Extern::Memory(m)) => m, - _ => { - log::warn!("callee does not export a memory as \"memory\""); - let e = wasi_common::wasi::Errno::Inval; - #handle_early_error - } - }; - // Wiggle does not expose any methods for - // functions to re-enter the WebAssembly module, - // or expose the memory via non-wiggle mechanisms. - // Therefore, creating a new BorrowChecker at the - // root of each function invocation is correct. - let bc = wiggle::BorrowChecker::new(); - let mem = WasiMemory { mem, bc }; - wasi_common::wasi::#module_id::#name_ident( - &mut my_cx.borrow_mut(), - &mem, - #(#hostcall_args),* - ) #cvt_ret - } - } - ); - }); - } - } - - quote! { - /// Lightweight `wasmtime::Memory` wrapper so that we can - /// implement `wiggle::GuestMemory` trait on it which is - /// now required to interface with `wasi-common`. - struct WasiMemory { - mem: wasmtime::Memory, - bc: wiggle::BorrowChecker, - } - - unsafe impl wiggle::GuestMemory for WasiMemory { - fn base(&self) -> (*mut u8, u32) { - (self.mem.data_ptr(), self.mem.data_size() as _) - } - fn borrow_checker(&self) -> &wiggle::BorrowChecker { - &self.bc - } - } - - /// An instantiated instance of the wasi exports. - /// - /// This represents a wasi module which can be used to instantiate other - /// wasm modules. This structure exports all that various fields of the - /// wasi instance as fields which can be used to implement your own - /// instantiation logic, if necessary. Additionally [`Wasi::get_export`] - /// can be used to do name-based resolution. - pub struct Wasi { - #(#fields,)* - } - - impl Wasi { - /// Creates a new [`Wasi`] instance. - /// - /// External values are allocated into the `store` provided and - /// configuration of the wasi instance itself should be all - /// contained in the `cx` parameter. - pub fn new(store: &wasmtime::Store, cx: WasiCtx) -> Wasi { - let cx = std::rc::Rc::new(std::cell::RefCell::new(cx)); - #(#ctor_externs)* - - Wasi { - #(#ctor_fields,)* - } - } - - /// Looks up a field called `name` in this structure, returning it - /// if found. - /// - /// This is often useful when instantiating a `wasmtime` instance - /// where name resolution often happens with strings. - pub fn get_export(&self, name: &str) -> Option<&wasmtime::Func> { - match name { - #(#get_exports,)* - _ => None, - } - } - - /// Adds all wasi items to the specified `Linker`. - pub fn add_to_linker(&self, linker: &mut wasmtime::Linker) -> anyhow::Result<()> { - #(#linker_add)* - Ok(()) - } - } - } -} diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 6616a6ef12..229816ebb7 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -19,6 +19,7 @@ wasmtime = { path = "../wasmtime", version = "0.18.0", default-features = false wasmtime-runtime = { path = "../runtime", version = "0.18.0" } wig = { path = "../wasi-common/wig", version = "0.18.0" } wiggle = { path = "../wiggle", version = "0.18.0" } +wasmtime-wiggle = { path = "../wiggle/wasmtime", version = "0.18.0" } [badges] maintenance = { status = "actively-developed" } diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index e92b1d21d9..d6f8e4fad8 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -6,7 +6,34 @@ pub use wasi_common::{WasiCtx, WasiCtxBuilder}; // Defines a `struct Wasi` with member fields and appropriate APIs for dealing // with all the various WASI exports. -wig::define_wasi_struct_for_wiggle!("phases/snapshot/witx/wasi_snapshot_preview1.witx"); +wasmtime_wiggle::wasmtime_integration!({ + // The wiggle code to integrate with lives here: + target: wasi_common::wasi, + // This must be the same witx document as used above: + witx: ["../wasi-common/WASI/phases/snapshot/witx/wasi_snapshot_preview1.witx"], + // This must be the same ctx type as used for the target: + ctx: WasiCtx, + // This macro will emit a struct to represent the instance, + // with this name and docs: + modules: { wasi_snapshot_preview1 => + { name: Wasi, + docs: "An instantiated instance of the wasi exports. + +This represents a wasi module which can be used to instantiate other wasm +modules. This structure exports all that various fields of the wasi instance +as fields which can be used to implement your own instantiation logic, if +necessary. Additionally [`Wasi::get_export`] can be used to do name-based +resolution.", + // Don't use the wiggle generated code to implement proc_exit, we need + // to hook directly into the runtime there: + function_override: { + proc_exit => wasi_proc_exit + } + }, + }, + // Error to return when caller module is missing memory export: + missing_memory: { wasi_common::wasi::Errno::Inval }, +}); pub fn is_wasi_module(name: &str) -> bool { // FIXME: this should be more conservative, but while WASI is in flux and diff --git a/crates/wiggle/wasmtime/Cargo.toml b/crates/wiggle/wasmtime/Cargo.toml new file mode 100644 index 0000000000..5983576994 --- /dev/null +++ b/crates/wiggle/wasmtime/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "wasmtime-wiggle" +version = "0.18.0" +authors = ["Pat Hickey ", "Jakub Konka ", "Alex Crichton "] +edition = "2018" +license = "Apache-2.0 WITH LLVM-exception" +description = "Integrate Wiggle code generator with Wasmtime" +categories = ["wasm"] +keywords = ["webassembly", "wasm"] +repository = "https://github.com/bytecodealliance/wasmtime" +include = ["src/**/*", "LICENSE"] + +[dependencies] +wasmtime = { path = "../../wasmtime", version = "0.18.0" } +wasmtime-wiggle-macro = { path = "./macro", version = "0.18.0" } +witx = { path = "../../wasi-common/WASI/tools/witx", version = "0.8.5", optional = true } +wiggle = { path = "..", version = "0.18.0" } + +[badges] +maintenance = { status = "actively-developed" } + +[features] +# The wiggle proc-macro emits some code (inside `pub mod metadata`) guarded +# by the `wiggle_metadata` feature flag. We use this feature flag so that +# users of wiggle are not forced to take a direct dependency on the `witx` +# crate unless they want it. +wiggle_metadata = ['witx', "wiggle/wiggle_metadata"] + +# The `tracing` crate can use the `log` ecosystem of backends with this +# non-default feature. We don't need to provide this by default, but its +# useful for users that don't want to use `tracing-subscriber` to get +# the logs out of wiggle-generated libraries. +tracing_log = [ "wiggle/tracing_log" ] + +default = ["wiggle_metadata" ] diff --git a/crates/wiggle/wasmtime/macro/Cargo.toml b/crates/wiggle/wasmtime/macro/Cargo.toml new file mode 100644 index 0000000000..30f46f2d9f --- /dev/null +++ b/crates/wiggle/wasmtime/macro/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "wasmtime-wiggle-macro" +version = "0.18.0" +authors = ["Pat Hickey ", "Jakub Konka ", "Alex Crichton "] +edition = "2018" +license = "Apache-2.0 WITH LLVM-exception" +description = "Macro for integrating Wiggle code generator with Wasmtime" +categories = ["wasm"] +keywords = ["webassembly", "wasm"] +repository = "https://github.com/bytecodealliance/wasmtime" +include = ["src/**/*", "LICENSE"] + +[lib] +proc-macro = true +test = false + +[dependencies] +witx = { path = "../../../wasi-common/WASI/tools/witx", version = "0.8.5" } +wiggle-generate = { path = "../../generate", version = "0.18.0" } +quote = "1.0" +syn = { version = "1.0", features = ["full"] } +proc-macro2 = "1.0" + +[badges] +maintenance = { status = "actively-developed" } + diff --git a/crates/wiggle/wasmtime/macro/src/config.rs b/crates/wiggle/wasmtime/macro/src/config.rs new file mode 100644 index 0000000000..8f2d9bbc0f --- /dev/null +++ b/crates/wiggle/wasmtime/macro/src/config.rs @@ -0,0 +1,319 @@ +use { + proc_macro2::{Span, TokenStream}, + std::collections::HashMap, + syn::{ + braced, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Error, Ident, Path, Result, Token, + }, + wiggle_generate::config::{CtxConf, WitxConf}, +}; + +#[derive(Debug, Clone)] +pub struct Config { + pub target: TargetConf, + pub witx: WitxConf, + pub ctx: CtxConf, + pub modules: ModulesConf, + pub missing_memory: MissingMemoryConf, +} + +#[derive(Debug, Clone)] +pub enum ConfigField { + Target(TargetConf), + Witx(WitxConf), + Ctx(CtxConf), + Modules(ModulesConf), + MissingMemory(MissingMemoryConf), +} + +mod kw { + syn::custom_keyword!(target); + syn::custom_keyword!(witx); + syn::custom_keyword!(witx_literal); + syn::custom_keyword!(ctx); + syn::custom_keyword!(modules); + syn::custom_keyword!(name); + syn::custom_keyword!(docs); + syn::custom_keyword!(missing_memory); + syn::custom_keyword!(function_override); +} + +impl Parse for ConfigField { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::target) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::Target(input.parse()?)) + } else if lookahead.peek(kw::witx) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::Witx(WitxConf::Paths(input.parse()?))) + } else if lookahead.peek(kw::witx_literal) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::Witx(WitxConf::Literal(input.parse()?))) + } else if lookahead.peek(kw::ctx) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::Ctx(input.parse()?)) + } else if lookahead.peek(kw::modules) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::Modules(input.parse()?)) + } else if lookahead.peek(kw::missing_memory) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::MissingMemory(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + +impl Config { + pub fn build(fields: impl Iterator, err_loc: Span) -> Result { + let mut target = None; + let mut witx = None; + let mut ctx = None; + let mut modules = None; + let mut missing_memory = None; + for f in fields { + match f { + ConfigField::Target(c) => { + if target.is_some() { + return Err(Error::new(err_loc, "duplicate `target` field")); + } + target = Some(c); + } + ConfigField::Witx(c) => { + if witx.is_some() { + return Err(Error::new(err_loc, "duplicate `witx` field")); + } + witx = Some(c); + } + ConfigField::Ctx(c) => { + if ctx.is_some() { + return Err(Error::new(err_loc, "duplicate `ctx` field")); + } + ctx = Some(c); + } + ConfigField::Modules(c) => { + if modules.is_some() { + return Err(Error::new(err_loc, "duplicate `modules` field")); + } + modules = Some(c); + } + ConfigField::MissingMemory(c) => { + if missing_memory.is_some() { + return Err(Error::new(err_loc, "duplicate `missing_memory` field")); + } + missing_memory = Some(c); + } + } + } + Ok(Config { + target: target.ok_or_else(|| Error::new(err_loc, "`target` field required"))?, + witx: witx.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?, + ctx: ctx.ok_or_else(|| Error::new(err_loc, "`ctx` field required"))?, + modules: modules.ok_or_else(|| Error::new(err_loc, "`modules` field required"))?, + missing_memory: missing_memory + .ok_or_else(|| Error::new(err_loc, "`missing_memory` field required"))?, + }) + } + + /// Load the `witx` document for the configuration. + /// + /// # Panics + /// + /// This method will panic if the paths given in the `witx` field were not valid documents. + pub fn load_document(&self) -> witx::Document { + self.witx.load_document() + } +} + +impl Parse for Config { + fn parse(input: ParseStream) -> Result { + let contents; + let _lbrace = braced!(contents in input); + let fields: Punctuated = + contents.parse_terminated(ConfigField::parse)?; + Ok(Config::build(fields.into_iter(), input.span())?) + } +} + +#[derive(Debug, Clone)] +pub struct TargetConf { + pub path: Path, +} + +impl Parse for TargetConf { + fn parse(input: ParseStream) -> Result { + Ok(TargetConf { + path: input.parse()?, + }) + } +} + +enum ModuleConfField { + Name(Ident), + Docs(String), + FunctionOverride(FunctionOverrideConf), +} + +impl Parse for ModuleConfField { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::name) { + input.parse::()?; + input.parse::()?; + Ok(ModuleConfField::Name(input.parse()?)) + } else if lookahead.peek(kw::docs) { + input.parse::()?; + input.parse::()?; + let docs: syn::LitStr = input.parse()?; + Ok(ModuleConfField::Docs(docs.value())) + } else if lookahead.peek(kw::function_override) { + input.parse::()?; + input.parse::()?; + Ok(ModuleConfField::FunctionOverride(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + +#[derive(Debug, Clone)] +pub struct ModuleConf { + pub name: Ident, + pub docs: Option, + pub function_override: FunctionOverrideConf, +} + +impl ModuleConf { + fn build(fields: impl Iterator, err_loc: Span) -> Result { + let mut name = None; + let mut docs = None; + let mut function_override = None; + for f in fields { + match f { + ModuleConfField::Name(c) => { + if name.is_some() { + return Err(Error::new(err_loc, "duplicate `name` field")); + } + name = Some(c); + } + ModuleConfField::Docs(c) => { + if docs.is_some() { + return Err(Error::new(err_loc, "duplicate `docs` field")); + } + docs = Some(c); + } + ModuleConfField::FunctionOverride(c) => { + if function_override.is_some() { + return Err(Error::new(err_loc, "duplicate `function_override` field")); + } + function_override = Some(c); + } + } + } + Ok(ModuleConf { + name: name.ok_or_else(|| Error::new(err_loc, "`name` field required"))?, + docs, + function_override: function_override.unwrap_or_default(), + }) + } +} + +impl Parse for ModuleConf { + fn parse(input: ParseStream) -> Result { + let contents; + let _lbrace = braced!(contents in input); + let fields: Punctuated = + contents.parse_terminated(ModuleConfField::parse)?; + Ok(ModuleConf::build(fields.into_iter(), input.span())?) + } +} + +#[derive(Debug, Clone)] +pub struct ModulesConf { + pub mods: HashMap, +} + +impl ModulesConf { + pub fn iter(&self) -> impl Iterator { + self.mods.iter() + } +} + +impl Parse for ModulesConf { + fn parse(input: ParseStream) -> Result { + let contents; + let _lbrace = braced!(contents in input); + let fields: Punctuated<(String, ModuleConf), Token![,]> = + contents.parse_terminated(|i| { + let name = i.parse::()?.to_string(); + i.parse::]>()?; + let val = i.parse()?; + Ok((name, val)) + })?; + Ok(ModulesConf { + mods: fields.into_iter().collect(), + }) + } +} + +#[derive(Debug, Clone)] +pub struct MissingMemoryConf { + pub err: TokenStream, +} +impl Parse for MissingMemoryConf { + fn parse(input: ParseStream) -> Result { + let contents; + let _lbrace = braced!(contents in input); + Ok(MissingMemoryConf { + err: contents.parse()?, + }) + } +} + +#[derive(Debug, Clone, Default)] +pub struct FunctionOverrideConf { + pub funcs: Vec, +} +impl FunctionOverrideConf { + pub fn find(&self, name: &str) -> Option<&Ident> { + self.funcs + .iter() + .find(|f| f.name == name) + .map(|f| &f.replacement) + } +} + +impl Parse for FunctionOverrideConf { + fn parse(input: ParseStream) -> Result { + let contents; + let _lbrace = braced!(contents in input); + let fields: Punctuated = + contents.parse_terminated(FunctionOverrideField::parse)?; + Ok(FunctionOverrideConf { + funcs: fields.into_iter().collect(), + }) + } +} + +#[derive(Debug, Clone)] +pub struct FunctionOverrideField { + pub name: String, + pub replacement: Ident, +} +impl Parse for FunctionOverrideField { + fn parse(input: ParseStream) -> Result { + let name = input.parse::()?.to_string(); + input.parse::]>()?; + let replacement = input.parse::()?; + Ok(FunctionOverrideField { name, replacement }) + } +} diff --git a/crates/wiggle/wasmtime/macro/src/lib.rs b/crates/wiggle/wasmtime/macro/src/lib.rs new file mode 100644 index 0000000000..57f1e635b8 --- /dev/null +++ b/crates/wiggle/wasmtime/macro/src/lib.rs @@ -0,0 +1,231 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::parse_macro_input; +use wiggle_generate::Names; + +mod config; + +use config::{MissingMemoryConf, ModuleConf, TargetConf}; + +/// Define the structs required to integrate a Wiggle implementation with Wasmtime. +/// +/// ## Arguments +/// +/// Arguments are provided using struct syntax e.g. `{ arg_name: value }`. +/// +/// * `target`: The path of the module where the Wiggle implementation is defined. +/// * `witx` or `witx_literal`: the .witx document where the interface is defined. +/// `witx` takes a list of filesystem paths, e.g. `["/path/to/file1.witx", +/// "./path/to_file2.witx"]`. Relative paths are relative to the root of the crate +/// where the macro is invoked. `witx_literal` takes a string of the witx document, e.g. +/// `"(typename $foo u8)"`. +/// * `ctx`: The context struct used for the Wiggle implementation. This must be the same +/// type as the [`wasmtime_wiggle::from_witx`] macro at `target` was invoked with. However, it +/// must be imported to the current scope so that it is a bare identifier e.g. `CtxType`, not +/// `path::to::CtxType`. +/// * `modules`: Describes how any modules in the witx document will be implemented as Wasmtime +/// instances. `modules` takes a map from the witx module name to a configuration struct, e.g. +/// `foo => { name: Foo }, bar => { name: Bar }` will generate integrations for the modules +/// named `foo` and `bar` in the witx document, as `pub struct Foo` and `pub struct Bar` +/// respectively. +/// The module configuration uses struct syntax with the following fields: +/// * `name`: required, gives the name of the struct which encapsulates the instance for +/// Wasmtime. +/// * `docs`: optional, a doc string that will be used for the definition of the struct. +/// * `function_override`: A map of witx function names to Rust function symbols for +/// functions that should not call the Wiggle-generated functions, but instead use +/// a separate implementation. This is typically used for functions that need to interact +/// with Wasmtime in a manner that Wiggle does not permit, e.g. wasi's `proc_exit` function +/// needs to return a Trap directly to the runtime. +/// Example: +/// `modules: { some_module => { name: SomeTypeName, docs: "Doc string for definition of +/// SomeTypeName here", function_override: { foo => my_own_foo } }`. +/// * `missing_memory`: Describes the error value to return in case the calling module does not +/// export a Memory as `"memory"`. This value is given in braces, e.g. `missing_memory: { +/// wasi_common::wasi::Errno::Inval }`. +/// +#[proc_macro] +pub fn wasmtime_integration(args: TokenStream) -> TokenStream { + let mut config = parse_macro_input!(args as config::Config); + config.witx.make_paths_relative_to( + std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env var"), + ); + let doc = config.load_document(); + let names = Names::new(&config.ctx.name, quote!(wasmtime_wiggle)); + + let modules = config.modules.iter().map(|(name, module_conf)| { + let module = doc + .module(&witx::Id::new(name)) + .unwrap_or_else(|| panic!("witx document did not contain module named '{}'", name)); + generate_module( + &module, + &module_conf, + &names, + &config.target, + &config.missing_memory, + ) + }); + quote!( #(#modules)* ).into() +} + +fn generate_module( + module: &witx::Module, + module_conf: &ModuleConf, + names: &Names, + target_conf: &TargetConf, + missing_mem_conf: &MissingMemoryConf, +) -> TokenStream2 { + let fields = module.funcs().map(|f| { + let name_ident = names.func(&f.name); + quote! { pub #name_ident: wasmtime::Func } + }); + let get_exports = module.funcs().map(|f| { + let func_name = f.name.as_str(); + let name_ident = names.func(&f.name); + quote! { #func_name => Some(&self.#name_ident) } + }); + let ctor_fields = module.funcs().map(|f| names.func(&f.name)); + + let module_name = module.name.as_str(); + + let linker_add = module.funcs().map(|f| { + let func_name = f.name.as_str(); + let name_ident = names.func(&f.name); + quote! { + linker.define(#module_name, #func_name, self.#name_ident.clone())?; + } + }); + + let target_path = &target_conf.path; + let module_id = names.module(&module.name); + let target_module = quote! { #target_path::#module_id }; + + let ctor_externs = module.funcs().map(|f| { + if let Some(func_override) = module_conf.function_override.find(&f.name.as_str()) { + let name_ident = names.func(&f.name); + quote! { let #name_ident = wasmtime::Func::wrap(store, #func_override); } + } else { + generate_func(&f, names, missing_mem_conf, &target_module) + } + }); + + let type_name = module_conf.name.clone(); + let type_docs = module_conf + .docs + .as_ref() + .map(|docs| quote!( #[doc = #docs] )) + .unwrap_or_default(); + let constructor_docs = format!( + "Creates a new [`{}`] instance. + +External values are allocated into the `store` provided and +configuration of the wasi instance itself should be all +contained in the `cx` parameter.", + module_conf.name.to_string() + ); + + let ctx_type = names.ctx_type(); + + quote! { + #type_docs + pub struct #type_name { + #(#fields,)* + } + + impl #type_name { + #[doc = #constructor_docs] + pub fn new(store: &wasmtime::Store, cx: #ctx_type) -> Self { + let cx = std::rc::Rc::new(std::cell::RefCell::new(cx)); + #(#ctor_externs)* + + Self { + #(#ctor_fields,)* + } + } + + /// Looks up a field called `name` in this structure, returning it + /// if found. + /// + /// This is often useful when instantiating a `wasmtime` instance + /// where name resolution often happens with strings. + pub fn get_export(&self, name: &str) -> Option<&wasmtime::Func> { + match name { + #(#get_exports,)* + _ => None, + } + } + + /// Adds all instance items to the specified `Linker`. + pub fn add_to_linker(&self, linker: &mut wasmtime::Linker) -> anyhow::Result<()> { + #(#linker_add)* + Ok(()) + } + } + } +} + +fn generate_func( + func: &witx::InterfaceFunc, + names: &Names, + missing_mem_conf: &MissingMemoryConf, + target_module: &TokenStream2, +) -> TokenStream2 { + let missing_mem_err = &missing_mem_conf.err; + let name_ident = names.func(&func.name); + + let coretype = func.core_type(); + + let arg_decls = coretype.args.iter().map(|arg| { + let name = names.func_core_arg(arg); + let atom = names.atom_type(arg.repr()); + quote! { #name: #atom } + }); + let arg_names = coretype.args.iter().map(|arg| names.func_core_arg(arg)); + + let (ret_ty, handle_early_error) = if let Some(ret) = &coretype.ret { + let ret_ty = match ret.signifies { + witx::CoreParamSignifies::Value(atom) => names.atom_type(atom), + _ => unreachable!("coretype ret should always be passed by value"), + }; + (quote! { #ret_ty }, quote! { return e.into(); }) + } else { + ( + quote! {()}, + quote! { panic!("unrecoverable error in {}: {}", stringify!(#name_ident), e) }, + ) + }; + + let runtime = names.runtime_mod(); + + quote! { + let my_cx = cx.clone(); + let #name_ident = wasmtime::Func::wrap( + store, + move |caller: wasmtime::Caller<'_> #(,#arg_decls)*| -> #ret_ty { + unsafe { + let mem = match caller.get_export("memory") { + Some(wasmtime::Extern::Memory(m)) => m, + _ => { + log::warn!("callee does not export a memory as \"memory\""); + let e = { #missing_mem_err }; + #handle_early_error + } + }; + // Wiggle does not expose any methods for functions to re-enter the WebAssembly + // instance, or expose the memory via non-wiggle mechanisms. However, the + // user-defined code may end up re-entering the instance, in which case this + // is an incorrect implementation - we require exactly one BorrowChecker exist + // per instance. + let bc = #runtime::BorrowChecker::new(); + let mem = #runtime::WasmtimeGuestMemory::new( mem, bc ); + #target_module::#name_ident( + &mut my_cx.borrow_mut(), + &mem, + #(#arg_names),* + ) + } + } + ); + } +} diff --git a/crates/wiggle/wasmtime/src/lib.rs b/crates/wiggle/wasmtime/src/lib.rs new file mode 100644 index 0000000000..bd4d303ed6 --- /dev/null +++ b/crates/wiggle/wasmtime/src/lib.rs @@ -0,0 +1,24 @@ +pub use wasmtime_wiggle_macro::*; +pub use wiggle::*; + +/// Lightweight `wasmtime::Memory` wrapper so we can implement the +/// `wiggle::GuestMemory` trait on it. +pub struct WasmtimeGuestMemory { + mem: wasmtime::Memory, + bc: BorrowChecker, +} + +impl WasmtimeGuestMemory { + pub fn new(mem: wasmtime::Memory, bc: BorrowChecker) -> Self { + Self { mem, bc } + } +} + +unsafe impl GuestMemory for WasmtimeGuestMemory { + fn base(&self) -> (*mut u8, u32) { + (self.mem.data_ptr(), self.mem.data_size() as _) + } + fn borrow_checker(&self) -> &BorrowChecker { + &self.bc + } +}