diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f9b9a99ca4..8a0c33badc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## **[Unreleased]** - [#1216](https://github.com/wasmerio/wasmer/pull/1216) `wasmer-interface-types` receives a binary encoder. +- [#1228](https://github.com/wasmerio/wasmer/pull/1228) Singlepass cleanup: Resolve several FIXMEs and remove protect_unix. - [#1218](https://github.com/wasmerio/wasmer/pull/1218) Enable Cranelift verifier in debug mode. Fix bug with table indices being the wrong type. - [#787](https://github.com/wasmerio/wasmer/pull/787) New crate `wasmer-interface-types` to implement WebAssembly Interface Types. - [#1213](https://github.com/wasmerio/wasmer/pull/1213) Fixed WASI `fdstat` to detect `isatty` properly. diff --git a/index.html b/index.html deleted file mode 100644 index 56b97fde0f6..00000000000 --- a/index.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/interface-types/README.md b/lib/interface-types/README.md new file mode 100644 index 00000000000..8dcf3ffc87e --- /dev/null +++ b/lib/interface-types/README.md @@ -0,0 +1,32 @@ +

+ + Wasmer logo + +

+ +

+ + Build Status + + + License + + + Join the Wasmer Community + + + Number of downloads from crates.io + + + Read our API documentation + +

+ +# Wasmer Interface Types + +Wasmer is a standalone JIT WebAssembly runtime, aiming to be fully +compatible with WASI, Emscripten, Rust and Go. [Learn +more](https://github.com/wasmerio/wasmer). + +This crate is an implementation of [the living WebAssembly Interface +Types standard](https://github.com/WebAssembly/interface-types). diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 513ede7fbc0..ad4b5078edb 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -51,6 +51,8 @@ pub struct Wasm { pub(crate) invoke_env: Option>, } +impl Kind for Wasm {} + impl Wasm { /// Create new `Wasm` from given parts. pub unsafe fn from_raw_parts( @@ -70,7 +72,6 @@ impl Wasm { /// by the host. pub struct Host(()); -impl Kind for Wasm {} impl Kind for Host {} /// Represents a list of WebAssembly values. @@ -110,14 +111,15 @@ pub trait WasmTypeList { Rets: WasmTypeList; } -/// Empty trait to specify the kind of `ExternalFunction`: With or +/// Empty trait to specify the kind of `HostFunction`: With or /// without a `vm::Ctx` argument. See the `ExplicitVmCtx` and the /// `ImplicitVmCtx` structures. /// -/// This type is never aimed to be used by a user. It is used by the +/// This trait is never aimed to be used by a user. It is used by the /// trait system to automatically generate an appropriate `wrap` /// function. -pub trait ExternalFunctionKind {} +#[doc(hidden)] +pub trait HostFunctionKind {} /// This empty structure indicates that an external function must /// contain an explicit `vm::Ctx` argument (at first position). @@ -127,8 +129,11 @@ pub trait ExternalFunctionKind {} /// x + 1 /// } /// ``` +#[doc(hidden)] pub struct ExplicitVmCtx {} +impl HostFunctionKind for ExplicitVmCtx {} + /// This empty structure indicates that an external function has no /// `vm::Ctx` argument (at first position). Its signature is: /// @@ -139,14 +144,13 @@ pub struct ExplicitVmCtx {} /// ``` pub struct ImplicitVmCtx {} -impl ExternalFunctionKind for ExplicitVmCtx {} -impl ExternalFunctionKind for ImplicitVmCtx {} +impl HostFunctionKind for ImplicitVmCtx {} /// Represents a function that can be converted to a `vm::Func` /// (function pointer) that can be called within WebAssembly. -pub trait ExternalFunction +pub trait HostFunction where - Kind: ExternalFunctionKind, + Kind: HostFunctionKind, Args: WasmTypeList, Rets: WasmTypeList, { @@ -227,8 +231,8 @@ where /// Creates a new `Func`. pub fn new(func: F) -> Func<'a, Args, Rets, Host> where - Kind: ExternalFunctionKind, - F: ExternalFunction, + Kind: HostFunctionKind, + F: HostFunction, { let (func, func_env) = func.to_raw(); @@ -310,6 +314,7 @@ macro_rules! impl_traits { where $( $x: WasmExternType ),*; + #[allow(unused_parens)] impl< $( $x ),* > WasmTypeList for ( $( $x ),* ) where $( $x: WasmExternType ),* @@ -380,7 +385,8 @@ macro_rules! impl_traits { } } - impl< $( $x, )* Rets, Trap, FN > ExternalFunction for FN + #[allow(unused_parens)] + impl< $( $x, )* Rets, Trap, FN > HostFunction for FN where $( $x: WasmExternType, )* Rets: WasmTypeList, @@ -495,7 +501,8 @@ macro_rules! impl_traits { } } - impl< $( $x, )* Rets, Trap, FN > ExternalFunction for FN + #[allow(unused_parens)] + impl< $( $x, )* Rets, Trap, FN > HostFunction for FN where $( $x: WasmExternType, )* Rets: WasmTypeList, @@ -607,6 +614,7 @@ macro_rules! impl_traits { } } + #[allow(unused_parens)] impl<'a $( , $x )*, Rets> Func<'a, ( $( $x ),* ), Rets, Wasm> where $( $x: WasmExternType, )* diff --git a/lib/runtime-core/src/types.rs b/lib/runtime-core/src/types.rs index 1b19bbc3d91..615964bb08e 100644 --- a/lib/runtime-core/src/types.rs +++ b/lib/runtime-core/src/types.rs @@ -104,44 +104,57 @@ where { /// Type for this `NativeWasmType`. const TYPE: Type; + /// Convert from u64 bites to self. fn from_binary(bits: u64) -> Self; + /// Convert self to u64 binary representation. fn to_binary(self) -> u64; } unsafe impl NativeWasmType for i32 { const TYPE: Type = Type::I32; + fn from_binary(bits: u64) -> Self { bits as _ } + fn to_binary(self) -> u64 { self as _ } } + unsafe impl NativeWasmType for i64 { const TYPE: Type = Type::I64; + fn from_binary(bits: u64) -> Self { bits as _ } + fn to_binary(self) -> u64 { self as _ } } + unsafe impl NativeWasmType for f32 { const TYPE: Type = Type::F32; + fn from_binary(bits: u64) -> Self { f32::from_bits(bits as u32) } + fn to_binary(self) -> u64 { self.to_bits() as _ } } + unsafe impl NativeWasmType for f64 { const TYPE: Type = Type::F64; + fn from_binary(bits: u64) -> Self { f64::from_bits(bits) } + fn to_binary(self) -> u64 { self.to_bits() } @@ -154,103 +167,41 @@ where { /// Native wasm type for this `WasmExternType`. type Native: NativeWasmType; + /// Convert from given `Native` type to self. fn from_native(native: Self::Native) -> Self; + /// Convert self to `Native` type. fn to_native(self) -> Self::Native; } -unsafe impl WasmExternType for i8 { - type Native = i32; - fn from_native(native: Self::Native) -> Self { - native as _ - } - fn to_native(self) -> Self::Native { - self as _ - } -} -unsafe impl WasmExternType for u8 { - type Native = i32; - fn from_native(native: Self::Native) -> Self { - native as _ - } - fn to_native(self) -> Self::Native { - self as _ - } -} -unsafe impl WasmExternType for i16 { - type Native = i32; - fn from_native(native: Self::Native) -> Self { - native as _ - } - fn to_native(self) -> Self::Native { - self as _ - } -} -unsafe impl WasmExternType for u16 { - type Native = i32; - fn from_native(native: Self::Native) -> Self { - native as _ - } - fn to_native(self) -> Self::Native { - self as _ - } -} -unsafe impl WasmExternType for i32 { - type Native = i32; - fn from_native(native: Self::Native) -> Self { - native - } - fn to_native(self) -> Self::Native { - self - } -} -unsafe impl WasmExternType for u32 { - type Native = i32; - fn from_native(native: Self::Native) -> Self { - native as _ - } - fn to_native(self) -> Self::Native { - self as _ - } -} -unsafe impl WasmExternType for i64 { - type Native = i64; - fn from_native(native: Self::Native) -> Self { - native - } - fn to_native(self) -> Self::Native { - self - } -} -unsafe impl WasmExternType for u64 { - type Native = i64; - fn from_native(native: Self::Native) -> Self { - native as _ - } - fn to_native(self) -> Self::Native { - self as _ - } -} -unsafe impl WasmExternType for f32 { - type Native = f32; - fn from_native(native: Self::Native) -> Self { - native - } - fn to_native(self) -> Self::Native { - self - } -} -unsafe impl WasmExternType for f64 { - type Native = f64; - fn from_native(native: Self::Native) -> Self { - native - } - fn to_native(self) -> Self::Native { - self - } +macro_rules! wasm_extern_type { + ($type:ty => $native_type:ty) => { + unsafe impl WasmExternType for $type { + type Native = $native_type; + + fn from_native(native: Self::Native) -> Self { + native as _ + } + + fn to_native(self) -> Self::Native { + self as _ + } + } + }; } +wasm_extern_type!(i8 => i32); +wasm_extern_type!(u8 => i32); +wasm_extern_type!(i16 => i32); +wasm_extern_type!(u16 => i32); +wasm_extern_type!(i32 => i32); +wasm_extern_type!(u32 => i32); +wasm_extern_type!(i64 => i64); +wasm_extern_type!(u64 => i64); +wasm_extern_type!(f32 => f32); +wasm_extern_type!(f64 => f64); + // pub trait IntegerAtomic // where // Self: Sized diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index 4c15a688c26..dbf20eed636 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -3,7 +3,6 @@ use crate::emitter_x64::*; use crate::machine::*; -use crate::protect_unix; #[cfg(target_arch = "aarch64")] use dynasmrt::aarch64::Assembler; #[cfg(target_arch = "x86_64")] @@ -28,7 +27,7 @@ use wasmer_runtime_core::{ }, cache::{Artifact, Error as CacheError}, codegen::*, - fault::raw::register_preservation_trampoline, + fault::{self, raw::register_preservation_trampoline}, loader::CodeMemory, memory::MemoryType, module::{ModuleInfo, ModuleInner}, @@ -369,6 +368,11 @@ impl RunnableModule for X64ExecutionContext { } fn get_trampoline(&self, _: &ModuleInfo, sig_index: SigIndex) -> Option { + // Correctly unwinding from `catch_unsafe_unwind` on hardware exceptions depends + // on the signal handlers being installed. Here we call `ensure_sighandler` "statically" + // outside `invoke()`. + fault::ensure_sighandler(); + unsafe extern "C" fn invoke( _trampoline: Trampoline, ctx: *mut vm::Ctx, @@ -383,8 +387,9 @@ impl RunnableModule for X64ExecutionContext { let args = slice::from_raw_parts(args, num_params_plus_one.unwrap().as_ptr() as usize - 1); - let ret = match protect_unix::call_protected( + let ret = match fault::catch_unsafe_unwind( || { + // Puts the arguments onto the stack and calls Wasm entry. #[cfg(target_arch = "x86_64")] { let args_reverse: SmallVec<[u64; 8]> = args.iter().cloned().rev().collect(); @@ -395,6 +400,9 @@ impl RunnableModule for X64ExecutionContext { func.as_ptr(), ) } + + // FIXME: Currently we are doing a hack here to convert between native aarch64 and + // "emulated" x86 ABIs. Ideally, this should be done using handwritten assembly. #[cfg(target_arch = "aarch64")] { struct CallCtx<'a> { @@ -519,7 +527,7 @@ impl RunnableModule for X64ExecutionContext { true } Err(err) => { - *error_out = Some(err.0); + *error_out = Some(err); false } }; @@ -545,8 +553,7 @@ impl RunnableModule for X64ExecutionContext { } unsafe fn do_early_trap(&self, data: Box) -> ! { - protect_unix::TRAP_EARLY_DATA.with(|x| x.set(Some(data))); - protect_unix::trigger_trap(); + fault::begin_unsafe_unwind(data); } fn get_code(&self) -> Option<&[u8]> { @@ -1686,14 +1693,11 @@ impl X64FunctionCode { Location::GPR(GPR::RSP), ); - // FIXME: Possible dynasm bug. This is a workaround. - // Using RSP as the source/destination operand of a `mov` instruction produces invalid code. - a.emit_mov(Size::S64, Location::GPR(GPR::RSP), Location::GPR(GPR::RCX)); for (i, r) in used_xmms.iter().enumerate() { a.emit_mov( Size::S64, Location::XMM(*r), - Location::Memory(GPR::RCX, (i * 8) as i32), + Location::Memory(GPR::RSP, (i * 8) as i32), ); } for r in used_xmms.iter().rev() { @@ -1771,37 +1775,26 @@ impl X64FunctionCode { } } match *param { - // Dynasm bug: RSP in memory operand does not work - Location::Imm64(_) | Location::XMM(_) => { - a.emit_mov( - Size::S64, - Location::GPR(GPR::RAX), - Location::XMM(XMM::XMM0), - ); - a.emit_mov( - Size::S64, - Location::GPR(GPR::RCX), - Location::XMM(XMM::XMM1), - ); - a.emit_sub(Size::S64, Location::Imm32(8), Location::GPR(GPR::RSP)); - a.emit_mov(Size::S64, Location::GPR(GPR::RSP), Location::GPR(GPR::RCX)); - a.emit_mov(Size::S64, *param, Location::GPR(GPR::RAX)); - a.emit_mov( - Size::S64, - Location::GPR(GPR::RAX), - Location::Memory(GPR::RCX, 0), - ); - a.emit_mov( - Size::S64, - Location::XMM(XMM::XMM0), - Location::GPR(GPR::RAX), - ); + Location::Imm64(_) => { + // Dummy value slot to be filled with `mov`. + a.emit_push(Size::S64, Location::GPR(GPR::RAX)); + + // Use R10 as the temporary register here, since it is callee-saved and not + // used by the callback `cb`. + a.emit_mov(Size::S64, *param, Location::GPR(GPR::R10)); a.emit_mov( Size::S64, - Location::XMM(XMM::XMM1), - Location::GPR(GPR::RCX), + Location::GPR(GPR::R10), + Location::Memory(GPR::RSP, 0), ); } + Location::XMM(_) => { + // Dummy value slot to be filled with `mov`. + a.emit_push(Size::S64, Location::GPR(GPR::RAX)); + + // XMM registers can be directly stored to memory. + a.emit_mov(Size::S64, *param, Location::Memory(GPR::RSP, 0)); + } _ => a.emit_push(Size::S64, *param), } } @@ -1873,12 +1866,10 @@ impl X64FunctionCode { // Restore XMMs. if used_xmms.len() > 0 { - // FIXME: Possible dynasm bug. This is a workaround. - a.emit_mov(Size::S64, Location::GPR(GPR::RSP), Location::GPR(GPR::RDX)); for (i, r) in used_xmms.iter().enumerate() { a.emit_mov( Size::S64, - Location::Memory(GPR::RDX, (i * 8) as i32), + Location::Memory(GPR::RSP, (i * 8) as i32), Location::XMM(*r), ); } diff --git a/lib/singlepass-backend/src/lib.rs b/lib/singlepass-backend/src/lib.rs index 2afdaa7f57d..98339cca304 100644 --- a/lib/singlepass-backend/src/lib.rs +++ b/lib/singlepass-backend/src/lib.rs @@ -40,7 +40,6 @@ extern crate smallvec; mod codegen_x64; mod emitter_x64; mod machine; -pub mod protect_unix; #[cfg(target_arch = "aarch64")] mod translator_aarch64; diff --git a/lib/singlepass-backend/src/protect_unix.rs b/lib/singlepass-backend/src/protect_unix.rs deleted file mode 100644 index 96f10748909..00000000000 --- a/lib/singlepass-backend/src/protect_unix.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Installing signal handlers allows us to handle traps and out-of-bounds memory -//! accesses that occur when runniing WebAssembly. -//! -//! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622 -//! -//! When a WebAssembly module triggers any traps, we perform recovery here. -//! -//! This module uses TLS (thread-local storage) to track recovery information. Since the four signals we're handling -//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here -//! unless you have memory unsafety elsewhere in your code. -//! -use std::any::Any; -use std::cell::Cell; -use wasmer_runtime_core::codegen::BreakpointMap; -use wasmer_runtime_core::fault::{begin_unsafe_unwind, catch_unsafe_unwind, ensure_sighandler}; - -thread_local! { - pub static TRAP_EARLY_DATA: Cell>> = Cell::new(None); -} - -pub unsafe fn trigger_trap() -> ! { - begin_unsafe_unwind(Box::new(())); -} - -pub struct CallProtError(pub Box); - -pub fn call_protected( - f: impl FnOnce() -> T, - breakpoints: Option, -) -> Result { - ensure_sighandler(); - unsafe { - let ret = catch_unsafe_unwind(|| f(), breakpoints); - match ret { - Ok(x) => Ok(x), - Err(e) => Err(CallProtError( - if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - data - } else { - e - }, - )), - } - } -} - -pub unsafe fn throw(payload: Box) -> ! { - begin_unsafe_unwind(payload); -} diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 6d9dd4fce65..167888e0e39 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -5,7 +5,8 @@ unused_mut, unused_variables, unused_unsafe, - unreachable_patterns + unreachable_patterns, + clippy::missing_safety_doc )] #![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] #![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index f7d022aa0d8..f05c44b9124 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -36,7 +36,7 @@ use wasmer_runtime_core::vm::Ctx; /// the fd value of the virtual root pub const VIRTUAL_ROOT_FD: __wasi_fd_t = 3; /// all the rights enabled -pub const ALL_RIGHTS: __wasi_rights_t = 0x1FFFFFFF; +pub const ALL_RIGHTS: __wasi_rights_t = 0x1FFF_FFFF; const STDIN_DEFAULT_RIGHTS: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC | __WASI_RIGHT_FD_READ | __WASI_RIGHT_FD_SYNC @@ -52,7 +52,10 @@ const STDOUT_DEFAULT_RIGHTS: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC const STDERR_DEFAULT_RIGHTS: __wasi_rights_t = STDOUT_DEFAULT_RIGHTS; /// Get WasiState from a Ctx -/// This function is unsafe because it must be called on a WASI Ctx +/// +/// # Safety +/// - This function must be called on a `Ctx` that was created with `WasiState` +/// in the data field pub unsafe fn get_wasi_state(ctx: &mut Ctx) -> &mut WasiState { &mut *(ctx.data as *mut WasiState) } @@ -186,7 +189,7 @@ impl WasiFs { for dir in preopened_dirs { debug!("Attempting to preopen {}", &dir.to_string_lossy()); // TODO: think about this - let default_rights = 0x1FFFFFFF; // all rights + let default_rights = ALL_RIGHTS; let cur_dir_metadata = dir.metadata().map_err(|e| { format!( "Could not get metadata for file {:?}: {}", @@ -236,7 +239,7 @@ impl WasiFs { for (alias, real_dir) in mapped_dirs { debug!("Attempting to open {:?} at {}", real_dir, alias); // TODO: think about this - let default_rights = 0x1FFFFFFF; // all rights + let default_rights = ALL_RIGHTS; let cur_dir_metadata = real_dir.metadata().map_err(|e| { format!( "Could not get metadata for file {:?}: {}", @@ -428,7 +431,7 @@ impl WasiFs { // create virtual root let root_inode = { - let all_rights = 0x1FFFFFFF; + let all_rights = ALL_RIGHTS; // TODO: make this a list of positive rigths instead of negative ones // root gets all right for now let root_rights = all_rights @@ -525,10 +528,15 @@ impl WasiFs { next } - /// like create dir all, but it also opens it + /// This function is like create dir all, but it also opens it. /// Function is unsafe because it may break invariants and hasn't been tested. /// This is an experimental function and may be removed - // dead code because this is an API for external use + /// + /// # Safety + /// - Virtual directories created with this function must not conflict with + /// the standard operation of the WASI filesystem. This is vague and + /// unlikely in pratice. Join the discussion at https://github.com/wasmerio/wasmer/issues/1219 + /// for what the newer, safer WASI FS APIs should look like. #[allow(dead_code)] pub unsafe fn open_dir_all( &mut self, @@ -1161,7 +1169,7 @@ impl WasiFs { stat.st_ino = self.get_next_inode_index(); Ok(self.inodes.insert(InodeVal { - stat: stat, + stat, is_preopened, name, kind, @@ -1210,10 +1218,14 @@ impl WasiFs { Ok(idx) } - /// This function is unsafe because it's the caller's responsibility to ensure that - /// all refences to the given inode have been removed from the filesystem + /// Low level function to remove an inode, that is it deletes the WASI FS's + /// knowledge of a file. + /// + /// This function returns the inode if it existed and was removed. /// - /// returns the inode if it existed and was removed + /// # Safety + /// - The caller must ensure that all references to the specified inode have + /// been removed from the filesystem. pub unsafe fn remove_inode(&mut self, inode: Inode) -> Option { self.inodes.remove(inode) } diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index b352669c7de..3308887de86 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -434,7 +434,7 @@ impl<'de> Deserialize<'de> for HostFile { } } - const FIELDS: &'static [&'static str] = &["host_path", "flags"]; + const FIELDS: &[&str] = &["host_path", "flags"]; deserializer.deserialize_struct("HostFile", FIELDS, HostFileVisitor) } } diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index 5f5559ce497..9890fb76db3 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -1,4 +1,4 @@ -#![allow(non_camel_case_types)] +#![allow(non_camel_case_types, clippy::identity_op)] use crate::ptr::{Array, WasmPtr}; use byteorder::{ReadBytesExt, WriteBytesExt, LE}; diff --git a/lib/wasi/src/utils.rs b/lib/wasi/src/utils.rs index 8a72a215a43..f1395ced2a9 100644 --- a/lib/wasi/src/utils.rs +++ b/lib/wasi/src/utils.rs @@ -30,10 +30,10 @@ pub enum WasiVersion { } /// Namespace for the `Snapshot0` version. -const SNAPSHOT0_NAMESPACE: &'static str = "wasi_unstable"; +const SNAPSHOT0_NAMESPACE: &str = "wasi_unstable"; /// Namespace for the `Snapshot1` version. -const SNAPSHOT1_NAMESPACE: &'static str = "wasi_snapshot_preview1"; +const SNAPSHOT1_NAMESPACE: &str = "wasi_snapshot_preview1"; /// Detect the version of WASI being used based on the import /// namespaces.