Add 'crates/wiggle/' from commit 'cd484e49932d8dd8f1bd1a002e0717ad8bff07fb'
git-subtree-dir: crates/wiggle git-subtree-mainline:2ead747f48git-subtree-split:cd484e4993
This commit is contained in:
1
crates/wiggle/crates/generate/.gitignore
vendored
Normal file
1
crates/wiggle/crates/generate/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target
|
||||
16
crates/wiggle/crates/generate/Cargo.toml
Normal file
16
crates/wiggle/crates/generate/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "wiggle-generate"
|
||||
version = "0.1.0"
|
||||
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkon@jakubkonka.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
wiggle-runtime = { path = "../runtime" }
|
||||
witx = "0.8.3"
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
heck = "0.3"
|
||||
anyhow = "1"
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
105
crates/wiggle/crates/generate/src/config.rs
Normal file
105
crates/wiggle/crates/generate/src/config.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::{
|
||||
braced, bracketed,
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
Error, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub witx: WitxConf,
|
||||
pub ctx: CtxConf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConfigField {
|
||||
Witx(WitxConf),
|
||||
Ctx(CtxConf),
|
||||
}
|
||||
|
||||
impl ConfigField {
|
||||
pub fn parse_pair(ident: &str, value: ParseStream, err_loc: Span) -> Result<Self> {
|
||||
match ident {
|
||||
"witx" => Ok(ConfigField::Witx(value.parse()?)),
|
||||
"ctx" => Ok(ConfigField::Ctx(value.parse()?)),
|
||||
_ => Err(Error::new(err_loc, "expected `witx` or `ctx`")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ConfigField {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let id: Ident = input.parse()?;
|
||||
let _colon: Token![:] = input.parse()?;
|
||||
Self::parse_pair(id.to_string().as_ref(), input, id.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn build(fields: impl Iterator<Item = ConfigField>, err_loc: Span) -> Result<Self> {
|
||||
let mut witx = None;
|
||||
let mut ctx = None;
|
||||
for f in fields {
|
||||
match f {
|
||||
ConfigField::Witx(c) => {
|
||||
witx = Some(c);
|
||||
}
|
||||
ConfigField::Ctx(c) => {
|
||||
ctx = Some(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Config {
|
||||
witx: witx
|
||||
.take()
|
||||
.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?,
|
||||
ctx: ctx
|
||||
.take()
|
||||
.ok_or_else(|| Error::new(err_loc, "`ctx` field required"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 WitxConf {
|
||||
pub paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Parse for WitxConf {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let content;
|
||||
let _ = bracketed!(content in input);
|
||||
let path_lits: Punctuated<LitStr, Token![,]> = content.parse_terminated(Parse::parse)?;
|
||||
let paths: Vec<PathBuf> = path_lits
|
||||
.iter()
|
||||
.map(|lit| PathBuf::from(lit.value()))
|
||||
.collect();
|
||||
Ok(WitxConf { paths })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CtxConf {
|
||||
pub name: Ident,
|
||||
}
|
||||
|
||||
impl Parse for CtxConf {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(CtxConf {
|
||||
name: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
263
crates/wiggle/crates/generate/src/funcs.rs
Normal file
263
crates/wiggle/crates/generate/src/funcs.rs
Normal file
@@ -0,0 +1,263 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::lifetimes::anon_lifetime;
|
||||
use crate::names::Names;
|
||||
|
||||
pub fn define_func(names: &Names, func: &witx::InterfaceFunc) -> TokenStream {
|
||||
let funcname = func.name.as_str();
|
||||
|
||||
let ident = names.func(&func.name);
|
||||
let ctx_type = names.ctx_type();
|
||||
let coretype = func.core_type();
|
||||
|
||||
let params = coretype.args.iter().map(|arg| {
|
||||
let name = names.func_core_arg(arg);
|
||||
let atom = names.atom_type(arg.repr());
|
||||
quote!(#name : #atom)
|
||||
});
|
||||
|
||||
let abi_args = quote!(
|
||||
ctx: &#ctx_type, memory: &dyn wiggle_runtime::GuestMemory,
|
||||
#(#params),*
|
||||
);
|
||||
let abi_ret = if let Some(ret) = &coretype.ret {
|
||||
match ret.signifies {
|
||||
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!(())
|
||||
};
|
||||
|
||||
let err_type = coretype.ret.map(|ret| ret.param.tref);
|
||||
let err_val = err_type
|
||||
.clone()
|
||||
.map(|_res| quote!(#abi_ret::from(e)))
|
||||
.unwrap_or_else(|| quote!(()));
|
||||
|
||||
let error_handling = |location: &str| -> TokenStream {
|
||||
if let Some(tref) = &err_type {
|
||||
let abi_ret = match tref.type_().passed_by() {
|
||||
witx::TypePassedBy::Value(atom) => names.atom_type(atom),
|
||||
_ => unreachable!("err should always be passed by value"),
|
||||
};
|
||||
let err_typename = names.type_ref(&tref, anon_lifetime());
|
||||
quote! {
|
||||
let e = wiggle_runtime::GuestError::InFunc { funcname: #funcname, location: #location, err: Box::new(e.into()) };
|
||||
let err: #err_typename = wiggle_runtime::GuestErrorType::from_error(e, ctx);
|
||||
return #abi_ret::from(err);
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
panic!("error: {:?}", e)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let marshal_args = func
|
||||
.params
|
||||
.iter()
|
||||
.map(|p| marshal_arg(names, p, error_handling(p.name.as_str())));
|
||||
let trait_args = func.params.iter().map(|param| {
|
||||
let name = names.func_param(¶m.name);
|
||||
match param.tref.type_().passed_by() {
|
||||
witx::TypePassedBy::Value { .. } => quote!(#name),
|
||||
witx::TypePassedBy::Pointer { .. } => quote!(&#name),
|
||||
witx::TypePassedBy::PointerLengthPair { .. } => quote!(&#name),
|
||||
}
|
||||
});
|
||||
|
||||
let (trait_rets, trait_bindings) = if func.results.len() < 2 {
|
||||
(quote!({}), quote!(_))
|
||||
} else {
|
||||
let trait_rets = func
|
||||
.results
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|result| names.func_param(&result.name));
|
||||
let tuple = quote!((#(#trait_rets),*));
|
||||
(tuple.clone(), tuple)
|
||||
};
|
||||
|
||||
// Return value pointers need to be validated before the api call, then
|
||||
// assigned to afterwards. marshal_result returns these two statements as a pair.
|
||||
let marshal_rets = func
|
||||
.results
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|result| marshal_result(names, result, &error_handling));
|
||||
let marshal_rets_pre = marshal_rets.clone().map(|(pre, _post)| pre);
|
||||
let marshal_rets_post = marshal_rets.map(|(_pre, post)| post);
|
||||
|
||||
let success = if let Some(ref err_type) = err_type {
|
||||
let err_typename = names.type_ref(&err_type, anon_lifetime());
|
||||
quote! {
|
||||
let success:#err_typename = wiggle_runtime::GuestErrorType::success();
|
||||
#abi_ret::from(success)
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote!(pub fn #ident(#abi_args) -> #abi_ret {
|
||||
#(#marshal_args)*
|
||||
#(#marshal_rets_pre)*
|
||||
let #trait_bindings = match ctx.#ident(#(#trait_args),*) {
|
||||
Ok(#trait_bindings) => #trait_rets,
|
||||
Err(e) => { return #err_val; },
|
||||
};
|
||||
#(#marshal_rets_post)*
|
||||
#success
|
||||
})
|
||||
}
|
||||
|
||||
fn marshal_arg(
|
||||
names: &Names,
|
||||
param: &witx::InterfaceFuncParam,
|
||||
error_handling: TokenStream,
|
||||
) -> TokenStream {
|
||||
let tref = ¶m.tref;
|
||||
let interface_typename = names.type_ref(&tref, anon_lifetime());
|
||||
|
||||
let try_into_conversion = {
|
||||
let name = names.func_param(¶m.name);
|
||||
quote! {
|
||||
let #name: #interface_typename = {
|
||||
use ::std::convert::TryInto;
|
||||
match #name.try_into() {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
#error_handling
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let read_conversion = {
|
||||
let pointee_type = names.type_ref(tref, anon_lifetime());
|
||||
let arg_name = names.func_ptr_binding(¶m.name);
|
||||
let name = names.func_param(¶m.name);
|
||||
quote! {
|
||||
let #name = match wiggle_runtime::GuestPtr::<#pointee_type>::new(memory, #arg_name as u32).read() {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
#error_handling
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
match &*tref.type_() {
|
||||
witx::Type::Enum(_e) => try_into_conversion,
|
||||
witx::Type::Flags(_f) => try_into_conversion,
|
||||
witx::Type::Int(_i) => try_into_conversion,
|
||||
witx::Type::Builtin(b) => match b {
|
||||
witx::BuiltinType::U8 | witx::BuiltinType::U16 | witx::BuiltinType::Char8 => {
|
||||
try_into_conversion
|
||||
}
|
||||
witx::BuiltinType::S8 | witx::BuiltinType::S16 => {
|
||||
let name = names.func_param(¶m.name);
|
||||
quote! {
|
||||
let #name: #interface_typename = match (#name as i32).try_into() {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
#error_handling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
witx::BuiltinType::U32
|
||||
| witx::BuiltinType::S32
|
||||
| witx::BuiltinType::U64
|
||||
| witx::BuiltinType::S64
|
||||
| witx::BuiltinType::USize
|
||||
| witx::BuiltinType::F32
|
||||
| witx::BuiltinType::F64 => {
|
||||
let name = names.func_param(¶m.name);
|
||||
quote! {
|
||||
let #name = #name as #interface_typename;
|
||||
}
|
||||
}
|
||||
witx::BuiltinType::String => {
|
||||
let lifetime = anon_lifetime();
|
||||
let ptr_name = names.func_ptr_binding(¶m.name);
|
||||
let len_name = names.func_len_binding(¶m.name);
|
||||
let name = names.func_param(¶m.name);
|
||||
quote! {
|
||||
let #name = wiggle_runtime::GuestPtr::<#lifetime, str>::new(memory, (#ptr_name as u32, #len_name as u32));
|
||||
}
|
||||
}
|
||||
},
|
||||
witx::Type::Pointer(pointee) | witx::Type::ConstPointer(pointee) => {
|
||||
let pointee_type = names.type_ref(pointee, anon_lifetime());
|
||||
let name = names.func_param(¶m.name);
|
||||
quote! {
|
||||
let #name = wiggle_runtime::GuestPtr::<#pointee_type>::new(memory, #name as u32);
|
||||
}
|
||||
}
|
||||
witx::Type::Struct(_) => read_conversion,
|
||||
witx::Type::Array(arr) => {
|
||||
let pointee_type = names.type_ref(arr, anon_lifetime());
|
||||
let ptr_name = names.func_ptr_binding(¶m.name);
|
||||
let len_name = names.func_len_binding(¶m.name);
|
||||
let name = names.func_param(¶m.name);
|
||||
quote! {
|
||||
let #name = wiggle_runtime::GuestPtr::<[#pointee_type]>::new(memory, (#ptr_name as u32, #len_name as u32));
|
||||
}
|
||||
}
|
||||
witx::Type::Union(_u) => read_conversion,
|
||||
witx::Type::Handle(_h) => {
|
||||
let name = names.func_param(¶m.name);
|
||||
let handle_type = names.type_ref(tref, anon_lifetime());
|
||||
quote!( let #name = #handle_type::from(#name); )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn marshal_result<F>(
|
||||
names: &Names,
|
||||
result: &witx::InterfaceFuncParam,
|
||||
error_handling: F,
|
||||
) -> (TokenStream, TokenStream)
|
||||
where
|
||||
F: Fn(&str) -> TokenStream,
|
||||
{
|
||||
let tref = &result.tref;
|
||||
|
||||
let write_val_to_ptr = {
|
||||
let pointee_type = names.type_ref(tref, anon_lifetime());
|
||||
// core type is given func_ptr_binding name.
|
||||
let ptr_name = names.func_ptr_binding(&result.name);
|
||||
let ptr_err_handling = error_handling(&format!("{}:result_ptr_mut", result.name.as_str()));
|
||||
let pre = quote! {
|
||||
let #ptr_name = wiggle_runtime::GuestPtr::<#pointee_type>::new(memory, #ptr_name as u32);
|
||||
};
|
||||
// trait binding returns func_param name.
|
||||
let val_name = names.func_param(&result.name);
|
||||
let post = quote! {
|
||||
if let Err(e) = #ptr_name.write(#val_name) {
|
||||
#ptr_err_handling
|
||||
}
|
||||
};
|
||||
(pre, post)
|
||||
};
|
||||
|
||||
match &*tref.type_() {
|
||||
witx::Type::Builtin(b) => match b {
|
||||
witx::BuiltinType::String => unimplemented!("string result types"),
|
||||
_ => write_val_to_ptr,
|
||||
},
|
||||
witx::Type::Pointer { .. } | witx::Type::ConstPointer { .. } | witx::Type::Array { .. } => {
|
||||
unimplemented!("pointer/array result types")
|
||||
}
|
||||
_ => write_val_to_ptr,
|
||||
}
|
||||
}
|
||||
44
crates/wiggle/crates/generate/src/lib.rs
Normal file
44
crates/wiggle/crates/generate/src/lib.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
pub mod config;
|
||||
mod funcs;
|
||||
mod lifetimes;
|
||||
mod module_trait;
|
||||
mod names;
|
||||
mod types;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub use config::Config;
|
||||
pub use funcs::define_func;
|
||||
pub use module_trait::define_module_trait;
|
||||
pub use names::Names;
|
||||
pub use types::define_datatype;
|
||||
|
||||
pub fn generate(doc: &witx::Document, config: &Config) -> TokenStream {
|
||||
let names = Names::new(config); // TODO parse the names from the invocation of the macro, or from a file?
|
||||
|
||||
let types = doc.typenames().map(|t| define_datatype(&names, &t));
|
||||
|
||||
let modules = doc.modules().map(|module| {
|
||||
let modname = names.module(&module.name);
|
||||
let fs = module.funcs().map(|f| define_func(&names, &f));
|
||||
let modtrait = define_module_trait(&names, &module);
|
||||
let ctx_type = names.ctx_type();
|
||||
quote!(
|
||||
pub mod #modname {
|
||||
use super::#ctx_type;
|
||||
use super::types::*;
|
||||
#(#fs)*
|
||||
|
||||
#modtrait
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
quote!(
|
||||
pub mod types {
|
||||
#(#types)*
|
||||
}
|
||||
#(#modules)*
|
||||
)
|
||||
}
|
||||
83
crates/wiggle/crates/generate/src/lifetimes.rs
Normal file
83
crates/wiggle/crates/generate/src/lifetimes.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub trait LifetimeExt {
|
||||
fn is_transparent(&self) -> bool;
|
||||
fn needs_lifetime(&self) -> bool;
|
||||
}
|
||||
|
||||
impl LifetimeExt for witx::TypeRef {
|
||||
fn is_transparent(&self) -> bool {
|
||||
self.type_().is_transparent()
|
||||
}
|
||||
fn needs_lifetime(&self) -> bool {
|
||||
self.type_().needs_lifetime()
|
||||
}
|
||||
}
|
||||
|
||||
impl LifetimeExt for witx::Type {
|
||||
fn is_transparent(&self) -> bool {
|
||||
match self {
|
||||
witx::Type::Builtin(b) => b.is_transparent(),
|
||||
witx::Type::Struct(s) => s.is_transparent(),
|
||||
witx::Type::Enum { .. }
|
||||
| witx::Type::Flags { .. }
|
||||
| witx::Type::Int { .. }
|
||||
| witx::Type::Handle { .. } => true,
|
||||
witx::Type::Union { .. }
|
||||
| witx::Type::Pointer { .. }
|
||||
| witx::Type::ConstPointer { .. }
|
||||
| witx::Type::Array { .. } => false,
|
||||
}
|
||||
}
|
||||
fn needs_lifetime(&self) -> bool {
|
||||
match self {
|
||||
witx::Type::Builtin(b) => b.needs_lifetime(),
|
||||
witx::Type::Struct(s) => s.needs_lifetime(),
|
||||
witx::Type::Union(u) => u.needs_lifetime(),
|
||||
witx::Type::Enum { .. }
|
||||
| witx::Type::Flags { .. }
|
||||
| witx::Type::Int { .. }
|
||||
| witx::Type::Handle { .. } => false,
|
||||
witx::Type::Pointer { .. }
|
||||
| witx::Type::ConstPointer { .. }
|
||||
| witx::Type::Array { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LifetimeExt for witx::BuiltinType {
|
||||
fn is_transparent(&self) -> bool {
|
||||
!self.needs_lifetime()
|
||||
}
|
||||
fn needs_lifetime(&self) -> bool {
|
||||
match self {
|
||||
witx::BuiltinType::String => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LifetimeExt for witx::StructDatatype {
|
||||
fn is_transparent(&self) -> bool {
|
||||
self.members.iter().all(|m| m.tref.is_transparent())
|
||||
}
|
||||
fn needs_lifetime(&self) -> bool {
|
||||
self.members.iter().any(|m| m.tref.needs_lifetime())
|
||||
}
|
||||
}
|
||||
|
||||
impl LifetimeExt for witx::UnionDatatype {
|
||||
fn is_transparent(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn needs_lifetime(&self) -> bool {
|
||||
self.variants
|
||||
.iter()
|
||||
.any(|m| m.tref.as_ref().map(|t| t.needs_lifetime()).unwrap_or(false))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn anon_lifetime() -> TokenStream {
|
||||
quote!('_)
|
||||
}
|
||||
57
crates/wiggle/crates/generate/src/module_trait.rs
Normal file
57
crates/wiggle/crates/generate/src/module_trait.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::lifetimes::{anon_lifetime, LifetimeExt};
|
||||
use crate::names::Names;
|
||||
use witx::Module;
|
||||
|
||||
pub fn define_module_trait(names: &Names, m: &Module) -> TokenStream {
|
||||
let traitname = names.trait_name(&m.name);
|
||||
let traitmethods = m.funcs().map(|f| {
|
||||
// Check if we're returning an entity anotated with a lifetime,
|
||||
// in which case, we'll need to annotate the function itself, and
|
||||
// hence will need an explicit lifetime (rather than anonymous)
|
||||
let (lifetime, is_anonymous) = if f
|
||||
.params
|
||||
.iter()
|
||||
.chain(&f.results)
|
||||
.any(|ret| ret.tref.needs_lifetime())
|
||||
{
|
||||
(quote!('a), false)
|
||||
} else {
|
||||
(anon_lifetime(), true)
|
||||
};
|
||||
let funcname = names.func(&f.name);
|
||||
let args = f.params.iter().map(|arg| {
|
||||
let arg_name = names.func_param(&arg.name);
|
||||
let arg_typename = names.type_ref(&arg.tref, lifetime.clone());
|
||||
let arg_type = match arg.tref.type_().passed_by() {
|
||||
witx::TypePassedBy::Value { .. } => quote!(#arg_typename),
|
||||
witx::TypePassedBy::Pointer { .. } => quote!(&#arg_typename),
|
||||
witx::TypePassedBy::PointerLengthPair { .. } => quote!(&#arg_typename),
|
||||
};
|
||||
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| names.type_ref(&err_result.tref, lifetime.clone()))
|
||||
.unwrap_or(quote!(()));
|
||||
|
||||
if is_anonymous {
|
||||
quote!(fn #funcname(&self, #(#args),*) -> Result<(#(#rets),*), #err>;)
|
||||
} else {
|
||||
quote!(fn #funcname<#lifetime>(&self, #(#args),*) -> Result<(#(#rets),*), #err>;)
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
pub trait #traitname {
|
||||
#(#traitmethods)*
|
||||
}
|
||||
}
|
||||
}
|
||||
140
crates/wiggle/crates/generate/src/names.rs
Normal file
140
crates/wiggle/crates/generate/src/names.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use witx::{AtomType, BuiltinType, Id, TypeRef};
|
||||
|
||||
use crate::lifetimes::LifetimeExt;
|
||||
use crate::Config;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Names {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Names {
|
||||
pub fn new(config: &Config) -> Names {
|
||||
Names {
|
||||
config: config.clone(),
|
||||
}
|
||||
}
|
||||
pub fn ctx_type(&self) -> Ident {
|
||||
self.config.ctx.name.clone()
|
||||
}
|
||||
pub fn type_(&self, id: &Id) -> TokenStream {
|
||||
let ident = format_ident!("{}", id.as_str().to_camel_case());
|
||||
quote!(#ident)
|
||||
}
|
||||
pub fn builtin_type(&self, b: BuiltinType, lifetime: TokenStream) -> TokenStream {
|
||||
match b {
|
||||
BuiltinType::String => quote!(wiggle_runtime::GuestPtr<#lifetime, str>),
|
||||
BuiltinType::U8 => quote!(u8),
|
||||
BuiltinType::U16 => quote!(u16),
|
||||
BuiltinType::U32 => quote!(u32),
|
||||
BuiltinType::U64 => quote!(u64),
|
||||
BuiltinType::S8 => quote!(i8),
|
||||
BuiltinType::S16 => quote!(i16),
|
||||
BuiltinType::S32 => quote!(i32),
|
||||
BuiltinType::S64 => quote!(i64),
|
||||
BuiltinType::F32 => quote!(f32),
|
||||
BuiltinType::F64 => quote!(f64),
|
||||
BuiltinType::Char8 => quote!(u8),
|
||||
BuiltinType::USize => quote!(usize),
|
||||
}
|
||||
}
|
||||
pub fn atom_type(&self, atom: AtomType) -> TokenStream {
|
||||
match atom {
|
||||
AtomType::I32 => quote!(i32),
|
||||
AtomType::I64 => quote!(i64),
|
||||
AtomType::F32 => quote!(f32),
|
||||
AtomType::F64 => quote!(f64),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_ref(&self, tref: &TypeRef, lifetime: TokenStream) -> TokenStream {
|
||||
match tref {
|
||||
TypeRef::Name(nt) => {
|
||||
let ident = self.type_(&nt.name);
|
||||
if nt.tref.needs_lifetime() {
|
||||
quote!(#ident<#lifetime>)
|
||||
} else {
|
||||
quote!(#ident)
|
||||
}
|
||||
}
|
||||
TypeRef::Value(ty) => match &**ty {
|
||||
witx::Type::Builtin(builtin) => self.builtin_type(*builtin, lifetime.clone()),
|
||||
witx::Type::Pointer(pointee) | witx::Type::ConstPointer(pointee) => {
|
||||
let pointee_type = self.type_ref(&pointee, lifetime.clone());
|
||||
quote!(wiggle_runtime::GuestPtr<#lifetime, #pointee_type>)
|
||||
}
|
||||
_ => unimplemented!("anonymous type ref"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enum_variant(&self, id: &Id) -> Ident {
|
||||
// FIXME this is a hack - just a proof of concept.
|
||||
if id.as_str().starts_with('2') {
|
||||
format_ident!("TooBig")
|
||||
} else if id.as_str() == "type" {
|
||||
format_ident!("Type")
|
||||
} else {
|
||||
format_ident!("{}", id.as_str().to_camel_case())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flag_member(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}", id.as_str().to_shouty_snake_case())
|
||||
}
|
||||
|
||||
pub fn int_member(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}", id.as_str().to_shouty_snake_case())
|
||||
}
|
||||
|
||||
pub fn struct_member(&self, id: &Id) -> Ident {
|
||||
// FIXME this is a hack - just a proof of concept.
|
||||
if id.as_str() == "type" {
|
||||
format_ident!("type_")
|
||||
} else {
|
||||
format_ident!("{}", id.as_str().to_snake_case())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}", id.as_str().to_snake_case())
|
||||
}
|
||||
|
||||
pub fn trait_name(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}", id.as_str().to_camel_case())
|
||||
}
|
||||
|
||||
pub fn func(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}", id.as_str().to_snake_case())
|
||||
}
|
||||
|
||||
pub fn func_param(&self, id: &Id) -> Ident {
|
||||
// FIXME this is a hack - just a proof of concept.
|
||||
if id.as_str() == "in" {
|
||||
format_ident!("in_")
|
||||
} else {
|
||||
format_ident!("{}", id.as_str().to_snake_case())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn func_core_arg(&self, arg: &witx::CoreParamType) -> Ident {
|
||||
match arg.signifies {
|
||||
witx::CoreParamSignifies::Value { .. } => self.func_param(&arg.param.name),
|
||||
witx::CoreParamSignifies::PointerTo => self.func_ptr_binding(&arg.param.name),
|
||||
witx::CoreParamSignifies::LengthOf => self.func_len_binding(&arg.param.name),
|
||||
}
|
||||
}
|
||||
|
||||
/// For when you need a {name}_ptr binding for passing a value by reference:
|
||||
pub fn func_ptr_binding(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}_ptr", id.as_str().to_snake_case())
|
||||
}
|
||||
|
||||
/// For when you need a {name}_len binding for passing an array:
|
||||
pub fn func_len_binding(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}_len", id.as_str().to_snake_case())
|
||||
}
|
||||
}
|
||||
113
crates/wiggle/crates/generate/src/types/enum.rs
Normal file
113
crates/wiggle/crates/generate/src/types/enum.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use super::{atom_token, int_repr_tokens};
|
||||
use crate::names::Names;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(super) fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype) -> TokenStream {
|
||||
let ident = names.type_(&name);
|
||||
|
||||
let repr = int_repr_tokens(e.repr);
|
||||
let abi_repr = atom_token(match e.repr {
|
||||
witx::IntRepr::U8 | witx::IntRepr::U16 | witx::IntRepr::U32 => witx::AtomType::I32,
|
||||
witx::IntRepr::U64 => witx::AtomType::I64,
|
||||
});
|
||||
|
||||
let mut variant_names = vec![];
|
||||
let mut tryfrom_repr_cases = vec![];
|
||||
let mut to_repr_cases = vec![];
|
||||
let mut to_display = vec![];
|
||||
|
||||
for (n, variant) in e.variants.iter().enumerate() {
|
||||
let variant_name = names.enum_variant(&variant.name);
|
||||
let docs = variant.docs.trim();
|
||||
let ident_str = ident.to_string();
|
||||
let variant_str = variant_name.to_string();
|
||||
tryfrom_repr_cases.push(quote!(#n => Ok(#ident::#variant_name)));
|
||||
to_repr_cases.push(quote!(#ident::#variant_name => #n as #repr));
|
||||
to_display.push(quote!(#ident::#variant_name => format!("{} ({}::{}({}))", #docs, #ident_str, #variant_str, #repr::from(*self))));
|
||||
variant_names.push(variant_name);
|
||||
}
|
||||
|
||||
quote! {
|
||||
#[repr(#repr)]
|
||||
#[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)]
|
||||
pub enum #ident {
|
||||
#(#variant_names),*
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for #ident {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
let to_str = match self {
|
||||
#(#to_display,)*
|
||||
};
|
||||
write!(f, "{}", to_str)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<#repr> for #ident {
|
||||
type Error = wiggle_runtime::GuestError;
|
||||
fn try_from(value: #repr) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
match value as usize {
|
||||
#(#tryfrom_repr_cases),*,
|
||||
_ => Err(wiggle_runtime::GuestError::InvalidEnumValue(stringify!(#ident))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<#abi_repr> for #ident {
|
||||
type Error = wiggle_runtime::GuestError;
|
||||
fn try_from(value: #abi_repr) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
#ident::try_from(value as #repr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for #repr {
|
||||
fn from(e: #ident) -> #repr {
|
||||
match e {
|
||||
#(#to_repr_cases),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for #abi_repr {
|
||||
fn from(e: #ident) -> #abi_repr {
|
||||
#repr::from(e) as #abi_repr
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> wiggle_runtime::GuestType<'a> for #ident {
|
||||
fn guest_size() -> u32 {
|
||||
#repr::guest_size()
|
||||
}
|
||||
|
||||
fn guest_align() -> usize {
|
||||
#repr::guest_align()
|
||||
}
|
||||
|
||||
fn read(location: &wiggle_runtime::GuestPtr<#ident>) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
use std::convert::TryFrom;
|
||||
let reprval = #repr::read(&location.cast())?;
|
||||
let value = #ident::try_from(reprval)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn write(location: &wiggle_runtime::GuestPtr<'_, #ident>, val: Self)
|
||||
-> Result<(), wiggle_runtime::GuestError>
|
||||
{
|
||||
#repr::write(&location.cast(), #repr::from(val))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl <'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
|
||||
#[inline]
|
||||
fn validate(location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
|
||||
use std::convert::TryFrom;
|
||||
// Validate value in memory using #ident::try_from(reprval)
|
||||
let reprval = unsafe { (location as *mut #repr).read() };
|
||||
let _val = #ident::try_from(reprval)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
crates/wiggle/crates/generate/src/types/flags.rs
Normal file
161
crates/wiggle/crates/generate/src/types/flags.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use super::{atom_token, int_repr_tokens};
|
||||
use crate::names::Names;
|
||||
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub(super) fn define_flags(names: &Names, name: &witx::Id, f: &witx::FlagsDatatype) -> TokenStream {
|
||||
let ident = names.type_(&name);
|
||||
let repr = int_repr_tokens(f.repr);
|
||||
let abi_repr = atom_token(match f.repr {
|
||||
witx::IntRepr::U8 | witx::IntRepr::U16 | witx::IntRepr::U32 => witx::AtomType::I32,
|
||||
witx::IntRepr::U64 => witx::AtomType::I64,
|
||||
});
|
||||
|
||||
let mut flag_constructors = vec![];
|
||||
let mut all_values = 0;
|
||||
for (i, f) in f.flags.iter().enumerate() {
|
||||
let name = names.flag_member(&f.name);
|
||||
let value = 1u128
|
||||
.checked_shl(u32::try_from(i).expect("flag value overflow"))
|
||||
.expect("flag value overflow");
|
||||
let value_token = Literal::u128_unsuffixed(value);
|
||||
flag_constructors.push(quote!(pub const #name: #ident = #ident(#value_token)));
|
||||
all_values += value;
|
||||
}
|
||||
let all_values_token = Literal::u128_unsuffixed(all_values);
|
||||
|
||||
let ident_str = ident.to_string();
|
||||
|
||||
quote! {
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)]
|
||||
pub struct #ident(#repr);
|
||||
|
||||
impl #ident {
|
||||
#(#flag_constructors);*;
|
||||
pub const EMPTY_FLAGS: #ident = #ident(0 as #repr);
|
||||
pub const ALL_FLAGS: #ident = #ident(#all_values_token);
|
||||
|
||||
pub fn contains(&self, other: &#ident) -> bool {
|
||||
!*self & *other == Self::EMPTY_FLAGS
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for #ident {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(f, "{}({:#b})", #ident_str, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitAnd for #ident {
|
||||
type Output = Self;
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
#ident(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitAndAssign for #ident {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
*self = *self & rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitOr for #ident {
|
||||
type Output = Self;
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
#ident(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitOrAssign for #ident {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
*self = *self | rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitXor for #ident {
|
||||
type Output = Self;
|
||||
fn bitxor(self, rhs: Self) -> Self::Output {
|
||||
#ident(self.0 ^ rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitXorAssign for #ident {
|
||||
fn bitxor_assign(&mut self, rhs: Self) {
|
||||
*self = *self ^ rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::Not for #ident {
|
||||
type Output = Self;
|
||||
fn not(self) -> Self::Output {
|
||||
#ident(!self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<#repr> for #ident {
|
||||
type Error = wiggle_runtime::GuestError;
|
||||
fn try_from(value: #repr) -> Result<Self, wiggle_runtime::GuestError> {
|
||||
if #repr::from(!#ident::ALL_FLAGS) & value != 0 {
|
||||
Err(wiggle_runtime::GuestError::InvalidFlagValue(stringify!(#ident)))
|
||||
} else {
|
||||
Ok(#ident(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<#abi_repr> for #ident {
|
||||
type Error = wiggle_runtime::GuestError;
|
||||
fn try_from(value: #abi_repr) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
#ident::try_from(value as #repr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for #repr {
|
||||
fn from(e: #ident) -> #repr {
|
||||
e.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for #abi_repr {
|
||||
fn from(e: #ident) -> #abi_repr {
|
||||
#repr::from(e) as #abi_repr
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> wiggle_runtime::GuestType<'a> for #ident {
|
||||
fn guest_size() -> u32 {
|
||||
#repr::guest_size()
|
||||
}
|
||||
|
||||
fn guest_align() -> usize {
|
||||
#repr::guest_align()
|
||||
}
|
||||
|
||||
fn read(location: &wiggle_runtime::GuestPtr<#ident>) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
use std::convert::TryFrom;
|
||||
let reprval = #repr::read(&location.cast())?;
|
||||
let value = #ident::try_from(reprval)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn write(location: &wiggle_runtime::GuestPtr<'_, #ident>, val: Self) -> Result<(), wiggle_runtime::GuestError> {
|
||||
let val: #repr = #repr::from(val);
|
||||
#repr::write(&location.cast(), val)
|
||||
}
|
||||
}
|
||||
unsafe impl <'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
|
||||
#[inline]
|
||||
fn validate(location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
|
||||
use std::convert::TryFrom;
|
||||
// Validate value in memory using #ident::try_from(reprval)
|
||||
let reprval = unsafe { (location as *mut #repr).read() };
|
||||
let _val = #ident::try_from(reprval)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
77
crates/wiggle/crates/generate/src/types/handle.rs
Normal file
77
crates/wiggle/crates/generate/src/types/handle.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use crate::names::Names;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use witx::Layout;
|
||||
|
||||
pub(super) fn define_handle(
|
||||
names: &Names,
|
||||
name: &witx::Id,
|
||||
h: &witx::HandleDatatype,
|
||||
) -> TokenStream {
|
||||
let ident = names.type_(name);
|
||||
let size = h.mem_size_align().size as u32;
|
||||
let align = h.mem_size_align().align as usize;
|
||||
quote! {
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)]
|
||||
pub struct #ident(u32);
|
||||
|
||||
impl From<#ident> for u32 {
|
||||
fn from(e: #ident) -> u32 {
|
||||
e.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for i32 {
|
||||
fn from(e: #ident) -> i32 {
|
||||
e.0 as i32
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for #ident {
|
||||
fn from(e: u32) -> #ident {
|
||||
#ident(e)
|
||||
}
|
||||
}
|
||||
impl From<i32> for #ident {
|
||||
fn from(e: i32) -> #ident {
|
||||
#ident(e as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for #ident {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(f, "{}({})", stringify!(#ident), self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> wiggle_runtime::GuestType<'a> for #ident {
|
||||
fn guest_size() -> u32 {
|
||||
#size
|
||||
}
|
||||
|
||||
fn guest_align() -> usize {
|
||||
#align
|
||||
}
|
||||
|
||||
fn read(location: &wiggle_runtime::GuestPtr<'a, #ident>) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
Ok(#ident(u32::read(&location.cast())?))
|
||||
}
|
||||
|
||||
fn write(location: &wiggle_runtime::GuestPtr<'_, Self>, val: Self) -> Result<(), wiggle_runtime::GuestError> {
|
||||
u32::write(&location.cast(), val.0)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
|
||||
#[inline]
|
||||
fn validate(_location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
|
||||
// All bit patterns accepted
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
93
crates/wiggle/crates/generate/src/types/int.rs
Normal file
93
crates/wiggle/crates/generate/src/types/int.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use super::{atom_token, int_repr_tokens};
|
||||
use crate::names::Names;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(super) fn define_int(names: &Names, name: &witx::Id, i: &witx::IntDatatype) -> TokenStream {
|
||||
let ident = names.type_(&name);
|
||||
let repr = int_repr_tokens(i.repr);
|
||||
let abi_repr = atom_token(match i.repr {
|
||||
witx::IntRepr::U8 | witx::IntRepr::U16 | witx::IntRepr::U32 => witx::AtomType::I32,
|
||||
witx::IntRepr::U64 => witx::AtomType::I64,
|
||||
});
|
||||
let consts = i
|
||||
.consts
|
||||
.iter()
|
||||
.map(|r#const| {
|
||||
let const_ident = names.int_member(&r#const.name);
|
||||
let value = r#const.value;
|
||||
quote!(pub const #const_ident: #ident = #ident(#value))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
quote! {
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)]
|
||||
pub struct #ident(#repr);
|
||||
|
||||
impl #ident {
|
||||
#(#consts;)*
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for #ident {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<#repr> for #ident {
|
||||
type Error = wiggle_runtime::GuestError;
|
||||
fn try_from(value: #repr) -> Result<Self, wiggle_runtime::GuestError> {
|
||||
Ok(#ident(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<#abi_repr> for #ident {
|
||||
type Error = wiggle_runtime::GuestError;
|
||||
fn try_from(value: #abi_repr) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
#ident::try_from(value as #repr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for #repr {
|
||||
fn from(e: #ident) -> #repr {
|
||||
e.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for #abi_repr {
|
||||
fn from(e: #ident) -> #abi_repr {
|
||||
#repr::from(e) as #abi_repr
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> wiggle_runtime::GuestType<'a> for #ident {
|
||||
fn guest_size() -> u32 {
|
||||
#repr::guest_size()
|
||||
}
|
||||
|
||||
fn guest_align() -> usize {
|
||||
#repr::guest_align()
|
||||
}
|
||||
|
||||
fn read(location: &wiggle_runtime::GuestPtr<'a, #ident>) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
Ok(#ident(#repr::read(&location.cast())?))
|
||||
|
||||
}
|
||||
|
||||
fn write(location: &wiggle_runtime::GuestPtr<'_, #ident>, val: Self) -> Result<(), wiggle_runtime::GuestError> {
|
||||
#repr::write(&location.cast(), val.0)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
|
||||
#[inline]
|
||||
fn validate(_location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
|
||||
// All bit patterns accepted
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
90
crates/wiggle/crates/generate/src/types/mod.rs
Normal file
90
crates/wiggle/crates/generate/src/types/mod.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
mod r#enum;
|
||||
mod flags;
|
||||
mod handle;
|
||||
mod int;
|
||||
mod r#struct;
|
||||
mod union;
|
||||
|
||||
use crate::lifetimes::LifetimeExt;
|
||||
use crate::names::Names;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub fn define_datatype(names: &Names, namedtype: &witx::NamedType) -> TokenStream {
|
||||
match &namedtype.tref {
|
||||
witx::TypeRef::Name(alias_to) => define_alias(names, &namedtype.name, &alias_to),
|
||||
witx::TypeRef::Value(v) => match &**v {
|
||||
witx::Type::Enum(e) => r#enum::define_enum(names, &namedtype.name, &e),
|
||||
witx::Type::Int(i) => int::define_int(names, &namedtype.name, &i),
|
||||
witx::Type::Flags(f) => flags::define_flags(names, &namedtype.name, &f),
|
||||
witx::Type::Struct(s) => r#struct::define_struct(names, &namedtype.name, &s),
|
||||
witx::Type::Union(u) => union::define_union(names, &namedtype.name, &u),
|
||||
witx::Type::Handle(h) => handle::define_handle(names, &namedtype.name, &h),
|
||||
witx::Type::Builtin(b) => define_builtin(names, &namedtype.name, *b),
|
||||
witx::Type::Pointer(p) => {
|
||||
define_witx_pointer(names, &namedtype.name, quote!(wiggle_runtime::GuestPtr), p)
|
||||
}
|
||||
witx::Type::ConstPointer(p) => {
|
||||
define_witx_pointer(names, &namedtype.name, quote!(wiggle_runtime::GuestPtr), p)
|
||||
}
|
||||
witx::Type::Array(arr) => define_witx_array(names, &namedtype.name, &arr),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn define_alias(names: &Names, name: &witx::Id, to: &witx::NamedType) -> TokenStream {
|
||||
let ident = names.type_(name);
|
||||
let rhs = names.type_(&to.name);
|
||||
if to.tref.needs_lifetime() {
|
||||
quote!(pub type #ident<'a> = #rhs<'a>;)
|
||||
} else {
|
||||
quote!(pub type #ident = #rhs;)
|
||||
}
|
||||
}
|
||||
|
||||
fn define_builtin(names: &Names, name: &witx::Id, builtin: witx::BuiltinType) -> TokenStream {
|
||||
let ident = names.type_(name);
|
||||
let built = names.builtin_type(builtin, quote!('a));
|
||||
if builtin.needs_lifetime() {
|
||||
quote!(pub type #ident<'a> = #built;)
|
||||
} else {
|
||||
quote!(pub type #ident = #built;)
|
||||
}
|
||||
}
|
||||
|
||||
fn define_witx_pointer(
|
||||
names: &Names,
|
||||
name: &witx::Id,
|
||||
pointer_type: TokenStream,
|
||||
pointee: &witx::TypeRef,
|
||||
) -> TokenStream {
|
||||
let ident = names.type_(name);
|
||||
let pointee_type = names.type_ref(pointee, quote!('a));
|
||||
|
||||
quote!(pub type #ident<'a> = #pointer_type<'a, #pointee_type>;)
|
||||
}
|
||||
|
||||
fn define_witx_array(names: &Names, name: &witx::Id, arr_raw: &witx::TypeRef) -> TokenStream {
|
||||
let ident = names.type_(name);
|
||||
let pointee_type = names.type_ref(arr_raw, quote!('a));
|
||||
quote!(pub type #ident<'a> = wiggle_runtime::GuestPtr<'a, [#pointee_type]>;)
|
||||
}
|
||||
|
||||
fn int_repr_tokens(int_repr: witx::IntRepr) -> TokenStream {
|
||||
match int_repr {
|
||||
witx::IntRepr::U8 => quote!(u8),
|
||||
witx::IntRepr::U16 => quote!(u16),
|
||||
witx::IntRepr::U32 => quote!(u32),
|
||||
witx::IntRepr::U64 => quote!(u64),
|
||||
}
|
||||
}
|
||||
|
||||
fn atom_token(atom: witx::AtomType) -> TokenStream {
|
||||
match atom {
|
||||
witx::AtomType::I32 => quote!(i32),
|
||||
witx::AtomType::I64 => quote!(i64),
|
||||
witx::AtomType::F32 => quote!(f32),
|
||||
witx::AtomType::F64 => quote!(f64),
|
||||
}
|
||||
}
|
||||
134
crates/wiggle/crates/generate/src/types/struct.rs
Normal file
134
crates/wiggle/crates/generate/src/types/struct.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use crate::lifetimes::{anon_lifetime, LifetimeExt};
|
||||
use crate::names::Names;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use witx::Layout;
|
||||
|
||||
pub(super) fn define_struct(
|
||||
names: &Names,
|
||||
name: &witx::Id,
|
||||
s: &witx::StructDatatype,
|
||||
) -> TokenStream {
|
||||
let ident = names.type_(name);
|
||||
let size = s.mem_size_align().size as u32;
|
||||
let align = s.mem_size_align().align as usize;
|
||||
|
||||
let member_names = s.members.iter().map(|m| names.struct_member(&m.name));
|
||||
let member_decls = s.members.iter().map(|m| {
|
||||
let name = names.struct_member(&m.name);
|
||||
let type_ = match &m.tref {
|
||||
witx::TypeRef::Name(nt) => names.type_(&nt.name),
|
||||
witx::TypeRef::Value(ty) => match &**ty {
|
||||
witx::Type::Builtin(builtin) => names.builtin_type(*builtin, quote!('a)),
|
||||
witx::Type::Pointer(pointee) | witx::Type::ConstPointer(pointee) => {
|
||||
let pointee_type = names.type_ref(&pointee, quote!('a));
|
||||
quote!(wiggle_runtime::GuestPtr<'a, #pointee_type>)
|
||||
}
|
||||
_ => unimplemented!("other anonymous struct members"),
|
||||
},
|
||||
};
|
||||
quote!(pub #name: #type_)
|
||||
});
|
||||
|
||||
let member_reads = s.member_layout().into_iter().map(|ml| {
|
||||
let name = names.struct_member(&ml.member.name);
|
||||
let offset = ml.offset as u32;
|
||||
let location = quote!(location.cast::<u8>().add(#offset)?.cast());
|
||||
match &ml.member.tref {
|
||||
witx::TypeRef::Name(nt) => {
|
||||
let type_ = names.type_(&nt.name);
|
||||
quote! {
|
||||
let #name = <#type_ as wiggle_runtime::GuestType>::read(&#location)?;
|
||||
}
|
||||
}
|
||||
witx::TypeRef::Value(ty) => match &**ty {
|
||||
witx::Type::Builtin(builtin) => {
|
||||
let type_ = names.builtin_type(*builtin, anon_lifetime());
|
||||
quote! {
|
||||
let #name = <#type_ as wiggle_runtime::GuestType>::read(&#location)?;
|
||||
}
|
||||
}
|
||||
witx::Type::Pointer(pointee) | witx::Type::ConstPointer(pointee) => {
|
||||
let pointee_type = names.type_ref(&pointee, anon_lifetime());
|
||||
quote! {
|
||||
let #name = <wiggle_runtime::GuestPtr::<#pointee_type> as wiggle_runtime::GuestType>::read(&#location)?;
|
||||
}
|
||||
}
|
||||
_ => unimplemented!("other anonymous struct members"),
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
let member_writes = s.member_layout().into_iter().map(|ml| {
|
||||
let name = names.struct_member(&ml.member.name);
|
||||
let offset = ml.offset as u32;
|
||||
quote! {
|
||||
wiggle_runtime::GuestType::write(
|
||||
&location.cast::<u8>().add(#offset)?.cast(),
|
||||
val.#name,
|
||||
)?;
|
||||
}
|
||||
});
|
||||
|
||||
let (struct_lifetime, extra_derive) = if s.needs_lifetime() {
|
||||
(quote!(<'a>), quote!())
|
||||
} else {
|
||||
(quote!(), quote!(, Copy, PartialEq))
|
||||
};
|
||||
|
||||
let transparent = if s.is_transparent() {
|
||||
let member_validate = s.member_layout().into_iter().map(|ml| {
|
||||
let offset = ml.offset;
|
||||
let typename = names.type_ref(&ml.member.tref, anon_lifetime());
|
||||
quote! {
|
||||
// SAFETY: caller has validated bounds and alignment of `location`.
|
||||
// member_layout gives correctly-aligned pointers inside that area.
|
||||
#typename::validate(
|
||||
unsafe { (location as *mut u8).add(#offset) as *mut _ }
|
||||
)?;
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
unsafe impl<'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
|
||||
#[inline]
|
||||
fn validate(location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
|
||||
#(#member_validate)*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Debug #extra_derive)]
|
||||
pub struct #ident #struct_lifetime {
|
||||
#(#member_decls),*
|
||||
}
|
||||
|
||||
impl<'a> wiggle_runtime::GuestType<'a> for #ident #struct_lifetime {
|
||||
fn guest_size() -> u32 {
|
||||
#size
|
||||
}
|
||||
|
||||
fn guest_align() -> usize {
|
||||
#align
|
||||
}
|
||||
|
||||
fn read(location: &wiggle_runtime::GuestPtr<'a, Self>) -> Result<Self, wiggle_runtime::GuestError> {
|
||||
#(#member_reads)*
|
||||
Ok(#ident { #(#member_names),* })
|
||||
}
|
||||
|
||||
fn write(location: &wiggle_runtime::GuestPtr<'_, Self>, val: Self) -> Result<(), wiggle_runtime::GuestError> {
|
||||
#(#member_writes)*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#transparent
|
||||
}
|
||||
}
|
||||
109
crates/wiggle/crates/generate/src/types/union.rs
Normal file
109
crates/wiggle/crates/generate/src/types/union.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use crate::lifetimes::LifetimeExt;
|
||||
use crate::names::Names;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use witx::Layout;
|
||||
|
||||
pub(super) fn define_union(names: &Names, name: &witx::Id, u: &witx::UnionDatatype) -> TokenStream {
|
||||
let ident = names.type_(name);
|
||||
let size = u.mem_size_align().size as u32;
|
||||
let align = u.mem_size_align().align as usize;
|
||||
let ulayout = u.union_layout();
|
||||
let contents_offset = ulayout.contents_offset as u32;
|
||||
|
||||
let lifetime = quote!('a);
|
||||
|
||||
let variants = u.variants.iter().map(|v| {
|
||||
let var_name = names.enum_variant(&v.name);
|
||||
if let Some(tref) = &v.tref {
|
||||
let var_type = names.type_ref(&tref, lifetime.clone());
|
||||
quote!(#var_name(#var_type))
|
||||
} else {
|
||||
quote!(#var_name)
|
||||
}
|
||||
});
|
||||
|
||||
let tagname = names.type_(&u.tag.name);
|
||||
|
||||
let read_variant = u.variants.iter().map(|v| {
|
||||
let variantname = names.enum_variant(&v.name);
|
||||
if let Some(tref) = &v.tref {
|
||||
let varianttype = names.type_ref(tref, lifetime.clone());
|
||||
quote! {
|
||||
#tagname::#variantname => {
|
||||
let variant_ptr = location.cast::<u8>().add(#contents_offset)?;
|
||||
let variant_val = <#varianttype as wiggle_runtime::GuestType>::read(&variant_ptr.cast())?;
|
||||
Ok(#ident::#variantname(variant_val))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! { #tagname::#variantname => Ok(#ident::#variantname), }
|
||||
}
|
||||
});
|
||||
|
||||
let write_variant = u.variants.iter().map(|v| {
|
||||
let variantname = names.enum_variant(&v.name);
|
||||
let write_tag = quote! {
|
||||
location.cast().write(#tagname::#variantname)?;
|
||||
};
|
||||
if let Some(tref) = &v.tref {
|
||||
let varianttype = names.type_ref(tref, lifetime.clone());
|
||||
quote! {
|
||||
#ident::#variantname(contents) => {
|
||||
#write_tag
|
||||
let variant_ptr = location.cast::<u8>().add(#contents_offset)?;
|
||||
<#varianttype as wiggle_runtime::GuestType>::write(&variant_ptr.cast(), contents)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#ident::#variantname => {
|
||||
#write_tag
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let (enum_lifetime, extra_derive) = if u.needs_lifetime() {
|
||||
(quote!(<'a>), quote!())
|
||||
} else {
|
||||
(quote!(), quote!(, Copy, PartialEq))
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Debug #extra_derive)]
|
||||
pub enum #ident #enum_lifetime {
|
||||
#(#variants),*
|
||||
}
|
||||
|
||||
impl<'a> wiggle_runtime::GuestType<'a> for #ident #enum_lifetime {
|
||||
fn guest_size() -> u32 {
|
||||
#size
|
||||
}
|
||||
|
||||
fn guest_align() -> usize {
|
||||
#align
|
||||
}
|
||||
|
||||
fn read(location: &wiggle_runtime::GuestPtr<'a, Self>)
|
||||
-> Result<Self, wiggle_runtime::GuestError>
|
||||
{
|
||||
let tag = location.cast().read()?;
|
||||
match tag {
|
||||
#(#read_variant)*
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn write(location: &wiggle_runtime::GuestPtr<'_, Self>, val: Self)
|
||||
-> Result<(), wiggle_runtime::GuestError>
|
||||
{
|
||||
match val {
|
||||
#(#write_variant)*
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
crates/wiggle/crates/runtime/.gitignore
vendored
Normal file
2
crates/wiggle/crates/runtime/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
target
|
||||
Cargo.lock
|
||||
8
crates/wiggle/crates/runtime/Cargo.toml
Normal file
8
crates/wiggle/crates/runtime/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "wiggle-runtime"
|
||||
version = "0.1.0"
|
||||
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkon@jakubkonka.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1"
|
||||
126
crates/wiggle/crates/runtime/src/borrow.rs
Normal file
126
crates/wiggle/crates/runtime/src/borrow.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
use crate::region::Region;
|
||||
use crate::{GuestError, GuestPtr, GuestType};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GuestBorrows {
|
||||
borrows: Vec<Region>,
|
||||
}
|
||||
|
||||
impl GuestBorrows {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
borrows: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
!self.borrows.iter().all(|b| !b.overlaps(r))
|
||||
}
|
||||
|
||||
pub(crate) fn borrow(&mut self, r: Region) -> Result<(), GuestError> {
|
||||
if self.is_borrowed(r) {
|
||||
Err(GuestError::PtrBorrowed(r))
|
||||
} else {
|
||||
self.borrows.push(r);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the region of memory pointed to by a `GuestPtr`. This is required for safety if
|
||||
/// you are dereferencing `GuestPtr`s while holding a reference to a slice via
|
||||
/// `GuestPtr::as_raw`.
|
||||
pub fn borrow_pointee<'a, T>(&mut self, p: &GuestPtr<'a, T>) -> Result<(), GuestError>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
self.borrow(Region {
|
||||
start: p.offset(),
|
||||
len: T::guest_size(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Borrow the slice of memory pointed to by a `GuestPtr<[T]>`. This is required for safety if
|
||||
/// you are dereferencing the `GuestPtr`s while holding a reference to another slice via
|
||||
/// `GuestPtr::as_raw`. Not required if using `GuestPtr::as_raw` on this pointer.
|
||||
pub fn borrow_slice<'a, T>(&mut self, p: &GuestPtr<'a, [T]>) -> Result<(), GuestError>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
let (start, elems) = p.offset();
|
||||
let len = T::guest_size()
|
||||
.checked_mul(elems)
|
||||
.ok_or_else(|| GuestError::PtrOverflow)?;
|
||||
self.borrow(Region { start, len })
|
||||
}
|
||||
|
||||
/// Borrow the slice of memory pointed to by a `GuestPtr<str>`. This is required for safety if
|
||||
/// you are dereferencing the `GuestPtr`s while holding a reference to another slice via
|
||||
/// `GuestPtr::as_raw`. Not required if using `GuestPtr::as_raw` on this pointer.
|
||||
pub fn borrow_str(&mut self, p: &GuestPtr<str>) -> Result<(), GuestError> {
|
||||
let (start, len) = p.offset();
|
||||
self.borrow(Region { start, len })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn nonoverlapping() {
|
||||
let mut bs = GuestBorrows::new();
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(10, 10);
|
||||
assert!(!r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
bs.borrow(r2).expect("can borrow r2");
|
||||
|
||||
let mut bs = GuestBorrows::new();
|
||||
let r1 = Region::new(10, 10);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(!r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
bs.borrow(r2).expect("can borrow r2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlapping() {
|
||||
let mut bs = GuestBorrows::new();
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(9, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||
|
||||
let mut bs = GuestBorrows::new();
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(2, 5);
|
||||
assert!(r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||
|
||||
let mut bs = GuestBorrows::new();
|
||||
let r1 = Region::new(9, 10);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||
|
||||
let mut bs = GuestBorrows::new();
|
||||
let r1 = Region::new(2, 5);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||
|
||||
let mut bs = GuestBorrows::new();
|
||||
let r1 = Region::new(2, 5);
|
||||
let r2 = Region::new(10, 5);
|
||||
let r3 = Region::new(15, 5);
|
||||
let r4 = Region::new(0, 10);
|
||||
assert!(r1.overlaps(r4));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
bs.borrow(r2).expect("can borrow r2");
|
||||
bs.borrow(r3).expect("can borrow r3");
|
||||
assert!(bs.borrow(r4).is_err(), "cant borrow r4");
|
||||
}
|
||||
}
|
||||
36
crates/wiggle/crates/runtime/src/error.rs
Normal file
36
crates/wiggle/crates/runtime/src/error.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::Region;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum GuestError {
|
||||
#[error("Invalid flag value {0}")]
|
||||
InvalidFlagValue(&'static str),
|
||||
#[error("Invalid enum value {0}")]
|
||||
InvalidEnumValue(&'static str),
|
||||
#[error("Pointer overflow")]
|
||||
PtrOverflow,
|
||||
#[error("Pointer out of bounds: {0:?}")]
|
||||
PtrOutOfBounds(Region),
|
||||
#[error("Pointer not aligned to {1}: {0:?}")]
|
||||
PtrNotAligned(Region, u32),
|
||||
#[error("Pointer already borrowed: {0:?}")]
|
||||
PtrBorrowed(Region),
|
||||
#[error("In func {funcname}:{location}:")]
|
||||
InFunc {
|
||||
funcname: &'static str,
|
||||
location: &'static str,
|
||||
#[source]
|
||||
err: Box<GuestError>,
|
||||
},
|
||||
#[error("In data {typename}.{field}:")]
|
||||
InDataField {
|
||||
typename: String,
|
||||
field: String,
|
||||
#[source]
|
||||
err: Box<GuestError>,
|
||||
},
|
||||
#[error("Invalid UTF-8 encountered: {0:?}")]
|
||||
InvalidUtf8(#[from] ::std::str::Utf8Error),
|
||||
#[error("Int conversion error: {0:?}")]
|
||||
TryFromIntError(#[from] ::std::num::TryFromIntError),
|
||||
}
|
||||
136
crates/wiggle/crates/runtime/src/guest_type.rs
Normal file
136
crates/wiggle/crates/runtime/src/guest_type.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use crate::{GuestError, GuestPtr};
|
||||
use std::mem;
|
||||
|
||||
pub trait GuestErrorType<'a> {
|
||||
type Context;
|
||||
fn success() -> Self;
|
||||
fn from_error(e: GuestError, ctx: &Self::Context) -> Self;
|
||||
}
|
||||
|
||||
/// A trait for types that are intended to be pointees in `GuestPtr<T>`.
|
||||
///
|
||||
/// This trait abstracts how to read/write information from the guest memory, as
|
||||
/// well as how to offset elements in an array of guest memory. This layer of
|
||||
/// abstraction allows the guest representation of a type to be different from
|
||||
/// the host representation of a type, if necessary. It also allows for
|
||||
/// validation when reading/writing.
|
||||
pub trait GuestType<'a>: Sized {
|
||||
/// Returns the size, in bytes, of this type in the guest memory.
|
||||
fn guest_size() -> u32;
|
||||
|
||||
/// Returns the required alignment of this type, in bytes, for both guest
|
||||
/// and host memory.
|
||||
fn guest_align() -> usize;
|
||||
|
||||
/// Reads this value from the provided `ptr`.
|
||||
///
|
||||
/// Must internally perform any safety checks necessary and is allowed to
|
||||
/// fail if the bytes pointed to are also invalid.
|
||||
///
|
||||
/// Typically if you're implementing this by hand you'll want to delegate to
|
||||
/// other safe implementations of this trait (e.g. for primitive types like
|
||||
/// `u32`) rather than writing lots of raw code yourself.
|
||||
fn read(ptr: &GuestPtr<'a, Self>) -> Result<Self, GuestError>;
|
||||
|
||||
/// Writes a value to `ptr` after verifying that `ptr` is indeed valid to
|
||||
/// store `val`.
|
||||
///
|
||||
/// Similar to `read`, you'll probably want to implement this in terms of
|
||||
/// other primitives.
|
||||
fn write(ptr: &GuestPtr<'_, Self>, val: Self) -> Result<(), GuestError>;
|
||||
}
|
||||
|
||||
/// A trait for `GuestType`s that have the same representation in guest memory
|
||||
/// as in Rust. These types can be used with the `GuestPtr::as_raw` method to
|
||||
/// view as a slice.
|
||||
///
|
||||
/// Unsafe trait because a correct GuestTypeTransparent implemengation ensures that the
|
||||
/// GuestPtr::as_raw methods are safe. This trait should only ever be implemented
|
||||
/// by wiggle_generate-produced code.
|
||||
pub unsafe trait GuestTypeTransparent<'a>: GuestType<'a> {
|
||||
/// Checks that the memory at `ptr` is a valid representation of `Self`.
|
||||
///
|
||||
/// Assumes that memory safety checks have already been performed: `ptr`
|
||||
/// has been checked to be aligned correctly and reside in memory using
|
||||
/// `GuestMemory::validate_size_align`
|
||||
fn validate(ptr: *mut Self) -> Result<(), GuestError>;
|
||||
}
|
||||
|
||||
macro_rules! primitives {
|
||||
($($i:ident)*) => ($(
|
||||
impl<'a> GuestType<'a> for $i {
|
||||
fn guest_size() -> u32 { mem::size_of::<Self>() as u32 }
|
||||
fn guest_align() -> usize { mem::align_of::<Self>() }
|
||||
|
||||
#[inline]
|
||||
fn read(ptr: &GuestPtr<'a, Self>) -> Result<Self, GuestError> {
|
||||
// Any bit pattern for any primitive implemented with this
|
||||
// macro is safe, so our `validate_size_align` method will
|
||||
// guarantee that if we are given a pointer it's valid for the
|
||||
// size of our type as well as properly aligned. Consequently we
|
||||
// should be able to safely ready the pointer just after we
|
||||
// validated it, returning it along here.
|
||||
let host_ptr = ptr.mem().validate_size_align(
|
||||
ptr.offset(),
|
||||
Self::guest_align(),
|
||||
Self::guest_size(),
|
||||
)?;
|
||||
Ok(unsafe { *host_ptr.cast::<Self>() })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write(ptr: &GuestPtr<'_, Self>, val: Self) -> Result<(), GuestError> {
|
||||
let host_ptr = ptr.mem().validate_size_align(
|
||||
ptr.offset(),
|
||||
Self::guest_align(),
|
||||
Self::guest_size(),
|
||||
)?;
|
||||
// Similar to above `as_raw` will do a lot of validation, and
|
||||
// then afterwards we can safely write our value into the
|
||||
// memory location.
|
||||
unsafe {
|
||||
*host_ptr.cast::<Self>() = val;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> GuestTypeTransparent<'a> for $i {
|
||||
#[inline]
|
||||
fn validate(_ptr: *mut $i) -> Result<(), GuestError> {
|
||||
// All bit patterns are safe, nothing to do here
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
)*)
|
||||
}
|
||||
|
||||
primitives! {
|
||||
// signed
|
||||
i8 i16 i32 i64 i128 isize
|
||||
// unsigned
|
||||
u8 u16 u32 u64 u128 usize
|
||||
// floats
|
||||
f32 f64
|
||||
}
|
||||
|
||||
// Support pointers-to-pointers where pointers are always 32-bits in wasm land
|
||||
impl<'a, T> GuestType<'a> for GuestPtr<'a, T> {
|
||||
fn guest_size() -> u32 {
|
||||
u32::guest_size()
|
||||
}
|
||||
|
||||
fn guest_align() -> usize {
|
||||
u32::guest_align()
|
||||
}
|
||||
|
||||
fn read(ptr: &GuestPtr<'a, Self>) -> Result<Self, GuestError> {
|
||||
let offset = ptr.cast::<u32>().read()?;
|
||||
Ok(GuestPtr::new(ptr.mem(), offset))
|
||||
}
|
||||
|
||||
fn write(ptr: &GuestPtr<'_, Self>, val: Self) -> Result<(), GuestError> {
|
||||
ptr.cast::<u32>().write(val.offset())
|
||||
}
|
||||
}
|
||||
547
crates/wiggle/crates/runtime/src/lib.rs
Normal file
547
crates/wiggle/crates/runtime/src/lib.rs
Normal file
@@ -0,0 +1,547 @@
|
||||
use std::cell::Cell;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::rc::Rc;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod borrow;
|
||||
mod error;
|
||||
mod guest_type;
|
||||
mod region;
|
||||
|
||||
pub use borrow::GuestBorrows;
|
||||
pub use error::GuestError;
|
||||
pub use guest_type::{GuestErrorType, GuestType, GuestTypeTransparent};
|
||||
pub use region::Region;
|
||||
|
||||
/// A trait which abstracts how to get at the region of host memory taht
|
||||
/// contains guest memory.
|
||||
///
|
||||
/// All `GuestPtr` types will contain a handle to this trait, signifying where
|
||||
/// the pointer is actually pointing into. This type will need to be implemented
|
||||
/// for the host's memory storage object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Safety around this type is tricky, and the trait is `unsafe` since there are
|
||||
/// a few contracts you need to uphold to implement this type correctly and have
|
||||
/// everything else in this crate work out safely.
|
||||
///
|
||||
/// The most important method of this trait is the `base` method. This returns,
|
||||
/// in host memory, a pointer and a length. The pointer should point to valid
|
||||
/// memory for the guest to read/write for the length contiguous bytes
|
||||
/// afterwards.
|
||||
///
|
||||
/// The region returned by `base` must not only be valid, however, but it must
|
||||
/// be valid for "a period of time before the guest is reentered". This isn't
|
||||
/// exactly well defined but the general idea is that `GuestMemory` is allowed
|
||||
/// to change under our feet to accomodate instructions like `memory.grow` or
|
||||
/// other guest modifications. Memory, however, cannot be changed if the guest
|
||||
/// is not reentered or if no explicitly action is taken to modify the guest
|
||||
/// memory.
|
||||
///
|
||||
/// This provides the guarantee that host pointers based on the return value of
|
||||
/// `base` have a dynamic period for which they are valid. This time duration
|
||||
/// must be "somehow nonzero in length" to allow users of `GuestMemory` and
|
||||
/// `GuestPtr` to safely read and write interior data.
|
||||
///
|
||||
/// # Using Raw Pointers
|
||||
///
|
||||
/// Methods like [`GuestMemory::base`] or [`GuestPtr::as_raw`] will return raw
|
||||
/// pointers to use. Returning raw pointers is significant because it shows
|
||||
/// there are hazards with using the returned pointers, and they can't blanket
|
||||
/// be used in a safe fashion. It is possible to use these pointers safely, but
|
||||
/// any usage needs to uphold a few guarantees.
|
||||
///
|
||||
/// * Whenever a `*mut T` is accessed or modified, it must be guaranteed that
|
||||
/// since the pointer was originally obtained the guest memory wasn't
|
||||
/// relocated in any way. This means you can't call back into the guest, call
|
||||
/// other arbitrary functions which might call into the guest, etc. The
|
||||
/// problem here is that the guest could execute instructions like
|
||||
/// `memory.grow` which would invalidate the raw pointer. If, however, after
|
||||
/// you acquire `*mut T` you only execute your own code and it doesn't touch
|
||||
/// the guest, then `*mut T` is still guaranteed to point to valid code.
|
||||
///
|
||||
/// * Furthermore, Rust's aliasing rules must still be upheld. For example you
|
||||
/// can't have two `&mut T` types that point to the area or overlap in any
|
||||
/// way. This in particular becomes an issue when you're dealing with multiple
|
||||
/// `GuestPtr` types. If you want to simultaneously work with them then you
|
||||
/// need to dynamically validate that you're either working with them all in a
|
||||
/// shared fashion (e.g. as if they were `&T`) or you must verify that they do
|
||||
/// not overlap to work with them as `&mut T`.
|
||||
///
|
||||
/// Note that safely using the raw pointers is relatively difficult. This crate
|
||||
/// strives to provide utilities to safely work with guest pointers so long as
|
||||
/// the previous guarantees are all upheld. If advanced operations are done with
|
||||
/// guest pointers it's recommended to be extremely cautious and thoroughly
|
||||
/// consider possible ramifications with respect to this API before codifying
|
||||
/// implementation details.
|
||||
pub unsafe trait GuestMemory {
|
||||
/// Returns the base allocation of this guest memory, located in host
|
||||
/// memory.
|
||||
///
|
||||
/// A pointer/length pair are returned to signify where the guest memory
|
||||
/// lives in the host, and how many contiguous bytes the memory is valid for
|
||||
/// after the returned pointer.
|
||||
///
|
||||
/// Note that there are safety guarantees about this method that
|
||||
/// implementations must uphold, and for more details see the
|
||||
/// [`GuestMemory`] documentation.
|
||||
fn base(&self) -> (*mut u8, u32);
|
||||
|
||||
/// Validates a guest-relative pointer given various attributes, and returns
|
||||
/// the corresponding host pointer.
|
||||
///
|
||||
/// * `offset` - this is the guest-relative pointer, an offset from the
|
||||
/// base.
|
||||
/// * `align` - this is the desired alignment of the guest pointer, and if
|
||||
/// successful the host pointer will be guaranteed to have this alignment.
|
||||
/// * `len` - this is the number of bytes, after `offset`, that the returned
|
||||
/// pointer must be valid for.
|
||||
///
|
||||
/// This function will guarantee that the returned pointer is in-bounds of
|
||||
/// `base`, *at this time*, for `len` bytes and has alignment `align`. If
|
||||
/// any guarantees are not upheld then an error will be returned.
|
||||
///
|
||||
/// Note that the returned pointer is an unsafe pointer. This is not safe to
|
||||
/// use in general because guest memory can be relocated. Additionally the
|
||||
/// guest may be modifying/reading memory as well. Consult the
|
||||
/// [`GuestMemory`] documentation for safety information about using this
|
||||
/// returned pointer.
|
||||
fn validate_size_align(
|
||||
&self,
|
||||
offset: u32,
|
||||
align: usize,
|
||||
len: u32,
|
||||
) -> Result<*mut u8, GuestError> {
|
||||
let (base_ptr, base_len) = self.base();
|
||||
let region = Region { start: offset, len };
|
||||
|
||||
// Figure out our pointer to the start of memory
|
||||
let start = match (base_ptr as usize).checked_add(offset as usize) {
|
||||
Some(ptr) => ptr,
|
||||
None => return Err(GuestError::PtrOverflow),
|
||||
};
|
||||
// and use that to figure out the end pointer
|
||||
let end = match start.checked_add(len as usize) {
|
||||
Some(ptr) => ptr,
|
||||
None => return Err(GuestError::PtrOverflow),
|
||||
};
|
||||
// and then verify that our end doesn't reach past the end of our memory
|
||||
if end > (base_ptr as usize) + (base_len as usize) {
|
||||
return Err(GuestError::PtrOutOfBounds(region));
|
||||
}
|
||||
// and finally verify that the alignment is correct
|
||||
if start % align != 0 {
|
||||
return Err(GuestError::PtrNotAligned(region, align as u32));
|
||||
}
|
||||
Ok(start as *mut u8)
|
||||
}
|
||||
|
||||
/// Convenience method for creating a `GuestPtr` at a particular offset.
|
||||
///
|
||||
/// Note that `T` can be almost any type, and typically `offset` is a `u32`.
|
||||
/// The exception is slices and strings, in which case `offset` is a `(u32,
|
||||
/// u32)` of `(offset, length)`.
|
||||
fn ptr<'a, T>(&'a self, offset: T::Pointer) -> GuestPtr<'a, T>
|
||||
where
|
||||
Self: Sized,
|
||||
T: ?Sized + Pointee,
|
||||
{
|
||||
GuestPtr::new(self, offset)
|
||||
}
|
||||
}
|
||||
|
||||
// Forwarding trait implementations to the original type
|
||||
|
||||
unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a T {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a mut T {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Box<T> {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Rc<T> {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Arc<T> {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A *guest* pointer into host memory.
|
||||
///
|
||||
/// This type represents a pointer from the guest that points into host memory.
|
||||
/// Internally a `GuestPtr` contains a handle to its original [`GuestMemory`] as
|
||||
/// well as the offset into the memory that the pointer is pointing at.
|
||||
///
|
||||
/// Presence of a [`GuestPtr`] does not imply any form of validity. Pointers can
|
||||
/// be out-of-bounds, misaligned, etc. It is safe to construct a `GuestPtr` with
|
||||
/// any offset at any time. Consider a `GuestPtr<T>` roughly equivalent to `*mut
|
||||
/// T`, although there are a few more safety guarantees around this type.
|
||||
///
|
||||
/// ## Slices and Strings
|
||||
///
|
||||
/// Note that the type parameter does not need to implement the `Sized` trait,
|
||||
/// so you can implement types such as this:
|
||||
///
|
||||
/// * `GuestPtr<'_, str>` - a pointer to a guest string
|
||||
/// * `GuestPtr<'_, [T]>` - a pointer to a guest array
|
||||
///
|
||||
/// Unsized types such as this may have extra methods and won't have methods
|
||||
/// like [`GuestPtr::read`] or [`GuestPtr::write`].
|
||||
///
|
||||
/// ## Type parameter and pointee
|
||||
///
|
||||
/// The `T` type parameter is largely intended for more static safety in Rust as
|
||||
/// well as having a better handle on what we're pointing to. A `GuestPtr<T>`,
|
||||
/// however, does not necessarily literally imply a guest pointer pointing to
|
||||
/// type `T`. Instead the [`GuestType`] trait is a layer of abstraction where
|
||||
/// `GuestPtr<T>` may actually be a pointer to `U` in guest memory, but you can
|
||||
/// construct a `T` from a `U`.
|
||||
///
|
||||
/// For example `GuestPtr<GuestPtr<T>>` is a valid type, but this is actually
|
||||
/// more equivalent to `GuestPtr<u32>` because guest pointers are always
|
||||
/// 32-bits. That being said you can create a `GuestPtr<T>` from a `u32`.
|
||||
///
|
||||
/// Additionally `GuestPtr<MyEnum>` will actually delegate, typically, to and
|
||||
/// implementation which loads the underlying data as `GuestPtr<u8>` (or
|
||||
/// similar) and then the bytes loaded are validated to fit within the
|
||||
/// definition of `MyEnum` before `MyEnum` is returned.
|
||||
///
|
||||
/// For more information see the [`GuestPtr::read`] and [`GuestPtr::write`]
|
||||
/// methods. In general though be extremely careful about writing `unsafe` code
|
||||
/// when working with a `GuestPtr` if you're not using one of the
|
||||
/// already-attached helper methods.
|
||||
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> {
|
||||
/// Creates a new `GuestPtr` from the given `mem` and `pointer` values.
|
||||
///
|
||||
/// Note that for sized types like `u32`, `GuestPtr<T>`, etc, the `pointer`
|
||||
/// vlue 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<'_, T> {
|
||||
GuestPtr {
|
||||
mem,
|
||||
pointer,
|
||||
_marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the offset of this pointer in guest memory.
|
||||
///
|
||||
/// Note that for sized types this returns a `u32`, but for slices and
|
||||
/// strings it returns a `(u32, u32)` pointer/length pair.
|
||||
pub fn offset(&self) -> T::Pointer {
|
||||
self.pointer
|
||||
}
|
||||
|
||||
/// Returns the guest memory that this pointer is coming from.
|
||||
pub fn mem(&self) -> &'a (dyn GuestMemory + 'a) {
|
||||
self.mem
|
||||
}
|
||||
|
||||
/// Casts this `GuestPtr` type to a different type.
|
||||
///
|
||||
/// This is a safe method which is useful for simply reinterpreting the type
|
||||
/// parameter on this `GuestPtr`. Note that this is a safe method, where
|
||||
/// again there's no guarantees about alignment, validity, in-bounds-ness,
|
||||
/// etc of the returned pointer.
|
||||
pub fn cast<U>(&self) -> GuestPtr<'a, U>
|
||||
where
|
||||
T: Pointee<Pointer = u32>,
|
||||
{
|
||||
GuestPtr::new(self.mem, self.pointer)
|
||||
}
|
||||
|
||||
/// Safely read a value from this pointer.
|
||||
///
|
||||
/// This is a fun method, and is one of the lynchpins of this
|
||||
/// implementation. The highlight here is that this is a *safe* operation,
|
||||
/// not an unsafe one like `*mut T`. This works for a few reasons:
|
||||
///
|
||||
/// * The `unsafe` contract of the `GuestMemory` trait means that there's
|
||||
/// always at least some backing memory for this `GuestPtr<T>`.
|
||||
///
|
||||
/// * This does not use Rust-intrinsics to read the type `T`, but rather it
|
||||
/// delegates to `T`'s implementation of [`GuestType`] to actually read
|
||||
/// the underlying data. This again is a safe method, so any unsafety, if
|
||||
/// any, must be internally documented.
|
||||
///
|
||||
/// * Eventually what typically happens it that this bottoms out in the read
|
||||
/// implementations for primitives types (like `i32`) which can safely be
|
||||
/// read at any time, and then it's up to the runtime to determine what to
|
||||
/// do with the bytes it read in a safe manner.
|
||||
///
|
||||
/// Naturally lots of things can still go wrong, such as out-of-bounds
|
||||
/// checks, alignment checks, validity checks (e.g. for enums), etc. All of
|
||||
/// these check failures, however, are returned as a [`GuestError`] in the
|
||||
/// `Result` here, and `Ok` is only returned if all the checks passed.
|
||||
pub fn read(&self) -> Result<T, GuestError>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
T::read(self)
|
||||
}
|
||||
|
||||
/// Safely write a value to this pointer.
|
||||
///
|
||||
/// This method, like [`GuestPtr::read`], is pretty crucial for the safe
|
||||
/// operation of this crate. All the same reasons apply though for why this
|
||||
/// method is safe, even eventually bottoming out in primitives like writing
|
||||
/// an `i32` which is safe to write bit patterns into memory at any time due
|
||||
/// to the guarantees of [`GuestMemory`].
|
||||
///
|
||||
/// Like `read`, `write` can fail due to any manner of pointer checks, but
|
||||
/// any failure is returned as a [`GuestError`].
|
||||
pub fn write(&self, val: T) -> Result<(), GuestError>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
T::write(self, val)
|
||||
}
|
||||
|
||||
/// Performs pointer arithmetic on this pointer, moving the pointer forward
|
||||
/// `amt` slots.
|
||||
///
|
||||
/// This will either return the resulting pointer or `Err` if the pointer
|
||||
/// arithmetic calculation would overflow around the end of the address
|
||||
/// space.
|
||||
pub fn add(&self, amt: u32) -> Result<GuestPtr<'a, T>, GuestError>
|
||||
where
|
||||
T: GuestType<'a> + Pointee<Pointer = u32>,
|
||||
{
|
||||
let offset = amt
|
||||
.checked_mul(T::guest_size())
|
||||
.and_then(|o| self.pointer.checked_add(o));
|
||||
let offset = match offset {
|
||||
Some(o) => o,
|
||||
None => return Err(GuestError::PtrOverflow),
|
||||
};
|
||||
Ok(GuestPtr::new(self.mem, offset))
|
||||
}
|
||||
|
||||
/// Returns a `GuestPtr` for an array of `T`s using this pointer as the
|
||||
/// base.
|
||||
pub fn as_array(&self, elems: u32) -> GuestPtr<'a, [T]>
|
||||
where
|
||||
T: GuestType<'a> + Pointee<Pointer = u32>,
|
||||
{
|
||||
GuestPtr::new(self.mem, (self.pointer, elems))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestPtr<'a, [T]> {
|
||||
/// For slices, specifically returns the relative pointer to the base of the
|
||||
/// array.
|
||||
///
|
||||
/// This is similar to `<[T]>::as_ptr()`
|
||||
pub fn offset_base(&self) -> u32 {
|
||||
self.pointer.0
|
||||
}
|
||||
|
||||
/// For slices, returns the length of the slice, in units.
|
||||
pub fn len(&self) -> u32 {
|
||||
self.pointer.1
|
||||
}
|
||||
|
||||
/// Returns an iterator over interior pointers.
|
||||
///
|
||||
/// Each item is a `Result` indicating whether it overflowed past the end of
|
||||
/// the address space or not.
|
||||
pub fn iter<'b>(
|
||||
&'b self,
|
||||
) -> impl ExactSizeIterator<Item = Result<GuestPtr<'a, T>, GuestError>> + 'b
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
let base = self.as_ptr();
|
||||
(0..self.len()).map(move |i| base.add(i))
|
||||
}
|
||||
|
||||
/// Attempts to read a raw `*mut [T]` pointer from this pointer, performing
|
||||
/// bounds checks and type validation.
|
||||
/// The resulting `*mut [T]` can be used as a `&mut [t]` as long as the
|
||||
/// reference is dropped before any Wasm code is re-entered.
|
||||
///
|
||||
/// This function will return a raw pointer into host memory if all checks
|
||||
/// succeed (valid utf-8, valid pointers, etc). If any checks fail then
|
||||
/// `GuestError` will be returned.
|
||||
///
|
||||
/// Note that the `*mut [T]` pointer is still unsafe to use in general, but
|
||||
/// there are specific situations that it is safe to use. For more
|
||||
/// information about using the raw pointer, consult the [`GuestMemory`]
|
||||
/// trait documentation.
|
||||
///
|
||||
/// For safety against overlapping mutable borrows, the user must use the
|
||||
/// same `GuestBorrows` to create all *mut str or *mut [T] that are alive
|
||||
/// at the same time.
|
||||
pub fn as_raw(&self, bc: &mut GuestBorrows) -> Result<*mut [T], GuestError>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
let len = match self.pointer.1.checked_mul(T::guest_size()) {
|
||||
Some(l) => l,
|
||||
None => return Err(GuestError::PtrOverflow),
|
||||
};
|
||||
let ptr =
|
||||
self.mem
|
||||
.validate_size_align(self.pointer.0, T::guest_align(), len)? as *mut T;
|
||||
|
||||
bc.borrow(Region {
|
||||
start: self.pointer.0,
|
||||
len,
|
||||
})?;
|
||||
|
||||
// Validate all elements in slice.
|
||||
// SAFETY: ptr has been validated by self.mem.validate_size_align
|
||||
for offs in 0..self.pointer.1 {
|
||||
T::validate(unsafe { ptr.add(offs as usize) })?;
|
||||
}
|
||||
|
||||
// SAFETY: iff there are no overlapping borrows (all uses of as_raw use this same
|
||||
// GuestBorrows), its valid to construct a *mut [T]
|
||||
unsafe {
|
||||
let s = slice::from_raw_parts_mut(ptr, self.pointer.1 as usize);
|
||||
Ok(s as *mut [T])
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `GuestPtr` pointing to the base of the array for the interior
|
||||
/// type `T`.
|
||||
pub fn as_ptr(&self) -> GuestPtr<'a, T> {
|
||||
GuestPtr::new(self.mem, self.offset_base())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GuestPtr<'a, str> {
|
||||
/// For strings, returns the relative pointer to the base of the string
|
||||
/// allocation.
|
||||
pub fn offset_base(&self) -> u32 {
|
||||
self.pointer.0
|
||||
}
|
||||
|
||||
/// Returns the length, in bytes, of th estring.
|
||||
pub fn len(&self) -> u32 {
|
||||
self.pointer.1
|
||||
}
|
||||
|
||||
/// Returns a raw pointer for the underlying slice of bytes that this
|
||||
/// pointer points to.
|
||||
pub fn as_bytes(&self) -> GuestPtr<'a, [u8]> {
|
||||
GuestPtr::new(self.mem, self.pointer)
|
||||
}
|
||||
|
||||
/// Attempts to read a raw `*mut str` pointer from this pointer, performing
|
||||
/// bounds checks and utf-8 checks.
|
||||
/// The resulting `*mut str` can be used as a `&mut str` as long as the
|
||||
/// reference is dropped before any Wasm code is re-entered.
|
||||
///
|
||||
/// This function will return a raw pointer into host memory if all checks
|
||||
/// succeed (valid utf-8, valid pointers, etc). If any checks fail then
|
||||
/// `GuestError` will be returned.
|
||||
///
|
||||
/// Note that the `*mut str` pointer is still unsafe to use in general, but
|
||||
/// there are specific situations that it is safe to use. For more
|
||||
/// information about using the raw pointer, consult the [`GuestMemory`]
|
||||
/// trait documentation.
|
||||
///
|
||||
/// For safety against overlapping mutable borrows, the user must use the
|
||||
/// same `GuestBorrows` to create all *mut str or *mut [T] that are alive
|
||||
/// at the same time.
|
||||
pub fn as_raw(&self, bc: &mut GuestBorrows) -> Result<*mut str, GuestError> {
|
||||
let ptr = self
|
||||
.mem
|
||||
.validate_size_align(self.pointer.0, 1, self.pointer.1)?;
|
||||
|
||||
bc.borrow(Region {
|
||||
start: self.pointer.0,
|
||||
len: self.pointer.1,
|
||||
})?;
|
||||
|
||||
// SAFETY: iff there are no overlapping borrows (all uses of as_raw use this same
|
||||
// GuestBorrows), its valid to construct a *mut str
|
||||
unsafe {
|
||||
let s = slice::from_raw_parts_mut(ptr, self.pointer.1 as usize);
|
||||
match str::from_utf8_mut(s) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(GuestError::InvalidUtf8(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Pointee> Clone for GuestPtr<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Pointee> Copy for GuestPtr<'_, T> {}
|
||||
|
||||
impl<T: ?Sized + Pointee> fmt::Debug for GuestPtr<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
T::debug(self.pointer, f)
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
impl<T> Sealed for T {}
|
||||
impl<T> Sealed for [T] {}
|
||||
impl Sealed for str {}
|
||||
}
|
||||
|
||||
/// Types that can be pointed to by `GuestPtr<T>`.
|
||||
///
|
||||
/// In essence everything can, and the only special-case is unsized types like
|
||||
/// `str` and `[T]` which have special implementations.
|
||||
pub trait Pointee: private::Sealed {
|
||||
#[doc(hidden)]
|
||||
type Pointer: Copy;
|
||||
#[doc(hidden)]
|
||||
fn debug(pointer: Self::Pointer, f: &mut fmt::Formatter) -> fmt::Result;
|
||||
}
|
||||
|
||||
impl<T> Pointee for T {
|
||||
type Pointer = u32;
|
||||
fn debug(pointer: Self::Pointer, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "*guest {:#x}", pointer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Pointee for [T] {
|
||||
type Pointer = (u32, u32);
|
||||
fn debug(pointer: Self::Pointer, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "*guest {:#x}/{}", pointer.0, pointer.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pointee for str {
|
||||
type Pointer = (u32, u32);
|
||||
fn debug(pointer: Self::Pointer, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
<[u8]>::debug(pointer, f)
|
||||
}
|
||||
}
|
||||
71
crates/wiggle/crates/runtime/src/region.rs
Normal file
71
crates/wiggle/crates/runtime/src/region.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
/// Represents a contiguous region in memory.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Region {
|
||||
pub start: u32,
|
||||
pub len: u32,
|
||||
}
|
||||
|
||||
impl Region {
|
||||
pub fn new(start: u32, len: u32) -> Self {
|
||||
assert!(len > 0, "Region cannot have 0 length");
|
||||
Self { start, len }
|
||||
}
|
||||
|
||||
/// Checks if this `Region` overlaps with `rhs` `Region`.
|
||||
pub fn overlaps(&self, rhs: Region) -> bool {
|
||||
let self_start = self.start as u64;
|
||||
let self_end = self_start + (self.len - 1) as u64;
|
||||
|
||||
let rhs_start = rhs.start as u64;
|
||||
let rhs_end = rhs_start + (rhs.len - 1) as u64;
|
||||
|
||||
if self_start <= rhs_start {
|
||||
self_end >= rhs_start
|
||||
} else {
|
||||
rhs_end >= self_start
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend(&self, times: u32) -> Self {
|
||||
let len = self.len * times;
|
||||
Self {
|
||||
start: self.start,
|
||||
len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn nonoverlapping() {
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(10, 10);
|
||||
assert!(!r1.overlaps(r2));
|
||||
|
||||
let r1 = Region::new(10, 10);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(!r1.overlaps(r2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlapping() {
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(9, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(2, 5);
|
||||
assert!(r1.overlaps(r2));
|
||||
|
||||
let r1 = Region::new(9, 10);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
|
||||
let r1 = Region::new(2, 5);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
}
|
||||
}
|
||||
2
crates/wiggle/crates/test/.gitignore
vendored
Normal file
2
crates/wiggle/crates/test/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
target
|
||||
Cargo.lock
|
||||
9
crates/wiggle/crates/test/Cargo.toml
Normal file
9
crates/wiggle/crates/test/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "wiggle-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkon@jakubkonka.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
wiggle-runtime = { path = "../runtime" }
|
||||
proptest = "0.9"
|
||||
329
crates/wiggle/crates/test/src/lib.rs
Normal file
329
crates/wiggle/crates/test/src/lib.rs
Normal file
@@ -0,0 +1,329 @@
|
||||
use proptest::prelude::*;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker;
|
||||
use wiggle_runtime::GuestMemory;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemAreas(Vec<MemArea>);
|
||||
impl MemAreas {
|
||||
pub fn new() -> Self {
|
||||
MemAreas(Vec::new())
|
||||
}
|
||||
pub fn insert(&mut self, a: MemArea) {
|
||||
// Find if `a` is already in the vector
|
||||
match self.0.binary_search(&a) {
|
||||
// It is present - insert it next to existing one
|
||||
Ok(loc) => self.0.insert(loc, a),
|
||||
// It is not present - heres where to insert it
|
||||
Err(loc) => self.0.insert(loc, a),
|
||||
}
|
||||
}
|
||||
pub fn iter(&self) -> impl Iterator<Item = &MemArea> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> From<R> for MemAreas
|
||||
where
|
||||
R: AsRef<[MemArea]>,
|
||||
{
|
||||
fn from(ms: R) -> MemAreas {
|
||||
let mut out = MemAreas::new();
|
||||
for m in ms.as_ref().into_iter() {
|
||||
out.insert(*m);
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<MemArea>> for MemAreas {
|
||||
fn into(self) -> Vec<MemArea> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(align(4096))]
|
||||
pub struct HostMemory {
|
||||
buffer: UnsafeCell<[u8; 4096]>,
|
||||
}
|
||||
impl HostMemory {
|
||||
pub fn new() -> Self {
|
||||
HostMemory {
|
||||
buffer: UnsafeCell::new([0; 4096]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mem_area_strat(align: u32) -> BoxedStrategy<MemArea> {
|
||||
prop::num::u32::ANY
|
||||
.prop_filter_map("needs to fit in memory", move |p| {
|
||||
let p_aligned = p - (p % align); // Align according to argument
|
||||
let ptr = p_aligned % 4096; // Put inside memory
|
||||
if ptr + align < 4096 {
|
||||
Some(MemArea { ptr, len: align })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
/// Takes a sorted list or memareas, and gives a sorted list of memareas covering
|
||||
/// the parts of memory not covered by the previous
|
||||
pub fn invert(regions: &MemAreas) -> MemAreas {
|
||||
let mut out = MemAreas::new();
|
||||
let mut start = 0;
|
||||
for r in regions.iter() {
|
||||
let len = r.ptr - start;
|
||||
if len > 0 {
|
||||
out.insert(MemArea {
|
||||
ptr: start,
|
||||
len: r.ptr - start,
|
||||
});
|
||||
}
|
||||
start = r.ptr + r.len;
|
||||
}
|
||||
if start < 4096 {
|
||||
out.insert(MemArea {
|
||||
ptr: start,
|
||||
len: 4096 - start,
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn byte_slice_strat(size: u32, exclude: &MemAreas) -> BoxedStrategy<MemArea> {
|
||||
let available: Vec<MemArea> = Self::invert(exclude)
|
||||
.iter()
|
||||
.flat_map(|a| a.inside(size))
|
||||
.collect();
|
||||
|
||||
Just(available)
|
||||
.prop_filter("available memory for allocation", |a| !a.is_empty())
|
||||
.prop_flat_map(|a| prop::sample::select(a))
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl GuestMemory for HostMemory {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
unsafe {
|
||||
let ptr = self.buffer.get();
|
||||
((*ptr).as_mut_ptr(), (*ptr).len() as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MemArea {
|
||||
pub ptr: u32,
|
||||
pub len: u32,
|
||||
}
|
||||
|
||||
impl MemArea {
|
||||
// This code is a whole lot like the Region::overlaps func thats at the core of the code under
|
||||
// test.
|
||||
// So, I implemented this one with std::ops::Range so it is less likely I wrote the same bug in two
|
||||
// places.
|
||||
pub fn overlapping(&self, b: Self) -> bool {
|
||||
// a_range is all elems in A
|
||||
let a_range = std::ops::Range {
|
||||
start: self.ptr,
|
||||
end: self.ptr + self.len, // std::ops::Range is open from the right
|
||||
};
|
||||
// b_range is all elems in B
|
||||
let b_range = std::ops::Range {
|
||||
start: b.ptr,
|
||||
end: b.ptr + b.len,
|
||||
};
|
||||
// No element in B is contained in A:
|
||||
for b_elem in b_range.clone() {
|
||||
if a_range.contains(&b_elem) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// No element in A is contained in B:
|
||||
for a_elem in a_range {
|
||||
if b_range.contains(&a_elem) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pub fn non_overlapping_set<M>(areas: M) -> bool
|
||||
where
|
||||
M: Into<MemAreas>,
|
||||
{
|
||||
let areas = areas.into();
|
||||
for (aix, a) in areas.iter().enumerate() {
|
||||
for (bix, b) in areas.iter().enumerate() {
|
||||
if aix != bix {
|
||||
// (A, B) is every pairing of areas
|
||||
if a.overlapping(*b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Enumerate all memareas of size `len` inside a given area
|
||||
fn inside(&self, len: u32) -> impl Iterator<Item = MemArea> {
|
||||
let end: i64 = self.len as i64 - len as i64;
|
||||
let start = self.ptr;
|
||||
(0..end).into_iter().map(move |v| MemArea {
|
||||
ptr: start + v as u32,
|
||||
len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn hostmemory_is_aligned() {
|
||||
let h = HostMemory::new();
|
||||
assert_eq!(h.base().0 as usize % 4096, 0);
|
||||
let h = Box::new(h);
|
||||
assert_eq!(h.base().0 as usize % 4096, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert() {
|
||||
fn invert_equality(input: &[MemArea], expected: &[MemArea]) {
|
||||
let input: MemAreas = input.into();
|
||||
let inverted: Vec<MemArea> = HostMemory::invert(&input).into();
|
||||
assert_eq!(expected, inverted.as_slice());
|
||||
}
|
||||
|
||||
invert_equality(&[], &[MemArea { ptr: 0, len: 4096 }]);
|
||||
invert_equality(
|
||||
&[MemArea { ptr: 0, len: 1 }],
|
||||
&[MemArea { ptr: 1, len: 4095 }],
|
||||
);
|
||||
|
||||
invert_equality(
|
||||
&[MemArea { ptr: 1, len: 1 }],
|
||||
&[MemArea { ptr: 0, len: 1 }, MemArea { ptr: 2, len: 4094 }],
|
||||
);
|
||||
|
||||
invert_equality(
|
||||
&[MemArea { ptr: 1, len: 4095 }],
|
||||
&[MemArea { ptr: 0, len: 1 }],
|
||||
);
|
||||
|
||||
invert_equality(
|
||||
&[MemArea { ptr: 0, len: 1 }, MemArea { ptr: 1, len: 4095 }],
|
||||
&[],
|
||||
);
|
||||
|
||||
invert_equality(
|
||||
&[MemArea { ptr: 1, len: 2 }, MemArea { ptr: 4, len: 1 }],
|
||||
&[
|
||||
MemArea { ptr: 0, len: 1 },
|
||||
MemArea { ptr: 3, len: 1 },
|
||||
MemArea { ptr: 5, len: 4091 },
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
fn set_of_slices_strat(
|
||||
s1: u32,
|
||||
s2: u32,
|
||||
s3: u32,
|
||||
) -> BoxedStrategy<(MemArea, MemArea, MemArea)> {
|
||||
HostMemory::byte_slice_strat(s1, &MemAreas::new())
|
||||
.prop_flat_map(move |a1| {
|
||||
(
|
||||
Just(a1),
|
||||
HostMemory::byte_slice_strat(s2, &MemAreas::from(&[a1])),
|
||||
)
|
||||
})
|
||||
.prop_flat_map(move |(a1, a2)| {
|
||||
(
|
||||
Just(a1),
|
||||
Just(a2),
|
||||
HostMemory::byte_slice_strat(s3, &MemAreas::from(&[a1, a2])),
|
||||
)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trivial_inside() {
|
||||
let a = MemArea { ptr: 24, len: 4072 };
|
||||
let interior = a.inside(24).collect::<Vec<_>>();
|
||||
|
||||
assert!(interior.len() > 0);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
// For some random region of decent size
|
||||
fn inside(r in HostMemory::mem_area_strat(123)) {
|
||||
let set_of_r = MemAreas::from(&[r]);
|
||||
// All regions outside of r:
|
||||
let exterior = HostMemory::invert(&set_of_r);
|
||||
// All regions inside of r:
|
||||
let interior = r.inside(22);
|
||||
for i in interior {
|
||||
// i overlaps with r:
|
||||
assert!(r.overlapping(i));
|
||||
// i is inside r:
|
||||
assert!(i.ptr >= r.ptr);
|
||||
assert!(r.ptr + r.len >= i.ptr + i.len);
|
||||
// the set of exterior and i is non-overlapping
|
||||
let mut all = exterior.clone();
|
||||
all.insert(i);
|
||||
assert!(MemArea::non_overlapping_set(all));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_slices((s1, s2, s3) in set_of_slices_strat(12, 34, 56)) {
|
||||
let all = MemAreas::from(&[s1, s2, s3]);
|
||||
assert!(MemArea::non_overlapping_set(all));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use std::cell::RefCell;
|
||||
use wiggle_runtime::GuestError;
|
||||
|
||||
// In lucet, our Ctx struct needs a lifetime, so we're using one
|
||||
// on the test as well.
|
||||
pub struct WasiCtx<'a> {
|
||||
pub guest_errors: RefCell<Vec<GuestError>>,
|
||||
lifetime: marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> WasiCtx<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
guest_errors: RefCell::new(vec![]),
|
||||
lifetime: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Errno is used as a first return value in the functions above, therefore
|
||||
// it must implement GuestErrorType with type Context = WasiCtx.
|
||||
// The context type should let you do logging or debugging or whatever you need
|
||||
// with these errors. We just push them to vecs.
|
||||
#[macro_export]
|
||||
macro_rules! impl_errno {
|
||||
( $errno:ty ) => {
|
||||
impl<'a> wiggle_runtime::GuestErrorType<'a> for $errno {
|
||||
type Context = WasiCtx<'a>;
|
||||
fn success() -> $errno {
|
||||
<$errno>::Ok
|
||||
}
|
||||
fn from_error(e: GuestError, ctx: &WasiCtx) -> $errno {
|
||||
eprintln!("GUEST ERROR: {:?}", e);
|
||||
ctx.guest_errors.borrow_mut().push(e);
|
||||
types::Errno::InvalidArg
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user