Merge pull request #1910 from bytecodealliance/pch/separate_wasmtime_wiggle_crate

Factor Wiggle's wasmtime integration into a standalone crate
This commit is contained in:
Pat Hickey
2020-06-25 09:34:34 -07:00
committed by GitHub
11 changed files with 687 additions and 285 deletions

22
Cargo.lock generated
View File

@@ -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"

View File

@@ -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",

View File

@@ -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()

View File

@@ -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(())
}
}
}
}

View File

@@ -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" }

View File

@@ -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

View File

@@ -0,0 +1,35 @@
[package]
name = "wasmtime-wiggle"
version = "0.18.0"
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkonk@jakubkonka.com>", "Alex Crichton <alex@alexcrichton.com>"]
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" ]

View File

@@ -0,0 +1,26 @@
[package]
name = "wasmtime-wiggle-macro"
version = "0.18.0"
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkonk@jakubkonka.com>", "Alex Crichton <alex@alexcrichton.com>"]
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" }

View File

@@ -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<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::target) {
input.parse::<kw::target>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Target(input.parse()?))
} else if lookahead.peek(kw::witx) {
input.parse::<kw::witx>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Witx(WitxConf::Paths(input.parse()?)))
} else if lookahead.peek(kw::witx_literal) {
input.parse::<kw::witx_literal>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Witx(WitxConf::Literal(input.parse()?)))
} else if lookahead.peek(kw::ctx) {
input.parse::<kw::ctx>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Ctx(input.parse()?))
} else if lookahead.peek(kw::modules) {
input.parse::<kw::modules>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Modules(input.parse()?))
} else if lookahead.peek(kw::missing_memory) {
input.parse::<kw::missing_memory>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::MissingMemory(input.parse()?))
} else {
Err(lookahead.error())
}
}
}
impl Config {
pub fn build(fields: impl Iterator<Item = ConfigField>, err_loc: Span) -> Result<Self> {
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<Self> {
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<ConfigField, Token![,]> =
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<Self> {
Ok(TargetConf {
path: input.parse()?,
})
}
}
enum ModuleConfField {
Name(Ident),
Docs(String),
FunctionOverride(FunctionOverrideConf),
}
impl Parse for ModuleConfField {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::name) {
input.parse::<kw::name>()?;
input.parse::<Token![:]>()?;
Ok(ModuleConfField::Name(input.parse()?))
} else if lookahead.peek(kw::docs) {
input.parse::<kw::docs>()?;
input.parse::<Token![:]>()?;
let docs: syn::LitStr = input.parse()?;
Ok(ModuleConfField::Docs(docs.value()))
} else if lookahead.peek(kw::function_override) {
input.parse::<kw::function_override>()?;
input.parse::<Token![:]>()?;
Ok(ModuleConfField::FunctionOverride(input.parse()?))
} else {
Err(lookahead.error())
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleConf {
pub name: Ident,
pub docs: Option<String>,
pub function_override: FunctionOverrideConf,
}
impl ModuleConf {
fn build(fields: impl Iterator<Item = ModuleConfField>, err_loc: Span) -> Result<Self> {
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<Self> {
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<ModuleConfField, Token![,]> =
contents.parse_terminated(ModuleConfField::parse)?;
Ok(ModuleConf::build(fields.into_iter(), input.span())?)
}
}
#[derive(Debug, Clone)]
pub struct ModulesConf {
pub mods: HashMap<String, ModuleConf>,
}
impl ModulesConf {
pub fn iter(&self) -> impl Iterator<Item = (&String, &ModuleConf)> {
self.mods.iter()
}
}
impl Parse for ModulesConf {
fn parse(input: ParseStream) -> Result<Self> {
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<(String, ModuleConf), Token![,]> =
contents.parse_terminated(|i| {
let name = i.parse::<Ident>()?.to_string();
i.parse::<Token![=>]>()?;
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<Self> {
let contents;
let _lbrace = braced!(contents in input);
Ok(MissingMemoryConf {
err: contents.parse()?,
})
}
}
#[derive(Debug, Clone, Default)]
pub struct FunctionOverrideConf {
pub funcs: Vec<FunctionOverrideField>,
}
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<Self> {
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<FunctionOverrideField, Token![,]> =
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<Self> {
let name = input.parse::<Ident>()?.to_string();
input.parse::<Token![=>]>()?;
let replacement = input.parse::<Ident>()?;
Ok(FunctionOverrideField { name, replacement })
}
}

View File

@@ -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),*
)
}
}
);
}
}

View File

@@ -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
}
}