Implement RFC 11: Redesigning Wasmtime's APIs (#2897)

Implement Wasmtime's new API as designed by RFC 11. This is quite a large commit which has had lots of discussion externally, so for more information it's best to read the RFC thread and the PR thread.
This commit is contained in:
Alex Crichton
2021-06-03 09:10:53 -05:00
committed by GitHub
parent a5a28b1c5b
commit 7a1b7cdf92
233 changed files with 13349 additions and 11997 deletions

View File

@@ -17,14 +17,33 @@ wiggle-macro = { path = "macro", version = "0.27.0" }
tracing = "0.1.15"
bitflags = "1.2"
async-trait = "0.1.42"
wasmtime = { path = "../wasmtime", version = "0.27.0", optional = true, default-features = false }
anyhow = "1.0"
[badges]
maintenance = { status = "actively-developed" }
[dev-dependencies]
wiggle-test = { path = "test-helpers" }
anyhow = "1"
proptest = "1.0.0"
[[test]]
name = "wasmtime_async"
path = "tests/wasmtime_async.rs"
required-features = ["wasmtime_async", "wasmtime/wat"]
[[test]]
name = "wasmtime_sync"
path = "tests/wasmtime_sync.rs"
required-features = ["wasmtime_integration", "wasmtime/wat"]
[[test]]
name = "wasmtime_integration"
path = "tests/wasmtime_integration.rs"
required-features = ["wasmtime_integration", "wasmtime/wat"]
[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
@@ -38,4 +57,9 @@ wiggle_metadata = ['witx', "wiggle-macro/wiggle_metadata"]
# the logs out of wiggle-generated libraries.
tracing_log = [ "tracing/log" ]
default = ["wiggle_metadata" ]
# Generate adapters for wasmtime, and expose the wasmtime_integration macro.
wasmtime_integration = [ "wasmtime", "wiggle-macro/wasmtime" ]
# Support for async in the wasmtime crates.
wasmtime_async = [ "wasmtime_integration", "wasmtime/async" ]
default = ["wiggle_metadata", "wasmtime_integration" ]

View File

@@ -1,17 +0,0 @@
[package]
name = "wiggle-borrow"
version = "0.27.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 = "A run-time borrow checker for use with Wiggle"
categories = ["wasm"]
keywords = ["webassembly", "wasm"]
repository = "https://github.com/bytecodealliance/wasmtime"
include = ["src/**/*", "LICENSE"]
[dependencies]
wiggle = { path = "..", version = "0.27.0" }
[badges]
maintenance = { status = "actively-developed" }

View File

@@ -6,21 +6,29 @@ use std::collections::HashMap;
use std::rc::Rc;
use witx::{Document, Id, InterfaceFunc, Module, NamedType, TypeRef};
pub use crate::config::Asyncness;
pub struct CodegenSettings {
pub errors: ErrorTransform,
async_: AsyncConf,
pub async_: AsyncConf,
pub wasmtime: bool,
}
impl CodegenSettings {
pub fn new(error_conf: &ErrorConf, async_: &AsyncConf, doc: &Document) -> Result<Self, Error> {
pub fn new(
error_conf: &ErrorConf,
async_: &AsyncConf,
doc: &Document,
wasmtime: bool,
) -> Result<Self, Error> {
let errors = ErrorTransform::new(error_conf, doc)?;
Ok(Self {
errors,
async_: async_.clone(),
wasmtime,
})
}
pub fn is_async(&self, module: &Module, func: &InterfaceFunc) -> bool {
self.async_
.is_async(module.name.as_str(), func.name.as_str())
pub fn get_async(&self, module: &Module, func: &InterfaceFunc) -> Asyncness {
self.async_.get(module.name.as_str(), func.name.as_str())
}
}

View File

@@ -14,6 +14,16 @@ pub struct Config {
pub witx: WitxConf,
pub errors: ErrorConf,
pub async_: AsyncConf,
pub wasmtime: bool,
}
mod kw {
syn::custom_keyword!(witx);
syn::custom_keyword!(witx_literal);
syn::custom_keyword!(block_on);
syn::custom_keyword!(errors);
syn::custom_keyword!(target);
syn::custom_keyword!(wasmtime);
}
#[derive(Debug, Clone)]
@@ -21,12 +31,7 @@ pub enum ConfigField {
Witx(WitxConf),
Error(ErrorConf),
Async(AsyncConf),
}
mod kw {
syn::custom_keyword!(witx);
syn::custom_keyword!(witx_literal);
syn::custom_keyword!(errors);
Wasmtime(bool),
}
impl Parse for ConfigField {
@@ -48,8 +53,20 @@ impl Parse for ConfigField {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: false,
functions: input.parse()?,
}))
} else if lookahead.peek(kw::block_on) {
input.parse::<kw::block_on>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: true,
functions: input.parse()?,
}))
} else if lookahead.peek(kw::wasmtime) {
input.parse::<kw::wasmtime>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Wasmtime(input.parse::<syn::LitBool>()?.value))
} else {
Err(lookahead.error())
}
@@ -61,6 +78,7 @@ impl Config {
let mut witx = None;
let mut errors = None;
let mut async_ = None;
let mut wasmtime = None;
for f in fields {
match f {
ConfigField::Witx(c) => {
@@ -81,6 +99,12 @@ impl Config {
}
async_ = Some(c);
}
ConfigField::Wasmtime(c) => {
if wasmtime.is_some() {
return Err(Error::new(err_loc, "duplicate `wasmtime` field"));
}
wasmtime = Some(c);
}
}
}
Ok(Config {
@@ -89,6 +113,7 @@ impl Config {
.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?,
errors: errors.take().unwrap_or_default(),
async_: async_.take().unwrap_or_default(),
wasmtime: wasmtime.unwrap_or(true),
})
}
@@ -284,9 +309,41 @@ impl Parse for ErrorConfField {
#[derive(Clone, Default, Debug)]
/// Modules and funcs that have async signatures
pub struct AsyncConf {
blocking: bool,
functions: AsyncFunctions,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Asyncness {
/// Wiggle function is synchronous, wasmtime Func is synchronous
Sync,
/// Wiggle function is asynchronous, but wasmtime Func is synchronous
Blocking,
/// Wiggle function and wasmtime Func are asynchronous.
Async,
}
impl Asyncness {
pub fn is_async(&self) -> bool {
match self {
Self::Async => true,
_ => false,
}
}
pub fn is_blocking(&self) -> bool {
match self {
Self::Blocking => true,
_ => false,
}
}
pub fn is_sync(&self) -> bool {
match self {
Self::Sync => true,
_ => false,
}
}
}
#[derive(Clone, Debug)]
pub enum AsyncFunctions {
Some(HashMap<String, Vec<String>>),
@@ -299,15 +356,36 @@ impl Default for AsyncFunctions {
}
impl AsyncConf {
pub fn is_async(&self, module: &str, function: &str) -> bool {
pub fn get(&self, module: &str, function: &str) -> Asyncness {
let a = if self.blocking {
Asyncness::Blocking
} else {
Asyncness::Async
};
match &self.functions {
AsyncFunctions::Some(fs) => fs
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some(),
AsyncFunctions::All => true,
AsyncFunctions::Some(fs) => {
if fs
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some()
{
a
} else {
Asyncness::Sync
}
}
AsyncFunctions::All => a,
}
}
pub fn contains_async(&self, module: &witx::Module) -> bool {
for f in module.funcs() {
if self.get(module.name.as_str(), f.name.as_str()).is_async() {
return true;
}
}
false
}
}
impl Parse for AsyncFunctions {
@@ -378,3 +456,97 @@ impl Parse for AsyncConfField {
}
}
}
#[derive(Clone)]
pub struct WasmtimeConfig {
pub c: Config,
pub target: syn::Path,
}
#[derive(Clone)]
pub enum WasmtimeConfigField {
Core(ConfigField),
Target(syn::Path),
}
impl WasmtimeConfig {
pub fn build(fields: impl Iterator<Item = WasmtimeConfigField>, err_loc: Span) -> Result<Self> {
let mut target = None;
let mut cs = Vec::new();
for f in fields {
match f {
WasmtimeConfigField::Target(c) => {
if target.is_some() {
return Err(Error::new(err_loc, "duplicate `target` field"));
}
target = Some(c);
}
WasmtimeConfigField::Core(c) => cs.push(c),
}
}
let c = Config::build(cs.into_iter(), err_loc)?;
Ok(WasmtimeConfig {
c,
target: target
.take()
.ok_or_else(|| Error::new(err_loc, "`target` field required"))?,
})
}
}
impl Parse for WasmtimeConfig {
fn parse(input: ParseStream) -> Result<Self> {
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<WasmtimeConfigField, Token![,]> =
contents.parse_terminated(WasmtimeConfigField::parse)?;
Ok(WasmtimeConfig::build(fields.into_iter(), input.span())?)
}
}
impl Parse for WasmtimeConfigField {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::target) {
input.parse::<kw::target>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Target(input.parse()?))
// The remainder of this function is the ConfigField impl, wrapped in
// WasmtimeConfigField::Core. This is required to get the correct lookahead error.
} else if lookahead.peek(kw::witx) {
input.parse::<kw::witx>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Witx(
WitxConf::Paths(input.parse()?),
)))
} else if lookahead.peek(kw::witx_literal) {
input.parse::<kw::witx_literal>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Witx(
WitxConf::Literal(input.parse()?),
)))
} else if lookahead.peek(kw::errors) {
input.parse::<kw::errors>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Error(
input.parse()?,
)))
} else if lookahead.peek(Token![async]) {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Async(AsyncConf {
blocking: false,
functions: input.parse()?,
})))
} else if lookahead.peek(kw::block_on) {
input.parse::<kw::block_on>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Async(AsyncConf {
blocking: true,
functions: input.parse()?,
})))
} else {
Err(lookahead.error())
}
}
}

View File

@@ -14,6 +14,26 @@ pub fn define_func(
func: &witx::InterfaceFunc,
settings: &CodegenSettings,
) -> TokenStream {
let (ts, _bounds) = _define_func(names, module, func, settings);
ts
}
pub fn func_bounds(
names: &Names,
module: &witx::Module,
func: &witx::InterfaceFunc,
settings: &CodegenSettings,
) -> Vec<Ident> {
let (_ts, bounds) = _define_func(names, module, func, settings);
bounds
}
fn _define_func(
names: &Names,
module: &witx::Module,
func: &witx::InterfaceFunc,
settings: &CodegenSettings,
) -> (TokenStream, Vec<Ident>) {
let rt = names.runtime_mod();
let ident = names.func(&func.name);
@@ -36,7 +56,7 @@ pub fn define_func(
};
let mut body = TokenStream::new();
let mut required_impls = vec![names.trait_name(&module.name)];
let mut bounds = vec![names.trait_name(&module.name)];
func.call_interface(
&module.name,
&mut Rust {
@@ -49,37 +69,40 @@ pub fn define_func(
module,
funcname: func.name.as_str(),
settings,
required_impls: &mut required_impls,
bounds: &mut bounds,
},
);
let asyncness = if settings.is_async(&module, &func) {
quote!(async)
} else {
let asyncness = if settings.get_async(&module, &func).is_sync() {
quote!()
} else {
quote!(async)
};
let mod_name = &module.name.as_str();
let func_name = &func.name.as_str();
quote! {
#[allow(unreachable_code)] // deals with warnings in noreturn functions
pub #asyncness fn #ident(
ctx: &(impl #(#required_impls)+*),
memory: &dyn #rt::GuestMemory,
#(#abi_params),*
) -> Result<#abi_ret, #rt::Trap> {
use std::convert::TryFrom as _;
(
quote! {
#[allow(unreachable_code)] // deals with warnings in noreturn functions
pub #asyncness fn #ident(
ctx: &mut (impl #(#bounds)+*),
memory: &dyn #rt::GuestMemory,
#(#abi_params),*
) -> Result<#abi_ret, #rt::Trap> {
use std::convert::TryFrom as _;
let _span = #rt::tracing::span!(
#rt::tracing::Level::TRACE,
"wiggle abi",
module = #mod_name,
function = #func_name
);
let _enter = _span.enter();
let _span = #rt::tracing::span!(
#rt::tracing::Level::TRACE,
"wiggle abi",
module = #mod_name,
function = #func_name
);
let _enter = _span.enter();
#body
}
}
#body
}
},
bounds,
)
}
struct Rust<'a> {
@@ -92,13 +115,13 @@ struct Rust<'a> {
module: &'a witx::Module,
funcname: &'a str,
settings: &'a CodegenSettings,
required_impls: &'a mut Vec<Ident>,
bounds: &'a mut Vec<Ident>,
}
impl Rust<'_> {
fn required_impl(&mut self, i: Ident) {
if !self.required_impls.contains(&i) {
self.required_impls.push(i);
fn bound(&mut self, i: Ident) {
if !self.bounds.contains(&i) {
self.bounds.push(i);
}
}
}
@@ -222,13 +245,13 @@ impl witx::Bindgen for Rust<'_> {
let trait_name = self.names.trait_name(&self.module.name);
let ident = self.names.func(&func.name);
if self.settings.is_async(&self.module, &func) {
if self.settings.get_async(&self.module, &func).is_sync() {
self.src.extend(quote! {
let ret = #trait_name::#ident(ctx, #(#args),*).await;
let ret = #trait_name::#ident(ctx, #(#args),*);
})
} else {
self.src.extend(quote! {
let ret = #trait_name::#ident(ctx, #(#args),*);
let ret = #trait_name::#ident(ctx, #(#args),*).await;
})
};
self.src.extend(quote! {
@@ -254,7 +277,7 @@ impl witx::Bindgen for Rust<'_> {
let val = match self.settings.errors.for_name(ty) {
Some(custom) => {
let method = self.names.user_error_conversion_method(&custom);
self.required_impl(quote::format_ident!("UserErrorConversion"));
self.bound(quote::format_ident!("UserErrorConversion"));
quote!(UserErrorConversion::#method(ctx, #val)?)
}
None => val,

View File

@@ -5,6 +5,7 @@ mod lifetimes;
mod module_trait;
mod names;
mod types;
pub mod wasmtime;
use heck::ShoutySnakeCase;
use lifetimes::anon_lifetime;
@@ -12,7 +13,7 @@ use proc_macro2::{Literal, TokenStream};
use quote::quote;
pub use codegen_settings::{CodegenSettings, UserErrorType};
pub use config::Config;
pub use config::{Config, WasmtimeConfig};
pub use funcs::define_func;
pub use module_trait::define_module_trait;
pub use names::Names;
@@ -42,7 +43,7 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
let abi_typename = names.type_ref(&errtype.abi_type(), anon_lifetime());
let user_typename = errtype.typename();
let methodname = names.user_error_conversion_method(&errtype);
quote!(fn #methodname(&self, e: super::#user_typename) -> Result<#abi_typename, #rt::Trap>;)
quote!(fn #methodname(&mut self, e: super::#user_typename) -> Result<#abi_typename, #rt::Trap>;)
});
let user_error_conversion = quote! {
pub trait UserErrorConversion {
@@ -55,12 +56,20 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
.funcs()
.map(|f| define_func(&names, &module, &f, &settings));
let modtrait = define_module_trait(&names, &module, &settings);
let wasmtime = if settings.wasmtime {
crate::wasmtime::link_module(&module, &names, None, &settings)
} else {
quote! {}
};
quote!(
pub mod #modname {
use super::types::*;
pub use super::types::UserErrorConversion;
#(#fs)*
#modtrait
#wasmtime
}
)
});

View File

@@ -75,16 +75,16 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
_ => unimplemented!(),
};
let asyncness = if settings.is_async(&m, &f) {
quote!(async)
} else {
let asyncness = if settings.get_async(&m, &f).is_sync() {
quote!()
} else {
quote!(async)
};
if is_anonymous {
quote!(#asyncness fn #funcname(&self, #(#args),*) -> #result; )
quote!(#asyncness fn #funcname(&mut self, #(#args),*) -> #result; )
} else {
quote!(#asyncness fn #funcname<#lifetime>(&self, #(#args),*) -> #result;)
quote!(#asyncness fn #funcname<#lifetime>(&mut self, #(#args),*) -> #result;)
}
});

View File

@@ -0,0 +1,186 @@
use crate::config::Asyncness;
use crate::funcs::func_bounds;
use crate::{CodegenSettings, Names};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use std::collections::HashSet;
pub fn link_module(
module: &witx::Module,
names: &Names,
target_path: Option<&syn::Path>,
settings: &CodegenSettings,
) -> TokenStream {
let module_ident = names.module(&module.name);
let send_bound = if settings.async_.contains_async(module) {
quote! { + Send, T: Send }
} else {
quote! {}
};
let mut bodies = Vec::new();
let mut bounds = HashSet::new();
for f in module.funcs() {
let asyncness = settings.async_.get(module.name.as_str(), f.name.as_str());
bodies.push(generate_func(&module, &f, names, target_path, asyncness));
let bound = func_bounds(names, module, &f, settings);
for b in bound {
bounds.insert(b);
}
}
let ctx_bound = if let Some(target_path) = target_path {
let bounds = bounds
.into_iter()
.map(|b| quote!(#target_path::#module_ident::#b));
quote!( #(#bounds)+* #send_bound )
} else {
let bounds = bounds.into_iter();
quote!( #(#bounds)+* #send_bound )
};
let func_name = if target_path.is_none() {
format_ident!("add_to_linker")
} else {
format_ident!("add_{}_to_linker", module_ident)
};
let rt = names.runtime_mod();
quote! {
/// Adds all instance items to the specified `Linker`.
pub fn #func_name<T, U>(
linker: &mut #rt::wasmtime_crate::Linker<T>,
get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
) -> #rt::anyhow::Result<()>
where
U: #ctx_bound #send_bound
{
#(#bodies)*
Ok(())
}
}
}
fn generate_func(
module: &witx::Module,
func: &witx::InterfaceFunc,
names: &Names,
target_path: Option<&syn::Path>,
asyncness: Asyncness,
) -> TokenStream {
let rt = names.runtime_mod();
let module_str = module.name.as_str();
let module_ident = names.module(&module.name);
let field_str = func.name.as_str();
let field_ident = names.func(&func.name);
let (params, results) = func.wasm_signature();
let arg_names = (0..params.len())
.map(|i| Ident::new(&format!("arg{}", i), Span::call_site()))
.collect::<Vec<_>>();
let arg_decls = params
.iter()
.enumerate()
.map(|(i, ty)| {
let name = &arg_names[i];
let wasm = names.wasm_type(*ty);
quote! { #name: #wasm }
})
.collect::<Vec<_>>();
let ret_ty = match results.len() {
0 => quote!(()),
1 => names.wasm_type(results[0]),
_ => unimplemented!(),
};
let await_ = if asyncness.is_sync() {
quote!()
} else {
quote!(.await)
};
let abi_func = if let Some(target_path) = target_path {
quote!( #target_path::#module_ident::#field_ident )
} else {
quote!( #field_ident )
};
let body = quote! {
let mem = match caller.get_export("memory") {
Some(#rt::wasmtime_crate::Extern::Memory(m)) => m,
_ => {
return Err(#rt::wasmtime_crate::Trap::new("missing required memory export"));
}
};
// Note the unsafety here. Our goal is to simultaneously borrow the
// memory and custom data from `caller`, and the store it's connected
// to. Rust will not let us do that, however, because we must call two
// separate methods (both of which borrow the whole `caller`) and one of
// our borrows is mutable (the custom data).
//
// This operation, however, is safe because these borrows do not overlap
// and in the process of borrowing them mutability doesn't actually
// touch anything. This is akin to mutably borrowing two indices in an
// array, which is safe so long as the indices are separate.
//
// TODO: depending on how common this is for other users to run into we
// may wish to consider adding a dedicated method for this. For now the
// future of `GuestPtr` may be a bit hazy, so let's just get this
// working from the previous iteration for now.
let (ctx, mem) = unsafe {
let mem = &mut *(mem.data_mut(&mut caller) as *mut [u8]);
(get_cx(caller.data_mut()), #rt::wasmtime::WasmtimeGuestMemory::new(mem))
};
match #abi_func(ctx, &mem #(, #arg_names)*) #await_ {
Ok(r) => Ok(<#ret_ty>::from(r)),
Err(#rt::Trap::String(err)) => Err(#rt::wasmtime_crate::Trap::new(err)),
Err(#rt::Trap::I32Exit(err)) => Err(#rt::wasmtime_crate::Trap::i32_exit(err)),
}
};
match asyncness {
Asyncness::Async => {
let wrapper = format_ident!("func_wrap{}_async", params.len());
quote! {
linker.#wrapper(
#module_str,
#field_str,
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| {
Box::new(async move { #body })
},
)?;
}
}
Asyncness::Blocking => {
quote! {
linker.func_wrap(
#module_str,
#field_str,
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> Result<#ret_ty, #rt::wasmtime_crate::Trap> {
let result = async { #body };
#rt::run_in_dummy_executor(result)
},
)?;
}
}
Asyncness::Sync => {
quote! {
linker.func_wrap(
#module_str,
#field_str,
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> Result<#ret_ty, #rt::wasmtime_crate::Trap> {
#body
},
)?;
}
}
}
}

View File

@@ -31,4 +31,5 @@ proc-macro2 = "1.0"
wiggle = { path = ".." }
[features]
wasmtime = []
wiggle_metadata = []

View File

@@ -17,15 +17,19 @@ use syn::parse_macro_input;
/// * For each `@interface func` defined in a witx module, an abi-level
/// function is generated which takes ABI-level arguments, along with
/// a ref that impls the module trait, and a `GuestMemory` implementation.
/// Users typically won't use these abi-level functions: The `wasmtime-wiggle`
/// and `lucet-wiggle` crates adapt these to work with a particular WebAssembly
/// engine.
/// Users typically won't use these abi-level functions: Either the
/// `wasmtime_integration` macro or the `lucet-wiggle` crates adapt these
/// to work with a particular WebAssembly engine.
///
/// * A public "module trait" is defined (called the module name, in
/// SnakeCase) which has a `&self` method for each function in the
/// module. These methods takes idiomatic Rust types for each argument
/// and return `Result<($return_types),$error_type>`
///
/// * When the `wiggle` crate is built with the `wasmtime_integration`
/// feature, each module contains an `add_to_linker` function to add it to
/// a `wasmtime::Linker`.
///
/// Arguments are provided using Rust struct value syntax.
///
/// * `witx` takes a list of string literal paths. Paths are relative to the
@@ -95,11 +99,11 @@ use syn::parse_macro_input;
/// /// whereas the witx-defined types like `Excuse` and `Errno` come
/// /// from the `pub mod types` emitted by the `wiggle::from_witx!`
/// /// invocation above.
/// fn int_float_args(&self, _int: u32, _floats: &GuestPtr<[f32]>)
/// fn int_float_args(&mut self, _int: u32, _floats: &GuestPtr<[f32]>)
/// -> Result<(), YourRichError> {
/// unimplemented!()
/// }
/// async fn double_int_return_float(&self, int: u32)
/// async fn double_int_return_float(&mut self, int: u32)
/// -> Result<f32, YourRichError> {
/// Ok(int.checked_mul(2).ok_or(YourRichError::Overflow)? as f32)
/// }
@@ -121,7 +125,7 @@ use syn::parse_macro_input;
/// /// to terminate WebAssembly execution by trapping.
///
/// impl types::UserErrorConversion for YourCtxType {
/// fn errno_from_your_rich_error(&self, e: YourRichError)
/// fn errno_from_your_rich_error(&mut self, e: YourRichError)
/// -> Result<types::Errno, wiggle::Trap>
/// {
/// println!("Rich error: {:?}", e);
@@ -144,11 +148,15 @@ pub fn from_witx(args: TokenStream) -> TokenStream {
let doc = config.load_document();
let names = wiggle_generate::Names::new(quote!(wiggle));
let error_transform =
wiggle_generate::CodegenSettings::new(&config.errors, &config.async_, &doc)
.expect("validating codegen settings");
let settings = wiggle_generate::CodegenSettings::new(
&config.errors,
&config.async_,
&doc,
cfg!(feature = "wasmtime") && config.wasmtime,
)
.expect("validating codegen settings");
let code = wiggle_generate::generate(&doc, &names, &error_transform);
let code = wiggle_generate::generate(&doc, &names, &settings);
let metadata = if cfg!(feature = "wiggle_metadata") {
wiggle_generate::generate_metadata(&doc, &names)
} else {
@@ -163,7 +171,35 @@ pub fn async_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
let _ = parse_macro_input!(attr as syn::parse::Nothing);
let item = proc_macro2::TokenStream::from(item);
TokenStream::from(quote! {
#[wiggle::async_trait_crate::async_trait(?Send)]
#[wiggle::async_trait_crate::async_trait]
#item
})
}
#[cfg(feature = "wasmtime")]
/// 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.
#[proc_macro]
pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
let config = parse_macro_input!(args as wiggle_generate::WasmtimeConfig);
let doc = config.c.load_document();
let names = wiggle_generate::Names::new(quote!(wiggle));
let settings = wiggle_generate::CodegenSettings::new(
&config.c.errors,
&config.c.async_,
&doc,
cfg!(feature = "wasmtime"),
)
.expect("validating codegen settings");
let modules = doc.modules().map(|module| {
wiggle_generate::wasmtime::link_module(&module, &names, Some(&config.target), &settings)
});
quote!( #(#modules)* ).into()
}

View File

@@ -1,11 +1,21 @@
use std::cell::RefCell;
use crate::{BorrowHandle, GuestError, Region};
use std::collections::HashMap;
use wiggle::{BorrowHandle, GuestError, Region};
use std::sync::Mutex;
pub struct BorrowChecker {
/// Unfortunately, since the terminology of std::cell and the problem domain of borrow checking
/// overlap, the method calls on this member will be confusing.
bc: RefCell<InnerBorrowChecker>,
///
/// Also, unfortunately, for now this uses a `Mutex`. The reason for that is
/// that this is shared as `&BorrowChecker` in a bunch of `GuestPtr` values.
/// Through this sharing we still want each `GuestPtr` to be `Send` and the
/// "naive" way to make `&T` `Send` with interior mutability is to use a
/// `Mutex`. Fixing this will likely require rethinking `GuestPtr` one way
/// or another. That needs to happen for other reasons as well (for example
/// to allow for wasm calls to happen while `GuestPtr` values are active),
/// so it's hoped that in a later rethinking of `GuestPtr` we can revisit
/// this and remove this `Mutex`.
bc: Mutex<InnerBorrowChecker>,
}
impl BorrowChecker {
@@ -19,7 +29,7 @@ impl BorrowChecker {
/// shared or mutable borrow.
pub fn new() -> Self {
BorrowChecker {
bc: RefCell::new(InnerBorrowChecker::new()),
bc: Mutex::new(InnerBorrowChecker::new()),
}
}
/// Indicates whether any outstanding shared or mutable borrows are known
@@ -27,25 +37,25 @@ impl BorrowChecker {
/// to be safe to recursively call into a WebAssembly module, or to
/// manipulate the WebAssembly memory by any other means.
pub fn has_outstanding_borrows(&self) -> bool {
self.bc.borrow().has_outstanding_borrows()
self.bc.lock().unwrap().has_outstanding_borrows()
}
pub fn shared_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
self.bc.borrow_mut().shared_borrow(r)
self.bc.lock().unwrap().shared_borrow(r)
}
pub fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
self.bc.borrow_mut().mut_borrow(r)
self.bc.lock().unwrap().mut_borrow(r)
}
pub fn shared_unborrow(&self, h: BorrowHandle) {
self.bc.borrow_mut().shared_unborrow(h)
self.bc.lock().unwrap().shared_unborrow(h)
}
pub fn mut_unborrow(&self, h: BorrowHandle) {
self.bc.borrow_mut().mut_unborrow(h)
self.bc.lock().unwrap().mut_unborrow(h)
}
pub fn is_shared_borrowed(&self, r: Region) -> bool {
self.bc.borrow().is_shared_borrowed(r)
self.bc.lock().unwrap().is_shared_borrowed(r)
}
pub fn is_mut_borrowed(&self, r: Region) -> bool {
self.bc.borrow().is_mut_borrowed(r)
self.bc.lock().unwrap().is_mut_borrowed(r)
}
}

View File

@@ -1,18 +1,21 @@
use std::cell::Cell;
use std::fmt;
use std::marker;
use std::rc::Rc;
use std::slice;
use std::str;
use std::sync::Arc;
pub use wiggle_macro::{async_trait, from_witx};
#[cfg(feature = "wasmtime")]
pub use anyhow;
#[cfg(feature = "wasmtime")]
pub use wiggle_macro::wasmtime_integration;
pub use bitflags;
#[cfg(feature = "wiggle_metadata")]
pub use witx;
pub mod borrow;
mod error;
mod guest_type;
mod region;
@@ -27,6 +30,14 @@ pub mod async_trait_crate {
pub use async_trait::*;
}
#[cfg(feature = "wasmtime")]
pub mod wasmtime;
#[cfg(feature = "wasmtime")]
pub mod wasmtime_crate {
pub use wasmtime::*;
}
/// A trait which abstracts how to get at the region of host memory taht
/// contains guest memory.
///
@@ -79,7 +90,7 @@ pub mod async_trait_crate {
/// the contents of a WebAssembly memory, all `GuestSlice`s and `GuestStr`s
/// for the memory must be dropped, at which point
/// `GuestMemory::has_outstanding_borrows()` will return `false`.
pub unsafe trait GuestMemory {
pub unsafe trait GuestMemory: Send + Sync {
/// Returns the base allocation of this guest memory, located in host
/// memory.
///
@@ -275,33 +286,6 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Box<T> {
}
}
unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Rc<T> {
fn base(&self) -> (*mut u8, u32) {
T::base(self)
}
fn has_outstanding_borrows(&self) -> bool {
T::has_outstanding_borrows(self)
}
fn is_mut_borrowed(&self, r: Region) -> bool {
T::is_mut_borrowed(self, r)
}
fn is_shared_borrowed(&self, r: Region) -> bool {
T::is_shared_borrowed(self, r)
}
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
T::mut_borrow(self, r)
}
fn shared_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
T::shared_borrow(self, r)
}
fn mut_unborrow(&self, h: BorrowHandle) {
T::mut_unborrow(self, h)
}
fn shared_unborrow(&self, h: BorrowHandle) {
T::shared_unborrow(self, h)
}
}
unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Arc<T> {
fn base(&self) -> (*mut u8, u32) {
T::base(self)
@@ -382,7 +366,6 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Arc<T> {
pub struct GuestPtr<'a, T: ?Sized + Pointee> {
mem: &'a (dyn GuestMemory + 'a),
pointer: T::Pointer,
_marker: marker::PhantomData<&'a Cell<T>>,
}
impl<'a, T: ?Sized + Pointee> GuestPtr<'a, T> {
@@ -392,11 +375,7 @@ impl<'a, T: ?Sized + Pointee> GuestPtr<'a, T> {
/// value is a `u32` offset into guest memory. For slices and strings,
/// `pointer` is a `(u32, u32)` offset/length pair.
pub fn new(mem: &'a (dyn GuestMemory + 'a), pointer: T::Pointer) -> GuestPtr<'a, T> {
GuestPtr {
mem,
pointer,
_marker: marker::PhantomData,
}
GuestPtr { mem, pointer }
}
/// Returns the offset of this pointer in guest memory.
@@ -630,7 +609,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
/// of this guest pointer is not equal to the length of the slice provided.
pub fn copy_from_slice(&self, slice: &[T]) -> Result<(), GuestError>
where
T: GuestTypeTransparent<'a> + Copy,
T: GuestTypeTransparent<'a> + Copy + 'a,
{
// bounds check ...
let mut self_slice = self.as_slice_mut()?;

View File

@@ -1,17 +1,15 @@
pub use wasmtime_wiggle_macro::*;
pub use wiggle::*;
use wiggle_borrow::BorrowChecker;
use crate::borrow::BorrowChecker;
use crate::{BorrowHandle, GuestError, GuestMemory, Region};
/// Lightweight `wasmtime::Memory` wrapper so we can implement the
/// `wiggle::GuestMemory` trait on it.
pub struct WasmtimeGuestMemory {
mem: wasmtime::Memory,
pub struct WasmtimeGuestMemory<'a> {
mem: &'a mut [u8],
bc: BorrowChecker,
}
impl WasmtimeGuestMemory {
pub fn new(mem: wasmtime::Memory) -> Self {
impl<'a> WasmtimeGuestMemory<'a> {
pub fn new(mem: &'a mut [u8]) -> Self {
Self {
mem,
// Wiggle does not expose any methods for functions to re-enter
@@ -28,9 +26,9 @@ impl WasmtimeGuestMemory {
}
}
unsafe impl GuestMemory for WasmtimeGuestMemory {
unsafe impl GuestMemory for WasmtimeGuestMemory<'_> {
fn base(&self) -> (*mut u8, u32) {
(self.mem.data_ptr(), self.mem.data_size() as _)
(self.mem.as_ptr() as *mut u8, self.mem.len() as u32)
}
fn has_outstanding_borrows(&self) -> bool {
self.bc.has_outstanding_borrows()

View File

@@ -14,7 +14,6 @@ publish = false
[dependencies]
proptest = "1.0.0"
wiggle = { path = "..", features = ["tracing_log"] }
wiggle-borrow = { path = "../borrow" }
[dev-dependencies]
thiserror = "1.0"

View File

@@ -32,7 +32,7 @@ impl_errno!(types::Errno);
/// When the `errors` mapping in witx is non-empty, we need to impl the
/// types::UserErrorConversion trait that wiggle generates from that mapping.
impl<'a> types::UserErrorConversion for WasiCtx<'a> {
fn errno_from_rich_error(&self, e: RichError) -> Result<types::Errno, wiggle::Trap> {
fn errno_from_rich_error(&mut self, e: RichError) -> Result<types::Errno, wiggle::Trap> {
wiggle::tracing::debug!(
rich_error = wiggle::tracing::field::debug(&e),
"error conversion"
@@ -49,7 +49,7 @@ impl<'a> types::UserErrorConversion for WasiCtx<'a> {
}
impl<'a> one_error_conversion::OneErrorConversion for WasiCtx<'a> {
fn foo(&self, strike: u32, _s: &types::S) -> Result<types::T, RichError> {
fn foo(&mut self, strike: u32, _s: &types::S) -> Result<types::T, RichError> {
// We use the argument to this function to exercise all of the
// possible error cases we could hit here
match strike {
@@ -78,12 +78,12 @@ fn main() {
env_logger::init();
}
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Exercise each of the branches in `foo`.
// Start with the success case:
let r0 = one_error_conversion::foo(&ctx, &host_memory, 0, 0, 8);
let r0 = one_error_conversion::foo(&mut ctx, &host_memory, 0, 0, 8);
assert_eq!(
r0,
Ok(types::Errno::Ok as i32),
@@ -92,7 +92,7 @@ fn main() {
assert!(ctx.log.borrow().is_empty(), "No error log for strike=0");
// First error case:
let r1 = one_error_conversion::foo(&ctx, &host_memory, 1, 0, 8);
let r1 = one_error_conversion::foo(&mut ctx, &host_memory, 1, 0, 8);
assert_eq!(
r1,
Ok(types::Errno::PicketLine as i32),
@@ -105,7 +105,7 @@ fn main() {
);
// Second error case:
let r2 = one_error_conversion::foo(&ctx, &host_memory, 2, 0, 8);
let r2 = one_error_conversion::foo(&mut ctx, &host_memory, 2, 0, 8);
assert_eq!(
r2,
Ok(types::Errno::InvalidArg as i32),

View File

@@ -1,9 +1,7 @@
use proptest::prelude::*;
use std::cell::UnsafeCell;
use std::marker;
use wiggle::{BorrowHandle, GuestMemory, Region};
use wiggle_borrow::BorrowChecker;
use wiggle::{borrow::BorrowChecker, BorrowHandle, GuestMemory, Region};
#[derive(Debug, Clone)]
pub struct MemAreas(Vec<MemArea>);
@@ -49,6 +47,9 @@ struct HostBuffer {
cell: UnsafeCell<[u8; 4096]>,
}
unsafe impl Send for HostBuffer {}
unsafe impl Sync for HostBuffer {}
pub struct HostMemory {
buffer: HostBuffer,
bc: BorrowChecker,

View File

@@ -9,11 +9,14 @@ wiggle::from_witx!({
impl_errno!(types::Errno);
impl<'a> atoms::Atoms for WasiCtx<'a> {
fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
fn int_float_args(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("INT FLOAT ARGS: {} {}", an_int, an_float);
Ok(())
}
fn double_int_return_float(&self, an_int: u32) -> Result<types::AliasToFloat, types::Errno> {
fn double_int_return_float(
&mut self,
an_int: u32,
) -> Result<types::AliasToFloat, types::Errno> {
Ok((an_int as f32) * 2.0)
}
}
@@ -28,10 +31,10 @@ struct IntFloatExercise {
impl IntFloatExercise {
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let e = atoms::int_float_args(&ctx, &host_memory, self.an_int as i32, self.an_float);
let e = atoms::int_float_args(&mut ctx, &host_memory, self.an_int as i32, self.an_float);
assert_eq!(e, Ok(types::Errno::Ok as i32), "int_float_args error");
}
@@ -57,11 +60,11 @@ struct DoubleIntExercise {
impl DoubleIntExercise {
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let e = atoms::double_int_return_float(
&ctx,
&mut ctx,
&host_memory,
self.input as i32,
self.return_loc.ptr as i32,

View File

@@ -14,12 +14,12 @@ impl_errno!(types::Errno);
#[wiggle::async_trait]
impl<'a> atoms::Atoms for WasiCtx<'a> {
async fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
async fn int_float_args(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("INT FLOAT ARGS: {} {}", an_int, an_float);
Ok(())
}
async fn double_int_return_float(
&self,
&mut self,
an_int: u32,
) -> Result<types::AliasToFloat, types::Errno> {
Ok((an_int as f32) * 2.0)
@@ -36,11 +36,11 @@ struct IntFloatExercise {
impl IntFloatExercise {
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let e = run(atoms::int_float_args(
&ctx,
&mut ctx,
&host_memory,
self.an_int as i32,
self.an_float,
@@ -70,11 +70,11 @@ struct DoubleIntExercise {
impl DoubleIntExercise {
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let e = run(atoms::double_int_return_float(
&ctx,
&mut ctx,
&host_memory,
self.input as i32,
self.return_loc.ptr as i32,

View File

@@ -31,7 +31,7 @@ mod convert_just_errno {
/// When the `errors` mapping in witx is non-empty, we need to impl the
/// types::UserErrorConversion trait that wiggle generates from that mapping.
impl<'a> types::UserErrorConversion for WasiCtx<'a> {
fn errno_from_rich_error(&self, e: RichError) -> Result<types::Errno, wiggle::Trap> {
fn errno_from_rich_error(&mut self, e: RichError) -> Result<types::Errno, wiggle::Trap> {
// WasiCtx can collect a Vec<String> log so we can test this. We're
// logging the Display impl that `thiserror::Error` provides us.
self.log.borrow_mut().push(e.to_string());
@@ -44,7 +44,7 @@ mod convert_just_errno {
}
impl<'a> one_error_conversion::OneErrorConversion for WasiCtx<'a> {
fn foo(&self, strike: u32) -> Result<(), RichError> {
fn foo(&mut self, strike: u32) -> Result<(), RichError> {
// We use the argument to this function to exercise all of the
// possible error cases we could hit here
match strike {
@@ -57,12 +57,12 @@ mod convert_just_errno {
#[test]
fn one_error_conversion_test() {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Exercise each of the branches in `foo`.
// Start with the success case:
let r0 = one_error_conversion::foo(&ctx, &host_memory, 0);
let r0 = one_error_conversion::foo(&mut ctx, &host_memory, 0);
assert_eq!(
r0,
Ok(types::Errno::Ok as i32),
@@ -71,7 +71,7 @@ mod convert_just_errno {
assert!(ctx.log.borrow().is_empty(), "No error log for strike=0");
// First error case:
let r1 = one_error_conversion::foo(&ctx, &host_memory, 1);
let r1 = one_error_conversion::foo(&mut ctx, &host_memory, 1);
assert_eq!(
r1,
Ok(types::Errno::PicketLine as i32),
@@ -84,7 +84,7 @@ mod convert_just_errno {
);
// Second error case:
let r2 = one_error_conversion::foo(&ctx, &host_memory, 2);
let r2 = one_error_conversion::foo(&mut ctx, &host_memory, 2);
assert_eq!(
r2,
Ok(types::Errno::InvalidArg as i32),
@@ -140,11 +140,11 @@ mod convert_multiple_error_types {
// each member of the `errors` mapping.
// Bodies elided.
impl<'a> types::UserErrorConversion for WasiCtx<'a> {
fn errno_from_rich_error(&self, _e: RichError) -> Result<types::Errno, wiggle::Trap> {
fn errno_from_rich_error(&mut self, _e: RichError) -> Result<types::Errno, wiggle::Trap> {
unimplemented!()
}
fn errno2_from_another_rich_error(
&self,
&mut self,
_e: AnotherRichError,
) -> Result<types::Errno2, wiggle::Trap> {
unimplemented!()
@@ -153,13 +153,13 @@ mod convert_multiple_error_types {
// And here's the witx module trait impl, bodies elided
impl<'a> two_error_conversions::TwoErrorConversions for WasiCtx<'a> {
fn foo(&self, _: u32) -> Result<(), RichError> {
fn foo(&mut self, _: u32) -> Result<(), RichError> {
unimplemented!()
}
fn bar(&self, _: u32) -> Result<(), AnotherRichError> {
fn bar(&mut self, _: u32) -> Result<(), AnotherRichError> {
unimplemented!()
}
fn baz(&self, _: u32) -> wiggle::Trap {
fn baz(&mut self, _: u32) -> wiggle::Trap {
unimplemented!()
}
}

View File

@@ -11,7 +11,7 @@ impl_errno!(types::Errno);
impl<'a> flags::Flags for WasiCtx<'a> {
fn configure_car(
&self,
&mut self,
old_config: types::CarConfig,
other_config_ptr: &GuestPtr<types::CarConfig>,
) -> Result<types::CarConfig, types::Errno> {
@@ -62,7 +62,7 @@ impl ConfigureCarExercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate input ptr
@@ -72,7 +72,7 @@ impl ConfigureCarExercise {
.expect("deref ptr mut to CarConfig");
let res = flags::configure_car(
&ctx,
&mut ctx,
&host_memory,
self.old_config.bits() as i32,
self.other_config_by_ptr.ptr as i32,

View File

@@ -11,10 +11,10 @@ wiggle::from_witx!({
impl_errno!(types::Errno);
impl<'a> handle_examples::HandleExamples for WasiCtx<'a> {
fn fd_create(&self) -> Result<types::Fd, types::Errno> {
fn fd_create(&mut self) -> Result<types::Fd, types::Errno> {
Ok(types::Fd::from(FD_VAL))
}
fn fd_consume(&self, fd: types::Fd) -> Result<(), types::Errno> {
fn fd_consume(&mut self, fd: types::Fd) -> Result<(), types::Errno> {
println!("FD_CONSUME {}", fd);
if fd == types::Fd::from(FD_VAL) {
Ok(())
@@ -31,10 +31,10 @@ struct HandleExercise {
impl HandleExercise {
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let e = handle_examples::fd_create(&ctx, &host_memory, self.return_loc.ptr as i32);
let e = handle_examples::fd_create(&mut ctx, &host_memory, self.return_loc.ptr as i32);
assert_eq!(e, Ok(types::Errno::Ok as i32), "fd_create error");
@@ -45,11 +45,11 @@ impl HandleExercise {
assert_eq!(h_got, 123, "fd_create return val");
let e = handle_examples::fd_consume(&ctx, &host_memory, h_got as i32);
let e = handle_examples::fd_consume(&mut ctx, &host_memory, h_got as i32);
assert_eq!(e, Ok(types::Errno::Ok as i32), "fd_consume error");
let e = handle_examples::fd_consume(&ctx, &host_memory, h_got as i32 + 1);
let e = handle_examples::fd_consume(&mut ctx, &host_memory, h_got as i32 + 1);
assert_eq!(
e,

View File

@@ -10,7 +10,7 @@ wiggle::from_witx!({
impl_errno!(types::Errno);
impl<'a> ints::Ints for WasiCtx<'a> {
fn cookie_cutter(&self, init_cookie: types::Cookie) -> Result<types::Bool, types::Errno> {
fn cookie_cutter(&mut self, init_cookie: types::Cookie) -> Result<types::Bool, types::Errno> {
let res = if init_cookie == types::COOKIE_START {
types::Bool::True
} else {
@@ -43,11 +43,11 @@ impl CookieCutterExercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let res = ints::cookie_cutter(
&ctx,
&mut ctx,
&host_memory,
self.cookie as i64,
self.return_ptr_loc.ptr as i32,

View File

@@ -33,7 +33,7 @@ mod module_trait_fn_and_arg_test {
});
impl<'a> self_::Self_ for WasiCtx<'a> {
#[allow(unused_variables)]
fn fn_(&self, use_: u32, virtual_: u32) {
fn fn_(&mut self, use_: u32, virtual_: u32) {
unimplemented!();
}
}

View File

@@ -10,7 +10,7 @@ impl_errno!(types::Errno);
impl<'a> lists::Lists for WasiCtx<'a> {
fn reduce_excuses(
&self,
&mut self,
excuses: &types::ConstExcuseArray,
) -> Result<types::Excuse, types::Errno> {
let last = &excuses
@@ -23,7 +23,7 @@ impl<'a> lists::Lists for WasiCtx<'a> {
Ok(last.read().expect("dereferencing ptr should succeed"))
}
fn populate_excuses(&self, excuses: &types::ExcuseArray) -> Result<(), types::Errno> {
fn populate_excuses(&mut self, excuses: &types::ExcuseArray) -> Result<(), types::Errno> {
for excuse in excuses.iter() {
let ptr_to_excuse = excuse
.expect("valid ptr to ptr")
@@ -74,7 +74,7 @@ impl ReduceExcusesExcercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate memory with pointers to generated Excuse values
@@ -97,7 +97,7 @@ impl ReduceExcusesExcercise {
}
let res = lists::reduce_excuses(
&ctx,
&mut ctx,
&host_memory,
self.array_ptr_loc.ptr as i32,
self.excuse_ptr_locs.len() as i32,
@@ -162,7 +162,7 @@ impl PopulateExcusesExcercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate array with valid pointers to Excuse type in memory
@@ -177,7 +177,7 @@ impl PopulateExcusesExcercise {
}
let res = lists::populate_excuses(
&ctx,
&mut ctx,
&host_memory,
self.array_ptr_loc.ptr as i32,
self.elements.len() as i32,
@@ -210,7 +210,7 @@ proptest! {
impl<'a> array_traversal::ArrayTraversal for WasiCtx<'a> {
fn sum_of_element(
&self,
&mut self,
elements: &GuestPtr<[types::PairInts]>,
index: u32,
) -> Result<i32, types::Errno> {
@@ -219,7 +219,7 @@ impl<'a> array_traversal::ArrayTraversal for WasiCtx<'a> {
Ok(pair.first.wrapping_add(pair.second))
}
fn sum_of_elements(
&self,
&mut self,
elements: &GuestPtr<[types::PairInts]>,
start: u32,
end: u32,
@@ -288,7 +288,7 @@ impl SumElementsExercise {
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate array
@@ -301,7 +301,7 @@ impl SumElementsExercise {
}
let res = array_traversal::sum_of_element(
&ctx,
&mut ctx,
&host_memory,
self.element_loc.ptr as i32,
self.elements.len() as i32,
@@ -320,7 +320,7 @@ impl SumElementsExercise {
// Off the end of the array:
let res = array_traversal::sum_of_element(
&ctx,
&mut ctx,
&host_memory,
self.element_loc.ptr as i32,
self.elements.len() as i32,
@@ -334,7 +334,7 @@ impl SumElementsExercise {
);
let res = array_traversal::sum_of_elements(
&ctx,
&mut ctx,
&host_memory,
self.element_loc.ptr as i32,
self.elements.len() as i32,
@@ -373,7 +373,7 @@ impl SumElementsExercise {
// Index an array off the end of the array:
let res = array_traversal::sum_of_elements(
&ctx,
&mut ctx,
&host_memory,
self.element_loc.ptr as i32,
self.elements.len() as i32,

View File

@@ -10,7 +10,7 @@ impl_errno!(types::Errno);
impl<'a> pointers::Pointers for WasiCtx<'a> {
fn pointers_and_enums<'b>(
&self,
&mut self,
input1: types::Excuse,
input2_ptr: &GuestPtr<'b, types::Excuse>,
input3_ptr: &GuestPtr<'b, types::Excuse>,
@@ -125,7 +125,7 @@ impl PointersAndEnumsExercise {
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
@@ -149,7 +149,7 @@ impl PointersAndEnumsExercise {
.expect("input4 ptr ref_mut");
let e = pointers::pointers_and_enums(
&ctx,
&mut ctx,
&host_memory,
self.input1 as i32,
self.input2_loc.ptr as i32,

View File

@@ -9,11 +9,11 @@ wiggle::from_witx!({
impl_errno!(types::Errno);
impl<'a> records::Records for WasiCtx<'a> {
fn sum_of_pair(&self, an_pair: &types::PairInts) -> Result<i64, types::Errno> {
fn sum_of_pair(&mut self, an_pair: &types::PairInts) -> Result<i64, types::Errno> {
Ok(an_pair.first as i64 + an_pair.second as i64)
}
fn sum_of_pair_of_ptrs(&self, an_pair: &types::PairIntPtrs) -> Result<i64, types::Errno> {
fn sum_of_pair_of_ptrs(&mut self, an_pair: &types::PairIntPtrs) -> Result<i64, types::Errno> {
let first = an_pair
.first
.read()
@@ -25,7 +25,7 @@ impl<'a> records::Records for WasiCtx<'a> {
Ok(first as i64 + second as i64)
}
fn sum_of_int_and_ptr(&self, an_pair: &types::PairIntAndPtr) -> Result<i64, types::Errno> {
fn sum_of_int_and_ptr(&mut self, an_pair: &types::PairIntAndPtr) -> Result<i64, types::Errno> {
let first = an_pair
.first
.read()
@@ -34,7 +34,7 @@ impl<'a> records::Records for WasiCtx<'a> {
Ok(first as i64 + second)
}
fn return_pair_ints(&self) -> Result<types::PairInts, types::Errno> {
fn return_pair_ints(&mut self) -> Result<types::PairInts, types::Errno> {
Ok(types::PairInts {
first: 10,
second: 20,
@@ -42,7 +42,7 @@ impl<'a> records::Records for WasiCtx<'a> {
}
fn return_pair_of_ptrs<'b>(
&self,
&mut self,
first: &GuestPtr<'b, i32>,
second: &GuestPtr<'b, i32>,
) -> Result<types::PairIntPtrs<'b>, types::Errno> {
@@ -52,7 +52,10 @@ impl<'a> records::Records for WasiCtx<'a> {
})
}
fn sum_array<'b>(&self, record_of_list: &types::RecordOfList<'b>) -> Result<u16, types::Errno> {
fn sum_array<'b>(
&mut self,
record_of_list: &types::RecordOfList<'b>,
) -> Result<u16, types::Errno> {
// my kingdom for try blocks
fn aux(record_of_list: &types::RecordOfList) -> Result<u16, wiggle::GuestError> {
let mut s = 0;
@@ -99,7 +102,7 @@ impl SumOfPairExercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
@@ -111,7 +114,7 @@ impl SumOfPairExercise {
.write(self.input.second)
.expect("input ref_mut");
let sum_err = records::sum_of_pair(
&ctx,
&mut ctx,
&host_memory,
self.input_loc.ptr as i32,
self.return_loc.ptr as i32,
@@ -187,7 +190,7 @@ impl SumPairPtrsExercise {
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
@@ -209,7 +212,7 @@ impl SumPairPtrsExercise {
.expect("input_struct ref");
let res = records::sum_of_pair_of_ptrs(
&ctx,
&mut ctx,
&host_memory,
self.input_struct_loc.ptr as i32,
self.return_loc.ptr as i32,
@@ -275,7 +278,7 @@ impl SumIntAndPtrExercise {
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
@@ -292,7 +295,7 @@ impl SumIntAndPtrExercise {
.expect("input_struct ref");
let res = records::sum_of_int_and_ptr(
&ctx,
&mut ctx,
&host_memory,
self.input_struct_loc.ptr as i32,
self.return_loc.ptr as i32,
@@ -332,10 +335,10 @@ impl ReturnPairInts {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let err = records::return_pair_ints(&ctx, &host_memory, self.return_loc.ptr as i32);
let err = records::return_pair_ints(&mut ctx, &host_memory, self.return_loc.ptr as i32);
assert_eq!(err, Ok(types::Errno::Ok as i32), "return struct errno");
@@ -397,7 +400,7 @@ impl ReturnPairPtrsExercise {
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
@@ -410,7 +413,7 @@ impl ReturnPairPtrsExercise {
.expect("input_second ref");
let res = records::return_pair_of_ptrs(
&ctx,
&mut ctx,
&host_memory,
self.input_first_loc.ptr as i32,
self.input_second_loc.ptr as i32,
@@ -498,7 +501,7 @@ impl SumArrayExercise {
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Write inputs to memory as an array
@@ -522,7 +525,7 @@ impl SumArrayExercise {
// Call wiggle-generated func
let res = records::sum_array(
&ctx,
&mut ctx,
&host_memory,
self.input_struct_loc.ptr as i32,
self.output_loc.ptr as i32,

View File

@@ -9,14 +9,14 @@ wiggle::from_witx!({
impl_errno!(types::Errno);
impl<'a> strings::Strings for WasiCtx<'a> {
fn hello_string(&self, a_string: &GuestPtr<str>) -> Result<u32, types::Errno> {
fn hello_string(&mut self, a_string: &GuestPtr<str>) -> Result<u32, types::Errno> {
let s = a_string.as_str().expect("should be valid string");
println!("a_string='{}'", &*s);
Ok(s.len() as u32)
}
fn multi_string(
&self,
&mut self,
a: &GuestPtr<str>,
b: &GuestPtr<str>,
c: &GuestPtr<str>,
@@ -69,7 +69,7 @@ impl HelloStringExercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate string in guest's memory
@@ -81,7 +81,7 @@ impl HelloStringExercise {
}
let res = strings::hello_string(
&ctx,
&mut ctx,
&host_memory,
self.string_ptr_loc.ptr as i32,
self.test_word.len() as i32,
@@ -181,7 +181,7 @@ impl MultiStringExercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let write_string = |val: &str, loc: MemArea| {
@@ -198,7 +198,7 @@ impl MultiStringExercise {
write_string(&self.c, self.sc_ptr_loc);
let res = strings::multi_string(
&ctx,
&mut ctx,
&host_memory,
self.sa_ptr_loc.ptr as i32,
self.a.len() as i32,
@@ -260,7 +260,7 @@ impl OverlappingStringExercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let write_string = |val: &str, loc: MemArea| {
@@ -276,7 +276,7 @@ impl OverlappingStringExercise {
let a_len = self.a.as_bytes().len() as i32;
let res = strings::multi_string(
&ctx,
&mut ctx,
&host_memory,
self.sa_ptr_loc.ptr as i32,
a_len,

View File

@@ -31,7 +31,7 @@ fn mult_zero_nan(a: f32, b: u32) -> f32 {
}
impl<'a> variant_example::VariantExample for WasiCtx<'a> {
fn get_tag(&self, u: &types::Reason) -> Result<types::Excuse, types::Errno> {
fn get_tag(&mut self, u: &types::Reason) -> Result<types::Excuse, types::Errno> {
println!("GET TAG: {:?}", u);
match u {
types::Reason::DogAte { .. } => Ok(types::Excuse::DogAte),
@@ -39,7 +39,11 @@ impl<'a> variant_example::VariantExample for WasiCtx<'a> {
types::Reason::Sleeping { .. } => Ok(types::Excuse::Sleeping),
}
}
fn reason_mult(&self, u: &types::ReasonMut<'_>, multiply_by: u32) -> Result<(), types::Errno> {
fn reason_mult(
&mut self,
u: &types::ReasonMut<'_>,
multiply_by: u32,
) -> Result<(), types::Errno> {
match u {
types::ReasonMut::DogAte(fptr) => {
let val = fptr.read().expect("valid pointer");
@@ -104,7 +108,7 @@ impl GetTagExercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let discriminant = reason_tag(&self.input) as u8;
@@ -126,7 +130,7 @@ impl GetTagExercise {
types::Reason::Sleeping => {} // Do nothing
}
let e = variant_example::get_tag(
&ctx,
&mut ctx,
&host_memory,
self.input_loc.ptr as i32,
self.return_loc.ptr as i32,
@@ -181,7 +185,7 @@ impl ReasonMultExercise {
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let discriminant = reason_tag(&self.input) as u8;
@@ -210,7 +214,7 @@ impl ReasonMultExercise {
types::Reason::Sleeping => {} // Do nothing
}
let e = variant_example::reason_mult(
&ctx,
&mut ctx,
&host_memory,
self.input_loc.ptr as i32,
self.multiply_by as i32,

View File

@@ -32,32 +32,32 @@ impl GuestErrorType for types::Errno {
}
impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
fn args_get(&self, _argv: &GuestPtr<GuestPtr<u8>>, _argv_buf: &GuestPtr<u8>) -> Result<()> {
fn args_get(&mut self, _argv: &GuestPtr<GuestPtr<u8>>, _argv_buf: &GuestPtr<u8>) -> Result<()> {
unimplemented!("args_get")
}
fn args_sizes_get(&self) -> Result<(types::Size, types::Size)> {
fn args_sizes_get(&mut self) -> Result<(types::Size, types::Size)> {
unimplemented!("args_sizes_get")
}
fn environ_get(
&self,
&mut self,
_environ: &GuestPtr<GuestPtr<u8>>,
_environ_buf: &GuestPtr<u8>,
) -> Result<()> {
unimplemented!("environ_get")
}
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size)> {
fn environ_sizes_get(&mut self) -> Result<(types::Size, types::Size)> {
unimplemented!("environ_sizes_get")
}
fn clock_res_get(&self, _id: types::Clockid) -> Result<types::Timestamp> {
fn clock_res_get(&mut self, _id: types::Clockid) -> Result<types::Timestamp> {
unimplemented!("clock_res_get")
}
fn clock_time_get(
&self,
&mut self,
_id: types::Clockid,
_precision: types::Timestamp,
) -> Result<types::Timestamp> {
@@ -65,7 +65,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn fd_advise(
&self,
&mut self,
_fd: types::Fd,
_offset: types::Filesize,
_len: types::Filesize,
@@ -75,7 +75,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn fd_allocate(
&self,
&mut self,
_fd: types::Fd,
_offset: types::Filesize,
_len: types::Filesize,
@@ -83,24 +83,24 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("fd_allocate")
}
fn fd_close(&self, _fd: types::Fd) -> Result<()> {
fn fd_close(&mut self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_close")
}
fn fd_datasync(&self, _fd: types::Fd) -> Result<()> {
fn fd_datasync(&mut self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_datasync")
}
fn fd_fdstat_get(&self, _fd: types::Fd) -> Result<types::Fdstat> {
fn fd_fdstat_get(&mut self, _fd: types::Fd) -> Result<types::Fdstat> {
unimplemented!("fd_fdstat_get")
}
fn fd_fdstat_set_flags(&self, _fd: types::Fd, _flags: types::Fdflags) -> Result<()> {
fn fd_fdstat_set_flags(&mut self, _fd: types::Fd, _flags: types::Fdflags) -> Result<()> {
unimplemented!("fd_fdstat_set_flags")
}
fn fd_fdstat_set_rights(
&self,
&mut self,
_fd: types::Fd,
_fs_rights_base: types::Rights,
_fs_rights_inherting: types::Rights,
@@ -108,16 +108,16 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("fd_fdstat_set_rights")
}
fn fd_filestat_get(&self, _fd: types::Fd) -> Result<types::Filestat> {
fn fd_filestat_get(&mut self, _fd: types::Fd) -> Result<types::Filestat> {
unimplemented!("fd_filestat_get")
}
fn fd_filestat_set_size(&self, _fd: types::Fd, _size: types::Filesize) -> Result<()> {
fn fd_filestat_set_size(&mut self, _fd: types::Fd, _size: types::Filesize) -> Result<()> {
unimplemented!("fd_filestat_set_size")
}
fn fd_filestat_set_times(
&self,
&mut self,
_fd: types::Fd,
_atim: types::Timestamp,
_mtim: types::Timestamp,
@@ -127,7 +127,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn fd_pread(
&self,
&mut self,
_fd: types::Fd,
iovs: &types::IovecArray<'_>,
_offset: types::Filesize,
@@ -158,12 +158,12 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("fd_pread")
}
fn fd_prestat_get(&self, _fd: types::Fd) -> Result<types::Prestat> {
fn fd_prestat_get(&mut self, _fd: types::Fd) -> Result<types::Prestat> {
unimplemented!("fd_prestat_get")
}
fn fd_prestat_dir_name(
&self,
&mut self,
_fd: types::Fd,
_path: &GuestPtr<u8>,
_path_len: types::Size,
@@ -172,7 +172,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn fd_pwrite(
&self,
&mut self,
_fd: types::Fd,
_ciovs: &types::CiovecArray<'_>,
_offset: types::Filesize,
@@ -180,12 +180,12 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("fd_pwrite")
}
fn fd_read(&self, _fd: types::Fd, _iovs: &types::IovecArray<'_>) -> Result<types::Size> {
fn fd_read(&mut self, _fd: types::Fd, _iovs: &types::IovecArray<'_>) -> Result<types::Size> {
unimplemented!("fd_read")
}
fn fd_readdir(
&self,
&mut self,
_fd: types::Fd,
_buf: &GuestPtr<u8>,
_buf_len: types::Size,
@@ -194,12 +194,12 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("fd_readdir")
}
fn fd_renumber(&self, _fd: types::Fd, _to: types::Fd) -> Result<()> {
fn fd_renumber(&mut self, _fd: types::Fd, _to: types::Fd) -> Result<()> {
unimplemented!("fd_renumber")
}
fn fd_seek(
&self,
&mut self,
_fd: types::Fd,
_offset: types::Filedelta,
_whence: types::Whence,
@@ -207,24 +207,24 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("fd_seek")
}
fn fd_sync(&self, _fd: types::Fd) -> Result<()> {
fn fd_sync(&mut self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_sync")
}
fn fd_tell(&self, _fd: types::Fd) -> Result<types::Filesize> {
fn fd_tell(&mut self, _fd: types::Fd) -> Result<types::Filesize> {
unimplemented!("fd_tell")
}
fn fd_write(&self, _fd: types::Fd, _ciovs: &types::CiovecArray<'_>) -> Result<types::Size> {
fn fd_write(&mut self, _fd: types::Fd, _ciovs: &types::CiovecArray<'_>) -> Result<types::Size> {
unimplemented!("fd_write")
}
fn path_create_directory(&self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
fn path_create_directory(&mut self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_create_directory")
}
fn path_filestat_get(
&self,
&mut self,
_fd: types::Fd,
_flags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
@@ -233,7 +233,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn path_filestat_set_times(
&self,
&mut self,
_fd: types::Fd,
_flags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
@@ -245,7 +245,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn path_link(
&self,
&mut self,
_old_fd: types::Fd,
_old_flags: types::Lookupflags,
_old_path: &GuestPtr<'_, str>,
@@ -256,7 +256,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn path_open(
&self,
&mut self,
_fd: types::Fd,
_dirflags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
@@ -269,7 +269,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn path_readlink(
&self,
&mut self,
_fd: types::Fd,
_path: &GuestPtr<'_, str>,
_buf: &GuestPtr<u8>,
@@ -278,12 +278,12 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("path_readlink")
}
fn path_remove_directory(&self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
fn path_remove_directory(&mut self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_remove_directory")
}
fn path_rename(
&self,
&mut self,
_fd: types::Fd,
_old_path: &GuestPtr<'_, str>,
_new_fd: types::Fd,
@@ -293,7 +293,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn path_symlink(
&self,
&mut self,
_old_path: &GuestPtr<'_, str>,
_fd: types::Fd,
_new_path: &GuestPtr<'_, str>,
@@ -301,12 +301,12 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("path_symlink")
}
fn path_unlink_file(&self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
fn path_unlink_file(&mut self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_unlink_file")
}
fn poll_oneoff(
&self,
&mut self,
_in_: &GuestPtr<types::Subscription>,
_out: &GuestPtr<types::Event>,
_nsubscriptions: types::Size,
@@ -314,24 +314,24 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("poll_oneoff")
}
fn proc_exit(&self, _rval: types::Exitcode) -> wiggle::Trap {
fn proc_exit(&mut self, _rval: types::Exitcode) -> wiggle::Trap {
unimplemented!("proc_exit")
}
fn proc_raise(&self, _sig: types::Signal) -> Result<()> {
fn proc_raise(&mut self, _sig: types::Signal) -> Result<()> {
unimplemented!("proc_raise")
}
fn sched_yield(&self) -> Result<()> {
fn sched_yield(&mut self) -> Result<()> {
unimplemented!("sched_yield")
}
fn random_get(&self, _buf: &GuestPtr<u8>, _buf_len: types::Size) -> Result<()> {
fn random_get(&mut self, _buf: &GuestPtr<u8>, _buf_len: types::Size) -> Result<()> {
unimplemented!("random_get")
}
fn sock_recv(
&self,
&mut self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_flags: types::Riflags,
@@ -340,7 +340,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
}
fn sock_send(
&self,
&mut self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_flags: types::Siflags,
@@ -348,7 +348,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
unimplemented!("sock_send")
}
fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> {
fn sock_shutdown(&mut self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> {
unimplemented!("sock_shutdown")
}
}

View File

@@ -1,26 +1,15 @@
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use wasmtime::{Config, Engine, Linker, Module, Store};
wasmtime_wiggle::from_witx!({
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
async: {
atoms::{double_int_return_float}
}
});
wasmtime_wiggle::wasmtime_integration!({
target: crate,
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
ctx: Ctx,
modules: { atoms => { name: Atoms } },
async: {
atoms::double_int_return_float
}
});
pub struct Ctx;
impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
@@ -28,28 +17,32 @@ impl wiggle::GuestErrorType for types::Errno {
}
}
#[wasmtime_wiggle::async_trait]
#[wiggle::async_trait]
impl atoms::Atoms for Ctx {
fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
fn int_float_args(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("INT FLOAT ARGS: {} {}", an_int, an_float);
Ok(())
}
async fn double_int_return_float(
&self,
&mut self,
an_int: u32,
) -> Result<types::AliasToFloat, types::Errno> {
Ok((an_int as f32) * 2.0)
}
}
fn run_sync_func(linker: &wasmtime::Linker) {
let shim_mod = shim_module(linker.store());
let shim_inst = run(linker.instantiate_async(&shim_mod)).unwrap();
#[test]
fn test_sync_host_func() {
let mut store = async_store();
let mut linker = Linker::new(store.engine());
atoms::add_to_linker(&mut linker, |cx| cx).unwrap();
let shim_mod = shim_module(linker.engine());
let shim_inst = run(linker.instantiate_async(&mut store, &shim_mod)).unwrap();
let results = run(shim_inst
.get_func("int_float_args_shim")
.get_func(&mut store, "int_float_args_shim")
.unwrap()
.call_async(&[0i32.into(), 123.45f32.into()]))
.call_async(&mut store, &[0i32.into(), 123.45f32.into()]))
.unwrap();
assert_eq!(results.len(), 1, "one return value");
@@ -60,17 +53,22 @@ fn run_sync_func(linker: &wasmtime::Linker) {
);
}
fn run_async_func(linker: &wasmtime::Linker) {
let shim_mod = shim_module(linker.store());
let shim_inst = run(linker.instantiate_async(&shim_mod)).unwrap();
#[test]
fn test_async_host_func() {
let mut store = async_store();
let mut linker = Linker::new(store.engine());
atoms::add_to_linker(&mut linker, |cx| cx).unwrap();
let shim_mod = shim_module(linker.engine());
let shim_inst = run(linker.instantiate_async(&mut store, &shim_mod)).unwrap();
let input: i32 = 123;
let result_location: i32 = 0;
let results = run(shim_inst
.get_func("double_int_return_float_shim")
.get_func(&mut store, "double_int_return_float_shim")
.unwrap()
.call_async(&[input.into(), result_location.into()]))
.call_async(&mut store, &[input.into(), result_location.into()]))
.unwrap();
assert_eq!(results.len(), 1, "one return value");
@@ -81,70 +79,14 @@ fn run_async_func(linker: &wasmtime::Linker) {
);
// The actual result is in memory:
let mem = shim_inst.get_memory("memory").unwrap();
let mem = shim_inst.get_memory(&mut store, "memory").unwrap();
let mut result_bytes: [u8; 4] = [0, 0, 0, 0];
mem.read(result_location as usize, &mut result_bytes)
mem.read(&store, result_location as usize, &mut result_bytes)
.unwrap();
let result = f32::from_le_bytes(result_bytes);
assert_eq!((input * 2) as f32, result);
}
#[test]
fn test_sync_host_func() {
let store = async_store();
let ctx = Rc::new(RefCell::new(Ctx));
let atoms = Atoms::new(&store, ctx.clone());
let mut linker = wasmtime::Linker::new(&store);
atoms.add_to_linker(&mut linker).unwrap();
run_sync_func(&linker);
}
#[test]
fn test_async_host_func() {
let store = async_store();
let ctx = Rc::new(RefCell::new(Ctx));
let atoms = Atoms::new(&store, ctx.clone());
let mut linker = wasmtime::Linker::new(&store);
atoms.add_to_linker(&mut linker).unwrap();
run_async_func(&linker);
}
#[test]
fn test_sync_config_host_func() {
let mut config = wasmtime::Config::new();
config.async_support(true);
Atoms::add_to_config(&mut config);
let engine = wasmtime::Engine::new(&config).unwrap();
let store = wasmtime::Store::new(&engine);
assert!(Atoms::set_context(&store, Ctx).is_ok());
let linker = wasmtime::Linker::new(&store);
run_sync_func(&linker);
}
#[test]
fn test_async_config_host_func() {
let mut config = wasmtime::Config::new();
config.async_support(true);
Atoms::add_to_config(&mut config);
let engine = wasmtime::Engine::new(&config).unwrap();
let store = wasmtime::Store::new(&engine);
assert!(Atoms::set_context(&store, Ctx).is_ok());
let linker = wasmtime::Linker::new(&store);
run_async_func(&linker);
}
fn run<F: Future>(future: F) -> F::Output {
let mut f = Pin::from(Box::new(future));
let waker = dummy_waker();
@@ -179,18 +121,19 @@ fn dummy_waker() -> Waker {
}
}
fn async_store() -> wasmtime::Store {
wasmtime::Store::new(
&wasmtime::Engine::new(wasmtime::Config::new().async_support(true)).unwrap(),
fn async_store() -> Store<Ctx> {
Store::new(
&Engine::new(Config::new().async_support(true)).unwrap(),
Ctx,
)
}
// Wiggle expects the caller to have an exported memory. Wasmtime can only
// provide this if the caller is a WebAssembly module, so we need to write
// a shim module:
fn shim_module(store: &wasmtime::Store) -> wasmtime::Module {
wasmtime::Module::new(
store.engine(),
fn shim_module(engine: &Engine) -> Module {
Module::new(
engine,
r#"
(module
(memory 1)

View File

@@ -0,0 +1,134 @@
use wasmtime::{Engine, Linker, Module, Store};
// from_witx invocation says the func is async. This context doesn't support async!
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
async: {
atoms::{double_int_return_float}
}
});
pub mod integration {
// The integration invocation says the func is blocking, so it will still work.
wiggle::wasmtime_integration!({
target: crate,
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
block_on: {
atoms::{double_int_return_float}
}
});
}
pub struct Ctx;
impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
types::Errno::Ok
}
}
#[wiggle::async_trait]
impl atoms::Atoms for Ctx {
fn int_float_args(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("INT FLOAT ARGS: {} {}", an_int, an_float);
Ok(())
}
async fn double_int_return_float(
&mut self,
an_int: u32,
) -> Result<types::AliasToFloat, types::Errno> {
Ok((an_int as f32) * 2.0)
}
}
#[test]
fn test_sync_host_func() {
let engine = Engine::default();
let mut linker = Linker::new(&engine);
integration::add_atoms_to_linker(&mut linker, |cx| cx).unwrap();
let mut store = store(&engine);
let shim_mod = shim_module(&engine);
let shim_inst = linker.instantiate(&mut store, &shim_mod).unwrap();
let results = shim_inst
.get_func(&mut store, "int_float_args_shim")
.unwrap()
.call(&mut store, &[0i32.into(), 123.45f32.into()])
.unwrap();
assert_eq!(results.len(), 1, "one return value");
assert_eq!(
results[0].unwrap_i32(),
types::Errno::Ok as i32,
"int_float_args errno"
);
}
#[test]
fn test_async_host_func() {
let engine = Engine::default();
let mut linker = Linker::new(&engine);
integration::add_atoms_to_linker(&mut linker, |cx| cx).unwrap();
let mut store = store(&engine);
let shim_mod = shim_module(&engine);
let shim_inst = linker.instantiate(&mut store, &shim_mod).unwrap();
let input: i32 = 123;
let result_location: i32 = 0;
let results = shim_inst
.get_func(&mut store, "double_int_return_float_shim")
.unwrap()
.call(&mut store, &[input.into(), result_location.into()])
.unwrap();
assert_eq!(results.len(), 1, "one return value");
assert_eq!(
results[0].unwrap_i32(),
types::Errno::Ok as i32,
"double_int_return_float errno"
);
// The actual result is in memory:
let mem = shim_inst.get_memory(&mut store, "memory").unwrap();
let mut result_bytes: [u8; 4] = [0, 0, 0, 0];
mem.read(&store, result_location as usize, &mut result_bytes)
.unwrap();
let result = f32::from_le_bytes(result_bytes);
assert_eq!((input * 2) as f32, result);
}
fn store(engine: &Engine) -> Store<Ctx> {
Store::new(engine, Ctx)
}
// Wiggle expects the caller to have an exported memory. Wasmtime can only
// provide this if the caller is a WebAssembly module, so we need to write
// a shim module:
fn shim_module(engine: &Engine) -> Module {
Module::new(
engine,
r#"
(module
(memory 1)
(export "memory" (memory 0))
(import "atoms" "int_float_args" (func $int_float_args (param i32 f32) (result i32)))
(import "atoms" "double_int_return_float" (func $double_int_return_float (param i32 i32) (result i32)))
(func $int_float_args_shim (param i32 f32) (result i32)
local.get 0
local.get 1
call $int_float_args
)
(func $double_int_return_float_shim (param i32 i32) (result i32)
local.get 0
local.get 1
call $double_int_return_float
)
(export "int_float_args_shim" (func $int_float_args_shim))
(export "double_int_return_float_shim" (func $double_int_return_float_shim))
)
"#,
)
.unwrap()
}

View File

@@ -0,0 +1,122 @@
use wasmtime::{Engine, Linker, Module, Store};
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
block_on: {
atoms::double_int_return_float
}
});
pub struct Ctx;
impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
types::Errno::Ok
}
}
#[wiggle::async_trait]
impl atoms::Atoms for Ctx {
fn int_float_args(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("INT FLOAT ARGS: {} {}", an_int, an_float);
Ok(())
}
async fn double_int_return_float(
&mut self,
an_int: u32,
) -> Result<types::AliasToFloat, types::Errno> {
Ok((an_int as f32) * 2.0)
}
}
#[test]
fn test_sync_host_func() {
let engine = Engine::default();
let mut linker = Linker::new(&engine);
atoms::add_to_linker(&mut linker, |cx| cx).unwrap();
let mut store = store(&engine);
let shim_mod = shim_module(&engine);
let shim_inst = linker.instantiate(&mut store, &shim_mod).unwrap();
let results = shim_inst
.get_func(&mut store, "int_float_args_shim")
.unwrap()
.call(&mut store, &[0i32.into(), 123.45f32.into()])
.unwrap();
assert_eq!(results.len(), 1, "one return value");
assert_eq!(
results[0].unwrap_i32(),
types::Errno::Ok as i32,
"int_float_args errno"
);
}
#[test]
fn test_async_host_func() {
let engine = Engine::default();
let mut linker = Linker::new(&engine);
atoms::add_to_linker(&mut linker, |cx| cx).unwrap();
let mut store = store(&engine);
let shim_mod = shim_module(&engine);
let shim_inst = linker.instantiate(&mut store, &shim_mod).unwrap();
let input: i32 = 123;
let result_location: i32 = 0;
let results = shim_inst
.get_func(&mut store, "double_int_return_float_shim")
.unwrap()
.call(&mut store, &[input.into(), result_location.into()])
.unwrap();
assert_eq!(results.len(), 1, "one return value");
assert_eq!(
results[0].unwrap_i32(),
types::Errno::Ok as i32,
"double_int_return_float errno"
);
// The actual result is in memory:
let mem = shim_inst.get_memory(&mut store, "memory").unwrap();
let mut result_bytes: [u8; 4] = [0, 0, 0, 0];
mem.read(&store, result_location as usize, &mut result_bytes)
.unwrap();
let result = f32::from_le_bytes(result_bytes);
assert_eq!((input * 2) as f32, result);
}
fn store(engine: &Engine) -> Store<Ctx> {
Store::new(engine, Ctx)
}
// Wiggle expects the caller to have an exported memory. Wasmtime can only
// provide this if the caller is a WebAssembly module, so we need to write
// a shim module:
fn shim_module(engine: &Engine) -> Module {
Module::new(
engine,
r#"
(module
(memory 1)
(export "memory" (memory 0))
(import "atoms" "int_float_args" (func $int_float_args (param i32 f32) (result i32)))
(import "atoms" "double_int_return_float" (func $double_int_return_float (param i32 i32) (result i32)))
(func $int_float_args_shim (param i32 f32) (result i32)
local.get 0
local.get 1
call $int_float_args
)
(func $double_int_return_float_shim (param i32 i32) (result i32)
local.get 0
local.get 1
call $double_int_return_float
)
(export "int_float_args_shim" (func $int_float_args_shim))
(export "double_int_return_float_shim" (func $double_int_return_float_shim))
)
"#,
)
.unwrap()
}

View File

@@ -1,53 +0,0 @@
[package]
name = "wasmtime-wiggle"
version = "0.27.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.27.0", default-features = false }
wasmtime-wiggle-macro = { path = "./macro", version = "0.27.0" }
witx = { version = "0.9.0", path = "../../wasi-common/WASI/tools/witx", optional = true }
wiggle = { path = "..", version = "0.27.0" }
wiggle-borrow = { path = "../borrow", version = "0.27.0" }
[dev-dependencies]
anyhow = "1"
proptest = "1.0.0"
[[test]]
name = "atoms_async"
path = "tests/atoms_async.rs"
required-features = ["async", "wasmtime/wat"]
[[test]]
name = "atoms_sync"
path = "tests/atoms_sync.rs"
required-features = ["wasmtime/wat"]
[badges]
maintenance = { status = "actively-developed" }
[features]
# Async support for wasmtime
async = [ 'wasmtime/async', 'wasmtime-wiggle-macro/async' ]
# 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", "async"]

View File

@@ -1,29 +0,0 @@
[package]
name = "wasmtime-wiggle-macro"
version = "0.27.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 = { version = "0.9.0", path = "../../../wasi-common/WASI/tools/witx" }
wiggle-generate = { path = "../../generate", version = "0.27.0" }
quote = "1.0"
syn = { version = "1.0", features = ["full", "extra-traits"] }
proc-macro2 = "1.0"
[badges]
maintenance = { status = "actively-developed" }
[features]
async = []
default = []

View File

@@ -1,324 +0,0 @@
use wiggle_generate::config::AsyncFunctions;
use {
proc_macro2::Span,
std::collections::HashMap,
syn::{
braced,
parse::{Parse, ParseStream},
punctuated::Punctuated,
Error, Ident, Path, Result, Token,
},
wiggle_generate::config::WitxConf,
};
#[derive(Debug, Clone)]
pub struct Config {
pub target: TargetConf,
pub witx: WitxConf,
pub ctx: CtxConf,
pub modules: ModulesConf,
pub async_: AsyncConf,
}
#[derive(Debug, Clone)]
pub enum ConfigField {
Target(TargetConf),
Witx(WitxConf),
Ctx(CtxConf),
Modules(ModulesConf),
Async(AsyncConf),
}
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!(function_override);
syn::custom_keyword!(block_on);
}
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(Token![async]) {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: false,
functions: input.parse()?,
}))
} else if lookahead.peek(kw::block_on) {
input.parse::<kw::block_on>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: true,
functions: 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 async_ = 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::Async(c) => {
if async_.is_some() {
return Err(Error::new(err_loc, "duplicate `async` field"));
}
async_ = 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"))?,
async_: async_.unwrap_or_default(),
})
}
/// 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 CtxConf {
pub name: syn::Type,
}
impl Parse for CtxConf {
fn parse(input: ParseStream) -> Result<Self> {
Ok(CtxConf {
name: input.parse()?,
})
}
}
#[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),
}
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 {
Err(lookahead.error())
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleConf {
pub name: Ident,
pub docs: Option<String>,
}
impl ModuleConf {
fn build(fields: impl Iterator<Item = ModuleConfField>, err_loc: Span) -> Result<Self> {
let mut name = None;
let mut docs = 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);
}
}
}
Ok(ModuleConf {
name: name.ok_or_else(|| Error::new(err_loc, "`name` field required"))?,
docs,
})
}
}
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(Clone, Default, Debug)]
/// Modules and funcs that have async signatures
pub struct AsyncConf {
blocking: bool,
functions: AsyncFunctions,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Asyncness {
/// Wiggle function is synchronous, wasmtime Func is synchronous
Sync,
/// Wiggle function is asynchronous, but wasmtime Func is synchronous
Blocking,
/// Wiggle function and wasmtime Func are asynchronous.
Async,
}
impl Asyncness {
pub fn is_sync(&self) -> bool {
match self {
Asyncness::Sync => true,
_ => false,
}
}
}
impl AsyncConf {
pub fn is_async(&self, module: &str, function: &str) -> Asyncness {
let a = if self.blocking {
Asyncness::Blocking
} else {
Asyncness::Async
};
match &self.functions {
AsyncFunctions::Some(fs) => {
if fs
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some()
{
a
} else {
Asyncness::Sync
}
}
AsyncFunctions::All => a,
}
}
}

View File

@@ -1,397 +0,0 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::parse_macro_input;
use wiggle_generate::Names;
mod config;
use config::{AsyncConf, Asyncness, 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 } }`.
///
#[proc_macro]
pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
let config = parse_macro_input!(args as config::Config);
let doc = config.load_document();
let names = Names::new(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.ctx.name,
&config.async_,
)
});
quote!( #(#modules)* ).into()
}
fn generate_module(
module: &witx::Module,
module_conf: &ModuleConf,
names: &Names,
target_conf: &TargetConf,
ctx_type: &syn::Type,
async_conf: &AsyncConf,
) -> 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 mut fns = Vec::new();
let mut ctor_externs = Vec::new();
let mut host_funcs = Vec::new();
for f in module.funcs() {
let asyncness = async_conf.is_async(module.name.as_str(), f.name.as_str());
match asyncness {
Asyncness::Blocking => {}
Asyncness::Async => {
assert!(
cfg!(feature = "async"),
"generating async wasmtime Funcs requires cargo feature \"async\""
);
}
_ => {}
}
generate_func(
&module_id,
&f,
names,
&target_module,
ctx_type,
asyncness,
&mut fns,
&mut ctor_externs,
&mut host_funcs,
);
}
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 instance itself should be all
contained in the `cx` parameter.",
module_conf.name.to_string()
);
let config_adder_definitions = host_funcs.iter().map(|(func_name, body)| {
let adder_func = format_ident!("add_{}_to_config", names.func(&func_name));
let docs = format!(
"Add the host function for `{}` to a config under a given module and field name.",
func_name.as_str()
);
quote! {
#[doc = #docs]
pub fn #adder_func(config: &mut wasmtime::Config, module: &str, field: &str) {
#body
}
}
});
let config_adder_invocations = host_funcs.iter().map(|(func_name, _body)| {
let adder_func = format_ident!("add_{}_to_config", names.func(&func_name));
let module = module.name.as_str();
let field = func_name.as_str();
quote! {
Self::#adder_func(config, #module, #field);
}
});
quote! {
#type_docs
pub struct #type_name {
#(#fields,)*
}
impl #type_name {
#[doc = #constructor_docs]
pub fn new(store: &wasmtime::Store, ctx: std::rc::Rc<std::cell::RefCell<#ctx_type>>) -> Self {
#(#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(())
}
/// Adds the host functions to the given [`wasmtime::Config`].
///
/// Host functions added to the config expect [`set_context`] to be called.
///
/// Host functions will trap if the context is not set in the calling [`wasmtime::Store`].
pub fn add_to_config(config: &mut wasmtime::Config) {
#(#config_adder_invocations)*
}
#(#config_adder_definitions)*
/// Sets the context in the given store.
///
/// Context must be set in the store when using [`add_to_config`] and prior to any
/// host function being called.
///
/// If the context is already set in the store, the given context is returned as an error.
pub fn set_context(store: &wasmtime::Store, ctx: #ctx_type) -> Result<(), #ctx_type> {
store.set(std::rc::Rc::new(std::cell::RefCell::new(ctx))).map_err(|ctx| {
match std::rc::Rc::try_unwrap(ctx) {
Ok(ctx) => ctx.into_inner(),
Err(_) => unreachable!(),
}
})
}
#(#fns)*
}
}
}
fn generate_func(
module_ident: &Ident,
func: &witx::InterfaceFunc,
names: &Names,
target_module: &TokenStream2,
ctx_type: &syn::Type,
asyncness: Asyncness,
fns: &mut Vec<TokenStream2>,
ctors: &mut Vec<TokenStream2>,
host_funcs: &mut Vec<(witx::Id, TokenStream2)>,
) {
let rt = names.runtime_mod();
let name_ident = names.func(&func.name);
let (params, results) = func.wasm_signature();
let arg_names = (0..params.len())
.map(|i| Ident::new(&format!("arg{}", i), Span::call_site()))
.collect::<Vec<_>>();
let arg_decls = params
.iter()
.enumerate()
.map(|(i, ty)| {
let name = &arg_names[i];
let wasm = names.wasm_type(*ty);
quote! { #name: #wasm }
})
.collect::<Vec<_>>();
let ret_ty = match results.len() {
0 => quote!(()),
1 => names.wasm_type(results[0]),
_ => unimplemented!(),
};
let async_ = if asyncness.is_sync() {
quote!()
} else {
quote!(async)
};
let await_ = if asyncness.is_sync() {
quote!()
} else {
quote!(.await)
};
let runtime = names.runtime_mod();
let fn_ident = format_ident!("{}_{}", module_ident, name_ident);
fns.push(quote! {
#async_ fn #fn_ident(caller: &wasmtime::Caller<'_>, ctx: &mut #ctx_type #(, #arg_decls)*) -> Result<#ret_ty, wasmtime::Trap> {
unsafe {
let mem = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
return Err(wasmtime::Trap::new("missing required memory export"));
}
};
let mem = #runtime::WasmtimeGuestMemory::new(mem);
match #target_module::#name_ident(ctx, &mem #(, #arg_names)*) #await_ {
Ok(r) => Ok(r.into()),
Err(wasmtime_wiggle::Trap::String(err)) => Err(wasmtime::Trap::new(err)),
Err(wasmtime_wiggle::Trap::I32Exit(err)) => Err(wasmtime::Trap::i32_exit(err)),
}
}
}
});
match asyncness {
Asyncness::Async => {
let wrapper = format_ident!("wrap{}_async", params.len());
ctors.push(quote! {
let #name_ident = wasmtime::Func::#wrapper(
store,
ctx.clone(),
move |caller: wasmtime::Caller<'_>, my_ctx: &std::rc::Rc<std::cell::RefCell<_>> #(,#arg_decls)*|
-> Box<dyn std::future::Future<Output = Result<#ret_ty, wasmtime::Trap>>> {
Box::new(async move { Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*).await })
}
);
});
}
Asyncness::Blocking => {
// Emit a synchronous function. Self::#fn_ident returns a Future, so we need to
// use a dummy executor to let any synchronous code inside there execute correctly. If
// the future ends up Pending, this func will Trap.
ctors.push(quote! {
let my_ctx = ctx.clone();
let #name_ident = wasmtime::Func::wrap(
store,
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
#rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*))
}
);
});
}
Asyncness::Sync => {
ctors.push(quote! {
let my_ctx = ctx.clone();
let #name_ident = wasmtime::Func::wrap(
store,
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*)
}
);
});
}
}
let host_wrapper = match asyncness {
Asyncness::Async => {
let wrapper = format_ident!("wrap{}_host_func_async", params.len());
quote! {
config.#wrapper(
module,
field,
move |caller #(,#arg_decls)*|
-> Box<dyn std::future::Future<Output = Result<#ret_ty, wasmtime::Trap>>> {
Box::new(async move {
let ctx = caller.store()
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*).await;
result
})
}
);
}
}
Asyncness::Blocking => {
// Emit a synchronous host function. Self::#fn_ident returns a Future, so we need to
// use a dummy executor to let any synchronous code inside there execute correctly. If
// the future ends up Pending, this func will Trap.
quote! {
config.wrap_host_func(
module,
field,
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
let ctx = caller
.store()
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
#rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*))
},
);
}
}
Asyncness::Sync => {
quote! {
config.wrap_host_func(
module,
field,
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
let ctx = caller
.store()
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*)
},
);
}
}
};
host_funcs.push((func.name.clone(), host_wrapper));
}

View File

@@ -1,25 +0,0 @@
(typename $errno
(enum (@witx tag u32)
;;; Success
$ok
;;; Invalid argument
$invalid_arg
;;; I really don't want to
$dont_want_to
;;; I am physically unable to
$physically_unable
;;; Well, that's a picket line alright!
$picket_line))
(typename $alias_to_float f32)
(module $atoms
(@interface func (export "int_float_args")
(param $an_int u32)
(param $an_float f32)
(result $error (expected (error $errno))))
(@interface func (export "double_int_return_float")
(param $an_int u32)
(result $error (expected $alias_to_float (error $errno))))
)

View File

@@ -1,176 +0,0 @@
use std::cell::RefCell;
use std::rc::Rc;
wasmtime_wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
async: {
atoms::{double_int_return_float}
}
});
wasmtime_wiggle::wasmtime_integration!({
target: crate,
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
ctx: Ctx,
modules: { atoms => { name: Atoms } },
block_on: {
atoms::double_int_return_float
}
});
pub struct Ctx;
impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
types::Errno::Ok
}
}
#[wasmtime_wiggle::async_trait]
impl atoms::Atoms for Ctx {
fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("INT FLOAT ARGS: {} {}", an_int, an_float);
Ok(())
}
async fn double_int_return_float(
&self,
an_int: u32,
) -> Result<types::AliasToFloat, types::Errno> {
Ok((an_int as f32) * 2.0)
}
}
fn run_int_float_args(linker: &wasmtime::Linker) {
let shim_mod = shim_module(linker.store());
let shim_inst = linker.instantiate(&shim_mod).unwrap();
let results = shim_inst
.get_func("int_float_args_shim")
.unwrap()
.call(&[0i32.into(), 123.45f32.into()])
.unwrap();
assert_eq!(results.len(), 1, "one return value");
assert_eq!(
results[0].unwrap_i32(),
types::Errno::Ok as i32,
"int_float_args errno"
);
}
fn run_double_int_return_float(linker: &wasmtime::Linker) {
let shim_mod = shim_module(linker.store());
let shim_inst = linker.instantiate(&shim_mod).unwrap();
let input: i32 = 123;
let result_location: i32 = 0;
let results = shim_inst
.get_func("double_int_return_float_shim")
.unwrap()
.call(&[input.into(), result_location.into()])
.unwrap();
assert_eq!(results.len(), 1, "one return value");
assert_eq!(
results[0].unwrap_i32(),
types::Errno::Ok as i32,
"double_int_return_float errno"
);
// The actual result is in memory:
let mem = shim_inst.get_memory("memory").unwrap();
let mut result_bytes: [u8; 4] = [0, 0, 0, 0];
mem.read(result_location as usize, &mut result_bytes)
.unwrap();
let result = f32::from_le_bytes(result_bytes);
assert_eq!((input * 2) as f32, result);
}
#[test]
fn test_sync_host_func() {
let store = store();
let ctx = Rc::new(RefCell::new(Ctx));
let atoms = Atoms::new(&store, ctx.clone());
let mut linker = wasmtime::Linker::new(&store);
atoms.add_to_linker(&mut linker).unwrap();
run_int_float_args(&linker);
}
#[test]
fn test_async_host_func() {
let store = store();
let ctx = Rc::new(RefCell::new(Ctx));
let atoms = Atoms::new(&store, ctx.clone());
let mut linker = wasmtime::Linker::new(&store);
atoms.add_to_linker(&mut linker).unwrap();
run_double_int_return_float(&linker);
}
#[test]
fn test_sync_config_host_func() {
let mut config = wasmtime::Config::new();
Atoms::add_to_config(&mut config);
let engine = wasmtime::Engine::new(&config).unwrap();
let store = wasmtime::Store::new(&engine);
assert!(Atoms::set_context(&store, Ctx).is_ok());
let linker = wasmtime::Linker::new(&store);
run_int_float_args(&linker);
}
#[test]
fn test_async_config_host_func() {
let mut config = wasmtime::Config::new();
Atoms::add_to_config(&mut config);
let engine = wasmtime::Engine::new(&config).unwrap();
let store = wasmtime::Store::new(&engine);
assert!(Atoms::set_context(&store, Ctx).is_ok());
let linker = wasmtime::Linker::new(&store);
run_double_int_return_float(&linker);
}
fn store() -> wasmtime::Store {
wasmtime::Store::new(&wasmtime::Engine::new(&wasmtime::Config::new()).unwrap())
}
// Wiggle expects the caller to have an exported memory. Wasmtime can only
// provide this if the caller is a WebAssembly module, so we need to write
// a shim module:
fn shim_module(store: &wasmtime::Store) -> wasmtime::Module {
wasmtime::Module::new(
store.engine(),
r#"
(module
(memory 1)
(export "memory" (memory 0))
(import "atoms" "int_float_args" (func $int_float_args (param i32 f32) (result i32)))
(import "atoms" "double_int_return_float" (func $double_int_return_float (param i32 i32) (result i32)))
(func $int_float_args_shim (param i32 f32) (result i32)
local.get 0
local.get 1
call $int_float_args
)
(func $double_int_return_float_shim (param i32 i32) (result i32)
local.get 0
local.get 1
call $double_int_return_float
)
(export "int_float_args_shim" (func $int_float_args_shim))
(export "double_int_return_float_shim" (func $double_int_return_float_shim))
)
"#,
)
.unwrap()
}