Merge pull request #2550 from bytecodealliance/pch/wiggle_trapping
wiggle: introduce Trap enum
This commit is contained in:
@@ -5,3 +5,14 @@ invoked as a Rust procedural macro.
|
||||
|
||||
Wiggle is not specialized to any particular WebAssembly runtime. It is usable
|
||||
in at least Wasmtime and Lucet.
|
||||
|
||||
## Learning more
|
||||
|
||||
Read the docs on [docs.rs](https://docs.rs/wiggle/).
|
||||
|
||||
There are child crates for [integrating with Wasmtime](https://github.com/bytecodealliance/wasmtime/tree/main/crates/wiggle/wasmtime) and [Lucet](https://github.com/bytecodealliance/lucet/tree/main/lucet-wiggle).
|
||||
|
||||
The [wasi-common crate](https://github.com/bytecodealliance/wasmtime/tree/main/crates/wasi-common) is implemented using Wiggle and the [wasmtime-wasi
|
||||
crate](https://github.com/bytecodealliance/wasmtime/tree/main/crates/wasi) integrates wasi-common with the Wasmtime engine.
|
||||
|
||||
Andrew Brown wrote a great [blog post](https://bytecodealliance.org/articles/implementing-wasi-nn-in-wasmtime) on using Wiggle with Wasmtime.
|
||||
|
||||
@@ -36,12 +36,6 @@ pub fn define_func(
|
||||
witx::CoreParamSignifies::Value(atom) => names.atom_type(atom),
|
||||
_ => unreachable!("ret should always be passed by value"),
|
||||
}
|
||||
} else if func.noreturn {
|
||||
// Ideally we would return `quote!(!)` here, but, we'd have to change
|
||||
// the error handling logic in all the marshalling code to never return,
|
||||
// and instead provide some other way to bail to the context...
|
||||
// noreturn func
|
||||
unimplemented!("noreturn funcs not supported yet!")
|
||||
} else {
|
||||
quote!(())
|
||||
};
|
||||
@@ -174,25 +168,42 @@ pub fn define_func(
|
||||
let mod_name = &module.name.as_str();
|
||||
let func_name = &func.name.as_str();
|
||||
|
||||
quote!(pub fn #ident(#abi_args) -> Result<#abi_ret, String> {
|
||||
let _span = #rt::tracing::span!(
|
||||
#rt::tracing::Level::TRACE,
|
||||
"wiggle abi",
|
||||
module = #mod_name,
|
||||
function = #func_name
|
||||
);
|
||||
let _enter = _span.enter();
|
||||
if func.noreturn {
|
||||
quote!(pub fn #ident(#abi_args) -> Result<#abi_ret, wiggle::Trap> {
|
||||
let _span = #rt::tracing::span!(
|
||||
#rt::tracing::Level::TRACE,
|
||||
"wiggle abi",
|
||||
module = #mod_name,
|
||||
function = #func_name
|
||||
);
|
||||
let _enter = _span.enter();
|
||||
|
||||
#(#marshal_args)*
|
||||
#(#marshal_rets_pre)*
|
||||
#log_marshalled_args
|
||||
let #trait_bindings = match #trait_name::#ident(ctx, #(#trait_args),*) {
|
||||
Ok(#trait_bindings) => { #trait_rets },
|
||||
Err(e) => { #ret_err },
|
||||
};
|
||||
#(#marshal_rets_post)*
|
||||
#success
|
||||
})
|
||||
#(#marshal_args)*
|
||||
#log_marshalled_args
|
||||
let trap = #trait_name::#ident(ctx, #(#trait_args),*);
|
||||
Err(trap)
|
||||
})
|
||||
} else {
|
||||
quote!(pub fn #ident(#abi_args) -> Result<#abi_ret, wiggle::Trap> {
|
||||
let _span = #rt::tracing::span!(
|
||||
#rt::tracing::Level::TRACE,
|
||||
"wiggle abi",
|
||||
module = #mod_name,
|
||||
function = #func_name
|
||||
);
|
||||
let _enter = _span.enter();
|
||||
|
||||
#(#marshal_args)*
|
||||
#(#marshal_rets_pre)*
|
||||
#log_marshalled_args
|
||||
let #trait_bindings = match #trait_name::#ident(ctx, #(#trait_args),*) {
|
||||
Ok(#trait_bindings) => { #trait_rets },
|
||||
Err(e) => { #ret_err },
|
||||
};
|
||||
#(#marshal_rets_post)*
|
||||
#success
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn marshal_arg(
|
||||
|
||||
@@ -40,7 +40,7 @@ pub fn generate(doc: &witx::Document, names: &Names, errs: &ErrorTransform) -> T
|
||||
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, String>;)
|
||||
quote!(fn #methodname(&self, e: super::#user_typename) -> Result<#abi_typename, wiggle::Trap>;)
|
||||
});
|
||||
let user_error_conversion = quote! {
|
||||
pub trait UserErrorConversion {
|
||||
|
||||
@@ -48,28 +48,34 @@ pub fn define_module_trait(names: &Names, m: &Module, errxform: &ErrorTransform)
|
||||
};
|
||||
quote!(#arg_name: #arg_type)
|
||||
});
|
||||
let rets = f
|
||||
.results
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|ret| names.type_ref(&ret.tref, lifetime.clone()));
|
||||
let err = f
|
||||
.results
|
||||
.get(0)
|
||||
.map(|err_result| {
|
||||
if let Some(custom_err) = errxform.for_abi_error(&err_result.tref) {
|
||||
let tn = custom_err.typename();
|
||||
quote!(super::#tn)
|
||||
} else {
|
||||
names.type_ref(&err_result.tref, lifetime.clone())
|
||||
}
|
||||
})
|
||||
.unwrap_or(quote!(()));
|
||||
|
||||
let result = if !f.noreturn {
|
||||
let rets = f
|
||||
.results
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|ret| names.type_ref(&ret.tref, lifetime.clone()));
|
||||
let err = f
|
||||
.results
|
||||
.get(0)
|
||||
.map(|err_result| {
|
||||
if let Some(custom_err) = errxform.for_abi_error(&err_result.tref) {
|
||||
let tn = custom_err.typename();
|
||||
quote!(super::#tn)
|
||||
} else {
|
||||
names.type_ref(&err_result.tref, lifetime.clone())
|
||||
}
|
||||
})
|
||||
.unwrap_or(quote!(()));
|
||||
quote!( Result<(#(#rets),*), #err> )
|
||||
} else {
|
||||
quote!(wiggle::Trap)
|
||||
};
|
||||
|
||||
if is_anonymous {
|
||||
quote!(fn #funcname(&self, #(#args),*) -> Result<(#(#rets),*), #err>;)
|
||||
quote!(fn #funcname(&self, #(#args),*) -> #result; )
|
||||
} else {
|
||||
quote!(fn #funcname<#lifetime>(&self, #(#args),*) -> Result<(#(#rets),*), #err>;)
|
||||
quote!(fn #funcname<#lifetime>(&self, #(#args),*) -> #result;)
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
|
||||
@@ -928,3 +928,16 @@ impl Pointee for str {
|
||||
<[u8]>::debug(pointer, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A runtime-independent way for Wiggle to terminate WebAssembly execution.
|
||||
/// Functions that are marked `(@witx noreturn)` will always return a Trap.
|
||||
/// Other functions that want to Trap can do so via their `UserErrorConversion`
|
||||
/// trait, which transforms the user's own error type into a `Result<abierror, Trap>`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Trap {
|
||||
/// A Trap which indicates an i32 (posix-style) exit code. Runtimes may have a
|
||||
/// special way of dealing with this for WASI embeddings and otherwise.
|
||||
I32Exit(i32),
|
||||
/// Any other Trap is just an unstructured String, for reporting and debugging.
|
||||
String(String),
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl_errno!(types::Errno, types::GuestErrorConversion);
|
||||
/// 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, String> {
|
||||
fn errno_from_rich_error(&self, e: RichError) -> Result<types::Errno, wiggle::Trap> {
|
||||
wiggle::tracing::debug!(
|
||||
rich_error = wiggle::tracing::field::debug(&e),
|
||||
"error conversion"
|
||||
|
||||
@@ -34,7 +34,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, String> {
|
||||
fn errno_from_rich_error(&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());
|
||||
@@ -115,8 +115,9 @@ mod convert_multiple_error_types {
|
||||
TooMuchCoffee(usize),
|
||||
}
|
||||
|
||||
// Just like the other error, except that we have a second errno type:
|
||||
// trivial function.
|
||||
// Just like the prior test, except that we have a second errno type. This should mean there
|
||||
// are two functions in UserErrorConversion.
|
||||
// Additionally, test that the function "baz" marked noreturn always returns a wiggle::Trap.
|
||||
wiggle::from_witx!({
|
||||
witx_literal: "
|
||||
(typename $errno (enum u8 $ok $invalid_arg $picket_line))
|
||||
@@ -127,7 +128,10 @@ mod convert_multiple_error_types {
|
||||
(result $err $errno))
|
||||
(@interface func (export \"bar\")
|
||||
(param $drink u32)
|
||||
(result $err $errno2)))
|
||||
(result $err $errno2))
|
||||
(@interface func (export \"baz\")
|
||||
(param $drink u32)
|
||||
(@witx noreturn)))
|
||||
",
|
||||
ctx: WasiCtx,
|
||||
errors: { errno => RichError, errno2 => AnotherRichError },
|
||||
@@ -159,13 +163,13 @@ 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, String> {
|
||||
fn errno_from_rich_error(&self, _e: RichError) -> Result<types::Errno, wiggle::Trap> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn errno2_from_another_rich_error(
|
||||
&self,
|
||||
_e: AnotherRichError,
|
||||
) -> Result<types::Errno2, String> {
|
||||
) -> Result<types::Errno2, wiggle::Trap> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -178,5 +182,8 @@ mod convert_multiple_error_types {
|
||||
fn bar(&self, _: u32) -> Result<(), AnotherRichError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn baz(&self, _: u32) -> wiggle::Trap {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +199,9 @@ fn generate_func(
|
||||
#(#arg_names),*
|
||||
);
|
||||
match result {
|
||||
Ok(r) => {return Ok(r.into());},
|
||||
Err(err) => { return Err(wasmtime::Trap::new(err)); },
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user