From f1e5cd39d8cb1c1e348f816703dac04c1450c92d Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 12 Nov 2019 12:54:28 -0800 Subject: [PATCH 1/5] Add support for new WASI snapshot, backwards compat too --- Cargo.lock | 1 - lib/wasi/Cargo.toml | 5 + lib/wasi/src/lib.rs | 87 ++++++++++- lib/wasi/src/syscalls/legacy/mod.rs | 6 + lib/wasi/src/syscalls/legacy/snapshot0.rs | 178 ++++++++++++++++++++++ lib/wasi/src/syscalls/mod.rs | 3 + lib/wasi/src/syscalls/types.rs | 66 +++++++- lib/wasi/src/utils.rs | 45 ++++-- src/bin/wasmer.rs | 49 +++--- 9 files changed, 399 insertions(+), 41 deletions(-) create mode 100644 lib/wasi/src/syscalls/legacy/mod.rs create mode 100644 lib/wasi/src/syscalls/legacy/snapshot0.rs diff --git a/Cargo.lock b/Cargo.lock index 0720c9eeb..705efb20b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1504,7 +1504,6 @@ dependencies = [ "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-backend 0.10.1", - "wasmer-llvm-backend 0.10.1", "wasmer-runtime-core 0.10.1", "wasmer-singlepass-backend 0.10.1", ] diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index 8a85acc2d..e413c313e 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -21,3 +21,8 @@ wasmer-runtime-core = { path = "../runtime-core", version = "0.10.1" } [target.'cfg(windows)'.dependencies] winapi = "0.3" + +[features] +# Enable support for the older snapshot0 WASI modules +snapshot0 = [] +default = ["snapshot0"] \ No newline at end of file diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 978517c7c..a40987b2b 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -37,7 +37,7 @@ use self::syscalls::*; use std::ffi::c_void; use std::path::PathBuf; -pub use self::utils::is_wasi_module; +pub use self::utils::{get_wasi_version, is_wasi_module, WasiVersion}; use wasmer_runtime_core::{func, import::ImportObject, imports}; @@ -79,7 +79,7 @@ pub fn generate_import_object( imports! { // This generates the wasi state. state_gen, - "wasi_unstable" => { + "wasi_snapshot_preview1" => { "args_get" => func!(args_get), "args_sizes_get" => func!(args_sizes_get), "clock_res_get" => func!(clock_res_get), @@ -128,3 +128,86 @@ pub fn generate_import_object( }, } } + +#[cfg(feature = "snapshot0")] +/// Creates a Wasi [`ImportObject`] with [`WasiState`]. +pub fn generate_import_object_snapshot0( + args: Vec>, + envs: Vec>, + preopened_files: Vec, + mapped_dirs: Vec<(String, PathBuf)>, +) -> ImportObject { + let state_gen = move || { + // TODO: look into removing all these unnecessary clones + fn state_destructor(data: *mut c_void) { + unsafe { + drop(Box::from_raw(data as *mut WasiState)); + } + } + let preopened_files = preopened_files.clone(); + let mapped_dirs = mapped_dirs.clone(); + //let wasi_builder = create_wasi_instance(); + + let state = Box::new(WasiState { + fs: WasiFs::new(&preopened_files, &mapped_dirs).expect("Could not create WASI FS"), + args: args.clone(), + envs: envs.clone(), + }); + + ( + Box::into_raw(state) as *mut c_void, + state_destructor as fn(*mut c_void), + ) + }; + imports! { + // This generates the wasi state. + state_gen, + "wasi_unstable" => { + "args_get" => func!(args_get), + "args_sizes_get" => func!(args_sizes_get), + "clock_res_get" => func!(clock_res_get), + "clock_time_get" => func!(clock_time_get), + "environ_get" => func!(environ_get), + "environ_sizes_get" => func!(environ_sizes_get), + "fd_advise" => func!(fd_advise), + "fd_allocate" => func!(fd_allocate), + "fd_close" => func!(fd_close), + "fd_datasync" => func!(fd_datasync), + "fd_fdstat_get" => func!(fd_fdstat_get), + "fd_fdstat_set_flags" => func!(fd_fdstat_set_flags), + "fd_fdstat_set_rights" => func!(fd_fdstat_set_rights), + "fd_filestat_get" => func!(legacy::snapshot0::fd_filestat_get), + "fd_filestat_set_size" => func!(fd_filestat_set_size), + "fd_filestat_set_times" => func!(fd_filestat_set_times), + "fd_pread" => func!(fd_pread), + "fd_prestat_get" => func!(fd_prestat_get), + "fd_prestat_dir_name" => func!(fd_prestat_dir_name), + "fd_pwrite" => func!(fd_pwrite), + "fd_read" => func!(fd_read), + "fd_readdir" => func!(fd_readdir), + "fd_renumber" => func!(fd_renumber), + "fd_seek" => func!(legacy::snapshot0::fd_seek), + "fd_sync" => func!(fd_sync), + "fd_tell" => func!(fd_tell), + "fd_write" => func!(fd_write), + "path_create_directory" => func!(path_create_directory), + "path_filestat_get" => func!(legacy::snapshot0::path_filestat_get), + "path_filestat_set_times" => func!(path_filestat_set_times), + "path_link" => func!(path_link), + "path_open" => func!(path_open), + "path_readlink" => func!(path_readlink), + "path_remove_directory" => func!(path_remove_directory), + "path_rename" => func!(path_rename), + "path_symlink" => func!(path_symlink), + "path_unlink_file" => func!(path_unlink_file), + "poll_oneoff" => func!(legacy::snapshot0::poll_oneoff), + "proc_exit" => func!(proc_exit), + "proc_raise" => func!(proc_raise), + "random_get" => func!(random_get), + "sched_yield" => func!(sched_yield), + "sock_recv" => func!(sock_recv), + "sock_send" => func!(sock_send), + "sock_shutdown" => func!(sock_shutdown), + }, + } +} diff --git a/lib/wasi/src/syscalls/legacy/mod.rs b/lib/wasi/src/syscalls/legacy/mod.rs new file mode 100644 index 000000000..94e713b23 --- /dev/null +++ b/lib/wasi/src/syscalls/legacy/mod.rs @@ -0,0 +1,6 @@ +//! These modules provide wrappers and implementations for older version of WASI. +//! +//! If you are relying on legacy WASI, please upgrade for the best experience. + +#[cfg(feature = "snapshot0")] +pub mod snapshot0; diff --git a/lib/wasi/src/syscalls/legacy/snapshot0.rs b/lib/wasi/src/syscalls/legacy/snapshot0.rs new file mode 100644 index 000000000..38a181114 --- /dev/null +++ b/lib/wasi/src/syscalls/legacy/snapshot0.rs @@ -0,0 +1,178 @@ +use crate::ptr::{Array, WasmPtr}; +use crate::syscalls; +use crate::syscalls::types::{self, snapshot0}; +use wasmer_runtime_core::vm::Ctx; + +/// Wrapper around `syscalls::fd_filestat_get` with extra logic to handle the size +/// difference of `wasi_filestat_t` +/// +/// WARNING: this function involves saving, clobbering, and restoring unrelated +/// Wasm memory. If the memory clobbered by the current syscall is also used by +/// that syscall, then it may break. +pub fn fd_filestat_get( + ctx: &mut Ctx, + fd: types::__wasi_fd_t, + buf: WasmPtr, +) -> types::__wasi_errno_t { + let memory = ctx.memory(0); + + // transmute the WasmPtr into a WasmPtr where T2 > T1, this will read extra memory. + // The edge case of this causing an OOB is not handled, if the new field is OOB, then the entire + // memory access will fail. + let new_buf: WasmPtr = unsafe { std::mem::transmute(buf) }; + + // Copy the data including the extra data + let new_filestat_setup: types::__wasi_filestat_t = + wasi_try!(new_buf.deref(memory)).get().clone(); + + // Set up complete, make the call with the pointer that will write to the + // struct and some unrelated memory after the struct. + let result = syscalls::fd_filestat_get(ctx, fd, new_buf); + + // reborrow memory + let memory = ctx.memory(0); + + // get the values written to memory + let new_filestat = wasi_try!(new_buf.deref(memory)).get(); + // translate the new struct into the old struct in host memory + let old_stat = snapshot0::__wasi_filestat_t { + st_dev: new_filestat.st_dev, + st_ino: new_filestat.st_ino, + st_filetype: new_filestat.st_filetype, + st_nlink: new_filestat.st_nlink as u32, + st_size: new_filestat.st_size, + st_atim: new_filestat.st_atim, + st_mtim: new_filestat.st_mtim, + st_ctim: new_filestat.st_ctim, + }; + + // write back the original values at the pointer's memory locations + // (including the memory unrelated to the pointer) + wasi_try!(new_buf.deref(memory)).set(new_filestat_setup); + + // Now that this memory is back as it was, write the translated filestat + // into memory leaving it as it should be + wasi_try!(buf.deref(memory)).set(old_stat); + + result +} + +/// Wrapper around `syscalls::path_filestat_get` with extra logic to handle the size +/// difference of `wasi_filestat_t` +pub fn path_filestat_get( + ctx: &mut Ctx, + fd: types::__wasi_fd_t, + flags: types::__wasi_lookupflags_t, + path: WasmPtr, + path_len: u32, + buf: WasmPtr, +) -> types::__wasi_errno_t { + // see `fd_filestat_get` in this file for an explanation of this strange behavior + let memory = ctx.memory(0); + + let new_buf: WasmPtr = unsafe { std::mem::transmute(buf) }; + let new_filestat_setup: types::__wasi_filestat_t = + wasi_try!(new_buf.deref(memory)).get().clone(); + + let result = syscalls::path_filestat_get(ctx, fd, flags, path, path_len, new_buf); + + let memory = ctx.memory(0); + let new_filestat = wasi_try!(new_buf.deref(memory)).get(); + let old_stat = snapshot0::__wasi_filestat_t { + st_dev: new_filestat.st_dev, + st_ino: new_filestat.st_ino, + st_filetype: new_filestat.st_filetype, + st_nlink: new_filestat.st_nlink as u32, + st_size: new_filestat.st_size, + st_atim: new_filestat.st_atim, + st_mtim: new_filestat.st_mtim, + st_ctim: new_filestat.st_ctim, + }; + + wasi_try!(new_buf.deref(memory)).set(new_filestat_setup); + wasi_try!(buf.deref(memory)).set(old_stat); + + result +} + +/// Wrapper around `syscalls::fd_seek` with extra logic to remap the values +/// of `__wasi_whence_t` +pub fn fd_seek( + ctx: &mut Ctx, + fd: types::__wasi_fd_t, + offset: types::__wasi_filedelta_t, + whence: snapshot0::__wasi_whence_t, + newoffset: WasmPtr, +) -> types::__wasi_errno_t { + let new_whence = match whence { + snapshot0::__WASI_WHENCE_CUR => types::__WASI_WHENCE_CUR, + snapshot0::__WASI_WHENCE_END => types::__WASI_WHENCE_END, + snapshot0::__WASI_WHENCE_SET => types::__WASI_WHENCE_SET, + // if it's invalid, let the new fd_seek handle it + _ => whence, + }; + syscalls::fd_seek(ctx, fd, offset, new_whence, newoffset) +} + +/// Wrapper around `syscalls::poll_oneoff` with extra logic to add the removed +/// userdata field back +pub fn poll_oneoff( + ctx: &mut Ctx, + in_: WasmPtr, + out_: WasmPtr, + nsubscriptions: u32, + nevents: WasmPtr, +) -> types::__wasi_errno_t { + // in this case the new type is smaller than the old type, so it all fits into memory, + // we just need to readjust and copy it + + // we start by adjusting `in_` into a format that the new code can understand + let memory = ctx.memory(0); + let mut in_origs: Vec = vec![]; + for in_sub in wasi_try!(in_.deref(memory, 0, nsubscriptions)) { + in_origs.push(in_sub.get().clone()); + } + + // get a pointer to the smaller new type + let in_new_type_ptr: WasmPtr = + unsafe { std::mem::transmute(in_) }; + + for (in_sub_new, orig) in wasi_try!(in_new_type_ptr.deref(memory, 0, nsubscriptions)) + .iter() + .zip(in_origs.iter()) + { + in_sub_new.set(types::__wasi_subscription_t { + userdata: orig.userdata, + type_: orig.type_, + u: if orig.type_ == types::__WASI_EVENTTYPE_CLOCK { + types::__wasi_subscription_u { + clock: types::__wasi_subscription_clock_t { + clock_id: unsafe { orig.u.clock.clock_id }, + timeout: unsafe { orig.u.clock.timeout }, + precision: unsafe { orig.u.clock.precision }, + flags: unsafe { orig.u.clock.flags }, + }, + } + } else { + types::__wasi_subscription_u { + fd_readwrite: unsafe { orig.u.fd_readwrite }, + } + }, + }); + } + + // make the call + let result = syscalls::poll_oneoff(ctx, in_new_type_ptr, out_, nsubscriptions, nevents); + + // replace the old values of in, in case the calling code reuses the memory + let memory = ctx.memory(0); + + for (in_sub, orig) in wasi_try!(in_.deref(memory, 0, nsubscriptions)) + .iter() + .zip(in_origs.into_iter()) + { + in_sub.set(orig); + } + + result +} diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 3db7323e7..3cf8d22b7 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -5,6 +5,9 @@ pub mod unix; #[cfg(any(target_os = "windows"))] pub mod windows; +#[cfg(any(feature = "snapshot0"))] +pub mod legacy; + use self::types::*; use crate::{ ptr::{Array, WasmPtr}, diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index 060f3cd43..6e4444ee9 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -416,7 +416,7 @@ pub struct __wasi_iovec_t { unsafe impl ValueType for __wasi_iovec_t {} -pub type __wasi_linkcount_t = u32; +pub type __wasi_linkcount_t = u64; pub type __wasi_lookupflags_t = u32; pub const __WASI_LOOKUP_SYMLINK_FOLLOW: u32 = 1 << 0; @@ -559,7 +559,6 @@ pub const __WASI_SUBSCRIPTION_CLOCK_ABSTIME: u16 = 1 << 0; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(C)] pub struct __wasi_subscription_clock_t { - pub userdata: __wasi_userdata_t, pub clock_id: __wasi_clockid_t, pub timeout: __wasi_timestamp_t, pub precision: __wasi_timestamp_t, @@ -575,8 +574,8 @@ pub struct __wasi_subscription_fs_readwrite_t { #[derive(Copy, Clone)] #[repr(C)] pub union __wasi_subscription_u { - clock: __wasi_subscription_clock_t, - fd_readwrite: __wasi_subscription_fs_readwrite_t, + pub clock: __wasi_subscription_clock_t, + pub fd_readwrite: __wasi_subscription_fs_readwrite_t, } #[derive(Copy, Clone)] @@ -698,6 +697,59 @@ pub type __wasi_timestamp_t = u64; pub type __wasi_userdata_t = u64; pub type __wasi_whence_t = u8; -pub const __WASI_WHENCE_CUR: u8 = 0; -pub const __WASI_WHENCE_END: u8 = 1; -pub const __WASI_WHENCE_SET: u8 = 2; +pub const __WASI_WHENCE_SET: u8 = 0; +pub const __WASI_WHENCE_CUR: u8 = 1; +pub const __WASI_WHENCE_END: u8 = 2; + +pub mod snapshot0 { + use serde::{Deserialize, Serialize}; + pub type __wasi_linkcount_t = u32; + use wasmer_runtime_core::types::ValueType; + + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[repr(C)] + pub struct __wasi_subscription_clock_t { + pub userdata: super::__wasi_userdata_t, + pub clock_id: super::__wasi_clockid_t, + pub timeout: super::__wasi_timestamp_t, + pub precision: super::__wasi_timestamp_t, + pub flags: super::__wasi_subclockflags_t, + } + + #[derive(Copy, Clone)] + #[repr(C)] + pub union __wasi_subscription_u { + pub clock: __wasi_subscription_clock_t, + pub fd_readwrite: super::__wasi_subscription_fs_readwrite_t, + } + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct __wasi_subscription_t { + pub userdata: super::__wasi_userdata_t, + pub type_: super::__wasi_eventtype_t, + pub u: __wasi_subscription_u, + } + + unsafe impl ValueType for __wasi_subscription_t {} + + pub type __wasi_whence_t = u8; + pub const __WASI_WHENCE_CUR: u8 = 0; + pub const __WASI_WHENCE_END: u8 = 1; + pub const __WASI_WHENCE_SET: u8 = 2; + + #[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + #[repr(C)] + pub struct __wasi_filestat_t { + pub st_dev: super::__wasi_device_t, + pub st_ino: super::__wasi_inode_t, + pub st_filetype: super::__wasi_filetype_t, + pub st_nlink: __wasi_linkcount_t, + pub st_size: super::__wasi_filesize_t, + pub st_atim: super::__wasi_timestamp_t, + pub st_mtim: super::__wasi_timestamp_t, + pub st_ctim: super::__wasi_timestamp_t, + } + + unsafe impl ValueType for __wasi_filestat_t {} +} diff --git a/lib/wasi/src/utils.rs b/lib/wasi/src/utils.rs index f96e35958..bb0a554f8 100644 --- a/lib/wasi/src/utils.rs +++ b/lib/wasi/src/utils.rs @@ -1,18 +1,37 @@ use wasmer_runtime_core::module::Module; +#[allow(dead_code)] /// Check if a provided module is compiled with WASI support pub fn is_wasi_module(module: &Module) -> bool { - if module.info().imported_functions.is_empty() { - return false; - } - for (_, import_name) in &module.info().imported_functions { - let namespace = module - .info() - .namespace_table - .get(import_name.namespace_index); - if namespace != "wasi_unstable" { - return false; - } - } - true + get_wasi_version(module) == Some(WasiVersion::Snapshot1) +} + +/// The version of WASI. This is determined by the namespace string +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum WasiVersion { + /// "wasi_unstable" + Snapshot0, + /// "wasi_snapshot_preview1" + Snapshot1, +} + +pub fn get_wasi_version(module: &Module) -> Option { + let mut import_iter = module + .info() + .imported_functions + .iter() + .map(|(_, import_name)| import_name.namespace_index); + + // returns None if empty + let first = import_iter.next()?; + if import_iter.all(|idx| idx == first) { + match module.info().namespace_table.get(first) { + "wasi_unstable" => Some(WasiVersion::Snapshot0), + "wasi_snapshot_preview1" => Some(WasiVersion::Snapshot1), + _ => None, + } + } else { + // not all funcs have the same namespace: therefore it's not WASI + None + } } diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 6195e60a6..ff8cc8841 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -582,25 +582,38 @@ fn execute_wasm(options: &Run) -> Result<(), String> { ) .map_err(|e| format!("{:?}", e))?; } else { - if cfg!(feature = "wasi") && wasmer_wasi::is_wasi_module(&module) { - let import_object = wasmer_wasi::generate_import_object( - if let Some(cn) = &options.command_name { - [cn.clone()] - } else { - [options.path.to_str().unwrap().to_owned()] + let wasi_version = wasmer_wasi::get_wasi_version(&module); + if cfg!(feature = "wasi") && wasi_version.is_some() { + let wasi_version = wasi_version.unwrap(); + let args = if let Some(cn) = &options.command_name { + [cn.clone()] + } else { + [options.path.to_str().unwrap().to_owned()] + } + .iter() + .chain(options.args.iter()) + .cloned() + .map(|arg| arg.into_bytes()) + .collect(); + let envs = env_vars + .into_iter() + .map(|(k, v)| format!("{}={}", k, v).into_bytes()) + .collect(); + let preopened_files = options.pre_opened_directories.clone(); + + let import_object = match wasi_version { + wasmer_wasi::WasiVersion::Snapshot0 => { + wasmer_wasi::generate_import_object_snapshot0( + args, + envs, + preopened_files, + mapped_dirs, + ) } - .iter() - .chain(options.args.iter()) - .cloned() - .map(|arg| arg.into_bytes()) - .collect(), - env_vars - .into_iter() - .map(|(k, v)| format!("{}={}", k, v).into_bytes()) - .collect(), - options.pre_opened_directories.clone(), - mapped_dirs, - ); + wasmer_wasi::WasiVersion::Snapshot1 => { + wasmer_wasi::generate_import_object(args, envs, preopened_files, mapped_dirs) + } + }; #[allow(unused_mut)] // mut used in feature let mut instance = module From 2b2a0628f7b21263a2f049a358a9211502ef243f Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 12 Nov 2019 17:02:07 -0800 Subject: [PATCH 2/5] Update from feedback, improve docs on new wasi fns --- CHANGELOG.md | 2 ++ Cargo.lock | 1 + lib/wasi/src/lib.rs | 7 ++++--- lib/wasi/src/utils.rs | 10 +++++++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9efec44fd..becd75e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## **[Unreleased]** +- [#957](https://github.com/wasmerio/wasmer/pull/957) Change the meaning of `wasmer_wasi::is_wasi_module` to detect any type of WASI module, add support for new wasi snapshot_preview1 + ## 0.10.0 - 2019-11-11 - [#952](https://github.com/wasmerio/wasmer/pull/952) Use C preprocessor to properly hide trampoline functions on Windows and non-x86_64 targets. diff --git a/Cargo.lock b/Cargo.lock index 705efb20b..0720c9eeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1504,6 +1504,7 @@ dependencies = [ "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-backend 0.10.1", + "wasmer-llvm-backend 0.10.1", "wasmer-runtime-core 0.10.1", "wasmer-singlepass-backend 0.10.1", ] diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index a40987b2b..c0bb562de 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -12,7 +12,7 @@ //! Wasmer's WASI implementation //! -//! Use `generate_import_object` to create an `ImportObject`. This `ImportObject` +//! Use `generate_import_object` to create an [`ImportObject`]. This [`ImportObject`] //! can be combined with a module to create an `Instance` which can execute WASI //! Wasm functions. //! @@ -47,7 +47,8 @@ pub struct ExitCode { pub code: syscalls::types::__wasi_exitcode_t, } -/// Creates a Wasi [`ImportObject`] with [`WasiState`]. +/// Creates a Wasi [`ImportObject`] with [`WasiState`] with the latest snapshot +/// of WASI. pub fn generate_import_object( args: Vec>, envs: Vec>, @@ -130,7 +131,7 @@ pub fn generate_import_object( } #[cfg(feature = "snapshot0")] -/// Creates a Wasi [`ImportObject`] with [`WasiState`]. +/// Creates a legacy Wasi [`ImportObject`] with [`WasiState`]. pub fn generate_import_object_snapshot0( args: Vec>, envs: Vec>, diff --git a/lib/wasi/src/utils.rs b/lib/wasi/src/utils.rs index bb0a554f8..b19a40488 100644 --- a/lib/wasi/src/utils.rs +++ b/lib/wasi/src/utils.rs @@ -1,9 +1,10 @@ use wasmer_runtime_core::module::Module; #[allow(dead_code)] -/// Check if a provided module is compiled with WASI support +/// Check if a provided module is compiled for some version of WASI. +/// Use [`get_wasi_version`] to find out which version of WASI the module is. pub fn is_wasi_module(module: &Module) -> bool { - get_wasi_version(module) == Some(WasiVersion::Snapshot1) + get_wasi_version(module).is_some() } /// The version of WASI. This is determined by the namespace string @@ -15,6 +16,7 @@ pub enum WasiVersion { Snapshot1, } +/// Detect the version of WASI being used from the namespace pub fn get_wasi_version(module: &Module) -> Option { let mut import_iter = module .info() @@ -25,13 +27,15 @@ pub fn get_wasi_version(module: &Module) -> Option { // returns None if empty let first = import_iter.next()?; if import_iter.all(|idx| idx == first) { + // once we know that all the namespaces are the same, we can use it to + // detect which version of WASI this is match module.info().namespace_table.get(first) { "wasi_unstable" => Some(WasiVersion::Snapshot0), "wasi_snapshot_preview1" => Some(WasiVersion::Snapshot1), _ => None, } } else { - // not all funcs have the same namespace: therefore it's not WASI + // not all funcs have the same namespace, therefore it's not WASI None } } From be217e8f8e989ea65ffaf5a57fd96f53dfc9bae9 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 21 Nov 2019 10:57:04 -0800 Subject: [PATCH 3/5] Update from feedback, fix CI issues, update wasi-test --- Cargo.toml | 2 +- lib/wasi-tests/src/lib.rs | 11 +- lib/wasi/Cargo.toml | 7 +- lib/wasi/src/lib.rs | 19 ++- lib/wasi/src/syscalls/legacy/mod.rs | 1 - lib/wasi/src/syscalls/mod.rs | 1 - src/bin/wasmer.rs | 231 ++++++++++++++-------------- 7 files changed, 142 insertions(+), 130 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d0c6b7ad..41c17719d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,8 +58,8 @@ members = [ "lib/wasi-tests", "lib/emscripten-tests", "lib/middleware-common-tests", - "examples/plugin-for-example", "examples/parallel", + "examples/plugin-for-example", "examples/parallel-guest", ] diff --git a/lib/wasi-tests/src/lib.rs b/lib/wasi-tests/src/lib.rs index 4508e432e..dba6298a7 100644 --- a/lib/wasi-tests/src/lib.rs +++ b/lib/wasi-tests/src/lib.rs @@ -16,7 +16,13 @@ fn serializing_works() { b"GOROOT=$HOME/.cargo/bin".into_iter().cloned().collect(), ]; let wasm_binary = include_bytes!("../wasitests/fd_read.wasm"); - let import_object = generate_import_object( + let module = compile(&wasm_binary[..]) + .map_err(|e| format!("Can't compile module: {:?}", e)) + .unwrap(); + + let wasi_version = get_wasi_version(&module).expect("WASI module"); + let import_object = generate_import_object_for_version( + wasi_version, args.clone(), envs.clone(), vec![], @@ -25,9 +31,6 @@ fn serializing_works() { std::path::PathBuf::from("wasitests/test_fs/hamlet"), )], ); - let module = compile(&wasm_binary[..]) - .map_err(|e| format!("Can't compile module: {:?}", e)) - .unwrap(); let state_bytes = { let instance = module.instantiate(&import_object).unwrap(); diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index e413c313e..d81e5e895 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -20,9 +20,4 @@ serde = { version = "1", features = ["derive"] } wasmer-runtime-core = { path = "../runtime-core", version = "0.10.1" } [target.'cfg(windows)'.dependencies] -winapi = "0.3" - -[features] -# Enable support for the older snapshot0 WASI modules -snapshot0 = [] -default = ["snapshot0"] \ No newline at end of file +winapi = "0.3" \ No newline at end of file diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index c0bb562de..ca439bfdb 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -130,9 +130,24 @@ pub fn generate_import_object( } } -#[cfg(feature = "snapshot0")] +/// Creates a Wasi [`ImportObject`] with [`WasiState`] for the given [`WasiVersion`]. +pub fn generate_import_object_for_version( + version: WasiVersion, + args: Vec>, + envs: Vec>, + preopened_files: Vec, + mapped_dirs: Vec<(String, PathBuf)>, +) -> ImportObject { + match version { + WasiVersion::Snapshot0 => { + generate_import_object_snapshot0(args, envs, preopened_files, mapped_dirs) + } + WasiVersion::Snapshot1 => generate_import_object(args, envs, preopened_files, mapped_dirs), + } +} + /// Creates a legacy Wasi [`ImportObject`] with [`WasiState`]. -pub fn generate_import_object_snapshot0( +fn generate_import_object_snapshot0( args: Vec>, envs: Vec>, preopened_files: Vec, diff --git a/lib/wasi/src/syscalls/legacy/mod.rs b/lib/wasi/src/syscalls/legacy/mod.rs index 94e713b23..62cc08f9e 100644 --- a/lib/wasi/src/syscalls/legacy/mod.rs +++ b/lib/wasi/src/syscalls/legacy/mod.rs @@ -2,5 +2,4 @@ //! //! If you are relying on legacy WASI, please upgrade for the best experience. -#[cfg(feature = "snapshot0")] pub mod snapshot0; diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 3cf8d22b7..af6ccab26 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -5,7 +5,6 @@ pub mod unix; #[cfg(any(target_os = "windows"))] pub mod windows; -#[cfg(any(feature = "snapshot0"))] pub mod legacy; use self::types::*; diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index ff8cc8841..03c66a26f 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -26,7 +26,7 @@ use wasmer_clif_backend::CraneliftCompiler; use wasmer_llvm_backend::{LLVMCompiler, LLVMOptions}; use wasmer_runtime::{ cache::{Cache as BaseCache, FileSystemCache, WasmHash}, - Func, Value, VERSION, + Value, VERSION, }; #[cfg(feature = "managed")] use wasmer_runtime_core::tiering::{run_tiering, InteractiveShellContext, ShellExitOperation}; @@ -41,25 +41,6 @@ use wasmer_singlepass_backend::SinglePassCompiler; #[cfg(feature = "wasi")] use wasmer_wasi; -// stub module to make conditional compilation happy -#[cfg(not(feature = "wasi"))] -mod wasmer_wasi { - use wasmer_runtime_core::{import::ImportObject, module::Module}; - - pub fn is_wasi_module(_module: &Module) -> bool { - false - } - - pub fn generate_import_object( - _args: Vec>, - _envs: Vec>, - _preopened_files: Vec, - _mapped_dirs: Vec<(String, std::path::PathBuf)>, - ) -> ImportObject { - unimplemented!() - } -} - #[derive(Debug, StructOpt)] #[structopt(name = "wasmer", about = "Wasm execution runtime.", author)] /// The options for the wasmer Command Line Interface @@ -308,6 +289,7 @@ fn get_mapped_dirs(input: &[String]) -> Result, String> { Ok(md) } +#[cfg(feature = "wasi")] fn get_env_var_args(input: &[String]) -> Result, String> { let mut ev = vec![]; for entry in input.iter() { @@ -323,11 +305,111 @@ fn get_env_var_args(input: &[String]) -> Result, String> { Ok(ev) } +/// Helper function for `execute_wasm` (the `Run` command) +#[cfg(feature = "wasi")] +fn execute_wasi( + wasi_version: wasmer_wasi::WasiVersion, + options: &Run, + env_vars: Vec<(&str, &str)>, + module: wasmer_runtime_core::Module, + mapped_dirs: Vec<(String, PathBuf)>, +) -> Result<(), String> { + let args = if let Some(cn) = &options.command_name { + [cn.clone()] + } else { + [options.path.to_str().unwrap().to_owned()] + } + .iter() + .chain(options.args.iter()) + .cloned() + .map(|arg| arg.into_bytes()) + .collect(); + let envs = env_vars + .into_iter() + .map(|(k, v)| format!("{}={}", k, v).into_bytes()) + .collect(); + let preopened_files = options.pre_opened_directories.clone(); + + let import_object = wasmer_wasi::generate_import_object_for_version( + wasi_version, + args, + envs, + preopened_files, + mapped_dirs, + ); + + #[allow(unused_mut)] // mut used in feature + let mut instance = module + .instantiate(&import_object) + .map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?; + + let start: wasmer_runtime::Func<(), ()> = + instance.func("_start").map_err(|e| format!("{:?}", e))?; + + #[cfg(feature = "managed")] + { + let start_raw: extern "C" fn(&mut wasmer_runtime_core::vm::Ctx) = + unsafe { ::std::mem::transmute(start.get_vm_func()) }; + + unsafe { + run_tiering( + module.info(), + &wasm_binary, + if let Some(ref path) = options.resume { + let mut f = File::open(path).unwrap(); + let mut out: Vec = vec![]; + f.read_to_end(&mut out).unwrap(); + Some( + wasmer_runtime_core::state::InstanceImage::from_bytes(&out) + .expect("failed to decode image"), + ) + } else { + None + }, + &import_object, + start_raw, + &mut instance, + options + .optimized_backends + .iter() + .map(|&backend| -> Box Box + Send> { + Box::new(move || get_compiler_by_backend(backend).unwrap()) + }) + .collect(), + interactive_shell, + )? + }; + } + + #[cfg(not(feature = "managed"))] + { + use wasmer_runtime::error::RuntimeError; + let result = start.call(); + + if let Err(ref err) = result { + match err { + RuntimeError::Trap { msg } => return Err(format!("wasm trap occured: {}", msg)), + #[cfg(feature = "wasi")] + RuntimeError::Error { data } => { + if let Some(error_code) = data.downcast_ref::() { + std::process::exit(error_code.code as i32) + } + } + #[cfg(not(feature = "wasi"))] + RuntimeError::Error { .. } => (), + } + return Err(format!("error: {:?}", err)); + } + } + Ok(()) +} + /// Execute a wasm/wat file fn execute_wasm(options: &Run) -> Result<(), String> { let disable_cache = options.disable_cache; let mapped_dirs = get_mapped_dirs(&options.mapped_dirs[..])?; + #[cfg(feature = "wasi")] let env_vars = get_env_var_args(&options.env_vars[..])?; let wasm_path = &options.path; @@ -582,103 +664,22 @@ fn execute_wasm(options: &Run) -> Result<(), String> { ) .map_err(|e| format!("{:?}", e))?; } else { + #[cfg(feature = "wasi")] let wasi_version = wasmer_wasi::get_wasi_version(&module); - if cfg!(feature = "wasi") && wasi_version.is_some() { - let wasi_version = wasi_version.unwrap(); - let args = if let Some(cn) = &options.command_name { - [cn.clone()] - } else { - [options.path.to_str().unwrap().to_owned()] - } - .iter() - .chain(options.args.iter()) - .cloned() - .map(|arg| arg.into_bytes()) - .collect(); - let envs = env_vars - .into_iter() - .map(|(k, v)| format!("{}={}", k, v).into_bytes()) - .collect(); - let preopened_files = options.pre_opened_directories.clone(); + #[cfg(feature = "wasi")] + let is_wasi = wasi_version.is_some(); + #[cfg(not(feature = "wasi"))] + let is_wasi = false; - let import_object = match wasi_version { - wasmer_wasi::WasiVersion::Snapshot0 => { - wasmer_wasi::generate_import_object_snapshot0( - args, - envs, - preopened_files, - mapped_dirs, - ) - } - wasmer_wasi::WasiVersion::Snapshot1 => { - wasmer_wasi::generate_import_object(args, envs, preopened_files, mapped_dirs) - } - }; - - #[allow(unused_mut)] // mut used in feature - let mut instance = module - .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?; - - let start: Func<(), ()> = instance.func("_start").map_err(|e| format!("{:?}", e))?; - - #[cfg(feature = "managed")] - { - let start_raw: extern "C" fn(&mut wasmer_runtime_core::vm::Ctx) = - unsafe { ::std::mem::transmute(start.get_vm_func()) }; - - unsafe { - run_tiering( - module.info(), - &wasm_binary, - if let Some(ref path) = options.resume { - let mut f = File::open(path).unwrap(); - let mut out: Vec = vec![]; - f.read_to_end(&mut out).unwrap(); - Some( - wasmer_runtime_core::state::InstanceImage::from_bytes(&out) - .expect("failed to decode image"), - ) - } else { - None - }, - &import_object, - start_raw, - &mut instance, - options - .optimized_backends - .iter() - .map(|&backend| -> Box Box + Send> { - Box::new(move || get_compiler_by_backend(backend).unwrap()) - }) - .collect(), - interactive_shell, - )? - }; - } - - #[cfg(not(feature = "managed"))] - { - use wasmer_runtime::error::RuntimeError; - let result = start.call(); - - if let Err(ref err) = result { - match err { - RuntimeError::Trap { msg } => { - return Err(format!("wasm trap occured: {}", msg)) - } - #[cfg(feature = "wasi")] - RuntimeError::Error { data } => { - if let Some(error_code) = data.downcast_ref::() { - std::process::exit(error_code.code as i32) - } - } - #[cfg(not(feature = "wasi"))] - RuntimeError::Error { .. } => (), - } - return Err(format!("error: {:?}", err)); - } - } + if is_wasi { + #[cfg(feature = "wasi")] + execute_wasi( + wasi_version.unwrap(), + options, + env_vars, + module, + mapped_dirs, + )?; } else { let import_object = wasmer_runtime_core::import::ImportObject::new(); let instance = module From ddccdb92b8396e6e7d328ec650cfba7e255ce8aa Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 21 Nov 2019 11:30:44 -0800 Subject: [PATCH 4/5] More fixes for CI --- lib/wasi-tests/tests/wasitests/_common.rs | 10 +++++++++- lib/wasi/src/syscalls/legacy/snapshot0.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/wasi-tests/tests/wasitests/_common.rs b/lib/wasi-tests/tests/wasitests/_common.rs index 958fdf6ad..304cda58a 100644 --- a/lib/wasi-tests/tests/wasitests/_common.rs +++ b/lib/wasi-tests/tests/wasitests/_common.rs @@ -33,7 +33,15 @@ macro_rules! assert_wasi_output { let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &get_compiler()) .expect("WASM can't be compiled"); - let import_object = generate_import_object(vec![], vec![], $po_dir_args, $mapdir_args); + let wasi_version = get_wasi_version(&module).expect("WASI module"); + + let import_object = generate_import_object_for_version( + wasi_version, + vec![], + vec![], + $po_dir_args, + $mapdir_args, + ); let instance = module .instantiate(&import_object) diff --git a/lib/wasi/src/syscalls/legacy/snapshot0.rs b/lib/wasi/src/syscalls/legacy/snapshot0.rs index 38a181114..67566a50c 100644 --- a/lib/wasi/src/syscalls/legacy/snapshot0.rs +++ b/lib/wasi/src/syscalls/legacy/snapshot0.rs @@ -1,7 +1,7 @@ use crate::ptr::{Array, WasmPtr}; use crate::syscalls; use crate::syscalls::types::{self, snapshot0}; -use wasmer_runtime_core::vm::Ctx; +use wasmer_runtime_core::{debug, vm::Ctx}; /// Wrapper around `syscalls::fd_filestat_get` with extra logic to handle the size /// difference of `wasi_filestat_t` From bdeec521523d66c043140b5559a8e804cdd6a403 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 21 Nov 2019 14:00:46 -0800 Subject: [PATCH 5/5] Fix imports in test; copy manual implementations of `Debug` for wasi --- lib/wasi-tests/tests/wasitests/_common.rs | 2 +- lib/wasi/src/syscalls/types.rs | 48 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/wasi-tests/tests/wasitests/_common.rs b/lib/wasi-tests/tests/wasitests/_common.rs index 304cda58a..82920c0c9 100644 --- a/lib/wasi-tests/tests/wasitests/_common.rs +++ b/lib/wasi-tests/tests/wasitests/_common.rs @@ -2,7 +2,7 @@ macro_rules! assert_wasi_output { ($file:expr, $name:expr, $po_dir_args: expr, $mapdir_args:expr, $envvar_args:expr, $expected:expr) => {{ use wasmer_dev_utils::stdio::StdioCapturer; use wasmer_runtime_core::{backend::Compiler, Func}; - use wasmer_wasi::generate_import_object; + use wasmer_wasi::{generate_import_object_for_version, get_wasi_version}; #[cfg(feature = "clif")] fn get_compiler() -> impl Compiler { diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index 6e4444ee9..5f5559ce4 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -752,4 +752,52 @@ pub mod snapshot0 { } unsafe impl ValueType for __wasi_filestat_t {} + + impl std::fmt::Debug for __wasi_filestat_t { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let convert_ts_into_time_string = |ts| { + let tspec = + time::Timespec::new(ts as i64 / 1_000_000_000, (ts % 1_000_000_000) as i32); + let tm = time::at(tspec); + let out_time = tm.rfc822(); + format!("{} ({})", out_time, ts) + }; + f.debug_struct("__wasi_filestat_t") + .field("st_dev", &self.st_dev) + .field("st_ino", &self.st_ino) + .field( + "st_filetype", + &format!( + "{} ({})", + super::wasi_filetype_to_name(self.st_filetype), + self.st_filetype, + ), + ) + .field("st_nlink", &self.st_nlink) + .field("st_size", &self.st_size) + .field("st_atim", &convert_ts_into_time_string(self.st_atim)) + .field("st_mtim", &convert_ts_into_time_string(self.st_mtim)) + .field("st_ctim", &convert_ts_into_time_string(self.st_ctim)) + .finish() + } + } + + impl std::fmt::Debug for __wasi_subscription_t { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("__wasi_subscription_t") + .field("userdata", &self.userdata) + .field("type", &super::eventtype_to_str(self.type_)) + .field( + "u", + match self.type_ { + super::__WASI_EVENTTYPE_CLOCK => unsafe { &self.u.clock }, + super::__WASI_EVENTTYPE_FD_READ | super::__WASI_EVENTTYPE_FD_WRITE => unsafe { + &self.u.fd_readwrite + }, + _ => &"INVALID EVENTTYPE", + }, + ) + .finish() + } + } }