Add 'crates/wiggle/' from commit 'cd484e49932d8dd8f1bd1a002e0717ad8bff07fb'

git-subtree-dir: crates/wiggle
git-subtree-mainline: 2ead747f48
git-subtree-split: cd484e4993
This commit is contained in:
Jakub Konka
2020-03-11 17:30:49 +01:00
54 changed files with 6541 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
submodules: true
- name: Install Rust
run: |
rustup update stable
rustup default stable
rustup component add rustfmt
- name: Cargo fmt
run: cargo fmt --all -- --check
build:
name: Build
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
os: [ubuntu, macOS, windows]
steps:
- uses: actions/checkout@v1
with:
submodules: true
- name: Install Rust
shell: bash
run: |
rustup update stable
rustup default stable
- name: Build
run: cargo build --all --release -vv
test:
name: Test
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
os: [ubuntu, macOS, windows]
steps:
- uses: actions/checkout@v1
with:
submodules: true
- name: Install Rust
shell: bash
run: |
rustup update stable
rustup default stable
- name: Test
run: cargo test --all

4
crates/wiggle/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
proptest-regressions

26
crates/wiggle/Cargo.toml Normal file
View File

@@ -0,0 +1,26 @@
[package]
name = "wiggle"
version = "0.1.0"
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkonk@jakubkonka.com>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
wiggle-generate = { path = "crates/generate" }
witx = "0.8.3"
syn = { version = "1.0", features = ["full"] }
[dev-dependencies]
wiggle-runtime = { path = "crates/runtime" }
wiggle-test = { path = "crates/test" }
proptest = "0.9"
[workspace]
members = [
"crates/generate",
"crates/runtime",
"crates/test",
]
exclude = ["crates/WASI"]

221
crates/wiggle/LICENSE Normal file
View File

@@ -0,0 +1,221 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

6
crates/wiggle/README.md Normal file
View File

@@ -0,0 +1,6 @@
# wiggle
An experimental implementation of `bytecodealliance/wig` crate which
generates Rust bindings from `*.witx` that are meant to be more idiomatic
and hopefully allowing for easier polyfilling between different WASI
snapshot versions in the future.

View File

@@ -0,0 +1 @@
target

View 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"] }

View 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()?,
})
}
}

View 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(&param.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 = &param.tref;
let interface_typename = names.type_ref(&tref, anon_lifetime());
let try_into_conversion = {
let name = names.func_param(&param.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(&param.name);
let name = names.func_param(&param.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(&param.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(&param.name);
quote! {
let #name = #name as #interface_typename;
}
}
witx::BuiltinType::String => {
let lifetime = anon_lifetime();
let ptr_name = names.func_ptr_binding(&param.name);
let len_name = names.func_len_binding(&param.name);
let name = names.func_param(&param.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(&param.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(&param.name);
let len_name = names.func_len_binding(&param.name);
let name = names.func_param(&param.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(&param.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,
}
}

View 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)*
)
}

View 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!('_)
}

View 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)*
}
}
}

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

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

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

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

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

View 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),
}
}

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

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

View File

@@ -0,0 +1,2 @@
target
Cargo.lock

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

View 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");
}
}

View 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),
}

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

View 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)
}
}

View 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
View File

@@ -0,0 +1,2 @@
target
Cargo.lock

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

View 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
}
}
};
}

11
crates/wiggle/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use syn::parse_macro_input;
#[proc_macro]
pub fn from_witx(args: TokenStream) -> TokenStream {
let config = parse_macro_input!(args as wiggle_generate::Config);
let doc = witx::load(&config.witx.paths).expect("loading witx");
TokenStream::from(wiggle_generate::generate(&doc, &config))
}

View File

@@ -0,0 +1,210 @@
use proptest::prelude::*;
use wiggle_runtime::{GuestError, GuestMemory, GuestPtr};
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
wiggle::from_witx!({
witx: ["tests/arrays.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
impl<'a> arrays::Arrays for WasiCtx<'a> {
fn reduce_excuses(
&self,
excuses: &types::ConstExcuseArray,
) -> Result<types::Excuse, types::Errno> {
let last = &excuses
.iter()
.last()
.expect("input array is non-empty")
.expect("valid ptr to ptr")
.read()
.expect("valid ptr to some Excuse value");
Ok(last.read().expect("dereferencing ptr should succeed"))
}
fn populate_excuses(&self, excuses: &types::ExcuseArray) -> Result<(), types::Errno> {
for excuse in excuses.iter() {
let ptr_to_excuse = excuse
.expect("valid ptr to ptr")
.read()
.expect("valid ptr to some Excuse value");
ptr_to_excuse
.write(types::Excuse::Sleeping)
.expect("dereferencing mut ptr should succeed");
}
Ok(())
}
}
#[derive(Debug)]
struct ReduceExcusesExcercise {
excuse_values: Vec<types::Excuse>,
excuse_ptr_locs: Vec<MemArea>,
array_ptr_loc: MemArea,
return_ptr_loc: MemArea,
}
impl ReduceExcusesExcercise {
pub fn strat() -> BoxedStrategy<Self> {
(1..256u32)
.prop_flat_map(|len| {
let len_usize = len as usize;
(
proptest::collection::vec(excuse_strat(), len_usize..=len_usize),
proptest::collection::vec(HostMemory::mem_area_strat(4), len_usize..=len_usize),
HostMemory::mem_area_strat(4 * len),
HostMemory::mem_area_strat(4),
)
})
.prop_map(
|(excuse_values, excuse_ptr_locs, array_ptr_loc, return_ptr_loc)| Self {
excuse_values,
excuse_ptr_locs,
array_ptr_loc,
return_ptr_loc,
},
)
.prop_filter("non-overlapping pointers", |e| {
let mut all = vec![e.array_ptr_loc, e.return_ptr_loc];
all.extend(e.excuse_ptr_locs.iter());
MemArea::non_overlapping_set(all)
})
.boxed()
}
pub fn test(&self) {
let mut ctx = WasiCtx::new();
let mut host_memory = HostMemory::new();
// Populate memory with pointers to generated Excuse values
for (&excuse, ptr) in self.excuse_values.iter().zip(self.excuse_ptr_locs.iter()) {
host_memory
.ptr(ptr.ptr)
.write(excuse)
.expect("deref ptr mut to Excuse value");
}
// Populate the array with pointers to generated Excuse values
{
let array: GuestPtr<'_, [GuestPtr<types::Excuse>]> =
host_memory.ptr((self.array_ptr_loc.ptr, self.excuse_ptr_locs.len() as u32));
for (slot, ptr) in array.iter().zip(&self.excuse_ptr_locs) {
let slot = slot.expect("array should be in bounds");
slot.write(host_memory.ptr(ptr.ptr))
.expect("should succeed in writing array");
}
}
let res = arrays::reduce_excuses(
&mut ctx,
&mut host_memory,
self.array_ptr_loc.ptr as i32,
self.excuse_ptr_locs.len() as i32,
self.return_ptr_loc.ptr as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "reduce excuses errno");
let expected = *self
.excuse_values
.last()
.expect("generated vec of excuses should be non-empty");
let given: types::Excuse = host_memory
.ptr(self.return_ptr_loc.ptr)
.read()
.expect("deref ptr to returned value");
assert_eq!(expected, given, "reduce excuses return val");
}
}
proptest! {
#[test]
fn reduce_excuses(e in ReduceExcusesExcercise::strat()) {
e.test()
}
}
fn excuse_strat() -> impl Strategy<Value = types::Excuse> {
prop_oneof![
Just(types::Excuse::DogAte),
Just(types::Excuse::Traffic),
Just(types::Excuse::Sleeping),
]
.boxed()
}
#[derive(Debug)]
struct PopulateExcusesExcercise {
array_ptr_loc: MemArea,
elements: Vec<MemArea>,
}
impl PopulateExcusesExcercise {
pub fn strat() -> BoxedStrategy<Self> {
(1..256u32)
.prop_flat_map(|len| {
let len_usize = len as usize;
(
HostMemory::mem_area_strat(4 * len),
proptest::collection::vec(HostMemory::mem_area_strat(4), len_usize..=len_usize),
)
})
.prop_map(|(array_ptr_loc, elements)| Self {
array_ptr_loc,
elements,
})
.prop_filter("non-overlapping pointers", |e| {
let mut all = vec![e.array_ptr_loc];
all.extend(e.elements.iter());
MemArea::non_overlapping_set(all)
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate array with valid pointers to Excuse type in memory
let ptr = host_memory.ptr::<[GuestPtr<'_, types::Excuse>]>((
self.array_ptr_loc.ptr,
self.elements.len() as u32,
));
for (ptr, val) in ptr.iter().zip(&self.elements) {
ptr.expect("should be valid pointer")
.write(host_memory.ptr(val.ptr))
.expect("failed to write value");
}
let res = arrays::populate_excuses(
&ctx,
&host_memory,
self.array_ptr_loc.ptr as i32,
self.elements.len() as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "populate excuses errno");
let arr: GuestPtr<'_, [GuestPtr<'_, types::Excuse>]> =
host_memory.ptr((self.array_ptr_loc.ptr, self.elements.len() as u32));
for el in arr.iter() {
let ptr_to_ptr = el
.expect("valid ptr to ptr")
.read()
.expect("valid ptr to some Excuse value");
assert_eq!(
ptr_to_ptr
.read()
.expect("dereferencing ptr to some Excuse value"),
types::Excuse::Sleeping,
"element should equal Excuse::Sleeping"
);
}
}
}
proptest! {
#[test]
fn populate_excuses(e in PopulateExcusesExcercise::strat()) {
e.test()
}
}

View File

@@ -0,0 +1,17 @@
(use "errno.witx")
(use "excuse.witx")
(typename $const_excuse_array (array (@witx const_pointer $excuse)))
(typename $excuse_array (array (@witx pointer $excuse)))
(module $arrays
(@interface func (export "reduce_excuses")
(param $excuses $const_excuse_array)
(result $error $errno)
(result $reduced $excuse)
)
(@interface func (export "populate_excuses")
(param $excuses $excuse_array)
(result $error $errno)
)
)

View File

@@ -0,0 +1,91 @@
use proptest::prelude::*;
use wiggle_runtime::{GuestError, GuestMemory};
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
wiggle::from_witx!({
witx: ["tests/atoms.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
impl<'a> atoms::Atoms for WasiCtx<'a> {
fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("INT FLOAT ARGS: {} {}", an_int, an_float);
Ok(())
}
fn double_int_return_float(&self, an_int: u32) -> Result<types::AliasToFloat, types::Errno> {
Ok((an_int as f32) * 2.0)
}
}
// There's nothing meaningful to test here - this just demonstrates the test machinery
#[derive(Debug)]
struct IntFloatExercise {
pub an_int: u32,
pub an_float: f32,
}
impl IntFloatExercise {
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let e = atoms::int_float_args(&ctx, &host_memory, self.an_int as i32, self.an_float);
assert_eq!(e, types::Errno::Ok.into(), "int_float_args error");
}
pub fn strat() -> BoxedStrategy<Self> {
(prop::num::u32::ANY, prop::num::f32::ANY)
.prop_map(|(an_int, an_float)| IntFloatExercise { an_int, an_float })
.boxed()
}
}
proptest! {
#[test]
fn int_float_exercise(e in IntFloatExercise::strat()) {
e.test()
}
}
#[derive(Debug)]
struct DoubleIntExercise {
pub input: u32,
pub return_loc: MemArea,
}
impl DoubleIntExercise {
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let e = atoms::double_int_return_float(
&ctx,
&host_memory,
self.input as i32,
self.return_loc.ptr as i32,
);
let return_val = host_memory
.ptr::<types::AliasToFloat>(self.return_loc.ptr)
.read()
.expect("failed to read return");
assert_eq!(e, types::Errno::Ok.into(), "errno");
assert_eq!(return_val, (self.input as f32) * 2.0, "return val");
}
pub fn strat() -> BoxedStrategy<Self> {
(prop::num::u32::ANY, HostMemory::mem_area_strat(4))
.prop_map(|(input, return_loc)| DoubleIntExercise { input, return_loc })
.boxed()
}
}
proptest! {
#[test]
fn double_int_return_float(e in DoubleIntExercise::strat()) {
e.test()
}
}

View File

@@ -0,0 +1,14 @@
(use "errno.witx")
(typename $alias_to_float f32)
(module $atoms
(@interface func (export "int_float_args")
(param $an_int u32)
(param $an_float f32)
(result $error $errno))
(@interface func (export "double_int_return_float")
(param $an_int u32)
(result $error $errno)
(result $doubled_it $alias_to_float))
)

View File

@@ -0,0 +1,13 @@
(typename $errno
(enum u32
;;; Success
$ok
;;; Invalid argument
$invalid_arg
;;; I really don't want to
$dont_want_to
;;; I am physically unable to
$physically_unable
;;; Well, that's a picket line alright!
$picket_line))

View File

@@ -0,0 +1,6 @@
(typename $excuse
(enum u8
$dog_ate
$traffic
$sleeping))

View File

@@ -0,0 +1,101 @@
use proptest::prelude::*;
use std::convert::TryFrom;
use wiggle_runtime::{GuestError, GuestMemory, GuestPtr};
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
wiggle::from_witx!({
witx: ["tests/flags.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
impl<'a> flags::Flags for WasiCtx<'a> {
fn configure_car(
&self,
old_config: types::CarConfig,
other_config_ptr: GuestPtr<types::CarConfig>,
) -> Result<types::CarConfig, types::Errno> {
let other_config = other_config_ptr.read().map_err(|e| {
eprintln!("old_config_ptr error: {}", e);
types::Errno::InvalidArg
})?;
Ok(old_config ^ other_config)
}
}
fn car_config_strat() -> impl Strategy<Value = types::CarConfig> {
(1u8..=types::CarConfig::ALL_FLAGS.into())
.prop_map(|v| {
types::CarConfig::try_from(v).expect("invalid value for types::CarConfig flag")
})
.boxed()
}
#[derive(Debug)]
struct ConfigureCarExercise {
old_config: types::CarConfig,
other_config: types::CarConfig,
other_config_by_ptr: MemArea,
return_ptr_loc: MemArea,
}
impl ConfigureCarExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
car_config_strat(),
car_config_strat(),
HostMemory::mem_area_strat(4),
HostMemory::mem_area_strat(4),
)
.prop_map(
|(old_config, other_config, other_config_by_ptr, return_ptr_loc)| Self {
old_config,
other_config,
other_config_by_ptr,
return_ptr_loc,
},
)
.prop_filter("non-overlapping ptrs", |e| {
MemArea::non_overlapping_set(&[e.other_config_by_ptr, e.return_ptr_loc])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate input ptr
host_memory
.ptr(self.other_config_by_ptr.ptr)
.write(self.other_config)
.expect("deref ptr mut to CarConfig");
let res = flags::configure_car(
&ctx,
&host_memory,
self.old_config.into(),
self.other_config_by_ptr.ptr as i32,
self.return_ptr_loc.ptr as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "configure car errno");
let res_config = host_memory
.ptr::<types::CarConfig>(self.return_ptr_loc.ptr)
.read()
.expect("deref to CarConfig value");
assert_eq!(
self.old_config ^ self.other_config,
res_config,
"returned CarConfig should be an XOR of inputs"
);
}
}
proptest! {
#[test]
fn configure_car(e in ConfigureCarExercise::strat()) {
e.test()
}
}

View File

@@ -0,0 +1,16 @@
(use "errno.witx")
(typename $car_config
(flags u8
$automatic
$awd
$suv))
(module $flags
(@interface func (export "configure_car")
(param $old_config $car_config)
(param $old_config_by_ptr (@witx const_pointer $car_config))
(result $error $errno)
(result $new_config $car_config)
)
)

View File

@@ -0,0 +1,74 @@
use proptest::prelude::*;
use wiggle_runtime::{GuestError, GuestMemory, GuestType};
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
const FD_VAL: u32 = 123;
wiggle::from_witx!({
witx: ["tests/handles.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
impl<'a> handle_examples::HandleExamples for WasiCtx<'a> {
fn fd_create(&self) -> Result<types::Fd, types::Errno> {
Ok(types::Fd::from(FD_VAL))
}
fn fd_consume(&self, fd: types::Fd) -> Result<(), types::Errno> {
println!("FD_CONSUME {}", fd);
if fd == types::Fd::from(FD_VAL) {
Ok(())
} else {
Err(types::Errno::InvalidArg)
}
}
}
#[derive(Debug)]
struct HandleExercise {
pub return_loc: MemArea,
}
impl HandleExercise {
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let e = handle_examples::fd_create(&ctx, &host_memory, self.return_loc.ptr as i32);
assert_eq!(e, types::Errno::Ok.into(), "fd_create error");
let h_got: u32 = host_memory
.ptr(self.return_loc.ptr)
.read()
.expect("return ref_mut");
assert_eq!(h_got, 123, "fd_create return val");
let e = handle_examples::fd_consume(&ctx, &host_memory, h_got as i32);
assert_eq!(e, types::Errno::Ok.into(), "fd_consume error");
let e = handle_examples::fd_consume(&ctx, &host_memory, h_got as i32 + 1);
assert_eq!(
e,
types::Errno::InvalidArg.into(),
"fd_consume invalid error"
);
}
pub fn strat() -> BoxedStrategy<Self> {
(HostMemory::mem_area_strat(types::Fd::guest_size()))
.prop_map(|return_loc| HandleExercise { return_loc })
.boxed()
}
}
proptest! {
#[test]
fn handle_exercise(e in HandleExercise::strat()) {
e.test()
}
}

View File

@@ -0,0 +1,12 @@
(use "errno.witx")
(typename $fd (handle))
(module $handle_examples
(@interface func (export "fd_create")
(result $error $errno)
(result $fd $fd))
(@interface func (export "fd_consume")
(param $fd $fd)
(result $error $errno))
)

View File

@@ -0,0 +1,79 @@
use proptest::prelude::*;
use std::convert::TryFrom;
use wiggle_runtime::{GuestError, GuestMemory};
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
wiggle::from_witx!({
witx: ["tests/ints.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
impl<'a> ints::Ints for WasiCtx<'a> {
fn cookie_cutter(&self, init_cookie: types::Cookie) -> Result<types::Bool, types::Errno> {
let res = if init_cookie == types::Cookie::START {
types::Bool::True
} else {
types::Bool::False
};
Ok(res)
}
}
fn cookie_strat() -> impl Strategy<Value = types::Cookie> {
(0..std::u64::MAX)
.prop_map(|x| types::Cookie::try_from(x).expect("within range of cookie"))
.boxed()
}
#[derive(Debug)]
struct CookieCutterExercise {
cookie: types::Cookie,
return_ptr_loc: MemArea,
}
impl CookieCutterExercise {
pub fn strat() -> BoxedStrategy<Self> {
(cookie_strat(), HostMemory::mem_area_strat(4))
.prop_map(|(cookie, return_ptr_loc)| Self {
cookie,
return_ptr_loc,
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let res = ints::cookie_cutter(
&ctx,
&host_memory,
self.cookie.into(),
self.return_ptr_loc.ptr as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "cookie cutter errno");
let is_cookie_start = host_memory
.ptr::<types::Bool>(self.return_ptr_loc.ptr)
.read()
.expect("deref to Bool value");
assert_eq!(
if is_cookie_start == types::Bool::True {
true
} else {
false
},
self.cookie == types::Cookie::START,
"returned Bool should test if input was Cookie::START",
);
}
}
proptest! {
#[test]
fn cookie_cutter(e in CookieCutterExercise::strat()) {
e.test()
}
}

View File

@@ -0,0 +1,18 @@
(use "errno.witx")
(typename $cookie
(int u64
(const $start 0)))
(typename $bool
(enum u8
$false
$true))
(module $ints
(@interface func (export "cookie_cutter")
(param $init_cookie $cookie)
(result $error $errno)
(result $is_start $bool)
)
)

View File

@@ -0,0 +1,191 @@
use proptest::prelude::*;
use wiggle_runtime::{GuestError, GuestMemory, GuestPtr};
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
wiggle::from_witx!({
witx: ["tests/pointers.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
impl<'a> pointers::Pointers for WasiCtx<'a> {
fn pointers_and_enums<'b>(
&self,
input1: types::Excuse,
input2_ptr: GuestPtr<'b, types::Excuse>,
input3_ptr: GuestPtr<'b, types::Excuse>,
input4_ptr_ptr: GuestPtr<'b, GuestPtr<'b, types::Excuse>>,
) -> Result<(), types::Errno> {
println!("BAZ input1 {:?}", input1);
let input2: types::Excuse = input2_ptr.read().map_err(|e| {
eprintln!("input2_ptr error: {}", e);
types::Errno::InvalidArg
})?;
println!("input2 {:?}", input2);
// Read enum value from immutable ptr:
let input3 = input3_ptr.read().map_err(|e| {
eprintln!("input3_ptr error: {}", e);
types::Errno::InvalidArg
})?;
println!("input3 {:?}", input3);
// Write enum to mutable ptr:
input2_ptr.write(input3).map_err(|e| {
eprintln!("input2_ptr error: {}", e);
types::Errno::InvalidArg
})?;
println!("wrote to input2_ref {:?}", input3);
// Read ptr value from mutable ptr:
let input4_ptr: GuestPtr<types::Excuse> = input4_ptr_ptr.read().map_err(|e| {
eprintln!("input4_ptr_ptr error: {}", e);
types::Errno::InvalidArg
})?;
// Read enum value from that ptr:
let input4: types::Excuse = input4_ptr.read().map_err(|e| {
eprintln!("input4_ptr error: {}", e);
types::Errno::InvalidArg
})?;
println!("input4 {:?}", input4);
// Write ptr value to mutable ptr:
input4_ptr_ptr.write(input2_ptr).map_err(|e| {
eprintln!("input4_ptr_ptr error: {}", e);
types::Errno::InvalidArg
})?;
Ok(())
}
}
fn excuse_strat() -> impl Strategy<Value = types::Excuse> {
prop_oneof![
Just(types::Excuse::DogAte),
Just(types::Excuse::Traffic),
Just(types::Excuse::Sleeping),
]
.boxed()
}
#[derive(Debug)]
struct PointersAndEnumsExercise {
pub input1: types::Excuse,
pub input2: types::Excuse,
pub input2_loc: MemArea,
pub input3: types::Excuse,
pub input3_loc: MemArea,
pub input4: types::Excuse,
pub input4_loc: MemArea,
pub input4_ptr_loc: MemArea,
}
impl PointersAndEnumsExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
excuse_strat(),
excuse_strat(),
HostMemory::mem_area_strat(4),
excuse_strat(),
HostMemory::mem_area_strat(4),
excuse_strat(),
HostMemory::mem_area_strat(4),
HostMemory::mem_area_strat(4),
)
.prop_map(
|(
input1,
input2,
input2_loc,
input3,
input3_loc,
input4,
input4_loc,
input4_ptr_loc,
)| PointersAndEnumsExercise {
input1,
input2,
input2_loc,
input3,
input3_loc,
input4,
input4_loc,
input4_ptr_loc,
},
)
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[
e.input2_loc,
e.input3_loc,
e.input4_loc,
e.input4_ptr_loc,
])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
.ptr(self.input2_loc.ptr)
.write(self.input2)
.expect("input2 ref_mut");
host_memory
.ptr(self.input3_loc.ptr)
.write(self.input3)
.expect("input3 ref_mut");
host_memory
.ptr(self.input4_loc.ptr)
.write(self.input4)
.expect("input4 ref_mut");
host_memory
.ptr(self.input4_ptr_loc.ptr)
.write(self.input4_loc.ptr)
.expect("input4 ptr ref_mut");
let e = pointers::pointers_and_enums(
&ctx,
&host_memory,
self.input1.into(),
self.input2_loc.ptr as i32,
self.input3_loc.ptr as i32,
self.input4_ptr_loc.ptr as i32,
);
assert_eq!(e, types::Errno::Ok.into(), "errno");
// Implementation of pointers_and_enums writes input3 to the input2_loc:
let written_to_input2_loc: i32 = host_memory
.ptr(self.input2_loc.ptr)
.read()
.expect("input2 ref");
assert_eq!(
written_to_input2_loc,
self.input3.into(),
"pointers_and_enums written to input2"
);
// Implementation of pointers_and_enums writes input2_loc to input4_ptr_loc:
let written_to_input4_ptr: u32 = host_memory
.ptr(self.input4_ptr_loc.ptr)
.read()
.expect("input4_ptr_loc ref");
assert_eq!(
written_to_input4_ptr, self.input2_loc.ptr,
"pointers_and_enums written to input4_ptr"
);
}
}
proptest! {
#[test]
fn pointers_and_enums(e in PointersAndEnumsExercise::strat()) {
e.test();
}
}

View File

@@ -0,0 +1,11 @@
(use "errno.witx")
(use "excuse.witx")
(module $pointers
(@interface func (export "pointers_and_enums")
(param $an_excuse $excuse)
(param $an_excuse_by_reference (@witx pointer $excuse))
(param $a_lamer_excuse (@witx const_pointer $excuse))
(param $two_layers_of_excuses (@witx pointer (@witx const_pointer $excuse)))
(result $error $errno))
)

View File

@@ -0,0 +1,223 @@
use proptest::prelude::*;
use wiggle_runtime::{GuestBorrows, GuestError, GuestMemory, GuestPtr};
use wiggle_test::{impl_errno, HostMemory, MemArea, MemAreas, WasiCtx};
wiggle::from_witx!({
witx: ["tests/strings.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
impl<'a> strings::Strings for WasiCtx<'a> {
fn hello_string(&self, a_string: &GuestPtr<str>) -> Result<u32, types::Errno> {
let mut bc = GuestBorrows::new();
let s = a_string.as_raw(&mut bc).expect("should be valid string");
unsafe {
println!("a_string='{}'", &*s);
Ok((*s).len() as u32)
}
}
fn multi_string(
&self,
a: &GuestPtr<str>,
b: &GuestPtr<str>,
c: &GuestPtr<str>,
) -> Result<u32, types::Errno> {
let mut bc = GuestBorrows::new();
let sa = a.as_raw(&mut bc).expect("A should be valid string");
let sb = b.as_raw(&mut bc).expect("B should be valid string");
let sc = c.as_raw(&mut bc).expect("C should be valid string");
unsafe {
let total_len = (&*sa).len() + (&*sb).len() + (&*sc).len();
println!(
"len={}, a='{}', b='{}', c='{}'",
total_len, &*sa, &*sb, &*sc
);
Ok(total_len as u32)
}
}
}
fn test_string_strategy() -> impl Strategy<Value = String> {
"\\p{Greek}{1,256}"
}
#[derive(Debug)]
struct HelloStringExercise {
test_word: String,
string_ptr_loc: MemArea,
return_ptr_loc: MemArea,
}
impl HelloStringExercise {
pub fn strat() -> BoxedStrategy<Self> {
(test_string_strategy(),)
.prop_flat_map(|(test_word,)| {
(
Just(test_word.clone()),
HostMemory::mem_area_strat(test_word.len() as u32),
HostMemory::mem_area_strat(4),
)
})
.prop_map(|(test_word, string_ptr_loc, return_ptr_loc)| Self {
test_word,
string_ptr_loc,
return_ptr_loc,
})
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[e.string_ptr_loc, e.return_ptr_loc])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate string in guest's memory
let ptr = host_memory.ptr::<str>((self.string_ptr_loc.ptr, self.test_word.len() as u32));
for (slot, byte) in ptr.as_bytes().iter().zip(self.test_word.bytes()) {
slot.expect("should be valid pointer")
.write(byte)
.expect("failed to write");
}
let res = strings::hello_string(
&ctx,
&host_memory,
self.string_ptr_loc.ptr as i32,
self.test_word.len() as i32,
self.return_ptr_loc.ptr as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "hello string errno");
let given = host_memory
.ptr::<u32>(self.return_ptr_loc.ptr)
.read()
.expect("deref ptr to return value");
assert_eq!(self.test_word.len() as u32, given);
}
}
proptest! {
#[test]
fn hello_string(e in HelloStringExercise::strat()) {
e.test()
}
}
#[derive(Debug)]
struct MultiStringExercise {
a: String,
b: String,
c: String,
sa_ptr_loc: MemArea,
sb_ptr_loc: MemArea,
sc_ptr_loc: MemArea,
return_ptr_loc: MemArea,
}
impl MultiStringExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
test_string_strategy(),
test_string_strategy(),
test_string_strategy(),
HostMemory::mem_area_strat(4),
)
.prop_flat_map(|(a, b, c, return_ptr_loc)| {
(
Just(a.clone()),
Just(b.clone()),
Just(c.clone()),
HostMemory::byte_slice_strat(a.len() as u32, &MemAreas::from([return_ptr_loc])),
Just(return_ptr_loc),
)
})
.prop_flat_map(|(a, b, c, sa_ptr_loc, return_ptr_loc)| {
(
Just(a.clone()),
Just(b.clone()),
Just(c.clone()),
Just(sa_ptr_loc),
HostMemory::byte_slice_strat(
b.len() as u32,
&MemAreas::from([sa_ptr_loc, return_ptr_loc]),
),
Just(return_ptr_loc),
)
})
.prop_flat_map(|(a, b, c, sa_ptr_loc, sb_ptr_loc, return_ptr_loc)| {
(
Just(a.clone()),
Just(b.clone()),
Just(c.clone()),
Just(sa_ptr_loc),
Just(sb_ptr_loc),
HostMemory::byte_slice_strat(
c.len() as u32,
&MemAreas::from([sa_ptr_loc, sb_ptr_loc, return_ptr_loc]),
),
Just(return_ptr_loc),
)
})
.prop_map(
|(a, b, c, sa_ptr_loc, sb_ptr_loc, sc_ptr_loc, return_ptr_loc)| {
MultiStringExercise {
a,
b,
c,
sa_ptr_loc,
sb_ptr_loc,
sc_ptr_loc,
return_ptr_loc,
}
},
)
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let write_string = |val: &str, loc: MemArea| {
let ptr = host_memory.ptr::<str>((loc.ptr, val.len() as u32));
for (slot, byte) in ptr.as_bytes().iter().zip(val.bytes()) {
slot.expect("should be valid pointer")
.write(byte)
.expect("failed to write");
}
};
write_string(&self.a, self.sa_ptr_loc);
write_string(&self.b, self.sb_ptr_loc);
write_string(&self.c, self.sc_ptr_loc);
let res = strings::multi_string(
&ctx,
&host_memory,
self.sa_ptr_loc.ptr as i32,
self.a.len() as i32,
self.sb_ptr_loc.ptr as i32,
self.b.len() as i32,
self.sc_ptr_loc.ptr as i32,
self.c.len() as i32,
self.return_ptr_loc.ptr as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "multi string errno");
let given = host_memory
.ptr::<u32>(self.return_ptr_loc.ptr)
.read()
.expect("deref ptr to return value");
assert_eq!((self.a.len() + self.b.len() + self.c.len()) as u32, given);
}
}
proptest! {
#[test]
fn multi_string(e in MultiStringExercise::strat()) {
e.test()
}
}

View File

@@ -0,0 +1,16 @@
(use "errno.witx")
(module $strings
(@interface func (export "hello_string")
(param $a_string string)
(result $error $errno)
(result $total_bytes u32)
)
(@interface func (export "multi_string")
(param $a string)
(param $b string)
(param $c string)
(result $error $errno)
(result $total_bytes u32)
)
)

View File

@@ -0,0 +1,422 @@
use proptest::prelude::*;
use wiggle_runtime::{GuestError, GuestMemory, GuestPtr};
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
wiggle::from_witx!({
witx: ["tests/structs.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
impl<'a> structs::Structs for WasiCtx<'a> {
fn sum_of_pair(&self, an_pair: &types::PairInts) -> Result<i64, types::Errno> {
Ok(an_pair.first as i64 + an_pair.second as i64)
}
fn sum_of_pair_of_ptrs(&self, an_pair: &types::PairIntPtrs) -> Result<i64, types::Errno> {
let first = an_pair
.first
.read()
.expect("dereferencing GuestPtr should succeed");
let second = an_pair
.second
.read()
.expect("dereferncing GuestPtr should succeed");
Ok(first as i64 + second as i64)
}
fn sum_of_int_and_ptr(&self, an_pair: &types::PairIntAndPtr) -> Result<i64, types::Errno> {
let first = an_pair
.first
.read()
.expect("dereferencing GuestPtr should succeed");
let second = an_pair.second as i64;
Ok(first as i64 + second)
}
fn return_pair_ints(&self) -> Result<types::PairInts, types::Errno> {
Ok(types::PairInts {
first: 10,
second: 20,
})
}
fn return_pair_of_ptrs<'b>(
&self,
first: GuestPtr<'b, i32>,
second: GuestPtr<'b, i32>,
) -> Result<types::PairIntPtrs<'b>, types::Errno> {
Ok(types::PairIntPtrs { first, second })
}
}
#[derive(Debug)]
struct SumOfPairExercise {
pub input: types::PairInts,
pub input_loc: MemArea,
pub return_loc: MemArea,
}
impl SumOfPairExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
prop::num::i32::ANY,
prop::num::i32::ANY,
HostMemory::mem_area_strat(8),
HostMemory::mem_area_strat(8),
)
.prop_map(|(first, second, input_loc, return_loc)| SumOfPairExercise {
input: types::PairInts { first, second },
input_loc,
return_loc,
})
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[e.input_loc, e.return_loc])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
.ptr(self.input_loc.ptr)
.write(self.input.first)
.expect("input ref_mut");
host_memory
.ptr(self.input_loc.ptr + 4)
.write(self.input.second)
.expect("input ref_mut");
let sum_err = structs::sum_of_pair(
&ctx,
&host_memory,
self.input_loc.ptr as i32,
self.return_loc.ptr as i32,
);
assert_eq!(sum_err, types::Errno::Ok.into(), "sum errno");
let return_val: i64 = host_memory
.ptr(self.return_loc.ptr)
.read()
.expect("return ref");
assert_eq!(
return_val,
self.input.first as i64 + self.input.second as i64,
"sum return value"
);
}
}
proptest! {
#[test]
fn sum_of_pair(e in SumOfPairExercise::strat()) {
e.test();
}
}
#[derive(Debug)]
struct SumPairPtrsExercise {
input_first: i32,
input_second: i32,
input_first_loc: MemArea,
input_second_loc: MemArea,
input_struct_loc: MemArea,
return_loc: MemArea,
}
impl SumPairPtrsExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
prop::num::i32::ANY,
prop::num::i32::ANY,
HostMemory::mem_area_strat(4),
HostMemory::mem_area_strat(4),
HostMemory::mem_area_strat(8),
HostMemory::mem_area_strat(8),
)
.prop_map(
|(
input_first,
input_second,
input_first_loc,
input_second_loc,
input_struct_loc,
return_loc,
)| SumPairPtrsExercise {
input_first,
input_second,
input_first_loc,
input_second_loc,
input_struct_loc,
return_loc,
},
)
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[
e.input_first_loc,
e.input_second_loc,
e.input_struct_loc,
e.return_loc,
])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
.ptr(self.input_first_loc.ptr)
.write(self.input_first)
.expect("input_first ref");
host_memory
.ptr(self.input_second_loc.ptr)
.write(self.input_second)
.expect("input_second ref");
host_memory
.ptr(self.input_struct_loc.ptr)
.write(self.input_first_loc.ptr)
.expect("input_struct ref");
host_memory
.ptr(self.input_struct_loc.ptr + 4)
.write(self.input_second_loc.ptr)
.expect("input_struct ref");
let res = structs::sum_of_pair_of_ptrs(
&ctx,
&host_memory,
self.input_struct_loc.ptr as i32,
self.return_loc.ptr as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "sum of pair of ptrs errno");
let doubled: i64 = host_memory
.ptr(self.return_loc.ptr)
.read()
.expect("return ref");
assert_eq!(
doubled,
(self.input_first as i64) + (self.input_second as i64),
"sum of pair of ptrs return val"
);
}
}
proptest! {
#[test]
fn sum_of_pair_of_ptrs(e in SumPairPtrsExercise::strat()) {
e.test()
}
}
#[derive(Debug)]
struct SumIntAndPtrExercise {
input_first: i32,
input_second: i32,
input_first_loc: MemArea,
input_struct_loc: MemArea,
return_loc: MemArea,
}
impl SumIntAndPtrExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
prop::num::i32::ANY,
prop::num::i32::ANY,
HostMemory::mem_area_strat(4),
HostMemory::mem_area_strat(8),
HostMemory::mem_area_strat(8),
)
.prop_map(
|(input_first, input_second, input_first_loc, input_struct_loc, return_loc)| {
SumIntAndPtrExercise {
input_first,
input_second,
input_first_loc,
input_struct_loc,
return_loc,
}
},
)
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[e.input_first_loc, e.input_struct_loc, e.return_loc])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
.ptr(self.input_first_loc.ptr)
.write(self.input_first)
.expect("input_first ref");
host_memory
.ptr(self.input_struct_loc.ptr)
.write(self.input_first_loc.ptr)
.expect("input_struct ref");
host_memory
.ptr(self.input_struct_loc.ptr + 4)
.write(self.input_second)
.expect("input_struct ref");
let res = structs::sum_of_int_and_ptr(
&ctx,
&host_memory,
self.input_struct_loc.ptr as i32,
self.return_loc.ptr as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "sum of int and ptr errno");
let doubled: i64 = host_memory
.ptr(self.return_loc.ptr)
.read()
.expect("return ref");
assert_eq!(
doubled,
(self.input_first as i64) + (self.input_second as i64),
"sum of pair of ptrs return val"
);
}
}
proptest! {
#[test]
fn sum_of_int_and_ptr(e in SumIntAndPtrExercise::strat()) {
e.test()
}
}
#[derive(Debug)]
struct ReturnPairInts {
pub return_loc: MemArea,
}
impl ReturnPairInts {
pub fn strat() -> BoxedStrategy<Self> {
HostMemory::mem_area_strat(8)
.prop_map(|return_loc| ReturnPairInts { return_loc })
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let err = structs::return_pair_ints(&ctx, &host_memory, self.return_loc.ptr as i32);
assert_eq!(err, types::Errno::Ok.into(), "return struct errno");
let return_struct: types::PairInts = host_memory
.ptr(self.return_loc.ptr)
.read()
.expect("return ref");
assert_eq!(
return_struct,
types::PairInts {
first: 10,
second: 20
},
"return_pair_ints return value"
);
}
}
proptest! {
#[test]
fn return_pair_ints(e in ReturnPairInts::strat()) {
e.test();
}
}
#[derive(Debug)]
struct ReturnPairPtrsExercise {
input_first: i32,
input_second: i32,
input_first_loc: MemArea,
input_second_loc: MemArea,
return_loc: MemArea,
}
impl ReturnPairPtrsExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
prop::num::i32::ANY,
prop::num::i32::ANY,
HostMemory::mem_area_strat(4),
HostMemory::mem_area_strat(4),
HostMemory::mem_area_strat(8),
)
.prop_map(
|(input_first, input_second, input_first_loc, input_second_loc, return_loc)| {
ReturnPairPtrsExercise {
input_first,
input_second,
input_first_loc,
input_second_loc,
return_loc,
}
},
)
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[e.input_first_loc, e.input_second_loc, e.return_loc])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
host_memory
.ptr(self.input_first_loc.ptr)
.write(self.input_first)
.expect("input_first ref");
host_memory
.ptr(self.input_second_loc.ptr)
.write(self.input_second)
.expect("input_second ref");
let res = structs::return_pair_of_ptrs(
&ctx,
&host_memory,
self.input_first_loc.ptr as i32,
self.input_second_loc.ptr as i32,
self.return_loc.ptr as i32,
);
assert_eq!(res, types::Errno::Ok.into(), "return pair of ptrs errno");
let ptr_pair_int_ptrs: types::PairIntPtrs<'_> = host_memory
.ptr(self.return_loc.ptr)
.read()
.expect("failed to read return location");
let ret_first_ptr = ptr_pair_int_ptrs.first;
let ret_second_ptr = ptr_pair_int_ptrs.second;
assert_eq!(
self.input_first,
ret_first_ptr
.read()
.expect("deref extracted ptr to first element")
);
assert_eq!(
self.input_second,
ret_second_ptr
.read()
.expect("deref extracted ptr to second element")
);
}
}
proptest! {
#[test]
fn return_pair_of_ptrs(e in ReturnPairPtrsExercise::strat()) {
e.test()
}
}

View File

@@ -0,0 +1,40 @@
(use "errno.witx")
(typename $pair_ints
(struct
(field $first s32)
(field $second s32)))
(typename $pair_int_ptrs
(struct
(field $first (@witx const_pointer s32))
(field $second (@witx const_pointer s32))))
(typename $pair_int_and_ptr
(struct
(field $first (@witx const_pointer s32))
(field $second s32)))
(module $structs
(@interface func (export "sum_of_pair")
(param $an_pair $pair_ints)
(result $error $errno)
(result $doubled s64))
(@interface func (export "sum_of_pair_of_ptrs")
(param $an_pair $pair_int_ptrs)
(result $error $errno)
(result $doubled s64))
(@interface func (export "sum_of_int_and_ptr")
(param $an_pair $pair_int_and_ptr)
(result $error $errno)
(result $double s64))
(@interface func (export "return_pair_ints")
(result $error $errno)
(result $an_pair $pair_ints))
(@interface func (export "return_pair_of_ptrs")
(param $first (@witx const_pointer s32))
(param $second (@witx const_pointer s32))
(result $error $errno)
(result $an_pair $pair_int_ptrs))
)

View File

@@ -0,0 +1,746 @@
;; Type names used by low-level WASI interfaces.
;;
;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi).
;;
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
(typename $size u32)
;;; Non-negative file size or length of a region within a file.
(typename $filesize u64)
;;; Timestamp in nanoseconds.
(typename $timestamp u64)
;;; Identifiers for clocks.
(typename $clockid
(enum u32
;;; The clock measuring real time. Time value zero corresponds with
;;; 1970-01-01T00:00:00Z.
$realtime
;;; The store-wide monotonic clock, which is defined as a clock measuring
;;; real time, whose value cannot be adjusted and which cannot have negative
;;; clock jumps. The epoch of this clock is undefined. The absolute time
;;; value of this clock therefore has no meaning.
$monotonic
;;; The CPU-time clock associated with the current process.
$process_cputime_id
;;; The CPU-time clock associated with the current thread.
$thread_cputime_id
)
)
;;; Error codes returned by functions.
;;; Not all of these error codes are returned by the functions provided by this
;;; API; some are used in higher-level library layers, and others are provided
;;; merely for alignment with POSIX.
(typename $errno
(enum u16
;;; No error occurred. System call completed successfully.
$success
;;; Argument list too long.
$2big
;;; Permission denied.
$acces
;;; Address in use.
$addrinuse
;;; Address not available.
$addrnotavail
;;; Address family not supported.
$afnosupport
;;; Resource unavailable, or operation would block.
$again
;;; Connection already in progress.
$already
;;; Bad file descriptor.
$badf
;;; Bad message.
$badmsg
;;; Device or resource busy.
$busy
;;; Operation canceled.
$canceled
;;; No child processes.
$child
;;; Connection aborted.
$connaborted
;;; Connection refused.
$connrefused
;;; Connection reset.
$connreset
;;; Resource deadlock would occur.
$deadlk
;;; Destination address required.
$destaddrreq
;;; Mathematics argument out of domain of function.
$dom
;;; Reserved.
$dquot
;;; File exists.
$exist
;;; Bad address.
$fault
;;; File too large.
$fbig
;;; Host is unreachable.
$hostunreach
;;; Identifier removed.
$idrm
;;; Illegal byte sequence.
$ilseq
;;; Operation in progress.
$inprogress
;;; Interrupted function.
$intr
;;; Invalid argument.
$inval
;;; I/O error.
$io
;;; Socket is connected.
$isconn
;;; Is a directory.
$isdir
;;; Too many levels of symbolic links.
$loop
;;; File descriptor value too large.
$mfile
;;; Too many links.
$mlink
;;; Message too large.
$msgsize
;;; Reserved.
$multihop
;;; Filename too long.
$nametoolong
;;; Network is down.
$netdown
;;; Connection aborted by network.
$netreset
;;; Network unreachable.
$netunreach
;;; Too many files open in system.
$nfile
;;; No buffer space available.
$nobufs
;;; No such device.
$nodev
;;; No such file or directory.
$noent
;;; Executable file format error.
$noexec
;;; No locks available.
$nolck
;;; Reserved.
$nolink
;;; Not enough space.
$nomem
;;; No message of the desired type.
$nomsg
;;; Protocol not available.
$noprotoopt
;;; No space left on device.
$nospc
;;; Function not supported.
$nosys
;;; The socket is not connected.
$notconn
;;; Not a directory or a symbolic link to a directory.
$notdir
;;; Directory not empty.
$notempty
;;; State not recoverable.
$notrecoverable
;;; Not a socket.
$notsock
;;; Not supported, or operation not supported on socket.
$notsup
;;; Inappropriate I/O control operation.
$notty
;;; No such device or address.
$nxio
;;; Value too large to be stored in data type.
$overflow
;;; Previous owner died.
$ownerdead
;;; Operation not permitted.
$perm
;;; Broken pipe.
$pipe
;;; Protocol error.
$proto
;;; Protocol not supported.
$protonosupport
;;; Protocol wrong type for socket.
$prototype
;;; Result too large.
$range
;;; Read-only file system.
$rofs
;;; Invalid seek.
$spipe
;;; No such process.
$srch
;;; Reserved.
$stale
;;; Connection timed out.
$timedout
;;; Text file busy.
$txtbsy
;;; Cross-device link.
$xdev
;;; Extension: Capabilities insufficient.
$notcapable
)
)
;;; File descriptor rights, determining which actions may be performed.
(typename $rights
(flags u64
;;; The right to invoke `fd_datasync`.
;;
;;; If `path_open` is set, includes the right to invoke
;;; `path_open` with `fdflags::dsync`.
$fd_datasync
;;; The right to invoke `fd_read` and `sock_recv`.
;;
;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pread`.
$fd_read
;;; The right to invoke `fd_seek`. This flag implies `rights::fd_tell`.
$fd_seek
;;; The right to invoke `fd_fdstat_set_flags`.
$fd_fdstat_set_flags
;;; The right to invoke `fd_sync`.
;;
;;; If `path_open` is set, includes the right to invoke
;;; `path_open` with `fdflags::rsync` and `fdflags::dsync`.
$fd_sync
;;; The right to invoke `fd_seek` in such a way that the file offset
;;; remains unaltered (i.e., `whence::cur` with offset zero), or to
;;; invoke `fd_tell`.
$fd_tell
;;; The right to invoke `fd_write` and `sock_send`.
;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`.
$fd_write
;;; The right to invoke `fd_advise`.
$fd_advise
;;; The right to invoke `fd_allocate`.
$fd_allocate
;;; The right to invoke `path_create_directory`.
$path_create_directory
;;; If `path_open` is set, the right to invoke `path_open` with `oflags::creat`.
$path_create_file
;;; The right to invoke `path_link` with the file descriptor as the
;;; source directory.
$path_link_source
;;; The right to invoke `path_link` with the file descriptor as the
;;; target directory.
$path_link_target
;;; The right to invoke `path_open`.
$path_open
;;; The right to invoke `fd_readdir`.
$fd_readdir
;;; The right to invoke `path_readlink`.
$path_readlink
;;; The right to invoke `path_rename` with the file descriptor as the source directory.
$path_rename_source
;;; The right to invoke `path_rename` with the file descriptor as the target directory.
$path_rename_target
;;; The right to invoke `path_filestat_get`.
$path_filestat_get
;;; The right to change a file's size (there is no `path_filestat_set_size`).
;;; If `path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`.
$path_filestat_set_size
;;; The right to invoke `path_filestat_set_times`.
$path_filestat_set_times
;;; The right to invoke `fd_filestat_get`.
$fd_filestat_get
;;; The right to invoke `fd_filestat_set_size`.
$fd_filestat_set_size
;;; The right to invoke `fd_filestat_set_times`.
$fd_filestat_set_times
;;; The right to invoke `path_symlink`.
$path_symlink
;;; The right to invoke `path_remove_directory`.
$path_remove_directory
;;; The right to invoke `path_unlink_file`.
$path_unlink_file
;;; If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`.
;;; If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`.
$poll_fd_readwrite
;;; The right to invoke `sock_shutdown`.
$sock_shutdown
)
)
;;; A file descriptor handle.
(typename $fd (handle))
;;; A region of memory for scatter/gather reads.
(typename $iovec
(struct
;;; The address of the buffer to be filled.
(field $buf (@witx pointer u8))
;;; The length of the buffer to be filled.
(field $buf_len $size)
)
)
;;; A region of memory for scatter/gather writes.
(typename $ciovec
(struct
;;; The address of the buffer to be written.
(field $buf (@witx const_pointer u8))
;;; The length of the buffer to be written.
(field $buf_len $size)
)
)
(typename $iovec_array (array $iovec))
(typename $ciovec_array (array $ciovec))
;;; Relative offset within a file.
(typename $filedelta s64)
;;; The position relative to which to set the offset of the file descriptor.
(typename $whence
(enum u8
;;; Seek relative to start-of-file.
$set
;;; Seek relative to current position.
$cur
;;; Seek relative to end-of-file.
$end
)
)
;;; A reference to the offset of a directory entry.
;;;
;;; The value 0 signifies the start of the directory.
(typename $dircookie u64)
;;; The type for the $d_namlen field of $dirent.
(typename $dirnamlen u32)
;;; File serial number that is unique within its file system.
(typename $inode u64)
;;; The type of a file descriptor or file.
(typename $filetype
(enum u8
;;; The type of the file descriptor or file is unknown or is different from any of the other types specified.
$unknown
;;; The file descriptor or file refers to a block device inode.
$block_device
;;; The file descriptor or file refers to a character device inode.
$character_device
;;; The file descriptor or file refers to a directory inode.
$directory
;;; The file descriptor or file refers to a regular file inode.
$regular_file
;;; The file descriptor or file refers to a datagram socket.
$socket_dgram
;;; The file descriptor or file refers to a byte-stream socket.
$socket_stream
;;; The file refers to a symbolic link inode.
$symbolic_link
)
)
;;; A directory entry.
(typename $dirent
(struct
;;; The offset of the next directory entry stored in this directory.
(field $d_next $dircookie)
;;; The serial number of the file referred to by this directory entry.
(field $d_ino $inode)
;;; The length of the name of the directory entry.
(field $d_namlen $dirnamlen)
;;; The type of the file referred to by this directory entry.
(field $d_type $filetype)
)
)
;;; File or memory access pattern advisory information.
(typename $advice
(enum u8
;;; The application has no advice to give on its behavior with respect to the specified data.
$normal
;;; The application expects to access the specified data sequentially from lower offsets to higher offsets.
$sequential
;;; The application expects to access the specified data in a random order.
$random
;;; The application expects to access the specified data in the near future.
$willneed
;;; The application expects that it will not access the specified data in the near future.
$dontneed
;;; The application expects to access the specified data once and then not reuse it thereafter.
$noreuse
)
)
;;; File descriptor flags.
(typename $fdflags
(flags u16
;;; Append mode: Data written to the file is always appended to the file's end.
$append
;;; Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized.
$dsync
;;; Non-blocking mode.
$nonblock
;;; Synchronized read I/O operations.
$rsync
;;; Write according to synchronized I/O file integrity completion. In
;;; addition to synchronizing the data stored in the file, the implementation
;;; may also synchronously update the file's metadata.
$sync
)
)
;;; File descriptor attributes.
(typename $fdstat
(struct
;;; File type.
(field $fs_filetype $filetype)
;;; File descriptor flags.
(field $fs_flags $fdflags)
;;; Rights that apply to this file descriptor.
(field $fs_rights_base $rights)
;;; Maximum set of rights that may be installed on new file descriptors that
;;; are created through this file descriptor, e.g., through `path_open`.
(field $fs_rights_inheriting $rights)
)
)
;;; Identifier for a device containing a file system. Can be used in combination
;;; with `inode` to uniquely identify a file or directory in the filesystem.
(typename $device u64)
;;; Which file time attributes to adjust.
(typename $fstflags
(flags u16
;;; Adjust the last data access timestamp to the value stored in `filestat::atim`.
$atim
;;; Adjust the last data access timestamp to the time of clock `clockid::realtime`.
$atim_now
;;; Adjust the last data modification timestamp to the value stored in `filestat::mtim`.
$mtim
;;; Adjust the last data modification timestamp to the time of clock `clockid::realtime`.
$mtim_now
)
)
;;; Flags determining the method of how paths are resolved.
(typename $lookupflags
(flags u32
;;; As long as the resolved path corresponds to a symbolic link, it is expanded.
$symlink_follow
)
)
;;; Open flags used by `path_open`.
(typename $oflags
(flags u16
;;; Create file if it does not exist.
$creat
;;; Fail if not a directory.
$directory
;;; Fail if file already exists.
$excl
;;; Truncate file to size 0.
$trunc
)
)
;;; Number of hard links to an inode.
(typename $linkcount u64)
;;; File attributes.
(typename $filestat
(struct
;;; Device ID of device containing the file.
(field $dev $device)
;;; File serial number.
(field $ino $inode)
;;; File type.
(field $filetype $filetype)
;;; Number of hard links to the file.
(field $nlink $linkcount)
;;; For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link.
(field $size $filesize)
;;; Last data access timestamp.
(field $atim $timestamp)
;;; Last data modification timestamp.
(field $mtim $timestamp)
;;; Last file status change timestamp.
(field $ctim $timestamp)
)
)
;;; User-provided value that may be attached to objects that is retained when
;;; extracted from the implementation.
(typename $userdata u64)
;;; Type of a subscription to an event or its occurrence.
(typename $eventtype
(enum u8
;;; The time value of clock `subscription_clock::id` has
;;; reached timestamp `subscription_clock::timeout`.
$clock
;;; File descriptor `subscription_fd_readwrite::file_descriptor` has data
;;; available for reading. This event always triggers for regular files.
$fd_read
;;; File descriptor `subscription_fd_readwrite::file_descriptor` has capacity
;;; available for writing. This event always triggers for regular files.
$fd_write
)
)
;;; The state of the file descriptor subscribed to with
;;; `eventtype::fd_read` or `eventtype::fd_write`.
(typename $eventrwflags
(flags u16
;;; The peer of this socket has closed or disconnected.
$fd_readwrite_hangup
)
)
;;; The contents of an $event when type is `eventtype::fd_read` or
;;; `eventtype::fd_write`.
(typename $event_fd_readwrite
(struct
;;; The number of bytes available for reading or writing.
(field $nbytes $filesize)
;;; The state of the file descriptor.
(field $flags $eventrwflags)
)
)
;;; An event that occurred.
(typename $event
(struct
;;; User-provided value that got attached to `subscription::userdata`.
(field $userdata $userdata)
;;; If non-zero, an error that occurred while processing the subscription request.
(field $error $errno)
;;; The type of event that occured
(field $type $eventtype)
;;; The contents of the event, if it is an `eventtype::fd_read` or
;;; `eventtype::fd_write`. `eventtype::clock` events ignore this field.
(field $fd_readwrite $event_fd_readwrite)
)
)
;;; Flags determining how to interpret the timestamp provided in
;;; `subscription_clock::timeout`.
(typename $subclockflags
(flags u16
;;; If set, treat the timestamp provided in
;;; `subscription_clock::timeout` as an absolute timestamp of clock
;;; `subscription_clock::id`. If clear, treat the timestamp
;;; provided in `subscription_clock::timeout` relative to the
;;; current time value of clock `subscription_clock::id`.
$subscription_clock_abstime
)
)
;;; The contents of a `subscription` when type is `eventtype::clock`.
(typename $subscription_clock
(struct
;;; The clock against which to compare the timestamp.
(field $id $clockid)
;;; The absolute or relative timestamp.
(field $timeout $timestamp)
;;; The amount of time that the implementation may wait additionally
;;; to coalesce with other events.
(field $precision $timestamp)
;;; Flags specifying whether the timeout is absolute or relative
(field $flags $subclockflags)
)
)
;;; The contents of a `subscription` when type is type is
;;; `eventtype::fd_read` or `eventtype::fd_write`.
(typename $subscription_fd_readwrite
(struct
;;; The file descriptor on which to wait for it to become ready for reading or writing.
(field $file_descriptor $fd)
)
)
;;; The contents of a `subscription`.
(typename $subscription_u
(union $eventtype
(field $clock $subscription_clock)
(field $fd_read $subscription_fd_readwrite)
(field $fd_write $subscription_fd_readwrite)
)
)
;;; Subscription to an event.
(typename $subscription
(struct
;;; User-provided value that is attached to the subscription in the
;;; implementation and returned through `event::userdata`.
(field $userdata $userdata)
;;; The type of the event to which to subscribe, and its contents
(field $u $subscription_u)
)
)
;;; Exit code generated by a process when exiting.
(typename $exitcode u32)
;;; Signal condition.
(typename $signal
(enum u8
;;; No signal. Note that POSIX has special semantics for `kill(pid, 0)`,
;;; so this value is reserved.
$none
;;; Hangup.
;;; Action: Terminates the process.
$hup
;;; Terminate interrupt signal.
;;; Action: Terminates the process.
$int
;;; Terminal quit signal.
;;; Action: Terminates the process.
$quit
;;; Illegal instruction.
;;; Action: Terminates the process.
$ill
;;; Trace/breakpoint trap.
;;; Action: Terminates the process.
$trap
;;; Process abort signal.
;;; Action: Terminates the process.
$abrt
;;; Access to an undefined portion of a memory object.
;;; Action: Terminates the process.
$bus
;;; Erroneous arithmetic operation.
;;; Action: Terminates the process.
$fpe
;;; Kill.
;;; Action: Terminates the process.
$kill
;;; User-defined signal 1.
;;; Action: Terminates the process.
$usr1
;;; Invalid memory reference.
;;; Action: Terminates the process.
$segv
;;; User-defined signal 2.
;;; Action: Terminates the process.
$usr2
;;; Write on a pipe with no one to read it.
;;; Action: Ignored.
$pipe
;;; Alarm clock.
;;; Action: Terminates the process.
$alrm
;;; Termination signal.
;;; Action: Terminates the process.
$term
;;; Child process terminated, stopped, or continued.
;;; Action: Ignored.
$chld
;;; Continue executing, if stopped.
;;; Action: Continues executing, if stopped.
$cont
;;; Stop executing.
;;; Action: Stops executing.
$stop
;;; Terminal stop signal.
;;; Action: Stops executing.
$tstp
;;; Background process attempting read.
;;; Action: Stops executing.
$ttin
;;; Background process attempting write.
;;; Action: Stops executing.
$ttou
;;; High bandwidth data is available at a socket.
;;; Action: Ignored.
$urg
;;; CPU time limit exceeded.
;;; Action: Terminates the process.
$xcpu
;;; File size limit exceeded.
;;; Action: Terminates the process.
$xfsz
;;; Virtual timer expired.
;;; Action: Terminates the process.
$vtalrm
;;; Profiling timer expired.
;;; Action: Terminates the process.
$prof
;;; Window changed.
;;; Action: Ignored.
$winch
;;; I/O possible.
;;; Action: Terminates the process.
$poll
;;; Power failure.
;;; Action: Terminates the process.
$pwr
;;; Bad system call.
;;; Action: Terminates the process.
$sys
)
)
;;; Flags provided to `sock_recv`.
(typename $riflags
(flags u16
;;; Returns the message without removing it from the socket's receive queue.
$recv_peek
;;; On byte-stream sockets, block until the full amount of data can be returned.
$recv_waitall
)
)
;;; Flags returned by `sock_recv`.
(typename $roflags
(flags u16
;;; Returned by `sock_recv`: Message data has been truncated.
$recv_data_truncated
)
)
;;; Flags provided to `sock_send`. As there are currently no flags
;;; defined, it must be set to zero.
(typename $siflags u16)
;;; Which channels on a socket to shut down.
(typename $sdflags
(flags u8
;;; Disables further receive operations.
$rd
;;; Disables further send operations.
$wr
)
)
;;; Identifiers for preopened capabilities.
(typename $preopentype
(enum u8
;;; A pre-opened directory.
$dir
)
)
;;; The contents of a $prestat when type is `preopentype::dir`.
(typename $prestat_dir
(struct
;;; The length of the directory name for use with `fd_prestat_dir_name`.
(field $pr_name_len $size)
)
)
;;; Information about a pre-opened capability.
(typename $prestat
(union $preopentype
(field $dir $prestat_dir)
)
)

View File

@@ -0,0 +1,255 @@
use proptest::prelude::*;
use wiggle_runtime::{GuestError, GuestMemory, GuestType};
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
wiggle::from_witx!({
witx: ["tests/union.witx"],
ctx: WasiCtx,
});
impl_errno!(types::Errno);
// Avoid panics on overflow
fn mult_lose_overflow(a: i32, b: u32) -> i32 {
let a_64: i64 = a as i64;
let b_64: i64 = b as i64;
let product = a_64 * b_64;
product as i32
}
// Avoid assert_eq(NaN, NaN) failures
fn mult_zero_nan(a: f32, b: u32) -> f32 {
if a.is_nan() {
0.0
} else {
let product = a * b as f32;
if product.is_nan() {
0.0
} else {
product
}
}
}
impl<'a> union_example::UnionExample for WasiCtx<'a> {
fn get_tag(&self, u: &types::Reason) -> Result<types::Excuse, types::Errno> {
println!("GET TAG: {:?}", u);
match u {
types::Reason::DogAte { .. } => Ok(types::Excuse::DogAte),
types::Reason::Traffic { .. } => Ok(types::Excuse::Traffic),
types::Reason::Sleeping { .. } => Ok(types::Excuse::Sleeping),
}
}
fn reason_mult(&self, u: &types::ReasonMut<'_>, multiply_by: u32) -> Result<(), types::Errno> {
match u {
types::ReasonMut::DogAte(fptr) => {
let val = fptr.read().expect("valid pointer");
println!("REASON MULT DogAte({})", val);
fptr.write(mult_zero_nan(val, multiply_by))
.expect("valid pointer");
}
types::ReasonMut::Traffic(iptr) => {
let val = iptr.read().expect("valid pointer");
println!("REASON MULT Traffic({})", val);
iptr.write(mult_lose_overflow(val, multiply_by))
.expect("valid pointer");
}
types::ReasonMut::Sleeping => {
println!("REASON MULT Sleeping");
}
}
Ok(())
}
}
fn reason_strat() -> impl Strategy<Value = types::Reason> {
prop_oneof![
prop::num::f32::ANY.prop_map(|v| types::Reason::DogAte(v)),
prop::num::i32::ANY.prop_map(|v| types::Reason::Traffic(v)),
Just(types::Reason::Sleeping),
]
.boxed()
}
fn reason_tag(r: &types::Reason) -> types::Excuse {
match r {
types::Reason::DogAte { .. } => types::Excuse::DogAte,
types::Reason::Traffic { .. } => types::Excuse::Traffic,
types::Reason::Sleeping { .. } => types::Excuse::Sleeping,
}
}
#[derive(Debug)]
struct GetTagExercise {
pub input: types::Reason,
pub input_loc: MemArea,
pub return_loc: MemArea,
}
impl GetTagExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
reason_strat(),
HostMemory::mem_area_strat(types::Reason::guest_size()),
HostMemory::mem_area_strat(types::Excuse::guest_size()),
)
.prop_map(|(input, input_loc, return_loc)| GetTagExercise {
input,
input_loc,
return_loc,
})
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[e.input_loc, e.return_loc])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let discriminant: u8 = reason_tag(&self.input).into();
host_memory
.ptr(self.input_loc.ptr)
.write(discriminant)
.expect("input discriminant ptr");
match self.input {
types::Reason::DogAte(f) => {
host_memory
.ptr(self.input_loc.ptr + 4)
.write(f)
.expect("input contents ref_mut");
}
types::Reason::Traffic(v) => host_memory
.ptr(self.input_loc.ptr + 4)
.write(v)
.expect("input contents ref_mut"),
types::Reason::Sleeping => {} // Do nothing
}
let e = union_example::get_tag(
&ctx,
&host_memory,
self.input_loc.ptr as i32,
self.return_loc.ptr as i32,
);
assert_eq!(e, types::Errno::Ok.into(), "get_tag errno");
let return_val: types::Excuse = host_memory
.ptr(self.return_loc.ptr)
.read()
.expect("return ref");
assert_eq!(return_val, reason_tag(&self.input), "get_tag return value");
}
}
proptest! {
#[test]
fn get_tag(e in GetTagExercise::strat()) {
e.test();
}
}
#[derive(Debug)]
struct ReasonMultExercise {
pub input: types::Reason,
pub input_loc: MemArea,
pub input_pointee_loc: MemArea,
pub multiply_by: u32,
}
impl ReasonMultExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
reason_strat(),
HostMemory::mem_area_strat(types::Reason::guest_size()),
HostMemory::mem_area_strat(4),
prop::num::u32::ANY,
)
.prop_map(
|(input, input_loc, input_pointee_loc, multiply_by)| ReasonMultExercise {
input,
input_loc,
input_pointee_loc,
multiply_by,
},
)
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[e.input_loc, e.input_pointee_loc])
})
.boxed()
}
pub fn test(&self) {
let ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let discriminant: u8 = reason_tag(&self.input).into();
host_memory
.ptr(self.input_loc.ptr)
.write(discriminant)
.expect("input discriminant ref_mut");
host_memory
.ptr(self.input_loc.ptr + 4)
.write(self.input_pointee_loc.ptr)
.expect("input pointer ref_mut");
match self.input {
types::Reason::DogAte(f) => {
host_memory
.ptr(self.input_pointee_loc.ptr)
.write(f)
.expect("input contents ref_mut");
}
types::Reason::Traffic(v) => {
host_memory
.ptr(self.input_pointee_loc.ptr)
.write(v)
.expect("input contents ref_mut");
}
types::Reason::Sleeping => {} // Do nothing
}
let e = union_example::reason_mult(
&ctx,
&host_memory,
self.input_loc.ptr as i32,
self.multiply_by as i32,
);
assert_eq!(e, types::Errno::Ok.into(), "reason_mult errno");
match self.input {
types::Reason::DogAte(f) => {
let f_result: f32 = host_memory
.ptr(self.input_pointee_loc.ptr)
.read()
.expect("input contents ref_mut");
assert_eq!(
mult_zero_nan(f, self.multiply_by),
f_result,
"DogAte result"
)
}
types::Reason::Traffic(v) => {
let v_result: i32 = host_memory
.ptr(self.input_pointee_loc.ptr)
.read()
.expect("input contents ref_mut");
assert_eq!(
mult_lose_overflow(v, self.multiply_by),
v_result,
"Traffic result"
)
}
types::Reason::Sleeping => {} // Do nothing
}
}
}
proptest! {
#[test]
fn reason_mult(e in ReasonMultExercise::strat()) {
e.test();
}
}

View File

@@ -0,0 +1,31 @@
(use "errno.witx")
(use "excuse.witx")
;; Every worker needs a union. Organize your workplace!
;; Fight for the full product of your labor!
(typename $reason
(union $excuse
(field $dog_ate f32)
(field $traffic s32)
(empty $sleeping)))
(typename $reason_mut
(union $excuse
(field $dog_ate (@witx pointer f32))
(field $traffic (@witx pointer s32))
(empty $sleeping)))
(module $union_example
(@interface func (export "get_tag")
(param $r $reason)
(result $error $errno)
(result $t $excuse)
)
(@interface func (export "reason_mult")
(param $r $reason_mut)
(param $multiply_by u32)
(result $error $errno)
)
)

342
crates/wiggle/tests/wasi.rs Normal file
View File

@@ -0,0 +1,342 @@
use wiggle_runtime::{GuestBorrows, GuestError, GuestErrorType, GuestPtr};
use wiggle_test::WasiCtx;
wiggle::from_witx!({
witx: ["tests/wasi.witx"],
ctx: WasiCtx,
});
type Result<T> = std::result::Result<T, types::Errno>;
impl<'a> GuestErrorType<'a> for types::Errno {
type Context = WasiCtx<'a>;
fn success() -> types::Errno {
types::Errno::Success
}
fn from_error(e: GuestError, ctx: &Self::Context) -> types::Errno {
eprintln!("GUEST ERROR: {:?}", e);
ctx.guest_errors.borrow_mut().push(e);
types::Errno::Io
}
}
impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
fn args_get(&self, _argv: GuestPtr<GuestPtr<u8>>, _argv_buf: GuestPtr<u8>) -> Result<()> {
unimplemented!("args_get")
}
fn args_sizes_get(&self) -> Result<(types::Size, types::Size)> {
unimplemented!("args_sizes_get")
}
fn environ_get(
&self,
_environ: GuestPtr<GuestPtr<u8>>,
_environ_buf: GuestPtr<u8>,
) -> Result<()> {
unimplemented!("environ_get")
}
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size)> {
unimplemented!("environ_sizes_get")
}
fn clock_res_get(&self, _id: types::Clockid) -> Result<types::Timestamp> {
unimplemented!("clock_res_get")
}
fn clock_time_get(
&self,
_id: types::Clockid,
_precision: types::Timestamp,
) -> Result<types::Timestamp> {
unimplemented!("clock_time_get")
}
fn fd_advise(
&self,
_fd: types::Fd,
_offset: types::Filesize,
_len: types::Filesize,
_advice: types::Advice,
) -> Result<()> {
unimplemented!("fd_advise")
}
fn fd_allocate(
&self,
_fd: types::Fd,
_offset: types::Filesize,
_len: types::Filesize,
) -> Result<()> {
unimplemented!("fd_allocate")
}
fn fd_close(&self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_close")
}
fn fd_datasync(&self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_datasync")
}
fn fd_fdstat_get(&self, _fd: types::Fd) -> Result<types::Fdstat> {
unimplemented!("fd_fdstat_get")
}
fn fd_fdstat_set_flags(&self, _fd: types::Fd, _flags: types::Fdflags) -> Result<()> {
unimplemented!("fd_fdstat_set_flags")
}
fn fd_fdstat_set_rights(
&self,
_fd: types::Fd,
_fs_rights_base: types::Rights,
_fs_rights_inherting: types::Rights,
) -> Result<()> {
unimplemented!("fd_fdstat_set_rights")
}
fn fd_filestat_get(&self, _fd: types::Fd) -> Result<types::Filestat> {
unimplemented!("fd_filestat_get")
}
fn fd_filestat_set_size(&self, _fd: types::Fd, _size: types::Filesize) -> Result<()> {
unimplemented!("fd_filestat_set_size")
}
fn fd_filestat_set_times(
&self,
_fd: types::Fd,
_atim: types::Timestamp,
_mtim: types::Timestamp,
_fst_flags: types::Fstflags,
) -> Result<()> {
unimplemented!("fd_filestat_set_times")
}
fn fd_pread(
&self,
_fd: types::Fd,
iovs: &types::IovecArray<'_>,
_offset: types::Filesize,
) -> Result<types::Size> {
// This is not functional code, but the type annotations demonstrate
// that we can use the wiggle API to create the datastructures we want
// for efficient implementation of this function elsewhere.
let mut bc = GuestBorrows::new();
let mut slices: Vec<&'_ mut [u8]> = Vec::new();
// Mark the iov elements as borrowed, to ensure that they does not
// overlap with any of the as_raw regions.
bc.borrow_slice(&iovs).expect("borrow iovec array");
for iov_ptr in iovs.iter() {
let iov_ptr = iov_ptr.expect("iovec element pointer is valid");
let iov: types::Iovec = iov_ptr.read().expect("read iovec element");
let base: GuestPtr<u8> = iov.buf;
let len: u32 = iov.buf_len;
let buf: GuestPtr<[u8]> = base.as_array(len);
let slice = buf.as_raw(&mut bc).expect("borrow slice from iovec");
slices.push(unsafe { &mut *slice });
}
println!("iovec slices: {:?}", slices);
unimplemented!("fd_pread")
}
fn fd_prestat_get(&self, _fd: types::Fd) -> Result<types::Prestat> {
unimplemented!("fd_prestat_get")
}
fn fd_prestat_dir_name(
&self,
_fd: types::Fd,
_path: GuestPtr<u8>,
_path_len: types::Size,
) -> Result<()> {
unimplemented!("fd_prestat_dir_name")
}
fn fd_pwrite(
&self,
_fd: types::Fd,
_ciovs: &types::CiovecArray<'_>,
_offset: types::Filesize,
) -> Result<types::Size> {
unimplemented!("fd_pwrite")
}
fn fd_read(&self, _fd: types::Fd, _iovs: &types::IovecArray<'_>) -> Result<types::Size> {
unimplemented!("fd_read")
}
fn fd_readdir(
&self,
_fd: types::Fd,
_buf: GuestPtr<u8>,
_buf_len: types::Size,
_cookie: types::Dircookie,
) -> Result<types::Size> {
unimplemented!("fd_readdir")
}
fn fd_renumber(&self, _fd: types::Fd, _to: types::Fd) -> Result<()> {
unimplemented!("fd_renumber")
}
fn fd_seek(
&self,
_fd: types::Fd,
_offset: types::Filedelta,
_whence: types::Whence,
) -> Result<types::Filesize> {
unimplemented!("fd_seek")
}
fn fd_sync(&self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_sync")
}
fn fd_tell(&self, _fd: types::Fd) -> Result<types::Filesize> {
unimplemented!("fd_tell")
}
fn fd_write(&self, _fd: types::Fd, _ciovs: &types::CiovecArray<'_>) -> Result<types::Size> {
unimplemented!("fd_write")
}
fn path_create_directory(&self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_create_directory")
}
fn path_filestat_get(
&self,
_fd: types::Fd,
_flags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
) -> Result<types::Filestat> {
unimplemented!("path_filestat_get")
}
fn path_filestat_set_times(
&self,
_fd: types::Fd,
_flags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
_atim: types::Timestamp,
_mtim: types::Timestamp,
_fst_flags: types::Fstflags,
) -> Result<()> {
unimplemented!("path_filestat_set_times")
}
fn path_link(
&self,
_old_fd: types::Fd,
_old_flags: types::Lookupflags,
_old_path: &GuestPtr<'_, str>,
_new_fd: types::Fd,
_new_path: &GuestPtr<'_, str>,
) -> Result<()> {
unimplemented!("path_link")
}
fn path_open(
&self,
_fd: types::Fd,
_dirflags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
_oflags: types::Oflags,
_fs_rights_base: types::Rights,
_fs_rights_inherting: types::Rights,
_fdflags: types::Fdflags,
) -> Result<types::Fd> {
unimplemented!("path_open")
}
fn path_readlink(
&self,
_fd: types::Fd,
_path: &GuestPtr<'_, str>,
_buf: GuestPtr<u8>,
_buf_len: types::Size,
) -> Result<types::Size> {
unimplemented!("path_readlink")
}
fn path_remove_directory(&self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_remove_directory")
}
fn path_rename(
&self,
_fd: types::Fd,
_old_path: &GuestPtr<'_, str>,
_new_fd: types::Fd,
_new_path: &GuestPtr<'_, str>,
) -> Result<()> {
unimplemented!("path_rename")
}
fn path_symlink(
&self,
_old_path: &GuestPtr<'_, str>,
_fd: types::Fd,
_new_path: &GuestPtr<'_, str>,
) -> Result<()> {
unimplemented!("path_symlink")
}
fn path_unlink_file(&self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_unlink_file")
}
fn poll_oneoff(
&self,
_in_: GuestPtr<types::Subscription>,
_out: GuestPtr<types::Event>,
_nsubscriptions: types::Size,
) -> Result<types::Size> {
unimplemented!("poll_oneoff")
}
fn proc_exit(&self, _rval: types::Exitcode) -> std::result::Result<(), ()> {
unimplemented!("proc_exit")
}
fn proc_raise(&self, _sig: types::Signal) -> Result<()> {
unimplemented!("proc_raise")
}
fn sched_yield(&self) -> Result<()> {
unimplemented!("sched_yield")
}
fn random_get(&self, _buf: GuestPtr<u8>, _buf_len: types::Size) -> Result<()> {
unimplemented!("random_get")
}
fn sock_recv(
&self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags)> {
unimplemented!("sock_recv")
}
fn sock_send(
&self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_flags: types::Siflags,
) -> Result<types::Size> {
unimplemented!("sock_send")
}
fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> {
unimplemented!("sock_shutdown")
}
}

View File

@@ -0,0 +1,532 @@
;; WASI Preview. This is an evolution of the API that WASI initially
;; launched with.
;;
;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi).
;;
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
(use "typenames.witx")
(module $wasi_snapshot_preview1
;;; Linear memory to be accessed by WASI functions that need it.
(import "memory" (memory))
;;; Read command-line argument data.
;;; The size of the array should match that returned by `args_sizes_get`
(@interface func (export "args_get")
(param $argv (@witx pointer (@witx pointer u8)))
(param $argv_buf (@witx pointer u8))
(result $error $errno)
)
;;; Return command-line argument data sizes.
(@interface func (export "args_sizes_get")
(result $error $errno)
;;; The number of arguments.
(result $argc $size)
;;; The size of the argument string data.
(result $argv_buf_size $size)
)
;;; Read environment variable data.
;;; The sizes of the buffers should match that returned by `environ_sizes_get`.
(@interface func (export "environ_get")
(param $environ (@witx pointer (@witx pointer u8)))
(param $environ_buf (@witx pointer u8))
(result $error $errno)
)
;;; Return environment variable data sizes.
(@interface func (export "environ_sizes_get")
(result $error $errno)
;;; The number of environment variable arguments.
(result $environc $size)
;;; The size of the environment variable data.
(result $environ_buf_size $size)
)
;;; Return the resolution of a clock.
;;; Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks,
;;; return `errno::inval`.
;;; Note: This is similar to `clock_getres` in POSIX.
(@interface func (export "clock_res_get")
;;; The clock for which to return the resolution.
(param $id $clockid)
(result $error $errno)
;;; The resolution of the clock.
(result $resolution $timestamp)
)
;;; Return the time value of a clock.
;;; Note: This is similar to `clock_gettime` in POSIX.
(@interface func (export "clock_time_get")
;;; The clock for which to return the time.
(param $id $clockid)
;;; The maximum lag (exclusive) that the returned time value may have, compared to its actual value.
(param $precision $timestamp)
(result $error $errno)
;;; The time value of the clock.
(result $time $timestamp)
)
;;; Provide file advisory information on a file descriptor.
;;; Note: This is similar to `posix_fadvise` in POSIX.
(@interface func (export "fd_advise")
(param $fd $fd)
;;; The offset within the file to which the advisory applies.
(param $offset $filesize)
;;; The length of the region to which the advisory applies.
(param $len $filesize)
;;; The advice.
(param $advice $advice)
(result $error $errno)
)
;;; Force the allocation of space in a file.
;;; Note: This is similar to `posix_fallocate` in POSIX.
(@interface func (export "fd_allocate")
(param $fd $fd)
;;; The offset at which to start the allocation.
(param $offset $filesize)
;;; The length of the area that is allocated.
(param $len $filesize)
(result $error $errno)
)
;;; Close a file descriptor.
;;; Note: This is similar to `close` in POSIX.
(@interface func (export "fd_close")
(param $fd $fd)
(result $error $errno)
)
;;; Synchronize the data of a file to disk.
;;; Note: This is similar to `fdatasync` in POSIX.
(@interface func (export "fd_datasync")
(param $fd $fd)
(result $error $errno)
)
;;; Get the attributes of a file descriptor.
;;; Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.
(@interface func (export "fd_fdstat_get")
(param $fd $fd)
(result $error $errno)
;;; The buffer where the file descriptor's attributes are stored.
(result $stat $fdstat)
)
;;; Adjust the flags associated with a file descriptor.
;;; Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.
(@interface func (export "fd_fdstat_set_flags")
(param $fd $fd)
;;; The desired values of the file descriptor flags.
(param $flags $fdflags)
(result $error $errno)
)
;;; Adjust the rights associated with a file descriptor.
;;; This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights
(@interface func (export "fd_fdstat_set_rights")
(param $fd $fd)
;;; The desired rights of the file descriptor.
(param $fs_rights_base $rights)
(param $fs_rights_inheriting $rights)
(result $error $errno)
)
;;; Return the attributes of an open file.
(@interface func (export "fd_filestat_get")
(param $fd $fd)
(result $error $errno)
;;; The buffer where the file's attributes are stored.
(result $buf $filestat)
)
;;; Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.
;;; Note: This is similar to `ftruncate` in POSIX.
(@interface func (export "fd_filestat_set_size")
(param $fd $fd)
;;; The desired file size.
(param $size $filesize)
(result $error $errno)
)
;;; Adjust the timestamps of an open file or directory.
;;; Note: This is similar to `futimens` in POSIX.
(@interface func (export "fd_filestat_set_times")
(param $fd $fd)
;;; The desired values of the data access timestamp.
(param $atim $timestamp)
;;; The desired values of the data modification timestamp.
(param $mtim $timestamp)
;;; A bitmask indicating which timestamps to adjust.
(param $fst_flags $fstflags)
(result $error $errno)
)
;;; Read from a file descriptor, without using and updating the file descriptor's offset.
;;; Note: This is similar to `preadv` in POSIX.
(@interface func (export "fd_pread")
(param $fd $fd)
;;; List of scatter/gather vectors in which to store data.
(param $iovs $iovec_array)
;;; The offset within the file at which to read.
(param $offset $filesize)
(result $error $errno)
;;; The number of bytes read.
(result $nread $size)
)
;;; Return a description of the given preopened file descriptor.
(@interface func (export "fd_prestat_get")
(param $fd $fd)
(result $error $errno)
;;; The buffer where the description is stored.
(result $buf $prestat)
)
;;; Return a description of the given preopened file descriptor.
(@interface func (export "fd_prestat_dir_name")
(param $fd $fd)
;;; A buffer into which to write the preopened directory name.
(param $path (@witx pointer u8))
(param $path_len $size)
(result $error $errno)
)
;;; Write to a file descriptor, without using and updating the file descriptor's offset.
;;; Note: This is similar to `pwritev` in POSIX.
(@interface func (export "fd_pwrite")
(param $fd $fd)
;;; List of scatter/gather vectors from which to retrieve data.
(param $iovs $ciovec_array)
;;; The offset within the file at which to write.
(param $offset $filesize)
(result $error $errno)
;;; The number of bytes written.
(result $nwritten $size)
)
;;; Read from a file descriptor.
;;; Note: This is similar to `readv` in POSIX.
(@interface func (export "fd_read")
(param $fd $fd)
;;; List of scatter/gather vectors to which to store data.
(param $iovs $iovec_array)
(result $error $errno)
;;; The number of bytes read.
(result $nread $size)
)
;;; Read directory entries from a directory.
;;; When successful, the contents of the output buffer consist of a sequence of
;;; directory entries. Each directory entry consists of a dirent_t object,
;;; followed by dirent_t::d_namlen bytes holding the name of the directory
;;; entry.
;;
;;; This function fills the output buffer as much as possible, potentially
;;; truncating the last directory entry. This allows the caller to grow its
;;; read buffer size in case it's too small to fit a single large directory
;;; entry, or skip the oversized directory entry.
(@interface func (export "fd_readdir")
(param $fd $fd)
;;; The buffer where directory entries are stored
(param $buf (@witx pointer u8))
(param $buf_len $size)
;;; The location within the directory to start reading
(param $cookie $dircookie)
(result $error $errno)
;;; The number of bytes stored in the read buffer. If less than the size of the read buffer, the end of the directory has been reached.
(result $bufused $size)
)
;;; Atomically replace a file descriptor by renumbering another file descriptor.
;;
;;; Due to the strong focus on thread safety, this environment does not provide
;;; a mechanism to duplicate or renumber a file descriptor to an arbitrary
;;; number, like `dup2()`. This would be prone to race conditions, as an actual
;;; file descriptor with the same number could be allocated by a different
;;; thread at the same time.
;;
;;; This function provides a way to atomically renumber file descriptors, which
;;; would disappear if `dup2()` were to be removed entirely.
(@interface func (export "fd_renumber")
(param $fd $fd)
;;; The file descriptor to overwrite.
(param $to $fd)
(result $error $errno)
)
;;; Move the offset of a file descriptor.
;;; Note: This is similar to `lseek` in POSIX.
(@interface func (export "fd_seek")
(param $fd $fd)
;;; The number of bytes to move.
(param $offset $filedelta)
;;; The base from which the offset is relative.
(param $whence $whence)
(result $error $errno)
;;; The new offset of the file descriptor, relative to the start of the file.
(result $newoffset $filesize)
)
;;; Synchronize the data and metadata of a file to disk.
;;; Note: This is similar to `fsync` in POSIX.
(@interface func (export "fd_sync")
(param $fd $fd)
(result $error $errno)
)
;;; Return the current offset of a file descriptor.
;;; Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.
(@interface func (export "fd_tell")
(param $fd $fd)
(result $error $errno)
;;; The current offset of the file descriptor, relative to the start of the file.
(result $offset $filesize)
)
;;; Write to a file descriptor.
;;; Note: This is similar to `writev` in POSIX.
(@interface func (export "fd_write")
(param $fd $fd)
;;; List of scatter/gather vectors from which to retrieve data.
(param $iovs $ciovec_array)
(result $error $errno)
;;; The number of bytes written.
(result $nwritten $size)
)
;;; Create a directory.
;;; Note: This is similar to `mkdirat` in POSIX.
(@interface func (export "path_create_directory")
(param $fd $fd)
;;; The path at which to create the directory.
(param $path string)
(result $error $errno)
)
;;; Return the attributes of a file or directory.
;;; Note: This is similar to `stat` in POSIX.
(@interface func (export "path_filestat_get")
(param $fd $fd)
;;; Flags determining the method of how the path is resolved.
(param $flags $lookupflags)
;;; The path of the file or directory to inspect.
(param $path string)
(result $error $errno)
;;; The buffer where the file's attributes are stored.
(result $buf $filestat)
)
;;; Adjust the timestamps of a file or directory.
;;; Note: This is similar to `utimensat` in POSIX.
(@interface func (export "path_filestat_set_times")
(param $fd $fd)
;;; Flags determining the method of how the path is resolved.
(param $flags $lookupflags)
;;; The path of the file or directory to operate on.
(param $path string)
;;; The desired values of the data access timestamp.
(param $atim $timestamp)
;;; The desired values of the data modification timestamp.
(param $mtim $timestamp)
;;; A bitmask indicating which timestamps to adjust.
(param $fst_flags $fstflags)
(result $error $errno)
)
;;; Create a hard link.
;;; Note: This is similar to `linkat` in POSIX.
(@interface func (export "path_link")
(param $old_fd $fd)
;;; Flags determining the method of how the path is resolved.
(param $old_flags $lookupflags)
;;; The source path from which to link.
(param $old_path string)
;;; The working directory at which the resolution of the new path starts.
(param $new_fd $fd)
;;; The destination path at which to create the hard link.
(param $new_path string)
(result $error $errno)
)
;;; Open a file or directory.
;;
;;; The returned file descriptor is not guaranteed to be the lowest-numbered
;;; file descriptor not currently open; it is randomized to prevent
;;; applications from depending on making assumptions about indexes, since this
;;; is error-prone in multi-threaded contexts. The returned file descriptor is
;;; guaranteed to be less than 2**31.
;;
;;; Note: This is similar to `openat` in POSIX.
(@interface func (export "path_open")
(param $fd $fd)
;;; Flags determining the method of how the path is resolved.
(param $dirflags $lookupflags)
;;; The relative path of the file or directory to open, relative to the
;;; `path_open::fd` directory.
(param $path string)
;;; The method by which to open the file.
(param $oflags $oflags)
;;; The initial rights of the newly created file descriptor. The
;;; implementation is allowed to return a file descriptor with fewer rights
;;; than specified, if and only if those rights do not apply to the type of
;;; file being opened.
;;
;;; The *base* rights are rights that will apply to operations using the file
;;; descriptor itself, while the *inheriting* rights are rights that apply to
;;; file descriptors derived from it.
(param $fs_rights_base $rights)
(param $fs_rights_inherting $rights)
(param $fdflags $fdflags)
(result $error $errno)
;;; The file descriptor of the file that has been opened.
(result $opened_fd $fd)
)
;;; Read the contents of a symbolic link.
;;; Note: This is similar to `readlinkat` in POSIX.
(@interface func (export "path_readlink")
(param $fd $fd)
;;; The path of the symbolic link from which to read.
(param $path string)
;;; The buffer to which to write the contents of the symbolic link.
(param $buf (@witx pointer u8))
(param $buf_len $size)
(result $error $errno)
;;; The number of bytes placed in the buffer.
(result $bufused $size)
)
;;; Remove a directory.
;;; Return `errno::notempty` if the directory is not empty.
;;; Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.
(@interface func (export "path_remove_directory")
(param $fd $fd)
;;; The path to a directory to remove.
(param $path string)
(result $error $errno)
)
;;; Rename a file or directory.
;;; Note: This is similar to `renameat` in POSIX.
(@interface func (export "path_rename")
(param $fd $fd)
;;; The source path of the file or directory to rename.
(param $old_path string)
;;; The working directory at which the resolution of the new path starts.
(param $new_fd $fd)
;;; The destination path to which to rename the file or directory.
(param $new_path string)
(result $error $errno)
)
;;; Create a symbolic link.
;;; Note: This is similar to `symlinkat` in POSIX.
(@interface func (export "path_symlink")
;;; The contents of the symbolic link.
(param $old_path string)
(param $fd $fd)
;;; The destination path at which to create the symbolic link.
(param $new_path string)
(result $error $errno)
)
;;; Unlink a file.
;;; Return `errno::isdir` if the path refers to a directory.
;;; Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.
(@interface func (export "path_unlink_file")
(param $fd $fd)
;;; The path to a file to unlink.
(param $path string)
(result $error $errno)
)
;;; Concurrently poll for the occurrence of a set of events.
(@interface func (export "poll_oneoff")
;;; The events to which to subscribe.
(param $in (@witx const_pointer $subscription))
;;; The events that have occurred.
(param $out (@witx pointer $event))
;;; Both the number of subscriptions and events.
(param $nsubscriptions $size)
(result $error $errno)
;;; The number of events stored.
(result $nevents $size)
)
;;; Terminate the process normally. An exit code of 0 indicates successful
;;; termination of the program. The meanings of other values is dependent on
;;; the environment.
(@interface func (export "proc_exit")
;;; The exit code returned by the process.
(param $rval $exitcode)
)
;;; Send a signal to the process of the calling thread.
;;; Note: This is similar to `raise` in POSIX.
(@interface func (export "proc_raise")
;;; The signal condition to trigger.
(param $sig $signal)
(result $error $errno)
)
;;; Temporarily yield execution of the calling thread.
;;; Note: This is similar to `sched_yield` in POSIX.
(@interface func (export "sched_yield")
(result $error $errno)
)
;;; Write high-quality random data into a buffer.
;;; This function blocks when the implementation is unable to immediately
;;; provide sufficient high-quality random data.
;;; This function may execute slowly, so when large mounts of random data are
;;; required, it's advisable to use this function to seed a pseudo-random
;;; number generator, rather than to provide the random data directly.
(@interface func (export "random_get")
;;; The buffer to fill with random data.
(param $buf (@witx pointer u8))
(param $buf_len $size)
(result $error $errno)
)
;;; Receive a message from a socket.
;;; Note: This is similar to `recv` in POSIX, though it also supports reading
;;; the data into multiple buffers in the manner of `readv`.
(@interface func (export "sock_recv")
(param $fd $fd)
;;; List of scatter/gather vectors to which to store data.
(param $ri_data $iovec_array)
;;; Message flags.
(param $ri_flags $riflags)
(result $error $errno)
;;; Number of bytes stored in ri_data.
(result $ro_datalen $size)
;;; Message flags.
(result $ro_flags $roflags)
)
;;; Send a message on a socket.
;;; Note: This is similar to `send` in POSIX, though it also supports writing
;;; the data from multiple buffers in the manner of `writev`.
(@interface func (export "sock_send")
(param $fd $fd)
;;; List of scatter/gather vectors to which to retrieve data
(param $si_data $ciovec_array)
;;; Message flags.
(param $si_flags $siflags)
(result $error $errno)
;;; Number of bytes transmitted.
(result $so_datalen $size)
)
;;; Shut down socket send and receive channels.
;;; Note: This is similar to `shutdown` in POSIX.
(@interface func (export "sock_shutdown")
(param $fd $fd)
;;; Which channels on the socket to shut down.
(param $how $sdflags)
(result $error $errno)
)
)