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