It's wiggle time! (#1202)

* Use wiggle in place of wig in wasi-common

This is a rather massive commit that introduces `wiggle` into the
picture. We still use `wig`'s macro in `old` snapshot and to generate
`wasmtime-wasi` glue, but everything else is now autogenerated by `wiggle`.
In summary, thanks to `wiggle`, we no longer need to worry about
serialising and deserialising to and from the guest memory, and
all guest (WASI) types are now proper idiomatic Rust types.

While we're here, in preparation for the ephemeral snapshot, I went
ahead and reorganised the internal structure of the crate. Instead of
modules like `hostcalls_impl` or `hostcalls_impl::fs`, the structure
now resembles that in ephemeral with modules like `path`, `fd`, etc.
Now, I'm not requiring we leave it like this, but I reckon it looks
cleaner this way after all.

* Fix wig to use new first-class access to caller's mem

* Ignore warning in proc_exit for the moment

* Group unsafes together in args and environ calls

* Simplify pwrite; more unsafe blocks

* Simplify fd_read

* Bundle up unsafes in fd_readdir

* Simplify fd_write

* Add comment to path_readlink re zero-len buffers

* Simplify unsafes in random_get

* Hide GuestPtr<str> to &str in path::get

* Rewrite pread and pwrite using SeekFrom and read/write_vectored

I've left the implementation of VirtualFs pretty much untouched
as I don't feel that comfortable in changing the API too much.
Having said that, I reckon `pread` and `pwrite` could be refactored
out, and `preadv` and `pwritev` could be entirely rewritten using
`seek` and `read_vectored` and `write_vectored`.

* Add comment about VirtFs unsafety

* Fix all mentions of FdEntry to Entry

* Fix warnings on Win

* Add aux struct EntryTable responsible for Fds and Entries

This commit adds aux struct `EntryTable` which is private to `WasiCtx`
and is basically responsible for `Fd` alloc/dealloc as well as storing
matching `Entry`s. This struct is entirely private to `WasiCtx` and
as such as should remain transparent to `WasiCtx` users.

* Remove redundant check for empty buffer in path_readlink

* Preserve and rewind file cursor in pread/pwrite

* Use GuestPtr<[u8]>::copy_from_slice wherever copying bytes directly

* Use GuestPtr<[u8]>::copy_from_slice in fd_readdir

* Clean up unsafes around WasiCtx accessors

* Fix bugs in args_get and environ_get

* Fix conflicts after rebase
This commit is contained in:
Jakub Konka
2020-03-20 21:54:44 +01:00
committed by GitHub
parent f700efeb03
commit 32595faba5
62 changed files with 4293 additions and 5027 deletions

View File

@@ -195,7 +195,7 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
let memory = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
let e = wasi_common::wasi::__WASI_ERRNO_INVAL;
let e = wasi_common::old::snapshot_0::wasi::__WASI_ERRNO_INVAL;
#handle_early_error
}
};
@@ -252,3 +252,255 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
}
}
}
pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream {
let (path, _phase) = utils::witx_path_from_args(args);
let doc = match witx::load(&[&path]) {
Ok(doc) => doc,
Err(e) => {
panic!("error opening file {}: {}", path, e);
}
};
let mut fields = Vec::new();
let mut get_exports = Vec::new();
let mut ctor_externs = Vec::new();
let mut ctor_fields = Vec::new();
for module in doc.modules() {
let module_id = Ident::new(module.name.as_str(), Span::call_site());
for func in module.funcs() {
let name = func.name.as_str();
let name_ident = Ident::new(func.name.as_str(), Span::call_site());
fields.push(quote! { pub #name_ident: wasmtime::Func });
get_exports.push(quote! { #name => Some(&self.#name_ident) });
ctor_fields.push(name_ident.clone());
let mut shim_arg_decls = Vec::new();
let mut params = Vec::new();
let mut formats = Vec::new();
let mut format_args = Vec::new();
let mut hostcall_args = Vec::new();
for param in func.params.iter() {
let name = utils::param_name(param);
// Registers a new parameter to the shim we're making with the
// given `name`, the `abi_ty` wasm type and `hex` defines
// whether it's debug-printed in a hex format or not.
//
// This will register a whole bunch of things:
//
// * The cranelift type for the parameter
// * Syntax to specify the actual function parameter
// * How to log the parameter value in a call to `trace!`
// * How to actually pass this argument to the host
// implementation, converting as necessary.
let mut add_param = |name: &Ident, abi_ty: Abi, hex: bool| {
match abi_ty {
Abi::I32 => {
params.push(quote! { types::I32 });
shim_arg_decls.push(quote! { #name: i32 });
}
Abi::I64 => {
params.push(quote! { types::I64 });
shim_arg_decls.push(quote! { #name: i64 });
}
Abi::F32 => {
params.push(quote! { types::F32 });
shim_arg_decls.push(quote! { #name: f32 });
}
Abi::F64 => {
params.push(quote! { types::F64 });
shim_arg_decls.push(quote! { #name: f64 });
}
}
formats.push(format!("{}={}", name, if hex { "{:#x}" } else { "{}" },));
format_args.push(name.clone());
hostcall_args.push(quote! { #name as _ });
};
match &*param.tref.type_() {
witx::Type::Int(e) => match e.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64, false),
_ => add_param(&name, Abi::I32, false),
},
witx::Type::Enum(e) => match e.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64, false),
_ => add_param(&name, Abi::I32, false),
},
witx::Type::Flags(f) => match f.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64, true),
_ => add_param(&name, Abi::I32, true),
},
witx::Type::Builtin(witx::BuiltinType::Char8)
| witx::Type::Builtin(witx::BuiltinType::S8)
| witx::Type::Builtin(witx::BuiltinType::U8)
| witx::Type::Builtin(witx::BuiltinType::S16)
| witx::Type::Builtin(witx::BuiltinType::U16)
| witx::Type::Builtin(witx::BuiltinType::S32)
| witx::Type::Builtin(witx::BuiltinType::U32)
| witx::Type::Builtin(witx::BuiltinType::USize) => {
add_param(&name, Abi::I32, false);
}
witx::Type::Builtin(witx::BuiltinType::S64)
| witx::Type::Builtin(witx::BuiltinType::U64) => {
add_param(&name, Abi::I64, false);
}
witx::Type::Builtin(witx::BuiltinType::F32) => {
add_param(&name, Abi::F32, false);
}
witx::Type::Builtin(witx::BuiltinType::F64) => {
add_param(&name, Abi::F64, false);
}
// strings/arrays have an extra ABI parameter for the length
// of the array passed.
witx::Type::Builtin(witx::BuiltinType::String) | witx::Type::Array(_) => {
add_param(&name, Abi::I32, true);
let len = format_ident!("{}_len", name);
add_param(&len, Abi::I32, false);
}
witx::Type::ConstPointer(_)
| witx::Type::Handle(_)
| witx::Type::Pointer(_) => {
add_param(&name, Abi::I32, true);
}
witx::Type::Struct(_) | witx::Type::Union(_) => {
panic!("unsupported argument type")
}
}
}
let mut results = func.results.iter();
let mut ret_ty = quote! { () };
let mut cvt_ret = quote! {};
let mut returns = Vec::new();
let mut handle_early_error = quote! { panic!("error: {:?}", e) };
// The first result is returned bare right now...
if let Some(ret) = results.next() {
handle_early_error = quote! { return e.into() };
match &*ret.tref.type_() {
// Eventually we'll want to add support for more returned
// types, but for now let's just conform to what `*.witx`
// definitions currently use.
witx::Type::Enum(e) => match e.repr {
witx::IntRepr::U16 => {
returns.push(quote! { types::I32 });
ret_ty = quote! { i32 };
cvt_ret = quote! { .into() }
}
other => panic!("unsupported ret enum repr {:?}", other),
},
other => panic!("unsupported first return {:?}", other),
}
}
// ... and all remaining results are returned via out-poiners
for result in results {
let name = format_ident!("{}", result.name.as_str());
params.push(quote! { types::I32 });
shim_arg_decls.push(quote! { #name: i32 });
formats.push(format!("{}={{:#x}}", name));
format_args.push(name.clone());
hostcall_args.push(quote! { #name });
}
let format_str = format!("{}({})", name, formats.join(", "));
ctor_externs.push(quote! {
let my_cx = cx.clone();
let #name_ident = wasmtime::Func::wrap(
store,
move |caller: wasmtime::Caller<'_> #(,#shim_arg_decls)*| -> #ret_ty {
log::trace!(
#format_str,
#(#format_args),*
);
unsafe {
let mem = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
let e = wasi_common::wasi::Errno::Inval;
#handle_early_error
}
};
let mem: WasiMemory = mem.into();
wasi_common::wasi::#module_id::#name_ident(
&mut my_cx.borrow_mut(),
&mem,
#(#hostcall_args),*
) #cvt_ret
}
}
);
});
}
}
quote! {
/// Lightweight `wasmtime::Memory` wrapper so that we can
/// implement `wiggle_runtime::GuestMemory` trait on it which is
/// now required to interface with `wasi-common`.
struct WasiMemory(wasmtime::Memory);
impl From<wasmtime::Memory> for WasiMemory {
fn from(mem: wasmtime::Memory) -> Self {
Self(mem)
}
}
unsafe impl wiggle_runtime::GuestMemory for WasiMemory {
fn base(&self) -> (*mut u8, u32) {
(self.0.data_ptr(), self.0.data_size() as _)
}
}
/// An instantiated instance of the wasi exports.
///
/// This represents a wasi module which can be used to instantiate other
/// wasm modules. This structure exports all that various fields of the
/// wasi instance as fields which can be used to implement your own
/// instantiation logic, if necessary. Additionally [`Wasi::get_export`]
/// can be used to do name-based resolution.
pub struct Wasi {
#(#fields,)*
}
impl Wasi {
/// Creates a new [`Wasi`] instance.
///
/// External values are allocated into the `store` provided and
/// configuration of the wasi instance itself should be all
/// contained in the `cx` parameter.
pub fn new(store: &wasmtime::Store, cx: WasiCtx) -> Wasi {
let cx = std::rc::Rc::new(std::cell::RefCell::new(cx));
#(#ctor_externs)*
Wasi {
#(#ctor_fields,)*
}
}
/// Looks up a field called `name` in this structure, returning it
/// if found.
///
/// This is often useful when instantiating a `wasmtime` instance
/// where name resolution often happens with strings.
pub fn get_export(&self, name: &str) -> Option<&wasmtime::Func> {
match name {
#(#get_exports,)*
_ => None,
}
}
}
}
}